Asterisk + Amocrm

AmoCRM популярное облачное решение с более-менее нормальным вижетом для Asterisk.
Само-подписанный SSl сертификат годится только для тестов,
для продакшена ОБЯЗАТЕЛЬНО требуется доверенный сертификат веб сервера выписанный на ваше доменное имя.
Данное руководство дает справочную информацию о настройке и тестировании интеграции Asterisk и AmoCRM и предполагает, что вы имеете базовые знания о Linux и Asterisk.
Настройку можно разделить на 4 основных этапа:

  • Настройка Asterisk
    • /etc/asterisk/manager.conf
    • /etc/asterisk/httpd.conf
  • Конфигурация скрипта
    • amocrm.php
  • Настройка ssl сертификата веб сервера.
  • Настройка вижета в AmoCRM

Скрипт amocrm.php должен быть доступен снаружи сети. Таким образом, если сервер Asterisk находится за nat, вам потребуется открыть веб сервер на asterisk, для доступа извне. Внешний адрес за которым находится Asterisk, должен быть указан как цель для используемого доменного имени.
Сертификат может быть выписан на доменное имя третьего уровня, например: asterisk.yourdomain.com.
Доменное имя должно быть назначено серверу Asterisk.
Несмотря на обширный текст, настройка данного решения довольно проста. Большая часть материала посвящена тонкой доводке и возможно не потребуется.

Настройка AMI и AJAM

Со стороны Asterisk требуется только включить AJAM: /etc/asterisk/manager.conf

