Skip to content
C Codeloom
System Design

System Design: A Payment Gateway

Design a payment gateway with idempotency, double-entry ledgers, retries, and PCI considerations, balancing safety and throughput.

·3 min read · By Codeloom
Intermediate 11 min read

What you'll learn

  • Why payment systems are different
  • Designing a double-entry ledger
  • Idempotency and exactly-once charges
  • Retries, webhooks, and reconciliation
  • PCI scope reduction strategies

Prerequisites

  • Familiar with HTTP and databases

What and Why

Payments are unusual because mistakes cost money and trust. A duplicate charge is a customer service nightmare. A missing charge is lost revenue. The system must be correct under retries, network failures, and partial outages.

The job is not “talk to a card network.” That part is mostly delegated to a processor. The job is to model money movements safely and to expose a clean API to your product.

Mental Model

Two ideas drive every good payment design:

  1. Every state change is a journal entry. Money moves between accounts in pairs of debit and credit so the books always balance.
  2. Every external call carries an idempotency key so retries do not create duplicates.

Once these two ideas are internalized, the rest is structure.

Architecture

The API receives a charge request with an idempotency key. The orchestrator records an intent in the ledger as “pending,” calls the processor, and on response moves the intent to “succeeded” or “failed.”

Client -> API (idempotency key)
          |
      Orchestrator
          |
  +-------+-------+
  v               v
Ledger        Processor
(pending) -> (charge) -> webhook
                            |
                       Orchestrator
                            |
                        Ledger (succeeded)
Payment orchestration with ledger

The ledger is append-only. To compute a balance, sum entries up to a point in time. Snapshots speed this up; they are recomputable from the journal.

Webhooks from the processor confirm async state changes. They arrive duplicated, out of order, and sometimes never. A reconciliation job compares processor reports against the ledger nightly.

Trade-offs

A real-time view of balances costs more than a daily one. Most platforms expose an “available” balance that lags slightly and a “pending” balance that is best-effort.

Strong consistency in the ledger forces a single-writer per account. For very high-volume accounts (large merchants) you partition by sub-account to scale writes at the cost of more complex reporting.

PCI compliance is largely about scope. Forward card data straight to the processor via tokenization. Never let it touch your application servers and the scope shrinks dramatically.

Practical Tips

Persist the idempotency key with the request fingerprint. If the same key arrives with a different body, reject it. If the same key arrives with the same body, return the prior response.

Use a state machine for each payment with explicit transitions: created, authorized, captured, refunded, failed. Forbid backward transitions in code, not just in policy.

Build a reconciliation tool from day one. It will save you the first time the processor’s view and yours disagree, which will happen.

Log money amounts as integers in the smallest currency unit (cents, paise). Floating-point math has no place in a ledger.

Wrap-up

A payment gateway rewards discipline. Idempotency, double-entry ledger, state machines, and reconciliation are the four pillars. Get them right and the system stays correct even as the surface area grows. Get any one wrong and you will spend years cleaning up.