Задача звучит одинаково у всех: сайт оформил заказ — он должен появиться в УНФ. Вариантов три: стандартный интерфейс OData, обмен файлами и собственный HTTP-сервис. OData быстро включается, но отдаёт наружу структуру базы и плохо переживает изменения. Файловый обмен надёжен, но не даёт мгновенного ответа сайту. HTTP-сервис — когда нужен контролируемый контракт и ответ в реальном времени.
Создание сервиса
В конфигураторе (или, что правильнее для типовой, в расширении): ветка «Общие → HTTP-сервисы». Создаём сервис IntegrationAPI с корневым URL api, добавляем шаблон URL /orders и метод POST с обработчиком ЗаказыPOST.
Обработчик в модуле сервиса:
Функция ЗаказыPOST(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Попытка
ТелоЗапроса = Запрос.ПолучитьТелоКакСтроку("UTF-8");
Данные = ПрочитатьJSONВСтруктуру(ТелоЗапроса);
Исключение
Возврат ОтветСОшибкой(400, "Некорректный JSON: " + ОписаниеОшибки());
КонецПопытки;
ВнешнийНомер = ОбщегоНазначенияКлиентСервер.СтруктураСвойство(Данные, "order_id");
Если Не ЗначениеЗаполнено(ВнешнийНомер) Тогда
Возврат ОтветСОшибкой(400, "Не передан order_id");
КонецЕсли;
// Идемпотентность: повторный запрос с тем же order_id не создаёт второй заказ.
СуществующийЗаказ = НайтиЗаказПоВнешнемуНомеру(ВнешнийНомер);
Если ЗначениеЗаполнено(СуществующийЗаказ) Тогда
Ответ.УстановитьТелоИзСтроки(ОтветПоЗаказу(СуществующийЗаказ, "duplicate"), КодировкаТекста.UTF8);
Возврат Ответ;
КонецЕсли;
Попытка
Заказ = СоздатьЗаказПокупателя(Данные, ВнешнийНомер);
Исключение
ЗаписьЖурналаРегистрации("Интеграция.Заказ с сайта",
УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки() + Символы.ПС + ТелоЗапроса);
Возврат ОтветСОшибкой(500, "Не удалось создать заказ");
КонецПопытки;
Ответ.УстановитьТелоИзСтроки(ОтветПоЗаказу(Заказ, "created"), КодировкаТекста.UTF8);
Возврат Ответ;
КонецФункции
Вспомогательные функции — там же, в модуле сервиса:
Функция ПрочитатьJSONВСтруктуру(Строка)
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Строка);
Результат = ПрочитатьJSON(Чтение, Истина);
Чтение.Закрыть();
Возврат Результат;
КонецФункции
Функция ОтветСОшибкой(КодСостояния, Текст)
Ответ = Новый HTTPСервисОтвет(КодСостояния);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Новый Структура("status, message", "error", Текст));
Ответ.УстановитьТелоИзСтроки(Запись.Закрыть(), КодировкаТекста.UTF8);
Возврат Ответ;
КонецФункции
Поиск дубля и создание документа
Внешний номер заказа хранится в дополнительном реквизите или в отдельном регистре сведений — это и есть ключ идемпотентности. Регистр сведений предпочтительнее: он индексируется и не требует изменения структуры документа.
Функция НайтиЗаказПоВнешнемуНомеру(ВнешнийНомер)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Соответствие.Заказ КАК Заказ
|ИЗ
| РегистрСведений.ЗаказыСайта КАК Соответствие
|ГДЕ
| Соответствие.ВнешнийНомер = &ВнешнийНомер";
Запрос.УстановитьПараметр("ВнешнийНомер", ВнешнийНомер);
Выборка = Запрос.Выполнить().Выбрать();
Возврат ?(Выборка.Следующий(), Выборка.Заказ, Неопределено);
КонецФункции
Само создание документа — обычный код: находим или создаём контрагента по телефону и почте, заполняем шапку и товары, записываем документ, пишем строку в регистр соответствия. Проводить документ сразу не обязательно: заказ с сайта чаще требует подтверждения менеджером.
Ключевое правило: при любой ошибке заказ не должен потеряться. Записывайте исходный JSON в журнал регистрации или в регистр сведений «Входящие сообщения» до начала разбора. Тогда сбой конвертации превращается в «разобрать три записи руками», а не в «клиент не получил товар».
Публикация на веб-сервере
Конфигуратор → «Администрирование → Публикация на веб-сервере». Обязательно включите флажок публикации HTTP-сервисов и отметьте нужный сервис в списке. Публикация создаёт файл default.vrd и настройки в Apache или IIS.
Проверка из командной строки:
curl -X POST "https://1c.example.ru/unf/hs/api/orders" ^
-u "SiteIntegration:пароль" ^
-H "Content-Type: application/json" ^
-d "{\"order_id\":\"W-10231\",\"phone\":\"+70000000000\",\"items\":[{\"sku\":\"A-100\",\"qty\":2}]}"
Обратите внимание на фрагмент /hs/ в адресе — так публикуются именно HTTP-сервисы, дальше идёт корневой URL сервиса и шаблон.
Безопасность
Публикация базы наружу — это то место, где ошибка стоит дороже всего:
- отдельный пользователь для интеграции, с минимальными правами и без интерактивного входа;
- только HTTPS, сертификат на веб-сервере, никакого HTTP «пока тестируем»;
- ограничение по IP на уровне веб-сервера или брандмауэра, если сайт имеет фиксированный адрес;
- в публикации отключить всё лишнее: веб-клиент, OData, если они не нужны;
- логирование всех обращений и регулярный просмотр журнала регистрации;
- лимит на размер тела запроса и защита от повторной отправки — идемпотентность, о которой шла речь выше.
Что делать с долгими операциями
Если обработка заказа занимает больше двух-трёх секунд, не держите соединение: примите данные, положите их в регистр «входящих», верните 202 Accepted и обрабатывайте фоновым заданием. Сайт получит быстрый ответ, а вы — возможность повторить обработку после сбоя, не теряя данные.