TSTier SlateAll templates

Payments & ledgers

Wallet transfers - the state machine, the hold that expires, and the reversal that cannot

Teaches how money moves between two accounts in one system without ever being in both places or neither, and what it costs to make every step undoable.

  • System design
  • starter
  • 8 step walkthrough
  • 5 tables

Share

  • Facebook
  • X

What this board gets wrong on purpose

The tension

Everything on the happy path is transactional and safe, and the entire risk of the system lives in the one state the transaction cannot cover: PENDING_EXTERNAL, where a hold exists on the sender’s money and the bank rail has been asked to move it. The hold sweeper is the only thing that ever rescues that state, it runs on a clock, and it cannot tell a dead transfer from a slow one - so it will eventually release a hold for a payout the bank is still processing, at which point the sender can spend money that is about to leave, and the wallet goes negative in a way no CHECK constraint prevented because the money was legitimately spent twice by two legitimate paths. Lengthening the expiry makes customers’ money unusable for longer; shortening it makes the race more likely. The second unresolved thing is the reversal, which is drawn as an ordinary transfer in the opposite direction and is therefore subject to every failure the original was - including insufficient funds. A recipient who has already spent the money cannot be reversed, and nothing on this board decides what happens then: the balance would go negative, the CHECK refuses it, and the reversal simply fails with no defined next step other than a human.

1 · Requirements

A wallet transfer looks like the simplest money problem in this collection - two rows, one database, subtract here and add there. The requirements below are all the ways that sentence turns out to be incomplete.

Ask these before designing

  • Can a transfer leave the system? If every transfer is wallet-to-wallet, the whole thing is one database transaction and most of this board is unnecessary. The instant a payout to a bank exists, there is a state that can be pending for three days, and that single state is where all the risk lives.
  • Can a wallet go negative? The honest answer for most products is "never, except for fees and reversals", and those two exceptions are what make it a CHECK with a carve-out rather than a CHECK.
  • Who may reverse, and for how long? Support reversing a transfer from last Tuesday is a different system from a 30-second undo window. The first needs a reversal that can fail; the second can just cancel.
  • Is the recipient notified before the money is final? If yes, a later reversal is a conversation with an angry customer. If no, the product feels slow. Most teams pick "yes" without noticing they have chosen the conversation.
  • One currency per wallet? Multi-currency wallets mean an FX rate at transfer time, a rate that can move between quote and execution, and somebody owning the resulting position. That is a second system, not a column.

Functional

  • Transfer an amount from one wallet to another, atomically, with an idempotency key.
  • Hold funds before an external leg, and release the hold if it does not complete.
  • Reverse a completed transfer by posting the opposite movement, linked to the original.
  • Show a balance that never includes money that is held.
  • List a wallet’s transfers with a stable order and a stable page boundary.
  • Prove daily that wallet balances plus live holds equal the bank float.

Non-functional

  • A wallet may never go negative, enforced by a database CHECK rather than by application logic, because the application will eventually have two versions running at once.
  • No money is ever in both wallets or neither. The internal leg is one transaction; there is no saga for it, and introducing one would be a downgrade.
  • p99 under 400 ms for an internal transfer, which is entirely dominated by the limits check.
  • 99.95% on the transfer path, 99.99% on balance reads. People check their balance far more often than they send money, and a balance page that is down feels like the money is gone.
  • Read-your-writes on balances, always. A sender who sees their old balance after sending assumes it failed and sends again.

Explicitly out of scope

The underlying ledger (its own board), card issuing, KYC and onboarding, FX, and the bank rail’s internals. This board is about the state machine around a movement, not about the movement itself.

The rest of this board

Board preview

Available on Tier Slate

This page publishes the question. The answer — 5 more written pages, an 8-step narrated walkthrough and a 5-table schema — is the board itself, and it opens in Tierslate.

  • 5written pages
  • 8walkthrough steps
  • 5tables32 columns

5 pages behind this one

  • Capacity estimation
  • Storage estimation
  • Availability
  • How it works
  • Deployment plan
Open this board in TierslateBrowse every board

More boards

Systems where losing a write is losing money.

  • Double-entry ledger - the balance is not a column
  • Fraud detection - a decision that must fit in 120 milliseconds
  • Payment gateway - what a timeout means when the card may already be charged
  • Matching engine - price-time priority, and why the clock cannot decide who was first
All 50 templatesOpen Tierslate
Tierslate

tierslate.com

HomeTemplatesPrivacyTerms