Интеграция 1С с Битрикс CRM через REST API



На фоне неутихающего обострения «бизнеса» по внедрению СРМ-систем остро встают вопросы обмена данными с уже существующими системами. В статье рассматривается выгрузка контактов, товаров и сделок из 1С в Битрикс CRM через REST API, приложена обработка для тестирования.

Существуют стандартные интеграции Битрикс и 1С, но они умеют плюс/минус ничего, зато с использованием rest api Битрикса можно получать и передавать практически все что угодно, двигать сделки по этапам, назначать ответственных и т.п.

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

Для изучения и тестирования методов настоятельно рекомендуется битриксовское же приложение «Документация по REST API». СРМу там посвящен свой раздел.

Обработка тестировалась на следующих версиях ПО:

1С-Битрикс: Корпоративный портал 18.1.8

1С:Предприятие 8.3 (8.3.14.1779)

Бухгалтерия предприятия КОРП, редакция 3.0 (3.0.70.39) (хотя конфигурация значения не имеет, в обработке используются только механизмы платформы)

 

Теория

Основная логика реализована в функции, которая действует по следующему алгоритму:

На входе получает структуру, где указан тип сущности, ее ORIGIN_ID (в боевых условиях для этих целей используются ГУИДы 1с) и структура всех полей  — типа названия, стоимости и т.д.

Ищет сущность в СРМ по ORIGIN_ID  

Если не находит – создает

Находит одну сущность – обновляет ее

Находит больше одной сущности – выдает ошибку

В случае успеха возвращает результат ок и Битрикс ИД созданной/обновленной сущности

В случае ошибки error и текст ошибки

&НаСервере
// Ищет и обновляет/создает сущность в СРМ Битрикс
// На входе - структура:
// СущностьСРМ - тип сущности, строка: contact, company, lead, deal, productsection, product
// ORIGIN_ID - ГУИД объекта 1С
// fields - структура полей для обновления
// Возвращает:
// Результат - ок/error
// Значение - если ок - битрикс ИД созданного/обновленного элемента, если error - текст ошибки
Функция СоздатьОбновитьСущностьБитрикс(СтруктураСущности)
//Готовим структуру для возврата
СтруктураСозданияОбновленияСущности = Новый Структура("Результат, Значение");

//Получаем настройки
ЛогинПортала = ЛогинПортала;
ПарольПортала = ПарольПортала;
АдресПортала = АдресПортала;
RestВебхук = RestВебхук;

//Создаем соединение
Соединение = Новый HTTPСоединение(АдресПортала, , ЛогинПортала, ПарольПортала,,, Новый ЗащищенноеСоединениеOpenSSL);

//Получаем название сущности СРМ
СущностьСРМ = СтруктураСущности.СущностьСРМ;

//Формируем запрос
HTTPЗапрос = Новый HTTPЗапрос(RestВебхук + "crm." + СущностьСРМ + ".list.json/");

//Запрос для поиска
СтрокаЗапроса = "";
Если СтруктураСущности.СущностьСРМ = "productsection" ИЛИ СтруктураСущности.СущностьСРМ = "product" Тогда
СтрокаЗапроса = СтрокаЗапроса + "filter[XML_ID]=" + КодироватьСтроку(СтруктураСущности.ORIGIN_ID, СпособКодированияСтроки.КодировкаURL);
Иначе
СтрокаЗапроса = СтрокаЗапроса + "filter[ORIGIN_ID]=" + КодироватьСтроку(СтруктураСущности.ORIGIN_ID, СпособКодированияСтроки.КодировкаURL);
КонецЕсли;

HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаЗапроса, КодировкаТекста.UTF8);
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
СтруктураОтвета = HTTPОтвет.ПолучитьТелоКакСтроку();

//Разбираем ответ
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтруктураОтвета);
Попытка
СтруктураОтвета = ПрочитатьJSON(ЧтениеJSON);
Исключение
СтруктураСозданияОбновленияСущности.Результат = "error";
СтруктураСозданияОбновленияСущности.Значение = ОписаниеОшибки() + " | " + СтруктураОтвета;
Возврат СтруктураСозданияОбновленияСущности;
КонецПопытки;


