Phase 03 — Product Delivery
HITLs #7-#10 Approved Architect + Dev + QA ← Ecosystem 🇧🇷 PT

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 PKidINTEGER emailTEXT UQ password_hashTEXT name, phoneTEXT roleTEXT FKrestaurant_id→ restaurants created_at, updated_at addresses PKidINTEGER FKuser_id→ users CASCADE label, street, number city, state, zip_code is_defaultINTEGER categories PKidINTEGER nameTEXT UQ emojiTEXT sort_order, is_activeINTEGER restaurants PKidINTEGER name, slugTEXT (slug UQ) FKcategory_id→ categories cuisine, subtitle, rating eta_min, eta_maxINTEGER delivery_fee, min_orderREAL cover_gradient, hero_emoji is_active, is_openINTEGER menu_items PKidINTEGER FKrestaurant_id→ restaurants name, descriptionTEXT priceREAL emoji, badge, categoryTEXT sort_order, is_availableINTEGER carts PKidINTEGER FKuser_id→ users UQ updated_atTEXT cart_items PKidINTEGER FKcart_id→ carts CASCADE FKmenu_item_id→ menu_items quantityINTEGER > 0 orders PKidINTEGER FKuser_id→ users address_textTEXT subtotal, delivery_feeREAL discount, totalREAL coupon_codeTEXT payment_methodTEXT statusTEXT (6 states) order_items PKidINTEGER FKorder_id→ orders CASCADE FKrestaurant_id→ restaurants restaurant_nameTEXT (snap) item_name, item_priceTEXT/REAL quantityINTEGER favorites PKidINTEGER FKuser_id→ users CASCADE FKrestaurant_id→ restaurants UNIQUE(user_id, restaurant_id) coupons PKidINTEGER codeTEXT UQ discount_typeTEXT (fixed|percent) discount_value, min_orderREAL max_uses, uses_countINTEGER is_active, expires_atINT/TEXT payments PKidTEXT (p_XXXX) FKorder_id→ orders UQ FKuser_id→ users methodTEXT (card|pix) statusTEXT (5 states) amount_centsINTEGER bank_transaction_idTEXT pix_qrcode_dataTEXT 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 PKPrimary Key FKForeign 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)

TableDescriptionColumns
usersUsers (consumer, restaurant, admin)id, email, password_hash, name, phone, role, restaurant_id, created_at, updated_at
addressesDelivery addressesid, user_id, label, street, number, complement, neighborhood, city, state, zip_code, is_default
categoriesMarketplace categoriesid, name, emoji, sort_order, is_active
restaurantsPartner restaurantsid, 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_itemsMenu itemsid, restaurant_id, name, description, price, emoji, badge, category, sort_order, is_available
cartsServer-side cart (1 per user)id, user_id, updated_at
cart_itemsCart itemsid, cart_id, menu_item_id, quantity
ordersOrders with calculated valuesid, user_id, address_text, subtotal, delivery_fee, discount, total, coupon_code, payment_method, status
order_itemsOrder items snapshotid, order_id, restaurant_id, restaurant_name, menu_item_id, item_name, item_price, quantity
favoritesConsumer-restaurant relationid, user_id, restaurant_id, created_at
couponsDiscount couponsid, code, discount_type, discount_value, min_order, max_uses, uses_count, is_active, expires_at
paymentsPayments integrated with ECP Bankid, 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

82
Quality Score
142
Test Cases
98
ACs Covered
6
Bugs Found
Conditional Go
Go/No-Go

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

Generated Artifacts

03-product-delivery/architecture.json 03-product-delivery/qa-report.json