#22 Transactional Integrity
Credit Card Processing
Take a card payment through authorisation, capture and settlement, exactly once, while handling card data safely.
What is being asked
Design the service a merchant calls to charge a card. It must talk to card networks through an acquiring bank, never charge twice, keep an exact record of every movement of money, and limit how much of the system ever sees card numbers.
Requirements
Functional
- Authorise, capture, void and refund.
- Report the status of each payment and reconcile with the bank's settlement files.
Non-functional
- Correctness above all: no double charges, no lost charges.
- Card data handled under PCI DSS rules.
- High availability, since a failed payment is lost revenue.
Design
- Card details go from the customer's browser straight to a tokenisation vault, which returns a token. The rest of the system only ever handles the token.
- The merchant calls the API with an idempotency key. The API stores the key with the request; a repeat returns the stored result.
- A payment moves through explicit states: created, authorised, captured, settled, or failed, refunded. Each transition is recorded before and after the call to the bank.
- Every money movement is written to an append-only double-entry ledger.
- A daily job compares the ledger with the bank's settlement file and flags differences.
Key decisions and trade-offs
- Idempotency keys are essential. Networks time out; the client cannot know whether the charge happened, so a retry must be safe.
- Unknown outcomes. If the bank call times out, the payment is "pending" and resolved by querying the bank or by reconciliation. Never assume failure and charge again.
- Isolate card data. A small vault in its own network keeps most services out of PCI scope.
- Ledger over balances. Append-only entries can be audited and re-summed; an updated balance column cannot.
Follow-up questions
- How do you handle a refund larger than the remaining captured amount?
- What happens if the database write succeeds but the bank call fails?
- How would you route to a second acquirer when the first is down?
- How do you store idempotency keys, and for how long?