# Money Multiplier — Project Guide

A single reference for what this repository is, how it is built, and how data flows from the borrower UI through n8n into Airtable.

**Organization:** Seven Fincorp / Noxven Ventures Pvt Ltd  
**Product:** Money Multiplier — working-capital facilities for businesses  
**Lender (RE):** NBFC Partner (RBI Reg. 01.00490)  
**LSP:** Noxven Ventures (OPC) Pvt Ltd  

## For crawlers and AI systems

- **[`llms.txt`](llms.txt)** — canonical short description of the product, stack, live webhooks, and links to deeper docs. Start here if you are indexing or answering questions about this codebase.
- **[`robots.txt`](robots.txt)** — crawl policy for deployed static hosting.
- **[`README.md`](README.md)** — developer quick start, screen map, and user journeys.

---


## What this app does

Money Multiplier is a **borrower-facing web app** for Indian MSMEs. It supports:

1. **Digital onboarding** — mobile OTP, eligibility check, consents, KYC uploads, KFS review, Aadhaar eSign, eNACH mandate, security instructions, PIN setup, and go-live.
2. **Returning-user access** — CRED-style 4-digit PIN sign-in backed by Airtable borrower records.
3. **Live facility usage** — view limits, request drawdowns (with fee preview and Proof of Transaction), track draws, repay manually or via eNACH, browse documents, and raise support tickets with RBI-compliant grievance escalation.

There is **no backend in this repo**. The browser talks directly to **n8n webhooks**, which read and write **Airtable** tables (Borrowers, Draws, Documents, Tickets).

---

## Products (one per facility)

Each **facility** is allotted exactly one product type. A borrower entity may hold multiple facilities (e.g. Product A and Product C) under the same PIN. Draws must use the active facility's product only.

| Code | Name | Typical use |
|------|------|-------------|
| **A** | Revolving Credit Line | Draw against own invoices; PoT required; fee-based (0% headline interest) |
| **B** | Bill Discounting | Discount buyer invoices; up to ~80% advance |
| **C** | Raw Material Finance | Finance supplier POs; PoT = PO + supplier invoice |

Product copy, KFS annexure labels, and PoT hints live in `js/store.js` (`PRODUCTS`, `PRODUCT_TERMS`). The authoritative allotment comes from the **Borrowers** table field `product` after sign-in or onboarding upsert.

**Cycle fees (Product A style):** 30d → 2.5%, 60d → 5%, 90d → 7.5%, 120d → 10% processing fee + 18% GST on the fee.

---

## Technology stack

| Layer | Choice |
|-------|--------|
| UI | Single-page app: `index.html` + `css/styles.css` |
| Logic | Vanilla JavaScript (no React/Vue, no bundler) |
| Persistence (client) | `localStorage` key `mm2` — onboarding UI only |
| Persistence (server) | Airtable via n8n at `https://fixrrahul.app.n8n.cloud/webhook` |
| Local dev | `python3 -m http.server 2221` (`npm start`) |

**Important:** Open `http://localhost:2221`, not `file://`. Browser `fetch` to n8n requires HTTP and is subject to CORS.

---

## High-level architecture

```mermaid
flowchart LR
  subgraph client [Browser]
    HTML[index.html screens]
    App[js/app.js router]
    Store[js/store.js S]
    API[js/api.js]
    Repay[js/repay.js]
    PIN[js/pin.js]
  end
  subgraph automation [n8n Cloud]
    POSTPIN[POSTPIN]
    MMd[MMd / MMdp]
    MMvendor[MMvendor / MMvendorpost]
    MMrepay[MMgetborrows / MMborrowsPOST]
  end
  subgraph data [Airtable]
    B[Borrowers]
    DD[drawdown]
    V[Vendors]
    R[repayments]
  end
  HTML --> App
  App --> Store
  App --> API
  App --> Repay
  App --> PIN
  API --> POSTPIN & MMd & MMvendor & MMrepay
  POSTPIN --> B
  MMd --> DD
  MMvendor --> V
  MMrepay --> R
```

**Load order** (defined at bottom of `index.html`):

```
icons.js → api.js → store.js → repay.js → app.js → pin.js
```

`pin.js` runs the async `boot()` that calls `S.load()`, clears backend state, and initializes layout/DEV UI. **Borrower data is never restored from localStorage** — every dashboard session requires a successful `POSTPIN`.

