Как настроить работу с Web-сервером АТОЛ ДТО 10 из 1С 7.7: отправка запросов, обработка JSON и отслеживание ошибок

Программист 1С v7.7 Торговля и дистрибуция
← На главную

При переходе на новые форматы фискальных данных и современные драйверы торгового оборудования (ДТО 10 от АТОЛ) разработчики часто сталкиваются с необходимостью интеграции старых платформ, таких как 1С 7.7, с веб-службами ККТ. В этой статье мы подробно разберем, как правильно организовать архитектуру запросов, какой COM-объект лучше использовать для стабильной связи, как обрабатывать JSON-ответы без лишней HTML-разметки и как решить проблему, когда сервер возвращает успешный код, но ККТ физически не печатает чек. Для реализации такого взаимодействия можно использовать готовое решение для работы 1С 7.7 с ККТ АТОЛ ДТО 10 через JSON-задания.

Синхронность или асинхронность: как устроен Web-сервер АТОЛ

Встроенный Web-сервер АТОЛ (а в современных релизах — служба Web Requests) по умолчанию спроектирован как асинхронный сервис. Это критически важное архитектурное отличие от прямой работы через традиционный драйвер.

Когда вы отправляете POST-запрос с заданием (например, на открытие смены, закрытие смены или пробитие чека), веб-сервер выполняет лишь базовую валидацию структуры JSON-пакета. Если структура корректна, он моментально записывает задачу во внутреннюю очередь (базу данных SQLite) и возвращает HTTP-статус 201 Created вместе с уникальным идентификатором задания — uuid.

Важно понимать: получение статуса 201 означает лишь то, что задача успешно принята в очередь на сервере. Это не гарантирует, что принтер чеков в этот момент вообще включен или в нем есть бумага. Для получения реального результата работы ККТ необходимо реализовать двухэтапный алгоритм:

  1. Отправка POST-запроса: передаем JSON с заданием, получаем статус 201 и извлекаем из тела ответа сгенерированный uuid.
  2. Опрос статуса задания (Polling): запускаем цикл, который с небольшим интервалом (например, 0.5 – 1 секунда) отправляет GET-запрос вида GET /api/v2/requests/<uuid>, пока статус задания во внутренней базе АТОЛ не изменится.

Задание в очереди может принимать следующие статусы, которые возвращаются в JSON-ответе:

Выбор COM-объекта: почему WinHttp лучше XMLHTTP

Для работы с HTTP-запросами в среде 1С 7.7 разработчики часто выбирают между объектами Microsoft.XMLHTTP (или его версиями MSXML2.XMLHTTP) и WinHttp.WinHttpRequest.5.1. В качестве альтернативы штатным COM-объектам можно использовать внешнюю компоненту HTTP-клиента для 1С 7.7.

Рекомендуется использовать именно WinHttp.WinHttpRequest.5.1 по следующим причинам:

Настройка HTTP-заголовков для получения чистого JSON без HTML

Частой проблемой при первых запросах к веб-серверу АТОЛ является получение HTML-кода вместо структурированного JSON. Обычно это происходит по двум причинам: сервер вернул ошибку веб-сервера (например, 404 или 500) или в запросе не были явно указаны ожидаемые форматы данных.

Для того чтобы сервер возвращал только JSON, необходимо при инициализации запроса передавать правильные заголовки (Headers). Разберем по шагам, как настроить объект WinHttp в 1С 7.7:


// Создаем объект для работы с HTTP
WinHttp = СоздатьОбъект("WinHttp.WinHttpRequest.5.1");

// Инициализируем POST-запрос (например, на локальный сервер АТОЛ)
АдресСервера = "http://localhost:16732/api/v2/requests";
WinHttp.Open("POST", АдресСервера, 0); // 0 - синхронный режим ожидания отправки

// Указываем заголовки, сообщающие серверу, что мы передаем и хотим получить JSON
WinHttp.SetRequestHeader("Content-Type", "application/json; charset=utf-8");
WinHttp.SetRequestHeader("Accept", "application/json");

Использование свойства WinHttp.Status позволяет сразу получить числовой HTTP-код ответа (например, 201, 400, 409), минуя парсинг текста. Само тело JSON-ответа будет находиться в свойстве WinHttp.ResponseText.

Реализация парсера JSON на платформе 1С 7.7

В платформе 1С 7.7 отсутствуют встроенные средства для работы с форматом JSON (такие как ЧтениеJSON или ЗаписьJSON в 1С 8). Писать полноценный парсер через внешние библиотеки или регулярные выражения бывает сложно, поэтому рассмотрим эффективную рекурсивную процедуру разбора строки JSON в объект СписокЗначений.

