Маркировка «Честный знак» — интеграция с POS

Зачем этот документ

Fiscal Core Integration описывает контракты ФЯ. Этот документ описывает как система маркировки работает целиком: что проверяет ФН, что проверяет ОИСМ, что проверяет локальный модуль, как всё это ложится на наш POS, и — главное — что происходит без интернета.

Архитектура проверки маркировки

┌─────────────────────────────────────────────────────────────────┐
│  Unitodi K10-F                                                   │
│                                                                   │
│  ┌──────────────┐    ┌──────────────┐    ┌───────────────────┐  │
│  │  erp-pos      │    │  ФЯ (api012) │    │  ЛМ ЧЗ           │  │
│  │  (наша касса) │    │  :8088       │    │  (CRPT софт)      │  │
│  │               │    │              │    │  локальная БД      │  │
│  │  Сканирует    ├──►│  labelcheck  ├──►│  запрещённых       │  │
│  │  DataMatrix   │    │  .json       │    │  серий (~30 МБ)   │  │
│  └──────────────┘    │              │    └───────────────────┘  │
│                       │  ┌────────┐  │                           │
│                       │  │  ФН    │  │                           │
│                       │  │ 1.1М   │  │                           │
│                       │  │ крипто │  │                           │
│                       │  │ ключи  │  │                           │
│                       │  │ КП КМ  │  │                           │
│                       │  └────────┘  │                           │
│                       └──────┬───────┘                           │
└──────────────────────────────┼───────────────────────────────────┘
                               │ HTTPS
                               ▼
                    ┌──────────────────┐
                    │  ОИСМ            │
                    │  (Честный знак)   │
                    │  crpt.ru          │
                    │  :21701           │
                    └────────┬─────────┘
                             ▼
                    ┌──────────────────┐
                    │  ОФД             │
                    │  (передаёт       │
                    │  уведомления)    │
                    └──────────────────┘

Три уровня проверки

При сканировании маркированного товара проверка идёт на трёх уровнях одновременно:

Уровень 1: Криптографическая проверка в ФН (локальная, мгновенная)

ФН-1.1М содержит ключи проверки КП КМ (криптографические ключи, зашитые при производстве). Когда ФЯ получает rawLabel, ФН:

  1. Извлекает из кода маркировки крипто-хвост (AI 92)
  2. Проверяет электронную подпись ключами, хранящимися в ФН
  3. Возвращает результат: fmCheck (тег 2004) = проверено/не проверено, fmCheckResult = положительный/отрицательный

Работает всегда, без интернета. Это аппаратная функция ФН. Подтверждает, что код маркировки криптографически валиден (не подделан). Но НЕ проверяет: продан ли уже товар, принадлежит ли он этой точке, не заблокирована ли серия.

Уровень 2: Онлайн-проверка в ОИСМ (требует интернет)

ФЯ отправляет запрос на сервер ОИСМ (Честный знак, labelsAddr:labelsPort из конфига ФЯ, по умолчанию myoism.ru:21701). ОИСМ проверяет:

  • Находится ли товар в обороте
  • Не был ли уже продан
  • Принадлежит ли текущему владельцу
  • Не заблокирована ли серия Роспотребнадзором
  • Совпадает ли ожидаемый статус (expectedStatus) с фактическим

Ответ ОИСМ (тег 2109, st2109):

ЗначениеСмысл
1Планируемый статус корректен — товар можно продать
2Планируемый статус некорректен — предупредить кассира
3Оборот товара приостановлен — продажа запрещена

Таймаут: настраивается в ФЯ через testTout (по умолчанию 3000 мс). Если ОИСМ не ответил за это время — см. уровень 3.

Уровень 3: Локальный модуль ЧЗ (офлайн-замена уровня 2)

ЛМ ЧЗ (Локальный Модуль Честного Знака) — бесплатное ПО от CRPT. Это НЕ часть ФН и НЕ часть ФЯ. Это отдельная программа, которая:

  1. Периодически скачивает из интернета БД запрещённых серий (~30 МБ)
  2. При проверке КМ сверяет код с локальной копией запрещённого списка
  3. Активируется автоматически, если ОИСМ не ответил в отведённое время

Что ЛМ ЧЗ умеет: проверить, что товар не в запрещённом списке (серии, отозванные Роспотребнадзором / Минздравом).

Что ЛМ ЧЗ НЕ умеет: проверить подлинность, проверить был ли товар продан ранее, проверить владельца, проверить срок годности. Это доступно только через ОИСМ (онлайн).

Офлайн-режим: что происходит без интернета

Это ключевой вопрос для общепита с K10

