Skip to main content

SDLC 03: Development Standards and Implementation Guidelines

Revision history: Updated June 2026 — 19-microservice architecture; Go/Python services; Meilisearch; BullMQ; expanded RBAC (12 roles); MFA enforcement; dual-key gateway security; feature flags; WhatsApp; ZRA invoicing.


1. Repository Structure

pakashop/
├── services/ # All 19 microservices
│ ├── gateway/ # pakashop-gateway (Node.js, port 8000)
│ │ ├── src/
│ │ │ ├── index.js
│ │ │ ├── routes/
│ │ │ ├── middleware/
│ │ │ │ ├── auth.js # x-pakashop-key validation
│ │ │ │ ├── internal.js # x-internal-key validation
│ │ │ │ └── rateLimit.js
│ │ │ └── lib/
│ │ │ └── logger.js
│ │ ├── package.json
│ │ └── systemd/
│ │ └── pakashop-gateway.service
│ │
│ ├── backend/ # pakashop-backend (Node.js, port 3080)
│ │ ├── src/
│ │ │ ├── index.js
│ │ │ ├── routes/ # Thin route definitions
│ │ │ ├── controllers/ # Request/response handling
│ │ │ ├── services/ # Business logic
│ │ │ │ ├── payment/ # PaymentService, Orchestrator, Adapters
│ │ │ │ ├── shops/
│ │ │ │ ├── auth/
│ │ │ │ ├── notifications/
│ │ │ │ ├── moderation/
│ │ │ │ ├── email/
│ │ │ │ ├── fraud/
│ │ │ │ ├── inventory/
│ │ │ │ ├── zra/
│ │ │ │ └── whatsapp/
│ │ │ ├── middleware/
│ │ │ │ ├── auth.js # JWT + MFA validation
│ │ │ │ ├── rbac.js # Role-based access control
│ │ │ │ ├── seller.upload.js
│ │ │ │ └── data.scope.js
│ │ │ ├── lib/
│ │ │ │ ├── logger.js
│ │ │ │ ├── prisma.js
│ │ │ │ └── tracing.js
│ │ │ └── utils/
│ │ │ ├── paymentUtils.js
│ │ │ ├── product.utils.js
│ │ │ └── compliance.js # PII masking, scrubbing
│ │ ├── prisma/
│ │ │ ├── schema.prisma # Canonical data model
│ │ │ └── migrations/
│ │ ├── bullmq/
│ │ │ └── queues.js # Job queue definitions
│ │ └── package.json
│ │
│ ├── config/ # pakashop-config (Node.js, port 3085)
│ ├── notifications/ # pakashop-notifications (Node.js, port 3090)
│ ├── tracking/ # pakashop-tracking (Node.js, port 3120)
│ ├── scheduler/ # pakashop-scheduler (Node.js, port 3004)
│ ├── fraud/ # pakashop-fraud (Node.js, port 3006)
│ ├── coupon/ # pakashop-coupon (Node.js, port 3008)
│ ├── loyalty/ # pakashop-loyalty (Node.js, port 3010)
│ ├── whatsapp/ # pakashop-whatsapp (Node.js, port 3009)
│ ├── reports/ # pakashop-reports (Node.js, port 3011)
│ ├── reconciliation/ # pakashop-reconciliation (Node.js, port 3012)
│ ├── invoicing/ # pakashop-invoicing (Node.js, port 3013)
│ ├── pricing/ # pakashop-pricing (Node.js, port 3014)
│ ├── settlement/ # pakashop-settlement (Node.js, port 3016)
│ ├── search/ # pakashop-search (Go, port 3005)
│ ├── analytics/ # pakashop-analytics (Go, port 3007)
│ ├── moderation/ # pakashop-moderation (Python, port 3110)
│ └── recommendations/ # pakashop-recommendations (Python, port 3100)

├── frontend/ # Next.js 15 (App Router)
│ ├── src/
│ │ ├── app/ # Pages (App Router)
│ │ │ ├── checkout/
│ │ │ ├── orders/
│ │ │ ├── payment/callback/
│ │ │ ├── agent/dashboard/
│ │ │ ├── courier/dashboard/
│ │ │ └── admin/
│ │ ├── components/
│ │ │ ├── checkout/ # DeliveryForm, PaymentMethodSelector, etc.
│ │ │ ├── tracking/ # LiveMap, AgentMarker, etc.
│ │ │ └── admin/
│ │ ├── contexts/ # AuthContext, CartContext
│ │ ├── hooks/ # useGeolocationTracking, useWebSocket
│ │ └── lib/
│ │ ├── payment.js # Payment API client
│ │ ├── delivery.js # Delivery API client
│ │ └── tracking.js # WebSocket client
│ └── public/
│ └── images/logos/ # MNO logos (mtn.svg, airtel.svg, zamtel.png)

├── shared/
│ ├── constants/
│ │ └── errors.js # Centralised error codes
│ ├── types/
│ │ └── index.d.ts # Shared TypeScript definitions
│ └── utils/
│ └── validators.js # Shared validation logic

