Как реализовать процедуру подписания УПД в Диадоке через REST API в 1С?

Программист 1С v8.3 (Управляемые формы) Налоговый учет IT и автоматизация бизнеса
← На главную

Интеграция 1С с системами электронного документооборота (ЭДО), такими как Диадок, через "голый" REST API — задача нетривиальная. Часто разработчики сталкиваются с кучей разрозненной документации, сложными схемами данных и неочевидными ошибками валидации. В этой статье мы подробно разберем, как правильно организовать процесс подписания входящего УПД (Универсального передаточного документа), избежать ручного формирования сложных XML-структур и решить распространенные проблемы при отправке данных.

Основные подходы к формированию титула покупателя

Когда мы подписываем входящий УПД, мы, по сути, формируем ответный документ — "Титул покупателя". В нем содержится информация о том, кто принял товар/услугу, есть ли претензии и данные подписанта. Существует два пути решения этой задачи:

  1. Ручное формирование XML. Вы создаете объект ЗаписьXML и методично, тег за тегом, прописываете всю структуру документа согласно XSD-схеме ФНС.
  2. Использование методов API Диадока. Вы передаете параметры в метод API, а сервис сам генерирует корректный XML, который остается только подписать.

Рассмотрим первый подход. Часто попытки собрать XML вручную приводят к ошибкам валидации, так как схемы (особенно форматы 5.02 и 5.03 по 970 приказу) очень строги к порядку полей и типам данных, поэтому лучше использовать готовые инструменты выгрузки в XML по приказам ФНС.

Пример попытки ручного сбора (который часто вызывает сложности):


// Пример того, как делать сложно и чревато ошибками
Файл = Новый ЗаписьXML;
Файл.ОткрытьФайл(ИмяФайла, "UTF-8");
Файл.ЗаписатьОбъявлениеXML();
Файл.ЗаписатьНачалоЭлемента("UniversalTransferDocumentBuyerTitle");
// ... множество атрибутов
Файл.ЗаписатьНачалоЭлемента("Signers");
Файл.ЗаписатьНачалоЭлемента("SignerDetails");
// Заполнение атрибутов подписанта
Файл.ЗаписатьАтрибут("LastName", ДанныеТитула.Фамилия);
// ...
Файл.ЗаписатьКонецЭлемента();
Файл.Закрыть();

Главная проблема здесь — необходимость отслеживать актуальность форматов. Если ФНС выпустит новый приказ, ваш код придется переписывать. Поэтому мы рекомендуем и подробно разберем второй путь — использование метода GenerateTitleXml.

Алгоритм правильного подписания документа

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

Этап 1. Получение данных исходного документа

Прежде чем подписывать документ, нам нужно получить его данные, чтобы иметь на руках 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

Этап 2. Генерация XML титула покупателя

Вместо того чтобы собирать 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.

Этап 3. Наложение электронной подписи

Теперь, когда у нас есть корректный XML (в переменной ТитулДокументаДД), его нужно подписать сертификатом организации. Здесь используются стандартные средства криптографии, доступные в 1С (через менеджер криптографии или COM-объекты типа CAPICOM/CAdESCOM).

Важный момент: Диадок принимает открепленную подпись (detached signature) в кодировке DER, преобразованную в Base64 (в JSON-структурах API).


// Примерная схема подписания (зависит от вашей реализации криптографии в 1С)
ДанныеПодписи = МенеджерКриптографии.Подписать(ТитулДокументаДД, Сертификат, Ложь); // Ложь = открепленная

Этап 4. Отправка подписанного документа (Patch)

Самый сложный момент, на котором часто возникают ошибки, — это отправка подписи и файла обратно в Диадок. Для этого используется метод PostMessagePatch (или отправка патча к сообщению).

Мы должны отправить структуру, содержащую:

  1. Идентификатор родительской сущности (к чему мы крепим титул).
  2. Сам XML-файл (который мы получили на этапе 2).
  3. Подпись этого файла (которую мы получили на этапе 3).

Проблема "Полки" и ошибка конфликта параметров

В ходе обсуждения выяснилось, что при отправке патча часто возникает ошибка, связанная с неправильным заполнением структуры 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:

  1. Прекратите формировать XML вручную через ЗаписьXML. Используйте метод GenerateTitleXml. Он гарантирует соответствие актуальным приказам ФНС (в том числе переход с формата 820 на 970).
  2. Проверьте параметры генерации. Для УПД важно правильно указывать версию формата в параметре documentVersion.

Если вы получаете ошибку при отправке (PostMessagePatch):

  1. Убедитесь, что вы не смешиваете прямую передачу контента (Content) и ссылки на полку (NameOnShelf).
  2. Если вы дорабатываете типовой модуль Диадока, найдите место, где заполняется структура SignedContent, и принудительно очистите NameOnShelf, если вы передаете данные в Content.

Используя этот подход, вы сэкономите массу времени на поддержке актуальности форматов документов, так как за структуру XML будет отвечать сам оператор ЭДО.

← На главную