Designing Reliable Payment APIs

Designing Reliable Payment APIs
Payment APIs are not CRUD with money attached. They are coordination systems that must remain correct when clients retry, workers crash, providers respond slowly, and humans investigate an incident weeks later.
Start with the state machine
The first design question is not which framework to use. It is which transitions are legal. A payment might move from pending to authorized, then to captured, failed, or canceled. Each transition needs an owner, an idempotency rule, and a record of why it happened.
created -> pending -> authorized -> captured
| |
v v
failed refunded
Do not let every handler update every status. Give each transition a narrow command and validate the current state before writing the next one. This makes illegal transitions visible instead of silently repairing them later.
Idempotency is a storage problem
An idempotency key is only useful when it is persisted with the result it represents. An in-memory map disappears on restart and a cache can expire while a client is still retrying.
A durable idempotency record should include:
- the merchant or account scope
- the client-provided key
- a request fingerprint
- the operation status
- the response or a reference to it
- timestamps and an expiry policy
The request fingerprint matters. If the same key is reused with a different amount or currency, return a conflict rather than replaying the old response for a new request.
Separate acceptance from settlement
A successful API response should communicate what the service actually knows. accepted is not the same as settled. A provider may acknowledge a request while the final settlement arrives through a webhook later.
That suggests two boundaries:
- Command path: validate the request, persist intent, and submit work.
- Reconciliation path: consume provider events, verify signatures, and converge local state.
The reconciliation path must be safe to run repeatedly. Store provider event IDs and make processing idempotent before applying a balance change.
Make failures observable
A payment incident needs an answer to three questions quickly: which request was made, which external operation was created, and what state do we believe now? Correlation IDs, provider references, structured transition logs, and a small audit table are more useful than a large volume of unstructured logs.
Track business metrics beside infrastructure metrics:
- authorization success rate
- time from authorization to capture
- duplicate-key conflicts
- webhook lag
- reconciliation mismatches
Closing principle
Reliable payment APIs are designed around invariants and recovery, not only around the happy path. If a team can explain every retry, transition, and reconciliation decision, the system is ready to earn trust.
Designing the command boundary
The command endpoint should do only the work needed to accept intent safely. Validate the merchant scope, normalize the amount and currency, reserve an idempotency key, and write the initial payment record in one transaction. Provider calls can happen after that boundary, because a slow provider must not hold an open database transaction.
Recovery is part of the design
A payment can be locally pending while the provider has already completed it. Reconciliation jobs should periodically compare provider state with local state, record the discrepancy, and apply a controlled transition. This is safer than asking support engineers to edit payment rows manually.