Skip to content

C4 Pay-Out Container & Component Diagrams

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

1. Overview

The Pay-Out system handles all outbound fund disbursements — single payouts, batch payouts, and scheduled settlements. It uses Temporal for durable workflow orchestration, ensuring reliable delivery even when downstream channels experience outages. Pay-Outs operate across Pakistan (1Link, RAAST, mobile wallets) and Bangladesh (bKash, BRAC, Faysal, Prime, Agrani).

2. Container Diagram

graph TB
    Merchant["🏪 Merchant<br/>(API Client)"]

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

    subgraph "Pay-Out Services"
        PayOutSvc["Pay-Out Service<br/>(Go)<br/>Disbursement orchestration"]
    end

    subgraph "Workflow Engine"
        Temporal["Temporal Server<br/>Durable workflows<br/>Retry, timeout, compensation"]
        TemporalWorker["Temporal Worker<br/>(Go)<br/>Executes payout activities"]
    end

    subgraph "Channel Adapters — Pakistan"
        OneLink_Adapter["1Link Adapter<br/>(ISO 8583)"]
        RAAST_Adapter["RAAST Adapter<br/>(ISO 20022)"]
        EP_PO_Adapter["Easypaisa Pay-Out Adapter"]
        JC_PO_Adapter["JazzCash Pay-Out Adapter"]
        HBL_PO_Adapter["HBL Konnect Pay-Out Adapter"]
    end

    subgraph "Channel Adapters — Bangladesh"
        bKash_PO["bKash Pay-Out Adapter"]
        BRAC_PO["BRAC Bank Adapter"]
        Faysal_PO["Faysal Bank Adapter"]
        Prime_PO["Prime Bank Adapter"]
        Agrani_PO["Agrani Bank Adapter"]
    end

    subgraph "Data Stores"
        SurrealDB["SurrealDB<br/>(Payouts, batches, ledger)"]
    end

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

    Merchant -- "HTTPS" --> KrakenD
    KrakenD -- "gRPC" --> PayOutSvc
    PayOutSvc -- "Start workflow" --> Temporal
    Temporal -- "Dispatch activities" --> TemporalWorker
    TemporalWorker -- "gRPC" --> OneLink_Adapter
    TemporalWorker -- "gRPC" --> RAAST_Adapter
    TemporalWorker -- "gRPC" --> EP_PO_Adapter
    TemporalWorker -- "gRPC" --> JC_PO_Adapter
    TemporalWorker -- "gRPC" --> HBL_PO_Adapter
    TemporalWorker -- "gRPC" --> bKash_PO
    TemporalWorker -- "gRPC" --> BRAC_PO
    TemporalWorker -- "gRPC" --> Faysal_PO
    TemporalWorker -- "gRPC" --> Prime_PO
    TemporalWorker -- "gRPC" --> Agrani_PO
    PayOutSvc --> SurrealDB
    TemporalWorker --> SurrealDB
    TemporalWorker -- "Publish" --> NSQ

3. Component Diagram — Pay-Out Service Internals

graph TB
    subgraph "Pay-Out Service (Go)"
        API["gRPC API Layer<br/>Request validation"]
        SinglePayout["Single Payout Handler<br/>Immediate disbursement"]
        BatchPayout["Batch Payout Handler<br/>CSV/API batch ingestion"]
        WorkflowStarter["Workflow Starter<br/>Temporal client"]
        BalanceChecker["Balance Checker<br/>Pre-flight sufficiency check"]
        LedgerWriter["Ledger Writer<br/>Double-entry bookkeeping"]
        EventPublisher["Event Publisher<br/>NSQ domain events"]
        PayoutRepository["Payout Repository<br/>SurrealDB persistence"]
    end

    API --> BalanceChecker
    BalanceChecker --> SinglePayout
    BalanceChecker --> BatchPayout
    SinglePayout --> WorkflowStarter
    BatchPayout --> WorkflowStarter
    WorkflowStarter --> |"Temporal SDK"| TemporalCluster["Temporal Server"]
    LedgerWriter --> PayoutRepository
    EventPublisher --> NSQCluster["NSQ"]

4. Temporal Workflow — Single Payout

sequenceDiagram
    participant M as Merchant
    participant K as KrakenD
    participant P as Pay-Out Service
    participant T as Temporal
    participant W as Temporal Worker
    participant A as Channel Adapter
    participant Ch as Channel
    participant DB as SurrealDB
    participant Q as NSQ

    M->>K: POST /v1/payouts {beneficiary, amount, channel}
    K->>P: gRPC CreatePayout
    P->>DB: Insert payout (status: initiated)
    P->>T: StartWorkflow(DisbursementWorkflow)
    P-->>K: 202 Accepted {payout_id}
    K-->>M: 202 Accepted

    T->>W: Execute ValidateBeneficiary activity
    W->>A: gRPC ValidateAccount
    A->>Ch: Account verification
    Ch-->>A: Valid
    A-->>W: Validated

    T->>W: Execute DebitMerchant activity
    W->>DB: Debit merchant ledger

    T->>W: Execute TransferFunds activity
    W->>A: gRPC Transfer
    A->>Ch: Fund transfer
    Ch-->>A: Transfer confirmed
    A-->>W: Success

    T->>W: Execute RecordSettlement activity
    W->>DB: Update payout (status: completed)
    W->>Q: Publish txn.payout.completed
    Q-->>M: Webhook notification

5. Temporal Workflow — Batch Payout

sequenceDiagram
    participant M as Merchant
    participant P as Pay-Out Service
    participant T as Temporal
    participant W as Temporal Worker
    participant DB as SurrealDB

    M->>P: POST /v1/payouts/batch {file or items[]}
    P->>DB: Insert batch record (status: processing)
    P->>T: StartWorkflow(BatchDisbursementWorkflow)

    loop For each payout item
        T->>W: Execute ValidateBeneficiary
        T->>W: Execute DebitMerchant
        T->>W: Execute TransferFunds
        Note over T,W: Failed items retry 3x<br/>then marked as failed
    end

    T->>W: Execute FinaliseBatch
    W->>DB: Update batch (status: completed, summary)

Batch limits: - Maximum 10,000 items per batch. - Items processed with concurrency of 50. - Failed items do not block the batch — they are retried independently.

6. Data Flows

6.1 Initiate

  1. Merchant submits payout request via REST API.
  2. KrakenD authenticates and forwards to Pay-Out Service.
  3. Service validates balance sufficiency, inserts record, starts Temporal workflow.
  4. Returns 202 Accepted immediately.

6.2 Process

  1. Temporal orchestrates: validate beneficiary → debit merchant → transfer funds.
  2. Each activity has configurable retries (default: 3 attempts, exponential back-off).
  3. If all retries exhausted, workflow enters compensation: credit merchant ledger, mark payout as failed.

6.3 Settle

  1. On successful transfer, ledger entries are finalised.
  2. End-of-day reconciliation (recon-svc) matches Simpaisa records against channel settlement files.
  3. Discrepancies trigger alerts via notification-svc.

7. Data Model — Key Entities

Entity Store Key Fields
Payout SurrealDB id, merchant_id, beneficiary, amount, currency, status
Batch SurrealDB id, merchant_id, total_items, completed, failed, status
LedgerEntry SurrealDB id, merchant_id, type (debit/credit), amount, payout_id

8. Non-Functional Requirements

Metric Target
Availability 99.95%
Single payout P99 < 30 s (channel dependent)
Batch throughput 200 items/min
Workflow durability Survives service restarts
Retry policy 3 attempts, exp back-off

9. Architectural Decision Records

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