Product & Technical Blueprint

DuitNow-native Accounts Receivable & Credit Control

From selected opportunity to buildable plan — one document for the commercial founder, one for the technical founder, same product, same delivery commitments.

Product definition

The selected idea, restated as a product: a Malaysian SME distributor sends 30–90 day credit to its business customers. Chasing late payment today means someone manually checking a spreadsheet, texting a buyer, then matching the bank statement by eye days later. This product automates that loop end to end, in Malaysian Ringgit, over WhatsApp, with a DuitNow QR code attached to every chase.

Problem

Late B2B payment is routine in Malaysia and the tools built to fix it (Chaser, Gaviti, Kolleno, TreviPay) are real, funded, and well-built — for a USD-billing enterprise with a live bank-API feed. None of them speak DuitNow, reconcile a Maybank or CIMB statement, or price for a RM2–20m/yr distributor. The gap is not "AR software doesn't exist," it's "AR software doesn't exist for this buyer."

Target customers

SME distributors and wholesalers in Malaysia extending 30–90 day terms to other businesses, roughly RM2–20m/yr revenue. The economic buyer is the finance manager or, at this size, the owner directly.

Core value proposition

Get paid faster without hiring a collections clerk: automatic ageing visibility, reminders that already carry a scannable DuitNow QR code, and a reconciliation view that tells you which invoices are actually settled — without replacing the accounting software already in use.

User roles

RoleCanCannot
OwnerEverything, plus billing and user management—
Finance staffManage customers, invoices, reminders, reconciliationBilling, inviting/removing users

Key business rules

Minimum Sellable Product (MSP)

A finance manager can load their open invoices (CSV or manual entry), see them ranked by age, trigger a WhatsApp/email reminder carrying a DuitNow QR code, and upload a bank statement to mark invoices paid. That alone is sellable — it replaces the spreadsheet-plus-manual-WhatsApp workflow on day one.

PriorityFeature
P0 — launchInvoice CSV import + manual entry; ageing dashboard; scheduled WhatsApp/email reminders with embedded DuitNow QR; manual bank-statement CSV upload and matching; single-currency MYR
P1 — after first customersAccounting-software export/import (SQL Account, AutoCount); reminder template customisation; partial-payment handling in the UI (not just the data model); basic reporting export
P2 — laterLive bank-feed integration (if/when a viable MY open-banking API exists); multi-currency; dispute/credit-note workflow; multi-entity accounts for distributors with several registered companies

Nothing here is added because a competitor has it — each P0 item is load-bearing for the MSP's single promise: see what's owed, chase it, confirm it landed.

Document A

Business & Product Overview — for the commercial founder

A.1

What we're selling

A web tool that chases unpaid invoices for you and tells you the moment they're actually paid. Load your open invoices, it ranks them by how overdue they are, sends a WhatsApp or email reminder with a DuitNow QR code attached so the buyer can pay in two taps, and when you upload your bank statement it matches payments back to invoices automatically.

A.2

Who should buy it

A distributor or wholesaler, roughly RM2m–RM20m a year in revenue, who gives their own customers 30 to 90 days to pay and currently chases that money by spreadsheet and manual WhatsApp messages. The person who signs is the finance manager, or the owner directly in a smaller shop.

A.3

Problems it solves

A.4

How the product works

  1. Load invoices — either upload a CSV from your existing books or type them in directly.
  2. The dashboard sorts every open invoice by age: current, due soon, overdue.
  3. On a schedule you set, the system sends a WhatsApp or email reminder with a DuitNow QR code the buyer scans to pay instantly.
  4. When money arrives, upload your bank statement — the system matches each transaction to the invoice it settles and marks it paid.
A.5

Main selling points

A.6

What's included in the first release

A.7

What's not included (yet)

