Интеграция Asterisk c PlanFix с использованием методов API
В рамках данной статьи будет рассмотрено как выполнить интеграцию Asterisk 13 с PlanFix с использование свободно доступных методов API телефонии. Возможности интеграции: Оповещение о входящем звонке 2. История звонков в карточке контакта 3. Детальная информация о звонке 4. Управление бизнес-процессом из карточки звонка При написании статьи так же использовались следующие скрипты и элементы диалплана: 1. […]
В рамках данной статьи будет рассмотрено как выполнить интеграцию Asterisk 13 с PlanFix с использование свободно доступных методов API телефонии.
Возможности интеграции:
- Оповещение
о входящем звонке
2. История звонков в карточке контакта
3. Детальная информация о звонке
4. Управление бизнес-процессом из карточки звонка
При написании статьи так же использовались следующие скрипты и элементы диалплана:
1. planfix-in.php — скрипт для отображения карточки звонки при прохождении входящего звонка на внутренний номер
2. planfix-end.php — скрипт завершения звонка и отправки ссылки на файл записи разговора
3. extensions_override_freepbx.conf — кастомизация контестов диалплана FreePBX
4. func_odbc.conf — ODBC функция для получения продолжительности звонка
Ранее так же были рассмотрены следующие возможные варианты интеграции:
1. Интеграция Asterisk c PlanFix через облачный сервис Telefum24
2. Интеграция Asterisk c PlanFix через облачный сервис Simplit.io
Инструкция:
- Выполним предварительную настройку PlanFix, для этого пройдем авторизацию с учетной записью с правами администратора и Меню->Управление аккаунтом->Интеграция с другими сервисами->Виртуальные АТС
2. Найдем пункт интеграции «API ПланФикса для телефоний», активируем данный пункт и перейдем в настройки. Из раздела настроек необходимо скопировать и сохранить «Адрес для принятия запросов» и «Ключ авторизации (token) ПланФикса», а так же прописать следующие настройки:
- Адрес АТС — внешний IP сервера Asterisk, с которого будут приходить обращения в PlanFix
- Ключ авторизации АТС — любой произвольный пароль, который необходимо будет так же указать в облачном сервисе Simplit.io для возможности осуществления исходящих звонков
- Короткие номера сотрудников — необходимо выполнить закрепление сотрудника за внутренним номером используемым на Asterisk
3. Выполним подключение по SSH к серверу IP АТС Asterisk. В зависимости от используемой системы(Windows, Linux, MacOS), подключение по SSH можно выполнить с использованием различного дополнительного программного обеспечения(Putty), либо системного терминала.
4. Выполним создание скриптов для обработки входящих звонков.
# nano /opt/planfix-in.php
Скрипт отображения карточки звонка:
<?php
//данные в запросе
$ExtNum = $argv[1]; //Номер клиента
$IntNum = $argv[2]; //Внутренний номер сотрудника
$CallID = $argv[3]; //Уникальный идентификатор
//$FullFname = $argv[4]; //Ссылка на файл записи разговора
//$CallMeDURATION = $argv[5]; //Продолжительность звонка
//$CallMeDISPOSITION = $argv[6]; //Статус звонка
//Формирования массива данных звонка
$result = getPlanfixApi(array(
'cmd' => 'event',
'type' => 'in',
'event' => 'INCOMING',
'phone' => $ExtNum,
'diversion' => $IntNum,
'ext' => $IntNum,
'callid' => $CallID,
'planfix_token' => '93d80b7404f50479f87cccf64b5a07bb',
));
//Функция обращения к методу EVENT API Planfix
function getPlanfixApi($data){
$url = 'https://voxlink-asterisk.planfix.ru/tel/api/';
if (!$url) return false;
$queryUrl = $url;
$queryData = http_build_query($data);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_POST => 1,
CURLOPT_HEADER => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $queryUrl,
CURLOPT_POSTFIELDS => $queryData,
));
$result = curl_exec($curl);
//echo $result;
curl_close($curl);
}
Ключевые переменные для изменения:
- planfix_token — ключ авторизации токен полученный в п.2 данной статьи(в рамках данной статьи: 93d80b7404f50479f87cccf64b5a07bb)
- url — адрес для принятия запрос к API, полученный в п.2 данной статьи (в рамках данной статьи:)
# # nano /opt/planfix-end.php
Скрипт завершения звонка:
<?php
//данные в запросе
$ExtNum = $argv[1]; //Номер клиента
$IntNum = $argv[2]; //Внутренний номер сотрудника
$CallID = $argv[3]; //Уникальный идентификатор
$FullFname = $argv[4]; //Ссылка на файл записи разговора
$CallMeDURATION = $argv[5]; //Продолжительность звонка
$CallMeDISPOSITION = $argv[6]; //Статус звонка
// Проверка статуса звонка
switch ($CallMeDISPOSITION) {
case 'ANSWERED':
$sipcode = 'Success'; // успешный звонок
break;
case 'ANSWER':
$sipcode = 'Success'; // успешный звонок
break;
case 'NO ANSWER':
$sipcode = 'Cancelled'; // нет ответа
break;
case 'BUSY':
$sipcode = 'Missed'; // занято
break;
default:
if(empty($CallMeDISPOSITION)) $sipcode = 'Missed'; //если пустой пришел, то поставим неотвечено
else $sipcode = 'Cancelled'; // отклонено, когда все остальное
break;
}
if ($CallMeDISPOSITION == "ANSWERED") {
//Формирования массива данных звонка
$result = getPlanfixApi(array(
'cmd' => 'event',
'type' => 'in',
'event' => 'ACCEPTED',
'phone' => $ExtNum,
'diversion' => $IntNum,
'ext' => $IntNum,
'callid' => $CallID,
'planfix_token' => '93d80b7404f50479f87cccf64b5a07bb'
));
$result = getPlanfixApi(array(
'cmd' => 'event',
'type' => 'in',
'event' => 'COMPLETED',
'phone' => $ExtNum,
'diversion' => $IntNum,
'ext' => $IntNum,
'callid' => $CallID,
'duration' => $CallMeDURATION,
'is_recorded' => '1',
'status' => $sipcode,
'record_link' => $FullFname,
'planfix_token' => '93d80b7404f50479f87cccf64b5a07bb'
));
}
else {
//Формирования массива данных звонка
$result = getPlanfixApi(array(
'cmd' => 'event',
'type' => 'in',
'event' => 'COMPLETED',
'phone' => $ExtNum,
'diversion' => $IntNum,
'ext' => $IntNum,
'callid' => $CallID,
'duration' => $CallMeDURATION,
'is_recorded' => '1',
'status' => $sipcode,
'record_link' => $FullFname,
'planfix_token' => '93d80b7404f50479f87cccf64b5a07bb'
));
}
//Функция обращения к методу EVENT API Planfix
function getPlanfixApi($data){
$url = 'https://voxlink-asterisk.planfix.ru/tel/api/';
if (!$url) return false;
$queryUrl = $url;
$queryData = http_build_query($data);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_POST => 1,
CURLOPT_HEADER => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $queryUrl,
CURLOPT_POSTFIELDS => $queryData,
));
$result = curl_exec($curl);
//echo $result;
curl_close($curl);
}
Ключевые переменные для изменения:
- planfix_token — ключ авторизации токен полученный в п.2 данной статьи (в рамках данной статьи: 93d80b7404f50479f87cccf64b5a07bb)
- url — адрес для принятия запрос к API, полученный в п.2 данной статьи (в раммках данной статьи:)
# chown asterisk:asterisk /opt/planfix-in.php
# chown asterisk:asterisk /opt/planfix-end.php
5. Добавим обращение к скриптам в диалплан, так же т. к. PlanFix может работать только с mp3 форматом аудио, то выполним настройки для конвертации записи по завершению звонка:
# nano /etc/asterisk/extensions_override_freepbx.conf
И приведем файл к виду:
[macro-hangupcall]
include => macro-hangupcall-custom
;Формируем набор переменных
exten => s,1,Set(IntNum=${CALLERID(num)}) ;Номер клиента
exten => s,n,Set(ExtNum=${DEXTEN}) ;Внутренний номер
exten => s,n,Set(CallID=${CHANNEL(LINKEDID)}) ;Уникальный ID звонка
exten => s,n,Set(FullFname=http://192.168.170.197:8077/monitor/${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.mp3) ;ссылка на файл записи звонка
exten => s,n,Set(CallMeDURATION=${ODBC_TIMECALL(${UNIQUEID})}) ;Продолжительность звонка
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)}) ;статус звонка
;конвертация записи разговора
exten => s,n,System(/usr/bin/lame -h -b 192 /var/spool/asterisk/monitor/${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MON_FMT} /var/spool/asterisk/monitor/${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.mp3)
;удаление wav записи
exten => s,n,System(/bin/rm -rf /var/spool/asterisk/monitor/${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MON_FMT})
exten => s,n,Set(CDR(recordingfile)=${CALLFILENAME}.mp3)
;-------------------------------Planfix-------------------------------
; Обращение к скрипту завершения звонка
exten => s,n,ExecIf($[$[ ${LEN(${CALLERID(num)})} < 5 ]]?System(/usr/bin/php /opt/planfix-end.php 84959898533 ${DEXTEN} ${CHANNEL(LINKEDID)} ${FullFname} ${CallMeDURATION} ${CallMeDISPOSITION})
;-------------------------------Planfix-------------------------------
exten => s,n,Hangup
exten => s,n,MacroExit()
;
[macro-dial-one]
include => macro-dial-one-custom
exten => s,1,Set(DEXTEN=${ARG3})
exten => s,n(setexttocall),ExecIf($[${LEN(${EXTTOCALL})}=0 & ${LEN(${DEXTEN})}>0]?Set(EXTTOCALL=${DEXTEN}))
exten => s,n,Set(DIALSTATUS_CW=)
exten => s,n,GosubIf($["${FROM_DID}"!="" & "${SCREEN}"="" & "${DB(AMPUSER/${DEXTEN}/screen)}"!=""]?screen,1())
exten => s,n,GosubIf($["${DB(CF/${DEXTEN})}"!=""]?cf,1())
exten => s,n,GotoIf($["${DEXTEN:-1}"="#" | "${DB(DND/${DEXTEN})}"=""]?skip1)
exten => s,n,Set(DEXTEN=)
exten => s,n,Set(DIALSTATUS=BUSY)
exten => s,n(skip1),GotoIf($["${DEXTEN}"=""]?nodial)
exten => s,n,GotoIf($["${DEXTEN:-1}"="#"]?continue)
exten => s,n,Set(EXTHASCW=${IF($["${CWIGNORE}"!=""]? :${DB(CW/${DEXTEN})})})
exten => s,n,GotoIf($["${EXTHASCW}"="" | "${DB(CFB/${DEXTEN})}"!="" | "${DB(CFU/${DEXTEN})}"!=""]?next1:cwinusebusy)
exten => s,n(next1),GotoIf($["${DB(CFU/${DEXTEN})}"!="" & ("${EXTENSION_STATE(${DEXTEN})}"="UNAVAILABLE" | "${EXTENSION_STATE(${DEXTEN})}"="UNKNOWN")]?docfu:skip3)
exten => s,n(docfu),Set(DEXTEN=)
exten => s,n,Set(DIALSTATUS=NOANSWER)
exten => s,n,Goto(nodial)
exten => s,n(skip3),GotoIf($["${EXTHASCW}"="" | "${DB(CFB/${DEXTEN})}"!=""]?next2:continue)
exten => s,n(next2),GotoIf($["${EXTENSION_STATE(${DEXTEN})}"="NOT_INUSE" | "${EXTENSION_STATE(${DEXTEN})}"="UNAVAILABLE" | "${EXTENSION_STATE(${DEXTEN})}"="UNKNOWN"]?continue)
exten => s,n,ExecIf($["${DB(CFB/${DEXTEN})}"!="" & "${CFIGNORE}"=""]?Set(DIALSTATUS=BUSY))
exten => s,n,GotoIf($["${EXTHASCW}"!="" | "${DEXTEN:-1}"="#"]?cwinusebusy)
exten => s,n,Set(DEXTEN=)
exten => s,n,Set(DIALSTATUS=BUSY)
exten => s,n,Goto(nodial)
exten => s,n(cwinusebusy),GotoIf($["${EXTHASCW}"!="" & "${CWINUSEBUSY}"="true"]?next3:continue)
exten => s,n(next3),ExecIf($["${EXTENSION_STATE(${DEXTEN})}"!="UNAVAILABLE" & "${EXTENSION_STATE(${DEXTEN})}"!="NOT_INUSE" & "${EXTENSION_STATE(${DEXTEN})}"!="UNKNOWN"]?Set(DIALSTATUS_CW=BUSY))
exten => s,n(continue),GotoIf($["${DEXTEN}"=""]?nodial)
exten => s,n,GosubIf($["${DEXTEN:-1}"!="#"]?dstring,1():dlocal,1())
exten => s,n,GotoIf($[${LEN(${DSTRING})}=0]?nodial)
exten => s,n,GotoIf($["${DEXTEN:-1}"="#"]?skiptrace)
exten => s,n,GosubIf($[${REGEX("^[\+]?[0-9]+$" ${CALLERID(number)})} = 1]?ctset,1():ctclear,1())
exten => s,n(skiptrace),Set(D_OPTIONS=${IF($["${NODEST}"!="" & ${REGEX("(M[(]auto-blkvm[)])" ${ARG2})} != 1]?${ARG2}M(auto-blkvm):${ARG2})})
exten => s,n,Noop(Blind Transfer: ${BLINDTRANSFER}, Attended Transfer: ${ATTENDEDTRANSFER}, User: ${AMPUSER}, Alert Info: ${ALERT_INFO})
exten => s,n,ExecIf($["${ALERT_INFO}"="" & ${LEN(${AMPUSER})}!=0 & ${LEN(${BLINDTRANSFER})}=0 & ${LEN(${ATTENDEDTRANSFER})}=0]?Set(ALERT_INFO=))
exten => s,n,ExecIf($[${LEN(${BLINDTRANSFER})}!=0]?Set(ALERT_INFO=))
exten => s,n,ExecIf($[${LEN(${ATTENDEDTRANSFER})}!=0]?Set(ALERT_INFO=))
exten => s,n,ExecIf($["${RVOL}"!=""]?Set(ALERT_INFO=${IF($["${ALERT_INFO}"!=""]?${ALERT_INFO}:Normal)}\;volume=${RVOL}))
exten => s,n,ExecIf($["${RVOL}"="" & "${DB(AMPUSER/${EXTTOCALL}/rvolume)}" != ""]?Set(ALERT_INFO=${IF($["${ALERT_INFO}"!=""]?${ALERT_INFO}:Normal)}\;volume=${DB(AMPUSER/${EXTTOCALL}/rvolume)}))
exten => s,n,GosubIf($["${ALERT_INFO}"!="" & "${ALERT_INFO}"!=" "]?func-set-sipheader,s,1(Alert-Info,${ALERT_INFO}))
exten => s,n,ExecIf($[("${MOHCLASS}"!="default") & ("${MOHCLASS}"!="")]?Set(CHANNEL(musicclass)=${MOHCLASS}))
exten => s,n,GosubIf($["${QUEUEWAIT}"!=""]?qwait,1())
exten => s,n,Set(__CWIGNORE=${CWIGNORE})
exten => s,n,Set(__KEEPCID=TRUE)
exten => s,n,GotoIf($["${USEGOTO}"="1"]?usegoto,1)
exten => s,n,Set(CALLEE_USERAGENT=${SIPPEER(${DEXTEN},useragent)})
exten => s,n,ExecIf(${REGEX("^Fanvil" ${CALLEE_USERAGENT})}?AGI(cut_callerid.pl,CALLERID(name),29,bytes))
exten => s,n,GotoIf($["${DB(AMPUSER/${EXTTOCALL}/cidname)}" = "" || "${DB(AMPUSER/${AMPUSER}/cidname)}" = ""]?godial)
exten => s,n,Set(CONNECTEDLINE(name,i)=${DB(AMPUSER/${EXTTOCALL}/cidname)})
exten => s,n,Set(CALLER_USERAGENT=${SIPPEER(${AMPUSER},useragent)})
exten => s,n,GotoIf($[${REGEX("^Fanvil" ${CALLER_USERAGENT})} = 0]?skip_cut_clname)
exten => s,n,Set(CUR_CONNECTEDLINE_NAME=${CONNECTEDLINE(name)})
exten => s,n,AGI(cut_callerid.pl,CUR_CONNECTEDLINE_NAME,39,bytes)
exten => s,n,Set(CONNECTEDLINE(name,i)=${CUR_CONNECTEDLINE_NAME})
exten => s,n(skip_cut_clname),Set(CONNECTEDLINE(num)=${EXTTOCALL})
exten => s,n,Set(D_OPTIONS=${D_OPTIONS}I)
exten => s,n(godial),Macro(dialout-one-predial-hook,)
exten => s,n,ExecIf($["${DIRECTION}" = "INBOUND"]?Set(D_OPTIONS=${STRREPLACE(D_OPTIONS,T)}I))
exten => s,n(dialapp),Noop()
;-------------------------------Planfix-------------------------------
; Обращение к скрипту отображения карточки звонка
exten => s,n,ExecIf($[$[ ${LEN(${CALLERID(num)})} < 5 ]]?System(/usr/bin/php /opt/planfix-in.php 84959898533 ${DEXTEN} ${CHANNEL(LINKEDID)})
;-------------------------------Planfix-------------------------------
exten => s,n,Dial(${DSTRING},${ARG1},${D_OPTIONS}b(func-apply-sipheaders^s^1))
exten => s,n,ExecIf($["${DIALSTATUS}"="ANSWER" & "${CALLER_DEST}"!=""]?MacroExit())
exten => s,n,ExecIf($["${DIALSTATUS_CW}"!=""]?Set(DIALSTATUS=${DIALSTATUS_CW}))
exten => s,n,GosubIf($[("${SCREEN}"!=""&("${DIALSTATUS}"="TORTURE"|"${DIALSTATUS}"="DONTCALL"))|"${DIALSTATUS}"="ANSWER"]?s-${DIALSTATUS},1())
exten => s,n,MacroExit()
exten => s,n(nodial),Noop()
exten => s,n,ExecIf($["${DIALSTATUS}" = ""]?Set(DIALSTATUS=NOANSWER))
exten => s,n,Noop(Returned from dial-one with nothing to call and DIALSTATUS: ${DIALSTATUS})
exten => s,n,MacroExit()
Для получения времени продолжительности звонка создадим функцию ODBC:
# nano /etc/asterisk/func_odbc.conf
И приведем данный файл к виду:
[TIMECALL]
dsn=asteriskcdrdb
readsql=SELECT TIMESTAMPDIFF(SECOND,(SELECT eventtime FROM cel WHERE uniqueid='${ARG1}' AND eventtype='BRIDGE_ENTER'),(SELECT eventtime FROM cel WHERE uniqueid='${ARG1}' AND eventtype='BRIDGE_EXIT')) as real_second_count;
6. На данном шаге все необходимые настройки произведены, выполним применение изменений в конфигурационных файлах и приступим к тестированию.
Остались вопросы?
Я - Виталий Шелест, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.
категории
- DECT
- Linux
- Вспомогательный софт при работе с Asterisk
- Интеграция с CRM и другими системами
- Интеграция с другими АТС
- Использование Elastix
- Использование FreePBX
- Книга
- Мониторинг и траблшутинг
- Настройка Asterisk
- Настройка IP-телефонов
- Настройка VoIP-оборудования
- Новости и Статьи
- Подключение операторов связи
- Разработка под Asterisk
- Установка Asterisk
VoIP оборудование
ближайшие курсы
Новые статьи
10 доводов в пользу Asterisk
Распространяется бесплатно.
Asterisk – программное обеспечение с открытым исходным кодом, распространяется по лицензии GPL. Следовательно, установив один раз Asterisk вам не придется дополнительно платить за новых абонентов, подключение новых транков, расширение функционала и прочие лицензии. Это приближает стоимость владения станцией к нулю.
Безопасен в использовании.
Любое программное обеспечение может стать объектом интереса злоумышленников, в том числе телефонная станция. Однако, сам Asterisk, а также операционная система, на которой он работает, дают множество инструментов защиты от любых атак. При грамотной настройке безопасности у злоумышленников нет никаких шансов попасть на станцию.
Надежен в эксплуатации.
Время работы серверов некоторых наших клиентов исчисляется годами. Это значит, что Asterisk работает несколько лет, ему не требуются никакие перезагрузки или принудительные отключения. А еще это говорит о том, что в районе отличная ситуация с электроэнергией, но это уже не заслуга Asterisk.
Гибкий в настройке.
Зачастую возможности Asterisk ограничивает только фантазия пользователя. Ни один конструктор шаблонов не сравнится с Asterisk по гибкости настройки. Это позволяет решать с помощью Asterisk любые бизнес задачи, даже те, в которых выбор в его пользу не кажется изначально очевидным.
Имеет огромный функционал.
Во многом именно Asterisk показал какой должна быть современная телефонная станция. За многие годы развития функциональность Asterisk расширилась, а все основные возможности по-прежнему доступны бесплатно сразу после установки.
Интегрируется с любыми системами.
То, что Asterisk не умеет сам, он позволяет реализовать за счет интеграции. Это могут быть интеграции с коммерческими телефонными станциями, CRM, ERP системами, биллингом, сервисами колл-трекинга, колл-бэка и модулями статистики и аналитики.
Позволяет телефонизировать офис за считанные часы.
В нашей практике были проекты, реализованные за один рабочий день. Это значит, что утром к нам обращался клиент, а уже через несколько часов он пользовался новой IP-АТС. Безусловно, такая скорость редкость, ведь АТС – инструмент зарабатывания денег для многих компаний и спешка во внедрении не уместна. Но в случае острой необходимости Asterisk готов к быстрому старту.
Отличная масштабируемость.
Очень утомительно постоянно возвращаться к одному и тому же вопросу. Такое часто бывает в случае некачественного исполнения работ или выбора заведомо неподходящего бизнес-решения. С Asterisk точно не будет такой проблемы! Телефонная станция, построенная на Asterisk может быть масштабируема до немыслимых размеров. Главное – правильно подобрать оборудование.
Повышает управляемость бизнеса.
Asterisk дает не просто набор полезных функций, он повышает управляемость организации, качества и комфортности управления, а также увеличивает прозрачность бизнеса для руководства. Достичь этого можно, например, за счет автоматизации отчетов, подключения бота в Telegram, санкционированного доступа к станции из любой точки мира.
Снижает расходы на связь.
Связь между внутренними абонентами IP-АТС бесплатна всегда, независимо от их географического расположения. Также к Asterisk можно подключить любых операторов телефонии, в том числе GSM сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.