Compile Ready
All system design questions
System Design/Common Interview Questions

Payment Gateway

Design a payment gateway with idempotent charges, ledgers, reconciliation, and PCI compliance.

Advanced 60m interview 23m read High frequency Popularity 89
Stripe Amazon Uber

Problem Statement

Design a Payment Gateway like Stripe Payments, Amazon Pay, or Uber's internal payments platform. The system lets merchants create payment intents, authorize cards or wallets, capture funds, refund charges, and receive reliable status updates while integrating with external payment service providers, card networks, acquiring banks, fraud systems, and settlement rails.

At interview scale, assume thousands of merchants, global traffic spikes, strict correctness requirements, and many unreliable dependencies outside your control. The hard part is not only calling a PSP API; it is ensuring client retries do not double-charge users, maintaining an immutable double-entry ledger, coordinating state transitions across services, reconciling asynchronous webhooks and settlement files, and keeping card data out of your core systems.

The default design should optimize for correctness, auditability, fault isolation, and compliance. Latency matters for checkout conversion, but the system must prefer a delayed or pending payment over an ambiguous duplicate charge.

Business use case

A payment gateway is the money movement layer for marketplaces, subscriptions, ride sharing, food delivery, e-commerce, SaaS billing, and in-app purchases. It gives product teams a stable API for collecting money without forcing every product surface to understand card network rules, regional PSP behavior, fraud scoring, refunds, chargebacks, or PCI-DSS scope.

Businesses also need payment observability. Finance teams require immutable records, settlement reconciliation, fee accounting, and audit trails; operations teams need clear retry, failure, and dispute handling; merchants need webhooks and dashboards that explain where each payment is in the lifecycle.

Functional Requirements

  • Create a payment intent with amount, currency, merchant, customer, payment method, and idempotency key.

  • Authorize a payment method and place a hold without necessarily moving funds immediately.

  • Capture a full or partial authorized amount and transition the payment through captured and settled states.

  • Refund a full or partial captured payment while preserving original accounting history.

  • Tokenize or vault card data so the core gateway stores only tokens and non-sensitive metadata.

  • Integrate with multiple external PSPs, card networks, wallets, and acquiring banks through provider adapters.

  • Deliver merchant-facing webhooks for state changes with retry, signing, deduplication, and delivery history.

  • Run asynchronous reconciliation jobs against provider webhooks, settlement files, and the internal ledger.

Non-Functional Requirements

Correctness

The system must prevent duplicate charges under client retries, network timeouts, worker retries, and webhook replay. Idempotency keys, unique operation identifiers, ledger constraints, and reconciliation should make the externally visible financial effect happen once.

Latency

Checkout authorization should typically complete in 300ms to 800ms p95 when the selected PSP is healthy, excluding 3-D Secure or bank challenge flows. Internal API overhead should stay below 100ms p95 so most latency comes from unavoidable external payment rails.

Availability

Payment creation and status reads should target 99.99 percent availability, but the design should allow graceful degradation. If a PSP is down, route eligible traffic to another provider, return pending for uncertain operations, and continue serving status from internal state.

Auditability

Every money movement must have a durable audit trail. The ledger is append-only, every provider request and response is correlated, and state transitions record who or what caused them so finance can explain balances months later.

Consistency

The internal state machine, idempotency records, outbox events, and ledger entries need transactional boundaries. Cross-service and provider interactions use sagas with compensating actions because a single distributed ACID transaction across banks and PSPs is not realistic.

Compliance

Reduce PCI-DSS scope by using hosted payment fields, network tokens, and a dedicated vault. Core payment services should never log or persist raw PAN, CVV, or magnetic stripe data.

Operability

Retries must use bounded exponential backoff, dead-letter queues, correlation IDs, provider-specific dashboards, reconciliation alerts, and manual repair workflows for ambiguous financial states.

Capacity Estimation

Assumptions

Assume a mature global platform processing 100M payment attempts per day across cards, wallets, and bank methods. Average traffic is about 1,160 payment attempts per second, and peak checkout traffic is 10x average, or about 11,600 attempts per second.

Assume 85 percent of attempts are authorized, 80 percent of authorizations are captured, 5 percent of captured payments are refunded, and every payment attempt generates several durable writes: payment state, provider attempt, idempotency record, outbox event, and ledger entries. Keep seven years of transaction history for audit and finance.

Daily payment attempts

100M per day

Across all merchants and payment methods

Average payment TPS

1,160 attempts per second

100M divided by 86,400 seconds

Peak payment TPS

11,600 attempts per second

10x average peak during campaigns and regional rush hours

Authorization volume

85M authorizations per day

85 percent authorization success rate

Capture volume

68M captures per day

80 percent of authorized payments are captured

Refund volume

3.4M refunds per day

5 percent of captured payments later produce a refund

Ledger write volume

285M ledger entries per day

At least two entries for each capture and refund, plus authorization holds, fees, and reversals

Peak ledger writes

33,000 to 45,000 entries per second

Peak payments times several immutable ledger rows per operation

Transaction history storage

140 to 180 TB raw over seven years

Payment, attempt, webhook, and ledger history before replication and indexes

Webhook delivery volume

250M to 400M deliveries per day

State-change events plus retries to merchant endpoints

