Метод POST /api/v1/purchase/scan-receipt

Документация API: Синхронный прием фискального QR-кода чека, парсинг ОФД/ФНС и автоматическое распределение позиций в очереди инвентаря

Published

July 3, 2026

1 Функциональное назначение

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

Метод решает три критические задачи:

  1. Валидация и деконструкция фискальной строки: Шлюз верхнего уровня проверяет сигнатуру QR-кода на соответствие государственным стандартам ФНС/ОФД (параметры t, s, fn, fd, fp).
  2. Интеграция с внешним контуром агрегаторов чеков: Сервис инициирует синхронный запрос к API ФНС или кэширующему хабу ОФД для получения полной спецификации чека (массив SKU, цены, налоги, скидки).
  3. Передача в асинхронный контур ИИ-маппинга: Полученный сырой массив номенклатуры упаковывается в шину данных для последующего сопоставления текстовых названий магазинов с элементами Периодической таблицы продуктов.

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)

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: Экран переходит в режим анимации загрузки продуктов в Холодильник

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): Шлюз асинхронно публикует событие в топик Kafka bupar.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."
  }
}