Найти тему
makeROI - amoCRM Интегратор

Разработка виджета amoCRM на PHP - Авторизация

Оглавление

Вводная информация

Меня зовут Вячеслав Родионов, я руководитель IT отдела в makeROI, мы занимаемся разработками для amoCRM. С amoCRM работаю несколько лет и иногда сталкиваюсь во всевозможных чатах с вопросами о авторизации в amoCRM. Это первая статья - решил ее написать, что бы помочь комьюнити и постараться повысить качество решений.

Начнем с самого простого - авторизации. Как не странно именно в ней возникает кучу сложностей у новичков. Когда начинал работать с amoCRM она мне тоже показалась страшной, но я погрузился и изучил ее полностью.

Мой привычный стек - Laravel, но для простоты в этом примере буду использовать натив.

Примеры максимально упрощены - нет ни обработки исключений, ни логгирования, валидации, даже повторяющиеся между примерами блоки не инкапсулированные в функции, не говоря об ОПП. Это сделано осознанно для максимального упрощения. Так же будут разжёваны все тонкости, касательно amoCRM, но весьма поверхностно относительно PHP (предполагая, что вы их знаете, или пойдете гуглить). Статься рассчитана на стажеров/джунов.

Стек

Что будем делать?

  • Авторизуемся в amoCRM
  • Настроим автоматическое обновление токенов доступа
  • Создадим пару сделок

Итак, погнали

Подготовка

Нам нужно подготовить следующее:

  • Аккаунт amoCRM (у меня - shalalala.amocrm.ru), и ваш пользователь - админ
  • Хостинг с PHP 7.4, Composer, доступом по SSH
  • Ваш собственный домен, который будет подключен к хостингу (у меня shalalala.amocrm.dev (что бы не было путаницы отдельно проясню amocrm.dev - мой собственный домен, который не имеет отношения к amocrm.ru, и я создал домен 3-его уровня)).

Создание интеграции со стороны amoCRM

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

Первым делом нужно создать интеграцию, для этого на странице amoМаркета открываем выпадающее меню и выбираем соответствующий пункт.
Первым делом нужно создать интеграцию, для этого на странице amoМаркета открываем выпадающее меню и выбираем соответствующий пункт.
Далее выбираем внешняя интеграция, нам сейчас понадобится именно она. Различая заботливо описаны в модалке.
Далее выбираем внешняя интеграция, нам сейчас понадобится именно она. Различая заботливо описаны в модалке.

Для следующего этапа нужно подумать об архитектуре. Этот простейший пример будет иметь три эндпоинта:

  • /api/credentials.php - это метод для авторизации (amoCRM будет сюда отправлять авторизационные данные)
  • /api/account-info.php - этот метод выведет нам информацию об аккаунте
  • /api/lead-create.php - этот метод создаст тестовую сделку и вернет ее ID.
Теперь заполняем настройки. Нам нужно будет указать эндпоинт для авторизации - у меня это https://shalalala.amocrm.dev/api/credentials.php, а так же описание и выбрать список зон доступов. Сохраняем
Теперь заполняем настройки. Нам нужно будет указать эндпоинт для авторизации - у меня это https://shalalala.amocrm.dev/api/credentials.php, а так же описание и выбрать список зон доступов. Сохраняем
Наша интеграция появилась в разделе неразобранное. Откроем ее настройки.
Наша интеграция появилась в разделе неразобранное. Откроем ее настройки.
Нас будет интересовать вкладка "Ключи и доступы", а позже "Список пользователей". Обратите внимание что в списке пользователей пока никого нет. Там будут отображаться пользователи от которых бэк интеграции имеет токены доступа.
Нас будет интересовать вкладка "Ключи и доступы", а позже "Список пользователей". Обратите внимание что в списке пользователей пока никого нет. Там будут отображаться пользователи от которых бэк интеграции имеет токены доступа.

Развертка проекта

Я развернул проект в PhpStorm, подключившись по SFTP к хостингу. На снимке видно что проект пустой - только хостинг заботливо положил туда index.html с затычкой на которой написано, что сайт создан. Так как проект пустой, я буду сразу же загружать на хостинг, а не запускать локальный веб-сервер.

Пустой проект
Пустой проект

Первое, с чего стоит начать - удалить index.html и развернуть composer.

К сожалению в разработке amoCRM есть люди которые не знают что такое composer, а тем более не используют его как настольный инструмент. Если это так, я рекомендую остановиться и пойти изучать, например здесь - https://habr.com/ru/post/439200/.

Перейдем в директорию проекта и запустим установку пакета командой composer require amocrm/amocrm-api-library

Composer установил пакет, а так же все необходимые зависимости и создал папку vendor и собственные файлы.
Composer установил пакет, а так же все необходимые зависимости и создал папку vendor и собственные файлы.
Сразу же заливаем composer.json и composer.lock (но не vendor) на хостинг.
Сразу же заливаем composer.json и composer.lock (но не vendor) на хостинг.
И запускаем установку тех же пакетов с помощью команды composer install.
И запускаем установку тех же пакетов с помощью команды composer install.

