Money Service — Overview
The Money Service is the financial core of the platform. It
provides safe wallet management, balance operations, and transaction
processing for all modules. No module handles money arithmetic
directly — modules call kernel.money() and the service guarantees
correctness, atomicity, and a full audit trail.
The single most important rule: All amounts are stored and transmitted as integer cents. Never use floating-point for money.
$12.50=1250.$0.01=1.
Technical Stack
| Component | Technology | Role |
|---|---|---|
| Go runtime | pgxpool + pgx/v5 + sqlc | Database access, connection pool |
| Database | PostgreSQL (ACID) | Wallets, transactions, holds |
| Amount type | shopspring/decimal | Decimal arithmetic without float rounding |
| Storage type | BIGINT | Integer cents; max ≈ $92 quadrillion |
| Idempotency | UUID per operation | Duplicate-safe at the database level |
| Limits cache | Valkey (5-minute TTL) | Billing plan limits (max tx amount, max balance) |
Design Principles
Integer Cents — No Float
All amounts are BIGINT cents. Float arithmetic is banned because
IEEE 754 rounding causes errors in financial calculations:
0.1 + 0.2 = 0.30000000000000004 ← float error
100 + 200 = 300 ← integer, exact
Every SDK method accepts and returns integer cents. Formatting
for display ($12.50) is the responsibility of the UI layer —
the Money Service never formats amounts as strings.
ACID Transactions
All operations (credit, debit, transfer, hold, confirm, cancel, reversal) run inside a single PostgreSQL ACID transaction. Partial failure is structurally impossible — either all steps commit or all steps roll back.
The Saga Pattern is not needed for internal Money operations. All wallets live in the same database. Saga applies only when coordinating Money with external payment gateways (Stripe, PayPal), which is handled by the hold/confirm/cancel pattern.
Double-Entry Bookkeeping
Every transaction produces two ledger entries: a debit on one account and a credit on another. This ensures the sum of all balances across the system is always zero (conservation of value).
Idempotency
Every balance-modifying operation requires an idempotencyKey (UUID).
A duplicate request with the same idempotencyKey returns the
original result without re-executing the operation. This makes retry
logic safe for all callers.
Wallet Balance Fields
A wallet balance has three independent fields:
| Field | Meaning |
|---|---|
available | Spendable balance. Reduced by debit and hold. Increased by credit and cancel. |
pending | Reserved for async deposit confirmations (e.g. payment gateway callback pending) |
frozen | Balance locked by a hold. Not spendable. Released by confirm (→ debit) or cancel (→ available) |
Total wallet value = available + pending + frozen
REST API
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/wallets | Create wallet |
GET | /api/v1/wallets | List wallets (paginated, filter by userId, currency) |
GET | /api/v1/wallets/:id | Get wallet details |
GET | /api/v1/wallets/:id/balance | Get current balance (available, pending, frozen) |
POST | /api/v1/wallets/:id/credit | Credit (add funds) |
POST | /api/v1/wallets/:id/debit | Debit (withdraw funds) |
POST | /api/v1/wallets/:id/hold | Freeze funds (hold) |
POST | /api/v1/wallets/:id/confirm | Confirm hold → debit |
POST | /api/v1/wallets/:id/cancel | Cancel hold → return to available |
POST | /api/v1/wallets/:id/reversal | Reverse a completed transaction |
POST | /api/v1/wallets/transfer | Transfer between two wallets |
GET | /api/v1/wallets/:id/transactions | Transaction history (paginated) |
GET | /api/v1/transactions/:id | Get transaction details |
Transaction Types
| Type | Description | Balance effect |
|---|---|---|
credit | Add funds to a wallet | available += amount |
debit | Withdraw funds from a wallet | available -= amount |
transfer | Move funds between two wallets | available -= amount (from), available += amount (to) |
hold | Freeze funds pending confirmation | available -= amount, frozen += amount |
confirm | Finalize a hold as a debit | frozen -= amount |
cancel | Release a hold back to available | frozen -= amount, available += amount |
reversal | Counter-transaction to a completed tx | Opposite effect of original |
Transaction Status Lifecycle
┌─────────┐
│ created │
└────┬────┘
│ Processing
┌─────────┴─────────┐
│ │
┌────▼─────┐ ┌──────▼──────┐
│ completed│ │ pending │
└────┬─────┘ └──────┬──────┘
│ │ hold
┌────▼──────┐ ┌──────▼──────┐
│ reversed │ │ held │
└───────────┘ └──┬───────┬──┘
confirm│ │cancel / TTL expiry
┌────▼──┐ ┌──▼──────┐
│confirm│ │canceled │
└───────┘ └─────────┘
Related Pages
- Wallets — create, list, balance fields, multi-currency
- Transactions — credit, debit, transfer, idempotency
- Hold / Confirm / Cancel — two-phase debit pattern, TTL, auto-cancel worker
- Reversals — counter-transactions, 365-day window
- Guarantees — ACID, concurrent protection, deadlock handling
- Limits — max amounts, Billing plan, fail-closed behavior