//////////////
Если СтруктураОтвета.Свойство("total") И СтруктураОтвета.total = 1 Тогда
//Найдена одна сущность, обновляем
Соединение = Новый HTTPСоединение(АдресПортала, , ЛогинПортала, ПарольПортала,,, Новый ЗащищенноеСоединениеOpenSSL);
ИДНайденнойСущности = СтруктураОтвета.result[0].ID;
HTTPЗапрос = Новый HTTPЗапрос(RestВебхук + "crm." + СущностьСРМ + ".update.json?id=" + ИДНайденнойСущности);

СтрокаЗапроса = "";
Для Каждого Поле Из СтруктураСущности.fields Цикл
Если ТипЗнч(Поле.Значение) = Тип("Массив") И (Поле.Ключ = "CONTACT_IDS") Тогда
//Это для связанных объектов
Для Индекс = 0 По Поле.Значение.Количество()-1 Цикл
СтрокаЗапроса = СтрокаЗапроса + "&fields[" + Поле.Ключ + "][" + Индекс + "]=" + КодироватьСтроку(Поле.Значение[Индекс], СпособКодированияСтроки.КодировкаURL);
КонецЦикла;
ИначеЕсли ТипЗнч(Поле.Значение) = Тип("Массив") Тогда
//Сие для телефонов и почты
Для Индекс = 0 По Поле.Значение.Количество()-1 Цикл
СтрокаЗапроса = СтрокаЗапроса + "&fields[" + Поле.Ключ + "][" + Индекс + "][VALUE]=" + КодироватьСтроку(Поле.Значение[Индекс], СпособКодированияСтроки.КодировкаURL);
КонецЦикла;
Иначе
СтрокаЗапроса = СтрокаЗапроса + "&fields[" + Поле.Ключ + "]=" + КодироватьСтроку(Поле.Значение, СпособКодированияСтроки.КодировкаURL);
КонецЕсли
КонецЦикла;

HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаЗапроса, КодировкаТекста.UTF8);
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
СтруктураОтвета = HTTPОтвет.ПолучитьТелоКакСтроку();

//Разбираем ответ
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтруктураОтвета);
СтруктураОтвета = ПрочитатьJSON(ЧтениеJSON);

Если СтруктураОтвета.Свойство("result") И СтруктураОтвета.result Тогда
//Все ок
СтруктураСозданияОбновленияСущности.Результат = "ок";
СтруктураСозданияОбновленияСущности.Значение = ИДНайденнойСущности;
Возврат СтруктураСозданияОбновленияСущности;
ИначеЕсли СтруктураОтвета.Свойство("error") Тогда
//Сервис вернул ошибку
ТекстОшибки = "Ошибка при обновлении " + СущностьСРМ + ": " + СтруктураОтвета.error;
Если СтруктураОтвета.Свойство("error_description") И СтруктураОтвета.error_description <> "" Тогда
ТекстОшибки = ТекстОшибки + ", error_description: " + СтруктураОтвета.error_description;
КонецЕсли;
СтруктураСозданияОбновленияСущности.Результат = "error";
СтруктураСозданияОбновленияСущности.Значение = ТекстОшибки;
Возврат СтруктураСозданияОбновленияСущности;
Иначе
//Прочая неизвестная хрень
СтруктураСозданияОбновленияСущности.Результат = "error";
СтруктураСозданияОбновленияСущности.Значение = "Неизвестная ошибка при обновлении " + СущностьСРМ + " с id=" + ИДНайденнойСущности;
Возврат СтруктураСозданияОбновленияСущности;
КонецЕсли;

ИначеЕсли СтруктураОтвета.Свойство("total") И СтруктураОтвета.total = 0 Тогда
//Не найдено, создаем
Соединение = Новый HTTPСоединение(АдресПортала, , ЛогинПортала, ПарольПортала,,, Новый ЗащищенноеСоединениеOpenSSL);
HTTPЗапрос = Новый HTTPЗапрос(RestВебхук + "crm." + СущностьСРМ + ".add.json/");

СтрокаЗапроса = "";

Если СтруктураСущности.СущностьСРМ = "productsection" ИЛИ СтруктураСущности.СущностьСРМ = "product" Тогда
СтрокаЗапроса = СтрокаЗапроса + "fields[XML_ID]=" + КодироватьСтроку(СтруктураСущности.ORIGIN_ID, СпособКодированияСтроки.КодировкаURL);
Иначе
СтрокаЗапроса = СтрокаЗапроса + "fields[ORIGIN_ID]=" + КодироватьСтроку(СтруктураСущности.ORIGIN_ID, СпособКодированияСтроки.КодировкаURL);
КонецЕсли;

