...

пятница, 31 июля 2015 г.

Архитектурные решения в системе телефонии «Битрикс24»

Несмотря на эпоху интернета и всевозможных средств коммуникации, телефонная связь по прежнему остаётся одним из важнейших каналов взаимодействия компании со своими клиентами. И каким бы ни был корпоративный сайт, — лицо фирмы — отсутствие телефона или плохое качество связи может сильно подпортить впечатление клиентов и партнёров. Поэтому, мы когда-то всерьёз озаботились тем, чтобы интегрировать в «Битрикс24» полноценную систему телефонной связи, которую наши пользователи могут применять для поддержки и развития своего бизнеса. И на страницах этого поста мы хотим рассказать об архитектуре и принципах функционирования созданной системы.
Прорабатывая требования к телефонии, которую планировалось интегрировать в «1С-Битрикс», мы сразу же решили, что каждый телефонный аккаунт должен быть изолирован друг от друга. Чтобы ни один клиент не мог помешать или навредить другому. Нас не устраивала схема, когда на нашу компанию выделяется один телефонный аккаунт, клиенты переводят нам деньги, а мы как-то распределяем трафик. Нам требовалась полная изоляция, чтобы у каждого пользователя была своя статистика, свой баланс, раздельное предоставление прочих услуг. Для этого потребовалось, чтобы компания Voximplant, чей продукт мы выбрали для создания телефонии, доработала свою систему. В результате был внедрён так называемый мастер-аккаунт, под которым создаются все дочерние аккаунты наших пользователей, для каждого портала свой. Хотя, на самом деле, технически используется четыре мастер-аккаунта: для рублей, гривен, долларов и евро.

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

Контроллер телефонии


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

Поэтому, чтобы реализовать задуманное нами разделение пользовательских аккаунтов, нам пришлось написать на PHP и MySQL модуль для Битрикс24, называющийся контроллер телефонии. Он выступает своеобразным информационным посредником между пользовательским порталом и сервером Voximplant.

Когда пользователь впервые инициирует вызов, то модуль телефонии на его портале обращается к контроллеру телефонии и запрашивает учётные данные. Контроллер проверяет на сервере лицензий, что данный пользователь имеет право на использование этой услуги, а потом обращается за учётными данными на сервер Voximplant. Если учётной записи ещё нет, то она сразу создаётся. После этого контроллер возвращает порталу учётные данные для подключения по SIP, они записываются в БД на портале и используются при всех последующих вызовах. То есть в дальнейшем контроллер телефонии уже не задействуется.

По получении учётных данных пользователь напрямую обращается к Voximplant и весь голосовой трафик идет по WebRTC без каких-либо посредников. Для передачи все прочей информации используется подключение по протоколу WebSocket. Когда вызов завершается, Voximplant уведомляет контроллер, а тот фиксирует это в статистике пользователя. Всё общение с серверами Voximplant осуществляется по принципу REST.

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

Иными словами, сами пользовательские порталы ничего не знают про Voximplant, для них точкой входа всегда является контроллер телефонии. Получилась изолированная система, при которой сам пользователь совершенно не беспокоится о том, как совершаются звонки. Вся статистика и текущий баланс отображаются прямо на портале, оплата осуществляется там же, пользователю не нужно заходить куда-то ещё. Это позволяет максимально быстро начать пользоваться услугой телефонии: достаточно зайти на свой портал, выбрать номер, оплатить тут же деньги, и можно совершать звонки. Не нужно заводить какие-то дополнительные аккаунты, что-то настраивать.

В отличие от звонков через браузер, телефонные аппараты пользователей «Битрикс24» подключаются к серверу Voximplant напрямую, минуя контроллер телефонии. Для этого при первичном подключении в телефон заносятся логин и пароль, которые впоследствии используются при каждом звонке. Эти учётные данные пользователь получает во время настройки в разделе «Телефония», процедура такая же, как и при совершении первого звонка. И уже на основе этих регистрационных данных Voximplant отправляет статистику о совершенных звонках. А поскольку телефонные аппараты (в том числе приложения для VoIP-телефонии) могут менять хозяев, то у администратора есть возможность быстро сменить пароль или вообще отключить данный аккаунт.

