sequenceDiagram
autonumber
participant MAG as Шина данных (Magnum)
participant API as Бэкенд-шлюз (FastAPI)
participant DB as Реляционная СУБД (PostgreSQL)
participant KAFKA as Брокер событий (Kafka)
participant APP as Мобильное приложение (Flutter)
MAG->>API: HTTP POST /integration/magnum/webhook (JSON чека + API Key)
activate API
note over API: Шаг 2: Валидация X-Magnum-Token (B2B-авторизация)
API->>DB: SELECT user_id, home_group_id FROM loyalty_cards WHERE token = loyalty_card_token
activate DB
DB-->>API: Возвращает идентификаторы пользователя и семьи
deactivate DB
API->>DB: SELECT 1 FROM processed_b2b_receipts WHERE receipt_number = receipt_number
activate DB
DB-->>API: Пустой ответ (Чек уникален, ранее не обрабатывался)
deactivate DB
Note over API, DB: Старт атомарной SQL-транзакции
API->>DB: INSERT INTO processed_b2b_receipts (Фиксация номера чека для дедупликации)
loop По каждому товару из массива items (Включая весовые позиции)
API->>DB: INSERT INTO fridge_inventory ON CONFLICT (home_group_id, product_name, unit) DO UPDATE
end
Note over API, DB: Коммит атомарной транзакции (COMMIT)
API->>KAFKA: send_to_kafka_async("bupar.fridge.events.add")
API-->>MAG: HTTP 200 OK (status: "PROCESSED", items_added: 2)
API->>APP: WebSocket /ws/push-stream/{id} -> Сигнал "STOCK_INTEGRATION_UPDATED"
deactivate API
note over APP: Экран холодильника мгновенно перерисовывается, отображая +1.695 кг Бананов
Метод POST /api/v1/integration/shop/webhook
Документация B2B API: Автоматическое зачисление продуктов через фискальный вебхук торговой сети Магазин
- INVENTORY-2XX Backlog — Краткое Описание задачи 1.
- INVENTORY-1XX Refinement (Уточнение) — Краткое Описание задачи 2.
1 Функциональное назначение
Метод представляет собой открытый B2B-эндпоинт (вебхук), предназначенный для автоматического пополнения холодильника пользователя товарами, приобретенными в розничной сети или магазине или любом продавце с интеграцией.
Метод решает следующие архитектурные задачи:
- Синхронный Direct-Inflow (Прямой импорт): В отличие от пользовательского контура (где фото чека уходит на OCR и ручной аудит), “Вендор” передает серверу строго типизированный, очищенный JSON-датасет. Это позволяет системе зачислять продукты в холодильник мгновенно, минуя буферные таблицы
pending_receipts. - Сквозная B2B-авторизация: Метод защищен протоколом
OAuth2 Client Credentialsили выделенным статическим API-ключом, так как запрос инициируется сервером торговой сети, а не мобильным приложением. - Связывание аккаунтов (User Mapping): Идентификация целевого холодильника (
home_group_id) происходит по переданному в теле запросаloyalty_card_token(токен карты лояльности Магазин, которую пользователь привязал в своем профиле Flutter).
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/integration/shop/webhook - Формат данных:
application/json
2.1 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу строго типизированного JSON-пакета | application/json |
X-Shop-Token |
Да | Секретный статический ключ авторизации партнера (B2B API Key) | shop_live_secret_abc123... |
X-Request-ID |
Да | Сквозной ID запроса, генерируемый шиной Magnum для трассировки | shop-tr-44bb-99ff |
2.2 Спецификация тела запроса (Request Body)
Структура данных полностью повторяет фискальный срез чека Magnum. Поля количества товара (quantity) поддерживают тип float/double для корректного зачисления весовых позиций (например, бананов весом 1.695 кг).
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
loyalty_card_token |
String | Да | Уникальный токен карты, по которому бэкенд находит user_id и home_group_id |
shop-card-8822-fa |
receipt_number |
String | Да | Фискальный номер чека для предотвращения дублирования | CH-20260701-09 |
items |
Array | Да | Список приобретенных товарных позиций | [..._name: "БАНАН", qty: 1.695] |
2.2.1 Пример сырого JSON-запроса (Payload):
{
"loyalty_card_token": "shop-card-8822-fa",
"receipt_number": "CH-20260701-09",
"purchase_timestamp": "2026-07-01T23:10:00Z",
"store_id": "shop-almaty-05",
"items": [
{
"product_id": "2110310",
"product_name": "БАНАН ЭКВАДОР КГ",
"quantity": 1.6950,
"price": 595.00,
"unit": "кг"
},
{
"product_id": "4001020",
"product_name": "ХЛЕБ АКСАЙ БОРОДИНСКИЙ",
"quantity": 1.0000,
"price": 180.00,
"unit": "шт"
}
]
}3 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена логика сквозной интеграции через B2B-вебхук: бэкенд мгновенно идентифицирует пользователя по карте лояльности, проверяет чек на дубликаты и зачисляет весовую матрицу товаров напрямую в инвентарь PostgreSQL без ручного вмешательства.
4 Расшифровка шагов
Шаг 1 (
MAG -> API): В момент закрытия фискальной смены или сразу после оплаты клиентом покупки на кассе супермаркета, шина данных розничной сетиMagnumотправляетHTTP POSTзапрос на наш вебхук-эндпоинт. В теле запроса передается полный состав чека, включая весовые и штучные позиции.Шаг 2 (
API -> API): Бэкенд-шлюзFastAPIизвлекает заголовокX-Shop-Tokenи сверяет его со значением в безопасном хранилище конфигураций. Если токен невалиден, шлюз мгновенно отсекает запрос со статусомHTTP 401 Unauthorized, защищая систему от спама.Шаг 3 (
API -> DB):FastAPIизвлекает из JSON полеloyalty_card_tokenи делает запрос к базе данных:SELECT user_id, home_group_id FROM user_loyalty_cards WHERE partner_code = 'MAGNUM' AND card_token = $1. Так система находит, к какому именно холодильнику привязана данная скидочная карта.Шаг 4 (
DB -> API): База данных возвращаетuser_idиhome_group_idконкретной семьи.Шаг 5 (
API -> DB): Для защиты от повторных зачислений продуктов при сбоях сети на стороне партнера (Idempotency / Идемпотентность), бэкенд выполняет проверку:SELECT 1 FROM processed_b2b_receipts WHERE partner_code = 'MAGNUM' AND receipt_number = $1.Шаг 6 (
DB -> API): База данных возвращает пустой ответ, подтверждая, что данный фискальный документ уникален и обрабатывается нашей системой впервые.- Вилка исключений (Дубликат): Если строка найдена, бэкенд прерывает выполнение и возвращает Magnum статус
HTTP 409 Conflict, предотвращая дублирование продуктов в холодильнике.
- Вилка исключений (Дубликат): Если строка найдена, бэкенд прерывает выполнение и возвращает Magnum статус
Шаг 7 (
API -> DB): Бэкенд открывает атомарную SQL-транзакцию вPostgreSQL. Первым действием выполняется логирование фискального номера:INSERT INTO processed_b2b_receipts (partner_code, receipt_number) VALUES ('MAGNUM', $1), что мгновенно блокирует параллельные запросы-дубликаты.Шаг 8 (
API -> DB): Запускается цикл прямого зачисления товаров в холодильник семьи. Бэкенд берет точные веса и штуки из JSON-пакета (например, дробное значение1.695кг для бананов) и последовательно выполняет DML-команды вставки с апдейтом при коллизии (Upsert):INSERT INTO fridge_inventory (user_id, home_group_id, product_name, quantity, unit) VALUES (\$1, \$2, \$3, \$4, \$5) ON CONFLICT (home_group_id, product_name, unit) DO UPDATE SET quantity = fridge_inventory.quantity + EXCLUDED.quantity;Шаг 9 (
API -> API): После успешного прохождения всего массива товаров транзакция фиксируется в БД (COMMIT). Данные становятся доступны для чтения.Шаг 10 (
API -> KAFKA): Бэкенд асинхронно отправляет системное бизнес-событие в топик Kafkabridge.fridge.events.addдля уведомления смежных микросервисов (аналитика, умные рецепты) о пополнении запасов.Шаг 11 (
API -> MAG): Шлюз возвращает серверу Magnum успешный ответHTTP 200 OKс подтверждением приема данных и количеством зачисленных позиций.Шаг 12 (
API -> APP): Чтобы пользователю не приходилось вручную обновлять экран смартфона,FastAPIнаходит активное WebSocket-соединение семьи поhome_group_idи отправляет сигнал"STOCK_INTEGRATION_UPDATED". На стороне Flutter вкладка холодильника мгновенно перерисовывается, отображая свежие бананы и хлеб, купленные в Magnum минуту назад.
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 1. Успешный ответ (Success Response)
5.1.1 HTTP 200 OK (Ответ на Шаге 11)
Возвращается шине данных Magnum в качестве успешного подтверждения приема, дедупликации и зачисления всего массива товаров из чека в базу данных.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "PROCESSED",
"receipt_number": "CH-20260701-09",
"loyalty_card_token": "shop-card-8822-fa",
"metrics": {
"items_processed_count": 2,
"db_transaction_status": "COMMITTED"
},
"timestamp": "2026-07-01T23:10:02Z"
}5.2 2. Спецификация ошибок и вилок исключений (Error Responses)
5.2.1 Ошибка дублирования чека (HTTP 409 Conflict — Шаг 6)
Выбрасывается на Шаге 5, если переданный receipt_number уже присутствует в таблице дедупликации processed_b2b_receipts. Это предотвращает повторное начисление веса продуктов при сбоях и ретраях на стороне Magnum.
{
"error_code": "ERR-RECEIPT-DUPLICATE",
"message": "Данный фискальный документ уже был успешно обработан и зачислен ранее.",
"details": {
"rejected_receipt_number": "CH-20260701-09",
"partner_code": "MAGNUM",
"action": "Idempotency trigger activated. No duplicate database writes executed."
}
}5.2.2 Ошибка: Карта лояльности не привязана к профилю (HTTP 422 Unprocessable Entity — Шаг 4)
Выбрасывается на Шаге 4, если loyalty_card_token верный по структуре, но отсутствует в таблице маппинга user_loyalty_cards. Система не может определить, в чей именно холодильник нужно положить продукты.
{
"error_code": "ERR-LOYALTY-CARD-NOT-MAPPED",
"message": "Карта лояльности партнера не связана ни с одним активным аккаунтом в экосистеме .",
"details": {
"unmapped_token": "mag-card-8822-fa",
"action": "The user must physically link their Magnum card inside the Flutter mobile app profile."
}
}