Calculations

  • Attempts: 100M attempts per day divided by 86,400 seconds is about 1,157 attempts per second, rounded to 1,160.
  • Peak: a 10x peak multiplier gives about 11,600 payment attempts per second.
  • Authorizations: 85 percent of 100M daily attempts gives 85M authorization successes per day.
  • Captures: 80 percent of 85M authorizations gives 68M captures per day.
  • Refunds: 5 percent of 68M captured payments gives about 3.4M refunds per day.
  • Ledger entries: every capture writes at least debit and credit entries, every refund writes at least debit and credit entries, and the system also writes authorization holds, fee entries, reversals, reserves, and clearing entries. A practical planning number is around 285M immutable ledger entries per day.
  • Ledger write TPS: 285M ledger entries per day divided by 86,400 seconds is about 3,300 ledger entries per second on average. With a 10x peak and extra fee or reserve lines, design for roughly 33,000 to 45,000 ledger writes per second.
  • Storage: if payment intent, provider attempt, webhook, audit, and ledger data average about 2KB per payment attempt after indexing metadata is excluded, 100M attempts per day creates about 200GB raw per day. Seven years is about 511TB for the full history. With tiering, compaction, and cold object storage, keep roughly 140 to 180TB in queryable raw operational stores and archive the rest in cheaper immutable storage. Replication and indexes can multiply this by 3x or more.
  • Webhooks: a payment can emit created, authorized, captured, settled, failed, and refunded events. If the average is 2.5 to 4 merchant deliveries per attempt including retries, expect 250M to 400M webhook delivery attempts per day.

API Design

POST/api/v1/payment-intents

Creates a payment intent. The merchant must send an idempotency key so retries after timeouts return the original result instead of creating a second payable object.

Request


Idempotency-Key: idem_merchant_123_order_987

{
  "merchantId": "mer_123",
  "customerId": "cus_456",
  "amountMinor": 12500,
  "currency": "USD",
  "captureMethod": "manual",
  "paymentMethodToken": "tok_card_789",
  "orderReference": "order_987"
}

Response


{
  "paymentIntentId": "pi_123",
  "state": "created",
  "amountMinor": 12500,
  "currency": "USD",
  "captureMethod": "manual",
  "nextAction": "authorize"
}
  • 201Created
  • 200Existing idempotent result returned
  • 400Invalid amount, currency, merchant, or payment method
  • 409Idempotency key reused with different request body
  • 429Merchant or client rate limit exceeded
POST/api/v1/payment-intents/{paymentIntentId}/authorize

Runs fraud checks and asks the selected provider or acquiring bank to authorize the payment method. The operation is idempotent at both the gateway and provider adapter layers.

Request


Idempotency-Key: idem_authorize_pi_123

{
  "merchantId": "mer_123",
  "clientContext": {
    "ipAddress": "203.0.113.8",
    "deviceId": "dev_abc",
    "userAgent": "mobile-app"
  }
}

Response


{
  "paymentIntentId": "pi_123",
  "state": "authorized",
  "authorizationId": "auth_456",
  "provider": "psp_a",
  "authorizedAmountMinor": 12500,
  "expiresAt": "2026-07-31T12:54:46Z"
}
  • 200Authorized or existing authorization returned
  • 202Pending external authentication or provider callback
  • 402Card declined or insufficient funds
  • 409Payment intent is not in an authorizable state
  • 503No healthy provider route available
POST/api/v1/payment-intents/{paymentIntentId}/capture

Captures funds for an authorized payment. Capture writes immutable ledger entries and can be full or partial depending on merchant policy and provider support.

Request


Idempotency-Key: idem_capture_pi_123

{
  "merchantId": "mer_123",
  "amountMinor": 12500,
  "currency": "USD"
}

Response


{
  "paymentIntentId": "pi_123",
  "state": "captured",
  "captureId": "cap_789",
  "capturedAmountMinor": 12500,
  "ledgerTransactionId": "ltx_123"
}
  • 200Captured or existing capture returned
  • 202Capture submitted and waiting for provider confirmation
  • 400Invalid amount or currency
  • 409Payment intent is not authorized or has already failed
POST/api/v1/refunds

Creates a full or partial refund against a captured payment. Refunds are modeled as new money movement operations with their own idempotency keys and ledger entries.

Request


Idempotency-Key: idem_refund_pi_123_1

{
  "paymentIntentId": "pi_123",
  "merchantId": "mer_123",
  "amountMinor": 5000,
  "reason": "customer_request"
}

Response


{
  "refundId": "re_123",
  "paymentIntentId": "pi_123",
  "state": "refund_pending",
  "amountMinor": 5000
}
  • 201Refund created
  • 200Existing idempotent refund returned
  • 400Invalid refund amount
  • 409Refund exceeds captured or refundable amount
GET/api/v1/payment-intents/{paymentIntentId}

Returns the gateway's current view of the payment state, provider references, ledger transaction references, and next actions. Merchants use this when webhooks are delayed or lost.

Response


{
  "paymentIntentId": "pi_123",
  "state": "settled",
  "amountMinor": 12500,
  "currency": "USD",
  "authorizationId": "auth_456",
  "captureId": "cap_789",
  "settlementBatchId": "set_20260726_01"
}
  • 200Payment intent returned
  • 401Authentication required
  • 403Merchant cannot access this payment
  • 404Payment intent not found