├── docs/ # This documentation suite
│ └── SDLC/

├── scripts/
│ ├── deploy.sh # Branch-aware deployment script
│ ├── pakashop-status.sh # Unified status dashboard
│ └── db-backup.sh # Nightly backup script

├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── deploy-staging.yml
│ ├── deploy-production.yml
│ ├── health-check.yml
│ └── db-backup.yml

└── .env.example # Environment variable template (no secrets)

2. Coding Standards

2.1 Module System

Node.js services use CommonJS (require / module.exports) throughout. Frontend uses ES Modules (import / export). Go uses standard Go modules. Python uses standard Python imports.

// Backend — CommonJS
const { prisma } = require('../../lib/prisma');
module.exports = { listShops };

// Frontend — ES Modules
import { initiatePayment } from '@/lib/payment';
export default function CheckoutPage() { ... }

Critical: Never mix require and import within the same backend file.

2.2 Import Path Conventions

Backend imports follow these relative depth rules:

File locationTo reach lib/Example
src/routes/../lib/require('../lib/logger')
src/controllers/feature/../../lib/require('../../lib/prisma')
src/services/feature/../../lib/require('../../lib/logger')
src/services/payment/webhooks/../../../lib/require('../../../lib/prisma')

Always verify the relative depth before adding a new require(). Incorrect paths are the most common source of startup crashes.

2.3 Structured Logging

Use the centralised pino logger. Never use console.log in production code.

const logger = require('../../lib/logger');

// Correct — structured, no raw PII
logger.info({ orderId, gateway: 'PAWAPAY' }, 'Payment initiation started');
logger.error({ err: error, orderId }, 'Payment initiation failed');

// Wrong
console.log('Payment started for order', orderId, 'phone:', phone);

All logs are shipped to Middleware.io via OpenTelemetry. PII must be masked before logging:

const { maskPhone, maskEmail, scrubPan } = require('../../lib/compliance');

logger.info({
orderId,
phone: maskPhone(phone), // +26097*****56
email: maskEmail(email), // jo***@example.com
pan: scrubPan(pan) // [CARD-REDACTED]
}, 'User action logged');

2.4 Observability — Custom Spans

Wrap critical business logic in withSpan to generate traces visible in Middleware.io:

const { withSpan } = require('../../lib/tracing');

async function initiateCheckout(orderId, method, phone) {
return withSpan('payment.initiateCheckout', {
'order.id': orderId,
'payment.method': method
}, async () => {
// ... business logic
});
}

2.5 Error Handling

  • Controller functions must never throw unhandled errors to Express. Always wrap in try/catch.
  • Return consistent error shapes using centralised error codes:
// constants/errors.js
const ErrorCodes = {
ORDER_NOT_FOUND: { code: 'ORDER_001', status: 404, message: 'Order not found' },
PAYMENT_FAILED: { code: 'PAY_001', status: 400, message: 'Payment processing failed' },
UNAUTHORIZED: { code: 'AUTH_001', status: 401, message: 'Authentication required' },
FORBIDDEN: { code: 'AUTH_002', status: 403, message: 'Insufficient permissions' },
RATE_LIMITED: { code: 'RATE_001', status: 429, message: 'Too many requests' },
};

// Controller usage
const err = new Error(ErrorCodes.ORDER_NOT_FOUND.message);
err.statusCode = ErrorCodes.ORDER_NOT_FOUND.status;
err.code = ErrorCodes.ORDER_NOT_FOUND.code;
throw err;

2.6 Prisma

  • Run npx prisma generate after every schema change.
  • All database queries use the Prisma client — no raw SQL except in migrations.
  • For transactions, use prisma.$transaction([...]) or the interactive client.
  • Development: npx prisma migrate dev --name <description>.
  • Production: npx prisma migrate deploy.
  • Never use prisma db push in production.

2.7 Payment — Critical Rules

// NEVER collect card data on Pakashop servers
const { cardNumber, cvv, expiry } = req.body; // Forbidden

// Always redirect to hosted payment page for cards
const { redirectUrl } = await FlutterwaveAdapter.createPaymentLink(order);

// NEVER log raw phone numbers
logger.info({ phone }); // Wrong

// Mask before logging
logger.info({ phone: maskPhone(phone) });

2.8 Feature Flags

Feature flags are managed by the pakashop-config service (port 3085) and cached in Redis:

const { getFeatureFlag } = require('../../lib/config');

// Check if ZRA invoicing is enabled
const zraEnabled = await getFeatureFlag('zra_invoicing_enabled');
if (zraEnabled) {
await invoicingService.generateSmartInvoice(order);
}

Flags can be toggled at runtime without redeployment via the admin dashboard.


3. Authentication Flow

3.1 JWT + MFA

