Как правильно отправить чек розничной продажи алкоголя в ЕГАИС (УТМ) из 1С?

Программист 1С v8.3 (Обычные формы) Торговля и дистрибуция
← На главную

При автоматизации розничной торговли алкоголем разработчики часто сталкиваются с необходимостью отправки данных о пробитых чеках в Универсальный Транспортный Модуль (УТМ) ЕГАИС. На первый взгляд задача кажется простой, если по аналогии брать отправку других документов (например, ТТН или актов). Однако отправка чеков имеет свои технические особенности. Давайте разберем по шагам весь процесс, проанализируем частые ошибки, которые возвращает УТМ, и выясним причины их возникновения.

Формирование структуры XML-файла чека

Для начала посмотрим на формат XML для чека с крепким алкоголем. В корневом элементе Cheque передаются данные организации и параметры кассовой смены, а в подчиненных элементах Bottle — проданные позиции с их акцизными марками.

Важно отметить: если вы продаете немаркированный алкоголь (пиво, пуаре, сидр, медовуху), структура файла чека будет немного другой. Вместо элемента Bottle используется тег nopdf. Набор атрибутов в нем также отличается: не нужно передавать марку (штрихкод), но обязательно указываются ИНН и КПП производителя, наименование товара, крепость и количество проданных единиц.

Ошибка парсинга: Cannot find the declaration of element 'Cheque'

Часто при первой отправке сформированного файла УТМ возвращает ошибку в ответном XML: org.xml.sax.SAXParseException; cvc-elt.1.a: Cannot find the declaration of element 'Cheque'. Ошибка говорит о том, что XML-парсер сервера УТМ не может прочитать корневой тег документа.

Выясним причину этой проблемы. Обычно она кроется не в самой структуре тегов, а в наличии скрытого маркера последовательности байтов — BOM (Byte Order Mark). Платформа 1С при формировании и сохранении текста в кодировке UTF-8 может добавлять эти невидимые символы в самое начало строки. Сервер ЕГАИС их не понимает и считает, что документ начинается с недопустимых символов. Чтобы избежать этого, при установке тела HTTP-запроса необходимо явно указывать отказ от использования BOM:


HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаСтрока, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

Также проблему с BOM можно обойти, используя запись через временные файлы и чтение двоичных данных:


ИмяФайла = ПолучитьИмяВременногоФайла("xml");
ЗаписьВременногоФайла = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать, Истина, Символы.ПС);
ЗаписьВременногоФайла.Записать(ТелоЗапросаСтрока);
ЗаписьВременногоФайла.Закрыть();
ДвоичныеДанныеВременногоФайла = Новый ДвоичныеДанные(ИмяФайла);
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеВременногоФайла);

Формирование POST-запроса и ошибка Missing initial multi part boundary

Для отправки чека необходимо сформировать HTTP POST-запрос в формате multipart/form-data. Если вы формируете тело запроса вручную с помощью конкатенации строк, можно получить ошибку Missing initial multi part boundary. Она возникает, когда веб-сервер Jetty (на котором работает УТМ) не может распознать границы (boundary) переданных файлов.

Рассмотрим подробнее механизм переноса строк в запросах. В коде 1С программисты часто используют константу Символы.ПС (перевод строки). Однако стандарт протокола HTTP для передачи multipart/form-data строго требует использования символов возврата каретки и перевода строки — CRLF. В языке 1С это комбинация Символы.ВК + Символы.ПС. При формировании тела обязательно используйте правильные спецсимволы переноса строк.

Куда отправлять чек? Правильный URL УТМ

Еще одна классическая ошибка — попытка отправить чек по аналогии с приходными накладными. УТМ разделяет потоки обрабатываемых документов:

  1. Оптовый и складской контур (ТТН, акты расхождений, акты постановки на баланс, запросы остатков и ответы на них) маршрутизируется в папку /opt/in/...
  2. Кассовые чеки розничной продажи алкоголя идут в обход этой логики и имеют свой выделенный адрес.

Если вы отправите чек на адрес http://[IP-адрес]:8080/opt/in/Cheque, документы могут зависнуть или УТМ вернет статус NotFound (404). Правильный относительный путь для отправки розничных чеков — это /xml (полный URL выглядит как http://[IP-адрес]:8080/xml).