Авторизация и сохранения токена доступа

Алгоритм:

  1. При включении интеграции со стороны amoCRM на указанный в настройках эндпоинт будет отправлен запрос, содержащий авторизационный код.
  2. Мы берем полученный код, секретный ключ интеграции, айди интеграции, и ссылку (все что мы указывали или посмотрели в настройках виджета) и отправляем в авторизационный сервис amoCRM.
  3. amoCRM возвращает нам связку токенов доступа (access и refresh), мы их сохраняем.
  4. При любых запросах к amoCRM используем access токен.

Реализация

Конфиги будем хранить в .env файле, токен сохранять в json файл.

Первым делом создадим конфиг .env, в котором укажем доступы.

Я укажу еще ACCOUNT_DOMAIN так как рассчитывается что интеграция будет только на один аккаунт. В ней я укажу полностью домен amoCRM shalalala.amocrm.ru.
Я укажу еще ACCOUNT_DOMAIN так как рассчитывается что интеграция будет только на один аккаунт. В ней я укажу полностью домен amoCRM shalalala.amocrm.ru.

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

Он проверяет, что в запросе был передан код, подгружает конфиги из .env, собирает на основе их класс клиента API из библиотеки (устанавливая ему домен), после этого получает с помощью кода авторизации объект токена, стерилизует его в json и сохраняет в файл.

Пример (в конце статьи будет ссылка на исходники).
Пример (в конце статьи будет ссылка на исходники).

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

Включаем и отключаем. Убеждаемся что появился пользователь.
Включаем и отключаем. Убеждаемся что появился пользователь.
А так же сохранился токен.
А так же сохранился токен.

У меня не появился токен и пользователь. Что делать?

Значит что то сделали не правильно. Как это найти? Наш скрипт то не пишет логи. Да очень просто. Нужно просто сымитировать запрос amoCRM и посмотреть ошибку. Как это сделать?

Идем на экран ключи и доступы и копируем код. Он есть в интерфейсе, но он одноразовый. Если используете его - нужно будет закрыть окно настроек виджета, и открыть заново - код будет новый. А еще он умирает за 20 минут.
Идем на экран ключи и доступы и копируем код. Он есть в интерфейсе, но он одноразовый. Если используете его - нужно будет закрыть окно настроек виджета, и открыть заново - код будет новый. А еще он умирает за 20 минут.
На соседней вкладке передаем скопированный код в качестве get-параметра code на наш эндпоинт. Видим ошибку (или надпись ОК, если все хорошо). На этом скрине я пробую использовать код повторно и получаю ошибку - Authorization code has been revoked.
Часто ошибки могут быть по невнимательности, например, в amoCRM указали ссылку авторизации с http, а в конфиге с https. Или обновили секретный ключ. Обычно ошибка не так информативна, что бы сразу же сделать вывод.
На соседней вкладке передаем скопированный код в качестве get-параметра code на наш эндпоинт. Видим ошибку (или надпись ОК, если все хорошо). На этом скрине я пробую использовать код повторно и получаю ошибку - Authorization code has been revoked. Часто ошибки могут быть по невнимательности, например, в amoCRM указали ссылку авторизации с http, а в конфиге с https. Или обновили секретный ключ. Обычно ошибка не так информативна, что бы сразу же сделать вывод.

Еще может быть ситуация что при отладке напрямую все ок, а при включении/отключении виджета ничего не происходит. У меня такое было пару раз - оказалось что я только делегировал домен и амо еще о нем не знало и отправляло данные не на мой хостинг, а на прошлый айпи домена. Решилось тем что подождал пару часов.

Запросы к API - получение информации об аккаунте

Создадим второй файл и напишем второй файл, результатом выполнения которого будет получения информации об аккаунте.

Начало файла точно такое же мы собираем объект клиента, указываем ему домен. Дальше мы собираем объект авторизационного токена из нашего token.json, а затем устанавливаем его в клиент.

Далее используем сервис получения информации об аккаунте $apiClient->account() и используем метод getCurrent(), что бы получить информацию о текущем аккаунте.

Преобразуем ее в массив с помощью метода toArray() и выводим.

Скрипт
Скрипт
Результат работы
Результат работы

Запросы к API - создание сделки

Предположим, у нас есть сайт, на котором есть форма из двух полей - Название товара и его стоимость (Нереалистичный пример, но осознанно не хочу в текущем гайде использовать сущность контакт), и есть задача:

Нужно создавать сделку с названием "Новая сделка %название товара%", в кастомное поле "Название товара" записывать название товара, в бюджет сделки записывать стоимость товара, а в кастомное поле "Маржинальность" записывать 50% от стоимость товара.

Предположим, название и стоимость сайт передает на наш эндпоинт GET-запросом.

Подготовка

Если поле Бюджет уже есть в amoCRM, то остальные поля кастомные, их нужно создать. Поэтому первым делом через интерфейс создадим необходимую структуру полей.

