В модуле sale выбор API зависит не только от данных, но и от действия над ними. Один заказ объединяет корзину, свойства, оплаты, отгрузки, скидки, статусы и историю. Если изменить только одну запись через ORM, связанные объекты могут остаться в несогласованном состоянии.
Для нового кода используйте объектную модель заказа, ORM-классы, менеджеры сервисов и обработчики D7. Классический API нужен для отдельных операций, у которых нет полной замены, и для поддержки существующего проекта.
Перед работой подключите модули:
-
sale— для работы с API интернет-магазина, -
currency— для сценариев с валютами, -
catalog— для товаров, цен, остатков, резервирования и складских операций.
Связи объектов и поля ORM-классов описаны в материале Схема работы интернет-магазина и основные объекты.
Как выбрать между объектной моделью, ORM и менеджерами
Сначала определите, нужен ли сценарию жизненный цикл объекта, выбор настроенного сервиса или только сохраненные данные. Затем выберите уровень API.
|
|| Задача | Метод | Пояснение ||
|| Создать или изменить корзину, заказ, оплату либо отгрузку | Объектная модель Bitrix\Sale | Проверяет данные, сохраняет связанные коллекции и запускает события. При сохранении заказа участвует в записи истории ||
|| Получить список, отфильтровать строки или рассчитать агрегаты | ORM-классы Bitrix\Sale\Internals\*Table | Формируют выборку без загрузки полного объекта заказа ||
|| Прочитать или изменить отдельную настройку | Профильный ORM-класс или менеджер | Работает с настройками типов плательщиков, свойств, статусов, компаний и других объектов вне жизненного цикла заказа ||
|| Выбрать платежную систему, службу доставки, кассу или компанию | Профильный Manager | Возвращает настроенный сервис и учитывает его ограничения ||
|| Рассчитать заказ, скидки, доставку или доступность сервиса | Объектная модель и профильный сервис | Расчет зависит от состава заказа, покупателя, сайта, типа плательщика и настроек ||
|| Поддержать существующий код или выполнить операцию без полного D7-аналога | Классический API CSale* | Сохраняет совместимость или предоставляет специальную операцию, которой нет на другом уровне API ||
|#
Объектная модель
Объектная модель подходит для операций, которые меняют состояние покупки. Основные классы объектной модели:
-
Bitrix\Sale\Basket— корзина, -
Bitrix\Sale\Order— заказ, -
Bitrix\Sale\PaymentCollection— коллекция оплат, -
Bitrix\Sale\ShipmentCollection— коллекция отгрузок.
Оплату и отгрузку создавайте через коллекции заказа, а не как независимые записи.
Способ сохранения корзины зависит от ее связи с заказом. Если корзина еще не привязана к заказу, вызовите Basket::save(). Корзину сохраненного заказа отдельно не сохраняйте: вызовите Order::save().
Оба метода возвращают объект Bitrix\Sale\Result. Проверьте его после сохранения: успешное изменение отдельного поля еще не означает, что все данные записаны.
ORM-классы
ORM подходит для списков, отчетов, фильтрации, сортировки и агрегатов. Для основных данных заказа используйте классы:
-
Bitrix\Sale\Internals\OrderTable— для заказов, -
Bitrix\Sale\Internals\BasketTable— для позиций корзины, -
Bitrix\Sale\Internals\PaymentTable— для оплат, -
Bitrix\Sale\Internals\ShipmentTable— для отгрузок.
Не изменяйте через эти классы заказ и его коллекции. ORM-запись не выполняет полный жизненный цикл объектной модели и не согласует корзину, оплаты, отгрузки, свойства, скидки и историю.
Ограничение не означает, что все ORM-классы модуля доступны только для чтения. Настройки и справочники можно создавать через профильные таблицы, если такой сценарий описан для конкретного класса. Например, PersonTypeTable используют для типов плательщиков, OrderPropsTable — для настроек свойств заказа, StatusTable — для статусов. Эти классы применяют, когда магазин настраивают через код.
Менеджеры и сервисы
Менеджер выбирает настроенную реализацию и применяет правила подсистемы. Используйте:
-
Bitrix\Sale\Delivery\Services\Manager— для служб доставки, -
Bitrix\Sale\PaySystem\Manager— для платежных систем, -
Bitrix\Sale\Cashbox\ManagerиBitrix\Sale\Cashbox\CheckManager— для касс и чеков, -
Bitrix\Sale\Services\Company\Manager— для компаний, -
Bitrix\Sale\DiscountCouponsManager— для купонов в расчете корзины или заказа.
Не выбирайте сервис только по идентификатору из пользовательского запроса. Сначала получите доступные варианты с учетом сайта, типа плательщика, суммы, состава заказа и режима ограничений.
Классический API
К классическому API относятся классы CSale*. Проверяйте назначение каждого класса отдельно. Одни классы устарели целиком, другие продолжают предоставлять отдельные актуальные операции.
Для нового сценария сначала ищите объектную модель, ORM-класс или менеджер. Обращайтесь к классическому API, если для конкретной операции нет полной замены в D7.
Корзина и заказ
Корзина хранит выбранные товары, а заказ объединяет корзину, свойства, оплаты и отгрузки. Для чтения или изменения одного заказа загрузите объект Bitrix\Sale\Order. Для списка сохраненных полей используйте ORM.
|
|| Задача | Метод | Пояснение ||
|| Создать пустую корзину | Bitrix\Sale\Basket::create() | Для нового кода не используйте CSaleBasket::Add() ||
|| Загрузить корзину покупателя | Basket::loadItemsForFUser() и Bitrix\Sale\Fuser | Методы загружают корзину по идентификатору покупателя без самостоятельного запроса к строкам корзины ||
|| Добавить товар каталога | Bitrix\Catalog\Product\Basket::addProductToBasket() | Метод заполняет рассчитанные поля товара через провайдера ||
|| Изменить позицию корзины | BasketItem::setField(), затем Basket::save() для самостоятельной корзины или Order::save() для корзины сохраненного заказа | Не обновляйте позицию через BasketTable::update() и не сохраняйте корзину заказа отдельно ||
|| Прочитать позиции для отчета | Bitrix\Sale\Internals\BasketTable::getList() | ORM-выборка не требует загружать каждую корзину в цикле ||
|| Создать заказ | Bitrix\Sale\Order::create(), коллекции заказа и Order::save() | Для нового кода не используйте CSaleOrder::Add() и прямые записи в таблицы ||
|| Загрузить и изменить заказ | Order::load(), методы заказа и его коллекций | Не изменяйте заказ через OrderTable::update() ||
|| Отменить или физически удалить заказ | Поле CANCELED и Order::save() для обычного отказа, Order::delete() только для служебного удаления | Не используйте физическое удаление для отмены покупки ||
|| Получить список заказов | Bitrix\Sale\Internals\OrderTable::getList() | Не загружайте каждый заказ через Order::load(), если нужны только сохраненные поля ||
|| Проверить права на конкретный заказ | Методы CSaleOrder::CanUser*() для нужной операции | Уровень доступа к модулю и статус заказа не заменяют проверку права на конкретную операцию ||
|#
При изменении заказа соблюдайте последовательность действий:
-
Проверьте права.
-
Загрузите объект.
-
Измените профильную коллекцию.
-
Пересчитайте зависимые данные.
-
Сохраните заказ.
Не запускайте save() повторно без разбора ошибок и предупреждений. Повтор без условий из обработчика события может вызвать рекурсию.
Настройки и свойства заказа
Настройки существуют независимо от конкретного заказа. Для них допустима запись через профильные ORM-классы. Значения свойств внутри заказа изменяйте через коллекцию загруженного объекта.
|
|| Задача | Метод | Пояснение ||
|| Создать или найти тип плательщика | Bitrix\Sale\Internals\PersonTypeTable, Bitrix\Sale\PersonType | Настройка должна быть связана с нужным сайтом ||
|| Создать группу или описание свойства заказа | Bitrix\Sale\Internals\OrderPropsGroupTable, OrderPropsTable | Классы работают с настройкой поля, не со значением в заказе ||
|| Заполнить или изменить значение свойства заказа | Order::getPropertyCollection() и объект значения | Изменения сохраняются вместе с заказом ||
|| Создать статус и локализованное название | Bitrix\Sale\Internals\StatusTable и StatusLangTable | Тип статуса заказа и отгрузки задавайте явно ||
|| Получить разрешенные статусы | Bitrix\Sale\OrderStatus или Bitrix\Sale\DeliveryStatus | Выберите класс по типу объекта ||
|| Создать или изменить профиль покупателя | CSaleOrderUserProps::DoSaveUserProfile() | D7-сценария с сопоставлением значений нет ||
|| Удалить профиль покупателя | CSaleOrderUserProps::Delete() | Метод удаляет профиль вместе со связанными значениями ||
|#
Не переносите значение свойства между типами плательщика только по его идентификатору. Сначала найдите настройку по роли или символьному коду в контексте выбранного типа плательщика.
Оплаты, доставки и кассы
Оплата и отгрузка принадлежат заказу, а платежная система, служба доставки и касса являются настроенными сервисами. Поэтому для одного сценария нужны и объектная модель, и профильный менеджер.
|
|| Задача | Метод | Пояснение ||
|| Создать оплату | Order::getPaymentCollection()->createItem() | Оплата сохраняется в составе заказа ||
|| Выбрать доступную платежную систему | Bitrix\Sale\PaySystem\Manager::getListWithRestrictions() | Учитывает ограничения для оплаты и заказа ||
|| Получить настроенную платежную систему | PaySystem\Manager::getObjectById() | Возвращает объект сервиса для запуска обработчика ||
|| Создать обработчик платежной системы | Наследник PaySystem\ServiceHandler или BaseServiceHandler | Работает в механизме платежных систем D7 ||
|| Создать отгрузку | Order::getShipmentCollection()->createItem() | Отгрузка сохраняется вместе с позициями и заказом ||
|| Выбрать доступную службу доставки | Delivery\Services\Manager::getRestrictedList() | Учитывает ограничения службы для отгрузки ||
|| Рассчитать доставку | Объект службы из Delivery\Services\Manager и объект отгрузки | Расчет зависит от состава и адресных данных заказа ||
|| Создать обработчик доставки | Наследник Bitrix\Sale\Delivery\Services\Base | Регистрируется как служба доставки нового типа ||
|| Работать с транспортной заявкой | Bitrix\Sale\Delivery\Requests\Manager и обработчик доставки | Связывает заявку с отгрузками и внешней службой ||
|| Получить доступную кассу | Bitrix\Sale\Cashbox\Manager | Учитывает ограничения кассы, оплаты, отгрузки и компании ||
|| Создать или перепечатать чек | Bitrix\Sale\Cashbox\CheckManager | Управляет жизненным циклом чека и результатом печати ||
|#
Не создавайте оплату или отгрузку прямой записью через PaymentTable и ShipmentTable. Для отчетов эти классы подходят, но для изменения состояния нужны коллекции заказа и профильные методы.
Скидки, купоны и резервирование
Расчетные подсистемы используют состояние корзины или заказа. Запись одной итоговой суммы не заменяет расчет правил, работу провайдера товара или изменение отгрузки.
|
|| Задача | Метод | Пояснение ||
|| Рассчитать правила для самостоятельной корзины без заказа | Bitrix\Sale\Discount::buildFromBasket(), calculate() и Basket::applyDiscount() | Не вызывайте buildFromBasket() для корзины заказа; сохраненный заказ пересчитывайте через Order::doFinalAction(true) ||
|| Пересчитать сохраненный заказ | Order::doFinalAction(true) | Выполняйте после изменений, которые влияют на стоимость ||
|| Передать купон в расчет | Bitrix\Sale\DiscountCouponsManager | Принятый код купона еще не подтверждает применение скидки ||
|| Создать правило работы с корзиной | CSaleDiscount::Add() | У сценария настройки правила нет полной замены в D7 ||
|| Зарезервировать или списать товар | Объекты заказа, отгрузки и сервисы Bitrix\Sale\Reservation | Операция связана с количеством в отгрузке и провайдером каталога ||
|| Провести складское движение | API складских документов модуля catalog | Заказ и отгрузка не заменяют складской документ ||
|#
Не рассчитывайте скидку вручную и не записывайте итоговую стоимость напрямую. Ручной расчет может не учесть купоны, группы пользователя, доставку, округление и порядок применения правил.
Местоположения, покупатели и связанные данные
Для справочников и статистики выбирайте профильный класс. Если данные участвуют в оформлении заказа, передайте их в объектную модель до расчета.
|
|| Задача | Метод | Пояснение ||
|| Найти местоположение | ORM-классы пространства имен Bitrix\Sale\Location | Используйте код или идентификатор конкретного адресного объекта ||
|| Передать адрес в расчет доставки | Коллекция свойств заказа и менеджер доставки | Значение свойства должно соответствовать типу и настройке местоположения ||
|| Получить общую сумму налога заказа | Order::getTaxValue() | Возвращает итог для объекта заказа ||
|| Прочитать сохраненные строки налогов | CSaleOrderTax | Полного D7-сценария для детализации нет ||
|| Связать корзину с покупателем | Bitrix\Sale\Fuser | Идентификатор покупателя корзины отличается от идентификатора пользователя ||
|| Получить показатели покупателя | Bitrix\Sale\BuyerStatistic | Возвращает сохраненную статистику по пользователю, сайту и валюте ||
|| Прочитать или изменить внутренний счет | CSaleUserAccount для баланса, Bitrix\Sale\Internals\UserTransactTable для чтения журнала | У D7 нет класса для безопасного изменения баланса. Передавайте в UpdateAccount() изменение суммы, а не новый итог ||
|#
Статусы, события и история
Статус меняйте в объектной модели после проверки разрешенной операции. Для реакции на жизненный цикл используйте события, а сохраненную историю читайте через профильный API.
|
|| Задача | Метод | Пояснение ||
|| Изменить статус заказа | OrderStatus, проверка CSaleOrder::CanUserChangeOrderStatus() и объект Order | Матрица статусов не заменяет проверку доступа к конкретному заказу ||
|| Изменить статус отгрузки | DeliveryStatus и объект Shipment | Не смешивайте статус отгрузки с признаками разрешения и списания ||
|| Подписаться на жизненный цикл заказа | Bitrix\Main\EventManager и события OnSale* | Выберите событие по моменту: до изменения, до сохранения или после полного сохранения ||
|| Прочитать журнал изменений | CSaleOrderChange | Класс Bitrix\Sale\OrderHistory формирует историю, но чтение журнала выполняет классический API ||
|| Отправить штатное уведомление | Настройки почтовых событий и жизненный цикл заказа | Не дублируйте штатное письмо собственным обработчиком ||
|#
Обработчик события не должен повторно сохранять тот же объект. Если повторное сохранение необходимо, защитите обработчик от рекурсии и повторной обработки одного заказа.
Отчеты, архив и интеграции
Эти сценарии чаще читают много заказов, поэтому полная загрузка объектной модели нужна не всегда.
|
|| Задача | Метод | Пояснение ||
|| Построить список или агрегат | OrderTable, BasketTable, PaymentTable, ShipmentTable | ORM-выборка подходит только для чтения ||
|| Прочитать архивный заказ или восстановить архивный объект в памяти | Bitrix\Sale\Archive\Manager | Метод returnArchivedOrder() возвращает объект только для чтения; не сохраняйте его как активный заказ ||
|| Подготовить печатную форму | Bitrix\Sale\Order, связанные коллекции и PHP-шаблон | ORM подходит для предварительного поиска, но не заменяет объект заказа с полным набором данных формы ||
|| Настроить штатный обмен с 1С | Компонент bitrix:sale.export.1c и настройки обмена | Не копируйте внутреннюю реализацию компонента в собственный модуль ||
|| Реализовать собственную интеграцию | OrderTable для пакетного отбора идентификаторов и простых сохраненных полей; Order для полного сообщения и изменений | Загружайте объект заказа непосредственно перед чтением связанных коллекций или записью; храните внешний идентификатор и состояние обмена ||
|| Оформить заказ в публичной части | Компонент bitrix:sale.order.ajax для типовой формы, объектная модель для собственной бизнес-логики | Не меняйте внутренние шаги компонента прямой записью в таблицы ||
|#
При пакетной обработке читайте строки частями с устойчивой сортировкой. ORM может предварительно выбрать кандидатов, но не заменяет объект заказа, если сообщение содержит корзину, свойства, оплаты или отгрузки. Перед формированием полного сообщения или записью заново загрузите заказ и проверьте его актуальное состояние. Такой порядок уменьшает риск передать устаревшие данные или перезаписать изменения параллельного процесса.
Сводная таблица классического API
К классическому API относятся классы CSale*. Для многих задач нового кода есть подход D7, но отдельные операции по-прежнему выполняют через классический API. Таблица помогает выбрать актуальный подход для конкретной задачи.
Внимание
При поддержке существующего кода учитывайте события, проверки прав и побочные эффекты текущего сценария. Если операция переносится на D7, заменяйте один вызов за раз и проверяйте сохраненный заказ и связанные объекты.
|
|| Задача | Классический API | Актуальный подход ||
|| Работать с корзиной | CSaleBasket | Bitrix\Sale\Basket, BasketItem, Fuser, Bitrix\Catalog\Product\Basket ||
|| Создать или изменить заказ | CSaleOrder | Bitrix\Sale\Order и коллекции заказа; методы CSaleOrder::CanUser*() остаются для проверки прав ||
|| Настроить платежную систему | CSalePaySystem, CSalePaySystemAction | Bitrix\Sale\PaySystem\Manager, Service, ServiceHandler ||
|| Настроить службу доставки | CSaleDelivery, CSaleDeliveryHandler | Bitrix\Sale\Delivery\Services\Manager и наследник Services\Base ||
|| Создать статус или проверить переход | CSaleStatus | Bitrix\Sale\Internals\StatusTable для настройки, OrderStatus и DeliveryStatus для разрешенных переходов ||
|| Создать или найти тип плательщика | CSalePersonType | Bitrix\Sale\Internals\PersonTypeTable и Bitrix\Sale\PersonType; тип плательщика нужно связать с сайтом ||
|| Настроить свойства заказа | CSaleOrderProps | OrderPropsTable для настроек и коллекция свойств объекта заказа для значений ||
|| Рассчитать или настроить правило корзины | CSaleDiscount | Bitrix\Sale\Discount и DiscountCouponsManager для расчета; для создания и чтения настроек правила полный D7-сценарий отсутствует ||
|| Создать, изменить или удалить профиль | CSaleOrderUserProps | ORM-классы профилей для чтения; DoSaveUserProfile() и Delete() сохраняют целостность связанных значений ||
|| Получить налог заказа | CSaleOrderTax | Order::getTaxValue() возвращает общую сумму; строки сохраненного налога читает классический API ||
|| Прочитать журнал изменений | CSaleOrderChange | Полной замены для чтения журнала нет; события D7 используют для реакции на новые изменения ||
|| Настроить обмен с 1С | CSaleExport, CSaleOrderLoader | Штатный компонент обмена или собственная интеграция на Order; классические классы остаются частью поддерживаемого штатного обмена ||
|#
Типичные ошибки выбора API
Ошибки выбора уровня API часто не приводят к синтаксическому сбою. Код выполняется, но пропускает часть жизненного цикла или получает неполные данные.
Выбирайте API по задаче:
-
объектная модель выполняет жизненный цикл покупки,
-
ORM читает данные массово,
-
менеджер выбирает настроенный сервис,
-
классический API выполняет операции без полной замены в D7.
После операции проверяйте результат:
-
после записи — объект
Result, -
расчета — итоговые значения и состояния,
-
пакетного чтения — полноту активных и архивных данных.
|
|| Ошибка | Почему возникает проблема | Как делать ||
|| Изменять заказ через OrderTable::update() | Не согласуются коллекции, расчеты, события и история | Загрузить Order, изменить профильный объект и вызвать save() ||
|| Создавать оплату или отгрузку отдельной ORM-записью | Объект не включается корректно в коллекцию заказа | Создать элемент через коллекцию загруженного заказа ||
|| Загружать каждый заказ через Order::load() для простого списка | Полная объектная модель создает лишнюю нагрузку | Выбрать нужные поля одним ORM-запросом ||
|| Менять справочник только через объект заказа | Настройка существует независимо от заказа | Использовать профильный ORM-класс или менеджер ||
|| Выбирать сервис только методом getObjectById() | Идентификатор не подтверждает доступность для текущего заказа | Сначала получить список с ограничениями, затем загрузить выбранный сервис ||
|| Считать успешное добавление купона подтверждением скидки | Купон может быть принят, но не примениться к условиям заказа | Пересчитать объект и проверить статус купона ||
|| Проверять только статус перед изменением заказа | Статус не подтверждает права на сайт и конкретную операцию | Вызвать профильный метод проверки прав для заказа ||
|| Смешивать активные и архивные заказы в одном источнике | После архивирования заказ исчезает из активных ORM-таблиц | Прочитать активные и архивные данные отдельно и объединить по явному правилу ||
|| Повторять save() при любой ошибке | Причина может сохраниться, а обработчики запустятся повторно | Разобрать результат, загрузить актуальное состояние и решить, допустим ли повтор ||
|| Переписать классический вызов без проверки границы замены | У метода может не быть полного D7-аналога | Сверить конкретную операцию со сводной таблицей и профильной статьей ||
|#