A ledger-first loan management platform for BNPL operators, micro-finance lenders, and in-house installment programmes in Thailand, Indonesia, and the Philippines. Double-entry ledger, repayment schedule engine, reconciliation workflow, and aging-bucket collections — all in one system, not four. Designed to meet Bank of Thailand, OJK, and Bangko Sentral audit expectations. Thai data residency available.
Every BNPL operator in the region looked the same in its first year. A founder, an ops lead, a Google Sheet with one tab per merchant, another tab for the master loan register, a third tab for payments received, and a column somewhere called "Status (manual)." It worked. For a while.
By loan 3,000 the reconciler started coming in on Saturdays. By loan 6,000 the status column was two days out of date before anyone looked at it. By 10,000 active loans the CFO stopped trusting the portfolio figure on the Monday dashboard — not because it was wrong, exactly, but because nobody could tell her within twenty minutes why it had changed from Friday.
The bank feed imports ran. Nobody could guarantee every baht was allocated to the correct installment. Collections called customers from last week's aging report, not today's. A loan that should have been closed three weeks ago still showed "active" because the final payment was ฿3.50 short and nobody had decided what to do about it. Four different team members had four different answers to "what is our NPL ratio right now." All four were defensible. None were the same.
Operators often plug a payment gateway in, then a CRM, then a BI dashboard, then bolt each of them onto the spreadsheet. The ledger — the record of what is owed, what has been paid, and what remains — is reconstructed every month from pieces of four systems. That reconstruction is expensive, error-prone, and the first thing an external auditor asks to see.
Core banking platforms are built for universal banks with current accounts, card issuance, swaps, and treasury books. For a BNPL operator with 15,000 simple-interest installment loans, the licence fees and integration cost are three orders of magnitude too high for what is, operationally, a repayment-schedule tracker with a collections queue.
Bank of Thailand inspections, OJK fit-and-proper reviews, and BSP examinations increasingly expect loan-origination records, payment allocation evidence, a tamper-evident activity log, and aging-bucket reports on demand. "It's in the spreadsheet — let me put it together" is no longer an acceptable answer. The system must produce the audit artefact in minutes, not days.
"We missed a BOT deadline by two weeks because the portfolio aging report had to be rebuilt by hand from the spreadsheet and the payments CSV. Nothing was wrong with the data. Everything was wrong with how we stored it."
Conversation with a head of credit operations at a Bangkok consumer-lending startup · January 2026
BNPL Loan Manager is ledger-first. Every loan, every installment, every payment, every late fee, every restructure, every write-off is a row — in a double-entry ledger, with an append-only history, timestamped and attributed. Status is derived from the ledger, not stored on the loan record. The same query that answers "what is our outstanding portfolio" also answers "prove it — installment by installment."
The reconciliation workflow imports the bank feed, allocates payments against installments by reference or amount, and flags the residue. The collections workflow watches the aging buckets and escalates without being asked. The auditor's report is a route in the application, not a spreadsheet that somebody has to build.
Every financial event writes a debit and a credit. Principal disbursement, installment accrual, payment receipt, late-fee application, write-off — each has a ledger signature that adds to zero. The trial balance reconciles every day because it is mathematically required to. This is the baseline financial auditors assume; most BNPL startups do not have it because their system started as a list, not a ledger.
There is no "status" column on the loan record that an operator can set manually to "closed." A loan is closed when the sum of its payments equals the sum of its installments plus fees. The view layer renders the badge; the database never lies about it. A loan cannot be accidentally marked paid. A loan cannot be accidentally marked active when all installments have cleared. The ledger is the source of truth; the status is the view.
Thai data residency is a deployment switch, not a six-month integration project. KYC/AML hooks exist at the customer and merchant records — designed for integration with VerifyOne or an equivalent eKYC system, not as an afterthought. The activity log is append-only with row-level attribution. Aging-bucket reports, NPL ratios, and portfolio-quality snapshots are one-click PDF. BOT, OJK, and BSP exam language — 30-day, 60-day, 90-day buckets, restructure tagging, write-off attribution — is in the schema from day one.
This isn't a BI dashboard sitting on top of a spreadsheet. It's the loan system spreadsheets were pretending to be.
A five-step wizard. Every field validates server-side. Every number is live.
The operator picks a customer (searchable by name, NRC / KTP / PSN, or phone) or creates one inline with KYC fields. A merchant is selected from the active roster, the product and price captured, a down payment entered. Interest rate and term are pre-filled from the customer's credit score band and are editable within configured limits. As the operator types, the monthly payment, total interest, and total payable recalculate, and an installment preview table is drawn beneath the form. Required documents attach at step four. Step five is review-and-disburse; on confirmation, the loan, its installments, and an origination entry in the activity log are created in a single database transaction.
Every loan, filterable. Status derived, not stored.
The loan list is a single page with server-side search on loan number, customer name, merchant name, and product name. Status, merchant, category, and loan officer are dropdown filters; the URL is persisted for bookmarkable views. Principal, monthly payment, and outstanding columns are sortable. A summary strip above the table shows the total principal and total outstanding of the currently filtered slice. Clicking a row opens the loan detail view — overview, repayment schedule, payment history, documents, and activity log, each as a tab.
Paid, pending, overdue, partial, waived. Row-coloured. Installment-level actions inline.
The repayment schedule is a table, one row per installment, coloured by status. Each row carries: installment number, due date, principal portion, interest portion, total, paid amount, status badge, late-fee amount, days overdue, and — for pending, overdue, and partial installments — a Pay button that opens the log-payment modal with the amount pre-filled. The math is transparent: principal portion, interest portion, and total amount per installment are visible. An outsider can reconstruct the schedule on a calculator in thirty seconds. Auditors appreciate this.
Amount, method, reference, date. Submit. Ledger updates. Status re-derives.
Payments can be logged from three entry points: the loan detail page, the Pay button on an installment row, or the centralised payment processing screen that takes loan number as a search input. The form captures amount, payment method (bank transfer, mobile money, cash, check), reference number, payment date, and optional notes. On submit, the ledger posts a payment receipt, allocates it against the selected installment, updates the paid amount and status on that installment, recalculates the loan's outstanding balance, and — if the payment closes the final installment — moves the loan to a closed state. The activity log records the action with operator attribution and timestamp. The whole round-trip is under 300 milliseconds in the sandbox.
Import the feed. Auto-match on reference. Resolve residues in one place.
The reconciliation workflow takes a bank-statement CSV (typically the daily or weekly feed from the operator's collection bank) and matches rows against outstanding installments by reference number, customer code, and amount. Matched payments are posted through the same ledger path as a manual log-payment action, so the audit trail is identical whether a payment was keyed or imported. Unmatched rows drop into a residue queue: credits without a reference, amounts that don't match any installment, duplicates, and reversals. The operator triages the queue in one screen. Once cleared, the daily collection summary — total collected, method breakdown, per-operator attribution — is the closing figure for the day.
Five buckets. Click to expand. Log the action inline.
The collections dashboard opens with five bucket cards — Current, 1–30 days overdue, 31–60, 61–90, and 90+ — each showing the count of affected loans and the total overdue baht. Clicking a bucket reveals the loan list for that slice: loan number, customer name, phone, outstanding balance, overdue amount, maximum days overdue, last payment date, and last collection action. The inline action form on each row is deliberately minimal: a dropdown (Called customer, Sent SMS, Field visit, Promise to pay, Escalated, No contact), a free-text note, and a save button. The action posts to the activity log as a collection event and is visible on the loan's timeline within seconds.
Portfolio, aging, collection, merchant, disbursement. CSV and PDF. One click each.
Month-end is a routes problem, not a spreadsheet problem. Five report types cover the external-audit and regulator-facing asks: Portfolio Summary (all loans, balances, status), Aging Report (overdue installments by bucket), Collection Report (payments received in a date range, by method and officer), Merchant Performance (origination and default by merchant), and Disbursement Report (new loans in a date range, by merchant and officer). Each generates as CSV in under ten seconds for a 10,000-loan portfolio; Portfolio Summary and Aging Report also export to PDF via the bundled renderer. Date-range parameters are set by the operator. Filenames include the date; retention is configurable.
Every financial event posts to a double-entry ledger: principal disbursement, installment accrual, payment receipt, late fee, waiver, restructure, write-off. Debits and credits are balanced by database constraint, not by convention. The trial balance reconciles every night because it cannot do otherwise.
A loan is active, closed, defaulted, restructured, or written_off based on the state of its installments — not a field anyone can edit directly. Closing, defaulting, and writing off are explicit operator actions that update the ledger through a known path; the status badge renders from the ledger.
Installments are rows in a first-class table, with principal portion, interest portion, total amount, paid amount, late fee, and days-overdue fields. The schedule generates on loan disbursement in a single transaction. Flat-interest is the default (common across Thai, Indonesian, and Filipino BNPL); reducing-balance and custom schedules are configurable per product.
Bank-feed imports match against outstanding installments by reference, customer code, and amount. Matches post as payments through the standard ledger path. Unmatched rows queue for manual allocation in a single triage screen. Duplicate detection and reversal handling are built in.
Current, 1–30, 31–60, 61–90, and 90+ days overdue. One click reveals the slice; inline action logging (called, SMS, field visit, promise to pay, escalated, no contact) records the event to the activity log with operator attribution and timestamp. Designed for collections teams that want a queue, not a CRM.
Customer records carry NRC / KTP / PSN fields, date of birth, address, employment status, employer, and monthly income. Merchant records carry registration number, business type, contact person, commission rate, and credit limit. Hooks for eKYC integration (VerifyOne or equivalent) and AML screening exist; the operator decides whether to enable them per-jurisdiction.
Every action on a loan — origination, payment logged, status change, restructure, write-off, reminder sent, note added, document uploaded — is recorded with operator, timestamp, and before/after values. Entries cannot be edited or deleted through the application. The log is the audit artefact regulators ask for.
Admin, loan officer, collection officer, merchant admin, customer support — each with a defined matrix of screens and actions. Write-off requires admin. Restructure requires admin or loan officer. Merchant admin sees only its own loan book. Support is read-only on financial fields. The matrix is enforced at the Livewire component level, not by hiding buttons in the view.
A mid-sized BNPL operator running 5,000–15,000 active loans across ten to thirty merchants shifts three operational burdens when the ledger becomes the primary system. Reconciliation moves from a weekly ops ritual to a daily ten-minute pass. Month-end regulator and investor reporting compresses from three days of spreadsheet assembly to twenty minutes of parameter-setting and review. The NPL and aging figures on the Monday dashboard match the ledger because the ledger is the dashboard.
| Metric | Before BNPL Loan Manager | After BNPL Loan Manager (8-week pilot) |
|---|---|---|
| Reconciliation per ฿1M portfolio per week | 4–6 hours | 30–60 minutes |
| Time to produce aging report | 1–2 days | under 10 minutes |
| Unallocated-payments residue at month-end | 3–8% | under 0.5% |
| NPL figure on dashboard vs. month-end audit | ±2–5% drift | ±0.1% drift |
| Collection-action attribution completeness | 40–60% | 100% |
| Time to answer "why did outstanding move" | hours | one loan-detail page |
Figures derived from internal pilot benchmarks, the 200-loan working sandbox (120 active, 50 closed, 15 defaulted, 10 restructured, 5 written off), and our founder's prior enterprise delivery on consumer-lending and hire-purchase systems across Southeast Asia (2017–2026). Individual pilot results vary by portfolio size, product mix, existing system, and operational maturity.
In operational terms: the reconciliation analyst recovers three to four working days per month, the collections team works today's aging instead of last week's, and the CFO trusts the Monday portfolio figure. In compliance terms: the aging-bucket report exists as a route, not a spreadsheet to be assembled, so a regulator's question answered in days becomes a question answered in minutes. In portfolio-quality terms: the NPL ratio on the dashboard equals the NPL ratio the external auditor calculates from first principles — because they are derived from the same ledger, the same way.
BNPL Loan Manager is built to clear procurement, InfoSec, and compliance review at a regulated lender without custom exceptions. Claims of formal certification require customer-side audit; we do not advertise certifications we have not achieved.
| Stack | Laravel 13 + Livewire 4 (+ Volt) + Preline UI 4 + Tailwind CSS 3 + Alpine.js + PostgreSQL 18 |
|---|---|
| Charts | Chart.js, bound via an Alpine.js component wrapper — dashboard donut, stacked bar, line, horizontal bar |
| PDF export | barryvdh/laravel-dompdf for Portfolio Summary and Aging Report, with Thai / Indonesian / Filipino script support |
| CSV / Excel export | Native streaming CSV for five report types; compatible with Excel, Google Sheets, and LibreOffice Calc |
| Authentication | Laravel Breeze (session-based) with role column on users table; roles: admin, loan_officer, collection_officer, merchant_admin, customer_support |
| Authorisation | Laravel Gates + Policies, enforced at Livewire component level; matrix of 20+ gates documented in the UI design spec |
| Financial precision | All monetary columns decimal(15,2); no floating-point arithmetic in financial code paths |
| Interest model | Flat-interest by default (common across Thai, Indonesian, Filipino BNPL); reducing-balance and custom schedules configurable per product |
| Ledger | Double-entry; every financial event posts balanced debit/credit rows; trial balance reconciles nightly |
| Activity log | Append-only loan_histories table; every write captures user_id, action, description, old_values, new_values, timestamp |
| Sandbox scale | 200 loans, ~2,500 installments, ~1,500 payments, ~1,100 history entries seeded out of the box |
| Production scale | Single instance handles ~50,000 active loans on 4 vCPU / 8GB RAM; horizontally scalable beyond that via read replicas and queue workers |
| Processing time | Log-payment round trip under 300ms; 10,000-loan aging report under 10 seconds; CSV exports stream so memory usage is constant |
| Deployment | On-premises via Docker Compose; or hosted tenant on Thai (NIPA) / Indonesian / Filipino data-resident infrastructure |
| Infrastructure | Minimum 2 vCPU / 4GB RAM for pilot; 4 vCPU / 8GB RAM for production; PostgreSQL 18 with WAL archiving recommended |
| Integrations (roadmap) | eKYC (VerifyOne, Jumio, Onfido), AML screening (ComplyAdvantage, Refinitiv), bank-feed importers (per-bank CSV / Open Banking where available), SMS/email reminder providers, accounting handoffs (QuickBooks, Xero, PEAK) |
| API | REST endpoints for loan origination, payment posting, and portfolio queries (post-pilot); authenticated via Laravel Sanctum tokens |
On-premises deployment keeps every byte inside the operator's own infrastructure. For hosted tenants, Thai data residency is available on NIPA Cloud AI-adjacent Thai infrastructure; Indonesian and Filipino data-resident hosting is on a per-deployment basis with named data-centre providers. Designed to meet Bank of Thailand data-localisation expectations for supervised non-bank lenders, OJK POJK 77 data-residency requirements, and BSP data-privacy circulars.
Every financial event writes a balanced debit and credit; the ledger posts are wrapped in a single database transaction alongside the installment and loan updates. An aborted payment posting cannot leave the loan in a partially-updated state. Nightly trial balance reconciles; a mismatch raises an operations alert before the business day opens.
Append-only loan_histories table records every action with operator attribution, timestamp, and before/after values. Entries cannot be edited or deleted through the application or through the standard database user the application uses in production. Retention is configurable from 12 months (default) to 10 years to match regulatory retention obligations.
Customer and merchant records carry the identifier, date-of-birth, address, and business-registration fields required for regional KYC (Thai NRC / Indonesian KTP / Filipino PSN). Hooks for eKYC providers and AML screening services exist at the customer-create and merchant-create paths; the operator decides whether to enable, and against which provider, per-jurisdiction. Designed for integration, not bundled with a specific provider, so the compliance lead can select against internal policy.
Five operator roles (admin, loan officer, collection officer, merchant admin, customer support) with a documented access matrix. Write-off is admin-only. Restructure is admin or loan officer. Merchant admin sees only its own book. Support is read-only on financial fields. Enforced in Laravel Gates and checked at Livewire component level — not by hiding buttons in the view.
TLS 1.3 in transit; AES-256 at rest for the database and attached file storage; sensitive fields (KYC identifiers, attached documents) can be configured for column-level encryption at the application layer. Passwords are bcrypt-hashed.
Designed to meet Bank of Thailand reporting, aging, and provisioning expectations for supervised non-bank consumer lenders; OJK POJK 77 / POJK 10 expectations for multifinance and peer-lending operators; and BSP reporting cadence for BSP-registered lending companies. Aligned with Thai PDPA for personal-data handling. The platform is not itself a regulatory filing; it produces the artefacts (aging-bucket reports, portfolio-quality snapshots, activity log exports) that the operator uses inside its own regulator-facing process. Formal certification (ISO 27001, PCI-DSS where applicable) is on the roadmap for production deployments; pilot deployments can be configured to operate inside the client's existing ISO envelope.
BNPL Loan Manager deploys as a pilot with a written KPI and an honest ending. Either the measured outcomes clear the KPI and we move to a production rollout — or they don't and we part ways with the pilot report in the operator's hands. No open-ended contracts, no roadmap hostages.
Operational walkthrough with the team currently running the portfolio — loan officers, reconcilers, collectors, the compliance lead, the CFO. Review the existing system, the regulator-facing report cadence, and the portfolio state. Write the KPIs: target reconciliation time, target aging-report turnaround, target residue percentage, target NPL-drift tolerance. Map data migration scope. Fixed pilot price confirmed.
Deploy to one book — typically one merchant segment or one portfolio slice — with real operators and real data. Week 3 migrates the opening balances and the existing loan book. Weeks 4–6 run the full loop: origination, reconciliation, collections, month-end. Week 7 stress-tests the audit artefacts. Weekly reviews are working-software sessions, not slide decks.
Measurement against the written KPIs. Honest gap analysis. Recommendation: scale deployment to the full portfolio, continue pilot on a second slice with adjustments, or walk away. All three endings are valid.
If the pilot cleared the KPI: roll out to the full portfolio, deploy on-premises or to a dedicated data-resident tenant, enable integrations (eKYC, AML screening, accounting handoff, bank-feed importers). If it didn't: we hand over the pilot findings and part ways.
Fixed-price pilot. Walk-away clause. Roadmap influence for design partners. No surprises.
Eight to twelve weeks, fixed. Deploy to one portfolio slice with one operating team. All costs confirmed in week 0.
Hosted tenant. Priced by monthly active-loan count.
Annual licence, unlimited active loans, on-premises deployment.
restructured. The activity log records the restructure event with before/after values. Regulator-facing restructure reporting is a filter on the Portfolio Summary.waived, the loan status becomes written_off, and the outstanding balance is posted against the operator's provision or P&L per the chart-of-accounts setup. The activity log records the write-off. Post-write-off recoveries (rare but not unheard of) are handled as recovery entries with their own ledger signature — they do not "re-open" the loan.The sandbox has 200 loans across five lifecycle states, 100 customers, ten merchants, and 18 months of payment history. Log a payment on an overdue installment. Watch the badge change, the outstanding balance recalculate, and the entry land in the activity log — in under a second. If the mechanics feel right, the pilot conversation starts from there. If they don't, you'll know inside five minutes.
BNPL Loan Manager didn't start from scratch. The ledger structure, the repayment-schedule engine, the reconciliation workflow, and the collections model are informed by our founder's prior enterprise delivery on consumer-lending and hire-purchase systems across Southeast Asia (2017–2026) — including BNPL, micro-finance, and auction-finance platforms that processed real portfolios at real scale, under real regulatory scrutiny.
That history is why the status column doesn't exist, why the activity log is append-only, and why the aging-bucket schema matches the language Thai, Indonesian, and Filipino regulators actually use. It is also why the pilot pricing is fixed and the walk-away clause is in writing — running a loan-management platform in production is expensive to get wrong, and the honest way to engage with that risk is to price the pilot, name the KPIs, and agree in advance what happens if they're not met.
Read the full track record →