Перейти к содержимому

Свой провайдер SMS - класс, регистрация, отправка и статусы

Подключаем своего оператора SMS: класс провайдера, регистрация событием, отправка через менеджер службы и проверка статуса доставки.

Механика

Служба сообщений отделяет отправку сообщения от кода прикладного приложения. Приложение говорит «отправь этот текст на этот номер», а как именно сообщение уйдёт, решает выбранный провайдер.

Такое разделение позволяет сменить оператора без правки прикладного кода вообще. Меняется идентификатор отправителя в настройках, а вызовы отправки в коде проекта остаются прежними.

Провайдер в платформе - это класс, а не запись в настройках. Список доступных отдаёт менеджер службы, и каждый провайдер известен по собственной строковой константе идентификатора.

Свой провайдер наследуют от базового класса службы сообщений и кладут в свой модуль. Он описывает своё имя, доступность, поля настроек и метод отправки, который обращается к программному интерфейсу оператора по протоколу HTTP.

Настройки провайдера - ключ доступа, имя отправителя и адрес интерфейса - хранят рядом с модулем. Провайдер читает их при проверке доступности и отдаёт признак готовности: без ключа он в списке отправителей просто не появится.

Регистрируют провайдер обработчиком события сбора отправителей службы сообщений. После регистрации он появляется в общем списке рядом со встроенными и становится доступен по своему идентификатору.

Отправка по умолчанию идёт через очередь, а не мгновенно. Сообщение уходит после завершения запроса, поэтому проверка «нажал и жду сразу» вводит в заблуждение и рождает лишние вопросы.

Номера приводятся к международному виду автоматически, без участия прикладного кода. Ручная чистка номера своим кодом чаще ломает это преобразование, чем помогает, и оператор получает неверный формат.

Результат отправки возвращается объектом результата, где лежат ошибки постановки. Его проверяют всегда: неактивный шаблон, чужой сайт и недоступный провайдер выглядят одинаково - сообщение просто не уходит.

Статус доставки запрашивают у службы отдельным вызовом по идентификатору сообщения. Оператор отдаёт свой внешний статус, а служба переводит его в понятный текст для журнала.

Шаги

  1. Заключить договор с оператором и получить доступ к его программному интерфейсу.
  2. Написать класс провайдера наследником базового класса службы сообщений платформы.
  3. Зарегистрировать класс обработчиком события сбора отправителей прямо в установщике модуля.
  4. Проверить, что провайдер появился в списке и доступен по своему идентификатору.
  5. Отправить пробное сообщение прямой отправкой, минуя очередь, и прочитать результат.
  6. Записать в журнал внешний статус доставки после успешной отправки сообщения.

Код

Подключаем модуль службы сообщений:

if (!\Bitrix\Main\Loader::includeModule('messageservice')) {
throw new \RuntimeException('модуль службы сообщений не установлен');
}
// без подключения модуля классы менеджера сообщений недоступны вовсе

Отсутствие модуля - первая причина ошибки «класс не найден» при работе с SMS. Проверку ставят один раз на входе в свой код, а не перед каждым вызовом отправки сообщения.

Описываем свой провайдер:

namespace Vendor\Module\Sms;
class OperatorSender extends \Bitrix\MessageService\Sender\Base
{
public const ID = 'vendor_operator';
public function getId(): string { return static::ID; }
public function getName(): string { return 'Оператор «Вендор»'; }
public function isRegistered(): bool { return $this->getOption('api_key') !== null; }
// метод отправки обращается к интерфейсу оператора и возвращает объект результата
}

Идентификатор провайдера попадает в поля сообщений и в настройки, поэтому его не меняют после запуска. Имя видно администратору в списке отправителей, а признак регистрации отвечает за доступность провайдера.

Отправляем сообщение из самого провайдера:

public function sendMessage(array $messageFields): \Bitrix\Main\Result
{
$http = new \Bitrix\Main\Web\HttpClient(['socketTimeout' => 5]);
$answer = $http->post($this->getOption('api_url'), [
'key' => $this->getOption('api_key'),
'to' => $messageFields['MESSAGE_TO'], // номер уже в международном виде
'text' => $messageFields['MESSAGE_BODY'],
]);
return $this->parseAnswer($answer); // ответ оператора превращаем в результат
}

Ограничение времени ожидания здесь обязательно: медленный оператор не должен задерживать обработку очереди. Ответ оператора разбирают в объект результата, чтобы служба видела ошибку, а не считала отправку успешной.

Регистрируем провайдер событием:

\Bitrix\Main\EventManager::getInstance()->registerEventHandler(
'messageservice', 'onGetSmsSenders', // событие сбора отправителей
'vendor.module', '\Vendor\Module\Sms\OperatorSender', 'onGetSmsSenders');
// регистрацию выполняют при установке модуля, а не на каждом запросе

Постоянную регистрацию делают в установщике модуля, чтобы она пережила перезагрузку. После этого провайдер виден в списке отправителей наравне со встроенными.