Открываем интерфейс создания сделки и переходим в настройки.
Открываем интерфейс создания сделки и переходим в настройки.
Создадим новое поле, оставив тип по умолчанию - Текст и введя название и нажмем Сохранить.
Создадим новое поле, оставив тип по умолчанию - Текст и введя название и нажмем Сохранить.
После сохранения amoCRM создаст поле и присвоит ему id - уникальный идентификатор в рамках одного аккаунта. Нажмите на вновь созданное поле и вы увидите его id.
После сохранения amoCRM создаст поле и присвоит ему id - уникальный идентификатор в рамках одного аккаунта. Нажмите на вновь созданное поле и вы увидите его id.
Повторим те же действия для поля "Маржинальность", но при этом выбрав тип "Число".
Повторим те же действия для поля "Маржинальность", но при этом выбрав тип "Число".
Дополним наш конфиг значениями этих полей.
Дополним наш конфиг значениями этих полей.

Разработка

Нам нужно будет точно так же собрать клиент, создать модель сделки, а потом сохранить ее в amoCRM. Библиотека реализует очень простой и интуитивно-понятный интерфейс для всех действий.

Тут кода побольше, поэтому разделил на два скриншота.

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

Первая часть кода
Первая часть кода

Далее нам нужно собрать модель сделки - специальный объект из библиотеки, который отправим в API с помощью клиента. Вначале все просто - установим название и цену. Далее чуть сложнее - устанавливаем громоздкий метод коллекции кастомных полей. Если пиглядется можно понять, что создается экземпляр коллекции CustomFieldsValuesCollection, а затем с помощью метода add() в него добавляется два объекта для каждого кастомного поля - Текстового и Числового. В каждом указывается айди из env и значение, которое мы рассчитали выше.

На текущий момент у нас есть локально собранная модель. Теперь ее нужно сохранить в амо. Важно понимать это.

После этого с помощью метода leads() клиента API и метода addOne() мы отправляем собранную модель в amoCRM. amoCRM возвращает id сделки и мы можем его получить с помощью метода getId() для вывода.

Вторая часть кода
Вторая часть кода

После этого переходим по URL, имитируя посланный GET-запрос: https://shalalala.amocrm.dev/api/lead-create.php?product_name=Печенье&product_price=3670

В ответ мы получили id сделки.
В ответ мы получили id сделки.
В первой воронке и первом этапе мы можем обнаружить нашу сделку. Что бы создать в другом этапе другой воронки можно передать их id в модель сделки при создании с помощью методов setPipelineId() и setPipelineId().
В первой воронке и первом этапе мы можем обнаружить нашу сделку. Что бы создать в другом этапе другой воронки можно передать их id в модель сделки при создании с помощью методов setPipelineId() и setPipelineId().
Внутри сделки мы видим запалённые наши поля, название. Сделка будет создавать на того пользователя, который подключил интеграцию (токен привязан к пользователю). Что бы создать на другого, можно использовать метод setResponsibleUserId() для модели сделки.
Внутри сделки мы видим запалённые наши поля, название. Сделка будет создавать на того пользователя, который подключил интеграцию (токен привязан к пользователю). Что бы создать на другого, можно использовать метод setResponsibleUserId() для модели сделки.

Вот и все :)

Обновление токенов

То что написано ниже - моя попытка просто изложить работу O2Auth протокола.

Выше можно было заметить что токена нам дали два - access и refresh. Мы особо не погружались как оно работает (спасибо библиотеке), но при всех запросах выше мы использовали только access токен. Зачем же нужен refrersh?

На самом деле access токен живет недолго - всего 24 часа с момента выпуска. Это сделано из соображений безопасности. Для его обновления и нужен refresh.

С помощью отдельного метода можно заменить refresh токен на новую связку access и refresh. Подробнее про токены можно почитать тут. При этому старый refresh и access перестанут работать.

Есть реферш токеном не пользоваться месяц (не обновлять по нему аксесс), то он тоже перестанет быть валидным.

Есть два решения обновления токенов

  1. обновление по планировщику - каждый день (а лучше два раза обновляем токен принудительно)
  2. обновление по потребности - в случае если мы делаем запрос в амо, а токен умер, мы попробуем его обновить и сделать запрос

Обновление по планировщику

Собираем клиент и с помощью OAuth клиента получаем новый токен из старого и сохраняем его.

Скрипт
Скрипт
Результат. Запуская скрипт можно увидеть что токен меняется
Результат. Запуская скрипт можно увидеть что токен меняется

Этот скрипт можно повесить на планировщик CRONTAB и регулярно обновлять токен.

Обновление по потребности

Собираем клиент и с помощью OAuth клиента получаем новый токен из старого и сохраняем его.

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

Реализуем его в метод api/account-info.php

Добавляем колбэк
Добавляем колбэк

Теперь вы можете проверить - открыть файл с токеном и стереть (или изменить) accessToken, после отправить GET- запрос на метод - https://shalalala.amocrm.dev/api/account-info.php.

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

Как лучше поступать?

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

Заключение

Мы подключились к апи, использовали библиотеку, получили инфо об аккаунте, создали сделку.

Проект на github.

Первый опыт написания подобной статьи, посмотрим насколько она оказалась полезной. Буду рад выслушать комментарии. Я на связи в телеграм - @makeroi.