Milaan: how we built a reconciliation engine that matched every rupee across UPI, cards, COD and bank settlements
A fast-growing D2C brand was closing its books three weeks late because nobody could say which payments had actually landed. We built Milaan, a reconciliation platform that pulls orders, gateway reports, courier COD remittances and bank statements into one ledger and matches them automatically — with 28 features and a finance team that now closes the month in days.

A 40-tab spreadsheet and a three-week close
Milaan's founder did not open with a feature list. She opened a spreadsheet. It had forty tabs: one per payment gateway, one per courier partner's COD remittance, one per bank account, and a dozen with names like "FINAL v3 (use this)". Two people in her finance team spent most of every month copying numbers between them.
Her D2C business sold across its own website, two marketplaces and a growing offline channel. Money arrived through UPI, cards, wallets, pay-later providers and cash on delivery collected by four different couriers. Each source reported in its own format, on its own schedule, with its own idea of what a transaction ID was.
The result was a month-end close that took three weeks, refunds that were sometimes paid twice, and COD cash that couriers had collected but not yet remitted — nobody could say how much. "I know we're profitable," she told us, "I just can't prove it on any given day."
“I know we're profitable. I just can't prove it on any given day.”
Reframing: not a dashboard, a ledger
The brief said "a reconciliation dashboard". In the first workshop we asked the finance lead to walk us through a single order, from click to cash in the bank. It took forty minutes and touched seven systems. That walk-through changed the project: a dashboard would only show the mess faster. What Milaan needed was a single ledger where every rupee had one identity from order to settlement.
We proposed a three-layer design. Ingestion would pull raw data from every source without changing it. A normalisation layer would translate each source into one common transaction model. A matching engine would link orders, payments, refunds, fees and bank credits, and anything it could not match would land in a queue for a person to resolve.
- One answer to "has this order's money reached our bank?"
- COD cash outstanding per courier, per day
- Gateway fees and GST on fees checked against contracted rates
- A month-end close measured in days, not weeks