Needs confirming before we promise this feature to anyone: whether we can legally generate our own DuitNow QR codes, or whether we need to route payment collection through a licensed gateway partner (such as CHIP) that already issues them. This changes neither the pitch nor the price, but it changes which company's logo sits on the QR code, so don't commit a launch date in a customer conversation until this is confirmed. See the technical blueprint, section B.10.
A.8

Customer onboarding

  1. Sign up and enter company details.
  2. Import their existing customer list and open invoices (CSV template provided, or type them in).
  3. Set the reminder schedule (e.g. 7 days before due, on due date, 7/14/30 days overdue).
  4. Send the first batch of reminders and watch the ageing dashboard update as payments come in.

Expect to sit with the first few pilot customers while they import their first invoice batch — the CSV template won't match their export format perfectly on the first try.

A.9

Expected delivery timeline

6–8 weeks to an MVP a pilot customer can actually use, assuming the DuitNow QR question above resolves quickly and the first 2–3 real bank statement samples are in hand before week 5 (statement parsing is the one part of this build that can't be estimated from a spec alone — see the technical roadmap).

A.10

Important limitations

Document B

Technical Blueprint — for the technical founder

B.1

Workflows

Six flows, drawn to the actual product above — not a generic SaaS template.

A. Complete customer journey

flowchart LR
  A[Hears about us via
distributor assoc. or
accounting-firm referral] --> B[Books a demo with
commercial founder] B --> C{Signs up for
trial?} C -- no --> Z[Stays manual —
follow up in 30 days] C -- yes --> D[Onboards: imports
invoices + sets reminders] D --> E[Sends first reminder batch] E --> F{Gets paid faster
in first 30 days?} F -- no --> G[Churns at trial end] F -- yes --> H[Converts to paid plan] H --> I[Monthly: uploads bank
statement, reviews ageing] I --> J[Renews month over month]
The product only earns renewal if the first 30 days visibly speed up a real payment — everything before that is just getting one chance to prove it.

B. Customer onboarding

flowchart TD
  A[Create account] --> B[Enter company + buyer
customer list] B --> C{Has invoice
export file?} C -- yes --> D[Upload CSV] C -- no --> E[Enter invoices manually] D --> F{Import
validates?} F -- no --> G[Show row-level errors,
fix and re-upload] G --> F F -- yes --> H[Set reminder schedule
+ WhatsApp/email] E --> H H --> I[Review ageing dashboard] I --> J[Send first reminder batch]
CSV validation failures are the most likely first-session drop-off point — row-level errors, not a single generic failure, are load-bearing.

C. Core product operation — the ageing/reminder/reconciliation loop

flowchart TD
  A["Invoice created
(import or manual)"] --> B[Scheduler computes
age bucket daily] B --> C{Due date
approaching or
passed?} C -- no --> B C -- yes --> D[Generate reminder
+ DuitNow QR] D --> E[Send via WhatsApp/email] E --> F[Log sent reminder] F --> G{Bank statement
uploaded?} G -- no --> B G -- yes --> H[Parse transactions] H --> I{Transaction matches
an open invoice?} I -- yes, confident --> J[Auto-mark invoice
paid/partially paid] I -- low confidence --> K[Queue for manual match] J --> L[Invoice leaves
ageing dashboard] K --> M[Finance staff
confirms match] M --> L
The loop has one real failure mode worth drawing: a transaction the matcher isn't confident about goes to a human, never silently to the wrong invoice.

D. User roles and permissions

flowchart LR
  subgraph Owner
    O1[Manage billing]
    O2[Invite/remove users]
    O3[All finance-staff actions]
  end
  subgraph FinanceStaff[Finance staff]
    F1[Manage customers]
    F2[Manage invoices]
    F3[Send/schedule reminders]
    F4[Upload statements,
confirm matches] end Owner --> FinanceStaff
Owner is a superset of finance staff, plus the two things a two-person pilot customer genuinely cares about gatekeeping: money (billing) and access (users).

E. Subscription and payment process

flowchart TD
  A[Trial signup] --> B[14-day trial,
full P0 feature set] B --> C{Converts before
trial ends?} C -- no --> D[Account downgrades
to read-only] C -- yes --> E[Owner enters payment
method via gateway] E --> F[One-time setup fee
RM1,500 charged] F --> G[Monthly plan billed
RM249-499 by volume] G --> H{Payment
succeeds?} H -- no --> I[Retry + email alert,
then suspend after
grace period] H -- yes --> J[Active — next billing
cycle in 30 days]
A failed renewal suspends access after a grace period rather than deleting data — a finance tool that silently drops invoices on a failed card is the worst possible failure mode.

F. Administrative workflow (our own ops)

flowchart TD
  A[Pilot customer
signs up] --> B[Founder manually
verifies account +
reviews first CSV import] B --> C{Import clean?} C -- no --> D[Founder fixes template
or assists customer] C -- yes --> E[Monitor reminder
delivery + bank-match
accuracy for 2 weeks] E --> F{Any failed
WhatsApp sends or
mismatches?} F -- yes --> G[Manual fix +
log as a product bug] F -- no --> H[Hand off to
self-serve]
At pilot scale there is no admin dashboard yet — the founder is the admin workflow. This box becomes a real internal tool only once self-serve volume demands it (P1).
B.2

Technical architecture

A modular monolith on managed infrastructure — nothing here justifies microservices, a job queue cluster, or a dedicated data pipeline at pilot scale.

Recommended stack

LayerChoiceWhy
Frontend + backendNext.js (App Router), deployed to VercelOne codebase, one deploy target, matches the house stack already used on other projects — no new tooling to learn mid-build
DatabasePostgres via Neon or SupabaseRelational fits this domain exactly (invoices, payments, matches are all foreign-keyed records); Supabase additionally gives auth and storage for free if chosen
ORMDrizzleExplicit schema, cheap to reason about tenant-scoping by hand in every query
AuthSupabase Auth or ClerkDon't hand-roll auth for a product that stores other companies' financial data
File storageVercel Blob or Supabase StorageCSV imports and uploaded bank statements are small files, no need for a dedicated object-storage setup
Background jobsVercel Cron for the daily ageing/reminder scheduler; a simple queue table for reminder sendsReminder sending and nightly ageing recompute are the only async work — a full queue product (SQS, BullMQ+Redis) is overkill at this volume
WhatsApp deliveryWhatsApp Business Platform API at paid-customer scale; a manual "copy to WhatsApp" wa.me link for the free pilot phaseAvoids paying for API access and Meta Business verification before a single customer has paid
Payments (our own billing)CHIP or StripePer-transaction pricing, no large upfront integration cost
Hosting/deployVercelZero-ops deploy, generous free tier through the pilot phase
flowchart LR
  subgraph Client
    Browser
  end
  subgraph Vercel
    NextApp["Next.js app
(UI + API routes)"] Cron["Vercel Cron
(daily ageing + reminder scheduler)"] end subgraph DataLayer[Data layer] PG[(Postgres
Neon/Supabase)] Blob[(File storage
CSV + statements)] end subgraph External[External services] WA[WhatsApp Business
Platform API] Email[Email provider
e.g. Resend] Pay[Billing gateway
CHIP/Stripe] QR[DuitNow QR issuer
— TBD, see B.10] end Browser -->|HTTPS| NextApp NextApp --> PG NextApp --> Blob Cron --> PG Cron --> WA Cron --> Email NextApp --> Pay NextApp --> QR
One app, one database, one scheduled job. The DuitNow QR issuer box is drawn as external and unresolved on purpose — see the assumption in B.10 before building against it.

