Case Study: Design a Payment System

“Payment system giống như ngân hàng trung ương xử lý hàng triệu giao dịch mỗi ngày — mỗi đồng tiền phải chính xác tuyệt đối. Sai 1 cent trên 1 triệu giao dịch = sai 10,000 đồng. Nhân lên 365 ngày, kết quả là thảm họa tài chính.”

Tags: system-design payment-system fintech exactly-once ledger alex-xu-vol2 case-study Student: Hieu Prerequisite: Tuan-02-Back-of-the-envelope · Tuan-08-Message-Queue · Tuan-11-Microservices-Pattern Lien quan: Tuan-14-AuthN-AuthZ-Security · Tuan-15-Data-Security-Encryption · Tuan-11-Microservices-Pattern · Tuan-07-Database-Sharding-Replication Reference: Alex Xu, System Design Interview Volume 2 — Chapter 7: Payment System


Context & Why — Tại sao Payment System quan trọng?

Analogy: Ngân hàng trung ương

Hieu, hãy tưởng tượng em đang vận hành ngân hàng trung ương của một quốc gia. Mỗi ngày có hàng triệu giao dịch diễn ra — từ việc mua cà phê 30,000 VND đến chuyển khoản 500 triệu VND mua nhà. Mỗi giao dịch phải:

  • Chính xác tuyệt đối — sai 1 đồng cũng không được
  • Không bị mất — tiền đã trừ mà hàng không giao = thảm họa
  • Không bị trùng — charge khách 2 lần = mất uy tín + kiện tụng
  • Có thể kiểm tra lại — auditor hỏi giao dịch cách đây 3 năm, em phải trả lời được
  • Tuân thủ pháp luật — vi phạm PCI-DSS hoặc AML = phạt hàng triệu đô + mất license

Tại sao Backend Dev cần hiểu Payment System?

Lý doGiải thích
Mọi sản phẩm đều cần paymentE-commerce, SaaS, marketplace, subscription — tất cả cần thu tiền
Sai sót = mất tiền thậtKhác với chat system (mất tin nhắn = khó chịu), payment (mất tiền = kiện tụng)
Complexity ẩn giấuTrông đơn giản (gửi tiền từ A → B), nhưng thực tế có hàng chục edge case
Interview favoritePayment system là bài yêu thích của Big Tech vì đòi hỏi hiểu sâu về distributed systems
Regulation-heavyPhải hiểu PCI-DSS, AML, KYC — không phải chỉ engineering

Payment System trong thực tế

Các hệ thống payment nổi tiếng: Stripe, PayPal, Adyen, Square, Shopify Payments, VNPay, MoMo, ZaloPay.

Mỗi ngày:

  • Stripe xử lý hàng trăm tỷ USD giao dịch
  • PayPal xử lý ~40 triệu transactions/day
  • Visa network xử lý ~150 triệu transactions/day

Key insight: Payment system không chỉ là “chuyển tiền”. Nó là sự kết hợp của distributed systems, database consistency, security, compliance, và business logic — tất cả phải hoạt động chính xác 100%.


Step 1 — Understand the Problem & Establish Design Scope

1.1 Clarifying Questions (Câu hỏi làm rõ)

Trong interview, luôn hỏi trước khi thiết kế. Dưới đây là các câu hỏi quan trọng và câu trả lời giả định:

Câu hỏiTrả lờiGhi chú
Pay-in (nhận tiền) hay pay-out (gửi tiền)?Cả haiPay-in: buyer trả merchant. Pay-out: platform trả merchant
Payment methods nào?Credit/debit card, digital walletQua PSP (Stripe, PayPal)
Scale bao lớn?1M transactions/dayQuy mô Shopify-like
Cần support multi-currency?USD, EUR, VND, v.v.
Exactly-once semantics?Bắt buộcKhông được charge 2 lần
Reconciliation?Đối soát hàng ngày
Refund support?Partial và full refund
Wallet service?Lưu số dư merchant
Compliance requirements?PCI-DSS Level 1Vì xử lý card data

1.2 Functional Requirements (Yêu cầu chức năng)

  • FR1: Pay-in flow — Buyer thanh toán cho merchant qua nhiều payment methods (credit card, debit card, digital wallet)
  • FR2: Pay-out flow — Platform chuyển tiền cho merchant (settlement)
  • FR3: Ledger — Ghi nhận mọi giao dịch theo chuẩn double-entry bookkeeping
  • FR4: Wallet — Quản lý số dư tài khoản của merchant trên platform
  • FR5: Reconciliation — Đối soát giữa internal ledger, PSP settlement, và bank statement
  • FR6: Refund — Hoàn tiền cho buyer (partial hoặc full)
  • FR7: Multi-currency — Xử lý giao dịch đa tiền tệ với exchange rate
  • FR8: Payment status tracking — Buyer và merchant xem được trạng thái giao dịch real-time

1.3 Non-functional Requirements (Yêu cầu phi chức năng)

Yêu cầuMục tiêuGiải thích
Reliability (Độ tin cậy)99.99% — không mất giao dịchMất 1 giao dịch = mất tiền thật
Exactly-once (Chính xác một lần)Không duplicate paymentCharge khách 2 lần = thảm họa
Consistency (Nhất quán)Strong consistency cho balanceSố dư phải luôn chính xác
Availability (Sẵn sàng)99.99% uptime (~52 phút downtime/năm)Payment down = revenue down
Auditability (Kiểm tra được)Mọi giao dịch có audit trailCompliance requirement
SecurityPCI-DSS Level 1Bảo vệ card data
LatencyP99 < 1s cho payment requestUser experience

Trade-off quan trọng: Trong payment system, Consistency > Availability. Đây là hệ thống CP (theo CAP theorem). Tham chiếu Tuan-07-Database-Sharding-Replication để hiểu CP vs AP.

1.4 Phân biệt Pay-in vs Pay-out

Đặc điểmPay-in (Thu tiền)Pay-out (Chi tiền)
Hướng tiềnBuyer → Merchant (qua platform)Platform → Merchant (settlement)
TriggerBuyer click “Pay”Scheduled (daily/weekly) hoặc manual
SpeedReal-time (vài giây)Batch (1-3 ngày làm việc)
VolumeCao (mỗi đơn hàng)Thấp hơn (aggregate nhiều đơn)
RiskFraud, chargebackInsufficient fund, wrong account
Ví dụBạn mua áo trên Shopee, trả 500KShopee chuyển 450K cho seller cuối tuần

Step 2 — High-Level Design

2.1 System Components Overview

Payment system bao gồm các component chính:

ComponentVai tròAnalogy
Payment ServiceOrchestrator — điều phối toàn bộ payment flowGiám đốc ngân hàng
PSP (Payment Service Provider)Xử lý thanh toán với card network/bankVisa/Mastercard processing center
Ledger ServiceGhi nhận mọi giao dịch (double-entry)Sổ cái kế toán
Wallet ServiceQuản lý số dư tài khoảnVí tiền merchant
Reconciliation ServiceĐối soát dữ liệu giữa các hệ thốngKiểm toán viên
Fraud Detection ServicePhát hiện giao dịch gian lậnCảnh sát tài chính

2.2 High-Level Architecture

