Idempotent Payment API Design
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
An idempotent payment API treats a payment attempt as a resource with its own ID and lifecycle. The client creates one operation key for one intended charge. The server scopes that key to the merchant or account, so a retry finds the same payment resource instead of creating another charge. A refund or a genuinely new payment attempt gets a new key because it is different work.
The server stores a canonical fingerprint of operation-defining input such as order, amount, currency, payee, and selected payment method reference. Money should use a fixed minor unit such as amountMinor: 1999, not binary floating point. If the same key later arrives with INR 2,999 instead of INR 1,999, the API rejects it rather than replaying or silently changing the original payment.
The service atomically reserves the key and creates payment P-83 in a durable state such as PENDING before sending a charge. This closes the local check-then-charge race and gives recovery code a stable record if the process stops. Only the reservation owner contacts the provider; concurrent retries inspect P-83 and follow its known state.
When the payment provider supports idempotency or a merchant operation reference, send a stable reference derived for P-83 and store the provider ID. Suppose the provider charges successfully but its response times out. The API leaves a recoverable uncertain state and checks the provider by that reference. It must not mark the attempt as safely failed and start a new charge merely because one network response disappeared.
A retry with the same key and fingerprint returns payment P-83 and its known outcome; it never creates P-84. The contract must define in-progress responses, key lifetime, final and retryable failures, status lookup, and reconciliation. Authorization, fraud checks, ledger correctness, audit history, and refund identity remain separate controls. Idempotency reduces duplicate payment effects, but it cannot prove that a payment was permitted or that every external system is consistent.
Idempotent Payment API Design
Direct Answer
An idempotent payment endpoint gives each payment attempt a client-generated operation key, binds it to the merchant and a canonical request fingerprint, reserves it atomically, and persists a payment state machine plus replayable response. Retries return the same payment outcome, while mismatched keys, concurrent calls, failures, and provider reconciliation follow documented rules.
Deep Dive & Architecture
A payment crosses at least two reliability boundaries: client to API, and usually API to payment provider. Either response can disappear after the work succeeds. Creating a local PENDING payment record before the provider call gives the service a durable identity to recover and reconcile.
The operation key identifies one payment attempt, not the shopping cart forever. A genuinely new attempt, such as trying another card after a confirmed failure, gets a new key. The request fingerprint prevents the old key from being reused for another order, amount, currency, or payee. Money should be stored in a fixed minor unit such as paise or cents, not binary floating point.
There are two separate duplicate risks. Atomic local reservation protects two client requests that arrive together. A stable provider operation reference protects a repeated downstream charge after the provider response is lost. Store both identities. If the provider times out, keep the payment in an uncertain or pending state and reconcile by that reference before another charge is allowed.
The replay record returns the same payment resource and known business outcome, even though a new trace ID or response date may appear. Key expiry must exceed the retry period promised to clients. Refunds are different operations and need their own identities. Ledger and audit entries should remain explicit records, not hidden changes made under the original payment key.
Code Example
POST /payments HTTP/1.1
Host: api.example.com
Authorization: Bearer <merchant-credential>
Idempotency-Key: 01J7QJWX4M8B8M2R3D7G9X1N5A
Content-Type: application/json
{"orderId":"O-42","amountMinor":1999,"currency":"INR"}
HTTP/1.1 201 Created
Location: /payments/P-83
Content-Type: application/json
{"id":"P-83","status":"pending"}
A retry uses the same key only for the same operation-defining body. A later refund or a genuinely new payment attempt receives its own key and resource identity.
Check your understanding
Answer these without looking back at the article.
- Why should the payment request use minor currency units?
- What should happen after a timeout from the payment provider?
- Why does a refund need a different idempotency identity?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.