Field note Electronic money
Building an electronic money system
What has to exist before a single unit of electronic money can move.
Most teams start an electronic money project by designing the wallet screen. That is the last thing that should be designed, because everything visible on it is a consequence of decisions taken three layers below.
You are issuing an obligation, not storing a number
Electronic money is defined in Directive 2009/110/EC as monetary value stored electronically, representing a claim on the issuer, issued on receipt of funds, and accepted by someone other than the issuer. Every word in that sentence has a consequence in the database.
"Claim on the issuer" means the balance a user sees is your debt to them. It is not a number you own and can adjust. "Issued on receipt of funds" means no unit may exist before the corresponding funds have arrived and been recognised. "Redeemable at par at any moment" means your system must be able to extinguish that claim on demand, in full, without netting it against anything else.
A team that internalises this stops asking how to update a balance and starts asking what event justifies the balance changing. That shift is the whole architecture.
The four invariants
Before any feature, four equalities have to hold continuously, and you have to be able to prove they held at any point in the past.
These are not accounting niceties. The first two are supervisory requirements you will be examined on. The last two are what make the first two provable rather than asserted.
If your system cannot recompute a user balance as of a date fourteen months ago, you do not have an electronic money system. You have a spreadsheet with an API.
| Invariant | What it says | Note |
|---|---|---|
| Issuance | Electronic money in circulation equals funds received and not yet redeemed. | Never issue ahead of funds |
| Safeguarding | Safeguarded funds cover outstanding electronic money at all times. | Measured daily, not monthly |
| Conservation | Every internal movement has a matching counter-movement. Nothing appears or disappears. | Double entry |
| Reconstruction | Any balance at any past instant can be recomputed from the journal alone. | No state without history |
Safeguarding is a system, not a bank account
The requirement is easy to state: funds received in exchange for electronic money must be segregated from the institution own funds, either held in a dedicated account at a credit institution, invested in secure liquid low-risk assets, or covered by an insurance policy or comparable guarantee.
The implementation is where it becomes engineering. You need a continuous comparison between two independent quantities: the outstanding electronic money computed from your ledger, and the safeguarded balance computed from bank statements you did not produce. Those two numbers come from different systems, arrive on different schedules, and will disagree.
Build the comparison as a first class object with its own history. Each run stores the two figures, the delta, the explanation of the delta, and who reviewed it. When a supervisor asks what your coverage was on a random Tuesday in the previous year, you answer with a record, not a reconstruction.
Two details cost teams months if discovered late. Funds in transit belong somewhere explicit: money that has left a payer and not yet landed in the safeguarding account is neither nothing nor available. And fees you are entitled to deduct are not safeguarded funds, but they only stop being safeguarded at the moment your contract says they are earned, which is a rule your code has to encode.
What has to exist, in order
The dependency order below is not a preference. Each layer is unusable until the one before it is correct.
Teams routinely build items one, seven and eight, then discover during the authorisation process that items two through six were the actual product.
The reason idempotency sits so early is that payment systems retry. Networks time out, providers replay webhooks, users double tap. Without a deduplication key stored alongside the movement, every retry issues a new unit of electronic money against funds received once. Calling that a reporting bug understates it. It is issuance without receipt, which is the one thing the directive forbids outright.
- An append-only journal of movements with stable identifiers
- A chart of internal accounts that expresses the invariants
- Balance computation as a fold over the journal, never a stored mutable field
- Idempotency on every entry point, keyed by a client-supplied reference
- Two-phase movements: authorisation that reserves, capture that settles, expiry that releases
- Reconciliation against every external source of truth, on a schedule, with stored results
- Limits and controls evaluated against ledger state rather than a cached copy
- The product surface
Three different times, none of them "now"
Every movement carries at least three timestamps and they are rarely equal: when the underlying event happened in the outside world, when your system learned about it, and when it takes accounting effect.
A card authorisation happens at 23:58 on the thirty-first, arrives in a settlement file at 04:00 on the first, and belongs to the previous month for reporting. If you store one timestamp, you will produce a report that is defensible under one interpretation and wrong under the other two, and you will not be able to explain which.
Storing event time, ingestion time and value date separately costs three columns. Not storing them costs a restatement, and restatements are how a supervisor learns that your figures are estimates.
Redemption is the test
Redemption at par, at any moment, is the obligation that distinguishes electronic money from a stored value scheme, a loyalty balance or a prepaid product with expiry. It is also the path least exercised in testing, because in normal operation few users empty an account to zero and close it.
Test it as the primary flow. Redeem a balance that includes a pending authorisation. Redeem while a reconciliation break is open on that account. Redeem an amount that a fee schedule would reduce below par, and confirm the system refuses to reduce it. Redeem after a partial reversal.
Each of those cases forces a decision that is easier to make deliberately in a design review than during an incident.
What this buys you
A system built in this order is slower to first demo and faster to authorisation, because the questions asked during authorisation are questions about the invariants, not about the interface.
It is also cheaper to operate. When balances are derived rather than stored, a class of incidents disappears: there is no drift to detect, no repair script to write, no reconciliation between two internal copies of the same truth. The remaining incidents are about the outside world, which is where they belong.
Related
Read next
Designing a ledger for electronic money
Balances are a consequence, not a field you update.
Field noteWallet, account, ledger
Three words that get used interchangeably and should not be.
Field noteWhat actually happens inside an electronic money institution
Emission, safeguarding, control and reporting: the operating loop behind the licence.
Field noteContact
Working on something in this territory?
Financial infrastructure, regulated systems, AI in controlled environments, cryptography, platforms at scale.