Найти в Дзене
Просто <> "1С"

API Национальный каталог / 1С УТ10.3 Обмен по API c НК(ЧЗ) / Подписание технической карточки товара в НК / Маркировка остатков

от 19.02.2025

в дополнение к посту https://dzen.ru/a/Z5XraPMlFyLRMPo6

Задача: Задача кратко если изложить то поставлена так:

1. Автоматизировать процесс создания технических карточек на остатки номенклатуры в национальном каталоге (далее по тексту "НК"). Источник Номенклатуры таблица или выборка запроса. Выполнено (с горем на пополам запросы на создание тех. карточки долетаю до НК).

2. Проверять статус карточек в НК, осуществлять подпись тех что успешно прошли модерацию.

3. Заказывать коды маркировки по присвоенным GTIN на карточки.

4. Вводить в "оборот" коды маркировки обклеенной продукции.

Полный код в конце поста.

2.2 . Подпись карточек, что успешно прошли модерацию.

Согласно методичке "API "Национального каталога" подписание карточки должно быть в такой последовательности:

  • Получить XML файл нашей карточки от НК;
  • Подписать XML файл открепленной подписью;
  • Отправить обратно XML файл и подпись в НК.

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

Далее циклом по выборке получаю XML файл от НК

-2

Ключ входа = это ключ доступа "API key" примерно такого формата "9r8eilxdfb9xbcy8" берётся в личном кабинете НК. (см. https://dzen.ru/a/Z430k5P46He6_Wqd тут).

В тело запроса передается только GTIN.

Поскольку есть ограничение по количеству запросов во времени ввел переменную i . Она отсчитывает 495 запросов к НК и делает паузу в 10 минут. Без паузы если кидать подряд запросы в НК, то они блочатся и уходят в ошибку при чтении ответа (HTTPОтвет).

-3

В "СтруктураОтвета.result.xmls[0].xml" содержится наш заветный XML карточки, который требуется подписать. Убираем из него символы ПС и ВК чтоб был текст в одну строку. Всё остальное что ниже "XMLКарточкаТовара" , чтоб отлавливать ошибки при подписании.

Дальше подписываем выуденный из ответа XML (Переменная "XMLКарточкаТовара").

Подписывать я пробовал так:

- Сохранять XML в отдельный файл, подписывать его с помощью утилиты КриптоПро "cryptcp.x64.exe" . Подписать получилось , открепить подпись получилось но НК такую шляпу не принял от меня. Может что то делал не так , но в интернете особо инфы я не нашёл.

Вот код для cryptcp.x64.exe если кому будет интересно:

Отпечаток = "KуRvАMwуmsv3whwRwxqqj1KmfYу=";

ОтпечатокВ16 = СтрЗаменить(Base64Значение(Отпечаток)," ", ""); // Из 64 в 16 , т.к. в крипто.про в 16 виде.

ТеккстКомандыКриптоПро = "cryptcp.x64.exe -signf -detached " + """" + "C:\KriptoProObmen\*.xml" + """" + " -dir " + """" + "C:\KriptoProObmen" + """" + " -thumbprint " + ОтпечатокВ16;

ЗапуститьПриложение(ТеккстКомандыКриптоПро);

- Использовал "МенеджерКриптографии" , через него пропущенный XML НК тоже не принимал, ругался на подпись.

- И вот третий способ который у меня сработал это использовать COMОбъект "CAdESCOM" данный код я нашёл в интернете. Не могу кинуть ссылку на исходник поскольку он был в одной из тех сотен вкладок что я открывал, а найти сразу я не смог. #Подписание XML с помощью CAdESCOM / Крипто Про

-4

"Отпечаток =" должен хранится в справочнике; "СертификатыКлючейЭлектроннойПодписиИШифрования" я его от дуда функцией и вытягиваю отбирая по Организации и Дате действия;

"Отпечаток" обязательно конвертируем в HEX. В крипто про они хранятся в 16-ном виде (перем. "sThumbprint");

"XMLКарточкаТовара" конвертируем в Base64 (перем. "ТелоЗапроса");

В "oSignedData.Content" записывается наш XML в Base64 (перем. "ТелоЗапроса");

В "oSignedData.SignCades(oSigner, CADESCOM_CADES_TYPE, Истина, EncodingType)" здесь "Истина" значит что подписываем открепленной подписью. И на выходе "sSignedMessage" это наша открепленная подпись данных которые были в "ТелоЗапроса".

Дальше отправляем нашу открепленную подпись и то что мы подписали обратно в НК.

-5

В тело запроса загоняем: goodID он приходил к нам ещё когда мы создавали карточку в НК, XML файл преобразованный в Base64, и открепленную подпись "sSignedMessage".

Читаем ответ если ошибок нет значит усё путём. Подписание удалось.

-6

Я сделал что если при чтении "СтруктураОтвета.result.errors[0].message" ничего нет , то товары подписаны. Прибавляем к i единичку для отсчета запроса и формируем следующий.

3. Заказывать коды маркировки по присвоенным GTIN на карточки.

Теперь на нашу утв. карточку неплохо бы заказать КМ. Чтоб замаркировать остатки номенклатуры..

Опишу в следующей статье как доделаю :)

