Вводные
1. Набор сотовых номеров привязанных к АТС
2. Нужно зафиксировать в 1С:УНФ для наполнения информацией о телефонных звонках и их контактах.
3. Из всего многообразия подключений именно с этой АТС нет, хотя на "источнике" в Рарус:Софтфон реализация появилась.
Документация и поддержка
Документация доступна в формате PDF только из ЛК АТС (https://vpbx.mts.ru/)
Сообщения идут с IP (в документации это не указано)
- 213.87.45.43
- 213.87.45.47
Для сис админов которые открывают порт для всего интернета рекомендую прочитать http://184.105.247.252/, этот IP выловлен из логов апача, при том что входящий порт HTTP нестандартный.
Сообщения телефонии шлют параллельно два канала (если включить),
- HTTP API, но для этого нужно добавить пользователя в подписку POST запросом, при этом добавляя подписку по одному пользователю уведомления идут по всем.
- WebHook (по факту тот же протокол) который при недоступности принимающей стороны может отключится, активируется через POST запрос
В документации такое поведение объяснено "оптимизацией нагрузки на сервер vpbx".
Вопрос решается по цепочке "менеджер по продажам" - "ответственный по АТС в регионе" - "группа разработки".
Нормальной системы тикетов нет, только переписка по почте, в которой часть вопросов теряется.
Функционал
По функционалу и админ панели серьезно уступает Манго (сравниваю с ним потому что у клиента в параллель эта АТС), используем только потому что привязка сотовых к МТС.
Функция переадресации по API запустить не удалось, поэтому только фиксируем входящие. Функцию вызова из формы не реализовывал, пользователи звонят с сотовых и состыковка (физически) с формой на ПК работает не так хорошо как со стационарным SIP.
Реализация
Реализовал в виде расширения, добавил HTTP сервис с одним корневым URL и методом POST. Опубликовал HTTP сервис на web сервере.
Нужно учитывать что HTTP сервисы расширений публикуются все, в отличии от сервисов конфигурации список которых вы можете выбрать при публикации. Для телефонии (типовому блоку из УНФ) в файле vrd (настроек подключения web компоненты к базе) прописывается логин с паролем сервисного пользователя "TelephonyService" с правами "Администратор системы", соответственно под этими правами потенциально (если не отфильтровать) могут запускаться остальные сервисы (HTTP, Web, Odata).
Функция ВсеЗапросыPOST(Запрос)
СтрЗаголовки = "";
Для каждого Элемент Из Запрос.Заголовки Цикл
СтрЗаголовки = СтрЗаголовки + Элемент.Ключ + " = " + Элемент.Значение + Символы.ПС;
КонецЦикла;
Если НЕ Аутентификация(Запрос) Тогда
Ответ = Новый HTTPСервисОтвет(401);
Возврат Ответ;
КонецЕсли;
СтрокаЗапрос = Запрос.ПолучитьтелоКакСтроку();
рш_TelephonyApiMTS.ОбработатьСобытиеТелефонии(СтрокаЗапрос);
Ответ = Новый HTTPСервисОтвет(200);
Возврат Ответ;
КонецФункции
Аутентификация проходит по токену
Функция Аутентификация(Запрос)
АутентификацияПройдена = Ложь;
ТокенВходящий = Запрос.Заголовки.Получить("X-AUTH-TOKEN");
Настройки = рш_TelephonyApiMTS.ПолучитьНастройки();
Если НЕ ПустаяСтрока(ТокенВходящий)
И НЕ ПустаяСтрока(Настройки.ТокенОбратногоВызова)
И ТокенВходящий = Настройки.ТокенОбратногоВызова Тогда
АутентификацияПройдена = Истина;
КонецЕсли;
Возврат АутентификацияПройдена;
КонецФункции
Функции вынесены в общий модуль потому что при критичной ошибке в коде (HTTP 500) если код в модуле HTTP сервиса в журнал регистрации ничего не пишется. Предположу что это "оптимизация" для предотвращения забивания ЖР большим потоком запросов, хотя факт аутентификации по HTTP работает.
Код обработки входящего сообщения
Процедура ОбработатьСобытиеТелефонии(СтрокаЗапрос) Экспорт
УстановитьПривилегированныйРежим(Истина);
ЗаписьЖурналаРегистрации("МТС.data",,,,СтрокаЗапрос);
Если ПустаяСтрока(СтрокаЗапрос) Тогда
Возврат;
КонецЕсли;
СоответствиеЗапрос = ИЗ_JSON(СтрокаЗапрос);
Если ТипЗнч(СоответствиеЗапрос) <> Тип("Соответствие") Тогда
Возврат;
КонецЕсли;
ТипСобытия = СоответствиеЗапрос.Получить("eventType");
Если ТипСобытия = "CHECK_ALIVE" Тогда // проверка связи
Возврат;
КонецЕсли;
ИдентификаторАбонента = СоответствиеЗапрос.Получить("abonentId");
ДанныеЗвонка = СоответствиеЗапрос.Получить("payload");
ДатаНачалаЗвонка = ДанныеЗвонка.Получить("startTime");
ИдентификаторВызова = ДанныеЗвонка.Получить("callId");
НомерКонтакта = ДанныеЗвонка.Получить("remotePartyAddress");
НомерКонтакта = СтрЗаменить(НомерКонтакта, "tel:", "");
ИмяГруппы = ДанныеЗвонка.Получить("remotePartyName");
Если СтрДлина(НомерКонтакта) < 10 Тогда //внутренние номера
Возврат;
КонецЕсли;
Если ТипСобытия = "CALL_RECEIVED" Тогда //подписчик получает входящий звонок.
ДанныеЗвонка = ТелефонияСервер.НовыйДанныеЗвонка();
ЗаполнитьПользовательПоИдентификаторуМТС(ИдентификаторАбонента, ДанныеЗвонка);
ДанныеЗвонка.Направление = Перечисления.ВходящееИсходящееСобытие.Входящее;
ДанныеЗвонка.ИдентификаторЗвонкаВАТС = ИдентификаторВызова;
ДанныеЗвонка.НомерКонтакта = НомерКонтакта;
ДанныеЗвонка.Пользователь.ВнутреннийНомер = ИдентификаторАбонента;
ДанныеЗвонка.ДатаНачалаЗвонка = ТекущаяДата();
ТелефонияСервер.ОбработатьВходящийЗвонок(ДанныеЗвонка, Истина);
ИначеЕсли ТипСобытия = "CALL_ANSWERED" Тогда //на входящий/исходящий вызов был получен ответ
ДанныеЗвонка = ТелефонияСервер.НовыйДанныеЗвонка();
ЗаполнитьПользовательПоИдентификаторуМТС(ИдентификаторАбонента, ДанныеЗвонка);
ДанныеЗвонка.ИдентификаторЗвонкаВАТС = ИдентификаторВызова;
ДанныеЗвонка.Пользователь.ВнутреннийНомер = ИдентификаторАбонента;
ДанныеЗвонка.ДатаНачалаРазговора = ТекущаяДата();
ТелефонияСервер.ОбработатьИзменениеЗвонка(ДанныеЗвонка);
ИначеЕсли ТипСобытия = "CALL_RELEASED" Тогда //момент завершения звонка
ДанныеЗвонка = ТелефонияСервер.НовыйДанныеЗвонка();
ЗаполнитьПользовательПоИдентификаторуМТС(ИдентификаторАбонента, ДанныеЗвонка);
ДанныеЗвонка.ИдентификаторЗвонкаВАТС = ИдентификаторВызова;
ДанныеЗвонка.Пользователь.ВнутреннийНомер = ИдентификаторАбонента;
ДанныеЗвонка.ДатаЗавершенияРазговора = ТекущаяДата();
ДанныеЗвонка.ОпределятьНеотвеченный = Истина;
ТелефонияСервер.ОбработатьЗавершениеЗвонка(ДанныеЗвонка);
ИначеЕсли ТипСобытия = "CALL_ORIGINATED" Тогда
ДанныеЗвонка = ТелефонияСервер.НовыйДанныеЗвонка();
ЗаполнитьПользовательПоИдентификаторуМТС(ИдентификаторАбонента, ДанныеЗвонка);
ДанныеЗвонка.Направление = Перечисления.ВходящееИсходящееСобытие.Исходящее;
ДанныеЗвонка.ИдентификаторЗвонкаВАТС = ИдентификаторВызова;
ДанныеЗвонка.НомерКонтакта = НомерКонтакта;
ДанныеЗвонка.Пользователь.ВнутреннийНомер = ИдентификаторАбонента;
ДанныеЗвонка.ДатаНачалаЗвонка = ТекущаяДата();
ТелефонияСервер.ОбработатьИсходящийЗвонок(ДанныеЗвонка);
КонецЕсли;
КонецПроцедуры
Результат обработки отправляю в подсистему телефонии УНФ, в которой решается вопрос фиксации события и уведомления пользователя.
Повтор подписки на событие, реализовал через функцию в общем модуле и сделал внешнюю обработку с серверным вызовом для простой настройки расписания.
Процедура ОбновитьПодписки() Экспорт
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| рш_ИдентификаторыПользователейМТС.Идентификатор КАК Идентификатор
|ИЗ
| РегистрСведений.рш_ИдентификаторыПользователейМТС КАК рш_ИдентификаторыПользователейМТС";
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
СоответствиеДанные = Новый Соответствие;
СоответствиеДанные.Вставить("abonentId", Выборка.Идентификатор);
СтрокаДанные = В_JSON(СоответствиеДанные);
Ответ = POST("/api/subscription", СтрокаДанные);
Сообщить("" + СтрокаДанные, СтатусСообщения.БезСтатуса);
КонецЦикла;
КонецПроцедуры
Итог
Относительно простое API сообщения от которого можно обработать одной процедурой и отправить в уже существующую подсистему телефонии.
Благодарю за внимание.