Когда из внешней сети поступает входящий вызов на номер, арендованный пользователем «Битрикс24», то он попадает на сервер Voximplant. Тот обращается к контроллеру за информацией о том, кому нужно направить звонок. На портале соответствующего пользователя появляется оповещение о входящем вызове, и как только кто-то отвечает, сервер связывает обоих абонентов напрямую.

Безопасность


Несмотря на наличие информационного посредника в виде контроллера телефонии, безопасности это не вредит. Во-первых, все данные передаются по HTTPS, то есть шифруются протоколом SSL. Кроме того, при каждом обмене данными (любая команда по REST) между порталом и контроллером телефонии, на основе передаваемой информации и лицензионного ключа формирует уникальный ключ запроса (для каждого набора данных он будет разным). Такое же шифрование осуществляется при обмене данным между контроллером и голосовыми терминалами Voximplant. Обмен ключами осуществляется и при установлении прямой связи между пользовательским порталом и сервером Voximplant.

Регистрация аккаунтов


Когда новый пользователь заходит на портале «Битрикс24» в раздел телефонии, то контроллер телефонии автоматически создает для него учетную запись в Voximplant. При совершении первого звонка или подключении телефонного аппарата информация о телефонном номере добавляется в учетную запись. То есть все операции по регистрации выполняются последовательно, по мере осуществления пользователем тех или иных действий. Тем самым мы избегаем возникновения пиковой нагрузки на серверы в процессе регистрации.

Обновление аккаунтов


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

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

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

Сценарии звонков


Voximplant позволяет создать абсолютно любой сценарий звонка. Ниже представлен пример упрощенной схемы входящего вызова.

Давайте разберемся в этой схеме. Итак, у нас есть входящий звонок. Поскольку Voximplant является для «Битрикс24» внешней системой, сначала ему нужно получить у контроллера настройки для вызываемого абонента (в формате JSON), на основании которых сценарий выполняет действия, предусмотренные для входящих или исходящих вызовов. После первого вызова настройки JSON кэшируются, и впоследствии нам не нужно каждый раз обращаться за ними на пользовательский портал. Сам же сценарий представляет собой программный модуль, написанный на JavaScript и содержащий около 3000 строк кода.

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

Мы обрабатываем все типовые сценарии, которые могут быть востребованы нашими пользователями. Например, если входящий звонок совершается ночью, то нужно сообщить о том, что на месте пока никого нет, перезвоните позже или оставьте сообщение. Или сценарии обработки CRM: нужно ли это делать, если да, то перевести вызов на ответственного человека и сразу сообщить ему, от кого именно поступил звонок. Чтобы звонящему сразу ответили: «Здравствуйте, Иван Иванович. Что вы хотите?»

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

Дополнительные возможности


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

Также контроллера позволяет нам переносить все настройки пользователя при его переходе с облачной версии «Битрикс24» на коробочную. Поскольку телефонный аккаунт не привязан жёстко к порталу, то достаточно просто переназначаем его на другую версию продукта. Поэтому клиенту ничего не нужно перенастраивать, сразу после миграции он может продолжить пользоваться телефонной связью.

Помимо прочего, посредством контроллера телефонии наша техническая поддержка может оказывать помощь пользователям. Когда кто-то обращается с некой проблемой, специалист техподдержки запрашивает у пользователя данные для авторизации. А затем по утверждённому регламенту спрашивает у него разрешение на просмотр статистики. После того, как разрешение получено, через контроллер телефонии мы получаем данные телефонного аккаунта, логи звонков и прочую информацию, позволяющую решить проблему, с которой обратился пользователь. Зачастую, для полной диагностики проблемы достаточно проанализировать лог вызова, поэтому доступ непосредственно к аккаунту телефонии наши специалисты запрашивают крайне редко. Вот как выглядит лог в оригинале:

