POS BFF — API Contract
POS BFF — это BFF (Backend-for-Frontend) для POS Desktop
Содержание
REST (Bearer JWT кассира)
- GET /marketing/active (BR 6.1 — собственный агрегирующий)
- (proxy) Authentication, Cashier roster, Shift, Order, Catalog, Tables, Aggregator, PayKeeper — см. соответствующие сервисы
SSE
- GET /pos/notifications/stream (мост Kafka → EventSource)
GET /marketing/active
(BR 6.1)
Активные маркетинговые слайды текущей ТТ кассира + конфиг standby-режима. Используется POS Desktop при входе в /standby и при получении SSE marketing.invalidate.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (любая роль с pos.access; store_id берётся из scope кассира — см. Ролевая модель) |
| Base path | /pos/api/v1 |
Query Parameters
Нет. store_id определяется по JWT кассира — кассир видит только свою ТТ.
Response 200
{
"data": {
"slides": [
{
"id": "uuid",
"image_url": "string — public URL картинки",
"order": "integer"
}
],
"standby_idle_minutes": "integer — для idle-timer (default 5)",
"standby_transition_seconds": "integer — для transition (default 9)"
}
}Слайды отсортированы по order ASC, фильтрация — только active = true AND deleted_at IS NULL.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Нет pos.access permission |
NO_STORE_IN_SCOPE | 400 | У кассира пустой store-scope (логическая ошибка — кассир не привязан к ТТ) |
Реализация в pos-bff
Прокси к Store Service: GET /internal/stores/{store_id}/marketing-slides/active (см. Store Service).
store_id берётся из req.user.scope.store_ids[0] (предполагается, что кассир имеет ровно одну ТТ; в противном случае — 400 MULTI_STORE_NOT_SUPPORTED).
GET /pos/notifications/stream
(BR SSE-3, расширено BR 6.1)
Server-Sent Events канал для realtime-обновлений POS Desktop. Один канал на терминал, удерживается долго (до 1 часа), Connection: keep-alive.
| Параметр | Значение |
|---|---|
| Auth | JWT через ?token= query параметр (EventSource не поддерживает Authorization header) |
| Content-Type | text/event-stream |
| Nginx | access_log off, proxy_buffering off, proxy_read_timeout 3600s |
События
Каждое сообщение — стандартный SSE event: <name>\ndata: <json>\n\n.
| Event | Payload | Источник Kafka |
|---|---|---|
menu.invalidate | { "category": "product|category|modifier|stoplist" } | catalog.* топики |
table.invalidate | { "table_id": "uuid", "status": "..." } | store.table.upserted |
aggregator.invalidate | { "order_id": "uuid" } | aggregator.order.received |
marketing.invalidate | { "store_id": "uuid", "action": "..." } (BR 6.1) | marketing.slide.changed |
| (heartbeat) | : ping каждые 30 сек | синтетический |
POS Desktop слушает события через useSSE hook и вызывает соответствующие store reloads.
Routing внутри pos-bff
При получении Kafka-сообщения с payload.store_id pos-bff делает broadcastToStore(store_id, eventName, data) — рассылает SSE-event всем подключённым клиентам этой ТТ. Клиенты других ТТ событие не получают.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный ?token= |
NO_STORE_IN_SCOPE | 400 | JWT без store_ids |
Общий формат ошибок
См. REST Conventions — единый формат { error: { code, message, details } }.