POST/api/v1/webhooks/{provider}

Receives asynchronous provider events for authorization, capture, settlement, refund, chargeback, and failure updates. The endpoint verifies signatures and deduplicates provider event IDs before updating internal state.

Request


Provider-Signature: sig_abc

{
  "providerEventId": "evt_123",
  "providerPaymentId": "psp_pay_456",
  "eventType": "capture.succeeded",
  "occurredAt": "2026-07-26T12:54:46Z"
}

Response


{
  "received": true,
  "deduplicated": false
}
  • 200Webhook accepted
  • 202Webhook stored for asynchronous processing
  • 400Malformed event
  • 401Invalid provider signature

Every mutating endpoint accepts an idempotency key scoped to merchant, operation, and request fingerprint. The gateway stores the first result and returns it for identical retries. Reusing the same key with a different amount, currency, or payment method must fail because that is usually a client bug.

The public API should expose stable payment intent states rather than raw provider codes. Provider-specific failures are normalized internally while preserving the raw response for debugging, disputes, and reconciliation.

Database Design

The core data model separates mutable workflow state from immutable accounting. Payment intent and attempt records track the operational state machine, while ledger entries are append-only and never updated in place. Provider events, idempotency keys, and outbox messages make retries and asynchronous processing safe.

Use a transactional relational database or strongly consistent distributed SQL store for payment state, idempotency, and ledger writes within one shard. Partition by merchant or payment intent for scale, but keep all rows needed for a single payment operation in the same transactional boundary whenever possible.

payment_intents
payment_intent_iduuidPrimary key for the merchant-facing payment object
merchant_iduuidTenant and ownership boundary
customer_iduuid nullableOptional buyer identity
amount_minorbigintAmount in the smallest currency unit
currencychar(3)ISO currency code
statevarchar(32)created, authorized, captured, settled, refunded, failed
capture_methodvarchar(16)automatic or manual
payment_method_token_iduuidReference to vaulted payment credentials
created_attimestampCreation time
updated_attimestampLast state transition time
idempotency_keys
merchant_iduuidScope for key uniqueness
idempotency_keyvarchar(128)Client supplied operation key
request_hashchar(64)Hash of method, path, and normalized body
resource_iduuid nullablePayment, capture, or refund created by this key
status_codeint nullableStored response status for safe replay
response_bodyjson nullableStored response for identical retries
expires_attimestampRetention window for retry safety
payment_attempts
attempt_iduuidPrimary key for one provider operation
payment_intent_iduuidParent payment intent
operationvarchar(24)authorize, capture, refund, void
providervarchar(64)Selected PSP or acquiring route
provider_request_idvarchar(128)Gateway generated idempotent provider request identifier
provider_referencevarchar(128) nullableExternal provider payment or transaction ID
statevarchar(32)pending, succeeded, failed, ambiguous
retry_countintNumber of retry attempts
next_retry_attimestamp nullableBackoff schedule for retry worker
ledger_entries
ledger_entry_iduuidPrimary key for an immutable ledger line
ledger_transaction_iduuidGroups debit and credit entries that must balance
merchant_iduuidTenant and accounting partition
account_idvarchar(64)Customer payable, merchant receivable, clearing, fee, reserve, or cash account
directionvarchar(6)debit or credit
amount_minorbigintPositive amount in smallest currency unit
currencychar(3)Currency for this ledger entry
source_typevarchar(32)capture, refund, settlement, chargeback, fee
source_iduuidPayment attempt or reconciliation source
created_attimestampAppend-only creation time
provider_events
providervarchar(64)Source PSP or banking partner
provider_event_idvarchar(128)Unique event ID for deduplication
provider_referencevarchar(128)External payment, capture, or refund reference
event_typevarchar(64)Provider event name
payload_hashchar(64)Integrity and deduplication helper
processed_attimestamp nullableNull until the event has updated internal state
received_attimestampGateway receive timestamp

Indexes

  • idempotency_keys.merchant_id, idempotency_key is unique and must be checked before mutating state.
  • payment_intents.merchant_id, created_at supports merchant dashboards and customer support lookups.
  • payment_attempts.provider, provider_request_id is unique so provider retries cannot create duplicate attempts.
  • payment_attempts.provider_reference supports webhook correlation from external providers.
  • ledger_entries.ledger_transaction_id supports balance checks that every ledger transaction has equal debits and credits.
  • provider_events.provider, provider_event_id is unique for webhook replay protection.

Relationships

A payment intent has many payment attempts because authorization, capture, refund, and retry operations are separate. Each successful money movement references one balanced ledger transaction with two or more immutable entries. Provider events may arrive before or after synchronous responses, so they correlate through provider_reference and can update the same payment attempt idempotently.

NoSQL alternatives

Pure NoSQL storage is risky for the ledger because financial correctness needs transactions, uniqueness constraints, and balance invariants. A common production split is distributed SQL for idempotency, state, and ledger records; a log or queue for outbox events; object storage for raw provider payloads and settlement files; and an OLAP store for analytics and merchant reporting.

High-Level Architecture

