Phase 03 — Product Delivery
DDD architecture, ADRs, REST API, database, tech stack and QA report.
Bounded Context Map (DDD)
Visual map of the 6 bounded contexts and their dependencies in the FoodFlow domain.
BOUNDED CONTEXT MAP — FOODFLOW
ECP Digital Bank
(External System)
PIX Transfer, QR Code,
Webhooks, Virtual Cards
Identity
Auth, sessions and profiles
Aggregates: User, Address, Session
JWT 24h + Refresh 7d + bcrypt
Roles: consumer, restaurant, admin
Catalog
Restaurants, menus and categories
Aggregates: Restaurant, MenuItem, Category
Public domain (no auth)
6 restaurants, 24 items, 7 categories
Ordering
Cart, orders and coupons
Aggregates: Cart, Order, Coupon
pending → confirmed → preparing
Free delivery >= R$ 120
Payment
ECP Card, PIX QR, webhooks, SSE
Aggregates: Payment, BankTransaction, PixQrCode
Circuit breaker (5 failures = 30s)
Values in cents | HMAC-SHA256
Delivery
Simulated delivery tracking
Aggregates: DeliveryTracking
confirmed → preparing → delivered
MVP: no GPS, ETA via eta_min/max
Admin
Dashboard and platform management
Sub: PlatformAdmin, RestaurantAdmin
CRUD restaurants, categories, coupons
Metrics: orders, revenue, avg ticket
user_id
auth token
menu_item_id
order_id
status update
API calls
webhook PIX
order status
CRUD
metrics + coupons
LEGEND
Direct dependency
Reference / Admin
External integration
External system
Bounded Context
Architecture — 6 Bounded Contexts
Identity
Authentication, authorization, sessions and profiles
Aggregates: User (User, Address, Session)
JWT 24h + Refresh Token 7d. Roles: consumer, restaurant, admin. bcryptjs 12 rounds.
Catalog
Restaurants, categories, menus and items
Aggregates: Restaurant, Category
Public domain (read without auth). 6 seed restaurants, 24 items, 7 categories.
Ordering
Cart, orders, order items, coupons
Aggregates: Cart, Order, Coupon
Status: pending_payment → confirmed → preparing → out_for_delivery → delivered. Free delivery ≥ R$ 120.
Payment
ECP Digital Bank integration: virtual card, PIX, webhooks, SSE
Aggregates: Payment + Services (bank-integration, webhook-handler, sse-manager)
Values in cents. Circuit breaker (5 failures = 30s block). PIX expires in 10 min. HMAC-SHA256 validation.
Delivery
Simulated delivery tracking
Cross-reference with Ordering context
MVP: restaurant updates status manually. No real GPS. ETA calculated from eta_min/eta_max.
Admin
Dashboard, management of restaurants, categories, coupons
Aggregates: PlatformAdmin, RestaurantAdmin
Metrics: total restaurants, orders, revenue, avg ticket. CRUD for restaurants, categories and coupons.
Architecture Decision Records (ADRs)
ADR-001
Monorepo Single-Server Architecture
Accepted
Decision: Fastify serves REST API (/api/*) + React SPA (client/dist/) in a single Node.js process. In dev, Vite with separate HMR; in production, static files.
Pros: Simplified deploy (PM2), minimal cost (VPS R$ 20-50/month), pattern validated in ecp-digital-banking.
Cons: Limited horizontal scalability, frontend and backend coupled in deploy.
ADR-002
SQLite with better-sqlite3 as Primary Database
Accepted
Decision: SQLite via better-sqlite3 11.x with WAL mode. File at ./data/foodflow.db. Inline migrations on startup.
Pros: Zero-config, exceptional performance for < 100k rows, native prepared statements, synchronous API.
Cons: Limited concurrent writes, no native replication.
ADR-003
ECP Digital Bank Integration as Payment Proxy
Accepted
Decision: FoodFlow acts as payment proxy. bank-integration.mjs encapsulates all calls. ECP Virtual Card via PIX transfer debit + PIX QR Code with webhook + SSE.
Pros: Real payments without external gateway, server-side security, ephemeral JWT.
Cons: Bank availability dependency, proxy latency.
ADR-004
SSE for Real-Time PIX Payment Notification
Accepted
Decision: Server-Sent Events via GET /api/payments/:id/events. SSE Manager with Map<paymentId, Set<ServerResponse>>. Auth via short-lived JWT token (5 min) in query param.
Pros: Native in browser (EventSource), unidirectional, low overhead, auto-reconnect.
Cons: EventSource does not support custom headers, 6-connection limit in HTTP/1.1.
ADR-005
CSS Modules with CSS Custom Properties for Theming
Accepted
Decision: CSS Modules (*.module.css) for isolated scope + CSS Custom Properties (CSS variables) for global Midnight Express theming.
Pros: Zero runtime JS, centralized theming, no style conflicts, minimal bundle.
Cons: No prop-based dynamic styling (resolved with inline style prop).
Sequence Diagram — Full Order + Payment Flow
End-to-end flow: from order to payment (ECP Card and PIX QR Code) with ECP Digital Bank integration.
SEQUENCE DIAGRAM — ORDER + PAYMENT (PIX & ECP CARD)
Consumer
(React SPA)
FoodFlow API
(Fastify Server)
ECP Digital Bank
(External API)
Webhook Handler
(HMAC Validator)
SSE Manager
(Real-time)
PHASE: ORDER
1. GET /api/restaurants/:id/menu
2. { menuItems[] }
3. POST /api/cart/items + POST /api/orders
4. { order_id, total, status: pending_payment }
FLOW A: ECP VIRTUAL CARD PAYMENT
5a. POST /api/payments/bank-auth { email, senha }
6a. bankLogin(email, password)
7a. { ephemeral bankJWT, cards[] }
8a. POST /api/payments/card { order_id, card_id }
9a. bankPixTransfer(bankJwt, amountCents)
10a. { transactionId, status: completed }
11a. Payment OK, order confirmed
FLOW B: PIX QR CODE PAYMENT (WITH WEBHOOK + SSE)
5b. POST /api/payments/pix { order_id }
6b. bankGeneratePixQrCode(platformJwt, amountCents)
7b. { qrcodeData, qrcodeImage, expiresAt (10min) }
8b. { paymentId, qrCodeImage, expiresAt }
Shows QR + timer
9b. GET /api/payments/:id/events (SSE EventSource)
Registers connection
Pays in bank app
(scans QR)
10b. POST /webhooks/bank/pix-received
{ event: pix.received, X-Webhook-Signature: HMAC }
Validates HMAC-SHA256
Extracts payment_id
11b. payment.status = completed, order.status = confirmed
12b. Emits payment_update
13b. SSE event: { type: payment_update, status: completed } (< 2s SLO)
Success! Redirect
Tracking screen
LEGEND
Request
Response
Bank API
Webhook
SSE
Success
Flow Resilience Rules
Idempotent: same payment.id resent does not duplicate transaction
Bank timeout: 10s with 1x retry for 5xx errors (2s backoff)
Circuit breaker: 5 consecutive failures = 30s block
PIX expires: 10 minutes — worker checks every 30s
Webhook: always returns 200 OK to prevent bank retries
SSE auth: short-lived JWT token (5 min) via query param
Ephemeral bank JWT: never persisted beyond the transaction, cleared after completion
REST API — 48 Endpoints
Authentication (3)
POST /api/auth/register
POST /api/auth/login
POST /api/auth/refresh
Consumer Profile (5)
GET /api/consumer/profile
PUT /api/consumer/profile
GET /api/consumer/addresses
POST /api/consumer/addresses
DELETE /api/consumer/addresses/:id
Catalog (4)
GET /api/categories
GET /api/restaurants
GET /api/restaurants/:id
GET /api/restaurants/:id/menu
Cart (5)
GET /api/cart
POST /api/cart/items
PUT /api/cart/items/:id
DELETE /api/cart/items/:id
DELETE /api/cart
Orders (4)
POST /api/orders
GET /api/orders
GET /api/orders/:id
PATCH /api/orders/:id/status
Coupons (1)
POST /api/coupons/validate
Payments (5)
POST /api/payments/bank-auth
GET /api/payments/bank-cards
GET /api/payments/bank-balance
POST /api/payments/card
POST /api/payments/pix
GET /api/payments/:id/events SSE
Webhook (1)
POST /api/webhooks/bank/pix-received
Favorites (3)
GET /api/favorites
POST /api/favorites
DELETE /api/favorites/:restaurantId
Restaurant Admin (7)
GET /api/restaurant-admin/profile
POST /api/restaurant-admin/menu-items
PUT /api/restaurant-admin/menu-items/:id
DELETE /api/restaurant-admin/menu-items/:id
GET /api/restaurant-admin/orders
PUT /api/restaurant-admin/orders/:id/status
PUT /api/restaurant-admin/settings
Platform Admin (9)
GET /api/admin/dashboard
GET /api/admin/restaurants
PATCH /api/admin/restaurants/:id/status
GET /api/admin/categories
POST /api/admin/categories
PUT /api/admin/categories/:id
DELETE /api/admin/categories/:id
GET /api/admin/coupons
POST /api/admin/coupons
PUT /api/admin/coupons/:id
Entity-Relationship Diagram (ER)
Complete data model with 12 tables, primary keys, foreign keys and cardinalities (14 FKs).
ENTITY-RELATIONSHIP DIAGRAM — SQLITE (better-sqlite3 11.x)
users
PK id INTEGER
email TEXT UQ
password_hash TEXT
name, phone TEXT
role TEXT
FK restaurant_id → restaurants
created_at, updated_at
addresses
PK id INTEGER
FK user_id → users CASCADE
label, street, number
city, state, zip_code
is_default INTEGER
categories
PK id INTEGER
name TEXT UQ
emoji TEXT
sort_order, is_active INTEGER
restaurants
PK id INTEGER
name, slug TEXT (slug UQ)
FK category_id → categories
cuisine, subtitle, rating
eta_min, eta_max INTEGER
delivery_fee, min_order REAL
cover_gradient, hero_emoji
is_active, is_open INTEGER
menu_items
PK id INTEGER
FK restaurant_id → restaurants
name, description TEXT
price REAL
emoji, badge, category TEXT
sort_order, is_available INTEGER
carts
PK id INTEGER
FK user_id → users UQ
updated_at TEXT
cart_items
PK id INTEGER
FK cart_id → carts CASCADE
FK menu_item_id → menu_items
quantity INTEGER > 0
orders
PK id INTEGER
FK user_id → users
address_text TEXT
subtotal, delivery_fee REAL
discount, total REAL
coupon_code TEXT
payment_method TEXT
status TEXT (6 states)
order_items
PK id INTEGER
FK order_id → orders CASCADE
FK restaurant_id → restaurants
restaurant_name TEXT (snap)
item_name, item_price TEXT/REAL
quantity INTEGER
favorites
PK id INTEGER
FK user_id → users CASCADE
FK restaurant_id → restaurants
UNIQUE(user_id, restaurant_id)
coupons
PK id INTEGER
code TEXT UQ
discount_type TEXT (fixed|percent)
discount_value, min_order REAL
max_uses, uses_count INTEGER
is_active, expires_at INT/TEXT
payments
PK id TEXT (p_XXXX)
FK order_id → orders UQ
FK user_id → users
method TEXT (card|pix)
status TEXT (5 states)
amount_cents INTEGER
bank_transaction_id TEXT
pix_qrcode_data TEXT
card_last4, pix_expiration
1:N
1:1
1:N (user_id)
1:N
1:N
1:N
1:N
1:N (restaurant_id)
1:N
N:1
1:N
1:1 (payments)
1:N (user_id)
LEGEND
PK Primary Key
FK Foreign Key
Direct relation
FK reference
UQ = UNIQUE | CASCADE = ON DELETE
14 Foreign Keys | WAL mode
SQLite via better-sqlite3 11.x | WAL mode | Seed: 7 categories, 6 restaurants, 24 items, coupon MVP10 (R$ 10 off, min R$ 80), 3 demo users
Database — 12 Tables (Summary)
Table Description Columns
users Users (consumer, restaurant, admin) id, email, password_hash, name, phone, role, restaurant_id, created_at, updated_at
addresses Delivery addresses id, user_id, label, street, number, complement, neighborhood, city, state, zip_code, is_default
categories Marketplace categories id, name, emoji, sort_order, is_active
restaurants Partner restaurants id, name, slug, cuisine, subtitle, category_id, rating, review_count, eta_min, eta_max, delivery_fee, min_order, cover_gradient, hero_emoji, promo_text, tags, is_active, is_open
menu_items Menu items id, restaurant_id, name, description, price, emoji, badge, category, sort_order, is_available
carts Server-side cart (1 per user) id, user_id, updated_at
cart_items Cart items id, cart_id, menu_item_id, quantity
orders Orders with calculated values id, user_id, address_text, subtotal, delivery_fee, discount, total, coupon_code, payment_method, status
order_items Order items snapshot id, order_id, restaurant_id, restaurant_name, menu_item_id, item_name, item_price, quantity
favorites Consumer-restaurant relation id, user_id, restaurant_id, created_at
coupons Discount coupons id, code, discount_type, discount_value, min_order, max_uses, uses_count, is_active, expires_at
payments Payments integrated with ECP Bank id, order_id, user_id, method, status, amount_cents, bank_transaction_id, pix_qrcode_data, pix_expiration, card_last4, webhook_received_at
Engine: SQLite via better-sqlite3 11.x | Seed: 7 categories, 6 restaurants, 24 items, coupon MVP10 (R$ 10 off, min R$ 80), 3 demo users.
Tech Stack
Backend
Runtime: Node.js 20 LTS
Framework: Fastify 5.x
Database: SQLite via better-sqlite3 11.x
Auth: JWT (jsonwebtoken) + bcryptjs
Validation: TypeBox (JSON Schema)
SSE: native reply.raw.write
Webhook: HMAC-SHA256 + timingSafeEqual
Frontend
Framework: React 18
Build: Vite 5.x
Styling: CSS Modules + CSS Custom Properties
Routing: React Router 6
State: Context API (Auth, Cart, Theme)
HTTP: Fetch API with custom wrapper
SSE: native EventSource + useSSE hook
QA Report
Critical Bugs
BUG-001 — Critical
Native EventSource does not support custom headers (Authorization: Bearer)
Without SSE authentication, anyone can listen to payment events. Solution: token via query param with short expiry (5 min).
BUG-002 — Critical
Free delivery rule does not correctly handle multi-restaurant cart
Delivery fees for all restaurants in the cart must be summed before applying exemption (≥ R$ 120). Aggregated calculation is non-trivial.
Major Bugs
BUG-003 — Major
MVP10 coupon inconsistency: 10% in backlog vs fixed R$ 10 in tech_spec
BUG-004 — Major
No per-restaurant minimum order validation at checkout
Deploy Conditions
Fix BUG-001: SSE auth via query param token
Fix BUG-002: multi-restaurant delivery fee rule
Resolve BUG-003: decide if MVP10 is 10% or R$ 10
Implement BUG-004: min_order validation
Smoke tests P0 (32 cases) passing 100%
Core tests P1 (68 cases) passing ≥ 95%
Zero critical bugs open
Generated Artifacts
03-product-delivery/architecture.json
03-product-delivery/qa-report.json