Skip to content

Data Model: Pay-Ins

Status: Draft | Owner: Platform Team | Last Updated: 2026-04-03

Overview

Pay-In domain model for Simpaisa's mobile wallet and bank collection products across PK, BD, NP, and IQ. Handles 270M+ transactions annually via operator-specific OTP and redirect flows. Extends the canonical Payment entity with operator-specific fields.


Entity: PayInTransaction

Extends Payment from the canonical model.

Field Type Description
id string ULID (inherited)
paymentId string FK → Payment (canonical)
operatorId string Mobile operator / wallet provider ID
transactionType PayInTxType Named enum (see below)
msisdn string Subscriber mobile number (E.164)
otpStatus OTPStatus OTP verification state
simpaisaToken string Simpaisa-issued payment token
operatorToken string Token returned by operator
redirectUrl string Merchant return URL after auth
callbackPayload bytes Raw operator callback (encrypted)
channelConfig string FK → ChannelConfig
amount decimal Inherited, precision 4
currency Currency Inherited
status PayInStatus Product-specific lifecycle state

PayInTxType Enum

Code Name Description
0 PAYMENT Standard one-time payment
1 AUTHORISATION Pre-authorisation hold
8 SUBSCRIPTION Recurring subscription charge
9 REFUND Full or partial refund

Entity: OTPRequest

Field Type Description
id string ULID
payInId string FK → PayInTransaction
msisdn string Target mobile number
operatorId string Operator that sends the OTP
status OTPStatus PENDING, SENT, VERIFIED, EXPIRED, FAILED
attempts int Verification attempts (max 3)
requestedAt datetime OTP request timestamp
verifiedAt datetime Successful verification timestamp
expiresAt datetime OTP validity window end

Entity: PaymentToken

Field Type Description
id string ULID
payInId string FK → PayInTransaction
simpaisaToken string Simpaisa-generated token (opaque)
operatorToken string Operator's reference token
status TokenStatus ACTIVE, USED, EXPIRED, REVOKED
expiryDate datetime Token validity end
createdAt datetime Issuance timestamp
usedAt datetime Redemption timestamp (nullable)

Entity: RecurringSubscription

Field Type Description
id string ULID
merchantId string FK → Merchant
msisdn string Subscriber number
operatorId string Wallet provider
amount decimal Recurring charge amount
currency Currency Charge currency
frequency Frequency DAILY, WEEKLY, MONTHLY, QUARTERLY
status SubStatus ACTIVE, PAUSED, CANCELLED, EXPIRED
nextChargeAt datetime Next scheduled execution
startDate date Subscription commencement
endDate date Subscription termination (nullable)
retryPolicy string FK → retry config

Entity: ChannelConfig

Field Type Description
id string ULID
channelId string FK → Channel (canonical)
operatorId string Operator identifier
country string ISO 3166-1 alpha-2
apiBaseUrl string Operator API endpoint
authMethod AuthMethod HMAC, OAUTH2, BASIC, CERTIFICATE
timeout duration Request timeout
retryMax int Maximum retry attempts
rateLimitRps int Requests per second cap
otpRequired bool Whether OTP flow is mandatory
status ChannelStatus ACTIVE, DEGRADED, MAINTENANCE, DOWN

Pay-In State Machine

                    ┌──────────┐
                    │ CREATED  │
                    └────┬─────┘
                         │ initiate
                         ▼
                    ┌──────────┐
              ┌─────│ PENDING  │─────┐
              │     └────┬─────┘     │
              │          │           │ timeout
              │          │ OTP       │
              │          ▼           ▼
              │     ┌──────────┐  ┌─────────┐
              │     │OTP_SENT  │  │ EXPIRED │
              │     └────┬─────┘  └─────────┘
              │          │ verify
              │          ▼
              │     ┌───────────┐
              │     │AUTHORISED │
              │     └─────┬─────┘
              │           │ execute
              │           ▼
              │     ┌────────────┐
              │     │ PROCESSING │
              │     └──┬─────┬───┘
              │        │     │
              │   success    failure
              │        │     │
              │        ▼     ▼
              │  ┌──────────┐ ┌────────┐
              │  │COMPLETED │ │ FAILED │
              │  └────┬─────┘ └────────┘
              │       │ refund
              │       ▼
              │  ┌──────────┐
              └─▶│ REVERSED │
                 └──────────┘

