When building financial applications like ClearBalance or subscription billing engines, amateur software engineers make a classic mistake: they represent user balances as a single mutable column in an `accounts` table:
-- DANGEROUS PATTERN: Never do this for real money!
UPDATE accounts SET balance = balance + 100 WHERE id = 42;
If a server crashes midway, a network packet retries, or concurrent transactions execute simultaneously, your database becomes corrupted with no audit trail explaining where money disappeared or appeared.
For over 500 years, accountants have solved this with Double-Entry Bookkeeping. Here is how to implement it correctly in software.
The Fundamental Accounting Equation
In double-entry accounting, money never appears out of thin air. Every transaction consists of at least two balanced entries: a Debit (DR) and a Credit (CR).
Total Debits MUST ALWAYS EQUAL Total Credits
Assets = Liabilities + Equity
+--------------------------------------------------------------+
| Double-Entry Transaction Schema |
| |
| [Transaction: Client Payment of $500] |
| | |
| +---> Entry 1: Debit Bank Account (Asset increases) |
| | +$500.00 |
| | |
| +---> Entry 2: Credit Accounts Receivable (Decreases) |
| -$500.00 |
| |
| Sum of Debits ($500) == Sum of Credits ($500) -> BALANCED |
+--------------------------------------------------------------+
1. Enforcing Immutability
Ledger entry rows must never be updated or deleted. Once written, an entry is permanent. If an invoice was entered with an incorrect amount, the system writes a compensating reversal transaction rather than mutating the original row.
2. Never Use Floating-Point Types for Currency
Floating-point numbers (`FLOAT`, `DOUBLE`) cannot represent decimal fractions precisely due to binary rounding quirks (`0.1 + 0.2 = 0.30000000000000004`). In financial ledgers:
3. Database-Level Constraint Validation
Ensure that transactions cannot be committed unless debits equal credits by wrapping entries in atomic database transactions with trigger assertions:
CREATE TABLE ledger_entries (
id BIGINT PRIMARY KEY,
transaction_id BIGINT NOT NULL,
account_id BIGINT NOT NULL,
amount_cents BIGINT NOT NULL, -- Positive for Debit, Negative for Credit
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Balance validation assertion
SELECT transaction_id, SUM(amount_cents)
FROM ledger_entries
GROUP BY transaction_id
HAVING SUM(amount_cents) != 0; -- Must return 0 rows
By enforcing double-entry discipline at the schema level, financial integrity is mathematically guaranteed.