Skip to content

Data Model: Cards

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

Overview

Card payment domain model for Simpaisa's multi-market payment gateway. Supports Visa, Mastercard, and local schemes across PK, BD, NP, and IQ. Handles the full auth→capture→refund lifecycle, 3D Secure authentication, PCI DSS-compliant tokenisation, and dispute management. Extends the canonical Payment entity.


Entity: CardPayment

Extends Payment from the canonical model.

Field Type Description PCI CDE
id string ULID (inherited) No
paymentId string FK → Payment (canonical) No
maskedPan string First 6 + last 4 (e.g. 411111****1111) No
cardBrand CardBrand VISA, MASTERCARD, LOCAL No
cardType CardType CREDIT, DEBIT, PREPAID No
authCode string Acquirer authorisation code No
threeDSStatus ThreeDSStatus 3DS authentication result No
threeDSVersion string 3DS protocol version (2.1, 2.2) No
eci string Electronic Commerce Indicator No
authAmount decimal Authorised amount No
capturedAmount decimal Total captured so far No
refundedAmount decimal Total refunded so far No
currency Currency ISO 4217 No
status CardPaymentStatus Lifecycle state No
acquirerId string Acquirer/processor ID No
acquirerRef string Acquirer's transaction reference No
merchantRef string Merchant order reference No
tokenId string FK → CardToken (nullable) No
captureMode CaptureMode AUTO, MANUAL No

Entity: CardAuthorisation

Field Type Description PCI CDE
id string ULID No
cardPaymentId string FK → CardPayment No
amount decimal Authorised amount No
currency Currency ISO 4217 No
responseCode string ISO 8583 response code No
authCode string Issuer authorisation code No
avsResult string Address Verification result No
cvvResult string CVV verification result No
declineReason string Human-readable decline reason No
authorisedAt datetime Authorisation timestamp No
expiresAt datetime Auth hold expiry (typically 7–30 days) No

Entity: CardCapture

Field Type Description
id string ULID
cardPaymentId string FK → CardPayment
authId string FK → CardAuthorisation
amount decimal Capture amount (≤ auth amount)
currency Currency ISO 4217
status CaptureStatus PENDING, COMPLETED, FAILED
capturedAt datetime Capture confirmation timestamp
settlementDate date Expected settlement date

Entity: CardRefund

Field Type Description
id string ULID
cardPaymentId string FK → CardPayment
captureId string FK → CardCapture
amount decimal Refund amount (≤ captured amount)
currency Currency ISO 4217
reason string Refund reason
status RefundStatus PENDING, COMPLETED, FAILED
acquirerRef string Acquirer refund reference
refundedAt datetime Refund confirmation timestamp

Entity: CardVoid

Field Type Description
id string ULID
cardPaymentId string FK → CardPayment
authId string FK → CardAuthorisation
reason string Void reason
status VoidStatus PENDING, COMPLETED, FAILED
voidedAt datetime Void confirmation timestamp

Entity: CardToken

Field Type Description PCI CDE
id string ULID No
merchantId string FK → Merchant (scoped) No
tokenisedPan string Opaque token replacing real PAN No
maskedPan string Display-safe masked PAN No
cardBrand CardBrand VISA, MASTERCARD, LOCAL No
expiryMonth int Card expiry month Yes
expiryYear int Card expiry year Yes
cardholderName string Name on card No
status TokenStatus ACTIVE, SUSPENDED, REVOKED, EXPIRED No
createdAt datetime Tokenisation timestamp No
lastUsedAt datetime Last transaction timestamp No

PCI DSS Note: The real PAN is stored ONLY in the isolated Card Data Environment (CDE) vault. tokenisedPan is a non-reversible reference. expiryMonth/expiryYear are CDE-adjacent — stored encrypted at rest with separate key management.

Entity: DisputeCase

Field Type Description
id string ULID
cardPaymentId string FK → CardPayment
merchantId string FK → Merchant
disputeType DisputeType CHARGEBACK, PRE_ARB, ARBITRATION
reasonCode string Scheme reason code
amount decimal Disputed amount
currency Currency ISO 4217
status DisputeStatus OPENED, MERCHANT_RESPONSE, WON, LOST, EXPIRED
evidence []string Evidence document references
deadline date Response deadline
openedAt datetime Dispute opened timestamp
resolvedAt datetime Resolution timestamp (nullable)

Card Payment State Machine (Auth → Capture → Refund)

┌──────────┐
│ CREATED  │
└────┬─────┘
     │ 3DS check
     ▼
┌────────────┐
│3DS_PENDING │
└──┬──────┬──┘
   │      │
 pass    fail
   │      │
   ▼      ▼
┌──────────┐  ┌──────────────┐
│AUTHORISED│  │3DS_FAILED    │
└────┬─────┘  └──────────────┘
     │
     ├────────────────┐
     │ (auto/manual)  │ void
     ▼                ▼
┌──────────┐    ┌────────┐
│ CAPTURED │    │ VOIDED │
└────┬─────┘    └────────┘
     │
     ├─────────────┐
     │ partial      │ full
     ▼              ▼
┌────────────────┐ ┌──────────┐
│PARTIAL_REFUNDED│ │ REFUNDED │
└────────────────┘ └──────────┘

Key Rules: - AUTHORISED → CAPTURED: Auto-capture or manual capture within auth window - AUTHORISED → VOIDED: Cancel before capture (releases hold) - CAPTURED → PARTIAL_REFUNDED: Refund < captured amount - CAPTURED → REFUNDED: Refund = captured amount - Auth hold expires after 7–30 days (scheme-dependent)


PCI DSS Field Classification