Security

Monitoring and backups

B.3

Database and API design

Multi-tenant by design: every distributor is an account, and every other table hangs off it.

erDiagram
  ACCOUNTS ||--o{ USERS : has
  ACCOUNTS ||--o{ BUYER_CUSTOMERS : has
  ACCOUNTS ||--o{ INVOICES : has
  ACCOUNTS ||--o{ BANK_IMPORTS : has
  ACCOUNTS ||--|| SUBSCRIPTIONS : has
  BUYER_CUSTOMERS ||--o{ INVOICES : owes
  INVOICES ||--o{ REMINDERS : triggers
  INVOICES ||--o{ PAYMENTS : settled_by
  BANK_IMPORTS ||--o{ BANK_TRANSACTIONS : contains
  BANK_TRANSACTIONS ||--o| PAYMENTS : matched_to

  ACCOUNTS {
    uuid id PK
    text company_name
    text registration_no
    text status
    timestamp created_at
  }
  USERS {
    uuid id PK
    uuid account_id FK
    text email
    text role
    timestamp created_at
  }
  BUYER_CUSTOMERS {
    uuid id PK
    uuid account_id FK
    text name
    text phone
    int credit_terms_days
  }
  INVOICES {
    uuid id PK
    uuid account_id FK
    uuid buyer_customer_id FK
    text invoice_number
    numeric amount
    date issue_date
    date due_date
    text status
  }
  REMINDERS {
    uuid id PK
    uuid invoice_id FK
    text channel
    timestamp scheduled_at
    timestamp sent_at
    text status
  }
  BANK_IMPORTS {
    uuid id PK
    uuid account_id FK
    text bank_name
    text status
    timestamp created_at
  }
  BANK_TRANSACTIONS {
    uuid id PK
    uuid import_id FK
    date txn_date
    numeric amount
    text match_status
  }
  PAYMENTS {
    uuid id PK
    uuid invoice_id FK
    uuid bank_transaction_id FK
    numeric amount
    date paid_at
  }
  SUBSCRIPTIONS {
    uuid id PK
    uuid account_id FK
    text plan
    text status
    date current_period_end
  }
    
Tenancy boundary is the account_id foreign key present on every top-level table — a buyer customer, invoice, or bank import that doesn't trace back to the requesting user's account is invisible by construction.

Key constraints

API endpoints

EndpointAuth / rolePurpose
POST /api/auth/loginPublicSession-based login
GET /api/accountAny roleCurrent tenant profile
POST /api/users/inviteOwner onlyInvite a finance-staff user
GET/POST /api/customersOwner, finance staffList / create buyer customers
GET/POST /api/invoicesOwner, finance staffList / create invoices
POST /api/invoices/importOwner, finance staffCSV import, returns row-level validation errors
POST /api/invoices/:id/reminders/send-nowOwner, finance staffForce-send a reminder outside schedule
POST /api/bank-importsOwner, finance staffUpload a bank statement file
POST /api/bank-imports/:id/transactions/:txnId/matchOwner, finance staffConfirm or correct a suggested match
GET /api/billingOwner onlyCurrent plan and invoice history
POST /api/webhooks/payment-providerSigned webhook, no user sessionBilling provider payment events

Request/response bodies follow the entities above directly (e.g. POST /api/invoices takes {buyer_customer_id, invoice_number, amount, issue_date, due_date} and returns the created invoice with its computed status) — no separate schema needed beyond the table definitions until implementation surfaces an exception.

B.4

UI/UX planning

Sitemap

RoutePurpose
/loginSession entry
/onboardingCompany setup → import invoices → reminder schedule
/dashboardAgeing summary: total outstanding, overdue count, a simple bar by bucket
/customers, /customers/:idBuyer customer list and their invoice history
/invoices, /invoices/:idFilterable list (all / due soon / overdue / paid); detail shows reminder log, payment log, QR preview
/invoices/importCSV upload wizard with row-level error display
/reconciliationUpload statement, review unmatched transactions, confirm suggested matches
/settingsReminder schedule/templates, WhatsApp number, users
/billingPlan, payment method, invoices from us

Key empty & error states

Responsive requirements

Primarily a desktop tool for an office finance workflow — optimise the dashboard, invoice table, and reconciliation screen for desktop first. Owner-level users will check the ageing dashboard from a phone occasionally, so that one screen needs a usable mobile layout (card list instead of a wide table); the CSV import and reconciliation screens can be desktop-only at MVP.

Visual style

Minimal, professional, numbers-forward — this is a finance tool a skeptical accountant needs to trust at a glance. Tabular figures, a single calm accent color, and status conveyed by a small colored indicator (current/due soon/overdue) rather than heavy color-blocking the whole row.

B.5

Development roadmap

Sized for one developer. The goal is a pilot customer live by week 6–8, not a finished platform.

PhaseObjectiveKey deliverablesDone when
0
Week 1
Foundations Auth, tenant (account) model, RLS policies, customer + invoice CRUD, empty dashboard A test account can log in and manually create a customer and an invoice, scoped correctly to itself
1
Weeks 2–3
Get invoices in and visible CSV import with row-level validation, ageing dashboard with real buckets A sample 50-row invoice CSV from a real distributor imports cleanly and the dashboard buckets it correctly
2
Weeks 3–4
Automate the chase Reminder scheduler, WhatsApp manual-tap link (or API if the Meta verification lands in time), email channel, DuitNow QR on every reminder (pending B.10) A scheduled reminder fires at the right age threshold and the QR code resolves to a real, scannable payment
3
Weeks 5–6
Close the loop Bank statement CSV upload, amount/date matching logic, manual-match UI for low-confidence cases A real bank statement from a pilot customer's bank matches at least 80% of its transactions automatically
4
Weeks 7–8
Launch-ready Billing/checkout (setup fee + subscription), settings screen, onboarding polish from phase-0 pilot feedback First 1–2 paying pilot customers are live and have been billed successfully
P1
Post-pilot
Scale what pilots proved Accounting-software export/import, live bank feed if a viable API exists, reminder template customisation Decided from real pilot usage data, not speculation

Commercial assumptions to validate before — or very early during — development

  1. DuitNow QR issuance. Confirm whether this product can generate its own DuitNow QR codes or must route payment collection through a licensed gateway partner (e.g. CHIP). This affects the architecture diagram's "QR issuer" box directly — resolve before Phase 2.
  2. Bank statement format variability. Get 3–5 real statement exports (Maybank, CIMB, Public Bank, Hong Leong at minimum) from prospective pilot customers before committing to a parsing approach in Phase 3 — this is the one task in the roadmap that can't be scoped from a spec alone.
  3. Willingness to pay RM249–499/mo for CSV-upload reconciliation (not live bank sync) — test this explicitly in the first sales conversations, don't assume it survives contact with a real buyer.
  4. Data-sensitivity objection. A finance manager may hesitate to upload their customer list and bank statements to an outside tool — have a clear, honest answer on data handling ready before the first pitch.
B.6

Parallel founder execution plan

StageTechnical founderCommercial founder
DiscoveryConfirm the DuitNow QR issuance question technicallyGet 3 distributor finance managers to confirm the pain and the RM249–499/mo price point in conversation, before a line of code is load-bearing on it
PlanningLock the CSV import schema against a real sample export, not an assumed oneLine up 2–3 pilot customers willing to hand over a real (anonymised, if needed) bank statement sample for Phase 3
DevelopmentBuild Phases 0–4 in sequence; flag immediately if bank-statement parsing is taking materially longer than scopedKeep the 2–3 pilot relationships warm; start the next 3 prospects in parallel so Phase 4 isn't launching into an empty pipeline
TestingFix issues surfaced by real pilot CSV/statement imports — these will not match the test fixtures exactlySit with the first pilot through their first import and first reminder send; collect what confused them, verbatim
LaunchDeploy, monitor reminder-delivery and bank-match failure ratesConvert the pilot(s) to paying, and start the next five-customer push through the same referral channels