Drag to pan · Ctrl/⌘ + scroll to zoom

The critical synchronous path is merchant backend to payment API to orchestrator to idempotency store, fraud decision, token vault, payment state database, provider adapter, and ledger. Asynchronous webhook, reconciliation, and merchant notification work is isolated behind the event queue.

The Payment Orchestrator owns the state machine: created -> authorized -> captured -> settled -> refunded or failed. It should transition state only through validated commands, and each transition should be persisted with a correlated attempt, provider reference, audit record, and outbox event.

The idempotency store is in the synchronous path because duplicate charges are worse than slow checkout. It stores the request fingerprint and the first terminal or pending response, allowing clients and workers to retry safely. Provider adapters also forward stable request identifiers to PSPs so external calls are idempotent when the provider supports it.

The ledger is the accounting source of truth, not a reporting cache. Successful captures, refunds, chargebacks, fees, reserves, and settlements produce immutable balanced ledger transactions. Webhooks and reconciliation workers can update operational states, but they never rewrite old ledger history; they append corrections.

Request Flow

  1. 1

    Merchant creates a payment intent

    The merchant backend receives a checkout action from the buyer and calls the payment API with amount, currency, payment method token, order reference, and an idempotency key. The gateway authenticates the merchant, rate-limits the request, hashes the normalized payload, and inserts or reads the idempotency record before creating anything new.

  2. 2

    Card data stays inside the PCI boundary

    The buyer's raw card data is collected through hosted fields, a mobile SDK, network tokenization, or a dedicated vault. The core gateway receives only a payment method token, last four digits, card brand, and expiry metadata, which greatly reduces PCI-DSS scope for the orchestrator, ledger, and merchant systems.

  3. 3

    Fraud and risk checks run before authorization

    The orchestrator calls a fraud service with merchant, customer, device, IP, amount, velocity, and historical dispute signals. Low-risk transactions continue, high-risk transactions fail or require step-up authentication, and uncertain transactions may be routed to a provider that supports stronger authentication.

  4. 4

    Authorization saga starts

    The orchestrator persists the payment intent in created state, creates a payment attempt, writes an outbox event, and calls the selected provider adapter with a stable provider request ID. If the merchant retries the same authorization key, the idempotency store returns the original attempt rather than calling the provider again.

  5. 5

    External provider responds or times out

    A successful provider response moves the payment to authorized and stores the provider authorization reference. A clear decline moves it to failed. A timeout or transport failure is ambiguous, so the attempt remains pending or ambiguous until webhook, status polling, or reconciliation determines the true provider outcome.

  6. 6

    Capture creates immutable accounting entries

    For automatic capture, the orchestrator immediately captures after authorization; for manual capture, the merchant calls capture later. On capture success, the ledger appends balanced debit and credit entries for customer payable, merchant receivable, clearing, platform fee, and reserves as needed. The payment moves to captured only when state and ledger writes commit together.

  7. 7

    Webhooks update state asynchronously

    PSPs and acquiring banks send events for authorization, capture, settlement, refund, chargeback, and failure. The webhook endpoint verifies signatures, stores the event with a unique provider event ID, and workers process it idempotently. Replayed webhooks are acknowledged without reapplying state or ledger effects.

  8. 8

    Reconciliation closes gaps

    Scheduled jobs fetch provider transaction reports and settlement files, compare them against payment attempts and ledger balances, and flag missing webhooks, amount mismatches, duplicate provider references, and unsettled captures. Differences produce adjustment ledger entries or manual investigation tasks.

  9. 9

    Refunds and failures use compensating actions

    Refunds are separate idempotent operations that call the provider, update payment state, and append reversing ledger entries. If capture succeeds but merchant order fulfillment fails, the saga issues a refund or void depending on state. The system never deletes the original charge history.

Core Components

Payment API Gateway

Exposes stable merchant APIs for payment creation, authorization, capture, refunds, and status reads.

The gateway handles merchant authentication, rate limiting, request validation, API versioning, and routing to the orchestrator. It requires idempotency keys for mutating operations and rejects key reuse with a different request fingerprint.

Payment Orchestrator

Owns the payment state machine and coordinates sagas across internal and external systems.

The orchestrator validates legal state transitions, selects provider routes, calls fraud and token services, writes payment attempts, emits outbox events, and decides whether to return succeeded, failed, or pending. It treats external PSP calls as unreliable and recoverable.

Idempotency Store

Prevents duplicate financial effects under retries.

This store maps merchant and idempotency key to request hash, resource ID, status, and response. It should be strongly consistent and transactionally coupled with initial resource creation. It allows safe retry after client timeouts, API gateway retries, and worker restarts.

Provider Adapter Layer

Normalizes integrations with PSPs, card networks, wallets, and acquiring banks.

Adapters translate internal authorize, capture, void, refund, and status operations into provider-specific APIs. They manage provider idempotency keys, response normalization, timeouts, circuit breakers, retry policy, and routing metadata.

Token Vault

Stores sensitive payment credentials behind a strict PCI-DSS boundary.

The vault holds encrypted PAN or network token material, exposes opaque tokens to the core gateway, enforces least privilege, rotates keys, and prevents raw card data from entering logs, analytics, support tools, or merchant databases.