[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0
webenabled = yes
httptimeout = 60

создать пользователя AMI для AmoCRM

[amocrm]
secret = PASSWORD
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 сервер
Во FreePBX: FreePBX: Settings > advanced_settings

или в конфиге Asterisk: /etc/asterisk/http.conf

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

Тест AJAM интерфейса

http status

http status

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

Enabled URI's:
/asterisk/httpstatus => Asterisk HTTP General Status
/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/ws => Asterisk HTTP WebSocket

Enabled Redirects:
  None.

https://asterisk_ip:8088/asterisk/httpstatus?action=login&username=<ami_user>&secret=<ami_password>

https://asterisk_ip:8088/asterisk/rawman?action=login&username=<ami_user>&secret=<ami_password>

 Response: Success
 Message: Authentication accepted

Скрипт AmoCRM

AmoCRM получает данные от Asterisk через GET запросы к скрипту, который в свою очередь,
запрашивает данные у Астера через AJAM или AMI и SQL запросами к БД asteriskcdrdb.

Базовая конфигурация скрипта

Сначала вам потребуется задать параметры доступа к базе данных asteriskcdrdb,
префикс AJAM, а также путь к директории записей.

  • AC_HOST - ip/hostname сервера AMI/AJAM (localhost)
  • AC_PORT - порт сервера AMI/AJAM (если AMI то пусто, если AJAM то 8088)
  • AC_PREFIX - префикс заданный в http.conf астериска (например: prefix=asterisk)
  • AC_DB_UNAME - пользователь БД asteriskcdrdb
  • AC_DB_UPASS - пароль БД asteriskcdrdb (можно посмотреть в файлах cdr_mysql.conf или res_odbc.conf
  • AC_RECORD_PATH путь к записям разговоров

Рассмотрим обще-употребимый пример получения записей из FreePBX.
Записи по умолчанию расположены в директории: /var/spool/asterisk/monitor/Год/Месяц/День
Создадим в веб директории в которой расположен скрипт мягкую ссылку на директорию записей Asterisk:

 ln -s /var/spool/asterisk/monitor monitor

Чтобы затруднить доступ к директории запретите листениг в файле .htaccess

  Options -Indexes 

В этом случае ссылка в скрипте amocrm будет выглядеть так:

https://ваше_доменное_имя/веб_директория_скрипта/monitor/%Y/%m/%d/#

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

Для отображения записей в AmoCRM, возможно, потребуется модифицировать SQL запросы в скрипте:

Получение имени файла из базы данных asteriskcdrdb производится по запросу:

GET ../amocrm.php?GETFILE=1505491053.1619

где 1505491053.1619 UNIQUEID вызова.
Скрипт amocrm в свою очередь пытается получить имя файла из колонки recordingfile БД asteriskcdrdb и таблицы cdr следующим запросом:

SELECT calldate,recordingfile FROM cdr WHERE uniqueid= :uid

где uid это UNIQUEID из GET запроса.

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

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

+---------------------+-------------------------------------------------------------+
| calldate            | recordingfile                                               |
+---------------------+-------------------------------------------------------------+
| 2017-09-15 18:57:33 |                                                             |
| 2017-09-15 18:57:33 |                                                             |
| 2017-09-15 18:58:00 | external-02-666-20170915-185733-1505491053.1619.wav     |
+---------------------+-------------------------------------------------------------+

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

SELECT calldate,recordingfile FROM cdr WHERE uniqueid= :uid order by calldate DESC limit 1

или
сохраним UNIQUEID в переменную $recfile:

 $recfile = $_GET['GETFILE']; 

а искать будем не по колонке uniqueid, а по recordingfile:

 SELECT calldate, recordingfile FROM cdr WHERE recordingfile like '%$recfile%';

Данные CDR в БД AmoCRM передаются по запросу (каждые 40 минут):

GET .. /amocrm.php?_login=<AMI_LOGIN>&_secret=<AMI_PASSWORD>&_action=cdr&date_from=<дата_от>&date_to=<и_до>

По которому, в свою очередь, скрипт делает выборку из БД следующего вида:

SELECT calldate,src,dst,duration,billsec,uniqueid,recordingfile FROM cdr WHERE disposition=\'ANSWERED\' AND billsec>=:minsec AND calldate> :from AND calldate< :to

По данной выборке в БД AmoCRM будут попадать все вызовы включая местные.
Если вы хотите избавиться, например, от внутренних вызовов, то можно модифицировать запрос следующим образом. В приведенном примере отфильтрованы вызовы с номеров 100-199.
добавив следующее условие AND src NOT RLIKE "^1xx"

SELECT calldate, src,dst,duration,billsec,uniqueid,recordingfile FROM cdr WHERE disposition=\'ANSWERED\'
AND src NOT RLIKE "^1xx" AND billsec>=:minsec AND calldate> :from AND calldate< :to

И так далее по образу и подобию, по вашим условиям.


Скрипт AmoCRM

Расположите скрипт в веб директории сервера asterisk.
В приведенных ниже примерах скрипт расположен в корне - /var/www/html/amocrm.php,
но лучше разместить его в директории с неявным именем.
Например:

  • /var/www/html/amo/amocrm.php - плохо
  • /var/www/html/09q2393ig5gj48u39203r2rR/amosramoboombarum.php - хорошо

amocrm.php

amocrm.php

<?php
/*
        amoCRM to  asterisk integration.
        mailto: info@asterisk-pbx.ru
        Date:   21.09.2017  
                         _____ _____  __  __
                        / ____|  __ \|  \/  |
   __ _ _ __ ___   ___ | |    | |__) | \  / |
  / _` | '_ ` _ \ / _ \| |    |  _  /| |\/| |
 | (_| | | | | | | (_) | |____| | \ \| |  | |_
  \__,_|_| |_| |_|\___/ \_____|_|  \_\_|  |_(_)
 
 
 
 */
ini_set('display_errors',0);
define('AC_HOST','localhost');
define('AC_PORT',8088);
define('AC_PREFIX','/aster/');
define('AC_TLS',false);
define('AC_DB_CS','mysql:host=localhost;port=3306;dbname=asteriskcdrdb');
define('AC_DB_UNAME','db_user');
define('AC_DB_UPASS','db_password');
define('AC_TIMEOUT',0.75);
define('AC_RECORD_PATH','https://hostname/amo_dir/monitor/%Y/%m/%d/#');
define('AC_TIME_DELTA',3); // 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 (no path)');
        try {
                $dbh = new PDO($db_cs, $db_u, $db_p);
                $sth = $dbh->prepare('SELECT calldate,recordingfile FROM cdr WHERE uniqueid= :uid order by calldate DESC limit 1');
                $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 (no filename in select)');
                $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'=>'Local/'.$_GET['from'].'@from-internal',
                'Exten'=>strval($_GET['to']),
                'Context'=>'from-internal',
                'priority'=>'1',
                '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;
}

Скрипт обрабатывает следующие запросы:

В зависимости от типа запросов они инициируется либо с IP-адреса сервера AmoCRM, либо с IP пользователя, работающего в AmoCRM.

  • status - core show channels (c ip пользователя)
  • test_cdr - Проверка соединения c БД asteriskcdrdb
  • cdr - Выборка из БД asteriskcdrdb.cdr (c ip сервера)
  • GETFILE - Получение файла записи разговора. (c ip пользователя)
  • call - Инициирует originate. (c ip пользователя)

Команда call позволяет инициировать соединение двух екстеншенов. Это функция потенциально опасна и требует дополнительных мер защиты. Например запретить обращение к веб серверу всем кроме AmoCRM и IP адресов с которых работают пользователи AmoCRM (что может быть проблематично, если пользователи работают из разных мест). Актуальный адрес с которого идут запросы AmoCRM на осень 2017 года: 88.212.249.27
Если вам не требуется данный функционал, просто запретите его использование в конфигурации пользователя amocrm в /etc/asterisk/manager.conf удалив опцию originate:

[amocrm]
secret = PASSWORD
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.0
read = cdr,reporting
write = reporting

Поверка работы скрипта:

status

https://host/amocrm.php?_login=<ami_user>&_secret=<ami_password>&_action=status

 asterisk_cb({"status":"ok","action":"status","data":[]});

test_cdr

https://host/amocrm.php?_login=<ami_user>&_secret=<ami_password>&_action=test_cdr

 asterisk_cb({"status":"ok","data":"connection ok"});

cdr

https://host/amocrm.php?_login=<ami_user>&_secret=<ami_password>&_action=cdr

cdr

cdr

{"calldate":"2015-03-11 12:38:16","src":"2274","dst":"3900","duration":"95","billsec":"87","uniqueid":"1426077496.1805","recordingfile":""},{"calldate":"2015-03-11 
12:39:24","src":"3710","dst":"9xxxxxxxxx","duration":"74","billsec":"59","uniqueid":"1426077564.1817","recordingfile":""},{"calldate":"2015-03-11 
12:41:56","src":"2776","dst":"3848","duration":"8","billsec":"8","uniqueid":"1426077716.1907","recordingfile":""},{"calldate":"2015-03-11 
12:42:28","src":"2274","dst":"4321","duration":"7","billsec":"7","uniqueid":"1426077748.1923","recordingfile":""},{"calldate":"2015-03-11 
12:42:59","src":"3892","dst":"2274","duration":"115","billsec":"113","uniqueid":"1426077779.1949","recordingfile":""},{"calldate":"2015-03-11 
12:43:29","src":"4108","dst":"2207","duration":"71","billsec":"69","uniqueid":"1426077809.1957","recordingfile":""},{"calldate":"2015-03-11 
12:45:06","src":"2776","dst":"3848","duration":"23","billsec":"16","uniqueid":"1426077906.2023","recordingfile":""},{"calldate":"2015-03-11 
12:46:12","src":"2373","dst":"3914","duration":"239","billsec":"235","uniqueid":"1426077972.2055","recordingfile":""},{"calldate":"2015-03-11 

call

 amocrm.php?_login=<ami_login>&_secret=<ami_password>&_action=call&from=<TEL>&to=<TEL>

За обработку запроса call отвечает ниже приведенный код скрипта.
Возможно вам понадобится изменить имя исходящего контекста с from-internal,
на нужное вам.

}elseif ($action==='call'){ // originate a call
        $params=array(
                'action'=>'Originate',
                'channel'=>'Local/'.$_GET['from'].'@from-internal',
                'Exten'=>strval($_GET['to']),
                'Context'=>'from-internal',
                'priority'=>'1',
                'Callerid'=>'"'.strval($_GET['as']).'" <'.intval($_GET['from']).'>',
                'Async'=>'Yes',
        );

Посмотреть запросы можно в логе ssl_access_log веб сервера:

 tail -f /var/log/httpd/ssl_access_log

Настройка сертификата

Рассмотрим установку популярного сертификата от Comodo на веб сервер apache в CentOS 7. Скачайте и распакуйте архив с сертификатами на сервер

 AddTrustExternalCARoot.crt
 your_domain_com.crt
 COMODORSAAddTrustCA.crt
 COMODORSADomainValidationSecureServerCA.crt

Создайте директорию для сертификатов:

 mkdir /etc/pki/tls/cert

Создайте файл ключа

  touch /etc/pki/tls/cert/your_domain_com.key   

И скопируйте в него ключ.

Объедините файлы crt.

 cat your_domain_com.crt COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt AddTrustExternalCARoot.crt > /etc/pki/tls/cert/ssl-bundle.crt

Включите ssl и укажите путь к созданным файлам /etc/httpd/conf.d/ssl.conf:

  SSLEngine on
  SSLCertificateKeyFile /etc/pki/tls/cert/your_domain_com.key
  SSLCertificateFile /etc/pki/tls/cert/ssl-bundle.crt

Задайте серверу доменное имя /etc/sysconfig/network

 HOSTNAME=your.domain.com

Добавьте имя в /etc/hosts:

 внешний_IP  your.domain.com hostname

Выполните команду hostname из командной строки

 hostname your.domain.com

Сделайте рестарт httpd и network

AmoCRM вижет

Дополнительно

При помощи модуля CallerID Lookup вы может подставлять имена клиентов из AmoCRM в CALLERID(name) asterisk.

FreePBX: admin > CallerID Lookup Sources

  • Host = yourhost.amocrm.ru
  • Port = 443
  • Username = ami_login
  • Password = ami_pass
  • Path = /private/acceptors/asterisk_new/
  • Query = number=[NUMBER]&USER_LOGIN=AMO_USER_LOGIN&USER_HASH=<HASH>

FreePBX: Connectivity > Inbound Routes
Подключите созданный CallerID Lookup в нужном входящем маршруте:

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

[root@asterisk ~]# cat /etc/asterisk/extensions_additional.conf | grep cidlookup
[cidlookup]
exten => cidlookup_1,1,Set(CURLOPT(httptimeout)=7)
exten => cidlookup_1,n,Set(CALLERID(name)=${CURL(https://api.opencnam.com/v2/phone/${CALLERID(num)}?format=pbx&ref=freepbx)})
exten => cidlookup_1,n,Set(current_hour=${STRFTIME(,,%Y-%m-%d %H)})
exten => cidlookup_1,n,Set(last_query_hour=${DB(cidlookup/opencnam_last_query_hour)})
exten => cidlookup_1,n,Set(total_hourly_queries=${DB(cidlookup/opencnam_total_hourly_queries)})
exten => cidlookup_1,n,ExecIf($["${last_query_hour}" != "${current_hour}"]?Set(DB(cidlookup/opencnam_total_hourly_queries)=0))
exten => cidlookup_1,n,ExecIf($["${total_hourly_queries}" = ""]?Set(DB(cidlookup/opencnam_total_hourly_queries)=0))
exten => cidlookup_1,n,Set(DB(cidlookup/opencnam_total_hourly_queries)=${MATH(${DB(cidlookup/opencnam_total_hourly_queries)}+1,i)})
exten => cidlookup_1,n,ExecIf($[${DB(cidlookup/opencnam_total_hourly_queries)} >= 60]?System(${ASTVARLIBDIR}/bin/opencnam-alert.php))
exten => cidlookup_1,n,Set(DB(cidlookup/opencnam_last_query_hour)=${current_hour})
exten => cidlookup_1,n,Return()
exten => cidlookup_2,1,GotoIf($[${DB_EXISTS(cidname/${CALLERID(num)})} = 1]?cidlookup,cidlookup_return,1)
exten => cidlookup_2,n,Set(CURLOPT(httptimeout)=7)
exten => cidlookup_2,n,Set(CALLERID(name)=${CURL(https://<ami_user>:<ami_password>@<amo_account>.amocrm.ru:443/private/acceptors/asterisk_new/?number=${CALLERID(num)}&USER_LOGIN=amo_user@mail.tld&USER_HASH=xxxxxxxxxxxxxxxxxxxxxxxxxxx)})
exten => cidlookup_2,n,Set(DB(cidname/${CALLERID(num)})=${CALLERID(name)})
exten => cidlookup_2,n,Return()
exten => cidlookup_return,1,ExecIf($["${DB(cidname/${CALLERID(num)})}" != ""]?Set(CALLERID(name)=${DB(cidname/${CALLERID(num)})}))
exten => cidlookup_return,n,Return()
;--== end of [cidlookup] ==--;
exten => _X.,n,Gosub(cidlookup,cidlookup_2,1())
[root@asterisk ~]# cat /etc/asterisk/extensions_override_freepbx.conf | grep cidlookup
[cidlookup]
include => cidlookup_custom
exten => cidlookup_2,1,Set(CALLERID(name)=${SHELL(wget -O - --quiet https://<amo_account>.amocrm.ru/private/acceptors/asterisk_new/?number=${CALLERID(num)}\&USER_LOGIN=amo_user@mail.tld\&USER_HASH=xxxxxxxxxxxxxxxxxxxxxxxxxx)})
exten => cidlookup_2,n,Return()
exten => cidlookup_return,1,ExecIf($["${DB(cidname/${CALLERID(num)})}" !=""]?Set(CALLERID(name)=${DB(cidname/${CALLERID(num)})}))
exten => cidlookup_return,n,Return()

ссылки по теме: Описание функций ~~socialite~~

  • soft/crm/amocrm.txt
  • Последние изменения: 2018/12/17