Digital Payment System — System Design Interview Practice
Design a payment processing system like PayPal or Stripe that handles financial transactions securely. Work through the requirements, architecture trade-offs, and an interactive design review.
Concepts and architecture decisions to consider
- paymentsConcept to explore
- securityConcept to explore
- complianceConcept to explore
Interview prompt
Design secure payment authorization, capture, refunds, ledgering, and reconciliation so users can authorize and capture a payment reliably at scale.
- Define the source of truth for double-entry ledger and idempotency keys and make retries idempotent.
- Use bounded, partitioned state to meet 10M payments per day with regional provider bursts and payment intent p95 <=500ms.
- Separate the critical request path from provider callbacks, settlement, fraud, and reconciliation.
- Explain consistency, failure recovery, authorization, observability, and a degraded mode.
Requirements and scale assumptions
- Support the core workflow to authorize and capture a payment.
- Expose status, results, and freshness appropriate to secure payment authorization, capture, refunds, ledgering, and reconciliation.
- Support authorization, validation, updates, deletion, and recovery semantics.
- Meet payment intent p95 <=500ms under normal load.
- Scale to 10M payments per day with regional provider bursts without a single hot key or unbounded synchronous work.
- Do not lose committed state; make retries and duplicate events safe.
- Degrade safely when downstream workers, caches, or external dependencies fail.
- 10M payments per day with regional provider bursts
- Partition by the primary tenant, user, item, or geographic key and isolate hot partitions.
- Keep serving state bounded; retain raw events or durable records for replay and auditing.
- Peak scale: 10M payments per day with regional provider bursts — Capacity assumption that drives partitioning and backpressure.
- Latency target: payment intent p95 <=500ms — User-facing budget for the primary request or read path.
- Durable boundary: Committed before async — The source of truth is double-entry ledger and idempotency keys.
- Async boundary: At-least-once workers — Keep provider callbacks, settlement, fraud, and reconciliation off the synchronous path.
Key entities
- PaymentIntentintentId, merchantId, amount, currency, status, version, idempotencyKey
Versioned state machine for an authorized payment system money movement.
- LedgerEntryentryId, intentId, accountId, direction, amount, currency, createdAt
Append-only double-entry record for the financial effect of payment system.
- ProviderAttemptattemptId, intentId, provider, requestHash, status, providerRef
Retry-safe external attempt with an unknown-outcome reconciliation path.
- SettlementRecordsettlementId, intentId, batchId, gross, fees, status
Reconciled payment system settlement and discrepancy state.
Data flow
- 1. Create an idempotent financial intentThe payment system gateway authenticates the merchant, validates amount and currency, tokenizes the instrument reference, and records the idempotency key.
- 2. Authorize and capture safelyThe orchestrator advances the payment system state machine with conditional writes and creates exactly one ledger effect for each business transition.
- 3. Resolve external outcomesProvider responses and webhooks are stored as immutable attempts; timeouts remain unknown until payment system reconciliation resolves them.
- 4. Publish status asynchronouslyAn outbox emits committed payment system events for merchant status, risk, notifications, and settlement without blocking the financial commit.
- 5. Reconcile and repairSettlement jobs compare provider reports, ledger entries, and internal intents; discrepancies become auditable repair tasks rather than silent edits.
Deep dives and trade-offs
- Exactly-once financial effectsUse the idempotency key at the API, orchestrator, provider-attempt, and ledger boundaries. Make capture, refund, reversal, and cancellation transitions conditional on the current intent version. Treat provider timeouts as unknown and resolve them through status lookup or signed webhook reconciliation.
- Ledger and reconciliation correctnessKeep payment system ledger entries append-only and derive balances or views from them. Reconcile gross amount, fees, currency, provider reference, and settlement batch with tolerances that are explicit. Never repair by deleting history; append a compensating entry and preserve the operator reason.
- Risk, privacy, and availabilityKeep payment system instrument data tokenized and minimize PCI or sensitive-data scope. Apply risk decisions before irreversible effects and make provider failover policy explicit. Expose pending and unknown states instead of retrying blindly or showing a false success.
- Single provider versus multi-provider routingStart with one provider behind an adapter and add routing only when availability, geography, or cost justifies it. Failing over an unknown payment system outcome can double-charge unless reconciliation proves the first attempt’s result.
- Synchronous confirmation versus asynchronous completionReturn a durable pending state quickly and complete provider, webhook, and settlement work asynchronously. Holding an HTTP request open across provider and risk systems creates ambiguous retries and poor tail latency.
- Ledger-first versus provider-first stateMake the internal intent and ledger the source of truth for recorded effects while treating provider state as an external fact to reconcile. Letting a provider response directly mutate balances bypasses audit and correction controls.