# Merchant Settlement Implementation Plan
# NPCI-Aligned UPI PSP Platform

## Settlement Roles (NPCI vs PSP)

| Settlement Layer | Owner | Description |
|---|---|---|
| NPCI Interbank (INR netting) | NPCI (external) | Nets positions between PSP and member banks via RBI RTGS. PSP is a passive receiver. |
| PSP → Merchant Disbursement | **This platform** | After PSP receives INR from NPCI, aggregate completed transactions and disburse to merchants. |

**Settlement Currency**: Always INR per NPCI spec — even for international corridors (SGD/USD/AED), the merchant settlement amount is always `inr_amount`, never `foreign_amount`.

**Settlement Trigger**: Only transactions with `status = "success"` AND `settlement_id IS NULL` are eligible.

---

## Phase 1 — UpiCore.Settlements Context (CRUD)
**File**: `apps/upi_core/lib/upi_core/settlements.ex`
**Status**: NOT STARTED

### What it does
The CRUD layer. Mirrors `UpiCore.Merchants` structure with real `Repo` queries.

### Functions to implement
| Function | Purpose |
|---|---|
| `create_settlement/1` | Insert settlement record via `Ecto.Multi` |
| `update_settlement/2` | Update status and fields atomically |
| `get_settlement/1` | Lookup by DB id |
| `get_settlement_by_reference/1` | Lookup by `reference_id` |
| `list_settlements/1` | Filtered, paginated `Repo.all()` |
| `aggregate_eligible_transactions/2` | Query `transactions` where `status="success"` AND `settlement_id IS NULL` grouped by merchant |

### Key constraint
`currency` must always be validated as `"INR"` — enforced in changeset, not just default.

---

## Phase 2 — UpiCore.Settlements.SettlementService (Business Logic)
**File**: `apps/upi_core/lib/upi_core/settlements/settlement_service.ex`
**Status**: NOT STARTED
**Depends on**: Phase 1

### What it does
Business logic layer. Modelled after `UpiInternationalService` using `Ecto.Multi` for atomicity.

### Functions to implement

#### `generate_merchant_settlement_batch(merchant, date)`
```
Multi.run: aggregate eligible transactions for merchant (via Phase 1)
Multi.insert: create Settlement record (type: "merchant", currency: "INR")
Multi.update_all: stamp settlement_id + settlement_status="settled" on all transactions in batch
Multi.run: append audit event
```

#### State transition functions
| Function | Transition |
|---|---|
| `approve_settlement/2` | `pending → approved` |
| `process_settlement/1` | `approved → bank_processing` |
| `mark_completed/2` | `bank_processing → completed` |
| `mark_failed/3` | any live state → `failed` (increments `retry_count`) |
| `raise_dispute/2` | `reconciliation_status → disputed` |

Each transition must **guard against invalid state** — reject if current status is not the expected predecessor.

#### `reconcile_settlement_window(from_date, to_date)`
- Query all settlements in the date window
- Compare `settlement_amount` against sum of linked transaction `inr_amount` values
- Flag mismatches as `reconciliation_status = "disputed"` with a reference ID
- Return reconciliation summary map

---

## Phase 3 — Fix Monitoring Context (Real DB Queries)
**File**: `apps/upi_core/lib/upi_core/monitoring.ex`
**Status**: NOT STARTED
**Depends on**: Phase 1

### What it does
Replace all hardcoded mock data blocks with real `UpiCore.Settlements.list_settlements/1` calls.
Pattern is identical to how `list_transactions/1` in the same file already works.

### Functions to replace
| Function | Current State | Target |
|---|---|---|
| `list_settlements_with_filters/1` | Returns 2 hardcoded structs | Real `Repo.all()` with filters |
| `get_settlement_stats/1` | Aggregates hardcoded data | Real `Repo.aggregate()` queries |
| `get_settlement_by_id/1` | Searches hardcoded list | `Repo.get()` |
| `list_active_settlement_partners/0` | Returns 3 hardcoded names | Query distinct partner_ids from settlements |

### Benefit
The LiveView (`UpiWeb.SettlementsLive`) already calls `Monitoring.*` functions correctly — once this phase is done, the UI automatically shows real data with zero LiveView changes.