K10 — портативный терминал. Wi-Fi может отваливаться (кухня, подсобка, выездная торговля). Нужно понимать, что происходит с маркировкой в офлайне.

Каскад деградации

flowchart TD
    SCAN["Кассир сканирует DataMatrix"] --> FN_CHECK["ФН: крипто-проверка (мгновенно)"]
    FN_CHECK --> OISM_REQ["ФЯ → ОИСМ: запрос статуса"]
    OISM_REQ -->|"ответ за < 3 сек"| ONLINE_OK["✅ Полная проверка (M+)"]
    OISM_REQ -->|"таймаут / нет сети"| LM_CHECK["ЛМ ЧЗ: проверка по локальной БД"]
    LM_CHECK -->|"код НЕ в запретном списке"| PARTIAL_OK["⚠️ Частичная проверка (M)"]
    LM_CHECK -->|"код В запретном списке"| BLOCKED["🚫 Продажа запрещена"]
    LM_CHECK -->|"ЛМ не установлен"| FN_ONLY["⚠️ Только крипто-проверка ФН"]

    style ONLINE_OK fill:#2d6a4f,color:#fff
    style PARTIAL_OK fill:#e9c46a,color:#000
    style BLOCKED fill:#e63946,color:#fff
    style FN_ONLY fill:#f4a261,color:#000

Три состояния на чеке

СимволТег 2106ЗначениеКогда
[M+]все биты = 1Полная проверка пройденаОИСМ ответил, ФН подтвердил, статус корректен
[M]бит 0,1 = 1, бит 2,3 = 0Только ФН-проверкаОИСМ не ответил (таймаут / нет сети)
[M-]бит 1 = 0 или бит 3 = 0Проверка проваленаФН отрицательный или ОИСМ запретил

Что конкретно работает без интернета

ПроверкаОнлайнОфлайн
Криптографическая подпись КМ (ФН)
Код не в запретном списке (ЛМ ЧЗ)✅ (если БД свежая)
Товар в обороте (ОИСМ)
Товар не продан ранее (ОИСМ)
Владелец корректен (ОИСМ)
Срок годности не истёк (ОИСМ)

Правило 72 часов

Если ЛМ ЧЗ не обновлял локальную БД запрещённых серий более 72 часов — торговля маркированными товарами блокируется. Это требование регулятора (CRPT). После восстановления интернета ЛМ автоматически обновляет БД и торговля возобновляется.

Уведомления о реализации (офлайн)

Когда ФЯ работает в автономном режиме:

  1. При каждой продаже маркированного товара ФН формирует уведомление о реализации (новый тип фискального документа в FFD 1.2)
  2. Уведомления хранятся в буфере ФН (labelBufUsed в cashboxstatus.json — % заполнения)
  3. При восстановлении интернета ФЯ автоматически отправляет уведомления через ОФД → ОИСМ
  4. Если буфер переполнен (notSendedNotifications растёт) — ФН блокирует дальнейшие операции с маркировкой

offlineNotifications.bin / .zip — эндпоинт API012 для ручной выгрузки уведомлений ОИСМ, если автоматическая отправка невозможна. Файл можно загрузить в личный кабинет Честного Знака вручную.

Структура кода маркировки (DataMatrix)

[FNC1] 01 <GTIN 14 цифр> 21 <Серийный номер> [GS] 91 <Ключ 4 сим.> [GS] 92 <Крипто-хвост 44/88 сим.>
КомпонентAIДлинаНазначение
GTIN0114 цифрГлобальный код товара
Серийный номер216-20 символовУникальный ID единицы
Ключ проверки914 символаАлгоритмический ключ
Крипто-хвост9244 или 88 символовЗашифрованные данные подлинности

Разделители:

  • FNC1 (ASCII 232) — в начале, обозначает GS1 стандарт. Сканер может передавать как ]d2 префикс
  • GS (ASCII 29, \x1d) — разделяет поля переменной длины

Пример реального кода:

010460406000600021N4N57RSCBUZTQ\x1d91808B\x1d92CuE2b4wBhPv9XeoBQDEux9wOKeNR4vf4I+q/QbhqzhRGyYQymkkpgtAZUtPHlfp0THGVN6i+D8ZxZQcbTnvEMg==

Разбор:

  • 01 + 04604060006000 = GTIN
  • 21 + N4N57RSCBUZTQ = серийный номер
  • 91 + 808B = ключ проверки
  • 92 + CuE2b4wBhPv9Xe... = крипто-хвост (88 символов → imcType = imcFmVerifyCode88 → ФН проверяет)

Классификация по длине крипто-хвоста:

