Architecture notebook

Idempotent payments / Design note (conceptual)

What happens when the happy path ends?

A provider can accept a request while its response is lost. A reliable design keeps the payment pending, verifies the provider outcome, and reconciles before retrying the money movement.

  1. 01Client + key
  2. 02Payment record
  3. 03Provider adapter
  4. 04Reconciliation
Duplicate callback → deduplicate → apply valid transition

01 Problem: the response can disappear

A network timeout cannot tell us whether a provider accepted a payment. Retrying immediately can move money twice. The system needs to distinguish a rejected request from an unknown outcome.

02 Make the request durable

Scope an idempotency key to the caller and operation, enforce uniqueness in the database, and store a fingerprint of the request. Reusing a key with different input should fail. Persist a pending payment before calling a provider.

03 Handle callbacks as untrusted, repeatable events

Authenticate the provider callback, validate amount and currency, and deduplicate its event identifier. Apply allowed state transitions in a transaction using a lock or conditional update. A late pending event must not overwrite a confirmed success.

04 Reconcile before retrying

For an unknown outcome, query the provider or reconcile against settlement records. Use bounded retries with backoff for safe status checks. If the provider cannot confirm an outcome, route it to manual review instead of guessing.

05 Keep ledger updates atomic

Where a ledger is required, balanced debit and credit entries should commit together with the local payment transition. An outbox can publish follow-up events after that commit; consumers still deduplicate.

06 Trade-off: more states, more operational work

Durability buys correctness with extra states, reconciliation jobs, and a manual-review path. That cost is only worth paying where a duplicated or lost payment is unacceptable.