sequenceDiagram
autonumber
actor Partner as REST-клиент Торговой Сети
participant GW as FastAPI Gateway (backend-api)
participant Helper as helper-service (INVENTORY)
participant Auth as auth-service (SECURITY/IAD)
participant K as Apache Kafka Cluster
participant Fridge as fridge-service (INVENTORY)
%% ПОТОК ОБРАБОТКИ ВЕБХУКА И gRPC ВАЛИДАЦИЯ
Partner->>GW: POST /api/v1/webhooks/stores/receipt (Authorization: shop_token)
GW->>Helper: Внутренний прокси сырого JSON вебхука
Helper->>Auth: gRPC: validateStoreToken(shop_token, shop_id)
alt Сценарий 3а: Токен НЕ валиден / Магазин заблокирован
Auth-->>Helper: gRPC Response: INVALID / BLOCKED
Helper-->>Partner: HTTP 401 Unauthorized
else Сценарий 3б: Токен валиден (Успех)
Auth-->>Helper: gRPC Response: VALID (OK)
Helper-->>Partner: HTTP 200 OK (Контракт исполнен, Fire-and-Forget)
%% АСИНХРОННЫЙ ХВОСТ ПРЯМОГО ЗАЧИСЛЕНИЯ
Helper->>K: Пуш в топик: bpds.inventory.in.fridge_item.deduct
K->>Fridge: Handler: ProcessTrustedB2BReceipt()
Note over Fridge: Шаг 7: Прямой транзакционный INSERT<br/>в PostgreSQL Fridge DB на баланс юзера
end
Метод POST /api/v1/webhooks/stores/receipt
Документация B2B API: Автоматическое зачисление продуктов через фискальный вебхук торговой сети Магазин
- IAD-MIGRATION-114 Backlog — Создать таблицу auth_db.b2b_trusted_stores
- INFRA-101 Ready for Development — Создать топик
bpds.inventory.in.fridge_item.deduct - INV-BACKEND-029 Backlog — Реализация хендлера прямого доверенного зачисления продуктов.
- IAD-BACKEND-012 Ready for Development — Реализация gRPC метода криптографической валидации токенов магазинов.
- INV-BACKEND-024 Backlog — Реализация оркестратора вебхуков и gRPC-авторизации B2B
- GW-39 Backlog — Подключение эндпоинта в шлюзе
- INV-FRONTEND-220 Refinement (Уточнение) — Экран управления B2B-интеграциями
- INV-FRONTEND-221 Refinement (Уточнение) — Обновление списка продуктов при фоновом B2B-зачислении чеков
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/webhooks/stores/receipt - Формат данных:
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 Успешный ответ (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 Спецификация ошибок и вилок исключений (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."
}
}