PHP-библиотека

Основы

PHP-библиотека предназначена для разработки серверной части виджета на своем локальном сервере. Для начала необходимо скачать библиотеку для взаимодействия с amoCRM. Файлы widget_lib.phar и .htaccess необходимо положить в корень вашего веб сервера, и его домен должен быть второго уровня. Так же, следует предварительно создать и загрузить себе в аккаунт архив разрабатываемого виджета(можно без php-части). Это необходимо для его идентификации и доступа к endpoint’ам.

Внимание! code и secret_key виджета должны быть идентичны в загруженном архиве и создаваемом далее manifest.json на сервере. После этого можно создать index файл, который будет содержать базовые настройки и подключение библиотеки.

<?php
header('Content-type: text/html;charset="utf-8"'); //отправка заголовка, сообщающего браузеру какую кодировку использовать
utf - 8
define('AMO_DOMAIN', 'amocrm.ru'); //домен CRM
define('AMO_PROTOCOL', 'https'); //протокол CRM
define('AUTO_BUILD', true); //автоматически собирать виджет при обращении, в противном случае необходимо каждый раз, после
изменения файлов, обращаться к контроллеру builder(описано ниже) .
require_once __DIR__ . '/widget_lib.phar'; //подключение самой библиотеки.

Теперь, необходимо создать папку /widget/ в которой будет происходить разработка виджета. В ней создаем всю структуру виджета и дополнительные файлы.

Файл /widget/widget.php

Файл /widget/widget.php должен содержать в себе класс Widget, который наследует системный класс \Helpers\Widgets.

<?php

class Widget extends \Helpers\Widgets
{
    /*
      Сюда мы добавим логику нашего виджета
    */
}

Класс Widget должен содержать в себе методы, которые выполняют роль точек входа в виджет. Эти методы должны иметь уровень доступа строже, чем public (т.е. protected и/ или private). Точка входа должна иметь название, начинающееся с префикса endpoint_, например: endpoint_get().

<?php

class Widget extends \Helpers\Widgets
{
    protected function endpoint_get()
    {
        /*
          Это точка входа "endpoint_get()"
        */
    }

    protected function endpoint_set()
    {
        /*
          Это точка входа "endpoint_set()"
        */
    }

    protected function endpoint_SomeoneElse()
    {
        /*
          Это точка входа "endpoint_SomeoneElse()"
        */
    }

    protected function endpoint_happy()
    {
        /*
          Это точка входа "endpoint_happy()"
        */
    }
}

Обращение к точке входа происходит по следующему URL: /#ACCOUNT#/#CONTROLLER#[/#METHOD#[/#ENDPOINT#]] . Где:
#ACCOUNT# – ваш аккаунт в системе
#CONTROLLER# – для обращения к точке входа виджета должен быть иметь значение loader
#METHOD# – код виджета (код должен содержать только строчные буквы!)
#ENDPOINT# – сама точка входа.
Так же, необходимо передавать get-параметры amouser и amohash. Например: /widgets/test/loader/addcontact/set?amouser=xxx&amohash=xxx

CONTROLLER так же может иметь значение builder. В этом случае METHOD и ENDPOINT указывать не нужно. Обращение к контроллеру builder соберет наш виджет для работы с контроллером loader и создаст zip архив для загрузки на amoCRM.

При первом обращении к контроллеру loader и после каждого изменения в файлах виджета необходимо вызывать контроллер builder. Или установить значение константы AUTO_BUILD как true (описано выше)

Так же, следует помнить, что для работы с сервером amoCRM при обращении к точке входа должны передаваться amouser и amohash. Их можно посмотреть в настройке профиля пользователя в системе (/settings/profile/). Поле amouser – E-Mail пользователя, amohash – ключ для авторизации в API.

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

Особенности и ограничения библиотеки

Все запросы должны перенаправляться на ваш файл index.php в корневой директории. Если у Вас стоит веб-сервер apache и вы распаковали в его корень архив с библиотекой, то там уже имеется файл .htaccess с инструкциями для mod_rewrite (убедитесь, что он включен)

