Skip to content

C4 Pay-In Container & Component Diagrams

Field Value
Status Draft
Owner Pay-In Squad
Last Updated 2026-04-03
Applies To Pay-In product line

1. Overview

The Pay-In system handles all inbound payment collection for Simpaisa — single charges (synchronous and asynchronous), recurring subscriptions, and direct carrier billing. It processes the highest transaction volume across all product lines.

2. Container Diagram

graph TB
    Merchant["🏪 Merchant<br/>(API Client)"]
    Consumer["👤 Consumer<br/>(Mobile/Web)"]

    subgraph "Cloudflare Edge"
        CF_WAF["Cloudflare WAF"]
        CF_Workers["Cloudflare Workers<br/>(Rate limiting, geo-routing)"]
    end

    subgraph "API Gateway"
        KrakenD["KrakenD Gateway<br/>(Auth, throttling, routing)"]
    end

    subgraph "Pay-In Services"
        PayInSvc["Pay-In Service<br/>(Go)<br/>Charge orchestration"]
        OTPSvc["OTP Service<br/>(Go)<br/>OTP generation & validation"]
        TokenSvc["Token Service<br/>(Go)<br/>Payment token lifecycle"]
    end

    subgraph "Channel Adapters"
        EP_Adapter["Easypaisa Adapter"]
        JC_Adapter["JazzCash Adapter"]
        HBL_Adapter["HBL Konnect Adapter"]
        Alfa_Adapter["Alfa Adapter"]
        Zindagi_Adapter["Zindagi Adapter"]
        Telenor_Adapter["Telenor DCB Adapter"]
        Zong_Adapter["Zong DCB Adapter"]
        Ufone_Adapter["Ufone DCB Adapter"]
        bKash_Adapter["bKash Adapter"]
        PayMob_Adapter["PayMob Adapter"]
        AamarPay_Adapter["AamarPay Adapter"]
    end

    subgraph "Data Stores"
        SurrealDB["SurrealDB<br/>(Transactions, tokens)"]
        Redis["Redis<br/>(OTP cache, idempotency)"]
    end

    subgraph "Messaging"
        NSQ["NSQ<br/>(Event bus)"]
    end

    Merchant -- "HTTPS" --> CF_WAF
    Consumer -- "HTTPS (OTP/redirect)" --> CF_WAF
    CF_WAF --> CF_Workers
    CF_Workers --> KrakenD
    KrakenD -- "gRPC" --> PayInSvc
    PayInSvc -- "gRPC" --> OTPSvc
    PayInSvc -- "gRPC" --> TokenSvc
    PayInSvc -- "gRPC" --> EP_Adapter
    PayInSvc -- "gRPC" --> JC_Adapter
    PayInSvc -- "gRPC" --> HBL_Adapter
    PayInSvc -- "gRPC" --> Alfa_Adapter
    PayInSvc -- "gRPC" --> Zindagi_Adapter
    PayInSvc -- "gRPC" --> Telenor_Adapter
    PayInSvc -- "gRPC" --> Zong_Adapter
    PayInSvc -- "gRPC" --> Ufone_Adapter
    PayInSvc -- "gRPC" --> bKash_Adapter
    PayInSvc -- "gRPC" --> PayMob_Adapter
    PayInSvc -- "gRPC" --> AamarPay_Adapter
    PayInSvc --> SurrealDB
    OTPSvc --> Redis
    TokenSvc --> SurrealDB
    PayInSvc -- "Publish" --> NSQ

3. Component Diagram — Pay-In Service Internals

graph TB
    subgraph "Pay-In Service (Go)"
        API["gRPC API Layer<br/>Request validation, auth"]
        Orchestrator["Charge Orchestrator<br/>Flow selection, state machine"]
        ChannelRouter["Channel Router<br/>Selects adapter by channel code"]
        IdempotencyGuard["Idempotency Guard<br/>Redis-backed dedup"]
        RecurringEngine["Recurring Engine<br/>Subscription lifecycle"]
        CallbackHandler["Callback Handler<br/>Async channel responses"]
        EventPublisher["Event Publisher<br/>NSQ domain events"]
        TxnRepository["Transaction Repository<br/>SurrealDB persistence"]
    end

    API --> IdempotencyGuard
    IdempotencyGuard --> Orchestrator
    Orchestrator --> ChannelRouter
    Orchestrator --> RecurringEngine
    ChannelRouter --> |"gRPC to adapters"| ExternalAdapters["Channel Adapters"]
    CallbackHandler --> Orchestrator
    Orchestrator --> TxnRepository
    Orchestrator --> EventPublisher