Fraud Service

Scores payment risk before committing to external authorization.

Fraud checks combine rules, velocity counters, device fingerprinting, merchant risk, historical disputes, BIN country, IP geography, and machine learning scores. The service can approve, decline, challenge, review, or route the transaction to stronger authentication.

Double-Entry Ledger

Provides immutable accounting truth for captures, refunds, fees, reserves, and settlements.

Every ledger transaction must balance: total debits equal total credits for the same currency. Rows are append-only, corrections are new entries, and database constraints or posting services prevent partial ledger writes.

Webhook and Reconciliation Workers

Handle asynchronous provider truth and merchant notification delivery.

Workers process provider webhooks, retry provider operations, deliver merchant webhooks, ingest settlement files, compare external and internal records, and route poison messages to dead-letter queues with enough context for repair.

Deep Dive

Idempotency and exactly-once charge semantics

Payment systems cannot rely on clients or networks to send a request exactly once. A buyer may tap twice, a mobile app may retry after a timeout, an API gateway may retry a 502, or a worker may crash after calling the provider but before storing the response. Without idempotency, any of those cases can double-charge the customer.

The gateway requires an idempotency key for every mutating operation. The key is scoped to merchant, operation, and request fingerprint. The first request inserts a key record and creates the resource inside the same transaction. Identical retries return the stored resource or response. A retry with the same key but a different amount, currency, or payment method fails with a conflict.

Exactly-once in payments is an effect, not a single network guarantee. Internally, the ledger enforces unique source IDs so a capture or refund can post once. Externally, provider adapters send stable provider request IDs and use PSP idempotency APIs when available. When the provider result is ambiguous, the gateway marks the attempt pending and reconciles through status polling, webhooks, or settlement files instead of issuing a blind second charge.

Payment state machine and saga consistency

A clear state machine prevents illegal transitions. The core path is created -> authorized -> captured -> settled. Terminal or side states include failed, canceled, refunded, partially_refunded, chargeback_open, and chargeback_lost. Authorization reserves funds, capture starts money movement, settlement confirms funds reached the acquiring or platform account, and refunds reverse part or all of a captured payment.

No real gateway can hold a distributed transaction across merchant order service, fraud service, card network, acquiring bank, ledger, and webhook delivery. Use a saga. Each step persists local state and an outbox event before calling the next dependency. If a later step fails, the system performs compensating actions: void an authorization, refund a capture, cancel fulfillment, or open a manual review.

For operations that need local atomicity, use a two-phase local pattern: prepare the state transition and outbox record in one transaction, then have a worker perform the external call, then commit the final state and ledger effects after confirmed success. This avoids losing work when a process crashes between database commit and network call.

Double-entry ledger and immutable accounting

The operational payment state answers what the checkout is doing now; the ledger answers where the money is. A ledger transaction contains two or more entries and must balance by currency. For a capture, the system may debit a customer clearing account and credit merchant receivable, platform fee revenue, tax liability, and reserve accounts. For a refund, it appends reversing entries rather than editing the original capture.

Immutability matters because finance, auditors, and regulators need a history that cannot be silently rewritten. If a provider later reports a fee adjustment, chargeback, or settlement mismatch, the gateway posts an adjustment ledger transaction with a reason and source reference. Reports are derived from ledger entries, not from mutable payment status rows.

The ledger posting service should enforce uniqueness on source_type and source_id, currency balance checks, positive amounts, monotonic sequence numbers per account, and append-only permissions. Reads can be served from replicas or OLAP stores, but writes should go through one audited posting path.

External PSP, card network, and acquiring bank integration

External payment rails are heterogeneous and imperfect. One PSP may support separate authorize and capture; another may only support sale. Some return synchronous declines, some rely heavily on webhooks, and some produce settlement files hours later. Network timeouts do not mean the payment failed; they mean the gateway does not yet know.

Provider adapters isolate this complexity. They map internal operations to provider APIs, maintain provider-specific idempotency keys, translate error codes into normalized decline or retry categories, and expose capabilities such as partial capture, void, refund, 3-D Secure, network token support, and settlement timing.

Routing should consider merchant configuration, currency, country, card brand, provider health, cost, approval rate, and risk. Failover is safe before authorization. After an authorization has been created at one provider, capture and refund usually need to return to the same provider reference, so routing decisions become sticky.

Webhooks, reconciliation, retries, and dead letters

Provider webhooks are asynchronous, duplicated, delayed, and sometimes out of order. The gateway should verify signatures, persist the raw payload, deduplicate by provider event ID, and process state changes idempotently. A capture webhook received before an authorization webhook should either be buffered or applied through a transition rule that can tolerate missing earlier events.

Retries need bounded exponential backoff with jitter. Retry only safe operations using provider idempotency keys. Distinguish retryable failures such as timeouts, 429, and 503 from hard declines such as stolen card or insufficient funds. After the retry budget is exhausted, the message goes to a dead-letter queue with merchant, payment, provider, request ID, payload hash, and last error.

Reconciliation is the safety net. Jobs compare internal attempts and ledger entries with provider status APIs, webhook logs, chargeback feeds, and settlement reports. The job should identify missing captures, duplicate provider references, unsettled payments, fee mismatches, and ledger transactions that do not match settlement totals.