Six weeks of data we had never seen
Before designing screens we asked for six weeks of raw exports from every source. This was the least glamorous and most valuable part of the project. We found gateways that split one payment into several settlement lines, a courier that remitted COD in weekly lumps with no order references, refunds that appeared in the gateway report two days before the order was marked returned, and bank statements where the narration was cut off after 30 characters.
Every one of these quirks became a matching rule. By the end of the audit we had a catalogue of 43 edge cases, each with a real example, and we built the matching engine's test suite from them before writing the engine itself.
Designing for people who live in spreadsheets
Finance teams trust spreadsheets because they can see every number and how it was calculated. Our designer's rule was that Milaan should never ask them to trust a number they could not trace. Every total can be clicked open to the underlying transactions, and every match shows which rule made it.
The daily workflow became an exceptions inbox: the engine handles everything it is sure about, and the team spends their morning on the handful of items it is not. Each exception shows the likely cause — a partial refund, a missing courier reference, a fee that does not match the contract — and suggests the most probable match.
The team
Reconciliation is a data problem wearing a UI, so the team was weighted toward backend and data engineering. The designer and the engagement lead spent a day a week sitting with Milaan's finance team throughout the build.
Scope, weekly demos and parallel-run sign-off with the finance lead.
Exceptions inbox, drill-down ledger views and the close checklist.
Ingestion connectors, normalisation layer and the ledger APIs.
Matching engine, scheduling, and the edge-case test suite.
Next.js finance app with fast, filterable tables.
Exception triage suggestions and bank-narration parsing.
Parallel-run comparisons and reconciliation regression tests.
8 people in total, working as one team.
Five decisions we made early
Money software punishes shortcuts, so we wrote down the expensive decisions and agreed them with the founder and her finance lead before building.
Store raw data, or only the cleaned version?
- Store only normalised transactions
- Keep every raw file immutable, normalise on top
Our call: Keep every raw file immutable, normalise on top. When a number is questioned months later, the raw file is the evidence. Keeping it immutable meant we could re-run matching with improved rules at any time without losing the audit trail.
Rules engine or machine learning for matching?
- Train an ML model to match
- Deterministic rules in passes, AI only for suggestions
- Manual matching with better UI
Our call: Deterministic rules in passes, AI only for suggestions. Auditors need to know why two records were matched. Deterministic passes give a clear reason for every match; AI helps rank candidates for the exceptions a person resolves.
Real-time or daily reconciliation?
- Real-time streaming
- Scheduled runs as each source publishes
Our call: Scheduled runs as each source publishes. Gateways, couriers and banks publish settlements on their own schedules, so real-time would only show partial pictures. Runs triggered as each file lands gave accurate numbers without the complexity.
Money as floating point or integers?
- Decimal floats
- Integer paise everywhere
Our call: Integer paise everywhere. Floating-point rounding errors are unacceptable in a ledger. Every amount is stored and calculated in integer paise, converted only for display.
Replace the spreadsheet on day one?
- Hard cut-over
- Two month-ends in parallel before switching
Our call: Two month-ends in parallel before switching. Running both let the finance team verify Milaan against numbers they already trusted, and surfaced errors in both directions before anyone depended on it.
Everything Milaan does
Twenty-eight features shipped in the first production release, organised around the finance team's day: bring the data in, match it, handle what does not match, and close the books.
- 01Payment gateway connectors
Automatic pulls of transaction and settlement reports from each gateway.
- 02Marketplace settlement import
Parse marketplace payout reports including commissions and deductions.
- 03Courier COD remittance import
Bring in COD collection and remittance files from every courier partner.
- 04Bank statement ingestion
Import statements from multiple accounts with narration parsing.
- 05Order and refund sync
Pull orders, cancellations and refunds from the storefront and ERP.
- 06Multi-pass matching
Exact references first, then amount-and-date windows, then grouped settlements.
- 07Many-to-one settlement matching
Link hundreds of payments to a single bank credit.
- 08Partial refund handling
Match partial and multiple refunds against the original payment.
- 09Match explanations
Every match records which rule created it and why.
- 10Re-run with new rules
Reprocess any period when rules improve, without losing history.
- 11Exceptions inbox
Only unmatched or suspicious items, ranked by amount and age.
- 12AI-suggested matches
Likely candidates with confidence scores for each exception.
- 13Split and merge tools
Resolve one-to-many and many-to-one cases by hand.
- 14Comments and assignment
Assign exceptions to team members and leave notes.
- 15Ageing alerts
Flags when an exception has been open too long.
- 16COD outstanding by courier
Collected-but-not-remitted cash per courier, per day.
- 17Gateway fee audit
Compare every fee and GST on fees with contracted rates.
- 18Chargeback tracking
Track disputes from notice to resolution with evidence.
- 19Settlement forecasting
Expected bank credits for the coming days by source.
- 20Month-end close checklist
Guided steps with owners and sign-off.
- 21Unified ledger view
Drill from any total down to individual transactions.
- 22Accounting export
Journal entries ready for the accounting system.
- 23Channel profitability report
Revenue, fees and returns by sales channel.
- 24Scheduled reports
Daily cash and weekly exception summaries by email.
- 25Role-based access
Separate rights for viewers, reconcilers and approvers.
- 26Immutable audit log
Every manual action recorded with who, when and why.
- 27Maker-checker approvals
Manual matches above a threshold need a second approver.
- 28Data retention and export
Full export of raw and processed data at any time.

Building it without breaking the books
We built Milaan alongside the existing spreadsheet, not instead of it. For two full month-end closes the finance team ran both, and we compared Milaan's numbers with theirs line by line. Every difference was either a bug in Milaan or an error in the spreadsheet; roughly half turned out to be the spreadsheet.
The matching engine went through four major revisions. The biggest leap came from matching in passes — exact reference matches first, then amount-and-date windows, then many-to-one settlement groups — rather than one clever algorithm trying to do everything at once.
- Weeks 1–3Discovery and data audit
Workshops, one order traced end to end, six weeks of raw exports collected.
- Weeks 4–6Edge cases and design
43 edge cases catalogued, test suite written, exceptions inbox prototyped.
- Weeks 7–13Build
Connectors, normalisation, four revisions of the matching engine.
- Weeks 14–17Parallel run
Two month-end closes run on both Milaan and the spreadsheet.
- Week 18Go-live
Milaan becomes the system of record from a new quarter.
Go-live and the first fast close
Milaan became the system of record at the start of a new financial quarter, which gave the team a clean opening balance. The first month-end close on Milaan took a fraction of the usual time, and for the first time the founder could see outstanding COD per courier on a single screen during a board meeting rather than promising to follow up.
The feature the finance team talks about most is not the matching engine. It is the fee audit, which compares every gateway and courier charge against the contracted rate and flags overcharges automatically.
What we took away
Reconciliation projects are won or lost in the data audit. The six weeks spent collecting ugly real-world exports saved months of guessing, and the edge-case catalogue became the most valuable artefact of the whole project.
Explainability is the product. Finance teams will adopt automation only when they can see why it did what it did; every match in Milaan carries its reason, and that is why the team trusted it within weeks.
- Next.js
- NestJS
- PostgreSQL
- Python matching engine
- Airflow-style scheduler
- LLM-assisted exception triage
- AWS Mumbai

