惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
T
The Blog of Author Tim Ferriss
Recent Announcements
Recent Announcements
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
MongoDB | Blog
MongoDB | Blog
U
Unit 42
B
Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
P
Privacy International News Feed
L
LINUX DO - 最新话题
博客园_首页
博客园 - Franky
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Privacy & Cybersecurity Law Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
K
Kaspersky official blog
C
Cisco Blogs
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
I
Intezer
罗磊的独立博客
MyScale Blog
MyScale Blog
Last Week in AI
Last Week in AI
A
About on SuperTechFans
G
GRAHAM CLULEY
Y
Y Combinator Blog
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
D
DataBreaches.Net
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
AWS News Blog
AWS News Blog
I
InfoQ
T
The Exploit Database - CXSecurity.com
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 叶小钗
Project Zero
Project Zero

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
How to build an accrual-based credit ledger
Paulo Victor · 2026-05-05 · via DEV Community

Ledgers are the heartbeat of any financial companies, fintech or old school financial. Not the API gateway, not the mobile app, not the underwriting model.

The ledger
trust me

Banks have known this for centuries. Fintechs sometimes need to rediscover it the hard way.

On fintech world Revolut runs multi-currency, multi-product financial infrastructure across 35+ countries. Stripe moves money and extends credit infrastructure for millions of businesses. Nubank serves more than 100 million customers and had to make credit work at Latin American scale. Chime built credit-builder products on top of a US neobank model. Klarna made deferred payments mainstream, which also means ledger complexity at global BNPL scale.

Different products. Different geographies. Same pressure: move fast without corrupting the books.

That is the uncomfortable thing about ledgers. The product team wants speed. The regulator wants auditability. Finance wants reconciliation. Engineering wants evolvability. Customers just want their balance to be correct.

And ledger decisions made in year one become the ceiling in year five. The model that worked for 10,000 users becomes the bottleneck at 10 million. The cron job that looked pragmatic becomes the reason nobody trusts end-of-day balances. The table that was “good enough for MVP” becomes the thing auditors stare at for three months.

The Synapse collapse in 2024 was a brutal reminder of this. When money movement companies cannot clearly reconcile who owns what, the failure is not only technical. It becomes customer harm. Millions of dollars can end up disputed, delayed, or unreconciled because the ledger was not strong enough to be the source of truth.

This article is about designing a credit ledger before that pain arrives.

One important boundary: this is intentionally about a single-geography credit ledger. No multi-country posting rules, no cross-currency accounting, no global consistency model. Those are a different beast. I will cover them in the next article.

The evolution most fintechs go through

Most credit products do not start with a beautiful accrual ledger. They start with a product requirement:

“We need to charge interest.”

Then a team adds a job. Later, events. Later, after enough incidents, a proper accrual model.

That evolution is normal. The trick is not pretending the first version is the final one.

Stage 1: the job-based ledger

The simplest ledger is a scheduled job.

Every hour, every night, or every billing cycle, a cron job scans accounts and creates bookkeeping movements:

00:00 ───────────────────────────────────────────────► time

10:03  purchase happens
11:18  payment happens
14:42  fee is incurred

23:59  ledger_job runs
      ├─ posts purchase movement
      ├─ posts payment movement
      └─ posts fee movement

Enter fullscreen mode Exit fullscreen mode

This works surprisingly well at small scale. It is easy to reason about. You can query the database, calculate what changed, and insert rows into a ledger table. Many early fintech systems start here because it lets the team ship.

The problem is that job-based ledgers mix three different concepts:

  1. when something happened,
  2. when the system noticed it happened,
  3. when accounting was posted.

At low volume, the difference is invisible. At scale, it becomes the whole problem.

A customer pays at 11:18, but the ledger does not reflect it until 23:59. A fee is incurred at 14:42, but the job fails and retries at 01:10. Two jobs overlap and double-post. A reprocessing script tries to fix yesterday but accidentally changes today.

The core issues are predictable:

  • Delayed consistency: balances are stale by design.
  • Race conditions: two jobs can process the same account or overlapping time windows.
  • Poor auditability: “what was the balance at 14:00?” becomes hard if entries are posted later without the correct effective date.
  • Painful reprocessing: fixing a bad job means deciding whether to delete, update, or overwrite ledger rows. All three are dangerous.
  • Scaling by brute force: when volume increases, you make the job faster. Eventually the job becomes a monster.