Field CDE Scope Storage Location Encryption
Full PAN In-scope CDE vault only AES-256-GCM
CVV/CVC Never stored
Expiry date In-scope CDE vault AES-256-GCM
Cardholder name Out-of-scope Application DB At-rest (TDE)
Masked PAN Out-of-scope Application DB At-rest (TDE)
Tokenised PAN Out-of-scope Application DB At-rest (TDE)
Auth code Out-of-scope Application DB At-rest (TDE)
3DS data Out-of-scope Application DB At-rest (TDE)

Entity Relationships (ERD)

┌───────────┐1      N┌─────────────┐1    N┌──────────────────┐
│  Merchant │────────│ CardPayment │──────│CardAuthorisation │
└───────────┘        └──────┬──────┘      └──────────────────┘
     │1                  │1    │1    │1
     │                   │     │     │
     N                  N│    N│    0..N
┌──────────┐      ┌─────┴┐ ┌─┴─────┐ ┌───────────┐
│CardToken │      │Capture│ │Refund │ │DisputeCase│
└──────────┘      └──────┘ └───────┘ └───────────┘
                              │
                           ┌──┴───┐
                           │ Void │
                           └──────┘

SurrealDB Table Mapping

DEFINE TABLE card_payment SCHEMAFULL;
DEFINE FIELD paymentId      ON card_payment TYPE record<payment>;
DEFINE FIELD maskedPan      ON card_payment TYPE string;
DEFINE FIELD cardBrand      ON card_payment TYPE string
  ASSERT $value IN ['VISA','MASTERCARD','LOCAL'];
DEFINE FIELD authCode       ON card_payment TYPE option<string>;
DEFINE FIELD threeDSStatus  ON card_payment TYPE string;
DEFINE FIELD authAmount     ON card_payment TYPE decimal;
DEFINE FIELD capturedAmount ON card_payment TYPE decimal DEFAULT 0;
DEFINE FIELD refundedAmount ON card_payment TYPE decimal DEFAULT 0;
DEFINE FIELD status         ON card_payment TYPE string
  ASSERT $value IN ['CREATED','3DS_PENDING','3DS_FAILED','AUTHORISED','CAPTURED','VOIDED','PARTIAL_REFUNDED','REFUNDED'];
DEFINE FIELD tokenId        ON card_payment TYPE option<record<card_token>>;
DEFINE FIELD createdAt      ON card_payment TYPE datetime DEFAULT time::now();

DEFINE INDEX idx_card_merchant ON card_payment FIELDS merchantId;
DEFINE INDEX idx_card_status   ON card_payment FIELDS status;
DEFINE INDEX idx_card_brand    ON card_payment FIELDS cardBrand;

DEFINE TABLE card_authorisation SCHEMAFULL;
DEFINE FIELD cardPaymentId ON card_authorisation TYPE record<card_payment>;
DEFINE FIELD amount        ON card_authorisation TYPE decimal;
DEFINE FIELD responseCode  ON card_authorisation TYPE string;
DEFINE FIELD authCode      ON card_authorisation TYPE string;
DEFINE FIELD authorisedAt  ON card_authorisation TYPE datetime;
DEFINE FIELD expiresAt     ON card_authorisation TYPE datetime;

DEFINE TABLE card_capture SCHEMAFULL;
DEFINE FIELD cardPaymentId ON card_capture TYPE record<card_payment>;
DEFINE FIELD authId        ON card_capture TYPE record<card_authorisation>;
DEFINE FIELD amount        ON card_capture TYPE decimal;
DEFINE FIELD status        ON card_capture TYPE string;
DEFINE FIELD capturedAt    ON card_capture TYPE option<datetime>;

DEFINE TABLE card_refund SCHEMAFULL;
DEFINE FIELD cardPaymentId ON card_refund TYPE record<card_payment>;
DEFINE FIELD captureId     ON card_refund TYPE record<card_capture>;
DEFINE FIELD amount        ON card_refund TYPE decimal;
DEFINE FIELD reason        ON card_refund TYPE string;
DEFINE FIELD status        ON card_refund TYPE string;
DEFINE FIELD refundedAt    ON card_refund TYPE option<datetime>;

DEFINE TABLE card_void SCHEMAFULL;
DEFINE FIELD cardPaymentId ON card_void TYPE record<card_payment>;
DEFINE FIELD authId        ON card_void TYPE record<card_authorisation>;
DEFINE FIELD reason        ON card_void TYPE string;
DEFINE FIELD status        ON card_void TYPE string;

-- CardToken: stored in CDE-segmented SurrealDB instance
DEFINE TABLE card_token SCHEMAFULL;
DEFINE FIELD merchantId    ON card_token TYPE record<merchant>;
DEFINE FIELD tokenisedPan  ON card_token TYPE string;
DEFINE FIELD maskedPan     ON card_token TYPE string;
DEFINE FIELD cardBrand     ON card_token TYPE string;
DEFINE FIELD status        ON card_token TYPE string
  ASSERT $value IN ['ACTIVE','SUSPENDED','REVOKED','EXPIRED'];
DEFINE FIELD createdAt     ON card_token TYPE datetime DEFAULT time::now();
-- expiryMonth/expiryYear encrypted at application layer before storage

DEFINE TABLE dispute_case SCHEMAFULL;
DEFINE FIELD cardPaymentId ON dispute_case TYPE record<card_payment>;
DEFINE FIELD merchantId    ON dispute_case TYPE record<merchant>;
DEFINE FIELD disputeType   ON dispute_case TYPE string;
DEFINE FIELD reasonCode    ON dispute_case TYPE string;
DEFINE FIELD amount        ON dispute_case TYPE decimal;
DEFINE FIELD status        ON dispute_case TYPE string;
DEFINE FIELD deadline      ON dispute_case TYPE datetime;
DEFINE FIELD openedAt      ON dispute_case TYPE datetime;