PROJECT STUDY

Ledger & Reconciliation

Every rupiah traced, every gap explained.

Payment reconciliation for Indonesian finance teams: QRIS, virtual account and e-wallet payments on a balanced ledger, matched to provider settlements and bank statements with reasons.

Explore the build
Reconciliation workbench illustration: bank statement lines joined to ledger items, two matched transfers, a QRIS settlement linked to three order payments as a suggested batch match, and an unmatched transfer flagged as an exception.
Original interface illustration · Synthetic demo data.View larger

At a glance

Industry
Finance
Capabilities
6
Technologies
8
Year
2026

The challenge

Indonesian businesses take payments through QRIS, virtual accounts at several banks, e-wallets and card gateways, and providers settle in batches, net of fees, sometimes days later.

Finance teams reconcile orders, provider settlements and bank statements by hand in spreadsheets, so partial payments, duplicates, refunds and late settlements can take days to resolve.

The work needs more than a match rate. Every figure in a cash report has to lead back to the journal that produced it, and every correction has to leave a trail that an auditor can follow.

What we built

Ledger & Reconciliation connects payment events, a double-entry ledger and statement matching in one TypeScript product, with a workbench where the finance team confirms what the engine suggests.

A balanced, append-only ledger

Accounts and journals in whole rupiah, where every journal's postings sum to zero, a correction is a reversal entry rather than an edit, and any balance can be read as of a point in time

A balanced, append-only ledger

Accounts and journals in whole rupiah, where every journal's postings sum to zero, a correction is a reversal entry rather than an edit, and any balance can be read as of a point in time

Verified payment webhooks

Events from a QRIS acquirer, a bank virtual account and an e-wallet are checked for an HMAC signature and a fresh timestamp, de-duplicated and applied through a payment state machine

Explainable matching

Provider settlement and bank statement CSVs are matched against the ledger by exact reference, deterministic rules and fee-aware amount and date windows, and many-to-one batch settlements are solved as a bounded subset-sum. Every match records its reason and a confidence

A reconciliation workbench

Statement lines sit on one side and ledger items on the other, with suggested matches and their reasons in between, next to an exceptions inbox and a journal explorer

Exceptions closed by journal

Unmatched lines, amount mismatches, duplicates and late settlements are assigned and resolved, and each resolution posts an adjusting journal

Reports that open their journals

Daily cash position, fee (MDR) analysis per channel and ageing of unreconciled items, with every figure traceable to the journals behind it

How it works

  1. Payments into the ledger

    Each provider's webhook is verified, stored once and applied by a state machine that accepts events in any order. The payment's state change, its journal and an outbox message commit in one transaction, and the transactional outbox then publishes the ledger event.

  2. Statements into matches

    Settlement reports and bank statements arrive as CSV files and are matched in tiers: exact reference first, then deterministic rules, then amount and date windows net of each channel's fees. A bank credit that covers a batch of payments is explained as a bounded subset-sum within a tolerance, and every match carries its reason and confidence into the workbench.

  3. Exceptions into journals

    Whatever stays unmatched lands in the exception queue as unmatched, amount mismatch, duplicate or late. A person assigns and resolves each item, and the resolution posts an adjusting journal, so the ledger and the reports stay in step.

  4. Reports from postings

    Balances are derived from postings, so any balance can be read as of a point in time. The daily cash position, the fee analysis per channel and the ageing of unreconciled items are built from the same postings, and each figure opens the journals behind it.

Engineering insights

Money rules are enforced where they cannot be skipped: in the database, at the webhook boundary and in property-based tests.

Stack by layer

Frontend
  • React
  • TypeScript
  • Vite
Backend
  • Node.js
  • TypeScript
  • Fastify
Data
  • PGlite (PostgreSQL)
  • Drizzle ORM
Infrastructure
  • fast-check
  • Biome
  1. The database refuses an unbalanced journal

    Amounts are whole rupiah integers, never floating point, and a journal whose postings do not sum to zero is rejected by the application and again by the database. Corrections are reversal entries, and an idempotency key makes a retried posting return the first journal instead of posting twice

  2. A webhook is a claim until verified

    Each event needs a valid HMAC signature and a timestamp inside a tolerance window, and it is stored once under its event ID, because the window alone would still let a replay through while it is open. The state machine then accepts events in any order, so a late expiry cannot undo a confirmed payment

  3. One transaction, then the outbox

    A payment's state change, its journal and its outbox message commit together, so downstream systems never hear of a payment the ledger does not hold

  4. Batches solved as a bounded search

    Many-to-one settlements are explained as a subset-sum over the candidate items, bounded in size and within a tolerance, so the search stays small. The answer reaches the workbench as a suggested match with its reason and confidence

  5. Properties, not examples

    Property-based tests with fast-check generate payment histories and assert that every journal balances, that balances equal the sum of postings at any point in time, that a reversal restores balances and that matching never uses a statement line twice

Design system

Ledger precise is calm, numerical and auditable, made for finance staff who spend month-end in dense tables: navy ink on a paper ground stays restful through long sessions, and each status colour means one thing only, so attention goes straight to what needs a decision.

Colour palette

  • Navy ink#0b2545Text, figures, headings and primary actions
  • Paper#f7f9fcPage, table and panel background
  • Matched mint#0f9d7aMatched status, as icons, row markers and match lines beside a navy label
  • Suggested amber#c27c0eSuggested matches waiting for a person to confirm, beside a navy label
  • Exception red#c0362cExceptions only, never a negative amount
  • Neutral line#dbe2eaTable rules and dividers
  • Colour

    Matched mint, suggested amber and exception red each mark one status, and red marks exceptions only, never a negative amount. Mint and amber sit just above WCAG's 3:1 on paper, enough for icons and markers but not for text, so every status pairs its colour with a navy label

  • Typography

    Inter with tabular figures on every amount, so digits align down a column, with amounts right-aligned in whole rupiah. References such as virtual account numbers, provider transaction IDs and journal numbers are set in a monospace face, so long identifiers can be compared character by character

  • Components

    A workbench that sets statement lines beside ledger items, match details that show the reason and confidence, an exception queue whose resolution posts an adjusting journal, and report tables in which every figure opens the journals behind it

  • Traceability

    Every number on screen is traceable to its journal, which is the trail an auditor follows

Where this fits

Any business that takes payments through several channels can start from the same foundation: a ledger that cannot be unbalanced and matching that explains itself. Explore our business systems and web apps, or talk to us about your reconciliation process.

talk to us about your reconciliation process

Let’s connect the dots

Let's put these ideas to work.

Share a process, product, or problem you want to improve. We will work through the possibilities together.

Discuss your project