The job-based ledger is not evil. It is just a starting point. The mistake is letting it become the foundation of a serious credit platform.

Stage 2: the event-based ledger

The next step is to post ledger entries when business events happen.

A payment is received. A charge is applied. A late fee is created. A statement closes. Instead of waiting for a batch job, the system reacts immediately.

Domain event                         Ledger reaction
────────────                         ───────────────
PaymentReceived ───────────────────► post payment entry
PurchaseAuthorized ────────────────► post authorization entry
FeeIncurred ───────────────────────► post fee entry
StatementClosed ───────────────────► post billing entries

Enter fullscreen mode Exit fullscreen mode

This is much better. The ledger is closer to real time, and the accounting logic sits near the business event that created it. Martin Fowler’s accounting patterns make this connection explicit: domain events and accounting entries should be linked, because accounting is a record of business reality, not an isolated reporting table.

But event-based does not automatically mean robust.

A common implementation looks like this:

API request
  ├─ update product state
  ├─ publish event
  └─ write ledger entry

Enter fullscreen mode Exit fullscreen mode

Or worse:

API request
  ├─ update product state
  ├─ call ledger service synchronously
  └─ return success to customer

Enter fullscreen mode Exit fullscreen mode

Now the ledger is coupled to the transaction path. If the ledger service is slow, the product is slow. If the event publish fails after the product state changes, the books are wrong. If the same event is delivered twice, you double-post. If events arrive out of order, the ledger reflects a reality that never existed.

        ┌──────────────┐
        │ Credit API   │
        └──────┬───────┘
               │ emits
               ▼
        ┌──────────────┐        tight coupling risk
        │ Domain event │ ──────────────────────────┐
        └──────┬───────┘                           │
               │ consumed                           ▼
               ▼                            ┌──────────────┐
        ┌──────────────┐                    │ User request │
        │ Ledger write │                    │ latency path │
        └──────────────┘                    └──────────────┘

Enter fullscreen mode Exit fullscreen mode

Event-based ledgering is the middle ground. It is a necessary evolution from cron jobs, but it still needs three things to become safe:

  • immutable events,
  • idempotent processing,
  • deterministic replay.

Without those, you do not have a ledger architecture. You have a distributed system hoping the happy path stays happy.

Stage 3: the accrual-based ledger

A credit ledger should not only record when cash moves. It should record when value is earned or incurred.

That is the core idea of accrual accounting.

Interest is earned daily. Fees are incurred when the customer triggers them. A settlement is a separate event from the revenue already earned. Billing is a presentation and collection mechanism, not the moment the economics magically appear.

For a credit product, this distinction matters a lot.

If a customer carries a balance for 20 days, the platform is earning interest across those 20 days. Waiting until the statement closes to create one giant interest entry may look simpler, but it hides the actual economics. It also makes “balance as of” queries, partial reversals, mid-cycle adjustments, and audit trails harder than they need to be.

An accrual-based credit ledger treats accounting like this:

Day 1      Day 2      Day 3      ...      Cycle close        Payment
│          │          │                    │                  │
▼          ▼          ▼                    ▼                  ▼
Accrue     Accrue     Accrue               Bill accrued       Settle cash
interest   interest   interest             interest           receivable

Accounting view:
- daily: debit interest receivable, credit interest income
- cycle close: move accrued amounts into statement balance
- payment: debit cash/settlement account, credit customer receivable

Enter fullscreen mode Exit fullscreen mode

This is how accounting actually works. Fintechs that skip it often end up retrofitting it later, usually after finance, risk, or regulators start asking questions the old model cannot answer.

Double-entry is the checksum

At the center of this design is double-entry bookkeeping.

Every ledger entry has two sides. One account is debited. Another account is credited. The total must balance.

For a daily interest accrual, the entry might be:

Debit:  Interest receivable      1.25 USD
Credit: Interest income          1.25 USD

Enter fullscreen mode Exit fullscreen mode

The customer owes more. The company earned revenue. Two sides of the same fact.

This is not accounting ceremony. It is a system invariant. If debits and credits do not net to zero, the ledger rejects the entry. That gives you a built-in checksum for every financial movement.

A single-sided balance table can tell you what you think a customer owes. A double-entry ledger can tell you whether the books still make sense.

That difference is everything.

The ledger should be append-only

A serious ledger does not update old entries. It does not delete them either.

It appends.

If you posted the wrong amount, you post a reversal and then a corrected entry. The history remains sacred.