Ниже приведен работающий алгоритм, который преобразует строку JSON в дерево списков значений, имитируя структуры и массивы:


//======================================================================
Процедура ЗаполнитьДанныеИзОтветаJSON(Результат, ТекстJSON, ТипДанных)
    ТекстJSON = СокрЛП(Сред(ТекстJSON, 2));    
    НомерЗначения = 0;
    
    Пока ТекстJSON <> "" Цикл
        ПервыйСимвол = Лев(ТекстJSON, 1);
        Если ПервыйСимвол = "{" Тогда
            Значение = СоздатьОбъект("СписокЗначений");
            ЗаполнитьДанныеИзОтветаJSON(Значение, ТекстJSON, "Структура");
            Если ТипДанных = "Структура" Тогда
                Результат.Установить("Значение" + ?(НомерЗначения = 0, "", НомерЗначения), Значение);
                НомерЗначения = НомерЗначения + 1;
            ИначеЕсли ТипДанных = "Массив" Тогда
                Результат.ДобавитьЗначение(Значение);
            КонецЕсли;
        ИначеЕсли ПервыйСимвол = "[" Тогда
            Значение = СоздатьОбъект("СписокЗначений");
            ЗаполнитьДанныеИзОтветаJSON(Значение, ТекстJSON, "Массив");
            
            Если ТипДанных = "Структура" Тогда
                Результат.Вставить("Значение" + ?(НомерЗначения = 0, "", НомерЗначения), Значение);
                НомерЗначения = НомерЗначения + 1;
            Иначе
                Результат.Добавить(Значение);
            КонецЕсли;
        ИначеЕсли (ПервыйСимвол = "}") И (ТипДанных = "Структура") Тогда
            ТекстJSON = СокрЛП(Сред(ТекстJSON, 2));
            Если Лев(ТекстJSON, 1) = "," Тогда
                ТекстJSON = СокрЛП(Сред(ТекстJSON, 2));
            КонецЕсли;
            Возврат;
        ИначеЕсли (ПервыйСимвол = "]") И (ТипДанных = "Массив") Тогда
            ТекстJSON = СокрЛП(Сред(ТекстJSON, 2));
            Если Лев(ТекстJSON, 1) = "," Тогда
                ТекстJSON = СокрЛП(Сред(ТекстJSON, 2));
            КонецЕсли;
            Возврат;
        Иначе
            Если ТипДанных = "Структура" Тогда
                Поз = Найти(ТекстJSON, ":");
                Если Поз = 0 Тогда
                    Прервать;
                КонецЕсли;
                
                ИмяЗначения = СокрЛП(Лев(ТекстJSON, Поз-1));
                ТекстJSON = СокрЛП(Сред(ТекстJSON, Поз+1));
                
                Если Лев(ТекстJSON, 1) = "{" Тогда
                    Значение = СоздатьОбъект("СписокЗначений");
                    ЗаполнитьДанныеИзОтветаJSON(Значение, ТекстJSON, "Структура");
                ИначеЕсли Лев(ТекстJSON, 1) = "[" Тогда
                    Значение = СоздатьОбъект("СписокЗначений");
                    ЗаполнитьДанныеИзОтветаJSON(Значение, ТекстJSON, "Массив");
                Иначе
                    Поз = 0;
                    Для Сч = 1 По СтрДлина(ТекстJSON) Цикл
                        Символ = Сред(ТекстJSON, Сч, 1);
                        Если (Символ = ",") ИЛИ (Символ = "]") ИЛИ (Символ = "}") Тогда
                            Поз = Сч;
                            Прервать;
                        КонецЕсли;
                    КонецЦикла;
                    
                    Если Поз = 0 Тогда
                        Значение = ТекстJSON;
                        ТекстJSON = "";
                    Иначе
                        Значение = Лев(ТекстJSON, Поз-1);
                        ТекстJSON = СокрЛП(Сред(ТекстJSON, Поз + ?(Сред(ТекстJSON, Поз, 1) = ",", 1, 0)));
                    КонецЕсли;
                    
                    Значение = СокрЛП(Значение);
                КонецЕсли;
                
                Результат.Установить(ИмяЗначения, Значение);
            ИначеЕсли ТипДанных = "Массив" Тогда
                Поз = 0;
                Для Сч = 1 По СтрДлина(ТекстJSON) Цикл
                    Символ = Сред(ТекстJSON, Сч, 1);
                    Если (Символ = ",") ИЛИ (Символ = "]") ИЛИ (Символ = "}") Тогда
                        Поз = Сч;
                        Прервать;
                    КонецЕсли;
                КонецЦикла;
                
                Если Поз = 0 Тогда
                    Значение = ТекстJSON;
                    ТекстJSON = "";
                Иначе
                    Значение = Лев(ТекстJSON, Поз-1);
                    ТекстJSON = СокрЛП(Сред(ТекстJSON, Поз + ?(Сред(ТекстJSON, Поз, 1) = ",", 1, 0)));
                КонецЕсли;
                
                Значение = СокрЛП(Значение);
                Результат.Добавить(Значение);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