1. User submits credentials
2. Backend validates password → checks MFA enrollment status
3. If MFA required and not enrolled → issue mfa_setup token (30min) → redirect to MFA setup
4. If MFA enrolled → issue challenge token (5min) → prompt for MFA code
5. User submits TOTP/email/SMS code → verified
6. Issue accessToken (24h) + refreshToken (30d) → set httpOnly cookies
7. Subsequent requests: validate accessToken → check role permissions

3.2 Role-Based Access Control

Roles are enforced via middleware:

// middleware/rbac.js
const requireRoles = (...allowedRoles) => {
return (req, res, next) => {
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({
success: false,
code: 'AUTH_002',
message: 'Insufficient permissions'
});
}
next();
};
};

// Route usage
router.post('/admin/users',
authenticate,
requireRoles('PLATFORM_ADMIN', 'MODERATOR'),
userController.createUser
);

3.3 Gateway Dual-Key Security

All requests to the gateway must include one of:

HeaderValueUsed By
x-pakashop-keyClient API keyFrontend, mobile apps, external integrations
x-internal-keyInternal service keyMicroservices calling each other
// Gateway middleware
const validateGatewayKey = (req, res, next) => {
const clientKey = req.headers['x-pakashop-key'];
const internalKey = req.headers['x-internal-key'];

if (clientKey === process.env.PAKASHOP_CLIENT_KEY) {
req.keyType = 'external';
return next();
}
if (internalKey === process.env.INTERNAL_API_KEY) {
req.keyType = 'internal';
return next();
}

return res.status(401).json({
success: false,
code: 'GATEWAY_001',
message: 'Invalid or missing API key'
});
};

4. Environment Variables

Variables are stored in /opt/pakashop/.env.production on EC2 (backend) and Vercel Environment Variables (frontend). Never commit .env files.

The .env.example at the project root documents all required variables with placeholder values.

ScopeManagement method
Backend (EC2)/opt/pakashop/.env.production loaded by systemd
Frontend (Vercel)Vercel Project Settings → Environment Variables
Local dev.env.local (gitignored)
Test.env.test (gitignored, separate DB, Redis DB 1)

5. Implementation Guidelines by Module

5.1 Authentication

  • JWT accessToken (24h TTL) + refreshToken (30d, stored in Redis).
  • OTP login via Resend; Google OAuth via OAuthService.js.
  • MFA mandatory for PLATFORM_ADMIN, SHOP_OWNER, DELIVERY_AGENT.
  • Always use the authenticate middleware on protected routes.
  • Role checks via requireRoles('PLATFORM_ADMIN', 'MODERATOR').

5.2 File Uploads

  • Uploads go to Cloudinary via signed URL.
  • After upload, trigger the moderation service asynchronously (fire-and-forget).
  • Maximum sizes: 10 MB for product images; 5 MB for KYC documents.
  • Accepted MIME types validated by multer before any Cloudinary call.

5.3 Payments

  • All payment orchestration goes through PaymentService (the only module the rest of the app imports).
  • Use PAYMENT_PROVIDER=MOCK in local development.
  • Mobile money: always validate Zambian phone format before calling the gateway.
  • Card: always use type: 'redirect' response — never collect card fields.
  • Fraud evaluation runs in parallel with payment initiation.

5.4 Multi-Vendor Order Handling

  • Each OrderItem is linked to a Shop and carries its own settlementStatus.
  • The SettlementLedger calculates vendorAmount and platformFee per item.
  • Only POST /payments/release/:orderId (admin or automated) transitions HELD → RELEASABLE.
  • Settlement service (port 3016) handles batch payouts.

5.5 Inventory Management

  • Stock movements tracked via StockMovement table (adjustments, sales, returns).
  • SKU auto-generation: {shopSlug}-{categoryCode}-{autoIncrement}.
  • Barcode generation via bwip-js for product labels.
  • Low-stock alerts triggered by BullMQ job queue.
  • Wholesale tiers: per-product volume discount tables.

5.6 SEO

  • Dynamic <title> and <meta name="description"> via Next.js generateMetadata() on every page.
  • Structured data (JSON-LD) on product detail pages.
  • sitemap.xml generated programmatically; submitted to Google Search Console.

5.7 WhatsApp Integration

  • WhatsApp service (port 3009) handles all WhatsApp Business API calls.
  • Templates must be pre-approved by Meta before use.
  • Cart recovery: 24-hour abandoned cart reminder via WhatsApp.
  • Delivery PINs: sent to buyer via WhatsApp + SMS fallback.

6. Git Workflow

BranchPurposeMerge policy
feature/*New featuresPR → main; requires 1 review + CI pass
hotfix/*Critical production fixesPR → production; fast-track review
chore/*Dependency updates, docsPR → main
mainIntegration / stagingProtected; no direct push
productionLive platformProtected; triggers EC2 deploy via GitHub Actions

Commit message format:

type(scope): short description

Types: feat, fix, chore, docs, refactor, test, style
Example: feat(payments): add PawaPay USSD overlay component

For internal use only. Do not distribute outside Pakashop engineering.