Кастомная служба отправки SMS в Bitrix
Не так давно в Bitrix появилась возможность отправлять SMS из "коробки". Ее работу обеспечивает новый модуль Служба сообщений (messageservice)
. В списке модулей он описывается так:
Модуль осуществляет отправку текстовых и иных сообщений
Для отправки SMS используется \Bitrix\Main\Sms\Event
. Пример
$sms = new \Bitrix\Main\Sms\Event(
"SMS_USER_CONFIRM_NUMBER",
[
"USER_PHONE" => $phoneNumber,
"CODE" => $code,
]
);
$smsResult = $sms->send(true);
- Первый параметр конструктора это
Тип события
, который создается так же как и почтовые типы событий тутНастройки > Настройки продукта > Почтовые и СМС события > Типы событий
- Второй параметр конструктора это массив полей, которые будут переданы в шаблон в качестве макроподстановок.
По умолчанию модуль messageservice
предоставляет ряд уже готовых Службы отправки SMS: SMS.RU, SMS-ассистент, Twilio.com. Мне понадобилось на одном из проектов сделать отправку SMS через существующий аккаунт SMS.RU. И тут возникли проблемы. После заполнения всех полей формы, на соответсвующей странице настроек модуля, в SMS.RU почему то зарегистрировался новый аккаунт. После повторного ввода данных, не приходили SMS с подтверждением. После копания в недрах модуля messageservice
нашел, что для работы с SMS.RU используется не документрированное API, о котором ТП SMS.RU ни чего не может сказать.
В итоге оказалось проще написать собственную реализацию Службы отправки SMS.RU для модуля messageservice
. И вот что у меня получилось:
<?php
namespace Gricuk\MessageService;
use Bitrix\Main\Application;
use Bitrix\Main\Error;
use Bitrix\Main\Result;
use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Json;
use Bitrix\Main\Loader;
use Bitrix\MessageService\Sender\Result\MessageStatus;
use Bitrix\MessageService\Sender\Result\SendMessage;
use Bitrix\MessageService;
class SmsRu extends \Bitrix\MessageService\Sender\Base
{
const API_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
public static function isSupported()
{
return true;
}
public function getId()
{
return 'customsmsru';
}
public function getName()
{
return "Custom Sms.ru";
}
public function getShortName()
{
return 'sms.ru';
}
public function isDemo()
{
return false;
}
public function canUse()
{
return true;
}
public function sendMessage(array $messageFields)
{
if (!$this->canUse()) {
$result = new SendMessage();
$result->addError(new Error(self::getMessage('MESSAGESERVICE_SENDER_SMS_SMSRU_CAN_USE_ERROR')));
return $result;
}
$params = array(
'to' => $messageFields['MESSAGE_TO'],
'msg' => $messageFields['MESSAGE_BODY']
);
$result = new SendMessage();
$apiResult = $this->callExternalMethod('sms/send', $params);
$resultData = $apiResult->getData();
if (!$apiResult->isSuccess()) {
if ((int)$resultData['status_code'] == 206) {
$result->setStatus(MessageService\MessageStatus::DEFERRED);
$result->addError(new Error($this->getErrorMessage($resultData['status_code'])));
} else {
$result->addErrors($apiResult->getErrors());
}
} else {
$smsData = current($resultData['sms']);
if (isset($smsData['sms_id'])) {
$result->setExternalId($smsData['sms_id']);
}
if ((int)$smsData['status_code'] !== 100) {
$result->addError(new Error($this->getErrorMessage($smsData['status_code'])));
} elseif ((int)$smsData['status_code'] == 206) {
$result->setStatus(MessageService\MessageStatus::DEFERRED);
$result->addError(new Error($this->getErrorMessage($smsData['status_code'])));
} else {
$result->setAccepted();
}
}
return $result;
}
public function getMessageStatus(array $messageFields)
{
$result = new MessageStatus();
$result->setId($messageFields['ID']);
$result->setExternalId($messageFields['EXTERNAL_ID']);
if (!$this->canUse()) {
$result->addError(new Error(self::getMessage('MESSAGESERVICE_SENDER_SMS_SMSRU_CAN_USE_ERROR')));
return $result;
}
$params = array(
'sms_id' => $result->getExternalId()
);
$apiResult = $this->callExternalMethod('sms/status', $params);
if (!$apiResult->isSuccess()) {
$result->addErrors($apiResult->getErrors());
} else {
$resultData = $apiResult->getData();
$smsData = current($resultData['sms']);
$result->setStatusCode($smsData['status_code']);
$result->setStatusText($smsData['status_text']);
if ((int)$resultData['status_code'] !== 100) {
$result->addError(new Error($this->getErrorMessage($smsData['status_code'])));
}
}
return $result;
}
public static function resolveStatus($serviceStatus)
{
$status = parent::resolveStatus($serviceStatus);
switch ((int)$serviceStatus) {
case 100:
return MessageService\MessageStatus::ACCEPTED;
break;
case 101:
return MessageService\MessageStatus::SENDING;
break;
case 102:
return MessageService\MessageStatus::SENT;
break;
case 103:
return MessageService\MessageStatus::DELIVERED;
break;
case 104: //timeout
case 105: //removed by moderator
case 106: //error on receiver`s side
case 107: //unknown reason
case 108: //rejected
return MessageService\MessageStatus::UNDELIVERED;
break;
case 110:
return MessageService\MessageStatus::READ;
break;
}
return $status;
}
private function callExternalMethod($method, $params = [])
{
$url = 'https://sms.ru/' . $method;
$params["api_id"] = self::API_ID;
$httpClient = new HttpClient(array(
"socketTimeout" => 10,
"streamTimeout" => 30,
"waitResponse" => true,
));
$httpClient->setHeader('User-Agent', 'Bitrix24');
$httpClient->setCharset('UTF-8');
$isUtf = Application::getInstance()->isUtfMode();
if (!$isUtf) {
$params = \Bitrix\Main\Text\Encoding::convertEncoding($params, SITE_CHARSET, 'UTF-8');
}
$params['json'] = 1;
$result = new Result();
$answer = array();
if ($httpClient->query(HttpClient::HTTP_POST, $url, $params) && $httpClient->getStatus() == '200') {
$answer = $this->parseExternalAnswer($httpClient->getResult());
}
$answerCode = isset($answer['status_code']) ? (int)$answer['status_code'] : 0;
if ($answerCode !== 100) {
$result->addError(new Error($this->getErrorMessage($answerCode, $answer)));
}
$result->setData($answer);
return $result;
}
private function parseExternalAnswer($httpResult)
{
try {
$answer = Json::decode($httpResult);
} catch (\Bitrix\Main\ArgumentException $e) {
$data = explode(PHP_EOL, $httpResult);
$code = (int)array_shift($data);
$answer = $data;
$answer['status_code'] = $code;
$answer['status'] = $code === 100 ? 'OK' : 'ERROR';
}
if (!is_array($answer) && is_numeric($answer)) {
$answer = array(
'status' => $answer === 100 ? 'OK' : 'ERROR',
'status_code' => $answer
);
}
return $answer;
}
private function getErrorMessage($errorCode, $answer = null)
{
$message = self::getMessage('MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_' . $errorCode);
if (!$message && $answer && !empty($answer['errors'])) {
$errorCode = $answer['errors'][0]['status_code'];
$message = self::getMessage('MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_' . $errorCode);
if (!$message) {
$message = $answer['errors'][0]['status_text'];
}
}
return $message ?: self::getMessage('MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_OTHER');
}
public static function getMessage($code)
{
$MESS = [];
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_NAME"] = "Компания SMS.RU";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_CAN_USE_ERROR"] = "Провайдер компании SMS.RU не настроен";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_201"] = "Не хватает средств на лицевом счету";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_202"] = "Неправильно указан получатель";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_203"] = "Нет текста сообщения";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_204"] = "Имя отправителя не согласовано с администрацией";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_205"] = "Сообщение слишком длинное (превышает 8 СМС)";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_206"] = "Будет превышен или уже превышен дневной лимит на отправку сообщений";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_207"] = "На этот номер (или один из номеров) нельзя отправлять сообщения, либо указано более 100 номеров в списке получателей";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_209"] = "Вы добавили этот номер (или один из номеров) в стоп-лист";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_230"] = "Превышен общий лимит количества сообщений на этот номер в день";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_231"] = "Превышен лимит одинаковых сообщений на этот номер в минуту";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_232"] = "Превышен лимит одинаковых сообщений на этот номер в день";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_301"] = "Неправильный пароль, либо пользователь не найден";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_302"] = "Аккаунт не подтвержден (пользователь не ввел код, присланный в регистрационной смс)";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_303"] = "Код подтверждения неверен";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_304"] = "Отправлено слишком много кодов подтверждения. Пожалуйста, повторите запрос позднее";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_305"] = "Слишком много неверных вводов кода, повторите попытку позднее";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_703"] = "Опечатка в номере мобильного телефона или номер из неподдерживаемой страны";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_900"] = "Код подтверждения указан неверно";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_901"] = "Неверно указан номер телефона, ошибка в формате";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_903"] = "В данный момент регистрация номера с таким кодом недоступна";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_905"] = "Пользователь не указал имя (или оно меньше 2х символов)";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_906"] = "Пользователь не указал фамилию (или оно меньше 2х символов)";
$MESS["MESSAGESERVICE_SENDER_SMS_SMSRU_ERROR_OTHER"] = "Системная ошибка. Необходимо повторить запрос еще раз";
return $MESS[$code];
}
public static function onGetSmsSenders()
{
$class = __CLASS__;
return [new $class()];
}
public function getFromList()
{
$result = $this->callExternalMethod('my/senders');
if ($result->isSuccess()) {
$from = array();
$resultData = $result->getData();
foreach ($resultData['senders'] as $sender) {
if (!empty($sender)) {
$from[] = array(
'id' => $sender,
'name' => $sender
);
}
}
return $from;
}
return [];
}
}
?>
А вот так выполняется подключение данной Службы отправки SMS
<?php
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler(
"messageservice",
"onGetSmsSenders",
array(
Gricuk\MessageService\SmsRu::class,
"onGetSmsSenders",
)
);
?>
Как видно для кастомной службы отправки SMS.RU необходимо написать класс, наследованный от \Bitrix\MessageService\Sender\Base
.
Так же существует класс \Bitrix\MessageService\Sender\BaseConfigurable
. Он является наследником \Bitrix\MessageService\Sender\Base
и отличается от него тем, что позволяет вывести в настройки модуля messageservice
форму с параметрами службы.