Разработка интеграций

Виджеты в digital pipeline

Виджеты могут взаимодействовать с функционалом цифровой воронки и реагировать на какие-либо из следующих событий:

  • Входящее письмо
  • Входящий звонок
  • Входящее сообщение из чата
  • Переход в этап
  • Заход на сайт (для этого события можно настроить отложенное действие)

Для взаимодействия виджета с цифровой воронкой, в файле manifest.json необходимо указать область видимости digital_pipeline и блок dp/settings, подробнее о структуре виджета, см. здесь. В файле widget.php должен присутствовать endpoint digital_pipeline.

При наступлении настроенного события на endpoint digital_pipeline система отправляет запрос, содержащий массив данных следующей структуры:

Пример ответа

  1. [
  2.     'event' => [
  3.         'type' => 15,
  4.             'type_code' => 'lead_appeared_in_status'
  5.                 'data' => [
  6.                     'id'          => 123124, // id сделки
  7.                     'element_type' => 2, // Тип элемента (2 - сделка, 12 - покупатель)
  8.                     'status_id'   => 654324, // статус сделки
  9.                     'pipeline_id' => 654324, // воронка сделки
  10.                 ],
  11.                 'time' => 1491300016,
  12.     ],
  13.     'action' => [
  14.         'settings' => [
  15.             'widget' => [
  16.                  'settings' => [
  17.                      //Настройки виджета dp
  18.                  ]
  19.             ]
  20.         ]
  21.     ],
  22.     'amouser' => 'example@amocrm.com',
  23.     'amohash' => 'c123ae456cd7891246bffb1e654abb9d',
  24.             ]

Список возможных значений type и type_code

Код Тип кода Описание
1 lead_added Добавление сделки
14 lead_status_changed Смена статуса сделки
15 lead_appeared_in_status Смена статуса внутри растянутого действия виджета на несколько этапов*
16 mail_in Входящие письмо
17 call_in Входящий звонок
18 site_visit Посещение сайта
19 chat Входящие сообщение из чата
20 customer_added Добавление покупателя
21 customer_period_changed Смена статуса покупателя
22 customer_appeared_in_period Смена статуса внутри растянутого действия виджета на несколько этапов*

Интеграция с API

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

*В том случае когда в блоке dp вашего manifest.json вы указываете action_multiple - true, то тем самым вы позволяете растягивать (задавать) действие вашего виджета сразу на несколько этапов. При смене статуса сделки/покупателя, в области растянутого на несколько этапов действия вашего виджета, будет приходить type_code = 15. При этом, при переходе сделки/покупателя в статус в котором активируется ваш виджет, изначально придёт type_code = 14.

Обмен данными digital pipeline с виджетом

В предыдущем разделе мы сказали о необходимости присутствия endpoint digital_pipeline в файле back-end части вашего виджета widget.php. Это необходимо для того, чтобы при наступлении определённого события ваш виджет мог получать информацию от digital pipline. Обмен данными с нашей стороны и вашим виджетом происходит с помощью отправки уведомлений о событиях технологией WebHook (подробнее о технологии см. здесь).

Обратите внимание, WebHook от digital pipeline отправляется на веб-сервер только один раз при наступление указанного события.

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

Пример интеграции

  1. protected function endpoint_digital_pipeline() {
  2.     foreach ($this->keys as $key) {
  3.       $this->_request[$key] = $this->check_request($key);
  4.     }
  5.     if (empty($this->_request['action']['settings']['widget']['settings'])) {
  6.       throw new \Exception('Empty widget settings');
  7.     }
  8.     $this->_request['settings'] = $this->_request['action']['settings']['widget']['settings'];
  9.     if (empty($this->_request['event']['data']['id'])) {
  10.       throw new \Exception('Empty entity id');
  11.     }
  12.     if (!in_array($this->_request['event']['type_code'], $this->_avalible_events)) {    //Проверка, есть ли в запросе код
  13. события, из
  14. определённого массива допустимых/выбранных событий (_avalible_events) для срабатывания виджета
  15.       return;
  16.     }
  17.     $this->parse_data();
  18.     $this->init_curl();
  19.     $this->_response = $this->curl_post(self::/* ссылка на ваш API */ . $this->api_methods['your_widget_hook'], $this-
  20. >_your_widget_hook,
  21. 'X-AMOCRM-REQUEST:Y', FALSE);
  22.     if ((int)$this->_response['code'] === 0) {
  23.       $this->customers_notes_set($this->_request['event']['data'], $this->_request['settings']['message']);
  24.       curl_close($this->_curl);
  25.       return TRUE;
  26.     } else {
  27.       //сообщение об ошибке пользователю
  28.       $error_message = "Error: " . var_export($this->_response, TRUE);
  29.       $this->customers_notes_set($this->_request['event']['data'], $error_message, FALSE, (int)$this->_response['code']);
  30.       curl_close($this->_curl);
  31.       throw new Helpers\Exception($error_message);
  32.     }
  33.   }

