Как реализовать асинхронный HTTP POST запрос к эквайринговому терминалу в 1С

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

При интеграции 1С с банковским оборудованием (терминалами) разработчики часто сталкиваются со специфическими протоколами обмена. Одна из типичных задач — отправка HEX-строки, описывающей транзакцию, на IP-адрес терминала. Трудности возникают, когда документация к оборудованию требует «отправки данных частями» или асинхронного режима работы, чтобы не блокировать интерфейс пользователя. Разберем, почему стандартные попытки отправить данные «кусками» через COM-объекты приводят к ошибкам и как правильно организовать этот процесс в современных версиях 1С.

Анализ основной ошибки: почему несколько вызовов Send() не работают

Рассмотрим ситуацию, когда разработчик пытается использовать объект WinHttp.WinHttpRequest.5.1 в асинхронном режиме. В коде это выглядит так:


ОбъектHTTP = Новый COMОбъект("WinHttp.WinHttpRequest.5.1");
ОбъектHTTP.Open("POST", "http://192.168.6.66:666", 1); // 1 - признак асинхронности
ОбъектHTTP.Send("ЧастьДанных_1");
ОбъектHTTP.Send("ЧастьДанных_2"); // Здесь возникает ошибка

Причина ошибки заключается в архитектуре протокола HTTP и реализации объекта WinHttpRequest. Метод Send() инициирует полноценную транзакцию HTTP. Как только вызван первый Send(), объект переходит в состояние «отправка/ожидание ответа». Попытка вызвать Send() повторно до завершения текущего цикла «запрос-ответ» вызывает исключение, так как объект занят. HTTP-протокол не предполагает «дозаписи» данных в один запрос через несколько вызовов метода отправки в прикладном коде.

Если терминал требует отправки данных частями по 32 символа, это может означать одно из двух:

  1. Использование низкоуровневого протокола TCP/IP (сокетов): Терминал ждет не HTTP-запрос, а просто поток байтов в открытое соединение. В этом случае использование HTTP-объектов в принципе некорректно.
  2. Особенность буферизации: Терминал ожидает один HTTP POST запрос, но его внутренний буфер обрабатывает данные сегментами. В этом случае вы должны сформировать всю строку целиком в 1С и отправить её одним вызовом Send().

Решение 1: Использование фоновых заданий (рекомендуемый способ)

В современной платформе 1С (8.3) для реализации асинхронности не нужно использовать флаги COM-объектов. Лучшая практика — выполнение синхронного запроса внутри фонового задания. Это позволяет не «вешать» клиентский сеанс и корректно дождаться ответа от оборудования.

Разберем алгоритм по шагам:

  1. Сформируйте полную HEX-строку транзакции. Убедитесь, что в начало добавлены служебные символы (например, длина сообщения), если того требует протокол.
  2. Запустите фоновое задание, которое выполнит запрос.
  3. В основном сеансе используйте ПодключитьОбработчикОжидания для проверки состояния задания.

Пример кода для фонового задания:


// В серверном модуле
Функция ОтправитьДанныеНаТерминал(ПараметрыЗапроса) Экспорт
    Соединение = Новый HTTPСоединение(ПараметрыЗапроса.IP, ПараметрыЗапроса.Порт, , , , 30);
    Заголовки = Новый Соответствие();
    Заголовки.Вставить("Content-Type", "application/octet-stream");
    
    // Преобразуем HEX-строку в ДвоичныеДанные
    ДанныеДляОтправки = ПолучитьДвоичныеДанныеИзГексСтроки(ПараметрыЗапроса.ТелоЗапроса);
    
    ЗапросHTTP = Новый HTTPЗапрос("/", Заголовки);
    ЗапросHTTP.УстановитьТелоИзДвоичныхДанных(ДанныеДляОтправки);
    
    Попытка
        Ответ = Соединение.ОтправитьДляОбъекта(ЗапросHTTP, "POST");
        Возврат Ответ.ПолучитьТелоКакСтроку();
    Исключение
        Возврат "Ошибка: " + ОписаниеОшибки();
    КонецПопытки;
КонецФункции

Решение 2: Работа через COM-объект с проверкой состояния

Если по каким-то причинам необходимо использовать именно WinHttpRequest в асинхронном режиме, мы должны организовать цикл ожидания ответа в 1С. Поскольку 1С не умеет напрямую перехватывать события (Events) COM-объектов, мы будем опрашивать свойство ReadyState или Status.

Проанализируем пример реализации ожидания:


ОбъектHTTP = Новый COMОбъект("WinHttp.WinHttpRequest.5.1");
ОбъектHTTP.Open("POST", "http://192.168.6.66:666", 1);
ОбъектHTTP.Send(ПолнаяHEXСтрока);

// Вместо второго Send, запускаем ожидание
Таймаут = 30; // секунд
Начало = ТекущаяДата();
Пока ОбъектHTTP.ReadyState <> 4 И (ТекущаяДата() - Начало) < Таймаут Цикл
    // Важно: в управляемых формах здесь нельзя просто зациклить, 
    // лучше использовать ОбработчикОжидания.
    // Для простого примера в обработке:
    #Если Клиент Тогда
        ОбработкаПрерыванияПользователя();
    #КонецЕсли
КонецЦикла;

Если ОбъектHTTP.ReadyState = 4 Тогда
    Результат = ОбъектHTTP.ResponseText;
Иначе
    Сообщить("Превышено время ожидания терминала");
КонецЕсли;

Важные технические нюансы

1. Перевод HEX в Двоичные данные. Терминалы крайне чувствительны к кодировкам. Если вы отправите HEX как обычную строку, 1С может преобразовать её в UTF-8, что изменит байт-код. Проанализируйте документацию: если требуется «бинарный вид», обязательно используйте объект ДвоичныеДанные. Для конвертации HEX-строки (например, "040001...") в массив байтов используйте методы Сред в цикле с преобразованием через ЧислоИзШестнадцатеричнойСтроки().

2. Длина сообщения. Как упоминалось в обсуждении, первые 8 знаков часто описывают длину сообщения. Если вы формируете их вручную, убедитесь, что расчет длины совпадает с реальным объемом передаваемых данных после конвертации в байты.

3. Проблема «кусков» и TCP-сокетов. Если терминал действительно разрывает соединение при попытке отправить всё одним пакетом (что бывает крайне редко для HTTP), выясните, не используется ли там «Raw TCP». Для работы с «сырыми» сокетами в 1С лучше использовать внешние компоненты или обертки над .NET объектом System.Net.Sockets.TcpClient. HTTP-объекты (как встроенные, так и COM) всегда добавляют свои заголовки (Host, Content-Length, User-Agent), которые терминал может не понять.

4. Таймауты. При работе с эквайрингом стандартные 30 секунд могут быть недостаточными (особенно при ожидании ввода пин-кода пользователем). Рекомендуем устанавливать таймаут на уровне 60–120 секунд. В объекте WinHttpRequest это делается методом SetTimeouts, а в HTTPСоединение — параметром конструктора.

Подводя итог: откажитесь от попыток слать данные несколькими вызовами Send(). Соберите пакет целиком, оберните его в фоновое задание и используйте стандартные механизмы HTTPСоединение для надежной и современной интеграции.

← На главную