Entity Relationships (ERD)

┌───────────┐1      N┌──────────────────┐
│  Merchant │────────│ PayInTransaction │
└───────────┘        └───────┬──────────┘
                         1│     │1     │1
                          │     │      │
                         N│    N│     0..1
                    ┌─────┴┐ ┌─┴──────┴──────┐
                    │OTPReq│ │ PaymentToken   │
                    └──────┘ └───────────────-┘
                                    │
┌───────────────────┐         0..1  │
│RecurringSubscript.│◄──────────────┘
└───────────────────┘
         │N
         │
         1
┌───────────────┐
│ ChannelConfig │
└───────────────┘

SurrealDB Table Mapping

DEFINE TABLE pay_in_transaction SCHEMAFULL;
DEFINE FIELD paymentId    ON pay_in_transaction TYPE record<payment>;
DEFINE FIELD operatorId   ON pay_in_transaction TYPE string;
DEFINE FIELD txType       ON pay_in_transaction TYPE string
  ASSERT $value IN ['PAYMENT','AUTHORISATION','SUBSCRIPTION','REFUND'];
DEFINE FIELD msisdn       ON pay_in_transaction TYPE string;
DEFINE FIELD otpStatus    ON pay_in_transaction TYPE string;
DEFINE FIELD status       ON pay_in_transaction TYPE string;
DEFINE FIELD amount       ON pay_in_transaction TYPE decimal;
DEFINE FIELD currency     ON pay_in_transaction TYPE string;
DEFINE FIELD createdAt    ON pay_in_transaction TYPE datetime DEFAULT time::now();
DEFINE FIELD updatedAt    ON pay_in_transaction TYPE datetime DEFAULT time::now();

DEFINE INDEX idx_payin_merchant ON pay_in_transaction FIELDS merchantId;
DEFINE INDEX idx_payin_msisdn   ON pay_in_transaction FIELDS msisdn;
DEFINE INDEX idx_payin_status   ON pay_in_transaction FIELDS status;
DEFINE INDEX idx_payin_created  ON pay_in_transaction FIELDS createdAt;

DEFINE TABLE otp_request SCHEMAFULL;
DEFINE FIELD payInId   ON otp_request TYPE record<pay_in_transaction>;
DEFINE FIELD msisdn    ON otp_request TYPE string;
DEFINE FIELD status    ON otp_request TYPE string;
DEFINE FIELD attempts  ON otp_request TYPE int DEFAULT 0;
DEFINE FIELD expiresAt ON otp_request TYPE datetime;

DEFINE TABLE payment_token SCHEMAFULL;
DEFINE FIELD payInId        ON payment_token TYPE record<pay_in_transaction>;
DEFINE FIELD simpaisaToken  ON payment_token TYPE string;
DEFINE FIELD operatorToken  ON payment_token TYPE string;
DEFINE FIELD status         ON payment_token TYPE string;
DEFINE FIELD expiryDate     ON payment_token TYPE datetime;

DEFINE TABLE recurring_subscription SCHEMAFULL;
DEFINE FIELD merchantId  ON recurring_subscription TYPE record<merchant>;
DEFINE FIELD msisdn      ON recurring_subscription TYPE string;
DEFINE FIELD amount      ON recurring_subscription TYPE decimal;
DEFINE FIELD frequency   ON recurring_subscription TYPE string;
DEFINE FIELD status      ON recurring_subscription TYPE string;
DEFINE FIELD nextChargeAt ON recurring_subscription TYPE datetime;

DEFINE TABLE channel_config SCHEMAFULL;
DEFINE FIELD channelId   ON channel_config TYPE record<channel>;
DEFINE FIELD operatorId  ON channel_config TYPE string;
DEFINE FIELD country     ON channel_config TYPE string;
DEFINE FIELD apiBaseUrl  ON channel_config TYPE string;
DEFINE FIELD authMethod  ON channel_config TYPE string;
DEFINE FIELD otpRequired ON channel_config TYPE bool DEFAULT true;
DEFINE FIELD status      ON channel_config TYPE string;