4. Data Flows

4.1 Single Charge — Synchronous

This flow applies to channels that return a final status in real time (e.g. Easypaisa, JazzCash with PIN).

sequenceDiagram
    participant M as Merchant
    participant K as KrakenD
    participant P as Pay-In Service
    participant A as Channel Adapter
    participant Ch as Channel (e.g. Easypaisa)
    participant DB as SurrealDB
    participant Q as NSQ

    M->>K: POST /v1/charges {amount, channel, msisdn}
    K->>P: gRPC CreateCharge
    P->>DB: Insert txn (status: pending)
    P->>A: gRPC InitiateCharge
    A->>Ch: REST API call
    Ch-->>A: Success/Failure
    A-->>P: ChargeResult
    P->>DB: Update txn (status: completed/failed)
    P->>Q: Publish txn.payin.completed
    P-->>K: ChargeResponse
    K-->>M: 200 OK {txn_id, status}

4.2 Single Charge — Asynchronous

This flow applies to channels requiring consumer interaction (OTP, wallet redirect).

sequenceDiagram
    participant M as Merchant
    participant K as KrakenD
    participant P as Pay-In Service
    participant O as OTP Service
    participant C as Consumer
    participant A as Channel Adapter
    participant DB as SurrealDB
    participant Q as NSQ

    M->>K: POST /v1/charges {amount, channel, msisdn}
    K->>P: gRPC CreateCharge
    P->>DB: Insert txn (status: pending_auth)
    P->>O: gRPC SendOTP
    O->>C: SMS OTP
    P-->>K: 202 Accepted {txn_id, status: pending_auth}
    K-->>M: 202 Accepted

    C->>K: POST /v1/charges/{id}/confirm {otp}
    K->>P: gRPC ConfirmCharge
    P->>O: gRPC VerifyOTP
    O-->>P: OTP Valid
    P->>A: gRPC ExecuteCharge
    A-->>P: ChargeResult
    P->>DB: Update txn (status: completed)
    P->>Q: Publish txn.payin.completed
    P-->>K: ChargeResponse
    K-->>M: 200 OK {txn_id, status: completed}
    Q-->>M: Webhook notification

4.3 Recurring Charges

sequenceDiagram
    participant M as Merchant
    participant P as Pay-In Service
    participant T as Token Service
    participant A as Channel Adapter
    participant DB as SurrealDB

    Note over M,DB: Initial subscription setup
    M->>P: POST /v1/subscriptions {channel, msisdn, interval}
    P->>T: gRPC CreateToken (after consumer authorisation)
    T->>DB: Store payment token
    P->>DB: Insert subscription record

    Note over M,DB: Recurring charge (triggered by scheduler)
    P->>T: gRPC ResolveToken
    T-->>P: Channel credentials
    P->>A: gRPC ExecuteCharge (with token)
    A-->>P: ChargeResult
    P->>DB: Update subscription, insert txn

5. Data Model — Key Entities

Entity Store Key Fields
Charge SurrealDB id, merchant_id, channel, amount, currency, status
PaymentToken SurrealDB id, merchant_id, msisdn, channel, token_ref, expires
Subscription SurrealDB id, merchant_id, token_id, interval, next_charge_at
OTP Redis key (txn_id), otp_hash, attempts, ttl (5 min)
Idempotency Redis key (merchant_id:request_id), response, ttl (24 hr)

6. Non-Functional Requirements

Metric Target
Availability 99.95%
P50 latency (sync) < 800 ms
P99 latency (sync) < 3 s
Throughput 500 TPS peak
Idempotency window 24 hours

7. Architectural Decision Records

Changes to Pay-In architecture require an ADR in /Standards/ADR/.