SuryanandHome

Payment Processing & Ledger

Problem statement

Process card payments, support refunds, partial captures, disputes, and maintain an internal ledger that matches PSP settlements (Stripe, Adyen).

How it works

  • PSP handles PCI vault, 3DS, network tokens.
  • Your system records intent → authorized → captured → settled with double-entry ledger for money movement.

Analogy: A bank passbook where every rupee is both debited from one account and credited to another — the sum of all lines must always be zero (balanced books).

High-level design

Rendering diagram…

Components explained — this design

ComponentWhat it isWhy we use it here
Payments APIYour controlled surface for creating intents, captures, refunds.Never expose raw PSP keys to browsers; centralizes validation and audit logging.
Payment intent state machineTracks requires_payment_method → succeeded etc.Maps PSP states to internal accounting states; supports partial captures and disputes.
Ledger serviceDouble-entry journal of money movement.Auditors and reconciliation require balanced books; append-only ledger prevents silent edits.
Stripe APICard network gateway + webhooks for async outcomes.Industry-standard PCI boundary; Radar fraud signals optional.
Signed webhooksHTTP callbacks verified with shared secret signature.Prevents forged payment events; always idempotent per event.id.
Reconciliation batchNightly job comparing PSP settlements vs ledger.Catches missing webhooks, currency rounding, chargebacks.

Shared definitions: 00-glossary-common-services.md

Low-level design

Webhooks

  • Stripe: verify Stripe-Signature with endpoint secret; process idempotently by event.id in DynamoDB.
  • Azure: Logic Apps + Event Grid for webhook ingestion with replay support.

Ledger (double-entry)

debit_accountcredit_accountamountcurrencyref
gateway_clearingrevenue10000USDpi_xxx
fees_expensegateway_clearing320USDfee_pi_xxx
  • PostgreSQL with SERIALIZABLE or append-only log table + materialized balance view updated async.
  • Why not single column “balance”: race conditions; ledger is auditable.

Payouts (marketplaces)

  • Stripe Connectseparate charges & transfers vs destination charges (tax/legal implications).

Idempotency

  • All mutating APIs require Idempotency-Key header; store response body 24h.

E2E: successful capture

Rendering diagram…

Tricky parts

ProblemSolution
Webhook arrives before API returnsOrder state must tolerate out-of-order; use state machine
Partial refundsNew ledger entries reversing subset; never delete rows
FXStore amount in minor units + FX rate snapshot id
ChargebacksDispute webhook → reserve from seller balance

Caveats

  • Strong consistency with external PSP is impossible — design for eventual + human ops tooling.
  • PCI SAQ-A if only Stripe.js; broader scope if you touch PAN.

Compliance

  • PSD2 SCA in EU — 3DS2 flows; network tokens for recurring.