Получение данных от WebHook digital pipeline

  1. private function check_request($key) {
  2.   return Helpers\Route::param($key) ? Helpers\Route::param($key) : NULL;  //Возвращаем либо массив, либо NULL
  3. }

Преобразуем данные пришедшие с помощью WebHook

  1. private function parse_data() {
  2.     $this->_lifepay_hook = [
  3.       'apikey' => self::/* Ключ вашего API */,
  4.       'data'   => json_encode([
  5.         [
  6.           'customerId' => $this->_request['event']['data']['id'],
  7.           'message'    => $this->_request['settings']['message'],
  8.         ],
  9.       ]),
  10.     ];
  11.   }

Отправка WebHook

  1. private function curl_post($link, $data, $http_header = 'Content-Type: application/json', $is_json = TRUE) {
  2.   curl_setopt($this->_curl, CURLOPT_URL, $link);
  3.   if ($is_json) {
  4.     $post_fields = json_encode($data);
  5.   } else {
  6.     $post_fields = http_build_query($data);
  7.   }
  8.   curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $post_fields);
  9.   curl_setopt($this->_curl, CURLOPT_HTTPHEADER, [$http_header]);
  10.   $response = json_decode(curl_exec($this->_curl), TRUE);
  11.   if (curl_errno($this->_curl)) {
  12.     $response = 'cURL error: ' . curl_error($this->_curl);
  13.   }
  14.   return $response;
  15. }

Инициализация и настройка cURL

  1. private function init_curl() {
  2.     $this->_curl = curl_init();
  3.     curl_setopt($this->_curl, CURLOPT_POST, TRUE);
  4.     curl_setopt($this->_curl, CURLOPT_SSL_VERIFYPEER, 0);
  5.     curl_setopt($this->_curl, CURLOPT_SSL_VERIFYHOST, 0);
  6.     curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, 2);
  7.     curl_setopt($this->_curl, CURLOPT_TIMEOUT, 2);
  8.     curl_setopt($this->_curl, CURLOPT_FOLLOWLOCATION, TRUE);
  9.     curl_setopt($this->_curl, CURLOPT_HEADER, FALSE);
  10.     curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, TRUE);
  11.   }

Блок настройки автоматического действия

В момент того, как ваш виджет будет успешно добавлен и доступен для интеграции, доступ к его настройкам станет возможен из нескольких областей. В первую очередь доступ к полной настройке будет возможен стандартно, как и для всех интеграций, из раздела Настройки -> Интеграции, вашего аккаунта. В том случае если ваш виджет является интеграцией с digital pipeline, то к нему появится доступ из настроек digital pipeline, раздела Сделки в области объявления автоматических действий для всех сделок.

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

Для примера опишим front-end часть виджета в script.js, которая отображает настройки внутри элемента быстрой настройки. Выберем отправку сообщения, по наступлению какого-либо выбранного пользователем условия (см. скриншот выше).

Пример

  1. dpSettings: function () {
  2.                 var w_code = self.get_settings().widget_code,   //Код виджета, заданный в manifest.json
  3.                     lang = self.i18n('settings'),
  4.                     dp_modal = $(".digital-pipeline__short-task_widget-style_"+w_code)  //Благодря подстановке
  5. кода(w_code) вашего
  6. виджета, мы можем обратиться к элементу содержащему именно ваш виджет
  7.                         .parent()
  8.                         .parent()
  9.                         .find('[data-action=send_widget_hook]'),
  10.                     message_label = dp_modal.find('[title^='+lang.message.split(" ")[0]+']'),   //Ваши пояснения к полям,
  11. описанные в
  12. ru.json
  13.                     message_label_new = "<div style='margin-top: 5px;'>" + lang.message + "</div>",
  14.                     message_input = dp_modal.find('input[name=message]'),   //Обращение к введёному тексту
  15.                     message_textarea = self.render(     //Отрисовываем поле ввода текста
  16.                     {ref: '/tmpl/controls/textarea.twig'},
  17.                     {
  18.                         id: 'dp_message',
  19.                         style: {'width': '396px', 'margin-top': '5px', 'margin-bottom': '-3px'},
  20.                         value: message_input.val(),
  21.                         placeholder: lang.message
  22.                     }
  23.                 );
  24.                 message_label.hide().after(message_label_new);
  25.  
  26.                 message_input.hide().after(message_textarea);
  27.  
  28.                 return true;
  29.             }

