Введение
В этой статье разберём, как настроить автоматическую отправку SMS-уведомлений покупателю при изменении статуса его заказа в 1С-Битрикс. SMS — один из самых эффективных каналов связи с клиентом: сообщение доставляется мгновенно, не теряется в спаме и почти всегда прочитывается. Уведомления о смене статуса (например, «Заказ принят», «Заказ отправлен», «Заказ доставлен») повышают лояльность и снижают нагрузку на службу поддержки.
В Bitrix за отправку SMS отвечает класс Bitrix\Main\Sms\Event. Он использует
заранее созданные в административной панели SMS-шаблоны — точно так же, как работают
почтовые события. Нам остаётся лишь подписаться на нужное событие модуля «Интернет-магазин»
(sale) и в обработчике собрать данные заказа, подставить их в поля SMS-события
и вызвать send().
Весь код размещается в файле init.php (в пространстве имён Bitrix\Main\Loader
он подключается автоматически) либо в кастомном модуле. Никаких дополнительных настроек
в админке, кроме создания самих SMS-шаблонов, не требуется.
Подготовка: создание SMS-шаблонов
Прежде чем писать код, зайдите в административную панель Bitrix:
«Настройки» → «Настройки продукта» → «События» → «SMS-шаблоны»
и создайте шаблон для каждого статуса, по которому будет отправляться уведомление.
Имя SMS-события должно совпадать с тем, которое мы укажем в коде —
например, SMS_STATUS_CHANGED_XX (для статуса «Доставлен») или
SMS_STATUS_CHANGED_N (для статуса «Новый»).
#ORDER_ID#,
#ORDER_PRICE#, #ORDER_DATE# — они будут заменены на значения
из массива $fields, который мы передадим в конструктор Event.
В коде событие формируется динамически, в зависимости от текущего статуса заказа:
$eventName = 'SMS_STATUS_CHANGED_' . $order->getField('STATUS_ID');
// Например: SMS_STATUS_CHANGED_XX, SMS_STATUS_CHANGED_N, SMS_STATUS_CHANGED_F
Таким образом, для каждого статуса создаётся отдельный шаблон со своим текстом —
это даёт максимальную гибкость. Статусы XX (доставлен) и N
(новый) используются здесь как пример; в вашем проекте коды статусов могут отличаться.
Подключение обработчика события
Bitrix построен на событийно-ориентированной архитектуре. Чтобы «поймать» момент сохранения заказа, мы регистрируем обработчик через глобальный менеджер событий:
<?php
use Bitrix\Main;
use Bitrix\Main\Sms\Event;
use Bitrix\Sale\Order;
Main\EventManager::getInstance()->addEventHandler(
'sale', // модуль
'OnSaleOrderSaved', // событие
'mySmsSendOrder' // имя функции-обработчика
);Разберём по строкам:
- Импорты. Подключаем классы
Bitrix\Main(для работы с событиями),Bitrix\Main\Sms\Event(отправка SMS) иBitrix\Sale\Order(работа с заказом). - Регистрация.
EventManager::addEventHandlerпринимает три аргумента: имя модуля ('sale'), имя события ('OnSaleOrderSaved') и имя колбэка ('mySmsSendOrder'). Теперь при каждом сохранении заказа Bitrix вызовет нашу функцию.
OnSaleOrderSaved срабатывает и при создании, и при обновлении заказа
(в отличие от OnSaleOrderPayed, который ловит только оплату). Это универсальное событие,
подходящее для отслеживания любых изменений — статуса, суммы, состава товаров и т.д.
Базовая отправка: функция mySmsSendOrder
В функции-обработчике мы получаем объект события $event, извлекаем из него
заказ и старые значения полей, фильтруем нужные статусы, собираем данные и отправляем SMS.
Полный код:
function mySmsSendOrder(Main\Event $event)
{
$order = $event->getParameter("ENTITY");
$oldValues = $event->getParameter("VALUES");
$isNew = $event->getParameter("IS_NEW");
// Отправляем только для статусов XX (доставлен) и N (новый)
if (!in_array($order->getField('STATUS_ID'), ['XX', 'N']))
return;
// Извлекаем телефон из свойства заказа с кодом PHONE
$phone = '';
foreach ($order->getPropertyCollection() as $property) {
if ($property->getField('CODE') == 'PHONE')
$phone = $property->getField('VALUE');
}
$fields = [
'PHONE' => $phone,
'ORDER_ID' => $order->getField('ACCOUNT_NUMBER'),
'ORDER_REAL_ID' => $order->getField('ID'),
'ORDER_DATE' => $order->getField('DATE_STATUS')->format('Y.m.d'),
'ORDER_STATUS' => $order->getField('STATUS_ID'),
'ORDER_PRICE' => \SaleFormatCurrency($order->getPrice(), $order->getCurrency()),
'ORDER_DESCRIPTION' => $order->getField('USER_DESCRIPTION'),
];
$sms = new Event('SMS_STATUS_CHANGED_' . $order->getField('STATUS_ID'), $fields);
$sms->setSite('s1');
$sms->setLanguage('ru');
$sms->send();
}Как это работает — по шагам
- Извлечение данных из события:
$event->getParameter("ENTITY")возвращает объектBitrix\Sale\Order— сам заказ.getParameter("VALUES")— массив старых значений полей до сохранения (нужен, чтобы понять, что именно изменилось).getParameter("IS_NEW")— флаг, указывающий, создан заказ впервые или обновлён существующий. - Фильтрация по статусу:
Мы проверяем, что текущий статус заказа входит в список интересующих нас статусов
(
XXиN). Если нет — выходим из функции, не отправляя SMS. Это гарантирует, что уведомления уходят только в нужные моменты. - Получение номера телефона:
В Bitrix телефон покупателя хранится в свойствах заказа. Мы проходим по всей коллекции
свойств (
getPropertyCollection()), ищем свойство с кодомPHONEи забираем его значение. Важно, чтобы в настройках интернет-магазина свойство телефона имело именно такой символьный код — иначе SMS не уйдёт. - Сборка полей:
Формируем ассоциативный массив
$fields, в который помещаем:PHONE— номер получателя;ORDER_ID— символьный код заказа (номер счёта);ORDER_REAL_ID— числовой ID заказа в БД;ORDER_DATE— дата установки статуса (форматY.m.d);ORDER_STATUS— код статуса;ORDER_PRICE— сумма заказа, отформатированная функциейSaleFormatCurrency;ORDER_DESCRIPTION— комментарий покупателя.
#ORDER_ID#. - Отправка SMS:
Создаём объект
Event, передавая ему имя SMS-события (динамически формируемое из префиксаSMS_STATUS_CHANGED_и кода статуса) и массив полей. Указываем сайт (s1) и язык (ru), затем вызываемsend(). Bitrix сам найдёт подходящий SMS-шаблон, подставит значения и отправит сообщение через настроенного SMS-провайдера.
send()
тихо проигнорирует вызов, и сообщение не уйдёт. Всегда проверяйте, что шаблон активен
и привязан к нужному сайту.
Асинхронная отправка с задержкой
Иногда требуется отправить SMS не сразу, а спустя какое-то время. Типичный кейс:
статус заказа меняется на «Отгружен» (XX), а через 3 дня, если статус
не изменился (например, покупатель не оформил возврат), нужно напомнить, что товар доставлен
и попросить оценить покупку.
Механизм такой: мы регистрируем отложенную задачу через register_shutdown_function,
которая выполнится после завершения текущего скрипта, и внутри неё ставим паузу
(sleep) на нужное количество секунд. Перед отправкой заново загружаем
заказ из БД и проверяем, остался ли статус прежним; если статус изменился — SMS не шлём.
fastcgi_finish_request()).
Пользователь не ждёт завершения долгой операции. Однако sleep всё равно
блокирует воркер, поэтому этот подход — демонстрационный. В реальной нагрузке используйте
агенты Bitrix, очередь задач или внешний планировщик.
<?php
Main\EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderSaved',
'mySmsSendOrderAsync'
);
function mySmsSendOrderAsync(Main\Event $event)
{
$order = $event->getParameter("ENTITY");
$oldValues = $event->getParameter("VALUES");
// Срабатываем только при свежей смене статуса на "XX"
if ($order->getField('STATUS_ID') !== 'XX' || $oldValues['STATUS_ID'] === 'XX')
return;
// Извлекаем телефон
$phone = '';
foreach ($order->getPropertyCollection() as $property) {
if ($property->getField('CODE') == 'PHONE')
$phone = $property->getField('VALUE');
}
$fields = [
'PHONE' => $phone,
'ORDER_ID' => $order->getField('ACCOUNT_NUMBER'),
'ORDER_DATE' => $order->getField('DATE_STATUS')->format('Y-m-d H:i:s'),
'ORDER_STATUS'=> $order->getField('STATUS_ID'),
'ORDER_PRICE' => SaleFormatCurrency($order->getPrice(), $order->getCurrency()),
];
// Регистрируем отложенную задачу
register_shutdown_function(function () use ($fields, $order) {
sleep(259200); // 3 дня (60 x 60 x 72)
// Проверяем, не изменился ли статус за прошедшее время
$currentOrder = Order::load($order->getId());
if ($currentOrder && $currentOrder->getField('STATUS_ID') === 'XX') {
sendSmsAsync($fields);
}
});
}
// Вспомогательная функция для отправки SMS с обработкой ошибок
function sendSmsAsync($fields)
{
try {
$sms = new Event('SMS_SALE_STATUS_CHANGED_' . $fields['ORDER_STATUS'], $fields);
$sms->setSite('s1');
$sms->setLanguage('ru');
$sms->send();
} catch (\Exception $e) {
// Логируем ошибки в файл /sms.log
file_put_contents(
$_SERVER["DOCUMENT_ROOT"] . '/sms.log',
$e->getMessage()
);
}
}Разбор асинхронного варианта
- Условие срабатывания: только при смене на «XX».
Проверка
$order->getField('STATUS_ID') !== 'XX'отсеивает все другие статусы. Вторая часть$oldValues['STATUS_ID'] === 'XX'не даёт сработать повторно, если заказ уже был в статусе «XX» и просто пересохраняется (например, менеджер редактирует комментарий). Мы ловим именно переход из другого статуса в «XX». - Сбор полей: Аналогично базовому варианту, но здесь мы не передаём
ORDER_REAL_IDиORDER_DESCRIPTION— только ключевые данные. Формат даты изменён наY-m-d H:i:sдля более точной фиксации момента. - Отложенное выполнение:
register_shutdown_functionпринимает анонимную функцию. Ключевое словоuse«захватывает» переменные$fieldsи$orderвнутрь замыкания, чтобы они были доступны, когда колбэк в конечном счёте выполнится. Внутри —sleep(259200)(60 × 60 × 72 = 3 дня). После паузы мы перезагружаем заказ из базы свежим методомOrder::load($order->getId()). - Проверка перед отправкой:
Если заказ всё ещё в статусе «XX» — вызываем
sendSmsAsync(). Если статус изменился (например, покупатель оформил возврат, и заказ перешёл в «Возврат»), SMS не отправляется. Это предотвращает ложные уведомления. - Вынос отправки в отдельную функцию с try-catch:
sendSmsAsync()обёрнута вtry-catch: если провайдер SMS недоступен или шаблон не найден, ошибка не «упадёт» с фаталом, а запишется в лог-файл/sms.logв корне сайта. Так мы не потеряем информацию о сбое и сможем вовремя отреагировать.
sleep(259200) блокирует PHP-процесс на 3 дня.
В production-среде это приведёт к исчерпанию пула воркеров. Для реальных проектов используйте
агенты Bitrix (метод CAgent::AddAgent) — они выполняются
в фоне при каждом хите и не блокируют пользовательские запросы. Либо выносите задачи
в очередь через RabbitMQ, Gearman или Redis.
Заключение
Мы разобрали два подхода к отправке SMS при смене статуса заказа в Bitrix: синхронный (мгновенная отправка) и асинхронный с задержкой (отложенная отправка с верификацией статуса).
Первый подходит для большинства сценариев: «Заказ подтверждён», «Заказ отправлен», «Заказ доставлен». Второй — для случаев, когда нужно напомнить клиенту о чём-то через продолжительное время, но только при условии, что ситуация не изменилась.
Обратите внимание на ограничения register_shutdown_function + sleep:
этот код написан в учебных целях. На боевом сайте обязательно замените его на
агенты или очередь.
Полный код для копирования
Готовый блок кода, который можно скопировать целиком и вставить в init.php:
<?php
// ============================================================
// 1. SMS при смене статуса (базовая отправка)
// ============================================================
use Bitrix\Main;
use Bitrix\Main\Sms\Event;
use Bitrix\Sale\Order;
Main\EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderSaved',
'mySmsSendOrder'
);
function mySmsSendOrder(Main\Event $event)
{
$order = $event->getParameter("ENTITY");
$oldValues = $event->getParameter("VALUES");
$isNew = $event->getParameter("IS_NEW");
if (!in_array($order->getField('STATUS_ID'), ['XX', 'N']))
return;
$phone = '';
foreach ($order->getPropertyCollection() as $property) {
if ($property->getField('CODE') == 'PHONE')
$phone = $property->getField('VALUE');
}
$fields = [
'PHONE' => $phone,
'ORDER_ID' => $order->getField('ACCOUNT_NUMBER'),
'ORDER_REAL_ID' => $order->getField('ID'),
'ORDER_DATE' => $order->getField('DATE_STATUS')->format('Y.m.d'),
'ORDER_STATUS' => $order->getField('STATUS_ID'),
'ORDER_PRICE' => \SaleFormatCurrency($order->getPrice(), $order->getCurrency()),
'ORDER_DESCRIPTION' => $order->getField('USER_DESCRIPTION'),
];
$sms = new Event('SMS_STATUS_CHANGED_' . $order->getField('STATUS_ID'), $fields);
$sms->setSite('s1');
$sms->setLanguage('ru');
$sms->send();
}
// ============================================================
// 2. Асинхронная отправка с задержкой 3 дня
// (с проверкой статуса перед отправкой)
// ============================================================
Main\EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderSaved',
'mySmsSendOrderAsync'
);
function mySmsSendOrderAsync(Main\Event $event)
{
$order = $event->getParameter("ENTITY");
$oldValues = $event->getParameter("VALUES");
if ($order->getField('STATUS_ID') !== 'XX' || $oldValues['STATUS_ID'] === 'XX')
return;
$phone = '';
foreach ($order->getPropertyCollection() as $property) {
if ($property->getField('CODE') == 'PHONE')
$phone = $property->getField('VALUE');
}
$fields = [
'PHONE' => $phone,
'ORDER_ID' => $order->getField('ACCOUNT_NUMBER'),
'ORDER_DATE' => $order->getField('DATE_STATUS')->format('Y-m-d H:i:s'),
'ORDER_STATUS'=> $order->getField('STATUS_ID'),
'ORDER_PRICE' => SaleFormatCurrency($order->getPrice(), $order->getCurrency()),
];
register_shutdown_function(function () use ($fields, $order) {
sleep(259200);
$currentOrder = Order::load($order->getId());
if ($currentOrder && $currentOrder->getField('STATUS_ID') === 'XX') {
sendSmsAsync($fields);
}
});
}
function sendSmsAsync($fields)
{
try {
$sms = new Event('SMS_SALE_STATUS_CHANGED_' . $fields['ORDER_STATUS'], $fields);
$sms->setSite('s1');
$sms->setLanguage('ru');
$sms->send();
} catch (\Exception $e) {
file_put_contents(
$_SERVER["DOCUMENT_ROOT"] . '/sms.log',
$e->getMessage()
);
}
}После вставки не забудьте создать SMS-шаблоны в админке для каждого используемого статуса. Коды статусов (XX, N) замените на свои, если они отличаются.
Ключевые моменты
- Событие
OnSaleOrderSaved— срабатывает при создании и каждом обновлении заказа, подходит для отслеживания изменений статуса - Телефон Извлекается из свойства заказа с кодом
PHONE(настраивается в админке); без корректного номера SMS не отправится - Шаблон Имя SMS-события формируется динамически:
SMS_STATUS_CHANGED_{STATUS_ID}— для каждого статуса нужен отдельный шаблон - Отложка При асинхронной отправке обязательно перепроверяйте статус заказа перед отправкой, чтобы не отправить устаревшее уведомление
- Ошибки Всегда оборачивайте отправку в
try-catchи логируйте исключения — это упростит отладку
