# Loan Repayment Flows — Frontend API Docs

Base URL: `https://borrowerapi.nextpayday.ng/api/v1`

All authenticated endpoints require:

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

---

## Overview

| Flow | Endpoint(s) | Auth | What it pays |
|------|-------------|------|--------------|
| **Pay one EMI** | `POST /loans/pay-installment-with-wallet` | Yes | Next due monthly installment |
| **Early settle (wallet)** | `POST /loans/settle-with-wallet` | Yes | Remaining principal + settlement fee |
| **Early settle (Paystack)** | `GET /public/loans/{reference}/settlement` + `POST /public/loans/settle` | No | Same as wallet early settle |

**Important:** Early settlement (wallet or Paystack) is **not** the sum of remaining EMIs. The user pays **remaining principal + settlement fee** and skips future interest.

---

## Shared: Get loan snapshot first

Use this on the loan detail / repayment screen to show schedule, next due amount, and `loan.id`.

```
GET /loans/{reference}
Authorization: Bearer {token}
```

**Example:** `GET /loans/LN-LIFVZBSIWR`

**Response (trimmed):**
```json
{
  "loan": {
    "id": 2,
    "reference": "LN-LIFVZBSIWR",
    "status": "disbursed",
    "amount": "33333.00",
    "emi": "5347.00",
    "duration_months": 12,
    "total_repayment": "64164.03",
    "repayment_schedules": [
      {
        "id": 2,
        "installment_number": 1,
        "amount": "5347.00",
        "due_date": "2026-07-01T00:00:00.000000Z",
        "status": "pending"
      }
    ]
  }
}
```

**FE usage:**
- `loan.id` → wallet endpoints
- `loan.reference` → Paystack settlement endpoints
- First `status: "pending"` schedule → show “next installment” amount and due date

---

## Shared: Check wallet balance

Required before wallet payments.

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

**Response:**
```json
{
  "balance": "33333.00",
  "transactions": { ... }
}
```

If balance is low, fund wallet first:

```
POST /wallet/top-up
GET /wallet/top-up/verify?reference={ref}
```

---

## Flow 1 — Pay one EMI with wallet

Pays the **next unpaid** installment only. Server picks the installment — do not send `installment_number`.

### Request

```
POST /loans/pay-installment-with-wallet
Authorization: Bearer {token}

{
  "loan_id": 2
}
```

### Success `200`

```json
{
  "message": "Installment #2 paid successfully from wallet.",
  "installment_number": 2,
  "amount_paid": 5347.00,
  "loan_closed": false
}
```

If this was the **last** installment:

```json
{
  "message": "Installment #12 paid successfully from wallet.",
  "installment_number": 12,
  "amount_paid": 5347.00,
  "loan_closed": true
}
```

### Errors

| Status | Body | When |
|--------|------|------|
| `401` | `{ "message": "Unauthenticated." }` | No/invalid token |
| `422` | `{ "message": "The loan id field is required." }` | Missing `loan_id` |
| `422` | `{ "message": "...", "errors": { "loan_id": [...] } }` | Invalid `loan_id` |
| `404` | `{ "message": "Loan not found or already fully paid." }` | Wrong loan, not owner, or loan closed |
| `404` | `{ "message": "No unpaid installment found for this loan." }` | All installments already paid |
| `422` | `{ "message": "Insufficient wallet balance...", "wallet_balance": 1000, "amount_required": 5347 }` | Not enough wallet funds |

### FE screen flow

1. `GET /loans/{reference}` → show next pending installment amount
2. `GET /wallet` → show balance vs `amount_required`
3. If balance OK → `POST /loans/pay-installment-with-wallet` with `{ "loan_id": loan.id }`
4. Refresh loan snapshot
5. If `loan_closed: true` → navigate to “loan completed” state

### Security notes

- User can only pay **their own** loans (server checks `user_id`)
- Cannot pick arbitrary installment — always the earliest unpaid one
- Cannot pay another user's loan even with a valid `loan_id`

---

## Flow 2 — Early settle with wallet

Pays off the **entire loan early** from wallet. Same amount logic as Paystack settlement.

### Step 1 — Get settlement quote (optional but recommended for UI)

```
GET /public/loans/{reference}/settlement?user_id={optional_user_id}
```

No auth required. Pass `user_id` from the logged-in user for an ownership check.

**Example:** `GET /public/loans/LN-LIFVZBSIWR/settlement?user_id=6`

**Response `200`:**
```json
{
  "loan_id": 2,
  "loan_reference": "LN-LIFVZBSIWR",
  "total_borrowed": 33333,
  "remaining_installments": 12,
  "current_full_balance": 64164.03,
  "remaining_principal": 33333,
  "interest_saved": 30831.03,
  "settlement_fee_percentage": 1,
  "settlement_fee": 333.33,
  "total_payable": 33666.33,
  "paystack_public_key": "pk_live_..."
}
```

**UI should show:**
- `total_payable` — amount to debit
- `interest_saved` — benefit of early payoff
- `settlement_fee` — admin fee
- `current_full_balance` — what they would pay if they kept all EMIs (for comparison)

**Quote errors:**

| Status | When |
|--------|------|
| `404` | Loan not found or already paid |
| `403` | `user_id` query does not match loan owner |

### Step 2 — Pay from wallet

```
POST /loans/settle-with-wallet
Authorization: Bearer {token}

{
  "loan_id": 2
}
```

Server recalculates `total_payable` — you do not send the amount.

### Success `200`

```json
{
  "message": "Loan settled successfully from wallet.",
  "loan_reference": "LN-LIFVZBSIWR",
  "amount_paid": 33666.33
}
```

### Errors

| Status | Body | When |
|--------|------|------|
| `401` | Unauthenticated | No token |
| `404` | `{ "message": "Loan not found or already settled." }` | Wrong loan / not owner / already paid |
| `422` | `{ "message": "Insufficient wallet balance...", "wallet_balance": 10000, "amount_required": 33666.33 }` | Low balance |

### FE screen flow

1. `GET /public/loans/{reference}/settlement?user_id={id}` → show breakdown
2. `GET /wallet` → confirm `balance >= total_payable`
3. If low → prompt top-up (`POST /wallet/top-up`)
4. Confirm dialog → `POST /loans/settle-with-wallet` with `{ "loan_id": loan.id }`
5. Refresh loan → `status` should be `paid`

### Security notes

- Auth required; loan must belong to logged-in user
- Amount is server-calculated (client cannot send a custom amount)

---

## Flow 3 — Early settle with Paystack

Same early-settlement math as wallet. User pays via Paystack checkout (card/transfer), not from wallet balance.

### Step 1 — Get settlement quote

Same as Flow 2:

```
GET /public/loans/{reference}/settlement?user_id={optional_user_id}
```

Use `total_payable` and `paystack_public_key` from the response.

### Step 2 — Initialize Paystack payment

```
POST /public/loans/settle
Content-Type: application/json

{
  "loan_reference": "LN-LIFVZBSIWR"
}
```

**No bearer token required.**

### Success `200`

```json
{
  "authorization_url": "https://checkout.paystack.com/...",
  "reference": "SETTLE-LN-LIFVZBSIWR-1718123456"
}
```

### FE screen flow

1. `GET /public/loans/{reference}/settlement?user_id={id}` → show quote
2. User taps “Pay with Paystack”
3. `POST /public/loans/settle` with `{ "loan_reference": loan.reference }`
4. Open `authorization_url` in WebView / in-app browser
5. On return from Paystack → poll `GET /loans/{reference}` until `status === "paid"`
6. Show success screen

### Errors

| Status | When |
|--------|------|
| `422` | Invalid `loan_reference` |
| `404` | Loan not found or already paid |
| `500` | `{ "message": "Failed to initialize payment with Paystack" }` |

### Paystack completion

Settlement is finalized by the **Paystack webhook** (`charge.success` with `metadata.type = "loan_settlement"`). The mobile app should **not** assume instant success when the WebView closes — poll loan status.

Webhook validates paid amount matches `total_payable` (±₦1 tolerance).

### Security notes

- Init endpoint is public (anyone with `loan_reference` can start checkout)
- Optional `user_id` on quote endpoint prevents showing quote to wrong user
- Loan only closes after verified Paystack payment amount matches quote

---

## Which button to show when

| User intent | Endpoint | Preconditions |
|-------------|----------|---------------|
| “Pay this month” | `POST /loans/pay-installment-with-wallet` | Active loan, pending installment, wallet ≥ EMI |
| “Settle loan early” (wallet) | `POST /loans/settle-with-wallet` | Active loan, wallet ≥ `total_payable` |
| “Settle loan early” (Paystack) | `POST /public/loans/settle` | Active loan, open Paystack URL |

---

## Amount cheat sheet

Example: ₦33,333 loan, 12 × ₦5,347 EMIs, all unpaid, 1% settlement fee.

| Action | Amount charged |
|--------|----------------|
| Pay 1 EMI (wallet) | ₦5,347 (`schedule.amount`) |
| Early settle (wallet or Paystack) | ₦33,666.33 (`total_payable` = principal + fee) |
| Pay all EMIs manually (not supported) | ₦64,164.03 (`current_full_balance`) |

Early settle saves ~₦30,831 interest (`interest_saved`).

---

## Error handling (FE)

```dart
// Pseudocode
if (response.statusCode == 401) redirectToLogin();
if (response.statusCode == 422 && body['amount_required'] != null) {
  showInsufficientWallet(body['wallet_balance'], body['amount_required']);
}
if (response.statusCode == 404) showLoanNotFoundOrClosed();
if (response.statusCode == 500) showRetryLater();
```

Always send `Accept: application/json` so errors return JSON, not HTML.

---

## Quick reference

```
# Loan snapshot
GET  /loans/{reference}

# Wallet
GET  /wallet
POST /wallet/top-up
GET  /wallet/top-up/verify?reference=...

# Pay one EMI
POST /loans/pay-installment-with-wallet
     Body: { "loan_id": number }

# Early settle — quote (public)
GET  /public/loans/{reference}/settlement?user_id={id}

# Early settle — wallet
POST /loans/settle-with-wallet
     Body: { "loan_id": number }

# Early settle — Paystack
POST /public/loans/settle
     Body: { "loan_reference": "LN-..." }
```
