Интеграция Asterisk и Битрикс24

d2blrdldi6khjwtmv0hylxi7vvq.png

В сети есть разные варианты интеграции 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.

Если вы все сделали правильно, то перейдя по ссылке должны увидеть нечто подобное
_0agijgm_tagwpymgdnehhbpbrg.png

Настройка Битрикс24.


Создадим два вебхука.
Входящий вебхук.
Под учетной записью администратора (с id 1) идем по пути:
Приложения → Вебхуки → Добавить вебхук → Входящий вебхук
wrgee-n17vzvnqfixsipxgiwg1w.png

Заполняем параметры входящего вебхука как на скринах:
mgkwhhqed4oxivgf-ngxwjxnrqi.png
8vvcaqms3hfrrssui-ngmi-jzc0.png

И жмем сохранить.
После сохранения Битрикс24 предоставит URL входящего вебхука, например:
suulvhnquhept_hkecbni3zmqzk.png

Сохраните себе ваш вариант URL без завершающего /profile/ — он будет использоваться в приложении для работы с входящими звонками.
У меня это
https://b24-xsynia.bitrix24.ru/rest/1/7eh61lh8pahw0fwt

Исходящий вебхук.
Приложения → Вебхуки → Добавить вебхук → Исходящий вебхук
Подробности снова на скринах:
50ucjbiwvtyqcylie_piamvp1py.png
dpgpxg97ora4e-vejqx_q0_1tom.png

Сохраняем и получаем код авторизации
touiwa_xq2j4svjcdmndny9fkfw.png

У меня это
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
Вопросы, пожелания — прошу в комменты. Также если интересно как шла разработка этой интеграции, напишите, а в очередной статье я постараюсь раскрыть все более детально.

© Habrahabr.ru