//======================================================================
Функция ЗаполнитьСтруктуруИзОтветаJSON(Знач ТекстJSON)
    Результат = СоздатьОбъект("СписокЗначений");
    
    // Очищаем экранирование кавычек
    ТекстJSON = СтрЗаменить(ТекстJSON, "\""", """");
    ТекстJSON = СтрЗаменить(ТекстJSON, """", "");    
    Если Лев(ТекстJSON, 1) = "{" Тогда
        ЗаполнитьДанныеИзОтветаJSON(Результат, ТекстJSON, "Структура");
    ИначеЕсли Лев(ТекстJSON, 1) = "[" Тогда
        МассивДанных = СоздатьОбъект("СписокЗначений");
        ЗаполнитьДанныеИзОтветаJSON(МассивДанных, ТекстJSON, "Массив");
        Результат.Вставить("Значение", МассивДанных);
    КонецЕсли;
        
    Возврат Результат;
КонецФункции

Как отследить, почему задача добавлена (201), но ККТ молчит

Рассмотрим практическую ситуацию: мы отправили JSON-запрос открытия смены, получили статус 201, но касса не открыла смену и ничего не напечатала. Выясним причину.

Для этого мы должны использовать полученный при создании задачи uuid. Алгоритм действий выглядит следующим образом:

  1. Извлекаем uuid из ответа первого запроса. Например, из JSON-строки {"uuid": "9c8a1b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c", "status": "wait"} с помощью нашего парсера получаем значение ключа uuid.
  2. Отправляем GET-запрос по адресу: http://localhost:16732/api/v2/requests/9c8a1b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c.
  3. Анализируем возвращенный JSON. Если статус равен error, внутри структуры ответа будет подробный блок ошибок драйвера и ККТ. Например, мы сможем узнать, что ККТ заблокирована, закончилась чековая лента, открыта крышка принтера или смена превысила 24 часа.

Пример организации цикла ожидания выполнения в 1С 7.7:


// Инициализируем цикл опроса статуса
МаксПопыток = 10;
ПопыткаОбработки = 1;
УспешноВыполнено = 0;

Пока ПопыткаОбработки <= МаксПопыток Цикл
    // Формируем GET-запрос к статусу задачи
    АдресСтатуса = "http://localhost:16732/api/v2/requests/" + ВыделенныйUUID;
    WinHttp.Open("GET", АдресСтатуса, 0);
    WinHttp.SetRequestHeader("Accept", "application/json");
    WinHttp.Send();
    
    Если WinHttp.Status = 200 Тогда
        СтруктураОтвета = ЗаполнитьСтруктуруИзОтветаJSON(WinHttp.ResponseText);
        ТекущийСтатус = "";
        СтруктураОтвета.Получить("status", ТекущийСтатус);
        
        Если ТекущийСтатус = "ready" Тогда
            УспешноВыполнено = 1;
            Прервать;
        ИначеЕсли ТекущийСтатус = "error" Тогда
            // Извлекаем ошибку
            КодОшибки = "";
            ОписаниеОшибки = "";
            // Обработка вложенной структуры ошибок ККТ
            Предупреждение("Ошибка выполнения на ККТ! Описание: " + ОписаниеОшибки);
            Прервать;
        КонецЕсли;
    КонецЕсли;
    
    // Небольшая пауза перед повторным запросом
    // В 1С 7.7 паузу можно сделать через вызов пинга локального хоста или WScript.Shell
    СекундПаузы = 1;
    Shell = СоздатьОбъект("Wscript.Shell");
    Shell.Run("ping 127.0.0.1 -n " + СокрЛП(СекундПаузы + 1), 0, 1);
    
    ПопыткаОбработки = ПопыткаОбработки + 1;
КонецЦикла;

Если УспешноВыполнено = 1 Тогда
    Сообщить("Операция на ККТ успешно завершена.");
КонецЕсли;

Важные изменения в новых версиях драйверов АТОЛ (10.9 и старше)

Если вы планируете использовать современные релизы ДТО (например, 10.9.x или 10.10.x), вам необходимо учесть несколько важных изменений в архитектуре АТОЛ:

← На главную