Как сформировать QR-код чека на ККТ до пробития в 1С и реализовать отложенную фискализацию на транспорте?

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

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

В этой статье мы подробно разберем, почему технически невозможно получить оригинальный фискальный QR-код до момента пробития чека, как эту проблему решает действующее законодательство РФ (54-ФЗ) для сферы транспорта и услуг, и как правильно спроектировать архитектуру решения в среде 1С:Предприятие 8.

Почему технически невозможно сгенерировать фискальный QR-код заранее?

Для начала проанализируем, как устроен QR-код кассового чека. Согласно требованиям ФНС, QR-код на чеке должен содержать строго определенный набор реквизитов, закодированных в текстовую строку:

Главным техническим тупиком является именно фискальный признак документа (ФПД). ФПД — это уникальная цифровая подпись чека, которая генерируется аппаратным криптографическим процессором, находящимся внутри защищенного устройства — фискального накопителя (ФН). ФН высчитывает ФПД в режиме реального времени на основе текущего состояния внутренних счетчиков ККТ (которые инкрементируются с каждым пробитым чеком), точного времени и контрольной суммы данных самого чека.

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

Законные основания для «отложенной фискализации» (54-ФЗ)

Многие разработчики и бизнес-аналитики опасаются, что неотправка чека в ОФД в момент приема денег является грубым нарушением закона 54-ФЗ. Однако для пассажирского транспорта и некоторых видов выездной торговли законодательство делает официальное исключение.

Согласно пункту 5.9 статьи 1.2 Федерального закона № 54-ФЗ, при продаже проездных документов (билетов) и талонов для проезда в общественном транспорте в салоне транспортного средства, перевозчик имеет право не выдавать классический кассовый чек на бумаге в момент расчета. Вместо этого разрешено использовать схему отложенной фискализации:

  1. Кондуктор или водитель выдает пассажиру бумажный билет (или талон) установленного образца.
  2. На билете должны быть указаны реквизиты, достаточные для идентификации продажи и получения электронного чека (уникальный номер билета, серия, стоимость, дата).
  3. Сам фискальный чек на ККТ должен быть сформирован в день осуществления расчета (то есть до завершения рейса или закрытия смены), а не мгновенно в секунду оплаты.

Таким образом, схема «собираем продажи за смену в 1С — фискализируем пачкой вечером через облачную ККТ» полностью легальна и активно применяется крупнейшими транспортными операторами.

Архитектура решения: Сервисный QR-код против Фискального

Чтобы предоставить пассажиру возможность проверить чек, но при этом обойти аппаратное ограничение ФН, мы разделим понятия фискального QR-кода и сервисного QR-кода:

Рассмотрим по шагам, как работает этот сценарий:

  1. В момент продажи билета мобильный терминал кондуктора или предпечатная система генерирует уникальный идентификатор транзакции (UUID) и печатает его на билете в виде QR-кода, ведущего на сайт компании.
  2. Если пассажир сканирует этот код сразу в автобусе, веб-страница сообщает ему: «Билет №12345 успешно зарегистрирован в системе. Фискальный чек находится в процессе формирования. Пожалуйста, обновите страницу после окончания смены».
  3. В конце смены данные о проданных билетах из 1С отправляются в облачную ККТ.
  4. Касса пробивает чеки, получает от ФН фискальные признаки (ФПД) и отправляет их в ОФД.
  5. 1С получает от ККТ (или из ОФД) подтверждение фискализации и связывает UUID каждого билета с конкретными реквизитами чека (ФД, ФПД, дата, сумма).
  6. При повторном сканировании QR-кода на билете веб-страница видит, что чек фискализирован, и выполняет автоматическое перенаправление (редирект) на официальный сайт проверки чеков ОФД или выводит реквизиты чека прямо на экране.

Реализация решения в 1С:Предприятие 8

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

Шаг 1. Генерация уникального идентификатора билета и QR-кода

При оформлении продажи билета нам необходимо программно сгенерировать UUID и сформировать строку для сервисного QR-кода. В платформе 1С начиная с версии 8.3.10 доступен встроенный генератор двухмерных штрихкодов без использования сторонних библиотек и компонент.

Посмотрим на пример кода формирования билета и вывода сервисного QR-кода в табличный документ:


&НаСервере
Функция СформироватьБилетНаПечать(ДанныеПродажи)
    
    // Инициализируем табличный документ билета
    ТабДок = Новый ТабличныйДокумент;
    Макет = ПолучитьМакет("МакетБилета");
    ОбластьБилет = Макет.ПолучитьОбласть("Билет");
    
    // Генерируем уникальный идентификатор для отложенной фискализации
    НовыйGUID = Новый УникальныйИдентификатор();
    СтрокаGUID = Строка(НовыйGUID);
    
    // Заполняем параметры макета
    ОбластьБилет.Параметры.НомерБилета = ДанныеПродажи.Номер;
    ОбластьБилет.Параметры.Цена = ДанныеПродажи.Цена;
    ОбластьБилет.Параметры.Дата = ТекущаяДатаСеанса();
    
    // Формируем сервисную ссылку для проверки
    СервиснаяСсылка = "https://check.mytransport.ru/verify?ticket_id=" + СтрокаGUID;
    
    // Добавляем QR-код на табличный документ средствами платформы
    РисунокQR = ОбластьБилет.Рисунки.Добавить(ТипРисункаТабличногоДокумента.QRКод);
    РисунокQR.Верх = 10; // Позиционирование в мм
    РисунокQR.Лево = 50;
    РисунокQR.Ширина = 40;
    РисунокQR.Высота = 40;
    РисунокQR.Текст = СервиснаяСсылка;
    
    ТабДок.Вывести(ОбластьБилет);
    
    // Сохраняем информацию о билете в базу данных для последующей фискализации
    ЗаписатьДанныеПроданногоБилета(ДанныеПродажи.Номер, ДанныеПродажи.Цена, НовыйGUID);
    
    Возврат ТабДок;
    
КонецФункции

Шаг 2. Хранение билетов и подготовка к фискализации

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

Шаг 3. Обработка запросов от пассажиров (HTTP-сервис в 1С)

Для того чтобы сайт проверки мог оперативно получать информацию о статусе билета, мы можем развернуть простейший веб-сервер или использовать механизм HTTP-сервисов в 1С.

Разберем пример реализации метода HTTP-сервиса 1С, который принимает ticket_id и возвращает JSON с текущим состоянием билета:


Функция ПолучитьСтатусБилета(Запрос)
    
    ИдентификаторБилета = Запрос.ПараметрыЗапроса.Получить("ticket_id");
    
    Ответ = Новый HTTPСервисОтвет(200);
    Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
    
    // Ищем билет в информационной базе
    ЗапросБД = Новый Запрос;
    ЗапросБД.Текст = 
        "ВЫБРАТЬ
        |	ПроданныеБилеты.СтатусФискализации КАК Статус,
        |	ПроданныеБилеты.ФПД КАК ФПД,
        |	ПроданныеБилеты.НомерФД КАК НомерФД,
        |	ПроданныеБилеты.ДатаЧека КАК ДатаЧека
        |ИЗ
        |	РегистрСведений.ПроданныеБилеты КАК ПроданныеБилеты
        |ГДЕ
        |	ПроданныеБилеты.ИдентификаторБилета = &ИдентификаторБилета";
        
    ЗапросБД.УстановитьПараметр("ИдентификаторБилета", ИдентификаторБилета);
    Выборка = ЗапросБД.Выполнить().Выбрать();
    
    СтруктураОтвета = Новый Структура;
    
    Если Выборка.Следующий() Тогда
        Если Выборка.Статус = Перечисления.СтатусыФискализации.Фискализирован Тогда
            СтруктураОтвета.Вставить("status", "fiscalized");
            СтруктураОтвета.Вставить("fpd", Выборка.ФПД);
            СтруктураОтвета.Вставить("fd", Выборка.НомерФД);
            СтруктураОтвета.Вставить("date", Формат(Выборка.ДатаЧека, "ДФ=yyyy-MM-ddTHH:mm:ss"));
            // Ссылка на ОФД для быстрого перехода
            СтруктураОтвета.Вставить("ofd_url", "https://lk.platformaofd.ru/web/noauth/cheque?fp=" + Выборка.ФПД);
        Иначе
            СтруктураОтвета.Вставить("status", "processing");
            СтруктураОтвета.Вставить("message", "Билет зарегистрирован. Чек формируется облачной кассой до конца дня.");
        КонецЕсли;
    Иначе
        Ответ.КодСостояния = 404;
        СтруктураОтвета.Вставить("status", "error");
        СтруктураОтвета.Вставить("message", "Билет с указанным идентификатором не найден.");
    КонецЕсли;
    
    // Сериализация структуры в JSON
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, СтруктураОтвета);
    
    Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());
    Возврат Ответ;
    
КонецФункции

Шаг 4. Пакетная отправка чеков в облачную кассу в конце смены

Для минимизации издержек и отказа от покупки физических касс в каждый автобус целесообразно использовать облачную ККТ (например, АТОЛ Онлайн, Кит Онлайн, Ferma и др.). Интеграция 1С с облачным сервисом осуществляется по API.

Рассмотрим процедуру регламентного задания в 1С, которое раз в день выбирает все нефискализированные продажи за смену и пачкой отправляет их на фискализацию:


&НаСервере
Процедура ВыполнитьПакетнуюФискализацию()
    
    // Получаем список нефискализированных билетов
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ПроданныеБилеты.ИдентификаторБилета КАК Идентификатор,
        |	ПроданныеБилеты.Сумма КАК Сумма
        |ИЗ
        |	РегистрСведений.ПроданныеБилеты КАК ПроданныеБилеты
        |ГДЕ
        |	ПроданныеБилеты.СтатусФискализации = ЗНАЧЕНИЕ(Перечисление.СтатусыФискализации.НеФискализирован)";
        
    РезультатЗапроса = Запрос.Выполнить();
    Если РезультатЗапроса.Пустой() Тогда
        Возврат; // Нечего фискализировать
    КонецЕсли;
    
    Выборка = РезультатЗапроса.Выбрать();
    
    // Настройки интеграции с Облачной ККТ (условный пример для API кассы)
    АдресСервиса = "partner.atol.ru";
    РесурсAPI = "/v4/sell";
    ТокенАвторизации = ПолучитьТокенДоступаККассе();
    
    Пока Выборка.Следующий() Цикл
        
        // Формируем JSON-пакет чека
        СтруктураЧека = Новый Структура;
        СтруктураЧека.Вставить("external_id", Выборка.Идентификатор);
        
        // Описываем покупку
        ПозицияЧека = Новый Структура;
        ПозицияЧека.Вставить("name", "Проездной билет пассажирский");
        ПозицияЧека.Вставить("price", Выборка.Сумма);
        ПозицияЧека.Вставить("quantity", 1);
        ПозицияЧека.Вставить("sum", Выборка.Сумма);
        ПозицияЧека.Вставить("tax", "none"); // Без НДС
        
        МассивПозиций = Новый Массив;
        МассивПозиций.Добавить(ПозицияЧека);
        
        Receipt = Новый Структура;
        Receipt.Вставить("items", МассивПозиций);
        Receipt.Вставить("total", Выборка.Сумма);
        
        СтруктураЧека.Вставить("receipt", Receipt);
        
        // Сериализуем и отправляем POST-запрос
        СтрокаJSON = СформироватьСтрокуJSON(СтруктураЧека);
        
        HTTPСоединение = Новый HTTPСоединение(АдресСервиса, , , , , , Новый ЗащищенноеСоединениеOpenSSL());
        HTTPЗапрос = Новый HTTPЗапрос(РесурсAPI);
        HTTPЗапрос.Заголовки.Вставить("Token", ТокенАвторизации);
        HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json");
        HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаJSON);
        
        Попытка
            HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
            Если HTTPОтвет.КодСостояния = 200 ИЛИ HTTPОтвет.КодСостояния = 201 Тогда
                // Помечаем в базе, что билет отправлен в ККТ
                ОбновитьСтатусБилета(Выборка.Идентификатор, Перечисления.СтатусыФискализации.ОтправленВККТ);
            Иначе
                ЗаписатьЛогОшибки(Выборка.Идентификатор, HTTPОтвет.ПолучитьТелоКакСтроку());
            КонецЕсли;
        Исключение
            ЗаписатьЛогОшибки(Выборка.Идентификатор, ОписаниеОшибки());
        КонецПопытки;
        
    КонецЦикла;
    
КонецПроцедуры

Примечание: После отправки пакета касса обрабатывает его асинхронно. Нам потребуется еще одно регламентное задание, которое будет периодически запрашивать у облачного сервиса результаты фискализации по external_id (в качестве которого мы передали UUID билета), забирать значения ФД, ФПД и переводить статус билета в «Фискализирован».

Современная альтернатива — бескондукторная оплата по QR через СБП

Если транспортное предприятие стремится полностью исключить бумажные билеты и ручную работу контролеров/кондукторов, идеальным решением является внедрение оплаты через Систему быстрых платежей (СБП) с автоматической фискализацией:

  1. В салоне транспортного средства размещаются наклейки со статическими (кассовыми) QR-кодами СБП.
  2. Пассажир сканирует QR-код камерой телефона, переходит в банковское приложение и подтверждает фиксированную стоимость проезда.
  3. Банк мгновенно уведомляет платежного агрегатора об успешной транзакции.
  4. Платежный агрегатор по API передает событие в 1С или напрямую в облачную ККТ.
  5. Касса мгновенно регистрирует электронный чек в ФНС. Пассажир получает электронный билет в своем банковском приложении или на специальной посадочной странице СБП, которую может предъявить контролеру.

Современные конфигурации 1С (такие как 1С:Розница 3.0, 1С:Управление нашей фирмой (УНФ)) имеют штатные модули интеграции со СБП крупных банков, что позволяет настроить этот процесс с минимальными затратами на программирование.

Выводы

Полноценный фискальный QR-код невозможно получить до физического пробития чека из-за особенностей криптографической защиты фискального накопителя. Тем не менее, для транспорта и выездной торговли закон 54-ФЗ разрешает отложенную фискализацию в течение суток. Лучшим техническим решением этой задачи является использование сервисных QR-кодов на билетах, ведущих на промежуточный веб-портал, интеграция 1С с облачной ККТ по API и пакетное проведение чеков в конце смены.

← На главную