Отправляем сообщение через менеджер:

$result = \Bitrix\MessageService\Sender\SmsManager::sendMessage([
'SENDER_ID' => \Vendor\Module\Sms\OperatorSender::ID,
'MESSAGE_TO' => '+79161234567', // номер в международном виде
'MESSAGE_BODY' => 'Заказ №' . $orderId . ' готов к выдаче',
]);
if (!$result->isSuccess()) {
\Bitrix\Main\Diag\Debug::writeToFile($result->getErrorMessages(), date('H:i:s'), 'sms.log');
}

Отправка кладёт сообщение в очередь и отдаёт результат постановки. Ошибки в результате означают отказ до отправки: неверный провайдер, пустой номер или недоступные настройки оператора.

Отправляем немедленно при проверке:

$result = \Bitrix\MessageService\Sender\SmsManager::sendMessageDirectly([
'SENDER_ID' => \Vendor\Module\Sms\OperatorSender::ID,
'MESSAGE_TO' => $phone, 'MESSAGE_BODY' => $text,
]);
// прямая отправка нужна для проверки: обычная уходит после завершения запроса

Прямая отправка полезна на стенде и в отладке, но не в обычном сценарии. На витрине она задерживает ответ страницы на время разговора с сервером оператора.

Читаем статус доставки:

$status = \Bitrix\MessageService\Sender\SmsManager::getMessageStatus($messageId);
printf("внешний=%s текст=%s\n", $status->getExternalStatus(), $status->getStatusText());
// внешний статус приходит от оператора, текст - перевод для журнала

Статус запрашивают не сразу, а через некоторое время после отправки. Оператор обновляет его по мере доставки, и мгновенный запрос почти всегда возвращает промежуточное значение.

Пишем в журнал после успешной отправки:

\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'messageservice', 'OnMessageSuccessfullySent',
static function (\Bitrix\Main\Event $event) {
\Bitrix\Main\Diag\Debug::writeToFile($event->getParameter('ID'), date('H:i:s'), 'sms.log');
});

Событие успешной отправки - удобное место для журнала и для отметки в своей таблице. Оно срабатывает уже после разговора с оператором, поэтому тяжёлую работу из него выносят в фоновое задание.

Ограничения

Провайдер отвечает за доставку, но не за содержание сообщения. Требования к подписи отправителя, к рекламным текстам и к согласию получателя выполняет ваш проект, а не платформа.

Стоимость сообщений считает оператор по своим правилам и своему тарифу. Ошибка в цикле рассылки обходится деньгами сразу, поэтому массовую отправку сначала проверяют на коротком списке номеров.

Внешние статусы у операторов различаются по названиям и приходят с заметной задержкой. Одинаковой картины доставки по всем провайдерам не бывает, и отчёты строят по своим отметкам, а не только по статусам оператора.

Очередь отправки живёт внутри платформы и зависит от фоновых заданий. Выключенные задания останавливают отправку целиком, и сообщения копятся до восстановления расписания.

Типичные проблемы

Вызов менеджера падает с ошибкой «класс не найден».

Модуль службы сообщений не подключён перед первым обращением к его менеджеру. Подключение проверяют один раз на входе в свой код.

Код отработал, а сообщение не пришло.

Сообщение ушло в очередь и отправляется после завершения запроса. Для проверки берут прямую отправку, которая уходит оператору без ожидания конца запроса.

Сообщения по шаблону не уходят молча.

Шаблон неактивен, привязан к другому сайту или его имя не совпадает с событием. Результат отправки проверяют всегда и печатают его ошибки в журнал проекта.

Оператор жалуется на неверный формат номера.

Номер почищен своим кодом и потерял международный вид перед отправкой оператору. Приведение номера к международному формату платформа выполняет сама, без помощи кода.

Свой провайдер не появился в списке отправителей.

Обработчик события сбора отправителей не зарегистрирован или зарегистрирован только на текущий запрос. Регистрацию выполняют в установщике модуля, чтобы она пережила перезагрузку сайта.

Частые вопросы

Можно ли работать без своего провайдера?

Да, в поставке есть готовые провайдеры популярных операторов. Свой пишут, когда нужного оператора среди них нет.

Как выбрать провайдера по умолчанию?

Менеджер отдаёт провайдера по умолчанию для региона и первый доступный из активных. В коде обычно указывают идентификатор явно.

Где хранить ключ доступа к API оператора?

В настройках модуля или в файле настроек проекта, но не в репозитории. Провайдер читает его при проверке своей доступности.

Как отправить сообщение по шаблону события?

Через высокоуровневый интерфейс событий: код передаёт имя события и данные, а текст берётся из активного шаблона. Так текст правит администратор.

Что делать с недоставленными сообщениями?

Смотреть внешний статус по идентификатору сообщения и вести свой журнал отправок. По журналу видно, повторять отправку или менять номер.

Смежное

Первоисточник