SkillByAIOpen interactive version →

Lesson 18 / 25

Design a Payment System and Ledger

Double-entry, idempotency, reconciliation.

Correctness first

A payment system must never lose or duplicate money. Components: a payment service that accepts requests with an idempotency key, a payment executor that calls external providers (card networks, banks via a payment service provider), a ledger, and a reconciliation job. The ledger uses double-entry bookkeeping: every transaction writes at least two entries whose debits and credits sum to zero, so balances can be derived and audited. Entries are append-only; corrections are new reversing entries, never edits. Store amounts as integers in minor units (or a decimal type) with a currency, never floating point. Reconciliation compares the internal ledger against provider settlement files and flags mismatches for investigation.

Double-entry data model

Illustrative schema and a transfer.

accounts(id, owner, currency, type)            -- e.g. user wallet, merchant, fees
transactions(id, idempotency_key UNIQUE, status, created_at)
entries(id, transaction_id, account_id, amount_minor, direction DEBIT|CREDIT)

Transfer 50.00 from customer to merchant with a 1.00 fee:
  txn T1 (idempotency_key = "pay-8842")
    DEBIT  customer_wallet   5000
    CREDIT merchant_account  4900
    CREDIT platform_fees      100
  sum(debits) = sum(credits) = 5000   -- checked in the same DB transaction

Balance(account) = sum(credits) - sum(debits)   (or a maintained balance row
                   updated in the same transaction, for liability-style accounts)
Retry with same key -> UNIQUE constraint hit -> return the original result

Expect unknown outcomes

Calls to a provider can time out with the result unknown. Mark the payment as pending, retry with the same idempotency key, and let webhooks and reconciliation settle the final state.

Quick check: How is a mistaken ledger entry corrected in a double-entry ledger?

  • By appending a reversing entry, keeping history intact
  • By editing the original amount
  • By deleting the transaction row
  • By changing the account balance directly
Answer

By appending a reversing entry, keeping history intact — Ledgers are append-only for auditability.