В сети есть разные варианты интеграции IP-АТС Asterisk и CRM Битрикс24, но мы, все таки, решили написать свою.
По функционалу все стандартно:
- Кликом на ссылку с номером телефона клиента в Битрикс24, Asterisk соединяет внутренний номер пользователя, от имени которого это клик совершен, с номером телефона клиента. В Битрикс24 фиксируется запись о звонке и по окончании вызова подтягивается запись разговора.
- На Asterisk поступает звонок извне — в интерфейсе Битрикс24 показываем карточку клиента тому сотруднику, на номер которого этот звонок прилетел.
Если такого клиента нет, откроем карточку создания нового лида.
Как только звонок завершен, отражаем это в карточке и подтягиваем запись разговора.
Под катом расскажу как все настроить у себя и дам линк на github — да-да, забирайте и пользуйтесь!
Общее описание.
Свою интеграцию мы назвали CallMe.
CallMe — это небольшое веб-приложение, написанное на PHP.
Используемые технологии и сервисы:
- PHP 5.6
- PHP AMI-библиотека (https://github.com/marcelog/PAMI)
- Composer
- Nginx + php-fpm
- supervisor
- AMI (Asterisk menegement interface)
- Вебхуки Bitrix (упрощенная реализация REST API)
Предварительная настройка.
На сервере с Astrisk необходимо установить web-сервер (у нас это nginx+php-fpm), supervisor и git.
Команда для установки (CentOS):
yum install nginx php-fpm supervisor git
Переходим директорию, доступную веб-серверу, тянем из гита приложение и выставляем нужные права на папку:
cd /var/www
git clone https://github.com/ViStepRU/callme.git
chown nginx. -R callme/
Далее настроим nginx, наш конфиг разместился в
/etc/nginx/conf.d/pbx.vistep.ru.conf
server {
server_name www.pbx.vistep.ru pbx.vistep.ru;
listen *:80;
rewrite ^ https://pbx.vistep.ru$request_uri? permanent;
}
server {
# listen *:80;
# server_name pbx.vistep.ru;
access_log /var/log/nginx/pbx.vistep.ru.access.log main;
error_log /var/log/nginx/pbx.vistep.ru.error.log;
listen 443 ssl http2;
server_name pbx.vistep.ru;
resolver 8.8.8.8;
ssl_stapling on;
ssl on;
ssl_certificate /etc/letsencrypt/live/pbx.vistep.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pbx.vistep.ru/privkey.pem;
ssl_dhparam /etc/nginx/certs/dhparam.pem;
ssl_session_timeout 24h;
ssl_session_cache shared:SSL:2m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers kEECDH+AES128:kEECDH:kEDH:-3DES:kRSA+AES128:kEDH+3DES:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!MD5:!EXPORT:!LOW:!SEED:!CAMELLIA:!IDEA:!PSK:!SRP:!SSLv2;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000;";
add_header Content-Security-Policy-Report-Only "default-src https:; script-src https: 'unsafe-eval' 'unsafe-inline'; style-src https: 'unsafe-inline'; img-src https: data:; font-src https: data:; report-uri /csp-report";
root /var/www/callme;
index index.php;
location ~ /\. {
deny all; # запрет для скрытых файлов
}
location ~* /(?:uploads|files)/.*\.php$ {
deny all; # запрет для загруженных скриптов
}
location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
access_log off;
log_not_found off;
expires max; # кеширование статики
}
location ~ \.php {
root /var/www/callme;
index index.php;
fastcgi_pass unix:/run/php/php5.6-fpm.sock;
# fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}
}
Разбор конфига, вопросы безопасности, получение сертификата и даже выбор web-сервера я оставлю за рамками статьи — об этом много написано. У приложения нет ограничений, оно работает и по http и по https.
У нас — https, сертификат let's encrypt.
Если вы все сделали правильно, то перейдя по ссылке должны увидеть нечто подобное
Настройка Битрикс24.
Создадим два вебхука.
Входящий вебхук.
Под учетной записью администратора (с id 1) идем по пути:
Приложения -> Вебхуки -> Добавить вебхук -> Входящий вебхук
Заполняем параметры входящего вебхука как на скринах:
И жмем сохранить.
После сохранения Битрикс24 предоставит URL входящего вебхука, например:
Сохраните себе ваш вариант URL без завершающего /profile/ — он будет использоваться в приложении для работы с входящими звонками.
У меня это
https://b24-xsynia.bitrix24.ru/rest/1/7eh61lh8pahw0fwt
Исходящий вебхук.
Приложения -> Вебхуки -> Добавить вебхук -> Исходящий вебхук
Подробности снова на скринах:
Сохраняем и получаем код авторизации
У меня это
xcrp2ylhzzd2v43cmfjqmkvrgrcbkni6
его тоже нужно скопировать себе, он нужен для совершения исходящих звонков.
Важно!
На сервере Битрикс24 должен быть настроен ssl-сертификат (можно использовать letsencrypt), иначе api битрикса не будет работать. Если у вас облачная версия, можете не волноваться — там уже есть ssl.
Настройка asterisk.
Для успешного взаимодействия Asterisk и Bitrix24 нам нужно добавить AMI-пользователя callme в manager.conf:
[callme]
secret = JD3clEB8_f23r-3ry84gJ
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.0
permit= 10.100.111.249/255.255.255.255
permit = 192.168.254.0/255.255.255.0
read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
write = system,call,agent,log,verbose,user,config,command,reporting,originate
Далее есть несколько хитростей, которые потребуется внедрить посредствам dialplan (у нас это extensions.ael)
Привожу весь файл, а после дам пояснения:
globals {
WAV=/var/www/pbx.vistep.ru/callme/records/wav; //Временный каталог с WAV
MP3=/var/www/pbx.vistep.ru/callme/records/mp3; //Куда выгружать mp3 файлы
URLRECORDS=https://pbx.vistep.ru/callme/records/mp3;
RECORDING=1; // Запись, 1 - включена.
};
macro recording(calling,called) {
if ("${RECORDING}" = "1"){
Set(fname=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H_%M)}-${calling}-${called});
Set(datedir=${STRFTIME(${EPOCH},,%Y/%m/%d)});
System(mkdir -p ${MP3}/${datedir});
System(mkdir -p ${WAV}/${datedir});
Set(monopt=nice -n 19 /usr/bin/lame -b 32 --silent "${WAV}/${datedir}/${fname}.wav" "${MP3}/${datedir}/${fname}.mp3" && rm -f "${WAV}/${fname}.wav" && chmod o+r "${MP3}/${datedir}/${fname}.mp3");
Set(FullFname=${URLRECORDS}/${datedir}/${fname}.mp3);
Set(CDR(filename)=${fname}.mp3);
Set(CDR(recordingfile)=${fname}.wav);
Set(CDR(realdst)=${called});
MixMonitor(${WAV}/${datedir}/${fname}.wav,b,${monopt});
};
};
context incoming {
888999 => {
&recording(${CALLERID(number)},${EXTEN});
Answer();
ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp()); // выставляем CallerID если узнали его у Битрикс24
Set(CallStart=${STRFTIME(epoch,,%s)});
Queue(Q1,tT);
Set(CallMeDISPOSITION=${CDR(disposition)});
Hangup();
}
h => {
Set(CDR_PROP(disable)=true);
Set(CallStop=${STRFTIME(epoch,,%s)});
Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)});
ExecIF(${ISNULL(${CallMeDISPOSITION})}?Set(CallMeDISPOSITION=${CDR(disposition)}):NoOP(=== CallMeDISPOSITION already was set ===));
System(curl -s https://pbx.vistep.ru/CallMeOut.php --data action=sendcall2b24 --data call_id=${CallMeCALL_ID} --data-urlencode FullFname=${FullFname} --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition=${CallMeDISPOSITION});
}
}
context default {
_X. => {
Hangup();
}
};
context dial_out {
_[1237]XX => {
&recording(${CALLERID(number)},${EXTEN});
Set(__CallIntNum=${CALLERID(num)})
Set(CallStart=${STRFTIME(epoch,,%s)});
Dial(SIP/${EXTEN},,tTr);
Hangup();
}
_11XXX => {
&recording(${CALLERID(number)},${EXTEN});
Set(CallStart=${STRFTIME(epoch,,%s)});
Set(__CallIntNum=${CALLERID(num)});
Dial(SIP/${EXTEN:2}@toOurAster,,t);
Hangup();
}
_. => {
&recording(${CALLERID(number)},${EXTEN});
Set(__CallIntNum=${CALLERID(num)})
Set(CallStart=${STRFTIME(epoch,,%s)});
Dial(SIP/${EXTEN}@toOurAster,,t);
Hangup();
}
h => {
Set(CDR_PROP(disable)=true);
Set(CallStop=${STRFTIME(epoch,,%s)});
Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)});
if(${ISNULL(${CallMeDISPOSITION})}) {
Set(CallMeDISPOSITION=${CDR(disposition)});
}
System(curl -s http://pbx.vistep.ru/CallMeOut.php --data action=sendcall2b24 --data call_id=${CallMeCALL_ID} --data-urlencode FullFname=${FullFname} --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition=${CallMeDISPOSITION});
}
};
Начнем с самого начала: директива globals.
Переменная URLRECORDS хранит в себе URL к файлам записей разговоров, по которому Bitrix24 будет их подтягивать в карточку контакта.
Далее нам интересен макрос макрос recording.
Здесь, помимо записи разговоров, мы установим переменную FullFname.
Set(FullFname=${URLRECORDS}/${datedir}/${fname}.mp3);
Она хранит полный URL к конкретному файлу (макрос вызывается везде).
Разберем исходящий звонок:
_. => {
&recording(${CALLERID(number)},${EXTEN});
Set(__CallIntNum=${CALLERID(num)})
Set(CallStart=${STRFTIME(epoch,,%s)});
Dial(SIP/${EXTEN}@toOurAster,,t);
Hangup();
}
h => {
Set(CDR_PROP(disable)=true);
Set(CallStop=${STRFTIME(epoch,,%s)});
Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)});
if(${ISNULL(${CallMeDISPOSITION})}) {
Set(CallMeDISPOSITION=${CDR(disposition)});
}
System(curl -s http://pbx.vistep.ru/CallMeOut.php --data action=sendcall2b24 --data call_id=${CallMeCALL_ID} --data-urlencode FullFname=${FullFname} --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition=${CallMeDISPOSITION});
}
Допустим мы звоним на 89991234567, первым делом попадаем сюда:
&recording(${CALLERID(number)},${EXTEN});
т.е. вызывается макрос записи разговора и проставляются нужные переменные.
Далее
Set(__CallIntNum=${CALLERID(num)})
Set(CallStart=${STRFTIME(epoch,,%s)});
записываем кто инициировал звонок и фиксируем время старта звонка.
И по его завершению, в специальном контексте h
h => {
Set(CDR_PROP(disable)=true);
Set(CallStop=${STRFTIME(epoch,,%s)});
Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)});
if(${ISNULL(${CallMeDISPOSITION})}) {
Set(CallMeDISPOSITION=${CDR(disposition)});
}
System(curl -s http://pbx.vistep.ru/CallMeOut.php --data action=sendcall2b24 --data call_id=${CallMeCALL_ID} --data-urlencode FullFname=${FullFname} --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition=${CallMeDISPOSITION});
}
отключаем запись в таблицу CDR для этого экстеншена (не нужно оно там), выставляем время завершения звонка, вычисляем продолжительность, если результат звонка не известен — ставим (переменная CallMeDISPOSITION) и, последним шагом, шлем все битриксу через системный curl.
И еще немного магии — входящий звонок:
888999 => {
&recording(${CALLERID(number)},${EXTEN});
Answer();
ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp()); // выставляем CallerID если узнали его у Битрикс24
Set(CallStart=${STRFTIME(epoch,,%s)}); // начинаем отсчет времени звонка
Queue(Q1,tT);
Set(CallMeDISPOSITION=${CDR(disposition)});
Hangup();
}
Здесь нас интересует только одна строка.
ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp());
Она говорит АТС установить CallerID(name) равным переменной CallMeCallerIDName.
Сама переменная CallMeCallerIDName, в свою очередь, устанавливается приложением CallMe (если в Bitrix24 есть ФИО для номера позвонившего — установим в качестве CallerID(name), нет — ничего не будем делать).
Настройка приложения.
Файл настроек приложения — /var/www/pbx.vistep.ru/config.php
Описание параметров приложения:
- CallMeDEBUG — если 1, то в лог файл будут писаться все события, обрабатываемые приложением, 0 — ничего не пишем
- tech — SIP/PJSIP/IAX/etc
- authToken — токен авторизации битрикс24, код авторизации исходящего вебхука
- bitrixApiUrl — URL входящего вебхука, без profile/
- extentions — список внешних номеров
- context — контекст для оригинации звонка
- listener_timeout — скорость обработки событий от asterisk
- asterisk — массив с настройками подключения к астериску:
- host — ip или hostname сервера астериск
- scheme — схема подключения (tcp://, tls://)
- port — порт
- username — имя пользователя
- secret — пароль
- connect_timeout — таймаут подключения
- read_timeout — таймаут чтения
пример файла настроек:
1, // дебаг сообщения в логе: 1 - пишем, 0 - не пишем
'tech' => 'SIP',
'authToken' => 'xcrp2ylhzzd2v43cmfjqmkvrgrcbkni6', //токен авторизации битрикса
'bitrixApiUrl' => 'https://b24-xsynia.bitrix24.ru/rest/1/7eh61lh8pahw0fwt', //url к api битрикса (входящий вебхук)
'extentions' => array('888999'), // список внешних номеров, через запятую
'context' => 'dial_out', //исходящий контекст для оригинации звонка
'asterisk' => array( // настройки для подключения к астериску
'host' => '10.100.111.249',
'scheme' => 'tcp://',
'port' => 5038,
'username' => 'callme',
'secret' => 'JD3clEB8_f23r-3ry84gJ',
'connect_timeout' => 10000,
'read_timeout' => 10000
),
'listener_timeout' => 300, //скорость обработки событий от asterisk
);
Настройка supervisor.
Supervisor служит для запуска процесса-обработчика событий от Asterisk CallMeIn.php, который отслеживает входящие звонки и взаимодействует с Битрикс24 (показать карточку, скрыть карточку и т.д.).
Файл настроек, который необходимо создать:
/etc/supervisord.d/callme.conf
[program:callme]
command=/usr/bin/php CallMeIn.php
directory=/var/www/pbx.vistep.ru
autostart=true
autorestart=true
startretries=5
stderr_logfile=/var/www/pbx.vistep.ru/logs/daemon.log
stdout_logfile=/var/www/pbx.vistep.ru/logs/daemon.log
Запуск и рестарт приложения:
supervisorctl start callme
supervisorctl restart callme
просмотр статуса работы приложения:
supervisorctl status callme
callme RUNNING pid 11729, uptime 17 days, 16:58:07
Заключение
Получилось достаточно сложно, но уверен — опытный администратор сумеет внедрить у себя и порадовать своих пользователей.
Как обещал, линк на гитхаб — github.com/ViStepRU/callme
Вопросы, пожелания — прошу в комменты. Также если интересно как шла разработка этой интеграции, напишите, а в очередной статье я постараюсь раскрыть все более детально.
Комментариев нет:
Отправить комментарий