---

## Phase 4 — SettlementScheduler GenServer
**File**: `apps/upi_core/lib/upi_core/settlements/settlement_scheduler.ex`
**Status**: NOT STARTED
**Depends on**: Phase 2

### What it does
Periodic background job to auto-generate settlement batches.
Modelled after `UpiCore.Transactions.Watchdog`.

### Behavior
```
Every tick:
  Merchants.list_active_merchants()
  |> Enum.filter(&settlement_due?/1)
  |> Enum.each(&SettlementService.generate_merchant_settlement_batch(&1, Date.utc_today()))
```

### `settlement_due?/1` logic
| `settlement_frequency` | When to run |
|---|---|
| `"T+0"` | Every tick (same-day settlement) |
| `"T+1"` | Only if today's date > transaction date |
| `"WEEKLY"` | Only on configured day of week (default: Monday) |

### Registration
Add to `UpiCore.Application` supervision tree alongside existing `Watchdog`:
```elixir
UpiCore.Settlements.SettlementScheduler
```

---

## Phase 5 — Wire Reconciliation Controller Endpoint
**File**: `apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/upi_controller.ex`
**Status**: NOT STARTED
**Depends on**: Phase 2

### What it does
Extends the existing `process_reconciliation/1` private function (line ~2255) to call `SettlementService.reconcile_settlement_window/2` and include settlement reference IDs + dispute flags in the XML response.

### Current flow
```
reconciliation/2 → process_reconciliation/1
  → extract_reconciliation_data/1 (parses from_date / to_date from XML)
  → UpiInternationalService.get_transactions_for_reconciliation/2
  → build_reconciliation_response/2
```

### Target flow (addition only)
```
build_reconciliation_response/2 also calls:
  → SettlementService.reconcile_settlement_window(from_date, to_date)
  → includes settlement_reference_ids[] and disputed_count in response XML
```

---

## Phase 6 — ODR Dispute Resolution Path
**File (service)**: `apps/upi_core/lib/upi_core/settlements/settlement_service.ex` (addition to Phase 2)
**File (LiveView)**: `apps/upi_web/lib/upi_web/live/settlements_live/settlements_live.ex`
**Status**: NOT STARTED
**Depends on**: Phase 2, Phase 3

### NPCI ODR Requirements mapped
| NPCI Requirement | Implementation |
|---|---|
| Transaction status check | Already exists via `ReqChkTxn` + `Watchdog` |
| Complaint management | `raise_dispute/2` in SettlementService |
| Auto status update of pending transactions | SettlementScheduler tick handles this |
| Seamless issue resolution | LiveView disputed filter + action button |

### LiveView additions
- Add `"disputed"` to filter options in `status_options/0`
- Add `handle_event("raise_dispute", ...)` that calls `SettlementService.raise_dispute/2`
- Broadcast `{:settlement_updated, settlement}` via PubSub after dispute raised

---

## Dependency Graph

```
Phase 1 (Settlements CRUD)
    └── Phase 2 (SettlementService)
            ├── Phase 3 (Fix Monitoring)  ──→  LiveView works
            ├── Phase 4 (Scheduler GenServer)
            ├── Phase 5 (Reconciliation endpoint)
            └── Phase 6 (ODR dispute path)
```

## File Checklist

| Phase | File | Action |
|---|---|---|
| 1 | `apps/upi_core/lib/upi_core/settlements.ex` | CREATE |
| 2 | `apps/upi_core/lib/upi_core/settlements/settlement_service.ex` | CREATE |
| 3 | `apps/upi_core/lib/upi_core/monitoring.ex` | EDIT (replace mock blocks) |
| 4 | `apps/upi_core/lib/upi_core/settlements/settlement_scheduler.ex` | CREATE |
| 4 | `apps/upi_core/lib/upi_core/application.ex` | EDIT (add to supervision tree) |
| 5 | `apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/upi_controller.ex` | EDIT (extend build_reconciliation_response) |
| 6 | `apps/upi_web/lib/upi_web/live/settlements_live/settlements_live.ex` | EDIT (add dispute events) |