Для Каждого Поле Из СтруктураСущности.fields Цикл
Если ТипЗнч(Поле.Значение) = Тип("Массив") И (Поле.Ключ = "CONTACT_IDS") Тогда
//Это для связанных объектов
Для Индекс = 0 По Поле.Значение.Количество()-1 Цикл
СтрокаЗапроса = СтрокаЗапроса + "&fields[" + Поле.Ключ + "][" + Индекс + "]=" + КодироватьСтроку(Поле.Значение[Индекс], СпособКодированияСтроки.КодировкаURL);
КонецЦикла;
ИначеЕсли ТипЗнч(Поле.Значение) = Тип("Массив") Тогда
//Сие для телефонов и почты
Для Индекс = 0 По Поле.Значение.Количество()-1 Цикл
СтрокаЗапроса = СтрокаЗапроса + "&fields[" + Поле.Ключ + "][" + Индекс + "][VALUE]=" + КодироватьСтроку(Поле.Значение[Индекс], СпособКодированияСтроки.КодировкаURL);
КонецЦикла;
Иначе
СтрокаЗапроса = СтрокаЗапроса + "&fields[" + Поле.Ключ + "]=" + КодироватьСтроку(Поле.Значение, СпособКодированияСтроки.КодировкаURL);
КонецЕсли
КонецЦикла;

HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаЗапроса, КодировкаТекста.UTF8);
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
СтруктураОтвета = HTTPОтвет.ПолучитьТелоКакСтроку();

//Разбираем ответ
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтруктураОтвета);
СтруктураОтвета = ПрочитатьJSON(ЧтениеJSON);

//////////////
Если СтруктураОтвета.Свойство("result") И СтруктураОтвета.result <> "" Тогда
СтруктураСозданияОбновленияСущности.Результат = "ок";
СтруктураСозданияОбновленияСущности.Значение = Формат(СтруктураОтвета.result, "Л=ru_RU; ЧГ=0");
Возврат СтруктураСозданияОбновленияСущности;
ИначеЕсли СтруктураОтвета.Свойство("error") Тогда
//Сервис вернул ошибку
ТекстОшибки = "Ошибка при создании " + СущностьСРМ + ": " + СтруктураОтвета.error;
Если СтруктураОтвета.Свойство("error_description") И СтруктураОтвета.error_description <> "" Тогда
ТекстОшибки = ТекстОшибки + ", error_description: " + СтруктураОтвета.error_description;
КонецЕсли;
СтруктураСозданияОбновленияСущности.Результат = "error";
СтруктураСозданияОбновленияСущности.Значение = ТекстОшибки;
Возврат СтруктураСозданияОбновленияСущности;
Иначе
//Прочая неизвестная хрень
СтруктураСозданияОбновленияСущности.Результат = "error";
СтруктураСозданияОбновленияСущности.Значение = "Неизвестная ошибка при создании " + СущностьСРМ;
Возврат СтруктураСозданияОбновленияСущности;
КонецЕсли;
ИначеЕсли СтруктураОтвета.Свойство("total") И СтруктураОтвета.total > 1 Тогда
//Найдено больше одной компании, обтекаем
СтруктураСозданияОбновленияСущности.Результат = "error";
СтруктураСозданияОбновленияСущности.Значение = "Нарушена целостность данных. Найдено больше одной " + СущностьСРМ + " с одинаковым ORIGIN_ID";
Возврат СтруктураСозданияОбновленияСущности;
ИначеЕсли СтруктураОтвета.Свойство("error") Тогда
//Сервис вернул ошибку
ТекстОшибки = "Ошибка при поиске " + СущностьСРМ + ": " + СтруктураОтвета.error;
Если СтруктураОтвета.Свойство("error_description") И СтруктураОтвета.error_description <> "" Тогда
ТекстОшибки = ТекстОшибки + ", error_description: " + СтруктураОтвета.error_description;
КонецЕсли;
СтруктураСозданияОбновленияСущности.Результат = "error";
СтруктураСозданияОбновленияСущности.Значение = ТекстОшибки;
Возврат СтруктураСозданияОбновленияСущности;
Иначе
//Прочая неизвестная хрень
СтруктураСозданияОбновленияСущности.Результат = "error";
СтруктураСозданияОбновленияСущности.Значение = "Неизвестная ошибка при поиске " + СущностьСРМ;
Возврат СтруктураСозданияОбновленияСущности;
КонецЕсли;
КонецФункции

 

