Phase 03 — Product Delivery
DDD architecture, REST API, React SPA frontend and quality — from domain design to deploy.
5
Bounded Contexts
7
ADRs
27
Endpoints
9
Tables
10
Pages
29
Test Cases
10/10
Quality Gates
Architecture — Domain-Driven Design
5 Bounded Contexts in a modular Node.js monolith (Fastify). Logical separation in distinct modules under /server, with extraction path to microservices in the future.
classDiagram
class IdentityAccess {
+User (aggregate root)
+Session (stateless JWT)
+Email, CPF, HashedPassword
POST /auth/register
POST /auth/login
GET /users/me
}
class BankingCore {
+Account (aggregate root)
+Transaction
+Money, TransactionCategory
GET /accounts/balance
GET /transactions
GET /transactions/summary
}
class Pix {
+PixKey (aggregate root)
+PixTransfer
+PixKeyType, PixLimit
POST /pix/transfer
GET /pix/keys
POST /pix/keys
}
class CardsPayments {
+Card (aggregate root)
+Invoice, CardPurchase
+CardNumber, CardLimit
GET /cards
POST /payments/boleto
}
class Intelligence {
+Notification (aggregate root)
+SalaryCycle, CategoryRule
GET /transactions/summary
}
IdentityAccess --> BankingCore : UserRegistered
IdentityAccess --> Pix : User exists
IdentityAccess --> CardsPayments : User exists
Pix --> BankingCore : debit/credit
CardsPayments --> BankingCore : debit
BankingCore --> Intelligence : events
Pix --> Intelligence : events
CardsPayments --> Intelligence : events
Bounded Contexts
| BC | Name | Type | Modules | Responsibility |
|---|---|---|---|---|
| BC-01 | Identity & Access | Supporting | auth, users | Identity, JWT authentication, authorization, session lifecycle |
| BC-02 | Banking Core | Core | accounts, transactions | Balance custody, atomic debit/credit, immutable ledger, categorization |
| BC-03 | Pix | Core | pix | Pix keys, transfers, time-based limits, QR Code |
| BC-04 | Cards & Payments | Supporting | cards, payments | Virtual card, invoices, purchases, bill payment |
| BC-05 | Intelligence | Supporting | transactions (summary), notifications | Automatic categorization, insights, notifications, payroll cycle |
Architecture Decision Records (ADRs)
ADR-001
SQLite3 as the MVP database instead of PostgreSQL
Status: Accepted
Zero configuration — single file database.sqlite. No separate server. Full ACID transactions via better-sqlite3 (synchronous). Excellent performance for loads <100 writes/second. Migration to PostgreSQL planned when horizontal scale is a requirement.
ADR-002
Direct SQL via better-sqlite3 instead of ORM (Prisma, TypeORM, Drizzle)
Status: Accepted
Financial domain queries (debit + transaction record) are explicit and auditable. Zero ORM overhead. Automatic prepared statements via better-sqlite3 — SQL injection impossible with correct usage.
ADR-003
Local JWT authentication with bcryptjs instead of external provider
Status: Accepted
Zero external dependency. CPF and email never leave the server. Enhanced authentication (BR-03) implemented as native Fastify middleware. JWT HS256 token with 24h expiry. bcryptjs (pure JS, saltRounds=10).
ADR-004
SPA with React+Vite instead of SSR with Next.js
Status: Accepted
Authenticated web-first product — no screen needs SEO. SPA delivers an experience closer to a desktop app. Vite HMR <50ms. Bundle served by Fastify itself as static files.
ADR-005
Monetary values as INTEGER in cents instead of DECIMAL/FLOAT
Status: Accepted
Stripe/Adyen/PagSeguro standard. Absolute precision — no floating point. R$1,234.56 = 123456 cents. Pix limits checked with integer comparison. SQLite INTEGER 64-bit supports up to ~R$92 quadrillion.
ADR-006
Soft delete with deleted_at column instead of physical deletion
Status: Accepted
BR-07 defines soft delete as a requirement. 100% audit history preserved. Banking compliance: records are never destroyed. Reversibility of mistaken operations.
ADR-007
3-file convention per module: schema.ts + routes.ts + service.ts
Status: Accepted
schema.ts: Zod schemas for validation + inferred TypeScript types. routes.ts: Fastify plugin with routes (zero business logic). service.ts: pure domain logic with direct database access. Service testable without HTTP server.
Technology Stack
Backend
- Runtime: Node.js 18+
- Framework: Fastify 5.0
- Database: SQLite3 (better-sqlite3) with WAL mode
- Auth: JWT (jsonwebtoken) + bcryptjs
- Validation: Zod 3.23
- Language: TypeScript 5.6 (strict)
- Test: Vitest
Frontend
- Framework: React 18.3
- Build: Vite 5.4
- Routing: React Router 6.26
- Styling: Tailwind CSS 3.4
- Icons: Lucide React
- Language: TypeScript 5.5 (strict)
- State: React hooks + Context
Sequence Diagrams
Authentication — Registration and Login
sequenceDiagram
participant U as User (Browser)
participant F as Frontend (React)
participant A as API (Fastify)
participant DB as SQLite
Note over U,DB: New account registration
U->>F: Fills name, email, CPF, password
F->>A: POST /api/auth/register
A->>DB: SELECT user WHERE email = ?
DB-->>A: null (does not exist)
A->>A: bcrypt.hash(password, 12)
A->>DB: INSERT users + INSERT accounts
DB-->>A: userId, accountId
A->>A: jwt.sign({ id, email })
A-->>F: { token, user }
F->>F: localStorage.set(ecp_token)
F-->>U: Redirect /dashboard
Note over U,DB: Login
U->>F: Fills email, password
F->>A: POST /api/auth/login
A->>DB: SELECT user WHERE email = ?
DB-->>A: user (with password_hash)
A->>A: bcrypt.compare(password, hash)
A->>A: jwt.sign({ id, email }, 7d)
A-->>F: { token, user }
F->>F: localStorage.set(ecp_token)
F-->>U: Redirect /dashboard
Send Pix — Full Flow with Validations
sequenceDiagram
participant U as User (Browser)
participant F as Frontend (React)
participant A as API (Fastify)
participant DB as SQLite
U->>F: Enters destination Pix key
F->>A: GET /api/pix/lookup?key=...
A->>DB: SELECT pix_key WHERE key_value = ?
DB-->>A: { userId, name, keyType }
A-->>F: { name: "John Smith", keyType: "cpf" }
F-->>U: Shows recipient
U->>F: Enters amount (e.g. R$500)
F->>A: POST /api/pix/transfer
Note over A: Middleware: JWT auth
A->>A: Check rate limit (BR-10: max 10/hour)
A->>DB: SELECT COUNT pix_rate_limit (last hour)
DB-->>A: count = 3 (OK)
A->>A: Check time-based limit
alt 10 PM to 6 AM (nighttime)
A->>A: BR-02: amount <= R$1,000?
else 6 AM to 10 PM (daytime)
A->>A: BR-01: daily total <= R$5,000?
end
alt Amount > R$2,000
A-->>F: Requires enhanced authentication (BR-03)
F-->>U: Confirmation modal + password
U->>F: Confirms with password
F->>A: POST /api/pix/transfer (with confirmation)
end
A->>A: Check balance (BR-04)
A->>DB: SELECT balance_cents FROM accounts
DB-->>A: balance = 150000 (R$1,500)
Note over A,DB: Atomic transaction (SQLite)
A->>DB: BEGIN TRANSACTION
A->>DB: UPDATE accounts SET balance -= 50000 (sender)
A->>DB: UPDATE accounts SET balance += 50000 (recipient)
A->>DB: INSERT transactions (sender debit)
A->>DB: INSERT transactions (recipient credit)
A->>DB: INSERT pix_rate_limit
A->>DB: INSERT notifications (recipient)
A->>DB: COMMIT
A-->>F: { transactionId, status: "completed", balanceAfter }
F-->>U: Success screen with receipt
Virtual Card Purchase
sequenceDiagram
participant M as Merchant / FoodFlow
participant A as API (Fastify)
participant DB as SQLite
M->>A: POST /api/cards/:id/purchase
Note over A: { amountCents, description, merchantName }
Note over A: Middleware: JWT auth
A->>DB: SELECT card WHERE id = ? AND user_id = ?
DB-->>A: card { limitCents: 300000, usedCents: 124750, isBlocked: false }
A->>A: Check card active and not blocked
A->>A: Check available limit
Note over A: available = 300000 - 124750 = 175250
alt amountCents > available
A-->>M: 400 CARD_LIMIT_EXCEEDED
else amountCents <= available
A->>DB: SELECT/CREATE invoice (current month, status open)
DB-->>A: invoice { id, totalCents }
Note over A,DB: Atomic transaction
A->>DB: BEGIN TRANSACTION
A->>DB: INSERT card_purchases
A->>DB: UPDATE cards SET used_cents += amountCents
A->>DB: UPDATE invoices SET total_cents += amountCents
A->>DB: COMMIT
A-->>M: 201 { purchaseId, status: "completed", availableAfterCents }
end
Bill Payment
sequenceDiagram
participant U as User (Browser)
participant F as Frontend (React)
participant A as API (Fastify)
participant DB as SQLite
U->>F: Pastes bill barcode
F->>F: Decodes barcode (amount, due date, beneficiary)
F-->>U: Shows transparency breakdown
U->>F: Confirms payment
F->>A: POST /api/payments/boleto
Note over A: { barcode, amount, scheduledFor? }
Note over A: Middleware: JWT auth
A->>A: Check balance (BR-04)
A->>DB: SELECT balance_cents FROM accounts
DB-->>A: balance = 423578
alt scheduledFor (scheduled)
A->>DB: INSERT transactions (status: pending, scheduled_for)
A-->>F: { transactionId, status: "pending", scheduledFor }
F-->>U: "Bill scheduled for MM/DD"
else Immediate payment
Note over A,DB: Atomic transaction
A->>DB: BEGIN TRANSACTION
A->>DB: UPDATE accounts SET balance -= amountCents
A->>DB: INSERT transactions (status: completed, category: boleto)
A->>DB: INSERT notifications
A->>DB: COMMIT
A-->>F: { transactionId, status: "completed", balanceAfter }
F-->>U: Success screen with receipt
end
Dashboard — Initial Load
sequenceDiagram
participant U as User (Browser)
participant F as Frontend (React)
participant A as API (Fastify)
participant DB as SQLite
U->>F: Navigates to /dashboard
F->>F: Checks JWT in localStorage
par Parallel calls
F->>A: GET /api/accounts/me/balance
A->>DB: SELECT balance_cents FROM accounts
A-->>F: { balanceCents: 423578 }
and
F->>A: GET /api/transactions?limit=5
A->>DB: SELECT * FROM transactions ORDER BY created_at DESC LIMIT 5
A-->>F: { transactions: [...] }
and
F->>A: GET /api/transactions/summary
A->>DB: SELECT category, SUM(amount) GROUP BY category
A-->>F: { categories: [...], totalIncome, totalExpense }
and
F->>A: GET /api/cards
A->>DB: SELECT * FROM cards WHERE user_id = ?
A-->>F: { cards: [...] }
and
F->>A: GET /api/notifications/unread-count
A->>DB: SELECT COUNT(*) WHERE is_read = 0
A-->>F: { count: 3 }
end
F->>F: Renders full dashboard
F-->>U: Balance + Donut chart + Transactions + Card + Notifications
API — 9 Modules, 27 Endpoints
Auth (3 endpoints)
POST/api/auth/register
POST/api/auth/login
GET/api/auth/me
Accounts (3 endpoints)
GET/api/accounts/me
GET/api/accounts/me/balance
PATCH/api/accounts/me/limit
Pix (5 endpoints)
GET/api/pix/keys
POST/api/pix/keys
DELETE/api/pix/keys/:keyId
POST/api/pix/transfer
GET/api/pix/lookup
Transactions (2 endpoints)
GET/api/transactions
GET/api/transactions/:id
Cards (5 endpoints)
GET/api/cards
GET/api/cards/:id
PATCH/api/cards/:id/limit
PATCH/api/cards/:id/block
GET/api/cards/:id/invoice
Payments (3 endpoints)
POST/api/payments/boleto
GET/api/payments/scheduled
DELETE/api/payments/scheduled/:id
Users (3 endpoints)
GET/api/users/me
PATCH/api/users/me
POST/api/users/me/change-password
Notifications (4 endpoints)
GET/api/notifications
GET/api/notifications/unread-count
PATCH/api/notifications/:id/read
POST/api/notifications/read-all
Dashboard (1 endpoint)
GET/api/dashboard
Entity-Relationship Model (ERM)
9 tables, 12 indexes. SQLite3 with WAL mode and foreign keys enabled. Monetary values in cents (INTEGER).
erDiagram
users {
TEXT id PK
TEXT name
TEXT email UK
TEXT cpf UK
TEXT password_hash
TEXT phone
TEXT avatar_url
INTEGER is_active
TEXT created_at
TEXT updated_at
}
accounts {
TEXT id PK
TEXT user_id FK "UK"
TEXT agency
TEXT number UK
INTEGER balance_cents
INTEGER daily_transfer_limit_cents
INTEGER daily_transferred_cents
TEXT last_transfer_date
INTEGER is_active
TEXT created_at
TEXT updated_at
}
pix_keys {
TEXT id PK
TEXT user_id FK
TEXT account_id FK
TEXT key_type "cpf email phone random"
TEXT key_value UK
INTEGER is_active
TEXT created_at
TEXT deleted_at "soft delete"
}
transactions {
TEXT id PK
TEXT account_id FK
TEXT type "credit debit"
TEXT category "pix boleto card_purchase transfer deposit withdrawal refund fee"
INTEGER amount_cents
INTEGER balance_after_cents
TEXT description
TEXT counterpart_name
TEXT counterpart_document
TEXT counterpart_institution
TEXT pix_key
TEXT pix_key_type
TEXT boleto_code
TEXT status "pending completed failed cancelled"
TEXT scheduled_for
TEXT created_at
}
cards {
TEXT id PK
TEXT user_id FK
TEXT account_id FK
TEXT type "physical virtual"
TEXT card_number
TEXT last4
TEXT card_holder
TEXT card_expiry
INTEGER limit_cents
INTEGER used_cents
INTEGER due_day
INTEGER is_active
INTEGER is_blocked
TEXT created_at
TEXT updated_at
}
invoices {
TEXT id PK
TEXT card_id FK
TEXT reference_month
INTEGER total_cents
TEXT due_date
TEXT status "open closed paid overdue"
TEXT paid_at
TEXT created_at
TEXT updated_at
}
card_purchases {
TEXT id PK
TEXT card_id FK
TEXT invoice_id FK
TEXT description
TEXT merchant_name
TEXT merchant_category
INTEGER amount_cents
INTEGER installments
INTEGER current_installment
TEXT status "pending completed cancelled refunded"
TEXT purchased_at
}
notifications {
TEXT id PK
TEXT user_id FK
TEXT title
TEXT body
TEXT type "transaction security marketing system"
INTEGER is_read
TEXT created_at
}
pix_rate_limit {
TEXT id PK
TEXT account_id FK
TEXT window_start
INTEGER transfer_count
}
users ||--|| accounts : "1:1 owns"
users ||--o{ pix_keys : "1:N registers"
users ||--o{ cards : "1:N owns"
users ||--o{ notifications : "1:N receives"
accounts ||--o{ pix_keys : "1:N links"
accounts ||--o{ transactions : "1:N moves"
accounts ||--o{ cards : "1:N associates"
accounts ||--o{ pix_rate_limit : "1:N controls"
cards ||--o{ invoices : "1:N generates"
cards ||--o{ card_purchases : "1:N records"
invoices ||--o{ card_purchases : "1:N groups"
Cardinalities
| Relationship | Type | Description |
|---|---|---|
| users ↔ accounts | 1:1 | Each user owns exactly one bank account |
| users ↔ pix_keys | 1:N | Up to 5 Pix keys per user (BR-05) |
| users ↔ cards | 1:N | Multiple virtual cards per user |
| users ↔ notifications | 1:N | Notifications of financial and security events |
| accounts ↔ transactions | 1:N | Immutable movement ledger (credit/debit) |
| accounts ↔ pix_rate_limit | 1:N | Control of 10 Pix/hour per account (BR-10) |
| cards ↔ invoices | 1:N | One invoice per reference month per card |
| cards ↔ card_purchases | 1:N | Purchases recorded on the card (merchant + category) |
| invoices ↔ card_purchases | 1:N | Purchases grouped in the month's invoice |
Database
SQLite3 with WAL mode and foreign keys. 9 tables, 12 indexes.
| Table | Description | Aggregate Root |
|---|---|---|
| users | Identity data (name, email, CPF, password hash, salary_day) | User (BC-01) |
| accounts | Bank account with balance in cents, account number, limits | Account (BC-02) |
| transactions | Immutable movement ledger (credit/debit) with category | — |
| pix_keys | Pix keys (CPF, email, phone, EVP) with soft delete | PixKey (BC-03) |
| cards | Virtual card with limit, status (active/blocked/cancelled) | Card (BC-04) |
| invoices | Monthly card invoices (open/closed/paid) | — |
| card_purchases | Purchases recorded on the card with merchant and category | — |
| notifications | Financial event notifications with read/unread status | Notification (BC-05) |
| pix_rate_limit | Pix rate limit control per hour per user (BR-10) | — |
Frontend — React SPA
10 Pages
| Page | File | Main Features |
|---|---|---|
| Login | routes/login.tsx | Email + password, show/hide password, error handling |
| Register | routes/register.tsx | Name, email, CPF mask, password validation (regex) |
| Dashboard | routes/dashboard.tsx | Balance show/hide, quick actions, SVG donut chart, card summary, last 5 transactions |
| Statement | routes/extrato.tsx | Paginated list with filters, category badges, pagination with counter |
| Pix Send | routes/pix/enviar.tsx | Multi-step flow (key → amount → confirm → success), key lookup |
| Pix Receive | routes/pix/receber.tsx | Key list, copy-to-clipboard, account data |
| Pix Keys | routes/pix/chaves.tsx | Full CRUD, modal, counter 5/5, confirm dialog |
| Cards | routes/cartoes.tsx | Card visual with gradient, limit bar, block/unblock, invoice |
| Payments | routes/pagamentos.tsx | Bill form, scheduling, scheduled payments list |
| Profile | routes/perfil.tsx | Edit name/phone, change password, logout |
9 Components
UI Components
- Button: 4 variants (primary, secondary, ghost, danger), 3 sizes, loading spinner
- Card: 2 variants (default, highlighted), 4 paddings, sub-components
- Input: Label, error, hint, left/right icons, forwardRef
- Modal: Escape key, backdrop click, scroll lock, 3 sizes
- Badge: 6 variants (success, warning, danger, info, lime, default)
- Table: Full table primitives
Layout Components
- Sidebar: Desktop nav with 6 items, user info, logout
- Header: Dynamic greeting, notifications with dropdown (unread count, 30s polling)
- MobileNav: Bottom nav with 5 items (lg:hidden)
QA — Quality and Testing
Quality Gates — 10/10 Passing
| Gate | Status | Detail |
|---|---|---|
| Type Safety | Pass | TypeScript strict in server (39 files) and web (28 files). Zod enforces runtime validation. |
| Input Validation | Pass | All 27 endpoints validate inputs via Zod + Fastify validation hooks. |
| Business Rules | Pass | 10/10 BRs implemented (BR-01 to BR-10). |
| Auth Security | Pass | JWT + bcrypt (cost 10). Auth middleware on all protected routes. CORS configured. |
| Error Handling | Pass | AppError with ErrorCode enum (15 categories). ZodError transformed. Safe 500 responses. |
| Design System | Pass | 100% compliance with design_spec.md. Dark theme, lime accent, Inter font. |
| API Integration | Pass | Web integrates with all 27 endpoints via centralized fetch wrapper (api.ts). |
| Responsive | Pass | Desktop sidebar (≥1024px) + mobile bottom nav. Tested: 375px, 768px, 1024px, 1440px. |
| Test Coverage | Pass | 29 test cases in 4 files. Highest-risk modules: auth (10), pix (11), accounts (4), payments (4). |
| Accessibility | Pass* | Semantic HTML, form labels, color contrast AA. *Notes: skip-to-content and aria-labels on icon buttons pending. |
Findings (5)
| Severity | Category | Description |
|---|---|---|
| low | Test Coverage | 5 modules (transactions, cards, users, notifications, dashboard) without dedicated test files. The 4 tested modules cover the highest-risk functionality. |
| info | Accessibility | Missing skip-to-content link, focus management on route changes, and aria-label on icon buttons. |
| info | Performance | Fetch wrapper without caching/deduplication. Consider React Query or SWR in a future iteration. |
| low | Security | JWT in localStorage (vs. httpOnly cookies). Acceptable for MVP without third-party scripts. |
| info | Observability | Logging via console without structured format. Integrate pino (native Fastify) in Phase 04. |
Phase 03 HITLs
| HITL | Scope | Agent | Decision |
|---|---|---|---|
| #7 | DDD Architecture (5 BCs, 7 ADRs, ubiquitous language, aggregates) | Software Architect | Approved |
| #8 | Backend (9 modules, 27 endpoints, 10/10 BRs, 9 tables, seed) | Backend Developer | Approved |
| #9 | Frontend (10 pages, 9 components, design system compliance) | Frontend Developer | Approved |
| #10 | QA (29 test cases, 10/10 quality gates, 5 info/low findings) | QA | Approved |