Полный Код:

Процедура ПодписатьВыборкуНажатие(Элемент)
// 1. htpp - параметры + Заголовок
ПараметрыОтправкиHTTPЗапросов = Новый Структура;
ПараметрыОтправкиHTTPЗапросов.Вставить("ПредставлениеСервиса", НСтр("ru = 'ГИС МТ'"));
ПараметрыОтправкиHTTPЗапросов.Вставить("Сервер", "апи.национальный-каталог.рф");
ПараметрыОтправкиHTTPЗапросов.Вставить("Порт", 443);
ПараметрыОтправкиHTTPЗапросов.Вставить("Таймаут", 60);
ПараметрыОтправкиHTTPЗапросов.Вставить("ИспользоватьЗащищенноеСоединение", Истина);
ЗаголовокHTTP = Новый Соответствие();
ЗаголовокHTTP.Вставить("Content-Type", "application/json; charset=utf-8");
ЗаголовокHTTP.Вставить("Accept-Charset", "utf-8");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СправочникКарточекТовара.Ссылка КАК Ссылка,
| СправочникКарточекТовара.ФидID КАК ФидID,
| СправочникКарточекТовара.GTIN КАК GTIN,
| СправочникКарточекТовара.GoodID КАК GoodID,
| СправочникКарточекТовара.ОрганизацияЭксплуатант КАК Подразделение
|ИЗ
| Справочник.СправочникКарточекТовара КАК СправочникКарточекТовара
|ГДЕ
| СправочникКарточекТовара.Статус = &Статус
| И СправочникКарточекТовара.ОрганизацияЭксплуатант = &ОрганизацияЭксплуатант
| И СправочникКарточекТовара.ПометкаУдаления = ЛОЖЬ";
Запрос.УстановитьПараметр("Статус", "товары прошли модерацию");
Запрос.УстановитьПараметр("ОрганизацияЭксплуатант", ПолеОрганизация);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
i=0;
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Если i = 495 тогда
i=0;
КомандаWindows = "Timeout /T " + Формат(60*10, "ЧГ=0") + " /NoBreak";
WshShell = Новый COMОбъект("WScript.Shell");
WshShell.Run(КомандаWindows, 0, -1);
КонецЕсли;
// 1. Получить XML файл
КлючВхода = ПОлучитьКлючВхода(ВыборкаДетальныеЗаписи.Подразделение);
URLЗапроса = "/v3/feed-product-document?apikey=" + КлючВхода;
// ПараметрыНоменклатуры в JSON (тело которое отправится в запросе)
ЗаписьJSON = Новый ЗаписьJSON();
ЗаписьJSON.УстановитьСтроку(Новый ПараметрыЗаписиJSON(, Символы.Таб));
ЗаписьJSON.ЗаписатьНачалоОбъекта();
ЗаписьJSON.ЗаписатьИмяСвойства("gtins"); // Массив GTIN -ов
ЗаписьJSON.ЗаписатьНачалоМассива();
ЗаписатьJSON(ЗаписьJSON, ВыборкаДетальныеЗаписи.GTIN);
ЗаписьJSON.ЗаписатьКонецМассива();
ЗаписьJSON.ЗаписатьКонецОбъекта();
ТелоЗапросаJSON = ЗаписьJSON.Закрыть();
// Отправляем запрос.
HTTPЗапрос = Новый HTTPЗапрос(URLЗапроса, ЗаголовокHTTP);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
HTTPОтвет = Неопределено;
ТекстОшибки = "";
ИнтернетПрокси = ПолучениеФайловИзИнтернета.ПолучитьПрокси("HTTPS");
ЗащищенноеСоединение = ИнтеграцияИСПовтИсп.ЗащищенноеСоединение();
Соединение = Новый HTTPСоединение(
ПараметрыОтправкиHTTPЗапросов.Сервер,
ПараметрыОтправкиHTTPЗапросов.Порт,,,
ИнтернетПрокси,
ПараметрыОтправкиHTTPЗапросов.Таймаут,
ЗащищенноеСоединение);
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос); // отправка
ПрочитатьОтвет = HTTPОтвет.ПолучитьТелоКакСтроку(); // ответ
// Читаем ответ
Попытка
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ПрочитатьОтвет);
СтруктураОтвета = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Если Строка(СтруктураОтвета.result.xmls[0].goodId) = ВыборкаДетальныеЗаписи.GoodID Тогда
XMLКарточкаТовара = СтруктураОтвета.result.xmls[0].xml; // XML карточки из ответа.
XMLКарточкаТовара = СтрЗаменить(XMLКарточкаТовара, Символы.ПС, "");
XMLКарточкаТовара = СтрЗаменить(XMLКарточкаТовара, Символы.ВК, "");
Иначе
Сообщить("Для каротчки с гуудID < " + ВыборкаДетальныеЗаписи.GoodID + " > не соответствует GoodID в ответе от сервера");
Продолжить;
КонецЕсли;
Исключение
Сообщить("Для каротчки с гуудID < " + ВыборкаДетальныеЗаписи.GoodID + " > ошибка чтения ответа от ЧЗ");
Сообщить(ПрочитатьОтвет);
Если Найти(ПрочитатьОтвет, "не готов к подписанию") > 0 Тогда
СсылкаНаОбъект = ВыборкаДетальныеЗаписи.Ссылка;
ОбъектСправочника = СсылкаНаОбъект.ПолучитьОбъект();
ОбъектСправочника.ИнфОбЗапросе = ПрочитатьОтвет;
ОбъектСправочника.Статус = "требует изменений" ;
ОбъектСправочника.Записать();
КонецЕсли;
Продолжить;
КонецПопытки;

