Перейти к содержанию

В модуле 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*() для нужной операции | Уровень доступа к модулю и статус заказа не заменяют проверку права на конкретную операцию || |#

При изменении заказа соблюдайте последовательность действий:

  1. Проверьте права.

  2. Загрузите объект.

  3. Измените профильную коллекцию.

  4. Пересчитайте зависимые данные.

  5. Сохраните заказ.

Не запускайте 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-аналога | Сверить конкретную операцию со сводной таблицей и профильной статьей || |#