imcTypeДлина КПФН проверяет?
imcFmVerifyCode8888 сим.✅ да
imcFmVerifyCode4444 сим.✅ да
imcVerifyCode4444 сим.❌ нет
imcVerifyCode44 сим.❌ нет
imcShortкороткий код❌ нет

Последовательность вызовов API012

Шаг 1: Очистить таблицу проверок в ФН

GET /api012/v1/startlabelschecksession.json

Очищает таблицу проверок маркировки в ФН. Макс. 128 маркированных позиций на чек. Таблица очищается автоматически после receipt.json, но вызывать перед каждым новым чеком — обязательно.

Шаг 2: Проверить каждый маркированный товар

POST /api012/v1/labelcheck.json?externalid=<uuid>

Request body:

{
  "document": {
    "checkFlags": 0,
    "excpectedStatus": 1,
    "quantity": "1.000",
    "fraction": {
      "nominator": 1,
      "denominator": 1
    }
  },
  "rawLabel": "010460406000600021N4N57RSCBUZTQ\u001d91808B\u001d92CuE2...",
  "unit": 0
}

checkFlags — битовая маска ответов кассира о результате проверки:

БитHexЗначение
00x01Марка не прошла проверку в ФН
10x02Проверка в ФН — отрицательный результат
20x04Нет связи с сервером ОИСМ
30x08Проверка маркировки — отрицательный результат
40x10Некорректный статус товара

При первом вызове checkFlags = 0

Это поле заполняется при повторном вызове, если кассир вручную подтверждает продажу несмотря на отрицательный результат. Для автоматического режима — всегда 0.

excpectedStatus (тег 2003, планируемый статус):

КодЗначение
1Штучный товар, реализация
2Мерный товар, реализация
3Возврат штучного товара
4Возврат мерного товара
5Штучный товар, этап реализации
6Мерный товар реализован
255Статус не изменится

Для общепита (продажа бутылки воды, молока) — 1 (штучный товар, реализация).

Response:

{
  "result": 0,
  "document": {
    "actualStatus": 1,
    "clFlags": 0,
    "rawLabel": "010460406000600021N4N57RSCBUZTQ...",
    "reqDt": "2021-11-30T07:26:44+03:00",
    "reqResult": 5,
    "reqCode2105": 0,
    "st2109": 1,
    "lct2100": 2,
    "productId": "04604060006000N4N57RSCBUZTQ",
    "quantity": "1.000",
    "unit": 0
  }
}

reqResult — битовая маска результата (тег 2106):

Бит01
Бит 1Проверка КМ — отрицательный результатПроверка КМ — положительный
Бит 3Статус товара некорректен (st2109=2 или 3)Статус корректен (st2109=1)
Биты 0, 2Заполняются единицами
Биты 4-7Заполняются нулями

reqCode2105 — код обработки запроса:

КодЗначение
0Формат запроса и код маркировки корректны
1Формат запроса некорректен
2Код маркировки не распознан

st2109 — ответ ОИСМ:

КодЗначениеДействие кассы
1Статус корректенПродолжаем
2Статус некорректенПредупредить кассира, дать выбор
3Оборот приостановленЗапретить продажу

Шаг 3: Включить результаты в чек

В receipt.json маркированные товары идут в labledOperations[] (а не в operations[]):

{
  "document": {
    "operations": [
      { "name": "Кофе", "price": "150.00", ... }
    ],
    "labledOperations": [
      {
        "name": "Молоко Parmalat 1л",
        "price": "89.00",
        "quantity": "1.000",
        "type": 33,
        "paymentType": 4,
        "vatRate": 1,
        "productCode": {
          "type": 1305,
          "data": "04604060006000N4N57RSCBUZTQ"
        },
        "checkLabelFlags": 15,
        "labelCheckResult": {
          "reqResult": 5,
          "reqCode2105": 0,
          "st2109": 1,
          "rawLabel": "010460406000600021N4N57RSCBUZTQ..."
        },
        "fraction": {
          "nominator": 1,
          "denominator": 1
        }
      }
    ]
  }
}

Различие operations vs labledOperations:

ПолеoperationslabledOperations
НазначениеНемаркированные товарыМаркированные товары
productCodeопционально (штрихкод)обязательно (КМ)
checkLabelFlagsнетобязательно (тег 2106)
labelCheckResultнетобязательно (из ответа labelcheck)
fractionнетда (для дробных количеств)
type (тег 1212)1 (товар)30-33 (маркированные товары)

Значения type для маркированных товаров:

КодЗначение
30Подакцизный товар с КМ
31Подакцизный товар без КМ (обязательная маркировка)
32Товар с КМ (не подакцизный)
33Товар без КМ (обязательная маркировка, не подакцизный)

Для общепита (молоко, вода) — обычно 32 (товар с КМ, не подакцизный).

Коды ошибок маркировки в API012

КодОписаниеДействие
51Слишком длинный код маркировкиПроверить сканер / формат
52Работа с маркировкой не поддерживаетсяФН или ФЯ не поддерживает маркировку
53Код маркировки не указанrawLabel пустой
54Неожиданный реквизитЛишнее поле в запросе
55Нет связи с сервером проверки маркировкиОИСМ недоступен (таймаут)
56Ошибка проверки КМ в ФНАппаратная ошибка ФН
57Ошибка проверки КМ на сервере ОИСМСервер вернул ошибку
58Только офлайн-режимФЯ работает без сети
59Ошибка создания файла уведомленийПроблема с офлайн-экспортом

Что актуально для общепита (наш скоуп)

Какие товары в общепите требуют маркировки

КатегорияОбязательная маркировкаРазрешительный режим
Бутилированная водас ноя 2021, поэкземплярно с мар 2025офлайн: мар 2025
Молочная продукцияс июн 2021, поэкземплярно с июн 2025офлайн: мар 2025
Пиво и слабоалк. напиткикеги с апр 2024, всё с ноя 2024офлайн: мар 2025
Безалкогольные напиткифев-июн 2025офлайн: июн 2025
Табакс 2019офлайн: мар 2025

Два сценария в общепите

Сценарий A: Продажа маркированного товара как есть Пример: покупатель берёт бутылку воды с витрины. → Кассир сканирует DataMatrix на бутылке → Полный цикл проверки (labelcheck → labledOperations в чеке)

Сценарий B: Использование маркированного товара в готовке Пример: молоко для латте, вода для готовки. → Товар выводится из оборота в личном кабинете Честного Знака (причина: «использование для собственных нужд») → Не сканируется на кассе → В чеке позиция «Латте» идёт как обычный товар в operations[], не в labledOperations[] → Срок подтверждения: 3 рабочих дня после вскрытия упаковки

Готовое блюдо НЕ маркируется

Если из маркированных ингредиентов приготовлено блюдо — блюдо само по себе маркировке НЕ подлежит. Маркировка распространяется на упакованный товар, а не на то, что из него приготовлено.

Пиво на розлив — исключение

Кеговое пиво, разливаемое в стаканы, передаёт в чеке только GTIN (без индивидуального серийного номера). Это освобождает от разрешительного режима проверки. Позиция идёт в обычный operations[] с productCode.type = 1302 (EAN-13).

FFD 1.2 vs FFD 1.05 — отличия для маркировки

АспектFFD 1.05FFD 1.2
Код товараТег 1162 (один blob)Тег 1163 (подтеги 1301-1325 по типам)
Проверка перед продажей❌ нет✅ полный цикл через ОИСМ
Типы фискальных документовСтандартные+ 4 новых: запрос о КМ, ответ, уведомление о реализации, квитанция
Тег 1212 (предмет расчёта)1-13+ 30-33 (маркированные)
Тег 2108 (единица измерения)НетЗаменяет тег 1197
Дробная продажаНетТег 1291 (числитель 1293, знаменатель 1294)
Требования к ФНФН-1.1 достаточноФН-1.1М обязателен

K10 и FFD

Проверить на физическом K10: какую версию FFD поддерживает установленный ФН. Если ФН-1.1 (без М) — маркировка через labelcheck.json работать не будет, нужна замена ФН на 1.1М.

Что нужно реализовать в erp-pos (Phase 2+)

Для MVP общепита маркировка — out of scope

В MVP кофейни/фудкорта основные позиции — готовые блюда (не маркируются). Маркированные товары (бутилированная вода, молоко на вынос) — Phase 2. Но архитектура должна быть готова.

  1. 2D-сканер — K10 имеет встроенный сканер. React Native модуль для чтения DataMatrix
  2. Парсер КМ — извлечение GTIN, серийного номера, КП из сканированной строки
  3. labelcheck клиент — вызов startlabelschecksession.json + labelcheck.json для каждого маркированного товара
  4. UI результата проверки — показать кассиру [M+] / [M] / [M-] после сканирования, блокировать продажу при st2109=3
  5. Разделение в чеке — маркированные товары → labledOperations[], остальные → operations[]
  6. МониторингlabelBufUsed и notSendedNotifications в health-статусе ККТ
  7. Каталог — флаг is_marked у Product в Catalog Service, определяющий нужна ли проверка при продаже

Связанное