Событие OnSaleOrderSaved — основная точка расширения модуля sale в новом ядре D7. Оно срабатывает после успешной записи заказа и даёт готовый объект \Bitrix\Sale\Order для интеграций, уведомлений и синхронизаций.
Место события в жизненном цикле заказа
В D7-ядре сохранение заказа проходит через метод $order->save(), который последовательно генерирует несколько событий. OnSaleOrderSaved вызывается после фиксации заказа в базе, когда все связанные сущности (корзина, отгрузки, оплаты) уже записаны.
Соседние события помогают выбрать правильную точку подключения:
| Событие | Момент | Отмена |
|---|---|---|
OnSaleOrderBeforeSaved | До записи | Да, через EventResult |
OnSaleOrderSaved | После записи | Нет |
OnSaleStatusOrderChange | При смене статуса | Нет |
OnSaleOrderPaid | При отметке оплаты | Нет |
\Bitrix\Main\Event, а не массив по ссылке. Отменить сохранение здесь уже нельзя — для проверок используйте OnSaleOrderBeforeSaved.Регистрация обработчика
Регистрируем обработчик в /local/php_interface/init.php через EventManager. Обратите внимание: у событий D7 модуля sale есть версия 2, которую подключают методом addEventHandler с указанием sort и параметров:
use Bitrix\Main\EventManager;
EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderSaved',
['\Vendor\Shop\OrderHandler', 'onOrderSaved']
);
Класс \Vendor\Shop\OrderHandler лучше держать в собственном модуле или в /local/php_interface/lib/ с настроенным автозагрузчиком — так обработчики остаются структурированными и не превращают init.php в свалку функций.
Получение объекта заказа из события
Внутри обработчика достаём заказ и служебные параметры из объекта события:
use Bitrix\Main\Event;
public static function onOrderSaved(Event $event): void
{
/** @var \Bitrix\Sale\Order $order */
$order = $event->getParameter('ENTITY');
$isNew = $event->getParameter('IS_NEW');
$oldValues = $event->getParameter('OLD_VALUES');
$orderId = $order->getId();
$sum = $order->getPrice();
$userId = $order->getUserId();
}
Ключевые параметры события:
ENTITY— объект\Bitrix\Sale\Orderс полным API: корзина, отгрузки, оплаты, свойства;IS_NEW—true, если заказ создан впервые (важно для разделения «создан» и «изменён»);OLD_VALUES— массив значений полей до сохранения, полезен для сравнения изменений.
Что можно получить из объекта Order
Объект Order — это агрегат всего заказа. Из него вытягивают состав корзины, свойства покупателя и данные о доставке и оплате:
// Товары заказа
foreach ($order->getBasket() as $item) {
$name = $item->getField('NAME');
$qty = $item->getQuantity();
$productId = $item->getProductId();
}
// Свойства заказа (телефон, ФИО, адрес)
$props = $order->getPropertyCollection();
$phone = $props->getPhone()?->getValue();
// Отгрузки и оплаты
$shipmentCollection = $order->getShipmentCollection();
$paymentCollection = $order->getPaymentCollection();
Такой доступ удобен для передачи заказа во внешнюю CRM или 1С: собираете структуру данных прямо из объекта, без дополнительных запросов к таблицам b_sale_order и b_sale_basket.
Рекурсия и повторное сохранение
Главная ловушка OnSaleOrderSaved — попытка изменить и снова сохранить заказ внутри обработчика. Вызов $order->save() заново запустит событие, и логика уйдёт в цикл.
Безопасный шаблон с флагом:
private static array $processed = [];
public static function onOrderSaved(Event $event): void
{
$order = $event->getParameter('ENTITY');
$id = (int)$order->getId();
if (isset(self::$processed[$id])) { return; }
self::$processed[$id] = true;
// ... безопасная логика ...
}
Практические сценарии и производительность
OnSaleOrderSaved закрывает большинство интеграционных задач витрины:
- отправка заказа в CRM или во внешнюю учётную систему;
- запись строки в собственный журнал заказов или в лог обмена;
- постановка задания в очередь (агент, RabbitMQ, таблица заданий) для тяжёлой обработки;
- отправка почтового или SMS-уведомления по факту создания заказа.
Различайте создание и изменение через IS_NEW: уведомление «спасибо за заказ» должно уходить один раз, а не при каждой правке менеджером в админке. Для точечной реакции на конкретные изменения сравнивайте текущие значения с параметром OLD_VALUES — например, отправляйте данные в 1С только при смене суммы, состава корзины или статуса, а не на каждое сохранение. Такой подход снижает лишний трафик обмена и защищает внешние системы от шквала одинаковых запросов при пакетном редактировании заказов.
Итог
OnSaleOrderSaved — точка, где заказ уже надёжно записан и доступен как полноценный объект Order. Отсюда удобно запускать интеграции, уведомления и журналирование, отличая новый заказ по IS_NEW и не допуская повторных save(). Для отмены и правки полей используйте более раннее OnSaleOrderBeforeSaved.
Когда обработка заказа перерастает в двусторонний обмен с 1С и CRM, очереди и отказоустойчивую передачу данных, мы в B2Bsite строим её как отдельный интеграционный слой с логированием и повторами, а не как разрастающийся обработчик события.
Частые вопросы
Можно ли отменить сохранение заказа в OnSaleOrderSaved?
Нет, к моменту вызова заказ уже записан в базу. Для проверок и отмены используйте OnSaleOrderBeforeSaved, где можно вернуть EventResult с ошибкой.
Как отличить новый заказ от изменённого?
Читайте параметр IS_NEW через $event->getParameter('IS_NEW'). Он равен true только при первом создании заказа, что важно для однократных уведомлений.
Как получить объект заказа внутри обработчика?
Через $order = $event->getParameter('ENTITY') — это экземпляр \Bitrix\Sale\Order с доступом к корзине, свойствам, отгрузкам и оплатам.
Почему возникает бесконечная рекурсия?
Из-за вызова $order->save() того же заказа внутри обработчика: каждое сохранение снова запускает OnSaleOrderSaved. Используйте статический флаг или переносите правки в OnSaleOrderBeforeSaved.
Можно ли в обработчике обращаться к внешнему API?
Технически да, но событие выполняется синхронно при оформлении. Медленные HTTP-запросы замедляют заказ, поэтому тяжёлую работу лучше ставить в очередь или агент.
Срабатывает ли событие при изменении заказа менеджером в админке?
Да, любое сохранение заказа через $order->save() вызывает событие. Отличить ручную правку от создания помогает параметр IS_NEW и сравнение OLD_VALUES.
Чем OnSaleOrderSaved отличается от старого OnSaleBeforeOrderUpdate?
OnSaleOrderSaved — событие нового D7-ядра и работает с объектами Order. Старые события sale оперировали массивами полей и относятся к устаревшему API оформления.
Как получить состав корзины заказа?
Через $order->getBasket() и итерацию по элементам, у каждого доступны getProductId, getQuantity, getField('NAME') и другие методы объекта корзины.
Поможем с настройкой и поддержкой Для разработчиков
Поможем с настройкой, доработкой и поддержкой Для разработчиков.