Asterisk. Интеграция с amoCRM, step-by-step guid

от автора

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

Здесь я представлю свой опыт подключения Asterisk к amoCRM в виде пошаговой инструкции, осветив все необходимые нюансы, начиная от получения ssl-сертификата, настройки web-сервера и заканчивая демонстрацией работы получившейся связки.

Вводные

На нашем тестовом стенде установлены:

  • ОС Debian
    lsb_release -a No LSB modules are available. Distributor ID:	Debian Description:	Debian GNU/Linux 8.7 (jessie) Release:	8.7 Codename:	jessie

  • IP PBX Asterisk
    *CLI> core show version  Asterisk 13.14.0 built by root @ asterisk.vistep.ru on a x86_64 running Linux on 2017-03-29 05:47:19 UTC

  • web-сервер NGINX
    sudo nginx -v nginx version: nginx/1.10.3

  • PHP-FPM
    php5-fpm -v PHP 5.6.30-0+deb8u1 (fpm-fcgi) (built: Feb  8 2017 08:51:18) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies     with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies

  • Доменное имя для теста
    tawny-owl:~$ dig +short asterisk.vistep.ru 138.201.164.52 

Получаем ssl-сертификат

В данном гайде мы будем использовать бесплатный сертификат от Let’s Encrypt.
Изначально я планировал использовать StartSSL и написал пошаговую инструкцию по получению сертификатов там, но только после заметил, что их корневые сертификаты не принимает ни один браузер.
Процедура его получения достаточно тривиальна, но я все же опишу ее по шагам.

  1. Переходим на сайт letsencrypt.org и жмем «Get Started»
    скрин

  2. Далее нас интересует раздел With Shell Access, в котором мы найдем все необходимые инструкции
    скрин

  3. Переходим на certbot.eff.org и выбираем наше ПО
    скрин

  4. После чего следуем инструкциям и выполняем
    несколько команд в косноли

     echo "deb http://ftp.debian.org/debian jessie-backports main" >> /etc/apt/sources.list apt-get update apt-get install certbot -t jessie-backports 

  5. Затем необходимо отправить запрос на получение сертификата при помощи утилиты certbot.
    Я пошел по наиболее примитивному пути:
    вбил команду
    certbot certonly

    и следовал этапам мастера, где указал свой email, путь к webroot, имя домена и пр.

    скрины





  6. На выходе видим
    заветное

    IMPORTANT NOTES:  - Congratulations! Your certificate and chain have been saved at    /etc/letsencrypt/live/asterisk.vistep.ru/fullchain.pem. Your cert    will expire on 2017-06-27. To obtain a new or tweaked version of    this certificate in the future, simply run certbot again. To    non-interactively renew *all* of your certificates, run "certbot    renew"  - If you like Certbot, please consider supporting our work by:     Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate    Donating to EFF:                    https://eff.org/donate-le 
  7. Копируем сертификаты в места их дислокации
    Скрытый текст

     cp /etc/letsencrypt/live/asterisk.vistep.ru/privkey.pem /etc/nginx/certs/vistep.ru.key cp /etc/letsencrypt/live/asterisk.vistep.ru/fullchain.pem /etc/nginx/certs/vistep.ru.pem 

Настройка web-сервера

Как и было сказано во вводной, мы будем использовать web-сервер NGINX.
Не стану разводить hollywar’ов и как-то мотивировать свой выбор, просто — у нас стоит NGINX и мы будем настраивать его.
Основой конфига послужила замечательная статья DimaSmirnov «Nginx и https. Получаем класс А+», за что ему, пользуясь случаем, выражаю благодарность.

Итак, конфигурационный файл web-сервера имеет следующий вид (некоторые комментарии даны непосредственно в конфиге):

/etc/nginx/conf.d/asterisk.vistep.ru.conf