PCI-DSS scope reduction and fraud controls

The safest core gateway is one that never sees raw card data. Use hosted payment fields, mobile SDK tokenization, network tokens, or a dedicated vault service so the main payment API works with opaque tokens. Mask PAN to last four digits, never store CVV, encrypt sensitive metadata, and block sensitive fields from logs and analytics.

Fraud is part of the payment decision, not an afterthought. Score before authorization using velocity, device, IP, merchant, BIN, customer age, historical disputes, amount anomalies, and card testing patterns. High-risk traffic can be challenged, blocked, routed through stronger authentication, or throttled through rate limits.

PCI and fraud design also improve reliability. By keeping tokenization and risk evaluation explicit, the orchestrator can make deterministic decisions, explain declines, and replay safe operations without exposing sensitive data or re-running non-idempotent card collection flows.

Scaling

Prototype: one region and one provider

Start with one API service, one relational database, one PSP adapter, hosted tokenization, and a basic ledger posting service. At this scale, 100 payment TPS is achievable with simple vertical database scaling, but still require idempotency keys, unique provider request IDs, and append-only ledger rows from day one.

Growth: multiple workers and asynchronous webhooks

At 1,000 payment TPS, split synchronous API traffic from webhook, retry, and reconciliation workers. Add an outbox table, event queue, dead-letter queues, fraud service, provider health checks, and merchant webhook delivery retries. Read replicas can serve dashboards and support tools.

Regional scale: sharded state and ledger partitions

At 5,000 to 10,000 payment TPS in one region, partition payment state by merchant or payment intent, scale ledger posting by account shard, isolate high-volume merchants, and move raw provider payloads to object storage. Keep idempotency, payment attempt, and ledger writes for one operation in the same shard where possible.

Global scale: active-active API with regional rails

At 25,000 peak payment TPS globally, route merchants to home regions, keep payment operations sticky to the region that created the provider reference, and replicate read models globally. Use regional PSP adapters and acquiring routes because payment methods, currencies, regulations, and provider performance differ by country.

Enterprise scale: routing optimization and finance automation

At very large scale, add approval-rate routing, cost-aware provider selection, automated chargeback workflows, near-real-time settlement reconciliation, ledger export pipelines, merchant-level rate isolation, and data retention tiers that keep hot history queryable while archiving old immutable records.

Bottlenecks & Optimizations

Idempotency store contention

Hot merchants can overload a single idempotency partition during retries or flash sales. Scope keys by merchant, hash partition within high-volume merchants, keep records compact, expire old keys after the safe retry window, and avoid storing large response bodies inline when object storage pointers are enough.

External provider latency and outages

Use short internal timeouts, provider-specific circuit breakers, health-based routing, and pending states for ambiguous outcomes. Fail over only before an operation creates a provider reference; after that, use status polling and reconciliation instead of blindly repeating the operation elsewhere.

Ledger write throughput

Ledger writes multiply payment volume because every money movement creates multiple entries. Partition by merchant or account, batch non-user-facing fee postings, keep the posting path append-only, and serve reporting from replicas or analytical projections rather than the primary ledger writer.

Webhook retry storms

Merchant endpoints often fail during incidents. Use per-merchant delivery queues, exponential backoff with jitter, retry budgets, circuit breakers, signed replay APIs, and dead-letter queues so one merchant cannot consume the entire webhook worker fleet.

Fraud service false positives and latency

Cache low-risk merchant configuration, precompute velocity features, bound synchronous scoring latency, and allow risk decisions such as challenge or review instead of only approve or decline. Track approval rate and dispute rate by rule version.

Large reconciliation scans

Use provider settlement file checkpoints, partition reconciliation by provider, merchant, currency, and settlement date, and store normalized external references. Reconcile incrementally instead of scanning all historical payments every run.

Failure Handling

Client retries after checkout timeout

The merchant reuses the same idempotency key. The gateway returns the already-created payment intent, authorization, capture, or refund response. If the first attempt is still pending, return pending with the same resource ID rather than creating a new attempt.

Provider call times out after the bank may have authorized

Mark the payment attempt ambiguous, stop automatic duplicate authorization, and schedule status polling with the same provider request ID. Resolve through provider status API, webhook, or settlement reconciliation. If still unknown after the allowed window, escalate to manual review.

Webhook arrives twice or out of order

Deduplicate by provider event ID and process transitions idempotently. If an event references an unknown provider payment, store it in a pending correlation table and retry after related events or status polling populate the provider reference.

Ledger posting fails after provider capture succeeds

Keep the payment in a captured_pending_accounting state, block settlement payout, and retry ledger posting through the outbox. Because provider capture succeeded, do not retry capture. If ledger posting cannot complete automatically, create a finance repair task.

Merchant webhook endpoint is down

Persist the event, retry with exponential backoff and jitter, expose webhook delivery status in the merchant dashboard, and move exhausted deliveries to a dead-letter queue. Merchants can fetch payment status through the API while webhooks are delayed.

Reconciliation finds a provider settlement mismatch

Freeze affected payouts if necessary, create a reconciliation case, attach provider file lines and internal ledger references, and post adjustment entries only after the discrepancy is classified. Never mutate historical ledger entries to force totals to match.