По-умолчанию в объекте вашего виджета уже имеются ссылки на объекты
– контактов – $this->contacts
– компаний – $this->company
– cделок – $this->leads
– примечаний – $this->notes
– задач – $this->tasks
– информации по аккаунту – $this->account->current()
Первые пять объектов имеют два метода get и set, параметры которых описаны в файле примере widget.php Объекты contacts и company имеют еще метод links() который соответствует подобному методу в API.

Для отправки cURL запросов на сторонний сервис можно использовать встроенный класс
\Helpers\Curl::init($url,[$post=FALSE],[$cookie=FALSE]);
где:
$url — ссылка, куда отправляется запрос,
$post — массив для передачи (если он заполнен, то запрос будет отправлен по методу POST),
$cookie — TRUE/FALSE использовать ли куки-файл или нет

Любые приходящие из GET или POST параметры нужно получать через
\Helpers\Route::param(#ELEMENT_KEY#)

Метод \Helpers\Route::param(#ELEMENT_KEY#) так же доступен как $this->param(#ELEMENT_KEY#)

Получение настроек текущего виджета в amoCRM возможно путем вызова
$this->account->current(‘widget’);

Для работы с языковыми сообщениями можно использовать встроенный класс
\Helpers\I18n::get(‘settings.enums.yes’)
Все языковые сообщения должны быть описаны в директории /widget/i18n/#lang#.json

Создание собственной Web-странички

На примере ниже мы продемонстрируем создание простой html-формы для добавления контакта.

Создадим файл с html-формой и назовём его form.php

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Добавление контакта</title>
</head>
<body>
<div id="wrapper">
    <header>
        <h1>Создание контакта</h1>
    </header>
    <div id="contact_form">
        <form action="test/loader/addcontact/add?
amouser=test@mail.ru&amohash=63bcbb0169df507cf144320b9c9a79af443efd1e" method="post">
            <div class="field">
                <label for="contact_name">Имя</label><input id="contact_name" type="text" name="name">
            </div>
            <div class="field">
                <label for="contact_company">Компания</label><input id="contact_company" type="text" name="company">
            </div>
            <div class="field">
                <label for="contact_position">Должность</label><input id="contact_position" type="text" name="position">
            </div>
            <div class="field">
                <label for="contact_phone">Телефон</label><input id="contact_phone" type="tel" name="phone">
            </div>
            <div class="field">
                <label for="contact_email">E-mail</label><input id="contact_email" type="email" name="email">
            </div>
            <div class="field">
                <label for="contact_web">Web-сайт</label><input id="contact_web" type="url" name="web">
            </div>
            <div class="field">
                <label for="contact_jabber">Jabber</label><input id="contact_jabber" type="text" name="jabber">
            </div>
            <div class="field">
                <label for="contact_scope">Сфера деятельности</label>
                <select id="contact_scope" name="scope[]" size="5" multiple>
                    <option value="it">IT, телекоммуникации, связь, электроника</option>
                    <option value="auto">Автосервис, автобизнес</option>
                    <option value="bookkeeping">Бухгалтерия, аудит</option>
                    <option value="restaurants">Рестораны, фастфуд</option>
                    <option value="economy">Экономика, финансы</option>
                </select>
            </div>
            <div>
                <button type="submit">Создать контакт</button>
                <button type="reset">Очистить форму</button>
            </div>
        </form>
    </div>
</div>
</body>
</html>

Создание манифеста

Манифест виджета – это файл с описанием и настройками виджета в формате JSON. Рекомендуется название, описание и другую статичную информацию выносить в файлы локализации виджета (смотрите ниже). Внимание! code и secret_key должны соответствовать, загруженному в аккаунт виджету.

{
  "widget": {
    "name": "widget.name",
    "description": "widget.description",
    "short_description": "widget.short_description",
    "code": "AddContact",
    "secret_key": "305ce9d34a329cbf1d0678e348s2d30dcdb6bae6d81832add4cd6584fbce4d33",
    "version": "1.0.0",
    "locale": [
      "ru",
      "en"
    ],
    "installation": true
  }
}

Создание файлов локализации

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

Создадим два файла локализации для нашего примера: на английском и на русском языках соответственно.

{
  "widget": {
    "name": "Добавление контактов и сделок",
    "short_description": "Виджет добавляет контакты, сделки, после чего привязывает некоторые сделки к некоторым контактам" ,
    "description": "Виджет добавляет контакты, сделки, после чего привязывает некоторые сделки к некоторым контактам"
  },
  "exceptions": {
    "error": "Ошибка",
    "name": "Не заполнено имя контакта",
    "email": {
      "empty": "Не заполнен E-mail контакта"
    },
    "custom_fields": {
      "undefined": "Невозможно получить дополнительные поля",
      "unknown": "В amoCRM отсутствуют следующие поля"
    },
    "contacts": {
      "error": "Невозможно получить список контактов"
    },
    "user_already_exists": "Такой контакт уже существует в amoCRM"
  },
  "custom_fields": {
    "scope": {
      "name": "Сфера деятельности",
      "fields": {
        "it": "IT, телекоммуникации, связь, электроника",
        "auto": "Автосервис, автобизнес",
        "bookkeeping": "Бухгалтерия, аудит",
        "restaurants": "Рестораны, фастфуд",
        "economy": "Экономика, финансы"
      }
    }
  }
}

Файл англоязычной локализации

{
  "widget": {
    "name": "Adding contacts and leads",
    "short_description": "Widget adds contacts, leads, and then binds some leads from some contacts",
    "description": "Widget adds contacts, leads, and then binds some leads from some contacts"
  },
  "exceptions": {
    "error": "Error",
    "name": "Not filled a contact name",
    "email": {
      "empty": "Not filled a contact email"
    },
    "custom_fields": {
      "undefined": "Can not get the additional fields",
      "unknown": "In amoCRM missing the following fields"
    },
    "contacts": {
      "error": "Unable to get a list of contacts"
    },
    "user_already_exists": "This contact already exists in amoCRM"
  },
  "custom_fields": {
    "scope": {
      "name": "Sphere of activity",
      "fields": {
        "it": "In IT, telecommunications, communications, electronics",
        "auto": "Auto Service, Autp Business",
        "bookkeeping": "Accounting, Auditing",
        "restaurants": "Restaurants, fast food",
        "economy": "Economy, Finances"
      }
    }
  }
}

Программирование виджета

Создадим пустой класс Widget, который наследует системный класс \Helpers\Widgets, а затем сделаем в нём точку входа, которую назовём add

class Widget extends \Helpers\Widgets
{
    protected function endpoint_add()
    {
        /* Здесь будет наш код*/
    }
}

Создадим внутренний метод get_data() (помеченный модификатором private), который будет получать данный из формы, и записывать их во внутреннее свойство $data, а затем поместим его вызов в точку входа.

class Widget extends \Helpers\Widgets
{
    private
        $data;

    protected function endpoint_add()
    {
        $this->get_data();
    }

    private function get_data()
    {
        #Получаем данные из POST-запроса
        $data = array(
            'name' => isset ($_POST ['name']) ? $_POST ['name'] : '',
            'company' => isset ($_POST ['company']) ? $_POST ['company'] : '',
            'position' => isset ($_POST ['position']) ? $_POST ['position'] : '',
            'phone' => isset ($_POST ['phone']) ? $_POST ['phone'] : '',
            'email' => isset ($_POST ['email']) ? $_POST ['email'] : '',
            'web' => isset ($_POST ['web']) ? $_POST ['web'] : '',
            'jabber' => isset ($_POST ['jabber']) ? $_POST ['jabber'] : '',
            'scope' => isset ($_POST ['scope']) && is_array($_POST ['scope']) ? $_POST ['scope'] : array()
        );

        #Если не указано имя или e-mail контакта - уведомляем
        if (empty ($data ['name']))
            die (\Helpers\I18n:: get('exceptions.name')); #Данные берутся из файлов локализации (смотрите выше)
        if (empty ($data ['email']))
            die (\Helpers\I18n:: get('exceptions.email'));
        $this->data = $data;
    }
}

Создадим внутренний метод get_custom_fields_info() для получения информации о нужных нам полях в amoCRM и сохраним его результат в переменной $custom_fields в точке входа.

class Widget extends \Helpers\Widgets
{
    private
        $data;

    protected function endpoint_add()
    {
        $this->get_data();
        $custom_fields = $this->get_custom_fields_info();
    }

    private function get_data()
    {
        #Получаем данные из POST-запроса
        $data = array(
            'name' => isset ($_POST ['name']) ? $_POST ['name'] : '',
            'company' => isset ($_POST ['company']) ? $_POST ['company'] : '',
            'position' => isset ($_POST ['position']) ? $_POST ['position'] : '',
            'phone' => isset ($_POST ['phone']) ? $_POST ['phone'] : '',
            'email' => isset ($_POST ['email']) ? $_POST ['email'] : '',
            'web' => isset ($_POST ['web']) ? $_POST ['web'] : '',
            'jabber' => isset ($_POST ['jabber']) ? $_POST ['jabber'] : '',
            'scope' => isset ($_POST ['scope']) && is_array($_POST ['scope']) ? $_POST ['scope'] : array()
        );

        #Если не указано имя или e-mail контакта - уведомляем
        if (empty ($data ['name']))
            die (\Helpers\I18n:: get('exceptions.name')); #Данные берутся из файлов локализации (смотрите выше)
        if (empty ($data ['email']))
            die (\Helpers\I18n:: get('exceptions.email'));
        $this->data = $data;
    }

    private function get_custom_fields_info()
    {
        #Получаем информацию по текущему аккаунту
        $account = $this->account->current();
        #Поля, ID которых нам нужно собрать
        $need = array_flip(array('POSITION', 'PHONE', 'EMAIL', 'WEB', 'IM', 'SCOPE'));
        if (isset ($account ['custom_fields'], $account ['custom_fields'] ['contacts']))
            do {
                foreach ($account ['custom_fields'] ['contacts'] as $field)
                    if (is_array($field) && isset ($field ['id'])) {
                        if (isset ($field ['code']) && isset ($need [$field ['code']]))
                            $fields [$field ['code']] = (int)$field ['id'];
                        #SCOPE - нестандартное поле, поэтому обрабатываем его отдельно
                        elseif (isset ($field ['name']) &&
                            $field ['name'] == \Helpers\I18n:: get('custom_fields.scope.name'))
                            $fields ['SCOPE'] = $field;

                        $diff = array_diff_key($need, $fields);
                        if (empty ($diff))
                            break 2;
                    }
                if (isset ($diff))
                    die (\Helpers\I18n:: get('exceptions.custom_fields.unknown') . ': ' . join(', ', $diff));
                else
                    die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
            } while (false);
        else
            die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
        return isset ($fields) ? $fields : false;
    }
}

Теперь нам необходимо узнать, существует ли контакт с указанным E-mail у пользователя. Для этого создадим внутренний метод is_contact_exists() и сделаем соответствующую проверку в точке входа.

class Widget extends \Helpers\Widgets
{
    private
        $data;

    protected function endpoint_add()
    {
        $this->get_data();
        $custom_fields = $this->get_custom_fields_info();
        if ($this->is_contact_exists())
            die (\Helpers\I18n:: get('exceptions.user_already_exists'));
    }

    private function get_data()
    {
        #Получаем данные из POST-запроса
        $data = array(
            'name' => isset ($_POST ['name']) ? $_POST ['name'] : '',
            'company' => isset ($_POST ['company']) ? $_POST ['company'] : '',
            'position' => isset ($_POST ['position']) ? $_POST ['position'] : '',
            'phone' => isset ($_POST ['phone']) ? $_POST ['phone'] : '',
            'email' => isset ($_POST ['email']) ? $_POST ['email'] : '',
            'web' => isset ($_POST ['web']) ? $_POST ['web'] : '',
            'jabber' => isset ($_POST ['jabber']) ? $_POST ['jabber'] : '',
            'scope' => isset ($_POST ['scope']) && is_array($_POST ['scope']) ? $_POST ['scope'] : array()
        );

        #Если не указано имя или e-mail контакта - уведомляем
        if (empty ($data ['name']))
            die (\Helpers\I18n:: get('exceptions.name')); #Данные берутся из файлов локализации (смотрите выше)
        if (empty ($data ['email']))
            die (\Helpers\I18n:: get('exceptions.email'));
        $this->data = $data;
    }

    private function get_custom_fields_info()
    {
        #Получаем информацию по текущему аккаунту
        $account = $this->account->current();
        #Поля, ID которых нам нужно собрать
        $need = array_flip(array('POSITION', 'PHONE', 'EMAIL', 'WEB', 'IM', 'SCOPE'));
        if (isset ($account ['custom_fields'], $account ['custom_fields'] ['contacts']))
            do {
                foreach ($account ['custom_fields'] ['contacts'] as $field)
                    if (is_array($field) && isset ($field ['id'])) {
                        if (isset ($field ['code']) && isset ($need [$field ['code']]))
                            $fields [$field ['code']] = (int)$field ['id'];
                        #SCOPE - нестандартное поле, поэтому обрабатываем его отдельно
                        elseif (isset ($field ['name']) &&
                            $field ['name'] == \Helpers\I18n:: get('custom_fields.scope.name'))
                            $fields ['SCOPE'] = $field;

                        $diff = array_diff_key($need, $fields);
                        if (empty ($diff))
                            break 2;
                    }
                if (isset ($diff))
                    die (\Helpers\I18n:: get('exceptions.custom_fields.unknown') . ': ' . join(', ', $diff));
                else
                    die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
            } while (false);
        else
            die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
        return isset ($fields) ? $fields : false;
    }

    private function is_contact_exists()
    {
        $params = array(
            'query' => $this->data ['email']
        );
        if ($contacts = $this->contacts->get($params))
            return $contacts;
        return false;
    }
}

Наконец, можем создать контакт в amoCRM. Для этого напишем внутренний метод add_new_contact ($custom_fields), принимающий в качестве параметра массив с информацией, которую мы собрали в методе get_custom_fields_info() и вызовем его в точке входа.

class Widget extends \Helpers\Widgets
{
    private
        $data;

    protected function endpoint_add()
    {
        $this->get_data();
        $custom_fields = $this->get_custom_fields_info();
        if ($this->is_contact_exists())
            die (\Helpers\I18n:: get('exceptions.user_already_exists'));
        $this->add_new_contact($custom_fields);
    }

    private function get_data()
    {
        #Получаем данные из POST-запроса
        $data = array(
            'name' => isset ($_POST ['name']) ? $_POST ['name'] : '',
            'company' => isset ($_POST ['company']) ? $_POST ['company'] : '',
            'position' => isset ($_POST ['position']) ? $_POST ['position'] : '',
            'phone' => isset ($_POST ['phone']) ? $_POST ['phone'] : '',
            'email' => isset ($_POST ['email']) ? $_POST ['email'] : '',
            'web' => isset ($_POST ['web']) ? $_POST ['web'] : '',
            'jabber' => isset ($_POST ['jabber']) ? $_POST ['jabber'] : '',
            'scope' => isset ($_POST ['scope']) && is_array($_POST ['scope']) ? $_POST ['scope'] : array()
        );

        #Если не указано имя или e-mail контакта - уведомляем
        if (empty ($data ['name']))
            die (\Helpers\I18n:: get('exceptions.name')); #Данные берутся из файлов локализации (смотрите выше)
        if (empty ($data ['email']))
            die (\Helpers\I18n:: get('exceptions.email'));
        $this->data = $data;
    }

    private function get_custom_fields_info()
    {
        #Получаем информацию по текущему аккаунту
        $account = $this->account->current();
        #Поля, ID которых нам нужно собрать
        $need = array_flip(array('POSITION', 'PHONE', 'EMAIL', 'WEB', 'IM', 'SCOPE'));
        if (isset ($account ['custom_fields'], $account ['custom_fields'] ['contacts']))
            do {
                foreach ($account ['custom_fields'] ['contacts'] as $field)
                    if (is_array($field) && isset ($field ['id'])) {
                        if (isset ($field ['code']) && isset ($need [$field ['code']]))
                            $fields [$field ['code']] = (int)$field ['id'];
                        #SCOPE - нестандартное поле, поэтому обрабатываем его отдельно
                        elseif (isset ($field ['name']) &&
                            $field ['name'] == \Helpers\I18n:: get('custom_fields.scope.name'))
                            $fields ['SCOPE'] = $field;

                        $diff = array_diff_key($need, $fields);
                        if (empty ($diff))
                            break 2;
                    }
                if (isset ($diff))
                    die (\Helpers\I18n:: get('exceptions.custom_fields.unknown') . ': ' . join(', ', $diff));
                else
                    die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
            } while (false);
        else
            die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
        return isset ($fields) ? $fields : false;
    }

    private function is_contact_exists()
    {
        $params = array(
            'query' => $this->data ['email']
        );
        if ($contacts = $this->contacts->get($params))
            return $contacts;
        return false;
    }

    private function add_new_contact($custom_fields)
    {
        $contact = array(
            'name' => $this->data ['name'],
            'custom_fields' => array(
                array(
                    'id' => $custom_fields ['EMAIL'],
                    'values' => array(
                        array(
                            'value' => $this->data ['email'],
                            'enum' => 'WORK'
                        )
                    )
                )
            )
        );

        if (!empty ($this->data ['company']))
            $contact += array('company_name' => $this->data ['company']);

        if (!empty ($this->data ['position']))
            $contact ['custom_fields'] [] = array(
                'id' => $custom_fields ['POSITION'],
                'values' => array(
                    array(
                        'value' => $this->data ['position']
                    )
                )
            );

        if (!empty ($this->data ['phone']))
            $contact ['custom_fields'] [] = array(
                'id' => $custom_fields ['PHONE'],
                'values' => array(
                    array(
                        'value' => $this->data ['phone'],
                        'enum' => 'OTHER'
                    )
                )
            );

        if (!empty ($this->data ['web']))
            $contact ['custom_fields'] [] = array(
                'id' => $custom_fields ['WEB'],
                'values' => array(
                    array(
                        'value' => $this->data ['web']
                    )
                )
            );

        if (!empty ($this->data ['jabber']))
            $contact ['custom_fields'] [] = array(
                'id' => $custom_fields ['IM'],
                'values' => array(
                    array(
                        'value' => $this->data ['jabber'],
                        'enum' => 'JABBER'
                    )
                )
            );

        if (!empty ($this->data ['scope'])) {
            foreach ($this->data ['scope'] as & $enum)
                $enum = trim(\Helpers\I18n:: get('custom_fields.scope.fields.' . $enum));
            unset ($enum);

            $intersect = array_intersect($custom_fields ['SCOPE'] ['enums'], $this->data ['scope']);

            foreach ($intersect as $k => $v)
                $values [] = array(
                    'value' => $v,
                    'enum' => $k
                );

            $scope = array(
                'id' => (int)$custom_fields ['SCOPE'] ['id'],
                'values' => $values
            );

            $contact ['custom_fields'] [] = $scope;
        }

        $request ['add'] [] = $contact;
        $this->contacts->set($request);
    }
}

Теперь PHP-логика нашего виджета готова!

Отладка

Заметим, что для отладки на локальных хостах, можно использовать метод \Helpers\Debug::vars($var_for_debug[, $name_of_debug_block]), который выводит свёрстанную страницу с отладочной информацией. Первым параметром передаётся переменная, которую нужно отладить, а вторым (необязательным) параметром можно передать название отладочного блока. При загрузке виджета к нам на аудирование не забывайте удалять отладочные выводы.

$str = 'Строка';
\Helpers\Debug::vars($str, 'Важная информация');

В ходе разработки своего виджета для интеграции с amoCRM, Вы можете столкнуться с числовыми кодами состояний или ошибок, возвращаемых вместе с ответом нашим API. Для того чтобы понять, что именно означает тот или иной код вы можете воспользоваться нашим справочником ответов API или использовать метод \Helpers\Curl::get_error_code($code), который возвращает сообщение ошибки по её числовому коду.

$code = 206;
echo \Helpers\Curl::get_error_code($code); // contacts set - request is empty

Упаковка и загрузка

Если при работе вы указали значение константы AUTO_BUILD как true то в папке, где вы создавали виджет должна быть автоматически создана структура /widgets/code/ (если же этой папки нет, вам необходимо обратиться к контроллеру builder вручную) где code – код виджета. В ней содержится архив widget.zip, который необходимо загрузить на amoCRM в разделе /settings/dev/

Качаем PHP-библиотеку для разработки виджетов

Вы всегда можете скачать актуальную версию php-библиотеки по ссылке, расположенной ниже:

Скачать

А пример, приведённый на этой странице, доступен здесь:

Скачать