server {     server_name asterisk.vistep.ru;     listen 138.201.164.52:80;     rewrite ^  https://asterisk.vistep.ru$request_uri? permanent; } server {     access_log /var/log/nginx/asterisk.vistep.ru.access.log;     error_log /var/log/nginx/asterisk.vistep.ru.error.log;     listen 443 ssl;     server_name asterisk.vistep.ru;     resolver 8.8.8.8;     ssl_stapling on;     ssl on;     ssl_certificate /etc/nginx/certs/vistep.ru.pem;     ssl_certificate_key /etc/nginx/certs/vistep.ru.key;     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/asterisk; 	index index.php index.html index.htm index.nginx-debian.html;      location records/ {     autoindex off;     allow 89.108.120.223;         allow 89.108.122.9;         allow 95.213.171.78;         allow 95.213.156.46;         allow 209.160.27.20;         allow 89.189.163.20; # адреса выше - адреса amoCRM и они нужны, а этот - мой домашний, не нужно его вставлять в конфиг ;) актуальный список адресов - https://www.amocrm.ru/security/iplist.txt     deny all; }   	location / { 		try_files $uri $uri/ =404;         allow 89.108.120.223;          allow 89.108.122.9;         allow 95.213.171.78;         allow 95.213.156.46;         allow 209.160.27.20;         allow 89.189.163.20; # адреса выше - адреса amoCRM и они нужны, а этот - мой домашний, не нужно его вставлять в конфиг ;) актуальный список адресов - https://www.amocrm.ru/security/iplist.txt 	deny all; 	} 	location ~ \.php$ { 	        fastcgi_pass unix:/var/run/php5-fpm.sock; 	       	fastcgi_index index.php; 	        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 	        include fastcgi_params; 		fastcgi_buffers 16 16k;  		fastcgi_buffer_size 32k; 	} } 

В папке /var/www/asterisk/ (в моем случае) нужно создать симлинк на папку, где будут храниться файлы записей разговоров (о настройке записей разговоров расскажу ниже)

Скрытый текст

cd /var/www/asterisk/
ln -s /var/calls/ records

Еще несколько слов о сертификатах.
Помимо уже размещенных на своих местах vistep.ru.key и vistep.ru.pem, нам также понадобится dhparam.pem.

создадим его

openssl dhparam -out /etc/nginx/certs/dhparam.pem 4096 

За сим с настройкой NGINX закончим и перейдем к настройке Asterisk.

Настройка IP PBX Asterisk

Для того, чтобы amoCRM могла коммуницировать с нашей Asterisk, manager.conf и http.conf нужно привести к виду:

manager.conf

 [general] enabled = yes port = 5038 bindaddr = 0.0.0.0 webenabled = yes httptimeout = 60 debug = on  [amocrm] secret = JD3clEB8f4-_3ry84gJ deny = 0.0.0.0/0.0.0.0 permit = 127.0.0.1/255.255.255.0 read = cdr,reporting,originate write = reporting,originate 

http.conf

 [general] enabled=yes enablestatic=yes bindaddr=0.0.0.0 bindport=8088 prefix=asterisk 

Перезапустим Asterisk и проверим все ли поднялось как нам нужно

выхлоп

asterisk*CLI> http show status
HTTP Server Status:
Prefix: /asterisk
Server: Asterisk/13.14.0
Server Enabled and Bound to 0.0.0.0:8088

Enabled URI's:
/asterisk/httpstatus => Asterisk HTTP General Status
/asterisk/phoneprov/... => Asterisk HTTP Phone Provisioning Tool
/asterisk/amanager => HTML Manager Event Interface w/Digest authentication
/asterisk/arawman => Raw HTTP Manager Event Interface w/Digest authentication
/asterisk/manager => HTML Manager Event Interface
/asterisk/rawman => Raw HTTP Manager Event Interface
/asterisk/static/... => Asterisk HTTP Static Delivery
/asterisk/amxml => XML Manager Event Interface w/Digest authentication
/asterisk/mxml => XML Manager Event Interface
/asterisk/ari/... => Asterisk RESTful API
/asterisk/ws => Asterisk HTTP WebSocket

Enabled Redirects:
None.
asterisk*CLI> manager show settings

Global Settings:
----------------
Manager (AMI): Yes
Web Manager (AMI/HTTP): Yes
TCP Bindaddress: 0.0.0.0:5038
HTTP Timeout (minutes): 60
TLS Enable: No
TLS Bindaddress: Disabled
TLS Certfile: asterisk.pem
TLS Privatekey:
TLS Cipher:
Allow multiple login: Yes
Display connects: Yes
Timestamp events: No
Channel vars:
Debug: Yes

Пример диалплана (я использую ael, но уверен, что любой сможет перевести в lua или conf при желании):

extensions.ael

globals {     WAV=/var/calls; //Временный каталог с WAV     MP3=/var/calls; //Куда выгружать 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 ${WAV}/${datedir});               Set(monopt=nice -n 19 /usr/bin/lame -b 32  --silent "${WAV}/${datedir}/${fname}.wav"  "${MP3}/${datedir}/${fname}.mp3" && chmod o+r "${MP3}/${datedir}/${fname}.*");               Set(CDR(filename)=${fname}.mp3); 	      Set(CDR(recordingfile)=${fname}.wav);               Set(CDR(realdst)=${called});               MixMonitor(${WAV}/${datedir}/${fname}.wav,b,${monopt});         }; };   context dial_out { // звоним друг другу _[71]XX => {         &recording(${CALLERID(number)},${EXTEN});         Dial(SIP/${EXTEN},,tTr);         Hangup();         }  // кому позвонить решит amoCRM! 100500 => {         Set(DEFMAN=123); // по умолчанию звоним на 123         Set(TOEXT=${SHELL(wget -O - --quiet "https://vistepru.amocrm.ru/private/acceptors/asterisk_new/?redirect=Y&number=${CALLERID(num)}&USER_LOGIN=ceo@vistep.ru&USER_HASH=1dc1444b0d3172c1113ffea9078c575c")}); // получаем номер ответственного менеджера         Dial(SIP/${TOEXT},,tTr); // звоним ответственному менеджеру // если он не отвечает или ошибка, звоним на номер по умолчанию         if ("${DIALSTSTUS}" != "ANSWERED") {             Dial(SIP/${DEFMAN},,tTr);         }         HangUP(); } // end 100500  _XXXXXX => { NoOP(=== CALL FROM ${CALLERID(number)} TO ${EXTEN} ===); &recording(${CALLERID(number)},${EXTEN}); Dial(SIP/83843${EXTEN}@multifon,180,tT); HangUP(); } // end of _XXXXXX  _[78]XXXXXXXXXX => { NoOP(=== CALL TO ${EXTEN} ===); &recording(${CALLERID(number)},${EXTEN}); Dial(SIP/${EXTEN}@multifon,180,tT); HangUP(); }// end of _[78]XXXXXXXXXX   _+7XXXXXXXXXX => { NoOP(=== CALL TO ${EXTEN} ===); &recording(${CALLERID(number)},${EXTEN}); Dial(SIP/${EXTEN}@multifon,180,tT); HangUP(); }// end of _+7XXXXXXXXXX   //все остальные звонки, не прописанные выше, идут в лес _X. => {         Hangup();         }  }   context default { // в контексте по умолчанию все отправляется лесом _X. => {         Hangup();         } };    context incoming { _[87]XXXXXXXXXX => { 	&recording(${CALLERID(number)},${EXTEN}); 	Answer(); 	Set(CHANNEL(musicclass)=vistep.ru); 	Set(CUSTOMER_NAME=${SHELL(wget -O - --quiet  "https://vistepru.amocrm.ru/private/acceptors/asterisk_new/?number=${CALLERID(num)}&USER_LOGIN=ceo@vistep.ru&USER_HASH=1dc1444b0d3172c1113ffea9078c575c"|cut -d "|" -f1)}); 	Set(CALLERID(name)=${CUSTOMER_NAME}); 	Queue(queue_1,tT); 	NoOp(=== ${HANGUPCAUSE} ===); 	HangUP(); } } 

Важно!
В контексте incoming (так я назвал контекст, где обрабатываю входящие вызовы), в единственном экстеншене, есть такая строка:

Set(CUSTOMER_NAME=${SHELL(wget -O - --quiet  "https://vistepru.amocrm.ru/private/acceptors/asterisk_new/?number=${CALLERID(num)}&USER_LOGIN=ceo@vistep.ru&USER_HASH=1dc1444b0d3172c1113ffea9078c575c"|cut -d "|" -f1)});

Эта команда позволяет нам отобразить на телефонах сотрудников ФИО звонящих клиентов, подцепляя их из amoCRM.
Разберем линк из этой команды на составляющие:

  1. vistepru.amocrm.ru/private/acceptors/asterisk_new? где вместо vistepru у вас должен быть прописан ваш поддомен в amocrm
  2. USER_LOGIN=ceo@vistep.ru где вместо моего email должен быть указан ваш (админский)
  3. USER_HASH=1dc1444b0d3172c1119593ffea9078c575c где вместо моего API ключа (в интерфейсе amoCRM «Настройки» -> «API) укажите свой API ключ

Пример работы команды

Скрытый текст

Теперь о специальном экстеншене 100500.
Напомню, в диалплане он выглядит

так

 // кому позвонить решит amoCRM! 100500 => {         Set(DEFMAN=123); // по умолчанию звоним на 123         Set(TOEXT=${SHELL(wget -O - --quiet "https://vistepru.amocrm.ru/private/acceptors/asterisk_new/?redirect=Y&number=${CALLERID(num)}&USER_LOGIN=ceo@vistep.ru&USER_HASH=1dc1444b0d3172c1113ffea9078c575c")}); // получаем номер ответственного менеджера         Dial(SIP/${TOEXT},,tTr); // звоним ответственному менеджеру // если он не отвечает или ошибка, звоним на номер по умолчанию         if ("${DIALSTSTUS}" != "ANSWERED") {             Dial(SIP/${DEFMAN},,tTr);         }         HangUP(); } // end 100500 

Линк для wget практически идентичен и для него действуют правила описанные выше.
А нужен он для т.н. „умной переадресации“, когда поступивший вызов переадресуется сотрудником на 100500, а дальше Asterisk и amoCRM уже сами решают кому его направить (читай направить ответственному менеджеру или менеджеру „по умолчанию“).

Почему это полезно, спросите вы? Представим обычную для офиса ситуацию:
- Входящий звонок от ООО "Шубы Саурона"
- Звонок принимает менеджер Боромир, понимает, что это не его клиент и начинает кричать в рупор на весь офис: - Чей клиент "Шубы Сарумана"? (еще и ошибается в добавок!)
- Галадриель из конца кабинета кричит, что ее и Боромир переводит вызов.

В связке с amoCRM это будет выглядеть так:
- Входящий звонок от ООО "Шубы Саурона"
- Звонок принимает менеджер Боромир, понимает, что это не его клиент и переводит вызов на 100500
- Asterisk и amoCRM путем не сложной магии сами решают, что вызов нужно отправить Галадриель
- PROFIT!

за информацию спасибо ребятам из voxlink — voxlink.ru/kb/integraciya-s-crm/amocrm-asterisk

И да, совсем забыл, если ваша Asterisk еще не настроена на ведение БД в MySQL, то в данной статье вы найдете все необходимые инструкции.
Также не забудьте добавить в табличку CDR еще одно поле (нужно для возможности слушать разговоры в карточке клиента в amoCRM)

Скрытый текст

ALTER TABLE `cdr` ADD `recordingfile` VARCHAR (120) NOT NULL

Настройка amoCRM

В данном пункте нас ждет наибольшее количество грабель, поэтому будьте внимательны.
Прежде всего подключим Asterisk в интерфейсе amoCRM.
Для этого идем в „Настройки“ -> „Интеграции“ -> находим там Asterisk и жмем „Установить“.
Нам предстанет описание интеграции и некоторое количество ссылок на гайды, все это смело пролистываем в самый низ до полей ввода информации.
Логин — amocrm (из manager.conf)
Пароль — JD3clEB8f4-_3ry84gJ (из manager.conf)
Путь к скрипту — _https://asterisk.vistep.ru/amocrm.php
А также внутренние номера сотрудников вашей компании.

скрин

Следующим шагом будет настройка скрипта amocrm.php.
Его можно скачать по ссылке в описании интеграции, но я хочу обратить внимание, что выложенный здесь исправлен под конкретный диалплан, точнее конкретный контекст оригинации вызовов dial_out (строка 99), дабы соответствовать настройкам Asterisk на стенде. Имейте это в виду и измените на ваш контекст, если он будет отличаться (это нужно для совершения вызовов в пару кликов прямо из amoCRM)

amocrm.php

<?php /* 	amoCRM to  asterisk integration. 	QSOFT LLC,  All rights reserved. 	mailto:      support@amocrm.com. 	Date:   10.04.2012   rev: 102703 	Cannot be redistributed  without 	     a written permission.                          _____ _____  __  __                         / ____|  __ \|  \/  |    __ _ _ __ ___   ___ | |    | |__) | \  / |   / _` | '_ ` _ \ / _ \| |    |  _  /| |\/| |  | (_| | | | | | | (_) | |____| | \ \| |  | |_   \__,_|_| |_| |_|\___/ \_____|_|  \_\_|  |_(_)     */ ini_set('log_errors','On'); ini_set('error_log', '/var/log/php_errors.log'); define('AC_HOST','localhost'); // где слушает  AMI/AJAM define('AC_PORT',8088); // какой порт слушает (у нас 8088) см. http.conf Asterisk'а define('AC_PREFIX','/asterisk/'); // см. http.conf Asterisk'а define('AC_TLS',false); define('AC_DB_CS','mysql:host=localhost;port=3306;dbname=asterisk'); //хост, где крутится MySQL с БД Asterisk'а, порт и имя БД define('AC_DB_UNAME','asterisk_user'); //каким юзером цепляться к БД define('AC_DB_UPASS','232wwQd293f_2edxse3e'); //пароль этого юзера define('AC_TIMEOUT',0.75); define('AC_RECORD_PATH','https://asterisk.vistep.ru/records/%Y/%m/%d/#'); //путь, по которому забирать файлы записей разговоров define('AC_TIME_DELTA',7); // hours. Ex. GMT+4 = 4   $db_cs=AC_DB_CS; $db_u=!strlen(AC_DB_UNAME)?NULL:AC_DB_UNAME; $db_p=!strlen(AC_DB_UPASS)?NULL:AC_DB_UPASS; date_default_timezone_set('UTC');   if (AC_PORT<1) die('Please, configure settings first!'); // die if not if (defined('AC_RECORD_PATH') AND !empty($_GET['GETFILE'])){ 	//get file. Do not check auth. (uniqueid is rather unique) 	$p=AC_RECORD_PATH; 	if (empty($p)) die('Error while getting file from asterisk'); 	try { 		$dbh = new PDO($db_cs, $db_u, $db_p); 		$sth = $dbh->prepare('SELECT calldate,recordingfile FROM cdr WHERE uniqueid= :uid'); 		$sth->bindValue(':uid',strval($_GET['GETFILE'])); 		$sth->execute(); 		$r = $sth->fetch(PDO::FETCH_ASSOC); 		if ($r===false OR empty($r['recordingfile'])) die('Error while getting file from asterisk'); 		$date=strtotime($r['calldate']); 		$replace=array(); 		$replace['#']=$r['recordingfile']; 		$dates=array('d','m','Y','y'); 		foreach ($dates as $d) $replace['%'.$d]=date($d,$date); // not a good idea! 		$p=str_replace(array_keys($replace),array_values($replace),$p); 		if (empty($_GET['noredirect'])) header('Location: '.$p); 		die($p); 	} catch (PDOException $e) { 		die('Error while getting file from asterisk'); 	} }   // filter parameters from _GET foreach (array('login','secret','action') as $k){ 	if (empty($_GET['_'.$k])) die('NO_PARAMS'); 	$$k=strval($_GET['_'.$k]); } // trying to check accacess $loginArr=array( 	'Action'=>'Login', 	'username'=>$login, 	'secret'=>$secret, //	'Events'=>'off', ); $resp=asterisk_req($loginArr,true); // problems? exiting if ($resp[0]['response']!=='Success') answer(array('status'=>'error','data'=>$resp[0]));  //auth OK. Lets perform actions if ($action==='status'){ // list channels status 	$params=array( 'action'=>'status'); 	$resp=asterisk_req($params); 	// report error of any 	if ($resp[0]['response']!=='Success') answer(array('status'=>'error','data'=>$resp[0])); 	// first an last chunks are useless 	unset($resp[end(array_keys($resp))],$resp[0]); 	// renumber keys for JSON 	$resp=array_values($resp); 	// report OK 	answer(array('status'=>'ok','action'=>$action,'data'=>$resp));  }elseif ($action==='call'){ // originate a call 	$params=array( 		'action'=>'Originate', 		'channel'=>'SIP/'.intval($_GET['from']), 		'Exten'=>strval($_GET['to']), 		'Context'=>'dial_out', //was from-internal 		'priority'=>'2', 		'Callerid'=>'"'.strval($_GET['as']).'" <'.intval($_GET['from']).'>', 		'Async'=>'Yes', 		// Not Implemented: 		//'Callernumber'=>'150', 		//'CallerIDName'=>'155', 	); 	$resp=asterisk_req($params,true); 	if ($resp[0]['response']!=='Success') answer(array('status'=>'error','data'=>$resp[0])); 	answer(array('status'=>'ok','action'=>$action,'data'=>$resp[0]));  } elseif ($action==='test_cdr'){ // test if DB connection params are OK. 	if (!class_exists('PDO')) answer(array('status'=>'error','data'=>'PDO_NOT_INSTALLED')); // we use PDO for accessing mySQL pgSQL sqlite within same algorythm 	try { 		$dbh = new PDO($db_cs, $db_u, $db_p); 	} catch (PDOException $e) { 		answer(array('status'=>'error','data'=>$e->getMessage())); 	} 	answer(array('status'=>'ok','data'=>'connection ok')); } elseif ($action==='cdr'){ // fetch call history 	try { 		$dbh = new PDO($db_cs, $db_u, $db_p);  		foreach (array('date_from','date_to') as $k){ 			$v=doubleval( (!empty($_GET[$k]))?intval($_GET[$k]):0 ); 			if ($v<0) $v=time()-$v; 			$$k=$v; 		} 		if ($date_from<time()-10*24*3600) $date_from=time()-7*24*3600; //retr. not more than 10d before 		$date_from=($date_from?$date_from+AC_TIME_DELTA*3600:0); //default 01-01-1970 		$date_to  =($date_to  ?$date_to  +AC_TIME_DELTA*3600:time()+AC_TIME_DELTA*3600);//default now() 		$sth = $dbh->prepare('SELECT calldate, src,dst,duration,billsec,uniqueid,recordingfile FROM cdr WHERE disposition=\'ANSWERED\' AND billsec>=:minsec AND calldate> :from AND calldate< :to'); 		// BETWEEN is illegal on some bcknds 		header("X-REAL_DATE:" . gmdate('Y-m-d H:i:s',$date_from).'@'. gmdate('Y-m-d H:i:s',$date_to)); 		$sth->bindValue(':from', date('Y-m-d H:i:s',$date_from) ); 		$sth->bindValue(':to',	 date('Y-m-d H:i:s',$date_to)); 		$sth->bindValue(':minsec',!empty($_GET['minsec'])?$_GET['minsec']:5,PDO::PARAM_INT); 		$sth->execute(); 		//$sth->debugDumpParams(); 	var_dump($sth->errorInfo()); 		$r = $sth->fetchAll(PDO::FETCH_ASSOC); 		foreach ($r as $k=>$v) $r[$k]['calldate']=date('Y-m-d H:i:s',strtotime($v['calldate'])-AC_TIME_DELTA*3600); 		answer(array('status'=>'ok','data'=>$r),true); 	} catch (PDOException $e) { 		answer(array('status'=>'error','data'=>$e->getMessage()),true); 	} } elseif ($action==='pop'){// fill test data. Maybe you will need it. Just comment line below. 	die(); 	$dbh = new PDO($db_cs, $db_u, $db_p); 	for ($i=0;$i<(int)$_GET['n'];$i++){ 		$array=array( 			date('Y-m-d H:i:s',time()-rand(100,7*24*3600)), 			'Auto <150>', 150,'791612345678','n/a','n/a','n/a','n/a','n/a',999, rand(7,999), 'ANSWERED',3,'',uniqid(),'','','' 		); 		$str=array(); 		foreach ($array as  $v) $str[]="'{$v}'"; 		$str=implode(', ',$str); 		$dbh->query("INSERT INTO cdr VALUES ({$str});"); 	} }  /** MakeRequest to asterisk interfacees  * @param $params -- array of req. params  * @return array -- response  */ function asterisk_req($params,$quick=false){ 	// lets decide if use AJAM or AMI 	return !defined('AC_PREFIX')?ami_req($params,$quick):ajam_req($params); }  /**  * Shudown function. Gently close the socket  */ function asterisk_socket_shutdown(){ami_req(NULL);}  /*** Make request with AMI  * @param $params -- array of req. params  * @param bool $quick -- if we need more than action result  * @return array result of req  */ function ami_req($params,$quick=false){ 	static $connection; 	if ($params===NULL and $connection!==NULL) { 		// close connection 		fclose($connection); 		return; 	} 	if ($connection===NULL){ 		$en=$es=''; 		$connection = fsockopen(AC_HOST, AC_PORT, $en, $es, 3); 		// trying to connect. Return an error on fail 		if ($connection) register_shutdown_function('asterisk_socket_shutdown'); 		else {$connection=NULL; return array(0=>array('response'=>'error','message'=>'socket_err:'.$en.'/'.$es));} 	} 	// building req. 	$str=array(); 	foreach($params as $k=>$v) $str[]="{$k}: {$v}"; 	$str[]=''; 	$str=implode("\r\n",$str); 	// writing 	fwrite($connection,$str."\r\n"); 	// Setting stream timeout 	$seconds=ceil(AC_TIMEOUT); 	$ms=round((AC_TIMEOUT-$seconds)*1000000); 	stream_set_timeout($connection,$seconds,$ms); 	// reading respomse and parsing it 	$str= ami_read($connection,$quick); 	$r=rawman_parse($str); 	//var_dump($r,$str); 	return $r; } /*** Reads data from coinnection  * @param $connection -- active connection  * @param bool $quick -- should we wait for timeout or return an answer after getting command status  * @return string RAW response  */ function ami_read($connection,$quick=false){ 	$str=''; 	do { 		$line = fgets($connection, 4096); 		$str .= $line; 		$info = stream_get_meta_data($connection); 		if ($quick and $line== "\r\n") break; 	}while ($info['timed_out'] == false ); 	return $str; }  /*** Echo`s data  * @param $array answer data  * @param bool $no_callback shold we output as JSON or use callback function  */ function answer($array,$no_callback=false){ 	header('Content-type: text/javascript;'); 	if (!$no_callback)  echo "asterisk_cb(".json_encode($array).');'; 	else echo json_encode($array); 	die(); }  /** Parse RAW response  * @param $lines RAW response  * @return array parsed response  */ function rawman_parse($lines){ 	$lines=explode("\n",$lines); 	$messages=array(); 	$message=array();  	foreach ($lines as $l){ 		$l=trim($l); 		if (empty($l) and count($message)>0){ $messages[]= $message;  $message=array(); continue;} 		if (empty($l))  continue; 		if (strpos($l,':')===false)  continue; 		list($k,$v)=explode(':',$l); 		$k=strtolower(trim($k)); 		$v=trim($v); 		if (!isset( $message[$k]))  $message[$k]=$v; 		elseif (!is_array( $message[$k]))  $message[$k]=array( $message[$k],$v); 		else  $message[$k][]=$v; 	} 	if (count($message)>0) $messages[]= $message; 	return $messages; }   /** Make request via AJAM  * @param $params req. params  * @return array parsed resp.  */ function ajam_req($params){ 	static $cookie; 	// EveryRequest Ajam sends back a cookir, needed for auth handling 	if ($cookie===NULL) $cookie=''; 	// make req. and store cookie 	list($body,$cookie)= rq(AC_PREFIX.'rawman?'.http_build_query($params),$cookie); 	// parse an answer 	return rawman_parse($body); }  /** make http req. to uri with cookie, parse resp and fetch a new cookie  * @param $url  * @param string $cookie  * @return array  ($body,$newcookie)  */ function rq($url,$cookie=''){ 	// get RAW data 	$r=_rq($url,$cookie); 	// divide in 2 parts 	list($headersRaw,$body)=explode("\r\n\r\n",$r,2); 	// parse headers 	$headersRaw=explode("\r\n",$headersRaw); 	$headers=array(); 	foreach ($headersRaw as $h){ 		if (strpos($h,':')===false) continue; 		list($hname,$hv)=explode(":",$h,2); 		$headers[strtolower(trim($hname))]=trim($hv); 	} 	// fetch cookie 	if (!empty($headers['set-cookie'])){ 		$listcookies=explode(';',$headers['set-cookie']); 		foreach ($listcookies as $c){ 			list($k,$v)=explode('=',trim($c),2); 			if ($k=='mansession_id') $cookie=$v; 		} 	}  	return array($body,$cookie); }  /**  mare a request to URI and return RAW resp or false on fail  * @param $url  * @param $cookie  * @return bool|string  */ function _rq($url,$cookie){ 	$errno=$errstr=""; 	$fp = fsockopen(AC_HOST, AC_PORT, $errno, $errstr, 3); 	if (!$fp) return false; 	$out = "GET {$url} HTTP/1.1\r\n"; 	$out .= "Host: ".AC_HOST."\r\n"; 	if (!empty($cookie)) $out.="Cookie: mansession_id={$cookie}\r\n"; 	$out .= "Connection: Close\r\n\r\n"; 	fwrite($fp, $out); 	$r=''; 	while (!feof($fp)) $r.=fgets($fp); 	fclose($fp); 	return $r; } 

Обратите внимание!
Мои пояснения к параметрам в начале скрипта даны прямо в коде.

Проверить работу скрипта можно по следующим линкам (обратите внимание — я использую свои логин/пароль и путь к скрипту, у вас они должны отличаться):
_https://asterisk.vistep.ru/amocrm.php?_login=amocrm&_secret=JD3clEB8f4-_3ry84gJ&_action=test_cdr
_https://asterisk.vistep.ru/amocrm.php?_login=amocrm&_secret=JD3clEB8f4-_3ry84gJ&_action=status
выхлоп должен быть как на

скринах

test_cdr

status

Тестируем получившуюся связку

По итогу выполненных настроек мы получим следующие фичи:

  • отображение звонка в amoCRM (если контакт уже есть, высвечивается ФИО и можно перейти в карточку контакта, если нет, то создать новый в один клик)
  • отображение ФИО контакта из amoCRM на телефоне при входящем звонке
  • возможность совершить вызов из интерфейса amoCRM в пару кликов
  • переадресовать вызов ответственному менеджеру, переведя его на специальный номер

Для демонстрации лучше всего подойдет видео-формат, поэтому извольте:

Заключение

Надеюсь данной статьей я сумел полностью закрыть вопрос интеграции amoCRM и Asterisk.
Если у вас возникнут вопросы, милости прошу в комментарии.
Нет аккаунта на Хабре? — Мои координаты есть профиле, пишите, постараюсь помочь.

Asterisk — это fun!
Всем удачи!
ссылка на оригинал статьи https://habrahabr.ru/post/325104/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *