# Loan Calculator & Booking — Mobile App API Docs

Base URL (production): `https://api.nextpaydayapp.com/api/v1`

Base URL (staging): `https://stagingapi.nextpaydayapp.com/api/v1`

All endpoints below require:

```
Authorization: Bearer {token}
Content-Type: application/json
Accept: application/json
```

---

## Overview

The mobile app should **not** calculate loan figures locally. Always call the API and display the response. The server uses flat monthly interest, employer-specific rates/fees, and tenure bucketing.

| Step | Endpoint | Purpose |
|------|----------|---------|
| 1 | `GET /dashboard` | Max amount hints, active loan check |
| 2 | `GET /loans/active-check` | Block booking if user already has a loan |
| 3 | `GET /loans/offer` | List employers (if picker needed) |
| 4 | `POST /loans/offer/details` | **Loan calculator** — preview breakdown |
| 5 | `POST /loans/requests` | **Book loan** after user confirms |

**Prerequisites before booking:**
- User completed verification (employer setup, eligibility, Mono mandate).
- Valid Mono mandate (not expired — 3-year validity). `mandate_expired: true` in calculator response means re-authorize first.
- No active unpaid loan (`GET /loans/active-check`).

---

## Screen flow (recommended UX)

```
[Loan Calculator Screen]
  ├── Amount selector
  ├── Tenure selector (1–12 months)
  ├── Loan type toggle (Set-Off | Capitalize)
  ├── [Calculate] button → POST /loans/offer/details
  ├── Breakdown card (from API response)
  └── [Book Loan] button → POST /loans/requests (disabled until preview loaded)

[Success] → wallet / loan snapshot
```

---

## UI inputs (what the user sets)

### 1. Amount selector

| Property | Value |
|----------|--------|
| Control | Slider and/or numeric input |
| Min | `1000` (₦1,000) — API minimum |
| Max | Use `loan_limits.max_principal` from calculator response, or `loan_offer.max_amount` from dashboard |
| Step | Suggest `1000` or `5000` |
| Format | Nigerian Naira, e.g. `₦100,000` |

**Max amount logic:**
- `GET /dashboard` → `loan_offer.max_amount` (for user's employer at max tenure).
- Each time tenure changes, call `POST /loans/offer/details` (or at least use `loan_limits.max_principal` from the last preview) because max scales with months: `monthly_ai × tenure`, capped by employer `max_amount_allowed`.

### 2. Tenure selector

| Property | Value |
|----------|--------|
| Control | Segmented control or picker |
| Options | `1` to `12` months (integers) |
| Max | `loan_offer.max_months` from dashboard or employer `max_month_allowed` (default 12) |

Show labels like `3 months`, `6 months`, `12 months`. Any value 1–12 is valid if employer allows it.

**Rate bucket (server-side — for reference only):**

| User selects tenure | Employer rate/fee column used |
|--------------------|------------------------------|
| 1–3 months | `rate_3`, `fees_3` |
| 4–6 months | `rate_6`, `fees_6` |
| 7–9 months | `rate_9`, `fees_9` |
| 10–12 months | `rate_12`, `fees_12` |

### 3. Loan type toggle

| UI label | API value `loan_type` | Meaning |
|----------|----------------------|---------|
| **Set-Off** | `setoff` | Insurance deducted from disbursal. User receives `disbursal_amount = amount - insurance_fee`. |
| **Capitalize** | `capitalize` | Full amount disbursed. Insurance added to total repayment. |

Both types use the same interest and management fee formulas; only disbursal and gross total differ.

### 4. Employer

| Source | Field |
|--------|--------|
| User's verified employment | `employer_id` from profile / employment step |
| Optional picker | `GET /loans/offer` → `employers[].id` |

Pass `employer_id` on every calculator and book request. Use the employer the user registered during verification unless product allows changing it.

---

## Buttons

| Button | When enabled | Action |
|--------|--------------|--------|
| **Calculate** / **Preview** | Amount ≥ 1000, tenure selected, employer known | `POST /loans/offer/details` |
| **Book Loan** / **Apply** | Preview loaded, `mandate_expired === false`, no active loan, user confirmed terms | `POST /loans/requests` |
| **Re-authorize mandate** | `mandate_expired === true` | Navigate to Mono mandate flow (`POST /loans/mandate/activate`) |

Debounce calculator calls (e.g. 300–500 ms) when amount/tenure/type changes if you auto-refresh the preview.

---

## API: Dashboard hints

```
GET /dashboard
Authorization: Bearer {token}
```

**Use on calculator screen load:**

```json
{
  "wallet_balance": 0,
  "loan_balance": 0,
  "loan_offer": {
    "monthly_amount": 50000,
    "max_amount": 600000,
    "max_months": 12
  },
  "active_loan": null
}
```

| Field | UI usage |
|-------|----------|
| `loan_offer.monthly_amount` | Helper text: “Up to ₦X per month” |
| `loan_offer.max_amount` | Initial slider max at longest tenure |
| `loan_offer.max_months` | Cap tenure picker |
| `active_loan` | If not null, redirect to active loan — do not allow new booking |

---

## API: Active loan guard

```
GET /loans/active-check
```

```json
{
  "has_active_loan": false,
  "loan": null
}
```

If `has_active_loan` is `true`, hide/disable **Book Loan** and show existing loan.

---

## API: Loan calculator (preview)

```
POST /loans/offer/details
```

### Request body

```json
{
  "amount": 100000,
  "duration_months": 6,
  "loan_type": "setoff",
  "employer_id": 1
}
```

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `amount` | number | Yes | Principal requested (min 1000) |
| `duration_months` | integer | Yes | 1–12 |
| `loan_type` | string | Yes | `setoff` or `capitalize` |
| `employer_id` | integer | Yes | From user employment |

### Success response (200)

```json
{
  "amount_requested": 100000,
  "gross_loan": 165000,
  "loan_charged": 5,
  "interest": 30000,
  "management_fee": 15000,
  "insurance_fee": 2500,
  "disbursal_amount": 97500,
  "loan_duration": 6,
  "monthly_repayment": 27500,
  "total_repayment": 165000,
  "commission_base": 97500,
  "type": "setoff",
  "method": "flat",
  "loan_limits": {
    "monthly_amount": 50000,
    "max_principal": 300000,
    "tenure_months": 6
  },
  "mandate_expired": false
}
```

### What to show in the UI

| API field | Label suggestion | Notes |
|-----------|------------------|-------|
| `amount_requested` | Loan amount | Principal user requested |
| `disbursal_amount` | **You receive** | Highlight — amount credited to wallet |
| `monthly_repayment` | **Monthly repayment** | EMI / direct debit amount |
| `loan_duration` | Tenure | e.g. “6 months” |
| `interest` | Total interest | Flat: principal × rate% × months |
| `management_fee` | Management fee | principal × mgt% × months |
| `insurance_fee` | Insurance | principal × insurance% (default 2.5%) |
| `total_repayment` | Total repayment | Sum user pays over life of loan |
| `loan_charged` | Interest rate | % per month from employer bucket |
| `loan_limits.max_principal` | Max eligible | Show near amount slider |
| `mandate_expired` | — | If `true`, block book + show mandate CTA |

**Optional footer lines:**
- Set-Off: “Insurance (₦X) is deducted from your disbursal.”
- Capitalize: “Insurance (₦X) is included in your total repayment.”

### Error responses (422)

| Message pattern | UI action |
|-----------------|-----------|
| Maximum loan tenor allowed by your employer is N months | Reduce tenure options |
| The maximum loan amount you are eligible for is … | Reduce amount to `max_principal` |

---

## Calculation reference (server logic)

Mobile should display API numbers only. For QA / tooltips:

**Insurance (one-time on principal):**
```
insurance_fee = amount × (insurance_percentage / 100)
```
`insurance_percentage` from system settings (default **2.5**).

**Interest (flat, per month):**
```
interest = amount × (monthly_rate / 100) × duration_months
```

**Management fee (% of principal, per month):**
```
management_fee = amount × (employer_fees_bucket / 100) × duration_months
```

**Set-Off (`loan_type: setoff`):**
```
disbursal_amount = amount - insurance_fee
total_repayment = amount + interest + management_fee
monthly_repayment = total_repayment / duration_months
```

**Capitalize (`loan_type: capitalize`):**
```
disbursal_amount = amount
total_repayment = amount + interest + management_fee + insurance_fee
monthly_repayment = total_repayment / duration_months
```

### Worked example

- Amount: ₦100,000  
- Tenure: 6 months  
- Employer `rate_6`: 5% / month  
- Employer `fees_6`: 5% / month  
- Insurance: 2.5%  
- Type: Set-Off  

```
interest         = 100,000 × 0.05 × 6 = 30,000
management_fee   = 100,000 × 0.05 × 6 = 30,000
insurance_fee    = 100,000 × 0.025    = 2,500
disbursal_amount = 100,000 - 2,500      = 97,500
total_repayment  = 100,000 + 30,000 + 30,000 = 160,000
monthly_repayment= 160,000 / 6          = 26,666.67
```

---

## API: Book loan

```
POST /loans/requests
```

### Request body

```json
{
  "amount": 100000,
  "duration_months": 6,
  "loan_type": "setoff",
  "employer_id": 1,
  "loan_mandate_activation_id": 12,
  "referral_code": "NPU-ABC123",
  "purpose": "School fees"
}
```

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `amount` | number | Yes | Same as preview |
| `duration_months` | integer | Yes | Same as preview |
| `loan_type` | string | Yes | `setoff` or `capitalize` |
| `employer_id` | integer | Yes | Same as preview |
| `loan_mandate_activation_id` | integer | No | Mono activation row ID if available |
| `referral_code` | string | No | Partner referral at booking time |
| `purpose` | string | No | Optional label |

Server **recalculates** everything on book — send the same inputs as the last successful preview.

### Success (200)

```json
{
  "message": "Loan approved and disbursed to wallet.",
  "loan": {
    "reference": "LN-ABCDEF1234",
    "amount": "100000.00",
    "emi": "26666.67",
    "duration_months": 6,
    "status": "disbursed",
    "disbursal_amount": "97500.00",
    "total_repayment": "160000.00",
    "repayment_schedules": [ ... ]
  }
}
```

| `status` | Meaning |
|----------|---------|
| `disbursed` | Auto-approved (amount ≤ auto approval limit), wallet credited |
| `pending` | Awaiting admin approval |

### Book errors (422)

| `error_code` | Message |
|--------------|---------|
| `MONO_MANDATE_EXPIRED` | Re-authorize Mono mandate |
| (none) | Tenor / max amount / validation messages |

---

## API: Employers list (optional)

```
GET /loans/offer
```

```json
{
  "employers": [
    { "id": 1, "name": "Federal Ministry of Education", "state": "FCT" }
  ]
}
```

Use only if the product allows employer selection on the calculator. Otherwise use `employer_id` from the user's employment profile.

---

## State management checklist

1. On screen open: `GET /dashboard` + `GET /loans/active-check`.
2. User changes amount, tenure, or loan type → `POST /loans/offer/details`.
3. Store last successful preview; enable **Book Loan** when preview matches current inputs.
4. On book: `POST /loans/requests` with same `amount`, `duration_months`, `loan_type`, `employer_id`.
5. On success: navigate to loan snapshot `GET /loans/{reference}` or wallet.

---

## Mobile UI wireframe (minimal)

```
┌─────────────────────────────────────┐
│  Loan Calculator                    │
├─────────────────────────────────────┤
│  Amount                             │
│  [━━━━━━●━━━━━━━━]  ₦100,000       │
│  Max eligible: ₦300,000             │
│                                     │
│  Tenure                             │
│  [ 3 ] [ 6 ] [ 9 ] [ 12 ] months    │
│                                     │
│  Repayment type                     │
│  (●) Set-Off    ( ) Capitalize      │
│                                     │
│  [ Calculate Preview ]              │
├─────────────────────────────────────┤
│  You receive          ₦97,500       │
│  Monthly repayment    ₦26,667       │
│  Total repayment      ₦160,000      │
│  ─────────────────────────────      │
│  Interest             ₦30,000       │
│  Management fee       ₦30,000       │
│  Insurance            ₦2,500        │
│  Rate                 5% / month    │
├─────────────────────────────────────┤
│  [ Book Loan ]                      │
└─────────────────────────────────────┘
```

---

## Related docs

- [Loan repayment flows](./loan-repayment-flows.md) — pay EMI, early settle  
- [Signup & auth](./signup-auth-flow.md) — verification before booking