Использование обработки для тестирования

Указываем настройки

Адрес  портала в виде «portal.mysite.com»  ( без https:// )

Логин и пароль портала если используется http авторизация

Вебхук в виде «rest/1234/abc7skjbv1vyqxb2/» — т.е. точка доступа и собственно вебхук в одной строке

 

Отправка

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

 

Результат на портале

7 Comments

  1. osivv

    Браво!

    Наконец-то и Битриксе стали что-то делать в направлении REST API, до классического правда далеко и не соответствует, но все таки. Потому что, на мой сугубо субъективный взгляд, не оптимально в «СтрокаЗапроса» запихивать все передаваемые «Ключ=Значение», а если их штук 20? Если дадите ссылки на документацию, буду признателен, я внятную не нашел, не говоря уже о каких-то примерах использования (чисто для расширения своих компьютерных знаний, в работе использую другой).

    А разве нельзя использовать обработчик ошибок по коду ответа, например КодОтвета=200 (всё ОК)? Потому что, проще выделить обработчик ответа в одну функцию и по коду понимать ошибка у нас или нет. Хотя не удивлюсь, что битрикс (как обычно) пошел своим, уникальным путём.

    А картинки таким макаром можно публиковать?

    По моему, это всё-таки не REST API, а как вы сами написали «Вебхуки».

    Через «Postman» или «curl» тестировать можно?

    В общем жирный «+» в карму.

    Reply
  2. muzipov

    Спасибо за оценку!

    (1)

    Если дадите ссылки на документацию, буду признателен

    Документация по REST Битрикса https://dev.1c-bitrix.ru/rest_help/, также она реализована в виде приложения в самом Битриксе, там есть примеры и оттуда можно сразу запускать код.

    (1)

    А разве нельзя использовать обработчик ошибок по коду ответа, например КодОтвета=200 (всё ОК)?

    Нет, потому что в некоторых случаях может возвращаться 200, а по факту то что нужно не произошло. К тому же, почти всегда из ответа нужна какая-то дополнительная информация — код найденного или созданного объекта например.

    (1)

    А картинки таким макаром можно публиковать?

    Методы REST-сервиса получают файлы в виде строки, закодированной в base64

    (1)

    Через «Postman» или «curl» тестировать можно?

    Конечно можно, я для этих целей использую SOAP UI

    Reply
  3. ltfriend

    (1) а как оптимально передавать параметры в http запросе?

    Reply
  4. kuntashov

    > Существуют стандартные интеграции Битрикс и 1С, но они умеют плюс/минус ничего

    А вы когда последний раз пробовали?

    Модуль 1С:Синхронизация с Битрикс24 работает через REST API, первые версии были выпущены более года назад.

    Сегодня публикуются в виде расширения.

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

    Reply
  5. kuntashov

    (3) REST API битриксоиды реализовали с отступлением от канонов, например, они не различают «глаголы» и для них методы GET и POST работают одинаково, любой метод API можно вызывать как GET, так и POST запросом с одинаковым эффектом. Зато большой запрос можно отправлять POSTом (и это рекомендуется), тело запроса в этом случае формируется также как и query-string (application/x-www-form-urlencoded).

    Reply
  6. kuntashov

    (4) Вот здесь, например, релизы модуля для УТ https://1c.1c-bitrix.ru/intranet/download.php?section=102557

    Reply
  7. ltfriend

    (5) я задавал вопрос автору первого комментария конкретно к этому предложению:

    Потому что, на мой сугубо субъективный взгляд, не оптимально в «СтрокаЗапроса» запихивать все передаваемые «Ключ=Значение», а если их штук 20?

    Мне стало интересно, знает ли он более оптимальную передачу параметров в POST запросе, помимо передачи их строкой (application/x-www-form-urlencoded).

    Reply

Leave a Comment

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