flowchart TB
    subgraph "Client Layer"
        BUYER[Buyer<br/>Web / Mobile App]
        MERCHANT[Merchant<br/>Dashboard]
    end

    subgraph "API Gateway"
        GW[API Gateway<br/>Rate Limiting, Auth, Routing]
    end

    subgraph "Core Payment Services"
        PS[Payment Service<br/>Orchestrator]
        LEDGER[Ledger Service<br/>Double-entry Bookkeeping]
        WALLET[Wallet Service<br/>Balance Management]
    end

    subgraph "External"
        PSP[PSP<br/>Stripe / PayPal / Adyen]
        CARD[Card Network<br/>Visa / Mastercard]
        BANK[Issuing Bank<br/>Buyer's Bank]
        MBANK[Acquiring Bank<br/>Merchant's Bank]
    end

    subgraph "Supporting Services"
        RECON[Reconciliation Service<br/>Daily/Hourly Batch]
        FRAUD[Fraud Detection<br/>Rule Engine + ML]
        NOTIFY[Notification Service<br/>Email / SMS / Push]
        CURRENCY[Currency Service<br/>Exchange Rate]
    end

    subgraph "Data Stores"
        PGDB[(PostgreSQL<br/>Payment Records)]
        LEDGERDB[(Ledger DB<br/>Immutable Append-only)]
        WALLETDB[(Wallet DB<br/>Balance Store)]
        MQ[Message Queue<br/>Kafka]
    end

    BUYER --> GW
    MERCHANT --> GW
    GW --> PS
    PS --> PSP
    PSP --> CARD
    CARD --> BANK
    CARD --> MBANK
    PS --> LEDGER
    PS --> WALLET
    PS --> FRAUD
    PS --> CURRENCY
    LEDGER --> LEDGERDB
    WALLET --> WALLETDB
    PS --> PGDB
    PS --> MQ
    MQ --> RECON
    MQ --> NOTIFY
    RECON --> LEDGERDB

2.3 Pay-in Flow Overview

Khi buyer click “Pay” trên website:

1. Buyer → API Gateway → Payment Service: "Tôi muốn trả 500,000 VND cho đơn hàng #12345"
2. Payment Service → Fraud Detection: "Giao dịch này có đáng ngờ không?"
3. Fraud Detection → Payment Service: "OK, hợp lệ"
4. Payment Service → PSP (Stripe): "Charge card **** 4242, số tiền 500,000 VND"
5. PSP → Card Network (Visa) → Issuing Bank: "Authorize 500,000 VND"
6. Bank → Card Network → PSP → Payment Service: "Approved"
7. Payment Service → Ledger: "Ghi nhận: Debit buyer 500K, Credit merchant 500K"
8. Payment Service → Wallet: "Cộng 500K vào wallet merchant"
9. Payment Service → Notification: "Gửi email xác nhận cho buyer & merchant"
10. Payment Service → Buyer: "Thanh toán thành công!"

2.4 Pay-out Flow Overview

Khi platform chuyển tiền cho merchant:

1. Scheduler trigger (mỗi ngày 00:00 UTC)
2. Pay-out Service: Tổng hợp tất cả giao dịch chưa settle cho merchant X
3. Pay-out Service: Trừ platform fee (ví dụ: 2.9% + $0.30 per transaction)
4. Pay-out Service → PSP: "Transfer 45,000,000 VND cho merchant X, bank account YYY"
5. PSP → Acquiring Bank → Merchant's Bank Account
6. Ledger: Ghi nhận settlement entry
7. Wallet: Trừ số dư merchant wallet
8. Notification: "Settlement 45,000,000 VND đã được chuyển"

Step 3 — Deep Dive

3.1 Payment Flow End-to-End (Chi tiết)

Flow diagram

sequenceDiagram
    participant B as Buyer
    participant FE as Frontend
    participant PS as Payment Service
    participant FD as Fraud Detection
    participant PSP as PSP (Stripe)
    participant CN as Card Network (Visa)
    participant IB as Issuing Bank
    participant L as Ledger
    participant W as Wallet
    participant N as Notification

    B->>FE: Click "Pay 500K VND"
    FE->>PS: POST /payments {order_id, amount, currency, method}

    Note over PS: Generate idempotency_key<br/>Save payment record (PENDING)

    PS->>FD: Check fraud risk
    FD-->>PS: Risk score: LOW (pass)

    PS->>PSP: Create charge (with idempotency_key)

    Note over PSP: Redirect to hosted payment page<br/>Buyer enters card details

    PSP->>CN: Authorization request
    CN->>IB: Authorize 500K VND
    IB-->>CN: Approved (auth_code: ABC123)
    CN-->>PSP: Approved

    PSP-->>PS: Webhook: payment.success {charge_id, auth_code}

    Note over PS: Update payment status → SUCCESS

    PS->>L: Record double-entry
    Note over L: Debit: buyer_payable 500K<br/>Credit: merchant_receivable 500K

    PS->>W: Credit merchant wallet +500K
    PS->>N: Send confirmation
    N-->>B: Email: "Payment successful"

Các giai đoạn của Payment Flow

Giai đoạnMô tảThời gianFailure mode
1. InitiationBuyer submit payment requestInstantValidation error
2. Fraud CheckKiểm tra gian lận50-200msFalse positive (block legit payment)
3. AuthorizationPSP gửi auth request qua card network tới issuing bank1-3sCard declined, insufficient funds
4. CaptureThực hiện charge (có thể tách khỏi auth)0-24hCapture expired
5. RecordingGhi ledger + update wallet50-100msDB write failure
6. NotificationThông báo cho buyer/merchantAsyncNotification lost (non-critical)
7. SettlementPSP chuyển tiền thực tế cho merchant1-3 business daysBank transfer failure

Authorization vs Capture: Trong nhiều use case (hotel booking, car rental), authorize trước nhưng capture sau. Authorization “giữ” tiền trong tài khoản buyer nhưng chưa thực sự charge. Capture mới thực sự rút tiền.

3.2 PSP Integration — Tích hợp Payment Service Provider

Tại sao dùng PSP thay vì tự build?

Tự buildDùng PSP (Stripe/PayPal)
Phải tuân thủ PCI-DSS Level 1 (tốn 2M/năm để audit)PSP chịu phần lớn PCI-DSS compliance
Phải tích hợp trực tiếp với từng card network (Visa, Mastercard, AMEX, JCB)PSP đã tích hợp sẵn 100+ payment methods
Phải xử lý fraud detection, chargeback, disputePSP cung cấp fraud tools built-in
Cần team 50+ engineers chuyên paymentTích hợp API trong vài tuần
Risk cực cao — sai sót = mất licensePSP chịu liability

Verdict: Trừ khi em là Stripe hay PayPal, luôn dùng PSP. Ngay cả Shopify — một trong những e-commerce platform lớn nhất — cũng dùng Stripe làm PSP.

Hosted Payment Page

PSP cung cấp hosted payment page — trang thanh toán do PSP host:

1. Buyer click "Pay" trên website merchant
2. Frontend redirect đến Stripe Checkout page (hoặc embed Stripe Elements)
3. Buyer nhập card number, expiry, CVV trên trang của Stripe
4. Stripe xử lý payment, trả kết quả về cho merchant qua webhook

Lợi ích của hosted payment page:

Lợi íchGiải thích
PCI-DSS scope reductionCard data KHÔNG BAO GIỜ đi qua server của merchant
TrustBuyer thấy trang Stripe/PayPal → tin tưởng hơn
Auto-updateStripe tự cập nhật 3D Secure, new card networks
Liability shiftNếu fraud xảy ra, PSP chịu liability (trong nhiều trường hợp)

Tokenization

Khi buyer nhập card number lần đầu, PSP tokenize card:

Card: 4242 4242 4242 4242
      ↓ (PSP tokenization)
Token: tok_1MqZ8bCjKG4sJv2D
Khái niệmGiải thích
TokenChuỗi random đại diện cho card, không thể reverse-engineer về card number
VaultPSP lưu card data trong encrypted vault (PCI-DSS certified)
ReuseMerchant lưu token để charge lần sau (subscription, 1-click buy)
ScopeToken chỉ valid cho merchant đã tạo nó

Rule vàng: KHÔNG BAO GIỜ lưu card number trên server của em. Chỉ lưu token từ PSP. Vi phạm rule này = vi phạm PCI-DSS = phạt hàng triệu đô + mất quyền xử lý card.

Webhook Pattern

PSP thông báo kết quả payment qua webhook (HTTP POST callback):

PSP → Merchant server: POST /webhooks/stripe
{
  "type": "payment_intent.succeeded",
  "data": {
    "id": "pi_3MqBv2CjKG4sJv2D",
    "amount": 500000,
    "currency": "vnd",
    "status": "succeeded"
  }
}

Xử lý webhook đúng cách:

Best PracticeLý do
Verify signatureĐảm bảo webhook thực sự từ PSP, không phải attacker
Idempotent handlingPSP có thể gửi cùng webhook nhiều lần (retry)
Return 200 nhanhXử lý async, return 200 ngay để PSP không retry
Persist before processLưu webhook event vào DB trước khi xử lý business logic
Dead letter queueWebhook processing fail → đẩy vào DLQ để retry sau

3.3 Double-Entry Ledger — Sổ cái kép

Tại sao cần Double-Entry?

Double-entry bookkeeping là nguyên tắc kế toán có từ thế kỷ 15 (Luca Pacioli, 1494). Mỗi giao dịch phải có ít nhất 2 entries: một debit và một credit, với tổng bằng nhau.

Accounting equation (phương trình kế toán cơ bản):

Aha Moment: Phương trình này LUÔN phải cân bằng. Nếu bất kỳ lúc nào Assets khác Liabilities + Equity, nghĩa là có bug hoặc fraud. Đây chính là cơ chế “self-checking” của hệ thống kế toán.

Double-Entry trong Payment System

Khi buyer trả 500,000 VND cho merchant:

EntryAccountTypeAmount
1Buyer Cash (Asset)Debit (tăng)500,000 VND
2Platform Revenue (Liability)Credit (tăng)500,000 VND

Khi platform settle cho merchant (trừ 2% fee = 10,000 VND):

EntryAccountTypeAmount
3Platform Revenue (Liability)Debit (giảm)500,000 VND
4Merchant Payout (Asset)Credit (tăng)490,000 VND
5Platform Fee Income (Equity/Revenue)Credit (tăng)10,000 VND

Kiểm tra: Tổng Debit = 500,000 + 500,000 = 1,000,000. Tổng Credit = 500,000 + 490,000 + 10,000 = 1,000,000. Cân bằng!

Ledger Data Model

FieldTypeDescription
ledger_entry_idUUIDPrimary key
transaction_idUUIDNhóm các entries cùng 1 giao dịch
account_idUUIDTài khoản bị ảnh hưởng
entry_typeENUMDEBIT hoặc CREDIT
amountBIGINTSố tiền (đơn vị nhỏ nhất — cents/đồng)
currencyVARCHAR(3)ISO 4217 (VND, USD, EUR)
created_atTIMESTAMPThời điểm tạo
descriptionTEXTMô tả giao dịch
idempotency_keyVARCHARChống duplicate entry

Đặc điểm quan trọng của Ledger:

Đặc điểmGiải thích
Append-onlyKHÔNG BAO GIỜ update hay delete entry. Sai → tạo reversal entry
ImmutableMỗi entry là bất biến sau khi ghi
CompleteMọi giao dịch đều phải có ledger entry
BalancedTổng debit = tổng credit cho mỗi transaction
AuditableCó thể trace lại mọi thay đổi balance

Tại sao append-only? Nếu cho phép update/delete, không thể audit. Khi phát hiện sai sót, tạo reversal entry (giao dịch đảo ngược) thay vì sửa entry cũ. Đây là nguyên tắc bất biến trong kế toán.

Double-Entry Ledger Flow

flowchart LR
    subgraph "Payment Event"
        PAY[Buyer pays 500K VND]
    end

    subgraph "Ledger Entries"
        D1[DEBIT<br/>Buyer Cash Account<br/>+500,000 VND]
        C1[CREDIT<br/>Platform Holding<br/>+500,000 VND]
    end

    subgraph "Validation"
        CHECK{Sum Debit<br/>= Sum Credit?}
    end

    subgraph "Result"
        OK[Entry Committed]
        FAIL[Entry Rejected<br/>Alert Triggered]
    end

    PAY --> D1
    PAY --> C1
    D1 --> CHECK
    C1 --> CHECK
    CHECK -->|Yes| OK
    CHECK -->|No| FAIL

    style D1 fill:#e53935,color:#fff
    style C1 fill:#43a047,color:#fff
    style OK fill:#1e88e5,color:#fff
    style FAIL fill:#ff6f00,color:#fff

3.4 Exactly-Once Delivery — Ngữ nghĩa chính xác một lần

Tại sao Exactly-Once là vấn đề khó nhất?

Trong distributed systems, có 3 mức delivery guarantee:

GuaranteeÝ nghĩaRủi ro
At-most-onceGửi 1 lần, không retryMất payment → buyer trả tiền nhưng không nhận hàng
At-least-onceRetry nếu không nhận responseDuplicate payment → buyer bị charge 2 lần
Exactly-onceĐảm bảo xử lý đúng 1 lầnKhông có rủi ro — nhưng KHÓ NHẤT để implement

Sự thật phũ phàng: Trong distributed systems, exactly-once delivery là bất khả thi (theo lý thuyết). Nhưng chúng ta có thể đạt exactly-once processing bằng cách kết hợp at-least-once delivery + idempotency.

Idempotency Key Pattern

Idempotency (tính lũy đẳng): thực hiện cùng 1 operation nhiều lần cho cùng kết quả như thực hiện 1 lần.

Request 1: POST /payments {idempotency_key: "abc123", amount: 500000}
→ Payment created, return payment_id: "pay_001"

Request 2: POST /payments {idempotency_key: "abc123", amount: 500000}
→ Return existing payment_id: "pay_001" (KHÔNG tạo payment mới)

Request 3: POST /payments {idempotency_key: "abc123", amount: 500000}
→ Return existing payment_id: "pay_001" (vẫn idempotent)

Implementation:

BướcHành động
1Client generate idempotency_key (UUID v4)
2Server nhận request, check DB: SELECT * FROM payments WHERE idempotency_key = ?
3aNếu không tồn tại → tạo payment mới, lưu idempotency_key
3bNếu đã tồn tại → trả về kết quả cũ (cached response)
4Response trả về cho client

Xử lý race condition: Hai request cùng idempotency_key đến cùng lúc?

ApproachCách hoạt độngTrade-off
DB unique constraintUNIQUE INDEX ON idempotency_key → request thứ 2 bị constraint violationĐơn giản, nhưng cần handle error
Distributed lockAcquire lock trên idempotency_key trước khi xử lýAn toàn hơn, nhưng tốn performance
Optimistic lockingCheck + insert with version, retry nếu conflictTốt cho low-contention

Best practice từ Stripe: Stripe require client gửi Idempotency-Key header cho mọi POST request. Key valid trong 24 giờ. Nếu retry với cùng key nhưng body khác → return 422 error.

Retry with Idempotency

Khi network timeout xảy ra, client không biết payment thành công hay thất bại:

Client → Server: POST /payments {idempotency_key: "abc123"}
Server: Xử lý xong, gửi response
Network: *timeout* — response bị mất
Client: Không nhận được response → RETRY
Client → Server: POST /payments {idempotency_key: "abc123"}
Server: Check DB → đã xử lý → trả về kết quả cũ
Client: Nhận response → OK!

Retry strategy:

StrategyMô tảKhi nào dùng
Exponential backoffWait 1s, 2s, 4s, 8s, 16s…Default cho mọi retry
JitterThêm random delay để tránh thundering herdLuôn kết hợp với exponential backoff
Max retriesGiới hạn số lần retry (ví dụ: 5 lần)Tránh retry vô hạn
Circuit breakerNgừng retry nếu service liên tục failBảo vệ downstream service

Aha Moment: Idempotency key là THE most important concept trong payment system. Nếu em chỉ nhớ được 1 thứ từ bài này, hãy nhớ idempotency. Mọi thứ khác đều xây dựng trên nền tảng này.

Payment State Machine

Payment KHÔNG phải là một function call đơn giản (gọi → xong). Payment là state machine — một chuỗi trạng thái có chuyển đổi rõ ràng:

stateDiagram-v2
    [*] --> CREATED: Buyer submits payment

    CREATED --> FRAUD_CHECK: Validate & check fraud
    FRAUD_CHECK --> REJECTED: Fraud detected
    FRAUD_CHECK --> PENDING: Fraud check passed

    PENDING --> PROCESSING: Send to PSP
    PROCESSING --> AUTHORIZED: Bank approved
    PROCESSING --> FAILED: Bank declined

    AUTHORIZED --> CAPTURED: Capture payment
    AUTHORIZED --> VOIDED: Cancel before capture
    AUTHORIZED --> EXPIRED: Auth expired (7-30 days)

    CAPTURED --> SETTLED: PSP settled funds
    CAPTURED --> REFUND_PENDING: Refund requested

    REFUND_PENDING --> REFUNDED: Refund completed
    REFUND_PENDING --> REFUND_FAILED: Refund failed

    REFUND_FAILED --> REFUND_PENDING: Retry refund
    FAILED --> [*]
    REJECTED --> [*]
    SETTLED --> [*]
    REFUNDED --> [*]
    VOIDED --> [*]
    EXPIRED --> [*]

Các trạng thái quan trọng:

StateÝ nghĩaAction
CREATEDPayment request vừa được tạoValidate input, generate idempotency key
FRAUD_CHECKĐang kiểm tra gian lậnRule engine + ML model
PENDINGChờ gửi đến PSPEnqueue for processing
PROCESSINGĐang xử lý tại PSP/BankWaiting for async response
AUTHORIZEDBank đã approve, tiền bị “hold”Chưa charge thực sự
CAPTUREDTiền đã bị charge thực sựLedger entry created
SETTLEDPSP đã chuyển tiền cho merchantSettlement complete
FAILEDPayment thất bạiNotify buyer, log reason
REFUNDEDĐã hoàn tiềnReversal ledger entry created
VOIDEDHủy trước khi captureRelease hold on buyer’s card

Tại sao state machine quan trọng? Vì payment có thể ở trạng thái “in-limbo” (PROCESSING) trong vài giây đến vài phút. Nếu không có state machine, em không biết payment đang ở đâu → không biết nên retry hay chờ → dẫn đến duplicate charge hoặc lost payment.

Allowed state transitions: Chỉ các transition được define trong state machine mới hợp lệ. Ví dụ:

  • CREATED → CAPTURED: INVALID (không thể skip fraud check & authorization)
  • SETTLED → PROCESSING: INVALID (không thể quay lại trạng thái trước)
  • CAPTURED → REFUND_PENDING: VALID

3.5 Reconciliation — Đối soát

Tại sao cần Reconciliation?

Trong hệ thống payment, có 3 nguồn sự thật (sources of truth):

SourceDữ liệuAi quản lý
Internal LedgerMọi giao dịch platform ghi nhậnPlatform (chúng ta)
PSP Settlement ReportGiao dịch PSP đã xử lýStripe/PayPal
Bank StatementTiền thực tế vào/ra tài khoản ngân hàngBank

Ba nguồn này phải khớp nhau. Nếu không khớp → có vấn đề (bug, fraud, hoặc timing difference).

Analogy: Giống như em kiểm tra sổ chi tiêu cá nhân (internal ledger) với lịch sử giao dịch trên app ngân hàng (bank statement) mỗi cuối tháng. Nếu có giao dịch em không nhận ra → có thể bị hack thẻ.

Reconciliation Pipeline

flowchart TB
    subgraph "Data Sources"
        IL[Internal Ledger<br/>Real-time]
        PSP_R[PSP Settlement Report<br/>Daily file T+1]
        BANK_S[Bank Statement<br/>Daily/Hourly via API]
    end

    subgraph "Reconciliation Engine"
        FETCH[Fetch & Normalize<br/>Convert to common format]
        MATCH[Matching Engine<br/>Match by transaction_id, amount, date]
        DIFF[Discrepancy Detection<br/>Find unmatched records]
    end

    subgraph "Results"
        MATCHED[Matched Records<br/>Everything OK]
        MISMATCH[Mismatched Records<br/>Amount differs]
        MISSING_INT[Missing Internal<br/>PSP has, we don't]
        MISSING_EXT[Missing External<br/>We have, PSP doesn't]
    end

    subgraph "Actions"
        AUTO[Auto-resolve<br/>Timing difference]
        ALERT[Alert Team<br/>Needs investigation]
        TICKET[Create Ticket<br/>Manual resolution]
    end

    IL --> FETCH
    PSP_R --> FETCH
    BANK_S --> FETCH
    FETCH --> MATCH
    MATCH --> DIFF
    DIFF --> MATCHED
    DIFF --> MISMATCH
    DIFF --> MISSING_INT
    DIFF --> MISSING_EXT
    MATCHED --> AUTO
    MISMATCH --> ALERT
    MISSING_INT --> ALERT
    MISSING_EXT --> TICKET

    style MISMATCH fill:#e53935,color:#fff
    style MISSING_INT fill:#ff6f00,color:#fff
    style MISSING_EXT fill:#ff6f00,color:#fff
    style MATCHED fill:#43a047,color:#fff

Các loại Discrepancy (sai lệch)

LoạiMô tảNguyên nhân phổ biếnCách xử lý
Timing differenceGiao dịch ngày 31/12 trong ledger nhưng PSP report ngày 01/01Timezone, batch processing cutoffAuto-resolve: match lại ngày hôm sau
Amount mismatchInternal: 500K, PSP: 499.7KPSP fee bị trừ trước, currency roundingKiểm tra fee structure, FX rate
Missing in internalPSP report có giao dịch mà ledger không cóWebhook failed, DB write failedInvestigate → manual adjustment
Missing in PSPLedger có nhưng PSP không reportPayment still processing, PSP bugWait T+2, contact PSP support
DuplicateCùng giao dịch xuất hiện 2 lầnIdempotency failureTạo reversal entry cho bản trùng
Status mismatchInternal: SUCCESS, PSP: FAILEDWebhook order, race conditionRollback: refund buyer, alert merchant

Reconciliation Schedule

Tần suấtĐối soát gìMục đích
HourlyInternal ledger vs payment DBPhát hiện internal inconsistency nhanh
Daily (T+1)Internal ledger vs PSP settlement fileMain reconciliation
WeeklyAggregated balance vs bank statementCross-check tổng tiền
MonthlyFull audit reconciliationCompliance reporting

Aha Moment: Reconciliation là “safety net” cuối cùng. Dù idempotency, state machine, retry logic có tốt đến đâu, vẫn cần reconciliation để bắt mọi sai sót mà code không catch được. Reconciliation catches everything.

3.6 Wallet Service — Quản lý số dư

Wallet Service làm gì?

Wallet service quản lý balance (số dư) của merchant trên platform. Mỗi merchant có một wallet, và wallet phải luôn chính xác.

OperationMô tảVí dụ
Credit (cộng tiền)Buyer thanh toán thành công+500,000 VND
Debit (trừ tiền)Settlement cho merchant-490,000 VND
Hold (giữ tiền)Chờ xử lý dispute/chargebackHold 500,000 VND
ReleaseGiải phóng holdRelease 500,000 VND

Concurrency Problem — Vấn đề đồng thời

Khi 100 buyer trả tiền cho cùng 1 merchant cùng lúc → 100 concurrent writes vào cùng 1 wallet balance:

Thread 1: Read balance = 1,000,000 → Add 500,000 → Write 1,500,000
Thread 2: Read balance = 1,000,000 → Add 300,000 → Write 1,300,000
                                                     ↑ Lost update!
(Kết quả đúng: 1,800,000 nhưng thực tế: 1,300,000 hoặc 1,500,000)

Optimistic vs Pessimistic Locking

ApproachCơ chếƯu điểmNhược điểmKhi nào dùng
Pessimistic LockingSELECT ... FOR UPDATE — lock row trước khi readĐảm bảo consistency 100%Performance thấp khi high contentionBalance update (ít conflict)
Optimistic LockingRead version → Update WHERE version = old_version → Retry nếu conflictPerformance caoNhiều retry khi high contentionRead-heavy, low-conflict scenarios
Database-level atomicUPDATE wallet SET balance = balance + 500000 WHERE id = ?Đơn giản, atomicKhông check negative balanceSimple credit operations

Pessimistic Locking flow:

BEGIN TRANSACTION;
SELECT balance, version FROM wallets WHERE merchant_id = 'M001' FOR UPDATE;
-- balance = 1,000,000, version = 5
-- Row is now LOCKED — other transactions must wait

-- Business logic: check balance, calculate new balance
-- new_balance = 1,000,000 + 500,000 = 1,500,000

UPDATE wallets SET balance = 1,500,000, version = 6 WHERE merchant_id = 'M001';
COMMIT;
-- Lock released

Optimistic Locking flow:

-- Step 1: Read (no lock)
SELECT balance, version FROM wallets WHERE merchant_id = 'M001';
-- balance = 1,000,000, version = 5

-- Step 2: Update with version check
UPDATE wallets SET balance = 1,500,000, version = 6
WHERE merchant_id = 'M001' AND version = 5;

-- If affected_rows = 0 → someone else updated → RETRY from Step 1
-- If affected_rows = 1 → SUCCESS

Recommendation: Cho payment system, dùng pessimistic locking cho balance updates. Lý do: tiền phải chính xác 100%, performance tradeoff chấp nhận được vì mỗi merchant thường không có quá nhiều concurrent transactions.

Distributed Transaction — Saga Pattern

Khi payment cần update nhiều services cùng lúc (payment DB + ledger + wallet), có 2 approaches:

ApproachMô tảTrade-off
2PC (Two-Phase Commit)Coordinator lock tất cả resources → commit cùng lúcStrong consistency nhưng blocking, single point of failure
Saga PatternChuỗi local transactions, mỗi step có compensating actionEventually consistent, non-blocking, nhưng complex

Saga Pattern cho Payment — tham chiếu Tuan-11-Microservices-Pattern:

StepServiceActionCompensating Action (nếu fail)
1Payment ServiceCreate payment record (PROCESSING)Mark payment as FAILED
2Fraud ServiceCheck fraud → passN/A (read-only)
3PSPCharge cardRefund via PSP
4LedgerCreate double-entryCreate reversal entry
5WalletCredit merchant balanceDebit merchant balance
6NotificationSend confirmationSend failure notification

Nếu Step 4 (Ledger) fail:

  • Compensate Step 3: Gọi PSP refund
  • Compensate Step 1: Mark payment as FAILED
  • Steps 5 & 6: Không cần compensate (chưa thực hiện)

3.7 Handling Failures — Xử lý lỗi

Timeout Handling — Payment “in limbo”

Đây là scenario đáng sợ nhất trong payment system:

Payment Service → PSP: "Charge 500K VND"
... 30 giây trôi qua ... không có response ...

Payment đang ở trạng thái “in limbo” — không biết thành công hay thất bại. Nếu:

  • Retry → có thể charge 2 lần
  • Không retry → buyer có thể đã bị charge mà không nhận hàng

Giải pháp:

StrategyCơ chếKhi nào
Idempotent retryRetry với cùng idempotency_key → PSP trả cached resultSau timeout
Payment status checkGọi GET /payments/{id} trên PSP để kiểm tra trạng tháiTrước khi retry
Timeout + async reconciliationMark payment as UNKNOWN → reconciliation service resolve sauKhi PSP không response
Payment in limbo queueĐẩy vào queue riêng → background worker check status mỗi 5 phútAutomated recovery

Rule: Khi timeout, KHÔNG bao giờ assume payment failed. Luôn check status trước. Tiền có thể đã bị charge.

Compensation Transactions — Giao dịch bù

Khi payment đã thành công nhưng cần hoàn lại (refund, chargeback, error):

ScenarioCompensation
Buyer request refundTạo refund transaction qua PSP
Chargeback (buyer dispute với bank)Bank tự lấy tiền lại, platform chịu phí
Double charge phát hiện qua reconciliationTạo reversal entry + refund
Merchant fraudFreeze wallet + hold settlement

Compensation flow:

Original: Debit buyer 500K, Credit merchant 500K
Refund:   Debit merchant 500K, Credit buyer 500K (reversal)

Quan trọng: KHÔNG BAO GIỜ delete original entry. Tạo reversal entry riêng. Audit trail phải complete.

Dead Letter Queue (DLQ)

Khi message processing fail sau nhiều lần retry:

Main Queue → Consumer: Process payment webhook
Consumer: FAIL (3 retries exhausted)
Consumer → DLQ: Move failed message to Dead Letter Queue
Alert → On-call engineer: "5 messages in DLQ — investigate!"
DLQ StrategyMô tả
Auto-retry with delayDLQ consumer retry mỗi 1 giờ
Manual investigationEngineer xem DLQ, fix root cause, replay message
Alert threshold> 10 messages in DLQ within 1 hour → PagerDuty alert
Max retentionDLQ messages giữ 14 ngày, sau đó archive to cold storage

3.8 Currency Handling — Xử lý tiền tệ

Store Amounts in Smallest Unit

Rule: Luôn lưu tiền ở đơn vị nhỏ nhất (cents cho USD, đồng cho VND):

CurrencyĐơn vị nhỏ nhấtVí dụStored value
USDcent$49.994999
EURcent29.50 EUR2950
VNDđồng500,000 VND500000
JPYyen (không có subunit)5,000 JPY5000
BHDfils (1/1000)10.500 BHD10500

Tại sao? Floating-point arithmetic KHÔNG chính xác. 0.1 + 0.2 = 0.30000000000000004 trong hầu hết ngôn ngữ lập trình. Dùng integer (BIGINT) → chính xác 100%.

Exchange Rate Service

Khi buyer trả USD nhưng merchant nhận VND:

StepAction
1Buyer pays $20 USD
2Exchange rate service: 1 USD = 25,450 VND (rate tại thời điểm giao dịch)
3Merchant receives 509,000 VND
4Lưu cả original amount ($20), converted amount (509,000 VND), và exchange rate (25,450)

Best practices:

  • Lock exchange rate tại thời điểm payment initiation (không thay đổi giữa chừng)
  • Store rate snapshot — lưu rate tại thời điểm giao dịch, không rely on current rate
  • Rounding rules — theo ISO 4217, mỗi currency có quy tắc rounding riêng
  • FX markup — platform có thể charge thêm 1-3% cho currency conversion

3.9 Fraud Detection — Phát hiện gian lận

Multi-Layer Fraud Detection

flowchart TB
    subgraph "Layer 1: Rules Engine"
        R1[Velocity Check<br/>5+ transactions trong 1 phút?]
        R2[Amount Check<br/>Giao dịch > threshold?]
        R3[Geo Check<br/>IP ở VN nhưng card ở US?]
        R4[Pattern Check<br/>Nhiều card khác nhau, cùng IP?]
    end

    subgraph "Layer 2: ML Model"
        ML1[Feature Extraction<br/>Transaction features]
        ML2[Scoring Model<br/>Fraud probability 0-1]
        ML3[Threshold Decision<br/>Score > 0.8 → block]
    end

    subgraph "Layer 3: Manual Review"
        MR[Human Review Queue<br/>Score 0.5-0.8]
    end

    subgraph "Decision"
        PASS[APPROVE]
        BLOCK[BLOCK + Alert]
        REVIEW[MANUAL REVIEW]
    end

    R1 --> |Pass| ML1
    R2 --> |Pass| ML1
    R3 --> |Pass| ML1
    R4 --> |Pass| ML1
    R1 --> |Fail| BLOCK
    R2 --> |Fail| BLOCK
    R3 --> |Fail| BLOCK
    R4 --> |Fail| BLOCK
    ML1 --> ML2
    ML2 --> ML3
    ML3 --> |Score < 0.5| PASS
    ML3 --> |Score 0.5-0.8| REVIEW
    ML3 --> |Score > 0.8| BLOCK
    REVIEW --> MR
    MR --> PASS
    MR --> BLOCK

Fraud Detection Methods

MethodMô tảVí dụ
Velocity checksĐếm số giao dịch trong time window> 5 transactions trong 1 phút → suspicious
Device fingerprintingXác định device dựa trên browser/device attributesCùng device thử 10 card khác nhau → fraud
Geo-IP analysisSo sánh IP location với billing addressIP ở Nigeria, card ở Norway → suspicious
BIN analysisKiểm tra Bank Identification Number (6 digit đầu)BIN thuộc bank thường bị fraud
Address Verification (AVS)So sánh billing address với card-on-file addressMismatch → suspicious
3D SecureBuyer verify với bank (SMS OTP, biometric)Liability shift cho merchant
Behavioral analysisPhân tích hành vi user trên siteCopy-paste card number (thay vì gõ) → bot
ML modelSupervised learning trên historical fraud dataFeature: amount, time, location, device, velocity

Capacity Estimation — Ước lượng năng lực

Assumptions

Thông sốGiá trịGiải thích
Transactions/day1,000,000 (1M)Quy mô Shopify-like
Average transaction amount$50 (USD)Mix of small & large payments
Peak-to-average ratio5xBlack Friday, flash sales
Ledger entries per transaction42 debit + 2 credit (platform fee)
Average payload size (payment record)2 KBJSON with metadata
Average ledger entry size500 bytesCompact, structured
Wallet update per transaction1Credit merchant wallet
PSP webhook retry3 attemptsStandard retry policy

QPS Calculation

Nhận xét: 58 TPS peak — đây là moderate scale. Không cần sharding phức tạp cho payment service. Nhưng ledger writes cao hơn vì mỗi transaction tạo nhiều entries.

Ledger Storage

Nhận xét: 3.65 TB cho 5 năm ledger — vừa đủ cho single PostgreSQL instance với partitioning. Nhưng vì ledger là append-onlyimmutable, có thể archive entries cũ hơn 1 năm sang cold storage (S3/Glacier).

Payment Records Storage

Wallet Database Sizing

Nhận xét: Wallet DB rất nhỏ (20 MB) vì chỉ lưu current balance. Nhưng write throughput là vấn đề chính — 58 TPS peak vào wallet với locking.

Wallet Write Throughput

Nhận xét: Nếu 1 merchant nhận 100+ payments/s (ví dụ: Shopee flash sale), pessimistic locking trên single row sẽ bottleneck. Đây là giả định serialization hoàn toàn dưới pessimistic lock — thực tế cao hơn nếu transactions được pipeline.

Giải pháp production (Stripe-style):

  • Sharded balance per merchant: Chia balance thành K rows với suffix random (balance_{merchant_id}_{shard_0..K-1}). Write phân tán → throughput tăng K lần. Đọc tổng = SUM tất cả shards.
  • Write-ahead buffer + async aggregation: Ghi vào append-only event log → aggregate balance bất đồng bộ. Throughput không bị giới hạn bởi lock contention.
  • Coalescing/batching: Nhiều update cùng merchant trong cùng tick (e.g., 10ms window) được merge thành 1 update — giảm số write thật.
  • Optimistic locking: Dùng version column thay vì pessimistic lock; conflict thì retry. Phù hợp khi conflict rate thấp.

Tham chiếu: Stripe Engineering — Online migrations at scaleIdempotency keys.

Tóm tắt Estimation

MetricValue
Transaction QPS (peak)~58/s
Ledger write QPS (peak)~232/s
Webhook QPS (peak)~87/s
Ledger storage/year~730 GB
Payment records/year~730 GB
Wallet DB size~20 MB
Total storage/year~1.5 TB
5-year retention~7.5 TB

Security — Bảo mật

PCI-DSS Compliance (Payment Card Industry Data Security Standard)

PCI-DSS là bộ tiêu chuẩn bảo mật bắt buộc cho mọi tổ chức xử lý, lưu trữ, hoặc truyền tải card data. Có 4 levels:

LevelĐiều kiệnYêu cầu Audit
Level 1> 6M transactions/yearAnnual on-site audit bởi QSA
Level 21M - 6M transactions/yearAnnual SAQ + quarterly scan
Level 320K - 1M transactions/yearAnnual SAQ + quarterly scan
Level 4< 20K transactions/yearAnnual SAQ

Với 1M transactions/day = 365M/year → Level 1. Chi phí audit: 500K/năm. Đây là lý do chính để dùng PSP — giảm PCI-DSS scope.

PCI-DSS 12 Requirements (Tóm tắt)

#RequirementÁp dụng cho Payment System
1Install and maintain firewallNetwork segmentation, WAF
2Change default passwordsHarden mọi service
3Protect stored cardholder dataKHÔNG lưu card number — dùng tokenization
4Encrypt transmission of cardholder dataTLS 1.2+ cho mọi communication
5Use and update anti-virusEndpoint protection
6Develop secure systemsSecure SDLC, code review, SAST/DAST
7Restrict access to cardholder dataRBAC, principle of least privilege
8Assign unique IDs to each personIndividual accounts, no shared credentials
9Restrict physical accessData center security
10Track and monitor all accessAudit logging — tham chiếu Tuan-14-AuthN-AuthZ-Security
11Test security systems regularlyPenetration testing, vulnerability scanning
12Maintain information security policyDocumentation, training

Tokenization — Không bao giờ lưu Card Number

Flow an toàn:
1. Buyer nhập card trên PSP's hosted page (KHÔNG phải server của em)
2. PSP tokenize: 4242-4242-4242-4242 → tok_abc123xyz
3. Em chỉ lưu token + last 4 digits (4242) + card brand (Visa)
4. Khi charge lại: gửi token cho PSP, PSP lookup card từ vault

Flow KHÔNG an toàn (vi phạm PCI-DSS):
1. Buyer nhập card trên form của em
2. Card number đi qua server của em → EM PHẢI COMPLY PCI-DSS LEVEL 1
3. Em lưu card number trong DB → THẢM HỌA nếu bị hack

Rule tuyệt đối: Card number KHÔNG BAO GIỜ touch server của em. Không transit, không store, không process. Dùng hosted payment page hoặc client-side tokenization (Stripe Elements).

Encryption — Mã hóa

LayerTypeStandardÁp dụng
In transitTLS 1.2+AES-256-GCMMọi API call, webhook, internal service communication
At restAES-256FIPS 140-2Database encryption, backup encryption
Application levelField-level encryptionAES-256 + KMSPII (tên, email, địa chỉ), token
Key managementKMS (Key Management Service)HSM-backedAWS KMS, HashiCorp Vault

AML (Anti-Money Laundering) — Chống rửa tiền

CheckMô tảThreshold
KYC (Know Your Customer)Verify danh tính merchant khi onboardBắt buộc cho mọi merchant
Transaction monitoringPhát hiện pattern bất thườngNhiều giao dịch nhỏ (structuring)
Sanctions screeningKiểm tra tên trong danh sách cấm vậnOFAC, EU sanctions list
Suspicious Activity Report (SAR)Báo cáo giao dịch đáng ngờ cho cơ quan chức năng> $10,000 USD (US) hoặc pattern bất thường
PEP screeningKiểm tra Politically Exposed PersonsGovernment officials, relatives

3D Secure (3DS)

3D Secure là protocol xác thực thêm một layer giữa buyer và issuing bank:

1. Buyer enter card details
2. PSP detect card enrolled in 3DS
3. Redirect to bank's 3DS page
4. Buyer verify: SMS OTP / Biometric / App notification
5. Bank confirm → PSP proceed with payment

Lợi ích:

  • Liability shift: Nếu fraud xảy ra với 3DS-authenticated transaction, bank chịu trách nhiệm (không phải merchant)
  • Reduce chargebacks: Buyer đã xác thực → khó dispute
  • SCA compliance: Strong Customer Authentication (EU PSD2 requirement)

Audit Logging

Mọi giao dịch phải có audit trail — tham chiếu Tuan-14-AuthN-AuthZ-Security:

FieldMô tả
event_idUUID unique cho mỗi event
timestampISO 8601 với timezone
actorUser/service thực hiện action
actionCREATE_PAYMENT, APPROVE_REFUND, UPDATE_STATUS, etc.
resourcepayment_id, transaction_id
old_valueGiá trị trước khi thay đổi
new_valueGiá trị sau khi thay đổi
ip_addressIP của actor
user_agentBrowser/client info
reasonLý do thay đổi (bắt buộc cho manual actions)

Rule: Audit log là append-only, immutable, lưu riêng biệt với application DB. Không ai (kể cả admin) có thể xóa audit log. Lưu tối thiểu 7 năm (regulatory requirement).


DevOps & Monitoring — Vận hành và giám sát

Key Metrics to Monitor

MetricMô tảAlert ThresholdSeverity
Payment Success Rate% giao dịch thành công< 95% → alertP1 (Critical)
Payment Latency P99Thời gian xử lý 99th percentile> 3s → alertP2 (High)
Payment Latency P50Median latency> 500ms → investigateP3 (Medium)
PSP Error Rate% request tới PSP bị lỗi> 5% → alertP1
Webhook Processing LagĐộ trễ xử lý webhook> 5 minutes → alertP2
Reconciliation DiscrepancySố giao dịch không khớp> 0.1% → alertP1
DLQ DepthSố message trong Dead Letter Queue> 10 → alertP2
Wallet Balance AnomalyBalance thay đổi bất thườngNegative balance → alertP1
Fraud Block Rate% giao dịch bị fraud detection block> 10% → investigateP3
Idempotency Hit Rate% request trùng idempotency key> 5% → investigate (client bug?)P3

Dashboard Layout

PanelMetricsVisualize
Payment HealthSuccess rate, error rate, volumeTime series (last 24h)
LatencyP50, P95, P99 per endpointHeatmap
Money FlowTotal pay-in, pay-out, netCounter + trend
PSP StatusPer-PSP success rate, latencyTable + sparkline
ReconciliationMatch %, discrepancy countDaily bar chart
FraudBlock rate, ML score distributionHistogram

PCI-DSS Audit Logging Requirements

Yêu cầuImplementation
Log mọi access tới cardholder dataAudit log cho mọi token lookup
Log mọi admin actionRBAC + audit trail cho admin panel
Log authentication attemptsSuccess + failure, lockout after 5 failures
Centralized loggingELK Stack hoặc Splunk, SIEM integration
Log integrityWrite-once storage, tamper-evident (hash chain)
RetentionMinimum 1 năm online, 7 năm total
Daily log reviewAutomated anomaly detection + manual review

Disaster Recovery

ScenarioRTO (Recovery Time Objective)RPO (Recovery Point Objective)Strategy
Single service failure< 30s0 (no data loss)Auto-restart, health check, replica
Database failure< 5 min0Hot standby, synchronous replication
AZ (Availability Zone) failure< 5 min0Multi-AZ deployment
Region failure< 30 min< 1 minCross-region replica, DNS failover
PSP outage< 1 min0Multi-PSP failover (Stripe → Adyen)
Complete data center loss< 1 hour< 5 minCross-region backup + restore

Multi-PSP strategy: Không bao giờ phụ thuộc vào 1 PSP. Khi Stripe outage (đã xảy ra nhiều lần), auto-failover sang Adyen hoặc PayPal. Cần abstract PSP interface.

Deployment Strategy

StrategyMô tảRisk
Blue-Green2 identical environments, switch trafficRollback instant nhưng tốn 2x resources
CanaryRoute 1-5% traffic sang new version, monitorPhát hiện bug sớm, rollback nhanh
Feature flagsToggle new payment features per merchantGranular control
Database migrationAlways backward compatible, no breaking changesZero-downtime migration

Payment system deployment rule: KHÔNG BAO GIỜ deploy payment changes vào Friday afternoon hoặc trước major sale events (Black Friday, 11.11). Luôn deploy vào đầu tuần, giờ thấp điểm.


Mermaid Diagrams — Tổng hợp

Diagram 1: Payment Flow End-to-End

flowchart LR
    B[Buyer] -->|1. Pay $50| FE[Frontend]
    FE -->|2. POST /payments| PS[Payment Service]
    PS -->|3. Check fraud| FD[Fraud Detection]
    FD -->|4. Pass| PS
    PS -->|5. Create charge| PSP[PSP - Stripe]
    PSP -->|6. Authorize| CN[Card Network - Visa]
    CN -->|7. Auth request| IB[Issuing Bank]
    IB -->|8. Approved| CN
    CN -->|9. Approved| PSP
    PSP -->|10. Webhook: success| PS
    PS -->|11. Record| LEDGER[Ledger]
    PS -->|12. Credit| WALLET[Wallet]
    PS -->|13. Notify| NOTIF[Notification]
    NOTIF -->|14. Email| B

    style PS fill:#1e88e5,color:#fff
    style PSP fill:#43a047,color:#fff
    style LEDGER fill:#e53935,color:#fff
    style WALLET fill:#ff6f00,color:#fff

Diagram 2: Double-Entry Ledger Flow

flowchart TB
    subgraph "Transaction: Buyer pays Merchant $50"
        T1[Transaction ID: txn_001]
    end

    subgraph "Ledger Entries"
        E1[Entry 1: DEBIT<br/>Account: Buyer Cash<br/>Amount: +$50.00]
        E2[Entry 2: CREDIT<br/>Account: Platform Holding<br/>Amount: +$50.00]
    end

    subgraph "Settlement: Platform pays Merchant"
        T2[Transaction ID: txn_002]
    end

    subgraph "Settlement Entries"
        E3[Entry 3: DEBIT<br/>Account: Platform Holding<br/>Amount: -$50.00]
        E4[Entry 4: CREDIT<br/>Account: Merchant Revenue<br/>Amount: +$48.55]
        E5[Entry 5: CREDIT<br/>Account: Platform Fee<br/>Amount: +$1.45]
    end

    subgraph "Validation"
        V1{Debit = Credit?<br/>$50 = $50}
        V2{Debit = Credit?<br/>$50 = $48.55 + $1.45}
    end

    T1 --> E1
    T1 --> E2
    E1 --> V1
    E2 --> V1
    T2 --> E3
    T2 --> E4
    T2 --> E5
    E3 --> V2
    E4 --> V2
    E5 --> V2

    style E1 fill:#e53935,color:#fff
    style E3 fill:#e53935,color:#fff
    style E2 fill:#43a047,color:#fff
    style E4 fill:#43a047,color:#fff
    style E5 fill:#43a047,color:#fff

Diagram 3: Reconciliation Pipeline

flowchart TB
    subgraph "Sources T+0"
        A[Internal Ledger<br/>Real-time writes]
    end

    subgraph "Sources T+1"
        B[PSP Settlement File<br/>CSV/JSON daily export]
        C[Bank Statement<br/>MT940 / API]
    end

    subgraph "ETL Pipeline"
        E1[Extract<br/>Fetch files from SFTP/API]
        E2[Transform<br/>Normalize format, currency]
        E3[Load<br/>Insert into reconciliation DB]
    end

    subgraph "Matching Engine"
        M1[Match by transaction_id]
        M2[Fuzzy match by amount + date]
        M3[Manual match for exceptions]
    end

    subgraph "Results"
        R1[MATCHED<br/>96-99% of transactions]
        R2[TIMING DIFF<br/>1-3%, auto-resolve next day]
        R3[DISCREPANCY<br/>< 0.1%, needs investigation]
    end

    subgraph "Actions"
        ACT1[Auto-close matched]
        ACT2[Re-run tomorrow]
        ACT3[Create JIRA ticket<br/>Alert on-call]
    end

    A --> E1
    B --> E1
    C --> E1
    E1 --> E2
    E2 --> E3
    E3 --> M1
    M1 --> M2
    M2 --> M3
    M1 --> R1
    M2 --> R2
    M3 --> R3
    R1 --> ACT1
    R2 --> ACT2
    R3 --> ACT3

    style R1 fill:#43a047,color:#fff
    style R2 fill:#ffa726,color:#000
    style R3 fill:#e53935,color:#fff

Diagram 4: Payment State Machine (Simplified)

stateDiagram-v2
    [*] --> CREATED
    CREATED --> PROCESSING: Submit to PSP
    PROCESSING --> AUTHORIZED: Bank approved
    PROCESSING --> FAILED: Bank declined
    AUTHORIZED --> CAPTURED: Capture funds
    AUTHORIZED --> VOIDED: Cancel payment
    CAPTURED --> SETTLED: Settlement complete
    CAPTURED --> REFUND_PENDING: Refund requested
    REFUND_PENDING --> REFUNDED: Refund processed
    FAILED --> [*]
    VOIDED --> [*]
    SETTLED --> [*]
    REFUNDED --> [*]

Aha Moments — Khoảnh khắc “A ha!”

#1 — Idempotency is THE most important concept: Trong payment system, mọi thứ có thể fail — network timeout, service crash, DB down. Idempotency key đảm bảo rằng dù retry bao nhiêu lần, kết quả vẫn giống nhau. Nếu em chỉ nhớ được 1 concept từ bài này, hãy nhớ idempotency. Stripe, PayPal, mọi PSP lớn đều xây dựng trên nền tảng này.

#2 — Never store card numbers: Đây không phải recommendation — đây là luật. Vi phạm PCI-DSS Requirement 3 = phạt 100,000/tháng + mất quyền xử lý card + reputational damage không thể phục hồi. Luôn dùng tokenization qua PSP.

#3 — Reconciliation catches everything: Dù code có tốt đến đâu, sẽ luôn có edge case: network blip, timezone bug, race condition, PSP error. Reconciliation là safety net cuối cùng. Trong thế giới fintech, câu nói nổi tiếng là: “Trust, but verify”. Reconciliation chính là “verify”.

#4 — Payment is a state machine, not a function call: Nhiều junior dev nghĩ payment đơn giản: chargeCard(amount) → success/fail. Thực tế, payment đi qua 10+ states, có thể stuck ở bất kỳ state nào, và cần compensation logic cho mỗi failure point. State machine giúp em biết chính xác payment đang ở đâu và nên làm gì tiếp.

#5 — Double-entry là self-checking mechanism: Kế toán dùng double-entry từ 500 năm trước vì một lý do đơn giản: nếu tổng debit khác tổng credit, chắc chắn có lỗi. Trong software, đây là built-in invariant check. Mỗi khi ledger imbalanced → trigger alert → investigate ngay.

#6 — Money has no “undo” button: Khác với database (ROLLBACK), tiền thật không có undo. Khi charge card → tiền đã chuyển. Muốn hoàn → phải tạo refund transaction riêng, mất 3-10 ngày làm việc. Đây là lý do payment system cần cẩn thận gấp 10 lần so với hệ thống khác.

#7 — Floating-point is the enemy of money: 0.1 + 0.2 != 0.3 trong hầu hết ngôn ngữ lập trình. Lưu tiền bằng float → sai sót tích lũy → reconciliation fail → audit finding. Luôn dùng integer (cents/đồng) hoặc Decimal type.


Common Pitfalls — Sai lầm thường gặp

Pitfall 1: Không có Idempotency Key

Sai: POST /payments {amount: 500000} — mỗi request tạo payment mới Đúng: POST /payments {amount: 500000, idempotency_key: "uuid-abc-123"} — retry an toàn

Hậu quả: Buyer bị charge 2 lần khi network timeout + retry. Phải refund manually → mất thời gian + uy tín.

Pitfall 2: Lưu Card Number trong DB

Sai: INSERT INTO payments (card_number, amount) VALUES ('4242424242424242', 500000) Đúng: INSERT INTO payments (card_token, last_four, amount) VALUES ('tok_abc', '4242', 500000)

Hậu quả: Vi phạm PCI-DSS → phạt + mất license. Nếu DB bị hack → hàng triệu card bị lộ → class action lawsuit.

Pitfall 3: Dùng Float cho tiền

Sai: amount FLOAT → 49.99 + 0.01 có thể = 50.000000001 Đúng: amount BIGINT → lưu 4999 (cents), display $49.99

Hậu quả: Reconciliation luôn sai lệch vài cent. Tích lũy hàng triệu giao dịch → sai lệch hàng nghìn đô.

Pitfall 4: Không handle Payment “in limbo”

Sai: Timeout → assume failed → không retry → buyer đã bị charge nhưng không nhận hàng Đúng: Timeout → check payment status via PSP API → retry with idempotency key nếu cần

Hậu quả: Tiền “biến mất” — buyer mất tiền, merchant không nhận được. Chỉ reconciliation mới catch được.

Pitfall 5: Cho phép Update/Delete Ledger Entry

Sai: UPDATE ledger SET amount = 400000 WHERE id = 123 — sửa entry cũ Đúng: Tạo reversal entry mới: INSERT INTO ledger (type, amount) VALUES ('REVERSAL', -500000)

Hậu quả: Mất audit trail. Auditor không thể verify lịch sử giao dịch. Compliance violation.

Pitfall 6: Single PSP dependency

Sai: Chỉ tích hợp Stripe. Stripe down → toàn bộ payment down. Đúng: Abstract PSP interface, tích hợp ít nhất 2 PSPs. Auto-failover khi 1 PSP down.

Hậu quả: Stripe outage (đã xảy ra nhiều lần trong lịch sử) = 100% revenue loss trong thời gian outage.

Pitfall 7: Không có Reconciliation

Sai: “Code của mình đúng rồi, không cần đối soát.” Đúng: Daily reconciliation là bắt buộc cho mọi payment system.

Hậu quả: Sau 6 tháng phát hiện bug gây sai lệch 50,000 giao dịch. Không biết bắt đầu fix từ đâu. Regulatory audit fail.

Pitfall 8: Deploy payment changes vào Friday

Sai: Deploy new payment flow vào Friday 5pm → bug → weekend → nobody available to fix Đúng: Deploy đầu tuần, giờ thấp điểm, canary 1% traffic trước

Hậu quả: Hàng nghìn giao dịch fail trong weekend. On-call engineer scramble fix trong khi thiếu context.


TopicLinkLiên quan thế nào
Authentication & SecurityTuan-14-AuthN-AuthZ-SecurityAPI authentication, RBAC cho admin panel, JWT cho service-to-service
Data Security & EncryptionTuan-15-Data-Security-EncryptionEncryption at rest/in transit, tokenization, KMS
Microservices PatternTuan-11-Microservices-PatternSaga pattern, service decomposition, event-driven architecture
Database Sharding & ReplicationTuan-07-Database-Sharding-ReplicationLedger partitioning, wallet DB replication, read replicas
Message QueueTuan-08-Message-QueueWebhook processing, DLQ, async notification
Monitoring & ObservabilityTuan-13-Monitoring-ObservabilityPayment metrics, alerting, distributed tracing
Back-of-the-envelopeTuan-02-Back-of-the-envelopeEstimation methodology

Tổng kết — Summary

Payment System Architecture Principles

PrincipleGiải thích
Idempotency everywhereMọi write operation phải idempotent
Never store sensitive dataCard number, CVV → tokenization via PSP
Append-only ledgerKhông update, không delete, chỉ append
State machine for paymentExplicit states + transitions, không implicit
Reconciliation as safety netDaily đối soát, bắt mọi sai lệch
Multi-PSP resilienceKhông phụ thuộc 1 PSP
Integer for moneyBIGINT in smallest unit, no floating-point
Audit everythingMọi action có trail, lưu 7+ năm
Defense in depthMultiple layers: fraud, 3DS, AML, reconciliation
Fail safe, not fail fastPayment in limbo → check status → retry safely

Khi nào em gặp lại Payment System?

  • E-commerce platform: Shopee, Tiki, Lazada
  • SaaS billing: Subscription management, usage-based billing
  • Marketplace: Grab, Uber, Airbnb (3-party payment: buyer → platform → seller)
  • Fintech: Digital wallet, P2P transfer, lending platform
  • Interview: Amazon, Stripe, PayPal, Shopify — đây là bài system design phổ biến

“Hieu, payment system là nơi mà mọi concept em đã học — distributed systems, database, security, monitoring — hội tụ lại. Hiểu payment system = hiểu cách tiền chảy trong internet. Và tiền chảy chính xác = niềm tin của hàng triệu người dùng.”


Tài liệu này dựa trên Chapter 7: Payment System từ “System Design Interview Volume 2” của Alex Xu, được mở rộng và bổ sung cho context của Hieu (Backend Dev transitioning to System Architect).