Architecture Overview

Architecture Overview

System Design

Drop Srbija is a fintech payment app for the Serbian market, enabling phone-based remittance and instant transfers via NBS IPS (Narodna Banka Srbije instant payment system).

Core principle: Money never touches Drop — it stays in user's bank account. Drop facilitates transfers only.

Technology Stack

Component Choice Rationale
Language (Backend) Kotlin ALAI standard 2026-03-17; full type safety + Ktor native support
Framework Ktor 3.1.2 Lightweight, async-first, Production-ready with netty transport
Database PostgreSQL 16 ALAI mandate; full ACID, timezone support, JSON operators
ORM Exposed (Kotlin DSL) Kotlin-native, composable, maps to SQL closely
Migrations Flyway Version control for schema; integration with Ktor startup
Connection Pool HikariCP Battle-tested, fast, connection reuse
Auth JWT (HS256) Stateless, easy to validate on backend + frontend
Language (Frontend) TypeScript + Next.js 15 React 19, server components, app router
Styling Tailwind CSS 4 + shadcn/ui ALAI standard; component library prebuilt
Icons Lucide React ALAI standard across all products
Testing (Backend) Kotest + Testcontainers Kotlin-native, containerized DB for integration tests
Testing (Frontend) Vitest + Playwright Unit + E2E, headless browser automation

Module Structure

DropSrbija/
├── backend/                    # Kotlin + Ktor 3.1.2
│   ├── src/main/kotlin/no/alai/dropsrbija/
│   │   ├── Application.kt      # Main entry point
│   │   ├── plugins/            # Ktor configuration
│   │   │   ├── Database.kt
│   │   │   ├── Routing.kt
│   │   │   ├── Authentication.kt
│   │   │   └── RateLimit.kt
│   │   ├── auth/               # JWT services
│   │   ├── models/             # Database tables (Exposed DSL)
│   │   └── modules/            # Business logic
│   │       ├── auth/           # Phone OTP flow
│   │       ├── user/           # User management
│   │       ├── transactions/   # Transaction handling
│   │       ├── recipients/     # Recipient management
│   │       └── ips/            # NBS IPS integration
│   └── src/main/resources/db/migration/  # Flyway migrations
└── frontend/                   # Next.js 15 + React 19
    └── src/app/
        ├── layout.tsx
        ├── page.tsx            # Landing page (Serbian)
        └── (app)/              # Authenticated app

Database Schema (Migrations V1-V9)

V1: Core Tables (Users, PhoneVerifications, Transactions, Recipients, NbsIpsLogs, Merchants, Settings)

Users Table:

PhoneVerifications Table:

Transactions Table:

NbsIpsLogs Table:

V2: ISO 20022 Message Support

V3: Linked Bank Accounts

V4: Transaction Idempotency

V5: KYC Sessions

V6: National ID Support

V7: AML Flags

V8: User Disclosure Acknowledgment

V9: Complaints System

Ports & Services

Service Port (Local) Description
PostgreSQL 5434 Drop Srbija database (separate from Drop Norway)
Redis 6380 Rate limiting and caching
Backend API 3003 Ktor HTTP server
Frontend 3000 Next.js dev server

Payment Flow Diagram

flowchart LR
    A[User Enters Phone + OTP] -->|POST /auth/verify-otp| B{OTP Valid?}
    B -->|Yes| C[JWT Issued]
    B -->|No| D[Increment Attempts]
    C --> E[User Initiates Payment]
    E -->|POST /v1/ips/initiate| F[Create Transaction Record]
    F --> G[Call NbsIpsService]
    G -->|ISO 20022 Message| H[NBS IPS API]
    H -->|ACCP/RJCT| I[Log Response to NbsIpsLogs]
    I -->|Async| J{Status?}
    J -->|ACCP| K[Update Transaction: completed]
    J -->|RJCT| L[Update Transaction: failed]
    K --> M[Recipient Receives RSD]
    L --> N[Notify User of Failure]

Branch Model

Feature branches are created off the previous feature branch, following a linear progression:

feat/drop-srbija-models (T1)
    ↓
feat/drop-srbija-otp (T2)
    ↓
feat/drop-srbija-jwt (T3)
    ↓
feat/drop-srbija-ips (T6)
    ↓
...
feat/drop-srbija-disclosure-complaints (T13)
    ↓
feat/drop-srbija-docs (T29 — this documentation)

Merge strategy: TBD — will be established when merging to main/production branch.

Key Architectural Decisions

  1. Bank Partner Adapter Pattern (Task 6): NBS IPS is ISO 20022 mTLS, not REST; single NBS direct integration impossible without bank license. Solution: Partner with licensed Serbian bank that provides IPS gateway access.

  2. Agent Model Year 1 → Own PI License Year 2: Start as registered agent under Serbian bank (Article 24 of Payment Services Law), then pursue own Payment Institution license once proven.

  3. Idempotency via SHA-256 Hash (Task 4): Store hash of idempotency key, not the raw key itself, to minimize PII surface area.

  4. Lock Threshold 5 vs Spec 6: Industry-standard practice is 5 failed OTP attempts before account lock, rather than 6 mentioned in some specs.

Security Considerations

  1. PII Encryption:

    • National ID stored encrypted (KMS key rotation)
    • Phone numbers indexed as hashes for lookups
    • Passwords NOT stored (auth via OTP only)
  2. OTP Security:

    • 6-digit, 10-minute expiry
    • Max 5 attempts per verification
    • Rate limit: 3 requests per minute per phone
    • SMS delivery via Twilio (TLS, no logging)
  3. JWT Validation:

    • HS256 with strong secret (64+ bytes)
    • Issuer: "dropsrbija-api", Audience: "dropsrbija"
    • httpOnly cookies (frontend sets automatic)
    • 24-hour expiry (refresh token TBD)
  4. NBS IPS Integration:

    • mTLS for NBS API calls
    • Request signing (HMAC-SHA256)
    • Audit log for all NBS transactions
    • Retry logic with exponential backoff
  5. Rate Limiting:

    • Global: 1000 req/min per IP
    • Auth: 10 OTP requests/hour per phone
    • Transaction: 50 transactions/hour per user
    • NBS API: Respect rate limits from NBS docs

Monitoring & Observability

Logs

Metrics

Alerts


Last Updated: 2026-04-16
Status: Architecture scaffold complete — ready for Phase 2 (Frontend Onboarding UI)


Revision #2
Created 2026-04-16 22:35:13 UTC by John
Updated 2026-05-31 20:06:04 UTC by John