entry_id   type        debit_account          credit_account       amount
────────   ────────    ─────────────────      ─────────────────    ──────
E1         ACCRUAL     interest_receivable    interest_income      10.00
E2         REVERSAL    interest_income        interest_receivable  10.00
E3         ACCRUAL     interest_receivable    interest_income       9.50

Enter fullscreen mode Exit fullscreen mode

That pattern feels annoying when you are moving fast. It is also the reason you can answer audit questions later.

Why was the customer balance different yesterday? Read the log.

What did the system believe at 10:35? Query entries with effective_date <= 10:35 and created_at <= investigation_cutoff.

Can we reproduce last month’s statement? Replay the events and ledger entries as they existed then.

This is where Mettle’s Write Once Double Entry pattern is a great real-world reference. WODE is basically the grown-up version of what many fintech teams eventually learn: write once, balance always, correct by appending, and make the ledger boring enough to trust.

Event sourcing is the backbone

Accrual ledgering and event sourcing fit naturally together.

The product emits immutable domain events:

  • PurchasePosted
  • PaymentSettled
  • InterestAccrued
  • LateFeeIncurred
  • StatementClosed
  • ChargeReversed

The ledger consumes those events and produces immutable accounting entries. Each entry points back to the event that caused it.

┌─────────────────┐
│ Domain command  │
│ "apply payment"│
└────────┬────────┘
         ▼
┌─────────────────┐
│ Domain event    │
│ PaymentSettled  │
└────────┬────────┘
         ▼
┌─────────────────┐
│ Accrual /       │
│ posting rules   │
└────────┬────────┘
         ▼
┌────────────────────────────────────────────┐
│ Immutable double-entry ledger entries       │
│ source_event_id = PaymentSettled.event_id   │
└────────────────────────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

This gives you a clean rule: product state is derived from product events, and accounting state is derived from accounting entries produced from those events.

If you replay all events through the same posting rules, you should get the same ledger. If you do not, either the rules are not deterministic or the event log is incomplete. Both are bugs worth finding early.

Event sourcing also changes how you think about failures. A failed projection is not a data-loss incident if the event is still there. You fix the processor, replay, and rebuild. That is the difference between a recoverable system and a spreadsheet with APIs.

Eventual consistency is fine. Wrong money is not.

A common objection is: “But if the ledger is event-driven, it may be slightly behind.”

Yes. That is usually fine for credit.

A credit ledger being 200 milliseconds behind the authorization system is not a disaster. A ledger double-posting interest is. A missing payment is. A fee applied before the balance it depends on is. An out-of-order reversal that leaves the customer owing money they do not owe is.

The trade-off is not strong consistency versus chaos. The trade-off is where you need immediate consistency and where you need deterministic eventual consistency.

For most credit ledgers:

  • the customer-facing authorization path may need immediate answers,
  • the ledger may process asynchronously,
  • the processing must be idempotent,
  • events must have ordering guarantees per account or credit line,
  • every ledger entry must carry an idempotency key,
  • replay must not create duplicate entries.

An idempotency key can be simple and powerful:

interest-accrual:{credit_account_id}:{accrual_date}:{rate_version}

Enter fullscreen mode Exit fullscreen mode

If the processor retries, the ledger sees the same key and refuses to post the same economic fact twice.

That is the difference between eventual consistency and eventual regret.

A practical ledger entry shape

You can make the schema more sophisticated later, but a useful starting point looks like this:

ledger_entries(
  entry_id,
  type,
  debit_account,
  credit_account,
  amount,
  currency,
  effective_date,
  created_at,
  idempotency_key,
  source_event_id
)

Enter fullscreen mode Exit fullscreen mode

A few fields matter more than they look:

  • effective_date is when the economic event applies.
  • created_at is when the system recorded it.
  • idempotency_key prevents duplicate posting.
  • source_event_id links accounting back to business reality.
  • currency should be explicit even in a single-geography system. You will thank yourself later.

For a serious platform, you will also add account types, journal batches, metadata, posting rule versions, actor/service identifiers, and partition keys. But do not lose the core shape: debit, credit, amount, currency, time, idempotency, source.

Daily interest accrual pseudocode

Here is the kind of logic I would expect in a first version. Not production code, but the shape is right.