Security

PCI-DSS scope reduction

Use hosted fields, mobile SDK tokenization, and a dedicated vault so raw PAN and CVV never enter the core gateway. Mask card metadata, encrypt sensitive fields, restrict vault access, and run separate logging controls for PCI systems.

Tokenization and vaulting

Payment method tokens should be opaque, scoped to merchant or platform policy, and revocable. Store network tokens or vault references instead of card numbers, rotate encryption keys, and keep token lookup permissions separate from payment state permissions.

Webhook authenticity

Verify provider and merchant webhook signatures, reject stale timestamps, deduplicate event IDs, and store payload hashes. Outbound merchant webhooks should be signed so merchants can verify they came from the gateway.

Fraud and abuse prevention

Rate-limit card testing, monitor high-decline merchants, detect velocity spikes, enforce merchant risk tiers, and integrate dispute feedback into risk scoring. Protect refund APIs because refund abuse directly moves money out.

Least privilege and audit

Separate permissions for support, finance, risk, engineering, and automated workers. High-risk actions such as manual refund, payout freeze, or ledger adjustment require strong authentication, approval workflows, and immutable audit logs.

Data privacy

Minimize buyer data in payment records, redact logs, apply retention rules, and isolate merchant tenants. Reporting exports should avoid exposing full payment method metadata or unnecessary personal data.

Tradeoffs

Pros

  • +Idempotency at every mutating layer protects buyers and merchants from duplicate financial effects.
  • +The state machine plus saga model makes external uncertainty explicit and recoverable.
  • +An immutable double-entry ledger gives finance-grade auditability and reliable settlement reporting.
  • +Provider adapters isolate PSP complexity and allow routing based on health, cost, region, and approval rate.
  • +Tokenization and vaulting reduce PCI-DSS scope for most core services.

Cons

  • Strong correctness adds latency, storage, and implementation complexity compared with a thin PSP proxy.
  • Ambiguous provider outcomes require pending states, reconciliation jobs, and support tooling.
  • A double-entry ledger is harder to build than mutable balance columns and demands strict operational discipline.
  • Multi-provider routing becomes sticky after authorization, limiting failover options for captures and refunds.
  • Fraud checks can reduce conversion if rules are too aggressive or scoring is slow.

Alternatives

Alternative one is a thin PSP wrapper. It is fast to launch and relies on the PSP for idempotency, vaulting, ledger-like reports, and webhooks. It is suitable for a small merchant but weak for marketplaces, multi-provider routing, and custom finance controls.

Alternative two is a wallet or stored-value system where users preload balances and purchases move internal money first. This can reduce card authorization latency for repeat users, but it adds regulatory, custody, and ledger complexity.

Alternative three is direct acquiring integration without a third-party PSP. It can reduce cost and improve control at massive scale, but it requires deeper compliance, certification, dispute handling, and bank relationships.

When not to use this design

Do not build a full payment gateway if the product only needs occasional low-volume checkout in one country. A hosted PSP checkout can provide compliance, fraud tools, and payment method coverage with far less engineering and operational risk. Build the gateway layer when you need multi-merchant orchestration, high volume, custom ledgering, routing control, or deep product integration.

Follow-up Questions

How does the system prevent double charges when the client retries?

Require an idempotency key on every mutating request, store the request fingerprint and first response in a strongly consistent table, and reuse stable provider request IDs. Identical retries return the original resource or pending result. Reuse with different parameters fails.

What do you do if the provider times out?

Treat the outcome as unknown, not failed. Mark the attempt ambiguous or pending, stop duplicate external attempts, poll the provider with the original request ID, wait for webhooks, and let reconciliation confirm the final state. Blind retry without idempotency can double-charge.

Why is a double-entry ledger necessary?

Payments need auditable money movement, not just status flags. Double-entry accounting ensures every debit has matching credits by currency, corrections are append-only, and finance can reconcile customer charges, merchant receivables, fees, reserves, refunds, chargebacks, and settlements.

How would you model partial capture and partial refund?

Keep the original payment intent amount, track captured and refunded totals, and create separate capture or refund attempts with their own idempotency keys. Ledger entries represent each money movement. State can become partially_captured or partially_refunded until the remaining amount is captured, voided, or refunded.

How do webhooks and reconciliation differ?

Webhooks are near-real-time provider notifications but can be duplicated, delayed, or lost. Reconciliation is the periodic source-of-truth check against provider status APIs and settlement files. A reliable gateway uses both.

How do you reduce PCI-DSS scope?

Collect card data through hosted fields, SDKs, network tokenization, or a dedicated vault. Core services store only opaque payment method tokens and masked metadata, block sensitive fields from logs, and tightly restrict vault access.

Can the gateway fail over to another PSP after an authorization timeout?

Usually not immediately. The original PSP may have authorized the card even though the gateway timed out. Failover is safe before creating a provider reference, but after an ambiguous authorization the system should query or reconcile the original PSP before trying another route.

Company Variations

Stripe

Stripe-style interviews emphasize API semantics, PaymentIntent state modeling, idempotency, webhooks, ledger correctness, merchant developer experience, and compliance boundaries. Be ready to explain why ambiguous outcomes become pending instead of failed.

Amazon

