The money model
Minor units, balance against available against held, double entry, what a transaction costs, and the float equation that ties a tenant together.
You can integrate WalletD by treating balances as numbers that go up and down. You will integrate it well if you spend ten minutes on what those numbers are.
Money is integer minor units
Every amount in every request and response is an integer in the currency's smallest unit.
| You mean | You send |
|---|---|
| $25.00 | 2500 |
| $0.07 | 7 |
| ¥1,200 (no minor unit) | 1200 |
There are no floats in the API in either direction. Format for display at the very edge of your system, and never let a float touch an amount on the way in: 0.1 + 0.2 is a rounding bug in an accounting system.
Balances are per purpose
A user does not have "a balance". They have one account per purpose, and each account has its own commodity.
curl -sS "$WALLETD_API/v1/users/$USER_ID/balances" -H "Authorization: Bearer $WALLETD_API_KEY"[
{ "purpose": "cash", "commodity": "USD", "balance": 4200, "available": 2700, "held": 1500 },
{ "purpose": "points", "commodity": "POINTS", "balance": 650, "available": 650, "held": 0 }
]Three numbers, three meanings:
balanceis the accounting total.heldis reserved by an authorization that has not been captured or voided yet. It is still the user's money; it is just spoken for.availableisbalance - held, and it is the only one that answers "can they spend this?"
Show available in your UI. A wallet that displays balance will tell a
user they have $42.00 and then decline a $30.00 purchase, which is the single
most common integration bug in this space. If your product shows a hold, show
it as its own line ("$15.00 pending at Hotel X"), never folded into one number.
Double entry, and why you can trust the number
Every movement writes at least two ledger postings that sum to zero. Money is never created or destroyed by an operation, only moved between accounts, and the sum of every posting in a tenant is always zero. That is checked continuously, not assumed.
The practical consequence for you: there is no such thing as a partial money movement. A transfer either debited the sender and credited the recipient and charged the fee, or none of it happened. A movement is posted to the ledger of record as one balanced posting or not at all; a caller sees either the completed movement or a refusal, never a half-finished one; and a replayed Idempotency-Key returns the same answer byte for byte. There is no intermediate state for you to observe or to clean up.
The float equation
Every unit of money in a tenant is somewhere, and the places add up:
user cash + merchant settlement + platform fees + treasury = money that enteredWhen a payout leaves, both sides shrink together. When a top-up lands, both sides grow. If you ever reconcile against your processor, this is the identity that has to hold, and the ledger summary endpoint reports it directly.
Account purposes you will meet
| Purpose | Whose | What it holds |
|---|---|---|
cash | user | Spendable money |
points | user | Reward points, a separate commodity |
settlement | merchant | Money earned and not yet paid out |
marketing | client | A client's own promotional budget |
fees | platform | Processing fees WalletD collected |
fees | tenant | Fees and marketplace commission the loop collected |
treasury | platform | Money that has left for a bank |
Points are a genuinely different commodity, not cash with a label. They can only become cash through an explicit conversion with a rule, which is what keeps a rewards programme from silently becoming a currency. See Rewards.
What a transaction costs
Every money path is priced the same way: a rate in basis points of the amount, plus a flat part in minor units, then held inside a minimum and an optional maximum. A path nobody has priced charges nothing. Percentage fees round up, by fixed policy, and a fee taken out of money never exceeds the money it comes from.
Six paths can carry a fee, and the direction differs:
| Path | Where it comes from |
|---|---|
| Payment | Deducted from the merchant's settlement |
| Subscription charge | Deducted from settlement, each period |
| Marketplace commission | Deducted from settlement, or added to what the buyer pays |
| Transfer | Added on top of what the sender pays, so the recipient receives the full amount |
| Top-up | Deducted from what the user is credited |
| Payout | Deducted from what leaves |
Two of those surprise people. A top-up fee does not reduce the money that entered the loop: the processor collected the full amount, treasury grows by all of it, and the fee is the part the wallet keeps rather than credits. A payout fee works the same way in reverse: the merchant is debited what it asked for, only the net leaves, and the fee stays behind. The identity above holds in both cases.
Marketplace commission is separate from the payment fee, is charged only on catalog sales, and is reported on the payment as its own commission field so you never have to infer it. If your loop adds commission on top rather than deducting it, the amount charged already includes it, because the catalog is the only source of price truth.
Negative balances are legal, sometimes
A user with a credit line can go below zero, down to their limit. That is not an error state and not a bug in your display: it is a business account spending on credit. available accounts for the headroom, so the same check still works.
Everyone else is floor-enforced at zero. An overdraft that was not explicitly granted is refused with 422 insufficient_funds.
Money in, money out
In is a gateway confirmation and nothing else. Not a client callback, not a redirect landing, not your own belief that a card cleared. The processor tells WalletD over a verified channel and only then does a balance move. See Top-ups.
Out is deliberately narrow. This is a semi-closed wallet: consumers cannot cash out to a bank. Money leaves through merchant payouts, which post to the ledger first and generate a statement that instructs the actual transfer. See Payouts.
Reading the trail
Every movement has an id, a type, a status, a timestamp, and its postings.
curl -sS "$WALLETD_API/v1/users/$USER_ID/transactions?limit=20" -H "Authorization: Bearer $WALLETD_API_KEY"
curl -sS "$WALLETD_API/v1/transactions?type=payment&limit=50" -H "Authorization: Bearer $WALLETD_API_KEY"The first is a user's own history. The second is the tenant-wide explorer with both sides of every posting resolved, which is what a finance or support screen is built on. Both are cursor-paged, newest first.
Next
- Top-ups to get money in.
- Payments and refunds for holds and captures, where
heldcomes from. - Rewards for the points commodity.