Важно помнить, об объявлении настроек в manifest.json, подробнее о структуре виджета здесь

  1. "locations": [
  2.     "settings",
  3.     "digital_pipeline"
  4.   ],
  5. "dp": {
  6.     "settings": {
  7.       "message": {
  8.         "name": "settings.message",
  9.         "type": "text",
  10.         "required": true
  11.       }
  12.     }

Макросы

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

Пример реализации макросов из боевого скрипта виджета Простор СМС от компании Axelerator, для подстановки некоторой информации о контакте:

Пример интеграции

  1. /* widget.php, back-end часть */
  2. /* Объявление массива макросов */
  3. private $macroses = array("{{contact_name}}", "{{contact_phone}}", "{{contact_email}}");
  4. /* Получаем и обрабатываем данные, для дальнейшего использования в подстановке. Обратите внимание, извлекаемые данные
  5. должны
  6. соответствовать объявленным макросам, чтобы избежать пустых подстановок  */
  7. protected function endpoint_digital_pipeline()
  8.   {
  9.     $this->GetData();
  10.     $account = $this->account->current();
  11.     $contact = $this->getContactById($lead["leads"][0]["main_contact_id"]);
  12. $contact_phone = "";
  13.     $contact_email = "";
  14.     foreach($contact["contacts"][0]["custom_fields"] as $field){
  15.       if($field["code"] == "PHONE"){
  16.         $contact_phone = $field["values"][0]["value"];
  17.       }
  18.       if($field["code"] == "EMAIL"){
  19.         $contact_email = $field["values"][0]["value"];
  20.       }
  21.     }
  22. /* Переменная $text будет содержать готовый текст, с заменёнными переменными на данные */
  23.   $text = $this->replaceWord($this->action['settings']['widget']['settings']['message'], $contact["contacts"][0]
  24. ["name"],$contact_phone,
  25. $contact_email);
  26. /* Функция перестановки маркеров в тексте на ранее полученные данные */
  27. private function replaceWord($message, $contact_name, $contact_phone, $contact_email)
  28.   {
  29. foreach($this->macroses as $macros){
  30.       if($macros == "{{contact_name}}"){
  31.         $message = str_replace($macros, $contact_name, $message);
  32.       }
  33. if($macros == "{{contact_phone}}"){
  34.         $message = str_replace($macros, $contact_phone, $message);
  35.       }
  36.       if($macros == "{{contact_email}}"){
  37.         $message = str_replace($macros, $contact_email, $message);
  38.       }
  39.     return $message;
  40.   }
  41. /* Функция извлечения данных элемента интересующей нас сущности (контакт) */
  42. private function getContactById($id) {
  43.     $account = $this->account->current();
  44.     $link = 'https://'.$account['subdomain'].'.amocrm.ru/private/api/v2/json/contacts/list?id[]='.$id;
  45.     $curl=curl_init();
  46.     curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  47.     curl_setopt($curl, CURLOPT_USERAGENT, 'amoCRM-API-client/1.0');
  48.     curl_setopt($curl, CURLOPT_URL, $link);
  49.     curl_setopt($curl, CURLOPT_HEADER, false);
  50.     curl_setopt($curl, CURLOPT_COOKIEFILE, dirname(__FILE__).'/cookie.txt');
  51.     curl_setopt($curl, CURLOPT_COOKIEJAR, dirname(__FILE__).'/cookie.txt');
  52.     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
  53.     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
  54.     $out=curl_exec($curl);
  55.     $code=curl_getinfo($curl,CURLINFO_HTTP_CODE);
  56.     curl_close($curl);
  57.     $response=json_decode($out,true);
  58.     $response=$response['response'];
  59.     return $response;
  60.   }
  61. private function GetData()
  62.   {
  63.     $event = \Helpers\Route::param("event");
  64.     $this->event = $event;
  65.     $action = \Helpers\Route::param("action");
  66.     $this->action = $action;
  67.     $amouser = \Helpers\Route::param("amouser");
  68.     $this->amouser = $amouser;
  69.     $amohash = \Helpers\Route::param("amohash");
  70.     $this->amohash = $amohash;
  71.   }

Далее небходимо добавить возможность указывать макросы в тексте сообщения в области быстрой настройки, при подключении виджета в автоматических действиях для всех сделок в digital pipeline.

  1. /* script.js, front-end часть */
  2. dpSettings: function () {
  3.   w_code = self.get_settings().widget_code;
  4.   $('.digital-pipeline__edit-bubble').css({'width': '500px'});
  5.   var message_input = $(".digital-pipeline__short-task_widget-style_" +
  6. w_code).parent().parent().find("input[name=message]");
  7.   var message_input_label = message_input.parent().parent();
  8.   var message_textarea = self.render(
  9.     {ref: '/tmpl/controls/textarea.twig'},
  10.       {
  11.         id: 'dp_message',
  12.         value: message_input.val()
  13.       }
  14.   );
  15.   var dp_marker = '<div class="marker-list__block dp_marker_block">' +
  16.     '<p>Контакт</p>' +
  17.     '<p class="marker-list__row"><span class="marker-list__tag-bot dp_marker_label" data-marker="{{contact_name}}"><span
  18. class="marker-
  19. list__tag">{{contact_name}}</span></span><span class="marker-list__tag-descr"> - Имя контакта</span></p>' +
  20.     '<p class="marker-list__row"><span class="marker-list__tag-bot dp_marker_label" data-marker="{{contact_phone}}"><span
  21. class="marker-
  22. list__tag">{{contact_phone}}</span></span><span class="marker-list__tag-descr"> - Телефон контакта</span></p>' +
  23.     '<p class="marker-list__row"><span class="marker-list__tag-bot dp_marker_label" data-marker="{{contact_email}}"><span
  24. class="marker-
  25. list__tag">{{contact_email}}</span></span><span class="marker-list__tag-descr"> - Email контакта</span></p>'
  26.     return true;
  27. }

Основной контакт

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

Для примера приведём один из вариантов реализации данной возможности, для простой интеграции с digital pipeline, которая рассылает текстовые сообщения.

Пример интеграции

  1. protected function endpoint_digital_pipeline() {
  2.         $model_type = intval(\Helpers\Route::param('event')['data']['element_type']);
  3.         $model_id = intval(\Helpers\Route::param('event')['data']['id']);
  4.         $message_text = \Helpers\Route::param('action')['settings']['widget']['settings']['message'];
  5.         $only_main = false;     //По умолчанию функция отправки только основному контакту будет выключена
  6.         if (!empty(\Helpers\Route::param('action')['settings']['widget']['settings']['only_main'])) {   //Если chekbox не
  7. пустой,
  8. присваиваем true
  9.             $only_main = in_array(\Helpers\Route::param('action')['settings']['widget']['settings']['only_main'], ['on',
  10. TRUE, '1']);
  11.         }
  12.         $this->event($model_type, $model_id, $message_text, $only_main);
  13.     }
  14. /* Функция рассылки по массиву полученных контактов */
  15. private function event($model_type, $model_id, $message_text, $only_main = false){
  16.         $contacts = $this->getContacts($model_type, $model_id, $only_main);
  17.         foreach ($contacts as $contact) {
  18.             $this->sendMessage($message_text, $contact);    //sendMessage ваша функция отправления сообщения
  19.         }
  20.     }
  21. /* Функция получения контактов */
  22. private function getContacts($model_type, $model_id, $only_main = false) {
  23.         $contacts = [];
  24.         $links = null;
  25.         switch ($model_type) {  //Условная конструкция для определения типа сущности: сделки (element_type = 2) или
  26. покупателя
  27. (element_type = 12)
  28.             case 2:
  29.                 $links = $this->contacts->links(['deals_link' => $model_id]);
  30.                 break;
  31.             case 12:
  32.                 $links = $this->contacts->links(['customers_link' => $model_id]);
  33.                 break;
  34.         }
  35.         if (!$links) {
  36.             return $contacts;
  37.         }
  38.         $contact_ids = [];
  39.         foreach ($links as $link) {
  40.             $contact_ids[] = intval($link['contact_id']);
  41.             if ($only_main === true) {      //Если пользователь выбрал рассылку только основному контатку, после первой
  42. итерации цикла -
  43. break. Таким образом в массиве $contact_ids будет только id основного контакта
  44.                 break;
  45.             }
  46.         }
  47.         if (empty($contact_ids)) {
  48.             return $contacts;
  49.         }
  50.         $contacts = $this->contacts->get(['id' => $contact_ids]);
  51.         return $contacts;
  52.     }

Далее необходимо добавить возможность выбирать рассылку только основному контакту в области быстрой настройки, при подключении виджета в автоматических действиях для всех сделок в digital pipeline.

  1. /* script.js, front-end часть */
  2. dpSettings: function () {
  3.    var lang = self.i18n('dp.settings');
  4.    var form = $('#widget_settings__fields_wrapper');
  5.    var field_divs = form.find('.widget_settings_block__input_field');
  6.    var textarea_div = field_divs.first();
  7.    textarea_div.html('<textarea name="message" ' +
  8.        'style="height:50px; width: 100%;" ' +
  9.        'id="message" ' +
  10.        'class="text-input text-input-textarea digital-pipeline__add-task-textarea textarea-autosize task-edit__textarea">'
  11. +
  12.        ''+ textarea_div.find('input').val() +
  13.        '</textarea>');
  14.    var checkbox_template = '<label class="control-checkbox">' +
  15.        '<div class="control-checkbox__body">' +
  16.        '<input type="checkbox" id=""/>' +
  17.        '<span class="control-checkbox__helper"></span>' +
  18.        '</div>' +
  19.        '<div class="control-checkbox__text element__text">' +
  20.        '<span class="control-checkbox__note-text">' + lang.only_main.name + '</span>' +
  21.        '</div>' +
  22.        '</div>' +
  23.        '</label>';
  24.    var checkbox_div = field_divs.last();
  25.    checkbox_div.siblings().html('');
  26.    checkbox_div.html(checkbox_template);
  27.    return true;
  28. }

Логирование

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

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

Для добавление системного события, необходимо указать note_type = 25.

В обязательное поле text присваивается JSON в котором указанно два поля text (Сообщение) и service (Название сервиса).

Пример интеграции

  1. /* Пример запроса на добавление системного сообщения в карточку сделки */
  2. $subdomain='example';
  3. $data['add'] = array(
  4.     array(
  5.         'element_id'=>2789651,
  6.         'element_type'=>2,  //Сделка - 2, Покупатель - 12
  7.         'note_type'=>25,
  8.         'params'=>array('text' => 'Сообщение успешно добавлено', 'service' => 'Your Service Name')
  9.     ));
  10. $link='https://'.$subdomain.'.amocrm.ru/api/v2/notes';
  11. $curl=curl_init();
  12. curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
  13. curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-API-client/1.0');
  14. curl_setopt($curl,CURLOPT_URL,$link);
  15. curl_setopt($curl,CURLOPT_CUSTOMREQUEST,'POST');
  16. curl_setopt($curl,CURLOPT_POSTFIELDS,json_encode($data));
  17. curl_setopt($curl,CURLOPT_HTTPHEADER,array('Content-Type: application/json'));
  18. curl_setopt($curl,CURLOPT_HEADER,false);
  19. curl_setopt($curl,CURLOPT_COOKIEFILE,dirname(__FILE__).'/cookie.txt'); #PHP>5.3.6 dirname(__FILE__) -> __DIR__
  20. curl_setopt($curl,CURLOPT_COOKIEJAR,dirname(__FILE__).'/cookie.txt'); #PHP>5.3.6 dirname(__FILE__) -> __DIR__
  21. curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,0);
  22. curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,0);
  23. $out=curl_exec($curl);
  24. $code=curl_getinfo($curl,CURLINFO_HTTP_CODE);
  25. $code=(int)$code;
  26. $errors=array(
  27.     301=>'Moved permanently',
  28.     400=>'Bad request',
  29.     401=>'Unauthorized',
  30.     403=>'Forbidden',
  31.     404=>'Not found',
  32.     500=>'Internal server error',
  33.     502=>'Bad gateway',
  34.     503=>'Service unavailable'
  35. );
  36. try
  37. {
  38.     #Если код ответа не равен 200 или 204 - возвращаем сообщение об ошибке
  39.   if($code!=200 && $code!=204)
  40.         throw new Exception(isset($errors[$code]) ? $errors[$code] : 'Undescribed error',(int)$code);
  41. }
  42. catch(Exception $E)
  43. {
  44.     die('Ошибка: '.$E->getMessage().PHP_EOL.'Код ошибки: '.$E->getCode());
  45. }

Подключение Salesbot

В amoCRM есть возможность подключить, уже реализованного, salesbot'a. Данного бота можно запрограммировать на выполнение определенных действий. Он помогает получать от пользователей данные через мессенджеры (Telegram, Facebook Messenger, VK, Viber).

Подробная инструкция по подключению, функционалу, настройке, языку и работе с нашим торговым ботом в разделе Salesbot

Обмен информацией с внешними веб-серверами

Каждый аккаунт в amoCRM на тарифе расширенный и выше имеет возможность сообщать о действиях на ваш веб- сервер. Эти "WebHooks" могут быть использованы для обновления информации о сделках в вашем магазине, отправки смс уведомлений или автоматизации ведения сделок. Каждый WebHook может быть настроен для определённой операции и событий.

WebHooks – это уведомление сторонних приложений посредством отправки уведомлений о событиях, произошедших в amoCRM. Подробнее о работе WebHooks и digital pipeline смотрите в разделе WebHook.