Проанализируем особенности обработки ответа на отправленный чек. В отличие от отправки обычной ТТН (когда УТМ возвращает просто ID пакета — url), при успешной отправке чека по адресу /xml УТМ подписывает его транспортным ключом RSA и сразу же возвращает ответный XML-документ. Этот документ содержит электронную подпись (в теге sign) и сформированную ссылку на проверку чека на сайте ФСРАР (в теге url). Ваша программа обязана получить эти данные из ответа и отправить их на фискальный регистратор, чтобы подпись и QR-код распечатались на бумажном слип-чеке.

Практический пример кода выгрузки чека

Соберем воедино все озвученные правила. Рассмотрим готовый пример формирования и отправки чека для платформы 1С:Предприятие 8:


АдресУТМ = "127.0.0.1";
ПортСервера = 8080;
АдресРесурса = "xml"; // Отправляем именно на /xml !

ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписьXML.ЗаписатьНачалоЭлемента("Cheque");

// Заполняем реквизиты организации и кассы
ЗаписьXML.ЗаписатьАтрибут("inn", "ВашИНН");
ЗаписьXML.ЗаписатьАтрибут("kpp", "ВашКПП");
ЗаписьXML.ЗаписатьАтрибут("address", "АдресТочки");
ЗаписьXML.ЗаписатьАтрибут("name", "НазваниеОрганизации");
ЗаписьXML.ЗаписатьАтрибут("kassa", "ЗаводскойНомерККТ");
ЗаписьXML.ЗаписатьАтрибут("shift", "1"); // Номер смены
ЗаписьXML.ЗаписатьАтрибут("number", "1234"); // Номер чека
ЗаписьXML.ЗаписатьАтрибут("datetime", Формат(ТекущаяДата(),"ДФ=ddMMyyHHmm"));

Для Каждого Стр Из ТаблицаКрепкийАлкоголь Цикл
    ЗаписьXML.ЗаписатьНачалоЭлемента("Bottle");
    ЗаписьXML.ЗаписатьАтрибут("barcode", Стр.АкцизнаяМарка);
    ЗаписьXML.ЗаписатьАтрибут("ean"    , Стр.Штрихкод);
    ЗаписьXML.ЗаписатьАтрибут("price"  , Формат(Стр.Цена,"ЧЦ=15; ЧДЦ=2; ЧРД=.; ЧГ=0"));
    ЗаписьXML.ЗаписатьАтрибут("volume" , XMLСтрока(Стр.Объем));
    ЗаписьXML.ЗаписатьКонецЭлемента();
КонецЦикла;

ЗаписьXML.ЗаписатьКонецЭлемента();
ТекстXML = ЗаписьXML.Закрыть();    

// Формируем boundary и используем правильный перенос строк CRLF
ВремГраница = СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
CRLF = Символы.ВК + Символы.ПС;

ТелоЗапросаСтрока = "--" + ВремГраница + CRLF +
"Content-Disposition: form-data; name=""xml_file""; filename=""Cheque.xml""" + CRLF +
"Content-Type: text/xml; charset=utf-8" + CRLF + CRLF +
ТекстXML + CRLF +
"--" + ВремГраница + "--";

HTTPЗапрос = Новый HTTPЗапрос(АдресРесурса);
HTTPЗапрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + ВремГраница);

// Обязательно отключаем использование BOM
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаСтрока, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

Соединение = Новый HTTPСоединение(АдресУТМ, ПортСервера);
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос);

Если HTTPОтвет.КодСостояния = 200 Тогда
    ОтветМодуляЕГАИС = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
    // На этом этапе следует распарсить ответМодуляЕГАИС (XML) 
    // для получения <sign> и <url> и передачи их на ККТ
Иначе
    Сообщить("УТМ вернул ошибку, код состояния: " + HTTPОтвет.КодСостояния);
КонецЕсли;

Применяя на практике эти правила — отключение скрытого маркера BOM, использование корректных переносов строк и точную маршрутизацию на адрес /xml, вы сможете легко устранить сбои связи с УТМ и настроить стабильную передачу розничных чеков ЕГАИС из своей программы.

← На главную