Designing a cross-border payment system

작성자

카테고리:

← 피드로
DEV Community · Weston Carnes · 2026-08-12 개발(SW)
Cover image for Designing a cross-border payment system

Weston Carnes

Cross-post. Original: stellarbytecapital.com/blog/cross-border-payment-system-design

A payment system has one job that dwarfs all the others: never lose track of money. Features, UI, and even uptime are negotiable in a pinch — a lost or duplicated transaction is not. Cross-border adds currencies, multiple payment channels, settlement delays, and regulators on top. Get the money-safety core right and everything else is ordinary engineering; get it wrong and no amount of polish saves you.

The ledger is the system

The single most important decision is to make an append-only, double-entry ledger the source of truth — not a mutable balance column you increment. Every movement of money is recorded as balanced entries (a debit and a matching credit) that sum to zero. A user’s balance is derived from the ledger, never stored as the primary fact.

  • Immutable entries. You never edit or delete a posting. A mistake is corrected with a new reversing entry, so history is a complete, auditable trail.
  • Balances always reconcile. Every entry is balanced, so the whole system sums to zero at all times. If it doesn’t, you have a bug — detectable immediately, not months later in an audit.
  • Every entry has a reason. Each posting references the event that caused it, so you can always answer “why is this number what it is?”

A mutable balance is a number you hope is right. A ledger is a number you can prove is right.

Idempotency: the network will retry, so must you survive it

Money movement crosses networks that time out, drop, and duplicate. The classic failure: your service calls a payment channel, the channel processes it, but the response is lost — so a retry charges the user twice. The defense is idempotency, end to end.

  • Client-supplied idempotency keys. Every money-moving write carries a unique key. The server records the key with the result; a repeat returns the original outcome instead of doing the work again.
  • Exactly-once at the boundary. Calls to external channels are wrapped so a retry never means a second real charge — the same discipline that keeps a trading bot from double-submitting orders.
  • Transactional writes. The ledger entry and the state change commit together, in one database transaction. Partial writes are the enemy.

Payment state as an explicit machine

A payment is never simply “done.” Model it as an explicit state machine — initiated → pending → settled, with failed and reversed branches — and persist every transition.

  • Everything is async. Channels confirm out of band, sometimes hours later. Hold a payment in pending and resolve it on a callback or poll; never assume synchronous success.
  • The external channel is the source of truth for its leg. Your local “I think it succeeded” means nothing until the channel confirms. Reconcile against the channel; trust the channel.

Reconciliation: assume drift, detect it daily

No matter how careful the writes, your records and the channels’ records will drift — missed callbacks, timing gaps. Reconciliation is a core scheduled job, not optional cleanup:

  • Pull each channel’s settlement report and match it line-by-line against your ledger.
  • Flag every mismatch into an exceptions queue a human can work.
  • Track a reconciliation watermark so you always know the last point the books were provably correct.

Multi-channel and multi-currency without chaos

  • A channel abstraction. Each provider sits behind a common interface (initiate, query, handle-callback, reconcile). Adding a channel is implementing that interface, not rewiring the core.
  • Currency as first-class data. Every amount carries its currency, stored in minor units as integers — never floats. FX conversions are themselves ledger events, so the books stay balanced across currencies.

What to avoid

  • A mutable balance column as the truth — the original sin; you can’t prove correctness or cleanly reconcile.
  • Floats for money — rounding errors compound into unexplainable discrepancies. Integers in minor units, always.
  • Assuming synchronous success — how double-charges and phantom balances happen.
  • Skipping reconciliation until there’s a problem — by then the drift is large, old, and expensive.

We’re Xingyao Byte — building payment platforms, quant trading systems, and secure AI-execution layers. Remote, async-first → stellarbytecapital.com

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다