for credit_account in active_credit_accounts:
    accrual_date = today_in_product_timezone()

    balance = principal_balance_as_of(
        credit_account.id,
        accrual_date.start
    )

    if balance <= 0:
        continue

    rate = interest_rate_for(
        credit_account.id,
        accrual_date
    )

    daily_interest = round_money(
        balance * rate.annual_percentage / days_in_year(accrual_date)
    )

    if daily_interest == 0:
        continue

    idempotency_key = "interest-accrual:" +
        credit_account.id + ":" +
        accrual_date + ":" +
        rate.version

    post_double_entry(
        type = "INTEREST_ACCRUAL",
        debit_account = account("interest_receivable", credit_account.id),
        credit_account = account("interest_income"),
        amount = daily_interest,
        currency = credit_account.currency,
        effective_date = accrual_date,
        source_event_id = current_accrual_event.id,
        idempotency_key = idempotency_key
    )

Enter fullscreen mode Exit fullscreen mode

There are many details hidden here: day-count convention, rounding policy, grace periods, promotional rates, delinquency state, local regulation, charge-off treatment. Those are product and accounting rules, not reasons to avoid the accrual model.

Actually, they are reasons to prefer it. Complex rules are easier to manage when every economic fact has a precise entry and a source event.

Correction entries, not edits

Imagine the system accrued 10.00 USD of interest, but later you discover the correct amount was 9.50 USD because a payment was effective one day earlier.

Do not update the original row.

Post this:

Original:
  Dr interest_receivable  10.00
  Cr interest_income      10.00

Reversal:
  Dr interest_income      10.00
  Cr interest_receivable  10.00

Corrected:
  Dr interest_receivable   9.50
  Cr interest_income       9.50

Enter fullscreen mode Exit fullscreen mode

Now the ledger tells the truth twice:

  1. what the system originally believed,
  2. how that belief was corrected.

That second part matters. A ledger that only stores the latest truth is not an audit trail. It is a mutable cache.

Where teams usually get hurt

The hardest part of building a credit ledger is not the table design. It is resisting shortcuts that feel harmless.

Shortcuts like:

  • storing only customer balances instead of entries,
  • treating billing as the moment revenue is earned,
  • letting jobs update historical rows,
  • using timestamps without separating effective_date from created_at,
  • allowing ledger writes without source events,
  • ignoring idempotency because “the event only fires once,”
  • building reconciliation after launch instead of as part of the ledger.

Every one of those shortcuts is understandable. Every one becomes expensive.

The better architecture is boring:

Domain events are immutable.
Posting rules are deterministic.
Ledger entries are double-entry.
Ledger writes are append-only.
Corrections are reversals.
Consumers are eventually consistent.
Reconciliation is continuous.

Enter fullscreen mode Exit fullscreen mode

That is not overengineering. That is the minimum foundation for money.

Build versus buy

There is a reason ledger-as-a-service companies and open-source ledgers are getting attention. Formance and Blnk are examples of the community moving toward purpose-built ledger infrastructure instead of every fintech reinventing the same accounting core. Temporal’s work on high-performance ledger patterns also points in the same direction: reliability, replayability, and operational resilience are not optional features.

My opinion: most fintechs should not casually build a ledger from scratch. If the ledger is not a core differentiator, buying or adopting a proven ledger engine is rational.

But credit platforms often have enough product-specific accounting behavior that the team still needs to deeply understand the model. Even if you buy the ledger infrastructure, you still own the posting rules. You still own correctness.

A vendor can provide the engine. It cannot decide your economics.

The simple standard

A good credit ledger should let you answer these questions without panic:

  • What does this customer owe right now?
  • What did they owe at the end of last month?
  • Which event created this entry?
  • Did every debit have a matching credit?
  • Can we reverse this without deleting history?
  • Can we replay the ledger from events?
  • Can finance reconcile cash, receivables, income, fees, and adjustments?
  • Can auditors see both the original mistake and the correction?

If the answer is no, the platform is not ready for scale. It may still work as a product. It may even grow quickly. But the ledger is already putting a ceiling on the company.

That ceiling always gets lower as volume grows.

Final thought

The right ledger architecture will not make your fintech move slower. It will let you keep moving after the product becomes serious.

Job-based ledgers help you start. Event-based ledgers help you react. Accrual-based ledgers help you tell the economic truth.

For credit, that truth matters every day interest is earned, every time a fee is incurred, every time a customer pays, and every time finance needs to close the books.

This article deliberately stayed inside a single-geography model. Multi-geography ledgers, currency conversion at the ledger layer, local accounting rules, and global consistency are out of scope here.

That is the next article.

References