sequenceDiagram
autonumber
actor User as Пользователь (Экран сканирования)
participant APP as Мобильное приложение (Flutter)
participant API as Бэкенд-шлюз (product-registry-service)
participant OFD as Внешний хаб (ФНС / ОФД API)
participant DB as Реляционная СУБД (PostgreSQL)
participant KAFKA as Брокер событий (Kafka)
User->>APP: Сканирует QR-код чека на кассе или рынке
APP->>API: HTTP POST /api/v1/shop/scan-receipt (JSON с qr_raw_string)
activate API
note over API: Шаг 3: Декодирование JWT и извлечение метаданных сессии
note over API: Шаг 4: Регулярный парсинг параметров фискальной строки
API->>OFD: GET /v1/receipts (Запрос состава чека по fn, fd, fp)
activate OFD
note over OFD: Извлечение массива номенклатурных позиций (наименования, цены, объемы)
OFD-->>API: HTTP 200 OK (Сырой массив товаров супермаркета)
deactivate OFD
Note over API, DB: Старт атомарной SQL-транзакции
API->>DB: INSERT INTO shop_receipts (ofd_id, home_group_id, raw_json) VALUES (...)
activate DB
DB-->>API: Фиксация чека, возврат internal_receipt_id
deactivate DB
Note over API, DB: Коммит атомарной транзакции (COMMIT)
API->>KAFKA: Отправка события "shop.receipt.downloaded" для ИИ-маппинга БЖУ
API-->>APP: HTTP 202 Accepted (Чек принят, запущен процесс умного зачисления)
deactivate API
note over APP: Экран переходит в режим анимации загрузки продуктов в Холодильник
Метод POST /api/v1/purchase/scan-receipt
Документация API: Синхронный прием фискального QR-кода чека, парсинг ОФД/ФНС и автоматическое распределение позиций в очереди инвентаря
1 Функциональное назначение
Метод является главным оркестратором автоматизации закупок. Он активируется при сканировании камерой мобильного приложения Flutter QR-кода с бумажного или электронного чека супермаркета, продуктового рынка или ОФД-платформы ритейлера.
Метод решает три критические задачи:
- Валидация и деконструкция фискальной строки: Шлюз верхнего уровня проверяет сигнатуру QR-кода на соответствие государственным стандартам ФНС/ОФД (параметры
t,s,fn,fd,fp). - Интеграция с внешним контуром агрегаторов чеков: Сервис инициирует синхронный запрос к API ФНС или кэширующему хабу ОФД для получения полной спецификации чека (массив SKU, цены, налоги, скидки).
- Передача в асинхронный контур ИИ-маппинга: Полученный сырой массив номенклатуры упаковывается в шину данных для последующего сопоставления текстовых названий магазинов с элементами Периодической таблицы продуктов.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/shop/scan-receipt - Формат данных:
application/json
2.1 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу строго типизированного JSON-пакета | application/json |
Authorization |
Да | Токен авторизации (Access Token). Выделяет home_group_id для зачисления ресурсов. |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для связывания логов сканирования с подсистемой ОФД | req-qr-receipt-55aa |
2.2 Спецификация тела запроса (Request Body)
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
qr_raw_string |
String | Да | Сырая текстовая строка, считанная сканером с QR-кода чека | "t=20260702T1532&s=1240.50&fn=9999...&fp=12345" |
2.2.1 Пример сырого JSON-запроса (Payload):
{
"qr_raw_string": "t=20260702T1532&s=1240.50&fn=9999440300012345&fd=123456&fp=987654321&n=1"
}3 Схема обработки запроса пользователя (Mermaid)
4 Расшифровка шагов работы метода
Шаг 1 (
User -> APP): Пользователь открывает камеру внутри модуляShopмобильного приложения и наводит её на фискальный QR-код напечатанного или электронного чека. Устройство считывает сырую строку параметров.Шаг 2 (
APP -> API): Приложение Flutter отправляет запросHTTP POST /api/v1/shop/scan-receiptна бэкенд-шлюз. В заголовкеAuthorizationпередаются данные сессии группы пользователя, а в теле — считанная строка.Шаг 3 (
API -> API): Бэкенд-шлюзFastAPIдекодирует JWT-токен, извлекаяhome_group_idиuser_id. Метод проверяет уровень доступа пользователя на право совершения транзакций слияния бюджетов группы.Шаг 4 (
API -> API): Регулярный парсинг параметров: Сервер валидирует структуруqr_raw_stringс помощью встроенного регулярного выражения, извлекая метку времениt, суммуs, номер фискального накопителяfn, номер фискального документаfdи фискальный признак документаfp. При нарушении сигнатуры операция прерывается.Шаг 5 (
API -> OFD): Шлюз инициирует защищенный HTTPS-запрос к внешнему API ОФД или кэширующему прокси-серверу ФНС для получения детальной спецификации чека.Шаг 6 (
OFD -> OFD): Сервер ОФД обращается к фискальному хранилищу, извлекая сырой массив номенклатурных позиций (названия продуктов, граммовки, цены за единицу, скидки).Шаг 7 (
OFD -> API): Агрегатор возвращает структурированный ответ с полным списком покупок. Передача данных происходит по протоколу TLS 1.3 с минимальными задержками (менее 150 мс при прогретом кэше ОФД).Шаг 8 (
API -> DB): Шлюз открывает атомарную транзакцию вPostgreSQLи фиксирует сырой лог чека в таблицуshop_receiptsдля истории финансовых трат группы:INSERT INTO shop_receipts (ofd_id, home_group_id, raw_json_payload, status) VALUES (\$1, \$2, \$3, 'DOWNLOADED') RETURNING id;Шаг 9 (
DB -> API): База данных успешно сохраняет сущность чека и возвращает внутренний идентификаторinternal_receipt_id. Транзакция закрывается командойCOMMIT.Шаг 10 (
API -> KAFKA): Шлюз асинхронно публикует событие в топик Kafkabupar.shop.events.receipt_downloaded, передавая тудаinternal_receipt_id. Это запускает фоновый ИИ-воркер, который начнет сопоставлять коммерческие названия товаров супермаркета (например,"Мол.Дом.в дер.3.2% 1л") с элементами Периодической таблицы продуктов.Шаг 11 (
API -> APP): Сервер возвращает мобильному приложению статусHTTP 202 Accepted. Интерфейс Flutter переходит в анимацию ожидания, сообщая пользователю: “Чек загружен. ИИ распределяет продукты в ваш Холодильник…”.
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 Успешный ответ (Success Response)
5.1.1 HTTP 202 Accepted (Ответ на Шаге 11)
Возвращается мобильному приложению сразу после успешного извлечения данных из ОФД и постановки чека в очередь ИИ-маппинга.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "RECEIPT_PROCESSING",
"internal_receipt_id": 88401,
"message": "Данные чека успешно импортированы из фискального контура. Запущен асинхронный процесс распределения продуктов по полкам.",
"timestamp": "2026-07-03T18:54:00Z"
}5.2 Спецификация JSON-Схемы фискального состава чека (Шаг 7)
Структура валидации входящего пакета данных от ОФД-шлюза для контроля целостности финансовых позиций.
{
"\$schema": "https://json-schema.org",
"title": "OfdReceiptPayload",
"type": "object",
"properties": {
"total_sum": {"type": "number"},
"retailer_name": {"type": "string"},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"quantity": {"type": "number"},
"sum": {"type": "number"}
},
"required": ["name", "price", "quantity", "sum"]
}
}
},
"required": ["total_sum", "retailer_name", "items"]
}5.3 Вилки исключений и обработка ошибок
5.3.1 Ошибка: Некорректный формат фискальной строки QR (HTTP 400 Bad Request — Шаг 4)
Выбрасывается на Шаге 4, если переданная строка со сканера камеры не содержит обязательных фискальных признаков государственного стандарта чеков.
{
"error_code": "ERR-INVALID-QR-SIGNATURE",
"message": "Ввод отклонен: строка QR-кода не соответствует паттерну фискальных данных ФНС/ОФД.",
"details": {
"received_string": "https://unknown-link.com",
"action": "Ensure you are scanning the official square fiscal QR code at the bottom of the receipt."
}
}5.3.2 Ошибка: Таймаут ответа сервера ОФД / ФНС (HTTP 504 Gateway Timeout — Шаг 5)
Выбрасывается бэкенд-шлюзом, если внешний контур агрегатора чеков не ответил в течение жесткого лимита времени (1000 мс) из-за перегрузки серверов налоговой.
{
"error_code": "ERR-FISCAL-HUB-TIMEOUT",
"message": "Внешний фискальный сервер не ответил вовремя. Повторите попытку сканирования позже.",
"details": {
"fn": "9999440300012345",
"action": "The request has been canceled to prevent UI blocking. Please try re-scanning in a few moments."
}
}