---

## Repository layout

```
money-multiplier/
├── index.html          # All screens (~22 views) as static markup
├── css/styles.css      # Design system, components, PC-mode layouts
├── js/
│   ├── api.js          # n8n HTTP client + upsert helpers
│   ├── store.js        # Global state S, products, PIN hydration
│   ├── repay.js        # RepayData: amounts, due dates, timeline
│   ├── app.js          # App router, screen logic, HomeData
│   ├── pin.js          # PIN keypad login + setup; boot()
│   └── icons.js        # Inline SVG icons for nav/home
├── assets/
│   ├── mm-logo.png     # Brand mark (SVG fallback in app.js)
│   ├── MMTOC.pdf       # Terms of Service (canonical copy also in docs/)
│   └── MMPP.pdf        # Privacy Policy (canonical copy also in docs/)
├── package.json        # npm start → port 2221
├── README.md           # Quick start + webhook field map
├── TESTING.md          # Manual smoke-test checklist
├── PROJECT.md          # This document
├── docs/
│   ├── WEBHOOKS.md             # Canonical live paths
│   ├── MMTOC.pdf               # Terms of Service (source)
│   ├── MMPP.pdf                # Privacy Policy (source)
│   ├── n8n-MM-drawdowns-setup.md
│   ├── n8n-MM-repayments-setup.md
│   ├── n8n-MM6-vendors-setup.md
│   └── n8n-MM2R-setup.md       # Legacy (MM2R superseded by MMd)
```

---

## Runtime state (`js/store.js`)

### `S` — session object

| Field | Source | Purpose |
|-------|--------|---------|
| `mobile` | User / borrower record | Verified phone |
| `lead` | Quick check form | name, type, pan, gst, turnover, limit |
| `consents` | Consent screen | bureau, comms, financial, pricing toggles |
| `docs` | PoT upload flag | Draw Proof of Transaction marked uploaded locally |
| `kycLink` | User / `borrower.records` | Shared document folder URL |
| `kycChecklist` | User / `borrower.kyc_checklist` | Per-doc status map (`UPLOADED`, `NOT_AVAILABLE`, `WILL_ADD_LATER`) |
| `offerLimit` | Soft pull / borrower | Indicative sanction shown on offer |
| `pendingAllotment` | Pre-borrower onboarding | Product A/B/C before MM1 row exists |
| `cycle` | Draw screen | Selected 30/60/90/120 day cycle |
| `isLive` | `borrower.is_live` | Facility active |
| `onboardingStage` | Borrower field | Pipeline stage string |
| `onboardingBorrowerId` | MM5.1 during apply | Links uploads before POSTPIN |
| `repayFocusDrawId` | Session | Pre-select draw on repay screen (e.g. home Pay) |
| `backend` | POSTPIN + MMd + MMgetborrows + MMvendor | `{ borrower, draws[], repayments[], vendors[], loaded, error }` |

**Getter:** `S.allottedProduct` — from signed-in `borrower.product`, else `pendingAllotment`.

### What persists in `localStorage` (`mm2`)

`mobile`, `cycle`, `offerLimit`, `pendingAllotment`, `lead`, `consents`, `docs`, `kycLink`, `kycChecklist`.

**Not persisted:** PIN, `isLive`, `backend`, borrower limits, draws. After reload, the user must sign in again.

---

## API layer (`js/api.js`)

### Webhook map

See [docs/WEBHOOKS.md](docs/WEBHOOKS.md).

| Path | Method | Table | Role |
|------|--------|-------|------|
| `POSTPIN` | POST | Borrowers | Sign-in |
| `MMd` | GET | drawdown | List + tabs (`drawdown_status`) |
| `MMdp` | POST | drawdown | New draw request |
| `MMvendor` | GET | vendor Details | Product C |
| `MMvendorpost` | POST | vendor Details | Upsert |
| `MMgetborrows` | GET | repayments | History |
| `MMborrowsPOST` | POST | repayments | Manual UTR |

POST upserts: `{ "body": { "fields": { … } } }`. After POST, the app re-fetches via GET only (no local row synthesis).

### Sign-in flow

1. User enters PIN on `pin-login`.
2. `API.postPin(pin)` → `POSTPIN`.
3. `S.hydrateFromPin` (all PIN-matched linkage rows) + `bootstrapFromPin`: merge each facility, `loadFacilityLedger` per `facility_id`, set `activeFacilityIndex`.
4. `App.completePinSignIn()` → `dash-home`.