Amazon interviewers often probe scale, multi-tenant isolation, DynamoDB or distributed SQL partitioning, operational alarms, reconciliation, and blast-radius control. Discuss how flash-sale traffic, provider outages, and ledger write volume are handled without affecting all merchants.

Uber

Uber may frame payments around trips, eats orders, regional payment methods, authorization before service, capture after completion, partial refunds, driver payouts, and operational recovery. Emphasize sagas across order, trip, payment, ledger, and payout services.

Interview Tips

Lead with correctness before scale. Draw the state machine and synchronous authorization path, then add idempotency, fraud, token vault, provider adapters, ledger, webhooks, and reconciliation. When discussing tradeoffs, emphasize that payment systems should prefer pending and recoverable states over ambiguous duplicate money movement.

What interviewers expect

  • State concrete volume assumptions for payment TPS, ledger writes, webhooks, and storage.
  • Separate synchronous checkout from asynchronous webhooks, retries, and reconciliation.
  • Use idempotency keys and provider request IDs at every mutating boundary.
  • Model the payment lifecycle and legal transitions explicitly.
  • Explain double-entry accounting and why ledger entries are append-only.
  • Discuss PSP integration, fraud checks, PCI scope reduction, and operational repair workflows.

Common mistakes

  • !Calling the PSP directly on every retry without an idempotency key.
  • !Treating provider timeout as a definitive failure.
  • !Using mutable balance columns instead of an immutable double-entry ledger.
  • !Ignoring webhook replay, out-of-order events, and settlement reconciliation.
  • !Letting raw card data flow through ordinary application services or logs.
  • !Assuming a distributed transaction is possible across banks, PSPs, and internal services.

Red flags

  • ×No state machine for created, authorized, captured, settled, refunded, and failed.
  • ×No explanation of how duplicate charges are prevented.
  • ×No ledger or audit trail for money movement.
  • ×No plan for ambiguous external outcomes.
  • ×No PCI-DSS or tokenization discussion.
  • ×No retry, backoff, dead-letter, or reconciliation strategy.

Revision Notes

  • Payment lifecycle: created -> authorized -> captured -> settled, with refunded and failed as terminal or side outcomes.
  • Idempotency keys are mandatory for create, authorize, capture, and refund. Store request fingerprints and first responses; reject mismatched reuse.
  • Exactly-once charge semantics come from layered controls: gateway idempotency, provider idempotency keys, unique ledger source IDs, pending states for ambiguity, and reconciliation.
  • A PSP timeout is unknown, not failed. Poll, wait for webhook, or reconcile before retrying externally.
  • The ledger is append-only and double-entry. Every money movement balances debits and credits by currency.
  • Webhooks are untrusted operational hints until verified, deduplicated, and reconciled.
  • Reconciliation compares provider status, settlement files, payment attempts, and ledger entries to find missing or mismatched money movement.
  • PCI-DSS scope is reduced through hosted fields, tokenization, vaulting, encryption, masking, and strict logging controls.
  • Fraud checks should happen before authorization and should consider velocity, device, merchant, buyer, amount, geography, and dispute history.
  • At 100M attempts per day, plan for about 1,160 average payment TPS, about 11,600 peak TPS, and tens of thousands of ledger writes per second at peak.

Flashcards

Quiz

0/7 answered

  1. 1.What should the gateway do when a client retries the same capture request with the same idempotency key?

  2. 2.A PSP authorization call times out after the request was sent. What is the safest internal state?

  3. 3.Which design best supports auditable accounting?

  4. 4.Why should raw card data avoid the core payment services?

  5. 5.Which mechanism handles provider webhooks that are duplicated?

  6. 6.At 100M payment attempts per day, what is the approximate average payment TPS?

  7. 7.What should happen after retries for a webhook delivery are exhausted?

Cheat Sheet

Goal: provide a safe merchant API for authorizing, capturing, refunding, settling, and reconciling payments across unreliable external payment rails.

State machine: created -> authorized -> captured -> settled. Side states include failed, canceled, refunded, partially_refunded, chargeback_open, and ambiguous.

Idempotency: every mutating operation requires a merchant-scoped idempotency key. Store request fingerprint, resource ID, status, and response. Replays return the same result; mismatched reuse fails.

Provider calls: send stable provider request IDs, use PSP idempotency when available, classify errors as hard decline, retryable, or ambiguous, and never blindly retry unknown outcomes.

Ledger: append-only double-entry accounting. Captures, refunds, fees, reserves, chargebacks, and settlements create balanced entries. Corrections are new entries, never edits.

Capacity: 100M attempts per day is about 1,160 average payment TPS and about 11,600 peak TPS at 10x. Ledger writes can reach 33,000 to 45,000 entries per second at peak.

Async work: provider webhooks, merchant webhooks, retries, dead letters, settlement ingestion, and reconciliation run behind queues and workers.

Reconciliation: compare provider status, webhook logs, settlement files, payment attempts, and ledger entries. Use it to resolve missing webhooks, duplicate references, and fee or settlement mismatches.

Compliance and fraud: use hosted fields, tokenization, vaulting, encryption, masking, least privilege, webhook signatures, velocity checks, device signals, merchant risk tiers, and card testing protection.

References