SDLC 02: System Design and Architectural Decisions
Revision history: Updated June 2026 — 19-microservice architecture; Go/Python services; Meilisearch; BullMQ; expanded RBAC; fraud detection; loyalty/coupons; ZRA invoicing; WhatsApp; delivery tracking; dual-key gateway security.
1. High-Level Architecture
[End Users / Browsers / Mobile Apps]
|
▼
Cloudflare (DNS · WAF · CDN · SSL)
|
┌────┴────────────────────────────────────┐
│ │
▼ ▼
Next.js Frontend (Vercel) Nginx Reverse Proxy (EC2)
│ │
│ API calls (/api/v1/*) │
└──────────────────────────────────┘
│
┌──────────┴──────────┐
│ pakashop-gateway │ Port 8000
│ (API Gateway) │
└──────────┬──────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Core Services │ │ Supporting │ │ Data Layer │
│ (Node.js) │ │ Services │ │ │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ pakashop- │ │ pakashop-search │ │ PostgreSQL │
│ backend 3080 │ │ (Go) 3005 │ │ (Prisma ORM) │
│ pakashop- │ │ pakashop- │ │ Meilisearch │
│ config 3085 │ │ analytics │ │ (Go) 3007 │ │ Redis │
│ pakashop- │ │ pakashop- │ │ (sessions, │
│ notifications │ │ moderation │ │ cache, │
│ 3090 │ │ (Python) 3110 │ │ pub/sub, │
│ pakashop- │ │ pakashop- │ │ queues) │
│ tracking 3120 │ │ recommendations│ │ Cloudinary │
│ pakashop- │ │ (Python) 3100 │ │ (files) │
│ scheduler 3004│ │ │ │ │
│ pakashop-fraud │ │ │ │ │
│ 3006 │ │ │ │ │
│ pakashop-coupon │ │ │ │ │
│ 3008 │ │ │ │ │
│ pakashop-loyalty│ │ │ │ │
│ 3010 │ │ │ │ │
│ pakashop- │ │ │ │ │
│ whatsapp 3009 │ │ │ │ │
│ pakashop-reports│ │ │ │ │
│ 3011 │ │ │ │ │
│ pakashop- │ │ │ │ │
│ reconciliation│ │ │ │ │
│ 3012 │ │ │ │ │
│ pakashop- │ │ │ │ │
│ invoicing 3013│ │ │ │ │
│ pakashop- │ │ │ │ │
│ pricing 3014 │ │ │ │ │
│ pakashop- │ │ │ │ │
│ settlement 3016│ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└──────────────────────────┼──────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
PawaPay Flutterwave Middleware.io
(MoMo primary) (Card + MoMo failover) (APM)
2. Microservices Architecture
The Pakashop platform comprises 19 services, all hosted on AWS EC2 and managed by systemd. Services communicate via the API Gateway using a dual-key security model.
2.1 Service Registry
| Service | Port | Language | Responsibility |
|---|---|---|---|
pakashop-gateway | 8000 | Node.js | Reverse proxy, routing, dual-key auth, rate limiting, WebSocket upgrades |
pakashop-backend | 3080 | Node.js | Core business logic: auth, orders, products, shops, payments, users |
pakashop-config | 3085 | Node.js | Centralised feature flags and runtime configuration (Redis-cached) |
pakashop-notifications | 3090 | Node.js | Email (Resend/SMTP), in-app notifications, SSE real-time stream |
pakashop-tracking | 3120 | Node.js | Real-time delivery tracking: WebSocket server, Redis Pub/Sub, GPS ingestion |
pakashop-moderation | 3110 | Python | Sightengine AI content moderation pipeline for uploaded images |
pakashop-recommendations | 3100 | Python | Collaborative filtering engine (Jaccard + k-NN) on Redis |
pakashop-scheduler | 3004 | Node.js | Background job infrastructure (BullMQ): periodic cleanup, retries |
pakashop-search | 3005 | Go | Full-text product search via Meilisearch: typo-tolerant, faceted |
pakashop-fraud | 3006 | Node.js | Rules-based fraud detection: velocity checks, anomalies, self-dealing |
pakashop-analytics | 3007 | Go | Business intelligence: vendor/admin dashboards, aggregation |
pakashop-coupon | 3008 | Node.js | Promotions & discount engine: creation, validation, redemption |
pakashop-loyalty | 3010 | Node.js | Points-based loyalty & rewards: earn, redeem, expire |
pakashop-whatsapp | 3009 | Node.js | WhatsApp Business Cloud API: order alerts, delivery PINs, cart recovery |
pakashop-reports | 3011 | Node.js | PDF/CSV/Excel report generation |
pakashop-reconciliation | 3012 | Node.js | Automated financial reconciliation: internal vs provider settlements |
pakashop-invoicing | 3013 | Node.js | ZRA Smart Invoice VSDC integration: configurable on/off, mock mode |
pakashop-pricing | 3014 | Node.js | Dynamic pricing engine: commissions, discounts, markups |
pakashop-settlement | 3016 | Node.js | Automated batch vendor & agent payouts |
2.2 Inter-Service Communication
All external traffic enters through the API Gateway (pakashop-gateway on port 8000). Internal service-to-service communication uses the gateway with an internal API key.
Dual-Key Security Model:
| Key | Header | Purpose | Who Uses It |
|---|---|---|---|
| External client key | x-pakashop-key | Identifies external clients (frontend, mobile apps) | Frontend, third-party integrations |
| Internal service key | x-internal-key | Authenticates inter-service requests | All microservices calling each other |
External Client → Gateway (x-pakashop-key) → Backend Service
Internal Service → Gateway (x-internal-key) → Another Service
The gateway validates keys, applies rate limiting, and routes to the appropriate upstream service based on path prefixes.
3. Key Architectural Decisions
3.1 Hosting: AWS EC2 with systemd
- Decision: All 19 services deployed on AWS EC2 instances, managed by systemd unit files.
- Rationale: EC2 provides persistent WebSocket connections, long-running background jobs (BullMQ, polling, settlement), and direct Redis access — requirements not met by serverless edge runtimes.
- Frontend remains on Vercel for CDN and preview deployment benefits.
3.2 Polyglot Services
- Decision: Use the best language for each service domain.
- Node.js (16 services): Primary language for I/O-bound services (API, notifications, payments, scheduling).
- Go (2 services): CPU-bound services requiring high throughput (search, analytics).
- Python (2 services): ML/AI pipelines (moderation via Sightengine, recommendations via collaborative filtering).
3.3 Database: Prisma ORM + PostgreSQL
- Decision: Prisma as the sole ORM for PostgreSQL across all Node.js services.
- Rationale: Type-safe migrations, strong typing, and seamless Node.js integration.
- Connection pooling: PgBouncer or Prisma Accelerate recommended for high-concurrency endpoints.
- Go services use
pgx(native PostgreSQL driver) directly. - Python services use
asyncpgorpsycopg2.
3.4 Search: Meilisearch
- Decision: Dedicated Meilisearch instance (Go service, port 3005) for product search.
- Rationale: Typo-tolerant, faceted search with sub-100ms response times; superior to PostgreSQL full-text search for marketplace scale.
- Sync: Product create/update/delete events trigger Meilisearch index updates via the search service.
3.5 Job Queue: BullMQ on Redis
- Decision: BullMQ (Redis-backed) for background jobs across all Node.js services.
- Use cases: Email sending, report generation, settlement batching, reconciliation, ZRA invoice transmission, inventory alerts.
- Scheduler service (port 3004) manages cron-style recurring jobs.
3.6 Caching: Redis
- Decision: Redis deployed on EC2 (same VPC as services).
- Use cases: User sessions, cart data, rate-limiting buckets, temporary payment status, product listing cache, pub/sub for tracking, BullMQ job queues.
- See
../caching-strategy.mdfor key naming and TTL conventions.
3.7 Payment Architecture: PawaPay + Flutterwave
- Decision: PawaPay as the primary mobile money provider; Flutterwave for card payments and mobile money failover.
- Rationale: PawaPay is purpose-built for Zambian MNOs (MTN, Airtel, Zamtel); Flutterwave provides SAQ-A-compliant card processing.
- PCI-DSS: Card data never touches Pakashop servers; all card entry handled by Flutterwave's hosted payment page.
- Settlement: Delayed settlement model — funds held by licensed providers, released after PIN-verified delivery.
- See
../payment-architecture.mdfor the full implementation.
3.8 Content Moderation: Sightengine
- Decision: All image uploads run through the
moderationmicroservice (Python, port 3110) which calls Sightengine. - Rationale: Automated NSFW/violence detection before images appear on the marketplace.
- See
../content-moderation.md.
3.9 Fraud Detection: Rules Engine
- Decision: Dedicated
fraudmicroservice (Node.js, port 3006) for real-time transaction monitoring. - Rationale: Velocity checks, amount anomalies, self-dealing detection, and risk scoring require isolated processing to avoid impacting checkout latency.
- See
../fraud-detection.md.
3.10 Observability: Middleware.io
- Decision: OpenTelemetry-based APM via Middleware.io across all 19 services.
- Rationale: Unified traces, logs, and metrics from a single dashboard without managing a self-hosted Grafana stack.
- Logging: pino structured logging with correlation IDs; shipped to Middleware.io and journald.
3.11 Email Infrastructure Split
- Resend: Transactional emails (OTPs, order confirmations, password reset). High deliverability, analytics.
- Zoho Mail: Human-to-human customer support correspondence.
3.12 File Uploads: Cloudinary
- All product images, shop logos, KYC documents, and delivery signature photos upload to Cloudinary with signed authentication.
- After upload, the URL is stored in PostgreSQL; Sightengine moderation runs asynchronously.
3.13 DNS and Security: Cloudflare
- Full DNS management, WAF rules (OWASP top 10), and CDN via Cloudflare.
- GoDaddy acts only as domain registrar with NS records pointing to Cloudflare.
3.14 WhatsApp Integration
- Decision: Dedicated
whatsappmicroservice (Node.js, port 3009) for WhatsApp Business Cloud API. - Use cases: Order alerts, delivery PIN notifications, cart recovery messages.
- Rationale: Isolates WhatsApp API complexity and rate limits from core backend.
4. Data Flow Examples
4.1 Mobile Money Checkout
1. Customer selects MTN Money on /checkout
2. Frontend → POST /api/v1/orders → Order created (PENDING)
3. Frontend → POST /api/v1/payments/initiate
4. PaymentOrchestrator → PawaPayAdapter.initiateDeposit()
5. PawaPay sends USSD push to customer's phone
6. Backend returns { type: 'ussd', transactionId, message }
7. Frontend shows MobileMoneyOverlay, polls GET /api/v1/payments/status/:orderId every 5 s
8. PawaPay webhook → POST /api/v1/payments/webhook/pawapay
9. WebhookHandler verifies HMAC → marks payment CAPTURED → Order = PAID/CONFIRMED
10. Poll detects PAID → redirect to /orders/:id
4.2 Card Checkout
1. Customer selects Visa/Mastercard on /checkout
2. Frontend → POST /api/v1/orders → Order created (PENDING)
3. Frontend → POST /api/v1/payments/initiate
4. FlutterwaveAdapter generates hosted payment link
5. Frontend redirects to Flutterwave's hosted page
6. Customer enters card details on Flutterwave (raw card data never reaches Pakashop)
7. Flutterwave redirects to /payment/callback?orderId=...
8. Flutterwave also POSTs to /api/v1/payments/webhook/flutterwave
9. Callback page polls status → shows success
4.3 Product Image Upload with Moderation
1. Merchant uploads image → Express API requests Cloudinary signed signature
2. Frontend uploads directly to Cloudinary using signature
3. Cloudinary URL stored in PostgreSQL via Prisma
4. Background job → moderation service (Python) → Sightengine API
5. If violation: image flagged, product hidden until manual review
6. Admin reviews in dashboard → approve/reject/escalate
4.4 Delivery Tracking
1. Agent picks up order → status changes to OUT_FOR_DELIVERY
2. Agent app posts GPS location every 5s → POST /api/v1/tracking/location
3. Tracking service applies Kalman filter → smooths GPS jitter
4. Redis PUBLISH order:{orderId}:location
5. WebSocket subscribers (buyer, vendor, admin) receive live location
6. Geofence check: within 400m of destination → ARRIVING event → buyer push notification
7. Agent arrives → customer provides 6-digit PIN → agent confirms delivery
8. Digital signature captured → SHA-256 hash stored for tamper evidence
9. Order status → DELIVERED → settlementStatus → RELEASABLE
4.5 Fraud Detection Flow
1. Payment initiation request received
2. Fraud service (port 3006) evaluates in parallel:
- Velocity: >5 payments/hour from same IP?
- Amount: >3x average order value?
- Self-dealing: buyer and seller same account?
- Risk score: composite score 0-100
3. If score > threshold: payment blocked, admin review queue entry created
4. Admin reviews in fraud dashboard → approve/release or reject
5. If approved: payment proceeds; if rejected: order cancelled
4.6 ZRA Smart Invoice Flow
1. Order confirmed (PAID) → invoicing service (port 3013) triggered
2. If ZRA integration enabled (feature flag) and vendor has valid TPIN:
a. Generate Smart Invoice payload per ZRA VSDC spec
b. Transmit to ZRA VSDC API
c. Log transmission (success/failure) with retry logic
3. If mock mode: simulate transmission, log mock receipt
4. Admin can view transmission status in dashboard
5. Failed transmissions auto-retry (3 attempts, exponential backoff)
5. Environment Configuration
| Variable | Purpose | Scope |
|---|---|---|
DATABASE_URL | PostgreSQL connection string | All |
REDIS_URL | Redis connection URL | All |
MEILISEARCH_URL | Meilisearch host | search, backend |
RESEND_API_KEY | Transactional email | notifications |
CLOUDINARY_CLOUD_NAME | Cloudinary config | backend |
CLOUDINARY_API_SECRET | Signed upload auth | backend |
PAWAPAY_API_KEY | PawaPay deposits & payouts | backend, settlement |
PAWAPAY_WEBHOOK_SECRET | HMAC signature verification | backend |
FLUTTERWAVE_SECRET_KEY | Flutterwave API auth | backend |
FLUTTERWAVE_SECRET_HASH | Webhook verif-hash | backend |
MIDDLEWARE_API_KEY | Middleware.io APM | All |
SIGHTENGINE_API_USER | Content moderation | moderation |
SIGHTENGINE_API_SECRET | Content moderation | moderation |
JWT_SECRET | Session token signing | backend, gateway |
PAYMENT_PROVIDER | MOCK | AUTO | PAWAPAY | backend |
INTERNAL_API_KEY | Inter-service authentication | All |
PAKASHOP_CLIENT_KEY | External client authentication | gateway |
TOTP_ENCRYPTION_KEY | TOTP secret encryption | backend |
TWILIO_ACCOUNT_SID | SMS/WhatsApp MFA | backend, whatsapp |
TWILIO_AUTH_TOKEN | SMS/WhatsApp MFA | backend, whatsapp |
WHATSAPP_BUSINESS_ID | WhatsApp Business API | |
ZRA_VSDC_API_KEY | ZRA Smart Invoice | invoicing |
ZRA_MOCK_MODE | ZRA mock transmission | invoicing |
FEATURE_FLAGS_REDIS_KEY | Feature flags cache key | config |
For internal use only. Do not distribute outside Pakashop engineering.