Double Entry Accounting System 101: A Developer's Guide
Our CTO dropped a spreadsheet on my desk during a sprint planning meeting. “We need to track where every dollar goes,” he said. “Customer payments, driver payouts, Stripe fees, refunds—everything needs to balance to zero.” I looked at the columns labeled “Debit” and “Credit” and realized I’d been treating financial data like simple database records when it’s actually a directed graph of money movement. Every transaction has a source and a destination, and if you can’t trace both sides, you don’t have the full picture. ...
E-commerce Ledger Integration: From Purchase to Payout
I was three months into building an e-commerce platform when our accountant asked a simple question that broke everything: “When a customer pays $100, and we keep 10% as commission, and Stripe takes 2.9% + 30¢, how much does the merchant actually receive?” I looked at my database schema and felt the familiar sinking feeling that I was missing something obvious. The math was simple—$100 - $10 - $3.20 = $86.80—but the tracking wasn’t. When did the merchant earn that money? When the customer paid? When the item shipped? When we transferred it? What if the customer refunded? What if the item was backordered for three weeks? ...
Building a Ledger System - Chapter 1: Foundations
I stood there for a solid ten minutes, marker in hand, not knowing where to start. My team needed to track money movement. Not just record transactions—we needed to validate them, reconcile them, and prove they actually happened the way we said they did. And if we got it wrong? Well, that’s the kind of mistake that keeps people up at night. The thing about financial systems is that they seem simple until they’re not. A user sends money. You deduct from their account, add to someone else’s. Easy, right? But then you need to handle failed transfers, partial settlements, multi-currency conversions, and that 2 AM page when the numbers don’t add up. ...
Building a Ledger System - Chapter 2: Transaction Lifecycle
This is the second chapter in our five-part series on building production-ready ledger systems. In Chapter 1, we covered double-entry bookkeeping, data modeling, and transaction validation. Now we’ll explore transaction state management and async processing. Layer 3: Transaction Lifecycle Real-world transactions aren’t instantaneous. They go through states: stateDiagram-v2 [*] --> Pending: Create Pending --> Validating: Submit Validating --> Rejected: Invalid Validating --> Validated: Valid Validated --> Reserving: Reserve Funds Reserving --> Failed: Insufficient Reserving --> Reserved: Reserved Reserved --> Posting: Post Posting --> Posted: Success Posted --> Reversing: Reverse Reversing --> Reversed: Reversed Rejected --> [*] Failed --> [*] Posted --> [*] Reversed --> [*] The Pending → Validated → Reserved → Posted flow matters because: ...
Building a Ledger System - Chapter 3: Advanced Topics
This is the third chapter in our five-part series on building production-ready ledger systems. In Chapter 2, we covered transaction state management and async processing. Now we’ll explore advanced topics: multi-currency support, reconciliation systems, and preventing race conditions. Multi-Currency Handling If you’re dealing with multiple currencies, things get interesting: flowchart TD Start([Multi-Currency]) --> Check{Same Currency?} Check -->|Yes| Direct[Direct Transfer] Check -->|No| FX[FX Conversion Required] FX --> Rate[Get Exchange Rate] Rate --> Calc[Calculate Amount] Calc --> Multi[Create 4 Entries] Multi --> E1[Debit Source Account] Multi --> E2[Credit FX Account<br/>Original Currency] Multi --> E3[Debit FX Account<br/>Target Currency] Multi --> E4[Credit Target Account] Direct --> Post[Post Transaction] E1 --> Post E2 --> Post E3 --> Post E4 --> Post Post --> End([Complete]) The key insight: currency conversion is just another account. When converting USD to EUR: ...
Building a Ledger System - Chapter 4: Production Operations
This is the fourth chapter in our five-part series on building production-ready ledger systems. In Chapter 3, we covered multi-currency handling, reconciliation, and database locking. Now we’ll focus on production operations: audit trails, balance snapshots, and settlement tracking. Audit Trail Queries: Finding the Truth Event sourcing gives you an immutable history, but you need to query it effectively. Here’s how to build audit trails that actually help. Event Store Schema erDiagram EVENTS { uuid id PK uuid aggregate_id string aggregate_type string event_type json payload int sequence_number timestamp occurred_at uuid user_id string ip_address string correlation_id } PROJECTIONS { uuid id PK string projection_name uuid last_event_id timestamp updated_at } ACCOUNT_BALANCE { uuid account_id PK decimal balance uuid last_entry_id timestamp updated_at } Query Patterns 1. Reconstruct Account History ...
Building a Ledger System - Chapter 5: Data Quality & Best Practices
This is the final chapter in our five-part series on building production-ready ledger systems. In the previous chapters, we covered foundations, transaction lifecycles, advanced topics, and production operations. Now we’ll focus on keeping your ledger healthy with continuous data quality checks and handling unsettled funds with holding accounts. Improvements: Continuous Data Quality Checks The worst time to discover a ledger bug is when your accountant asks why the trial balance doesn’t match. Data quality checks catch problems before they become disasters. ...
Building a Ledger System - Chapter 6: Displaying Balance and Mutations to Users
The first time our mobile app loaded a user’s transaction history, it took 12 seconds. Twelve. Whole. Seconds. I watched our CEO stare at the loading spinner during a demo, and I could see the math happening in his head: If this takes 12 seconds for one user, what happens when we have 10,000 users checking their balance at 9 AM on payday? He didn’t say anything. He just raised an eyebrow. ...
The DNS Mystery: Five Factors and a Ruby Gem
“Connection reset by peer.” If you’ve run applications in Kubernetes long enough, you’ve probably seen this error. Usually it’s a service that went away, a network hiccup, something transient. You retry, it works, you move on. But what if it keeps happening? What if it only happens with DNS lookups, and only sometimes, and only in production? That’s where I found myself a few weeks ago. The Event Our Ruby application started throwing intermittent Errno::ECONNRESET errors. Not on HTTP requests to external APIs — on DNS lookups. The stack trace pointed to getaddrinfo, the standard libc function for resolving hostnames. ...
CPU Limits vs Memory Limits: When 'Survival' Means Different Things
In my previous post, I said: “CPU limits are about performance. Memory limits are about survival.” I stand by that statement, but I oversimplified what “survival” actually means depending on what kind of application you’re running. Let me break this down. Stateless Applications: The “Easy” Case When I wrote about memory limits being “about survival,” I was thinking primarily of stateless services. You know, the typical microservices: APIs, web servers, queue consumers. ...