One wallet, one ledger, and a saga that cannot lose a movement.
Every merchant holds one funded wallet. Cards are issued against it, spending draws from it, and withdrawals return from it. Nothing moves without posting balanced legs to a ledger that is only ever appended to.
Wallets
One wallet per merchant, in integer minor units.
A merchant wallet holds a balance in integer minor units — cents, not decimals. No floating-point value exists anywhere in the money path, so no rounding error can accumulate across a million movements. Balances are derived from the ledger rather than trusted as a running total, and an hourly watchdog re-derives them and alerts on any drift.
- Unit
- integer minor units (BIGINT)
- Balance source
- derived from ledger entries
- Drift watchdog
- hourly, alerts on mismatch
Deposits
Funding arrives as crypto, credited on confirmation.
A merchant requests a permanent per-network deposit address. Funds sent to it are credited to the wallet once confirmed, as a balanced movement like any other. Deposit settlement runs in two phases — the provider notifies, then the platform fetches the authoritative record — so a notification that carries no amount can never credit the wrong figure. An hourly reconciliation sweep re-checks anything a webhook missed, idempotently.
- Addresses
- permanent, per network
- Settlement
- two-phase: notify, then fetch record
- Fallback
- hourly reconciliation, idempotent
Issuing
Cards are issued through hold, provider, settle.
Issuing places a hold on the wallet for the initial load plus the product issue fee, calls the licensed issuing partner outside the database transaction, and settles the movement to the ledger on success. The provider call never runs inside a transaction or a lock, so a slow or failed issuer cannot hold a merchant's wallet open. Where the partner provisions asynchronously, the call returns a pending card and the same settlement runs later from the partner's webhook or from reconciliation — either path, once only.
- Saga
- hold → provider → settle
- Provider call
- never inside a transaction or lock
- Async result
- 202 pending, settled by webhook or sweep
Cardholders
Identity is stored once, encrypted, and reused.
Cardholder identity lives in a customer registry rather than being re-sent with every request. Records are encrypted at rest and submitted to the issuing partner only inside an issue request. Card numbers and security codes are never exposed through the API and never persisted — a card resource carries a masked number and its last four digits, nothing more.
- Storage
- encrypted at rest
- PAN / CVV
- never exposed, never persisted
- Card resource
- masked number + last four only
Authorizations
Spending resolves to legs that sum to zero.
An authorization does not mutate a balance. It posts a movement whose debit and credit legs are equal and opposite, so the movement nets to zero and the balance is whatever the ledger says it is when you read it back. Corrections are posted as new entries, never as edits. Nothing in the ledger is ever updated or deleted.
- Ledger
- insert-only
- Every movement
- minimum two balanced legs
- Corrections
- posted as new entries
Withdrawals
Money leaves through an approval loop, not an API call.
Withdrawals have no public API. A merchant requests one from the portal, a platform operator approves it, and completion is recorded with its payout proof. Every state change is written to an insert-only audit trail with the actor who made it. This is deliberate: the endpoint that moves money out of the platform is the one endpoint that should not be reachable by a leaked API key.
- Public API
- none — portal and admin only
- Flow
- request → approve → complete
- Audit
- every transition, with actor
Reporting
Figures are read from the ledger, not from a counter.
Nightly aggregates are built from ledger entries rather than from a denormalised revenue column, so a report and the underlying money cannot disagree. Test-mode activity is excluded structurally by a join rather than by a filter someone has to remember to apply.
- Source
- ledger entries
- Schedule
- nightly
- Test activity
- excluded structurally
Where this stands today.
The platform runs against a simulated card provider by default. The first real issuing integration is built and sandbox-certified, and it moves to production once credentials and a webhook secret are confirmed with the partner. Crypto deposit signing is implemented and awaits live certification.
Until those are complete this page describes behaviour that is implemented and tested, not a live issuing service. We would rather say that here than have you find it out during an integration.