Интеграция 1С с системами электронного документооборота (ЭДО), такими как Диадок, через "голый" REST API — задача нетривиальная. Часто разработчики сталкиваются с кучей разрозненной документации, сложными схемами данных и неочевидными ошибками валидации. В этой статье мы подробно разберем, как правильно организовать процесс подписания входящего УПД (Универсального передаточного документа), избежать ручного формирования сложных XML-структур и решить распространенные проблемы при отправке данных.
Когда мы подписываем входящий УПД, мы, по сути, формируем ответный документ — "Титул покупателя". В нем содержится информация о том, кто принял товар/услугу, есть ли претензии и данные подписанта. Существует два пути решения этой задачи:
ЗаписьXML и методично, тег за тегом, прописываете всю структуру документа согласно XSD-схеме ФНС.Рассмотрим первый подход. Часто попытки собрать XML вручную приводят к ошибкам валидации, так как схемы (особенно форматы 5.02 и 5.03 по 970 приказу) очень строги к порядку полей и типам данных, поэтому лучше использовать готовые инструменты выгрузки в XML по приказам ФНС.
Пример попытки ручного сбора (который часто вызывает сложности):
// Пример того, как делать сложно и чревато ошибками
Файл = Новый ЗаписьXML;
Файл.ОткрытьФайл(ИмяФайла, "UTF-8");
Файл.ЗаписатьОбъявлениеXML();
Файл.ЗаписатьНачалоЭлемента("UniversalTransferDocumentBuyerTitle");
// ... множество атрибутов
Файл.ЗаписатьНачалоЭлемента("Signers");
Файл.ЗаписатьНачалоЭлемента("SignerDetails");
// Заполнение атрибутов подписанта
Файл.ЗаписатьАтрибут("LastName", ДанныеТитула.Фамилия);
// ...
Файл.ЗаписатьКонецЭлемента();
Файл.Закрыть();
Главная проблема здесь — необходимость отслеживать актуальность форматов. Если ФНС выпустит новый приказ, ваш код придется переписывать. Поэтому мы рекомендуем и подробно разберем второй путь — использование метода GenerateTitleXml.
Процесс подписания входящего документа можно разделить на несколько ключевых этапов. Давайте пройдемся по каждому из них.
Прежде чем подписывать документ, нам нужно получить его данные, чтобы иметь на руках EntityId (идентификатор сущности документа) и MessageId (идентификатор сообщения), предварительно настроив авторизацию через HTTP-сервис. Это необходимо для привязки нашего ответа.
Используем метод GetDocument, который предварительно можно проверить через инструмент тестирования HTTP-сервисов:
URL = "/V3/GetDocument";
// boxid, MessageID, EntityId должны быть известны заранее (например, из списка входящих)
HTTPЗапрос = Новый HTTPЗапрос(URL + "?boxid=" + boxid + "&MessageID=" + MessageID + "&EntityId="+EntityId);
HTTPЗапрос.Заголовки.Вставить("Accept", "application/json");
HTTPЗапрос.Заголовки.Вставить("Authorization", "DiadocAuth ddauth_api_client_id="+ddauth_api_client + ",ddauth_token="+Токен);
HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
Результат = HTTPОтвет.ПолучитьТелоКакСтроку();
Реквизиты = ПолучитьСтруктуруИзJSON(Результат); // Ваша функция парсинга JSON
Вместо того чтобы собирать XML вручную, мы поручим это серверу Диадока. Для этого используется метод GenerateTitleXml. Мы отправляем запрос, указывая параметры документа, и получаем готовый бинарный файл (XML), который соответствует всем требованиям текущего законодательства.
Обратите внимание на формирование запроса для авторизации в системе:
// Формируем URL для генерации титула
URL = "/GenerateTitleXml";
// Параметры строки запроса (Query Parameters)
ПараметрыЗапроса = "?boxid=" + boxid +
"&documentTypeNamedId=" + DocumentType + // например, UniversalTransferDocument
"&documentFunction=" + DocumentFunction + // например, CCE (счет-фактура и первичка)
"&documentVersion=" + DocumentVersion + // версия формата, например, utd_05_02_01
"&titleIndex=1" + // 1 - это титул покупателя
"&LetterID=" + MessageId +
"&DocumentID=" + EntityId;
HTTPЗапрос = Новый HTTPЗапрос(URL + ПараметрыЗапроса);
// Если нужно передать специфические данные (например, данные доверенности или подписанта),
// их можно передать в теле запроса в формате POST, либо, если данные стандартные,
// Диадок может взять их из настроек организации, но чаще требуется передача тела.
// В простом случае, если используем параметры по умолчанию или тело UserData:
HTTPЗапрос.Заголовки.Вставить("Authorization", "DiadocAuth ddauth_api_client_id="+ClientId + ",ddauth_token="+Token);
// Если отправляем уточнения данных в теле (в JSON или Protobuf), нужно указать Content-Type
// HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json");
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос); // Обычно это POST запрос
Если HTTPОтвет.КодСостояния <> 200 Тогда
Результат = HTTPОтвет.ПолучитьТелоКакСтроку();
Сообщить("Ошибка формирования титула документа: " + Результат);
Возврат;
КонецЕсли;
// Получаем готовый XML файл в виде двоичных данных
ТитулДокументаДД = HTTPОтвет.ПолучитьТелоКакДвоичныеДанные();
Этот метод возвращает нам именно тот файл, который нужно подписать. Нам не нужно думать о порядке тегов SignerDetails или SignerReference.
Теперь, когда у нас есть корректный XML (в переменной ТитулДокументаДД), его нужно подписать сертификатом организации. Здесь используются стандартные средства криптографии, доступные в 1С (через менеджер криптографии или COM-объекты типа CAPICOM/CAdESCOM).
Важный момент: Диадок принимает открепленную подпись (detached signature) в кодировке DER, преобразованную в Base64 (в JSON-структурах API).
// Примерная схема подписания (зависит от вашей реализации криптографии в 1С)
ДанныеПодписи = МенеджерКриптографии.Подписать(ТитулДокументаДД, Сертификат, Ложь); // Ложь = открепленная
Самый сложный момент, на котором часто возникают ошибки, — это отправка подписи и файла обратно в Диадок. Для этого используется метод PostMessagePatch (или отправка патча к сообщению).
Мы должны отправить структуру, содержащую:
В ходе обсуждения выяснилось, что при отправке патча часто возникает ошибка, связанная с неправильным заполнением структуры SignedContent. Рассмотрим это подробнее.
Структура данных для отправки (RecipientTitleAttachment) содержит поле SignedContent. В нем есть два взаимоисключающих способа передачи самого файла:
Суть ошибки: Если в вашей структуре JSON (или объекте 1С, который сериализуется в JSON) заполнены оба этих поля, или если заполнено NameOnShelf значением по умолчанию (когда вы не загружали ничего на полку), сервер вернет ошибку или не сможет корректно обработать запрос.
Ошибка может выглядеть как невозможность найти документ на полке или общая ошибка валидации структуры.
Решение: При формировании JSON для метода PostMessagePatch убедитесь, что вы передаете файл напрямую в Content (для небольших файлов типа УПД это оптимально) и при этом поле NameOnShelf либо отсутствует, либо равно Null/пустой строке (в зависимости от сериализатора).
Пример правильной структуры отправки патча (псевдокод формирования JSON):
СтруктураPatch = Новый Структура;
СтруктураPatch.Вставить("BoxId", BoxId);
СтруктураPatch.Вставить("MessageId", MessageId);
СписокТитулов = Новый Массив;
Титул = Новый Структура;
Титул.Вставить("ParentEntityId", EntityId); // ID исходного документа
ПодписанныйКонтент = Новый Структура;
// Важно! Передаем контент файла, полученного из GenerateTitleXml
ПодписанныйКонтент.Вставить("Content", Base64Строка(ТитулДокументаДД));
// Важно! Поле NameOnShelf должно отсутствовать или быть пустым, если мы не используем полку
// ПодписанныйКонтент.Вставить("NameOnShelf", ""); <--- Лучше вообще не включать этот ключ
// Передаем подпись
ПодписанныйКонтент.Вставить("Signature", Base64Строка(ДанныеПодписи));
Титул.Вставить("SignedContent", ПодписанныйКонтент);
СписокТитулов.Добавить(Титул);
СтруктураPatch.Вставить("RecipientTitles", СписокТитулов);
// Сериализация в JSON и отправка POST запроса на /V3/PostMessagePatch
Если при подписании вы получаете ошибку вида SignerDetails not found или проблемы с валидацией XSD:
ЗаписьXML. Используйте метод GenerateTitleXml. Он гарантирует соответствие актуальным приказам ФНС (в том числе переход с формата 820 на 970).documentVersion.Если вы получаете ошибку при отправке (PostMessagePatch):
Content) и ссылки на полку (NameOnShelf).SignedContent, и принудительно очистите NameOnShelf, если вы передаете данные в Content.Используя этот подход, вы сэкономите массу времени на поддержке актуальности форматов документов, так как за структуру XML будет отвечать сам оператор ЭДО.