Маркировка «Честный знак» — интеграция с 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, ФН:
- Извлекает из кода маркировки крипто-хвост (AI 92)
- Проверяет электронную подпись ключами, хранящимися в ФН
- Возвращает результат:
fmCheck(тег 2004) = проверено/не проверено,fmCheckResult= положительный/отрицательный
Работает всегда, без интернета. Это аппаратная функция ФН. Подтверждает, что код маркировки криптографически валиден (не подделан). Но НЕ проверяет: продан ли уже товар, принадлежит ли он этой точке, не заблокирована ли серия.
Уровень 2: Онлайн-проверка в ОИСМ (требует интернет)
ФЯ отправляет запрос на сервер ОИСМ (Честный знак, labelsAddr:labelsPort из конфига ФЯ, по умолчанию myoism.ru:21701). ОИСМ проверяет:
- Находится ли товар в обороте
- Не был ли уже продан
- Принадлежит ли текущему владельцу
- Не заблокирована ли серия Роспотребнадзором
- Совпадает ли ожидаемый статус (
expectedStatus) с фактическим
Ответ ОИСМ (тег 2109, st2109):
| Значение | Смысл |
|---|---|
1 | Планируемый статус корректен — товар можно продать |
2 | Планируемый статус некорректен — предупредить кассира |
3 | Оборот товара приостановлен — продажа запрещена |
Таймаут: настраивается в ФЯ через testTout (по умолчанию 3000 мс). Если ОИСМ не ответил за это время — см. уровень 3.
Уровень 3: Локальный модуль ЧЗ (офлайн-замена уровня 2)
ЛМ ЧЗ (Локальный Модуль Честного Знака) — бесплатное ПО от CRPT. Это НЕ часть ФН и НЕ часть ФЯ. Это отдельная программа, которая:
- Периодически скачивает из интернета БД запрещённых серий (~30 МБ)
- При проверке КМ сверяет код с локальной копией запрещённого списка
- Активируется автоматически, если ОИСМ не ответил в отведённое время
Что ЛМ ЧЗ умеет: проверить, что товар не в запрещённом списке (серии, отозванные Роспотребнадзором / Минздравом).
Что ЛМ ЧЗ НЕ умеет: проверить подлинность, проверить был ли товар продан ранее, проверить владельца, проверить срок годности. Это доступно только через ОИСМ (онлайн).
Офлайн-режим: что происходит без интернета
Это ключевой вопрос для общепита с 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). После восстановления интернета ЛМ автоматически обновляет БД и торговля возобновляется.
Уведомления о реализации (офлайн)
Когда ФЯ работает в автономном режиме:
- При каждой продаже маркированного товара ФН формирует уведомление о реализации (новый тип фискального документа в FFD 1.2)
- Уведомления хранятся в буфере ФН (
labelBufUsedвcashboxstatus.json— % заполнения) - При восстановлении интернета ФЯ автоматически отправляет уведомления через ОФД → ОИСМ
- Если буфер переполнен (
notSendedNotificationsрастёт) — ФН блокирует дальнейшие операции с маркировкой
offlineNotifications.bin / .zip — эндпоинт API012 для ручной выгрузки уведомлений ОИСМ, если автоматическая отправка невозможна. Файл можно загрузить в личный кабинет Честного Знака вручную.
Структура кода маркировки (DataMatrix)
[FNC1] 01 <GTIN 14 цифр> 21 <Серийный номер> [GS] 91 <Ключ 4 сим.> [GS] 92 <Крипто-хвост 44/88 сим.>
| Компонент | AI | Длина | Назначение |
|---|---|---|---|
| GTIN | 01 | 14 цифр | Глобальный код товара |
| Серийный номер | 21 | 6-20 символов | Уникальный ID единицы |
| Ключ проверки | 91 | 4 символа | Алгоритмический ключ |
| Крипто-хвост | 92 | 44 или 88 символов | Зашифрованные данные подлинности |
Разделители:
FNC1(ASCII 232) — в начале, обозначает GS1 стандарт. Сканер может передавать как]d2префиксGS(ASCII 29,\x1d) — разделяет поля переменной длины
Пример реального кода:
010460406000600021N4N57RSCBUZTQ\x1d91808B\x1d92CuE2b4wBhPv9XeoBQDEux9wOKeNR4vf4I+q/QbhqzhRGyYQymkkpgtAZUtPHlfp0THGVN6i+D8ZxZQcbTnvEMg==
Разбор:
01+04604060006000= GTIN21+N4N57RSCBUZTQ= серийный номер91+808B= ключ проверки92+CuE2b4wBhPv9Xe...= крипто-хвост (88 символов →imcType = imcFmVerifyCode88→ ФН проверяет)
Классификация по длине крипто-хвоста:
| imcType | Длина КП | ФН проверяет? |
|---|---|---|
imcFmVerifyCode88 | 88 сим. | ✅ да |
imcFmVerifyCode44 | 44 сим. | ✅ да |
imcVerifyCode44 | 44 сим. | ❌ нет |
imcVerifyCode4 | 4 сим. | ❌ нет |
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 | Значение |
|---|---|---|
| 0 | 0x01 | Марка не прошла проверку в ФН |
| 1 | 0x02 | Проверка в ФН — отрицательный результат |
| 2 | 0x04 | Нет связи с сервером ОИСМ |
| 3 | 0x08 | Проверка маркировки — отрицательный результат |
| 4 | 0x10 | Некорректный статус товара |
При первом вызове
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):
| Бит | 0 | 1 |
|---|---|---|
| Бит 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:
| Поле | operations | labledOperations |
|---|---|---|
| Назначение | Немаркированные товары | Маркированные товары |
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.05 | FFD 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. Но архитектура должна быть готова.
- 2D-сканер — K10 имеет встроенный сканер. React Native модуль для чтения DataMatrix
- Парсер КМ — извлечение GTIN, серийного номера, КП из сканированной строки
labelcheckклиент — вызовstartlabelschecksession.json+labelcheck.jsonдля каждого маркированного товара- UI результата проверки — показать кассиру
[M+]/[M]/[M-]после сканирования, блокировать продажу приst2109=3 - Разделение в чеке — маркированные товары →
labledOperations[], остальные →operations[] - Мониторинг —
labelBufUsedиnotSendedNotificationsв health-статусе ККТ - Каталог — флаг
is_markedу Product в Catalog Service, определяющий нужна ли проверка при продаже
Связанное
- Fiscal Core Integration — контракты API012
- Sale Flow — оркестрация продажи
- Репозитории —
erp-pos