Skip to content

What payment systems taught me about reliability

Some lessons about designing systems where failures are unavoidable.

Payments10 August 2026 · 3 min read

Payment systems fail in ordinary ways. A bank is slow. A rail is in maintenance. A provider returns an ambiguous status. A user retries. None of this is exceptional, and none of it can be designed away.

The useful question is not how to prevent every failure. It is how the system should behave when a failure is already in progress.

Failure is a product surface

In many software products, an error page is an interruption. In payments, the error is the product for a surprising amount of the user’s time.

A merchant wants to know whether money moved. A platform wants to know whether it can retry. A treasury team wants to know whether funds are in flight or settled. If the system cannot answer those questions clearly, the underlying rails can be working and the product still feels unreliable.

This is why status, idempotency and reconciliation are not backend details. They are part of what the product is promising.

Third parties are part of the architecture

A payment platform is a composition of other systems: banks, schemes, providers, ledgers, fraud tools, and local rails with their own hours and failure modes.

That composition is easy to hide behind a single API. It is harder to hide operationally. When something goes wrong, the question is rarely “did our code throw?” It is “which part of the path is uncertain, and what can we do without making it worse?”

Designing around third-party failure means:

  • treating external status as evidence, not truth
  • making retries safe enough to be routine
  • keeping enough of your own record that you can reconstruct a payment without the provider
  • deciding, in advance, which failures are retriable, which are terminal, and which need a person

The last of these is a product decision. Engineering can implement the states. It cannot invent them after the incident.

Reliability is not the same as uptime

A system can be up and still be unreliable. If it accepts a payment, then loses track of it, the availability graph will look fine. The merchant will not.

This is close to a lesson I keep returning to in AI work. A model can produce an answer every time and still be a poor system. Coverage is not the same as trust.

In payments, the equivalent of hallucination is a confident but unreconciled state: the interface says success, the ledger is unsure, and nobody can prove what happened. The fix is not a better loading spinner. It is a stricter relationship between what the system claims and what it can verify.

What production teaches that benchmarks do not

Benchmarks are clean. Production is not. The cases that matter are often the ones that were too awkward to put in a test suite: a timeout after debit, a duplicate callback, a rail that returns success and then reverses, a user who pays twice because the first attempt looked stuck.

Those cases are where architecture becomes organisational. Someone has to own the uncertain state. Someone has to decide whether the system waits, retries, refunds, or asks for help. If that ownership is unclear, the incident will find the gap.

I do not think this is unique to payments. It is simply more visible there, because money makes ambiguity expensive. The same discipline is useful anywhere a system is allowed to act under uncertainty.