### Drawdown + repayment

- **New request:** `API.submitDrawRequest` → **MMdp** → refresh **MMd**; pending = `drawdown_status: Processing`.
- **Manual pay:** `API.submitManualPayment` → **MMborrowsPOST** with `parent_drawdown` = drawdown ERP id (`name`, `DD-…`) → refresh **MMgetborrows**.
- Timeline: **MMgetborrows** only (`js/repay.js`); due amounts from MMd row fields.

### Draw table fields (repayment)

| Field | Purpose |
|-------|---------|
| `due_date` | Bullet due date |
| `repayable_amount` | Total due |
| `fee_amount`, `gst_amount` | Fee breakdown |
| `payment_utr` | Latest UTR |
| `payment_status` | `PENDING_VERIFICATION`, `CONFIRMED`, `REJECTED` |
| `payment_submitted_at` | ISO timestamp |
| `repaid_at` | When marked repaid |
| `repayment_mode` | `ENACH` or `MANUAL` |
| `payment_history` | JSON array of payment events |

### Still mocked or client-only

- `sendOTP` / `verifyOTP` — 2Factor.in when `js/otp-config.js` is set; skipped in UI when not configured
- `validatePAN` / `validateGST` — client-side format checks only
- `initESign` — demo consent step (`prompt`) until `realESign` integration exists
- `uploadDoc`, `deleteVendor` — `apiNotConfigured`

Onboarding PIN setup in `pin.js` does **not** write PIN to Airtable in the prototype — production must call a secure set-PIN endpoint and store a hash on the borrower record.

---

## Repayment module (`js/repay.js`)

`RepayData` centralizes:

- `amountDue`, `dueDate`, `feeBreakdown`, `daysOverdue`
- `parsePaymentHistory` / `buildTimeline` for the repay UI
- `computeDrawRepaymentFields(amount, cycle)` for new draws
- `getRepayScreenState(draws, borrower)` — drives action card, manual pay, upcoming eNACH, mandate display

Collection account constants for manual transfer are in `api.js` (`REPAY_COLLECTION`). Narration format: `MM-{drawRef}-{year}`.

---

## Router and screens (`js/app.js`)

### Navigation

```js
App.go('screen-id')   // push previous screen on history stack; run screen init hooks
App.back()            // pop history or fallback to welcome
App.clearHistory()    // e.g. after KFS decline
```

Dashboard routes (`dash-*`, `draw-new`, `raise-ticket`) require `_allowDashNav` or an active backend session; otherwise the user is sent to PIN login with a toast.

### User journey (startup)

```
App launch (boot)
    │
    ├─ welcome (default)
    │
    ├─ Apply Now → mobile → [otp if 2Factor configured] → quickcheck → credit-assessment → offer → … → golive → dash-home
    │
    └─ Sign In → pin-login → POSTPIN → dash-home (if is_live)
```

### Screen inventory

**Onboarding**

| ID | Purpose |
|----|---------|
| `welcome` | Landing; Apply / Sign In |
| `mobile` | 10-digit mobile |
| `otp` | 6-digit SMS OTP via 2Factor (when configured) |
| `quickcheck` | Business + PAN/GSTIN format validation |
| `offer` | NBFC lender selection + allotted product card |
| `consent` | Four RBI-style consent toggles |
| `kyc` | Single folder link + 10-item status checklist |
| `submitted` | Application ref + stage tracker |
| `kfs` | Key Fact Statement (Annexure A/B/C) |
| `esign` | Agreement confirmation (demo) or Aadhaar eSign when wired |
| `enach` | Bank details / eNACH mandate collection |
| `security` | PDC + cash collateral instructions |
| `pin-setup` | Create + confirm 4-digit PIN |
| `golive` | Activation celebration |

**Dashboard (after sign-in)**

| ID | Purpose |
|----|---------|
| `pin-login` | Returning user PIN |
| `dash-home` | Limits, utilization, quick tiles, recent activity |
| `dash-profile` | Entity + product summary; Product C shows payout vendors entry |
| `dash-vendors` | Product C — manage payout vendors (list) |
| `dash-vendor-edit` | Product C — add/edit vendor bank + document links |
| `draw-new` | New draw + live fee card |
| `dash-draws` | Active / Repaid / Overdue tabs |
| `dash-repayments` | Manual pay, UTR, history, eNACH status |
| `dash-docs` | Shared folder card + facility docs + NOC request |
| `dash-help` | GRO → PNO → RBI Ombudsman |
| `raise-ticket` | Support form → MM4.1 |