// 2. Получить открепленную подпись и подписать документ.
CADESCOM_BASE64_TO_BINARY = 1; // Входные данные пришли в Base64
CADESCOM_CADES_TYPE = 1; // Тип усовершенствованной подписи
Отпечаток = ПолучитьОтпечатокЭП (ВыборкаДетальныеЗаписи.Подразделение); // он в Base64 , в самой крипто про в 16-ном
Если Отпечаток = Неопределено Тогда
Продолжить ;
КонецЕсли;
sThumbprint = СтрЗаменить(Base64Значение(Отпечаток)," ", "");
oSigner = Новый COMОбъект("CAdESCOM.CPSigner");
// Объект, задающий параметры создания и содержащий информацию об усовершенствованной подписи.
oSigner.Certificate = ПолучитьСертификатПоОтпечатку(sThumbprint);
// при подписании документов не используем штамп времени
oSigningTimeAttr = Новый COMОбъект("CAdESCOM.CPAttribute");
oSigningTimeAttr.Name = 0;
oSigningTimeAttr.Value = ТекущаяДатаСеанса();
oSigner.AuthenticatedAttributes2.Add(oSigningTimeAttr);
oSignedData = Новый COMОбъект("CAdESCOM.CadesSignedData");
// Объект CadesSignedData предоставляет свойства и методы для работы с усовершенствованной подписью.
oSignedData.ContentEncoding = CADESCOM_BASE64_TO_BINARY;
//oSignedData.propset_ContentEncoding = CADESCOM_BASE64_TO_BINARY;
ТелоЗапроса = ПолучитьДвоичныеДанныеИзСтроки(XMLКарточкаТовара);
ТелоЗапроса = Base64Строка(ТелоЗапроса);
oSignedData.Content = СокрЛП(ТелоЗапроса);
EncodingType = 0;
sSignedMessage = oSignedData.SignCades(oSigner, CADESCOM_CADES_TYPE,
Истина, EncodingType);
// Метод добавляет к сообщению усовершенствованную подпись.
sSignedMessage = СтрЗаменить(sSignedMessage,Символы.ПС,"");
sSignedMessage = СтрЗаменить(sSignedMessage,Символы.ВК,"");
//3. Отправить XML и открепленную подпись
URLЗапроса = "/v3/feed-product-sign-pkcs?apikey=" + КлючВхода;
ЗаписьJSON = Новый ЗаписьJSON();
ЗаписьJSON.УстановитьСтроку(Новый ПараметрыЗаписиJSON(, Символы.Таб));
ЗаписьJSON.ЗаписатьНачалоМассива();
ЗаписьJSON.ЗаписатьНачалоОбъекта();
ЗаписьJSON.ЗаписатьИмяСвойства("goodId");
ЗаписьJSON.ЗаписатьЗначение(СтрЗаменить(ВыборкаДетальныеЗаписи.GoodID, Символы.НПП, ""));
ЗаписьJSON.ЗаписатьИмяСвойства("base64Xml"); // XML
ЗаписьJSON.ЗаписатьЗначение(ТелоЗапроса);
ЗаписьJSON.ЗаписатьИмяСвойства("signature"); // Подпись
ЗаписьJSON.ЗаписатьЗначение(sSignedMessage);
ЗаписьJSON.ЗаписатьКонецОбъекта();
ЗаписьJSON.ЗаписатьКонецМассива();
ТелоЗапросаJSON = ЗаписьJSON.Закрыть();
HTTPЗапрос = Новый HTTPЗапрос(URLЗапроса, ЗаголовокHTTP);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
HTTPОтвет = Неопределено;
ТекстОшибки = "";
ИнтернетПрокси = ПолучениеФайловИзИнтернета.ПолучитьПрокси("HTTPS");
ЗащищенноеСоединение = ИнтеграцияИСПовтИсп.ЗащищенноеСоединение();
Соединение = Новый HTTPСоединение(
ПараметрыОтправкиHTTPЗапросов.Сервер,
ПараметрыОтправкиHTTPЗапросов.Порт,,,
ИнтернетПрокси,
ПараметрыОтправкиHTTPЗапросов.Таймаут,
ЗащищенноеСоединение);
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос); // отправка
ПрочитатьОтвет = HTTPОтвет.ПолучитьТелоКакСтроку(); // ответ
// 4. Проверка ответа
Попытка
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ПрочитатьОтвет);
СтруктураОтвета = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
СсылкаНаОбъект = ВыборкаДетальныеЗаписи.Ссылка;
ОбъектСправочника = СсылкаНаОбъект.ПолучитьОбъект();
Сообшение = СтруктураОтвета.result.errors[0].message;
ОбъектСправочника.ИнфОбЗапросе = Сообшение;
ОбъектСправочника.Записать();
Исключение
ОбъектСправочника.ИнфОбЗапросе = Сообшение;
ОбъектСправочника.Опубликованна = Истина;
ОбъектСправочника.Статус = "одобренные модератором, товары подписаны";
ОбъектСправочника.Записать();
КонецПопытки;
i=i+1;
КонецЦикла;

КонецПроцедуры