Loading...
Loading...
Covers Sale module — API choice (D7 object model vs ORM vs CSale*), FUSER, Basket, Order create/update, properties, statuses and events, payments, delivery/shipments, discounts and coupons, reservation/deduction, permissions, buyer accounts. Applied for cart/checkout, order lifecycle, pay/ship integration, and order automation. Key terms — sale, Basket, Order, Fuser, Payment, Shipment, PaySystem\Manager, Delivery\Services\Manager, doFinalAction, STATUS_ID, DiscountCouponsManager, tryReserve, CanUserUpdateOrder.
npx skill4agent add bxmaximum/bitrix-framework-skills bitrix-salesalesalecatalogiblock\Bitrix\Main\Loader::includeModule('sale');
\Bitrix\Main\Loader::includeModule('catalog'); // products, prices, stock, reservation| Task | API |
|---|---|
| Create/change basket, order, payment, shipment | Object model |
| Lists, reports, aggregates | ORM |
| Settings/dictionaries via code | Profile ORM: |
| Pick a configured service | Managers: |
| Operations with no full D7 replacement | Legacy |
OrderTable::update()Order::load()OrderTable::getList()Order::getList()CSale*OrderBitrix\Sale\FuserUSER_ID$fuserId = Fuser::getId(); // creates if missing
$fuserId = Fuser::getId(true); // skip create → null if none
$fuserId = Fuser::getIdByUserId($userId); // false if cannot resolve/createUSER_IDFUSER_ID<?php declare(strict_types=1);
use Bitrix\Catalog\Product\Basket as CatalogBasket;
use Bitrix\Sale\Basket;
use Bitrix\Sale\Basket\RefreshFactory;
use Bitrix\Sale\Fuser;
$basket = Basket::loadItemsForFUser(Fuser::getId(), $siteId); // only rows with ORDER_ID = null
// Preferred for catalog products: sets module, provider, and product data itself
$r = CatalogBasket::addProductToBasket($basket, ['PRODUCT_ID' => $productId, 'QUANTITY' => 1], ['SITE_ID' => $siteId]);
// merges into an existing row by default; pass ['USE_MERGE' => 'N'] as 4th arg for a separate row
// Manual alternative:
$item = $basket->createItem('catalog', $productId);
$item->setFields(['QUANTITY' => 1, 'PRODUCT_PROVIDER_CLASS' => CatalogBasket::getDefaultProviderName()]);
$basket->refresh(RefreshFactory::createSingle($item->getBasketCode())); // provider fills PRICE/CURRENCY/NAME/VAT/weight
$result = $basket->save(); // only for a basket NOT bound to an orderPRICECURRENCYCUSTOM_PRICE => 'Y'PRICECURRENCYPRODUCT_IDBitrix\Catalog\ProductTable$order->getBasket()Order::save()loadItemsForFUser()$basket->save()$basket->refresh()refreshData()$basket->getOrderableItems()$item->getPropertyCollection()->createItem()redefine()getPrice()getBasePrice()getPriceWithVat()getDiscountPrice()Discount::buildFromBasket($basket, new Discount\Context\Fuser($basket->getFUserId()))calculate()$basket->applyDiscount($data['BASKET_ITEMS'])doFinalAction(true)save()<?php declare(strict_types=1);
use Bitrix\Sale\Delivery\Services\Manager as DeliveryManager;
use Bitrix\Sale\Order;
use Bitrix\Sale\PaySystem\Manager as PaySystemManager;
use Bitrix\Sale\Services\Base\RestrictionManager;
$order = Order::create($siteId, $userId); // currency: site's, else base
$order->setPersonTypeId($personTypeId); // BEFORE getPropertyCollection(); not validated vs site
$order->setBasket($orderableBasket); // new (unsaved) order only
// Properties (set depends on person type)
$prop = $order->getPropertyCollection()->getItemByOrderPropertyCode('PHONE');
$prop?->setValue($phone); // each setValue returns Result
// Shipment: create user shipment, bind basket items, pick allowed delivery
$shipment = $order->getShipmentCollection()->createItem(); // system shipment exists already — never assign it a service
foreach ($order->getBasket() as $basketItem) {
$shipmentItem = $shipment->getShipmentItemCollection()->createItem($basketItem);
$shipmentItem->setQuantity($basketItem->getQuantity());
}
$deliveries = DeliveryManager::getRestrictedObjectsList($shipment);
$shipment->setDeliveryService($deliveries[$deliveryId] ?? throw new \RuntimeException('delivery unavailable'));
$order->getShipmentCollection()->calculateDelivery();
// Payment: create, preliminary SUM, pick allowed pay system
$payment = $order->getPaymentCollection()->createItem();
$payment->setField('SUM', $order->getPrice());
$allowed = PaySystemManager::getListWithRestrictions($payment, RestrictionManager::MODE_CLIENT);
isset($allowed[$paySystemId]) or throw new \RuntimeException('pay system unavailable');
$payment->setPaySystemService(PaySystemManager::getObjectById($paySystemId));
$order->doFinalAction(true); // discounts, taxes, totals — check Result
$payment->setField('SUM', $order->getPrice()); // sync after final calc
// re-check getRestrictedObjectsList / getListWithRestrictions here — totals may change availability
$saveResult = $order->save(); // check isSuccess() AND getWarningMessages()
$orderId = $saveResult->getId();XML_IDOrder::load($id)Order::loadByAccountNumber($number)Order::loadByFilter([...])Order::lock()/isLocked()/unlock()| Change | calculateDelivery | doFinalAction(true) | sync unpaid payments SUM |
|---|---|---|---|
| Status, cancel, mark, comment, tracking, allow-delivery | – | – | – |
| Location/address in restrictions | yes | yes | if price changed |
| Basket items/quantity; delivery service/cost; shipment removal | yes | yes | if price changed |
| Coupon/discount/tax data | if delivery affected | yes | if price changed |
ShipmentItem::setQuantity()BasketItem::setField('QUANTITY')refreshsetField('CANCELED', 'Y')REASON_CANCELEDOrder::delete()PERSON_TYPE_IDCURRENCYUSER_IDsetField()SUM_PAIDPAYEDOrderPropsTableENTITY_TYPEPropertyValueCollectionOrderPropsGroupTable::add()OrderPropsTable::add()getItemByOrderPropertyCode()getItemByOrderPropertyId()getDeliveryLocation()getGroups()LOCATIONENUMVALUE$propertyValue->getPropertyObject()->getOptions()MULTIPLE=YPropertyValueCollection::setValuesFromPost($_POST, $_FILES)verify()isRequired()checkRequiredValue()Order::save()OrderPropsValueTableSTATUS_IDNFBitrix\Sale\OrderStatusSTATUS_IDDNDFDeliveryStatusStatusTableTYPE_ORDERTYPE_SHIPMENTStatusLangTableOrderStatus::getAllowedUserStatuses($userId, $currentStatusId)getStatusesUserCanDoOperations()canGroupDoOperations()viewupdatedeletecancelmarkpaymentdeliverydeductionfromtoCSaleOrderCanUserViewOrder()CanUserUpdateOrder()0, $groups, $siteIdCanUserCancelOrder()CanUserChangeOrderStatus()CanUserChangeOrderFlag($id, 'PERM_PAYMENT'|'PERM_DELIVERY'|'PERM_DEDUCTION', $groups)CanUserDeleteOrder()Order::load()DPUWOrderHistoryCSaleOrderChange::GetList()@TYPE => ['ORDER_STATUS_CHANGED', ...]EventManagerinit.phpOnSaleOrderBeforeSavedOnSaleOrderSavedIS_NEWIS_CHANGEDOnSaleStatusOrderChangeVALUEOLD_VALUEOnSaleOrderPaidOnSaleOrderCanceledOnSaleStatusShipmentChangeOnShipmentDeductedOnShipmentAllowDeliveryOnShipmentTrackingNumberChangeOn[Before]Sale{BasketItem,Payment,Shipment,ShipmentItem,PropertyValue}SetFieldOnSale*EntitySavedOnSaleBasketItemBeforeSaved/SavedOnSaleBasketItemRefreshDataOn{Before,After}SaleOrderFinalAction$order->save()OnSaleOrderSavedOnSaleOrderBeforeSavedOnBefore*EventResult::ERRORsetField()save()getPaymentCollection()->createItem($service)PaySystem\Manager::getListWithRestrictions($payment, MODE_CLIENT|MODE_MANAGER)getListWithRestrictionsByOrder()$payment->getPaySystem()->initiatePay($payment, $request, BaseServiceHandler::STRING)ServiceResultgetTemplate()getPaymentUrl()$payment->setPaid('Y')$payment->setReturn(Payment::RETURN_PS|RETURN_INNER|RETURN_NONE)Service::refund($payment, $sum)IRefundIRecurringisRecurring()/repeatRecurrent()PaySystem\Manager::getInnerPaySystemId()Payment::isInner()CSaleUserAccount::GetByUserID()UpdateAccount($userId, $delta, ...)Internals\UserTransactTableBitrix\Sale\BuyerStatistic/local/php_interface/include/sale_payment/<code>/handler.phpPaySystem\ServiceHandler.description.phptemplate//bitrix/modules/sale/payment//bitrix/tools/sale_ps_result.phpServices\Base\RestrictiononSalePaySystemRestrictionsClassNamesBuildListDelivery\Services\Manager::getRestrictedObjectsList($shipment)getRestrictedList($shipment, Restrictions\Manager::MODE_CLIENT)getObjectById()ShipmentCollection::calculateDelivery()CUSTOM_PRICE_DELIVERY='Y'Manager::calculateDeliveryPrice($shipment, $deliveryId, $extraServices)CalculationResultisSystem()getBasketItemDistributedQuantity()allowDelivery()disallowDelivery()setField('DEDUCTED', 'Y')TRACKING_NUMBERsetStoreId()Delivery\Services\BasecalculateConcrete()getConfigStructure()onSaleDeliveryHandlersClassNamesBuildListManager::add()onSaleDeliveryRestrictionsClassNamesBuildListDelivery\ExtraServices\*Shipment::setExtraServices()CSaleDiscount::Add()/Update()CONDITIONSACTIONSPRIORITYSORTLAST_DISCOUNTInternals\DiscountTable::delete()Discount::buildFromBasket()calculate()applyDiscount()Order::doFinalAction(true)buildFromBasket()DiscountCouponsManager::init(MODE_CLIENT|MODE_MANAGER|MODE_ORDER [, userId/orderId])add($code)add() === trueget(true, ['COUPON' => $code], true, true)STATUS === STATUS_APPLYEDInternals\DiscountCouponTableTYPE_ONE_ORDERTYPE_MULTI_ORDERMAX_USE$order->getDiscount()->getApplyResult()OrderDiscount::loadResultFromDb($orderId)Internals\OrderRulesTableShipment::tryReserve()tryUnreserve()isReserved()BasketItem::getReserveQuantityCollection()create()setStoreId()setQuantity()Order::save()RESERVED*Shipment::setField('DEDUCTED', 'Y')StoreProductTable.AMOUNT/QUANTITY_RESERVEDSale\Configuration::getProductReservationCondition()ReserveCondition::ON_CREATE|ON_PAY|ON_FULL_PAY|ON_ALLOW_DELIVERY|ON_SHIPgetProductReserveClearPeriod()Helpers\ReservedProductCleanerReservation\BasketReservationService::getAvailableCountForBasketItem()/ForOrder()selectORDER_IDPAYEDDEDUCTEDBitrix\Sale\Archive\Manager::getList()/getById()returnArchivedOrder()doFinalAction(true)save()Result::isSuccess()getWarningMessages()salecontrollers.restIntegrationbitrix-controllersbitrix:sale.order.ajaxsalecatalogFuseraddProductToBasketrefreshdoFinalAction(true)save()CSaleOrder::CanUser*getAllowedUserStatusesCANCELED='Y'Order::delete()STATUS_APPLYEDadd()Resultsave()OnSaleOrderSavedbitrix-catalogbitrix-iblocksbitrix-result-and-errorsbitrix-eventsbitrix-controllersbitrix-service-locator