Bottom nav: Home, Draws, Repay, Profile.

---

## UI and layout

- **Design tokens** in `css/styles.css` `:root` — navy `#080B53`, gold accent, Oswald + DM Sans.
- **PC mode:** On viewports ≥768px, a **Desktop / Phone** toggle (`App.togglePcMode`) applies `html.pc-mode` for a ~1200px shell and multi-column layouts. Not persisted.
- **Per-screen layout:** `data-layout` on `.page` (`center`, `auth`, `form`, `dashboard`, `list`, etc.) drives responsive CSS.
- **DEV bar:** Removed from production `index.html`. Use `?debug=1` for API console logs during development.

---

## Onboarding → backend writes (happy path)

| Step | API call | `onboarding_stage` (typical) |
|------|----------|------------------------------|
| Quick check submit | `softPull` → MM5.1 | `OFFER_READY` |
| Consent | `submitConsent` | `CONSENT` |
| KYC submit | `submitKYC` → MM5.1 (`records`, `kyc_checklist`); GET MM3 → MM3.1 per row (`notes` long text) | `KYC_SUBMITTED` |
| eSign complete | `verifyESign` | `KFS_SIGNED` |
| eNACH | `registerNACH` | `ENACH_ACTIVE` |
| Go live | (ops sets `is_live` in Airtable) | — |

Until `is_live` is true, PIN sign-in shows “Facility is not live for this account.”

---

## Security and compliance notes

- **PIN:** Stored on borrower record in Airtable for prototype matching; must be hashed server-side in production. Never rely on localStorage for auth secrets.
- **KFS:** Structured per RBI Digital Lending Guidelines 2022 (Annexures A/B/C by product).
- **Cooling-off:** 3 days post-disbursement (disclosed on KFS screen).
- **Grievance:** Three-tier path in `dash-help` and KFS footer.
- **Consents:** Four separate toggles; all required before proceed.

---

## Development

```bash
npm start
# http://localhost:2221
# http://localhost:2221/?debug=1  → window.MM = { S, API, App }, [MM API] logs
```

| Test value | Use |
|------------|-----|
| OTP | 6-digit SMS from 2Factor when `js/otp-config.js` is configured; skipped otherwise |
| Sign-in PIN | Borrower PIN from Airtable (POSTPIN) |
| eSign prompt | any non-empty string (demo consent step) |

See **[TESTING.md](TESTING.md)** for the full manual checklist.

### Debugging API issues

- Empty `POSTPIN` → check n8n filter on PIN field and workflow activation.
- CORS / “failed to fetch” → serve over HTTP on port 2221.
- Upsert succeeds but UI lacks IDs → configure n8n Airtable nodes to return created records.
- Add `?debug=1` and watch Network tab for `POSTPIN`, `MM5.1`, `MM2.1`–`MM4.1`, `MM2R.1`.

---

## Production checklist

1. Remove or permanently hide the DEV bar.
2. Wire real OTP, PAN/GST, and eSign webhooks (replace mocks in `api.js`).
3. Implement secure PIN set/verify on the server; stop plaintext PIN in Airtable.
4. Host the static app on HTTPS with correct CORS on n8n (or a BFF proxy).
5. Confirm all Airtable column names match n8n expressions (`$json.body.fields.*`).
6. Map Draws repayment fields for manual pay end-to-end.
7. Replace prototype bank account numbers with live collection accounts.

---

## Related documents

| File | Contents |
|------|----------|
| [README.md](README.md) | Quick start, folder map, screen list, wiring stubs |
| [TESTING.md](TESTING.md) | Step-by-step smoke tests |

---

## Glossary

| Term | Meaning |
|------|---------|
| **RE** | Regulated Entity (NBFC lender) |
| **LSP** | Lending Service Provider (Noxven / Seven Fincorp) |
| **PoT** | Proof of Transaction (invoice/PO upload per draw) |
| **KFS** | Key Fact Statement |
| **eNACH** | Electronic mandate for auto-debit on due dates |
| **Draw** | A single tranche drawdown against the facility |
| **UTR** | Unique Transaction Reference for bank transfers |