Лог
2015-07-24 08:50:08 Loading scenario bitrix24
2015-07-24 08:50:08 Sent event to JS onPhoneEvent with params [{accessURL = ;  accountId = XXXXX ;  applicationId = XXXXX ;  logURL = ;  name = Application.Started ;  sessionId = 48475497 ;  userId = 1 ;  } ;  ]
2015-07-24 08:50:08 Sent event to JS onPhoneEvent with params [{callerid = test ;  destination = 74012XXXXXX ;  displayName = 79112233444 ;  fromURI = sip:test@ip.accountName.voximplant.com ;  headers = {VI-Client-Device = SIP ;  VI-Client-IP = 69.167.178.6 ;  VI-Client-Type = user ;  } ;  id = 16214d90008034a8.1437727808.225341 ;  name = Application.CallAlerting ;  toURI = sip:74012XXXXXX@ip.accountName.voximplant.com ;  } ;  ]
2015-07-24 08:50:08 Executing JS command: SetCustomData with params [{data = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:08 Executing JS command: StartAudio with params [{headers = NULL ;  id = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:08 Executing JS command: PlayToneScript with params [{id = 16214d90008034a8.1437727808.225341 ;  loop = true ;  script = 440@-19,480@-19;*(2/4/1+2) ;  } ;  ]
2015-07-24 08:50:08
2015-07-24 08:50:08 ------------------------------------------------------------------------------------------
2015-07-24 08:50:08 Start INCOMING scenario (version: 9)
2015-07-24 08:50:08 ------------------------------------------------------------------------------------------
2015-07-24 08:50:08
2015-07-24 08:50:08
2015-07-24 08:50:08 ------------------------------------------------------------------------------------------
2015-07-24 08:50:08 Pure variables
2015-07-24 08:50:08 Call scenario user: 1
2015-07-24 08:50:08 Call callerId: test
2015-07-24 08:50:08 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = 16214d90008034a8.1437727808.225341 ;  name = Call.AudioStarted ;  } ;  ]
2015-07-24 08:50:08 Call destination: 74012XXXXXX
2015-07-24 08:50:08 Call displayName: 79112233444
2015-07-24 08:50:08 Call number/destination: 74012XXXXXX
2015-07-24 08:50:08 ------------------------------------------------------------------------------------------
2015-07-24 08:50:08
2015-07-24 08:50:09
2015-07-24 08:50:09 ------------------------------------------------------------------------------------------
2015-07-24 08:50:09 Get B24 config:
2015-07-24 08:50:09 ID: 23   /   PORTAL_MODE: RENT   /   SEARCH_ID: test   /   PHONE_NAME: 88002501860   /   CRM: Y   /   CRM_RULE: queue   /   CRM_CREATE: lead   /   CRM_FORWARD: Y   /   QUEUE_TIME: 3   /   QUEUE_TYPE: evenly   /   DIRECT_CODE: Y   /   DIRECT_CODE_RULE: voicemail   /   RECORDING: Y   /   RECORDING_TIME: 0   /   NO_ANSWER_RULE: voicemail   /   FORWARD_NUMBER:    /   FORWARD_LINE: default   /   TIMEMAN: N   /   VOICEMAIL: Y   /   MELODY_LANG: RU   /   MELODY_WELCOME: http://ift.tt/1Ixg9UF   /   MELODY_WELCOME_ENABLE: Y   /   MELODY_WAIT: http://ift.tt/1IcykeK   /   MELODY_HOLD: http://ift.tt/1IcykeK   /   DATE_DELETE: null   /   TO_DELETE: N   /   MELODY_VOICEMAIL: http://ift.tt/1Ixg9UH   /   PHONE_TITLE: 88002501860   /   PORTAL_URL: http://ift.tt/1IcyhzN
2015-07-24 08:50:09 PORTAL_SIGN: -hidden-
2015-07-24 08:50:09 ------------------------------------------------------------------------------------------
2015-07-24 08:50:09
2015-07-24 08:50:09
2015-07-24 08:50:09 ------------------------------------------------------------------------------------------
2015-07-24 08:50:09 Call type is: TEST CALL
2015-07-24 08:50:09 Phone number: 74012XXXXXX
2015-07-24 08:50:09 Call to: 79112233444
2015-07-24 08:50:09 ------------------------------------------------------------------------------------------
2015-07-24 08:50:09
2015-07-24 08:50:09 Call in correct worktime
2015-07-24 08:50:09 ------------------------------------------------------------------------------------------
2015-07-24 08:50:09
2015-07-24 08:50:09 Executing JS command: AcceptCall with params [{headers = NULL ;  id = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:09 Executing JS command: Play with params [{id = 16214d90008034a8.1437727808.225341 ;  loop = false ;  url = http://ift.tt/1Ixg9UF ;  } ;  ]
2015-07-24 08:50:09 Executing JS command: HandleTones with params [{handle = true ;  id = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:10 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = 16214d90008034a8.1437727808.225341 ;  name = Call.Connected ;  } ;  ]
2015-07-24 08:50:17 Sent event to JS onPhoneEvent with params [{id = 16214d90008034a8.1437727808.225341 ;  name = Call.PlaybackFinished ;  } ;  ]
2015-07-24 08:50:17 Executing JS command: Stop with params [{id = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:19 Executing JS command: HandleTones with params [{handle = false ;  id = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:19
2015-07-24 08:50:19 ------------------------------------------------------------------------------------------
2015-07-24 08:50:19 Direct code is: none
2015-07-24 08:50:19 ------------------------------------------------------------------------------------------
2015-07-24 08:50:19
2015-07-24 08:50:19 Executing JS command: Play with params [{id = 16214d90008034a8.1437727808.225341 ;  loop = true ;  url = http://ift.tt/1IcykeK ;  } ;  ]
2015-07-24 08:50:20
2015-07-24 08:50:20 ------------------------------------------------------------------------------------------
2015-07-24 08:50:20 Get B24 invite answer: {"COMMAND":"wait","TYPE_CONNECT":"crm","USER_ID":"67","USER_HAVE_PHONE":"Y"}
2015-07-24 08:50:20 ------------------------------------------------------------------------------------------
2015-07-24 08:50:20
2015-07-24 08:50:20
2015-07-24 08:50:20 ------------------------------------------------------------------------------------------
2015-07-24 08:50:20 Action: Send invite to phone67
2015-07-24 08:50:20 from number: 74012XXXXXX
2015-07-24 08:50:20 ------------------------------------------------------------------------------------------
2015-07-24 08:50:20
2015-07-24 08:50:20 Executing JS command: CallUser with params [{id = viXMtbt2TrSdBzRsqoyzSUQTaqDxKESGr_bNdm7UuBI ;  } ;  {callerid = 79112233444 ;  displayName = NULL ;  headers = NULL ;  username = phone67 ;  video = NULL ;  } ;  ]
2015-07-24 08:50:21 Sent event to JS onPhoneEvent with params [{content = {"COMMAND":"wait","OPERATOR_ID":"67"} ;  method = POST ;  name = Application.HttpRequest ;  path = /request/28ccd1538fd88709.1437727808.225342_38.88.16.65/e2a2f42cebd2f7a2 ;  } ;  ]
2015-07-24 08:50:21
2015-07-24 08:50:21 ------------------------------------------------------------------------------------------
2015-07-24 08:50:21 Get command from B24 portal: {"COMMAND":"wait","OPERATOR_ID":"67"}
2015-07-24 08:50:21 ------------------------------------------------------------------------------------------
2015-07-24 08:50:21
2015-07-24 08:50:22 Sent event to JS onPhoneEvent with params [{content = {"COMMAND":"wait","OPERATOR_ID":"67"} ;  method = POST ;  name = Application.HttpRequest ;  path = /request/28ccd1538fd88709.1437727808.225342_38.88.16.65/e2a2f42cebd2f7a2 ;  } ;  ]
2015-07-24 08:50:22
2015-07-24 08:50:22 ------------------------------------------------------------------------------------------
2015-07-24 08:50:22 Get command from B24 portal: {"COMMAND":"wait","OPERATOR_ID":"67"}
2015-07-24 08:50:22 ------------------------------------------------------------------------------------------
2015-07-24 08:50:22
2015-07-24 08:50:25 Sent event to JS onPhoneEvent with params [{content = {"COMMAND":"user","OPERATOR_ID":"67","USER_ID":67} ;  method = POST ;  name = Application.HttpRequest ;  path = /request/28ccd1538fd88709.1437727808.225342_38.88.16.65/e2a2f42cebd2f7a2 ;  } ;  ]
2015-07-24 08:50:25
2015-07-24 08:50:25 ------------------------------------------------------------------------------------------
2015-07-24 08:50:25 Get command from B24 portal: {"COMMAND":"user","OPERATOR_ID":"67","USER_ID":67}
2015-07-24 08:50:25 ------------------------------------------------------------------------------------------
2015-07-24 08:50:25
2015-07-24 08:50:25
2015-07-24 08:50:25 ------------------------------------------------------------------------------------------
2015-07-24 08:50:25 Action: Connect to user67
2015-07-24 08:50:25 from number: 74012XXXXXX
2015-07-24 08:50:25 ------------------------------------------------------------------------------------------
2015-07-24 08:50:25
2015-07-24 08:50:25 Executing JS command: CallUser with params [{id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  } ;  {callerid = 79112233444 ;  displayName = NULL ;  headers = NULL ;  username = user67 ;  video = NULL ;  } ;  ]
2015-07-24 08:50:26 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.Ringing ;  } ;  ]
2015-07-24 08:50:26 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.Ringing ;  } ;  ]
2015-07-24 08:50:26 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.AudioStarted ;  } ;  ]
2015-07-24 08:50:26 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.Connected ;  } ;  ]
2015-07-24 08:50:26 Executing JS command: HangupCall with params [{code =  3.0200E+02 ;  headers = NULL ;  id = viXMtbt2TrSdBzRsqoyzSUQTaqDxKESGr_bNdm7UuBI ;  } ;  ]
2015-07-24 08:50:26 Executing JS command: SendMediaBetween with params [{id1 = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  id2 = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:26 Executing JS command: Record with params [{id = 16214d90008034a8.1437727808.225341 ;  stereo = false ;  } ;  ]
2015-07-24 08:50:26 Sent event to JS onPhoneEvent with params [{id = 16214d90008034a8.1437727808.225341 ;  name = Call.RecordStarted ;  url = http://ift.tt/1IcykeO ;  } ;  ]
2015-07-24 08:50:26
2015-07-24 08:50:26 ------------------------------------------------------------------------------------------
2015-07-24 08:50:26 Send CallStart: COMMAND=StartCall&CALL_ID=16214d90008034a8.1437727808.225341&CALL_DEVICE=WEBRTC&EXTERNAL=N&USER_ID=67
2015-07-24 08:50:26 ------------------------------------------------------------------------------------------
2015-07-24 08:50:26
2015-07-24 08:50:26
2015-07-24 08:50:26 ------------------------------------------------------------------------------------------
2015-07-24 08:50:26 Start recording call: http://ift.tt/1IcykeO
2015-07-24 08:50:26 ------------------------------------------------------------------------------------------
2015-07-24 08:50:26
2015-07-24 08:50:31 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.MessageReceived ;  text = {"COMMAND":"hold"} ;  } ;  ]
2015-07-24 08:50:31
2015-07-24 08:50:31 ------------------------------------------------------------------------------------------
2015-07-24 08:50:31 Get command from B24 user: {"COMMAND":"hold"}
2015-07-24 08:50:31 ------------------------------------------------------------------------------------------
2015-07-24 08:50:31
2015-07-24 08:50:31 Executing JS command: StopMediaBetween with params [{id1 = 16214d90008034a8.1437727808.225341 ;  id2 = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  } ;  ]
2015-07-24 08:50:31 Executing JS command: Stop with params [{id = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:31 Executing JS command: Play with params [{id = 16214d90008034a8.1437727808.225341 ;  loop = true ;  url = http://ift.tt/1IcykeK ;  } ;  ]
2015-07-24 08:50:31 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.MessageReceived ;  text = {"COMMAND":"meter","PERCENT":100,"GRADE":5} ;  } ;  ]
2015-07-24 08:50:31
2015-07-24 08:50:31 ------------------------------------------------------------------------------------------
2015-07-24 08:50:31 Get command from B24 user: {"COMMAND":"meter","PERCENT":100,"GRADE":5}
2015-07-24 08:50:31 ------------------------------------------------------------------------------------------
2015-07-24 08:50:31
2015-07-24 08:50:36 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.MessageReceived ;  text = {"COMMAND":"meter","PERCENT":100,"GRADE":5} ;  } ;  ]
2015-07-24 08:50:36
2015-07-24 08:50:36 ------------------------------------------------------------------------------------------
2015-07-24 08:50:36 Get command from B24 user: {"COMMAND":"meter","PERCENT":100,"GRADE":5}
2015-07-24 08:50:36 ------------------------------------------------------------------------------------------
2015-07-24 08:50:36
2015-07-24 08:50:36 Sent event to JS onPhoneEvent with params [{code = 487 ;  headers = {} ;  id = viXMtbt2TrSdBzRsqoyzSUQTaqDxKESGr_bNdm7UuBI ;  name = Call.Failed ;  reason = Request Terminated ;  } ;  ]
2015-07-24 08:50:39 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.MessageReceived ;  text = {"COMMAND":"unhold"} ;  } ;  ]
2015-07-24 08:50:39
2015-07-24 08:50:39 ------------------------------------------------------------------------------------------
2015-07-24 08:50:39 Get command from B24 user: {"COMMAND":"unhold"}
2015-07-24 08:50:39 ------------------------------------------------------------------------------------------
2015-07-24 08:50:39
2015-07-24 08:50:39 Executing JS command: Stop with params [{id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  } ;  ]
2015-07-24 08:50:39 Executing JS command: Stop with params [{id = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:39 Executing JS command: SendMediaBetween with params [{id1 = 16214d90008034a8.1437727808.225341 ;  id2 = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  } ;  ]
2015-07-24 08:50:41 Sent event to JS onPhoneEvent with params [{headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.MessageReceived ;  text = {"COMMAND":"meter","PERCENT":100,"GRADE":5} ;  } ;  ]
2015-07-24 08:50:41
2015-07-24 08:50:41 ------------------------------------------------------------------------------------------
2015-07-24 08:50:41 Get command from B24 user: {"COMMAND":"meter","PERCENT":100,"GRADE":5}
2015-07-24 08:50:41 ------------------------------------------------------------------------------------------
2015-07-24 08:50:41
2015-07-24 08:50:44 Sent event to JS onPhoneEvent with params [{cost =  0.0000E+00 ;  direction = any outgoing voip ;  duration = 18 ;  headers = {} ;  id = aI2BuvHLRLmeITs0LNDdq1O4AmrcWkZUmtayuwx7k_o ;  name = Call.Disconnected ;  } ;  ]
2015-07-24 08:50:44 Executing JS command: HangupCall with params [{code =  6.0300E+02 ;  headers = NULL ;  id = 16214d90008034a8.1437727808.225341 ;  } ;  ]
2015-07-24 08:50:44 Sent event to JS onPhoneEvent with params [{content = {"COMMAND":"queue","OPERATOR_ID":"67"} ;  method = POST ;  name = Application.HttpRequest ;  path = /request/28ccd1538fd88709.1437727808.225342_38.88.16.65/e2a2f42cebd2f7a2 ;  } ;  ]
2015-07-24 08:50:44
2015-07-24 08:50:44 ------------------------------------------------------------------------------------------
2015-07-24 08:50:44 Get command from B24 portal: {"COMMAND":"queue","OPERATOR_ID":"67"}
2015-07-24 08:50:44 ------------------------------------------------------------------------------------------
2015-07-24 08:50:44
2015-07-24 08:50:44 Sent event to JS onPhoneEvent with params [{cost =  0.0000E+00 ;  direction = any Incoming VoIP ;  duration = 34 ;  headers = {} ;  id = 16214d90008034a8.1437727808.225341 ;  name = Call.Disconnected ;  } ;  ]
2015-07-24 08:50:44
2015-07-24 08:50:44 ------------------------------------------------------------------------------------------
2015-07-24 08:50:44 Send HangupCall: COMMAND=HangupCall&PHONE_NUMBER=74012XXXXXX&ACCOUNT_SEARCH_ID=test&CALL_ID=16214d90008034a8.1437727808.225341&USER_ID=67
2015-07-24 08:50:44 ------------------------------------------------------------------------------------------
2015-07-24 08:50:44
2015-07-24 08:50:44
2015-07-24 08:50:44 ------------------------------------------------------------------------------------------
2015-07-24 08:50:44 Send call history
2015-07-24 08:50:44 Call code: 200
2015-07-24 08:50:44 Call reason: Success call
2015-07-24 08:50:44 Call direction: any%20Incoming%20VoIP
2015-07-24 08:50:44 Call params: COMMAND=AddCallHistory&ACCOUNT_ID=XXXXX&PORTAL_USER_ID=67&APPLICATION_ID=XXXXX&PORTAL_TYPE=RENT&PORTAL_NUMBER=74012XXXXXX&PORTAL_CALL=N&ACCOUNT_SEARCH_ID=test&PHONE_NUMBER=79112233444&URL=http://ift.tt/1Ixg9UL
2015-07-24 08:50:44 ------------------------------------------------------------------------------------------
2015-07-24 08:50:44
2015-07-24 08:50:45 Sent event to JS onPhoneEvent with params [{cost = 0 ;  duration = 18 ;  id = 16214d90008034a8.1437727808.225341 ;  name = Call.RecordStopped ;  reason = Stopped by user ;  url = http://ift.tt/1IcykeO ;  } ;  ]
2015-07-24 08:50:45 Executing JS command: close with params [void ;  ]
2015-07-24 08:50:45 Session terminated


Все вызываемые JavaScript-методы прописаны в API Voximplant.
Чтобы специалистам было проще читать логи, мы сделали небольшой плагин для Chrome, который немного меняет форматирование:

Так как услуга телефонии предоставляется в рамках сервиса «Битрикс24», то к ней мы применяем такие же политики безопасности, как и к самому сервису. Мы гарантируем пользователям, что без их ведома не заходим к ним на порталы, не прослушиваем записи разговоров и так далее. У нас хранятся только статистические данные по количеству и продолжительности звонков. А сами записи разговоров хранятся только у оператора в закрытом разделе и на портале у клиента. Кроме того, круг специалистов, которые могут с разрешения пользователей получать доступ к записям, строго ограничен.

Трудности внедрения


В целом мы не сталкивались с какими-то сложностями при внедрении системы телефонной связи в «Битрикс24». Можно отметить разве что один момент, да и тот вообще не связан с платформой Voximplant.

Проблема была связана с реализацией «SIP-коннектора». Это услуга, позволяющая пользователю подключить свою АТС (облачную или офисную), чтобы совершать звонки через неё, а не через интерфейс браузера. Оказалось, что разные поставщики услуг и операторы облачных АТС (не говоря уже про офисные АТС) по-разному работают с форматами номеров. Кто-то прекрасно понимает, что такое 7495; кому-то нужно указывать ведущий +; для некоторых не нужно указывать код страны. Для казахских операторов нужно вместо 7495 указывать 8495, иначе не дозвонишься. Также абонентам казахских операторов для международных вызовов (на все номера, кроме казахских и российских) нужно перед кодом страны указывать 810.

Вообще проблема нумерации существует даже в головах пользователей: кто-то набирает перед номером префикс +7, кто-то 8; кто-то набирает 007495, а кто-то 0117495. Многие пользователи любят указывать номера в таком виде: 8 (495) 445-11-20, и нам приходится приводить их к международному формату (без знака +) 74954451120.

Не так давно Voximplant реализовали у себя логику, с помощью которой система распознаёт, откуда звонит пользователь. И для РФ, например, префикс 8 заменяет на 7. Мы пока не готовы к этому переходу, нам кажется, что тут нужно ориентироваться не на страну регистрации номера, а на предпочтения пользователя и сложившиеся традиции; всё-таки русскому человеку, находящемуся в командировке, куда привычнее набирать 8, чем какие-то непонятные 00.

Планы на будущее


Возможности платформы Voximplant весьма обширны, и на сегодняшний день мы используем лишь порядка 30% её функционала. Например, мы планируем внедрить у себя систему многоуровневого IVR. Также собираемся дополнить функцию предупреждения о малом остатке средств на счёте неким адаптивным алгоритмом. По умолчанию в Voximplant уведомления рассылаются по достижении некой границы, общей для всех пользователей. Но в каждой компании свой уровень использования телефонной связи, и если для одной 300 рублей на счёте вполне позволят спокойно работать пару дней, пока не будет пополнен баланс, то для другой даже 2000 рублей не хватит на рабочий день. Так что мы хотим дополнить контроллер телефонии модулем, который будет анализировать расходы компании на телефонную связь в течение недели, и исходя из полученных данных будет устанавливать индивидуальный порог остатка на счёте, по достижении которого будет отправляться уведомление.

Кроме того, в ближайшее время мы внедрим возможность совершения звонков на внутренние номера, трансфер звонков между телефонными аппаратами, конференц-связь и фиксацию источника вызова в CRM. Весь этот функционал уже находится на стадии тестирования, и ближе к середине августа станет доступен нашим пользователям.

Иными словами, телефония для «Битрикс24» активно развивается, мы внимательно отслеживаем все пожелания наших пользователей и стараемся максимально быстро внедрять необходимые новинки.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий