# Settlement Dispatch Flow

## Overview

The settlement dispatch flow allows Mercury to fetch merchant transaction data from the database, build a standardized settlement file, and dispatch it to the settlement simulator (representing a partner bank/AANI system).

---

## Architecture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        SETTLEMENT DISPATCH FLOW                         │
└─────────────────────────────────────────────────────────────────────────┘

  You (curl)
      │
      │ POST /api/v1/settlements/dispatch
      │ { merchantTag, settlementDate, bankUserId }
      ▼
┌─────────────────────┐
│   upi_settlement    │  port 4016
│  (sender / Mercury) │
│                     │
│  1. Find merchant   │──── SELECT * FROM merchants WHERE merchant_code = ?
│  2. Fetch txns      │──── SELECT * FROM transactions WHERE merchant_id = ? AND date = ?
│  3. Compute amounts │
│  4. Build payload   │
│  5. SHA256 checksum │
│  6. POST to sim     │
└─────────┬───────────┘
          │
          │ POST /dispatch-transactions
          │ { full settlement JSON payload }
          ▼
┌─────────────────────┐
│ settlement_simulator│  port 4015
│ (receiver / bank)   │
│                     │
│  1. Auth check      │
│  2. Validate fields │
│  3. Count check     │
│  4. Verify checksum │
│  5. Return ACK      │
└─────────┬───────────┘
          │
          │ { status: ACCEPTED, checksumValid: true, ... }
          ▼
      upi_settlement
          │
          │ { status: DISPATCHED, simulatorResponse: {...} }
          ▼
      You (curl)
```

---

## Endpoints

### 1. Trigger Dispatch — `upi_settlement`

| Property | Value |
|----------|-------|
| Method | `POST` |
| URL | `http://demo.ctrmv.com:4016/api/v1/settlements/dispatch` |
| App | `apps/upi_settlement` |

**Request Headers:**
```
Authorization: Bearer jfx2ozaJQpszIuek2b5NHagH93Z61sNVk_lYTWyVAlk
Content-Type: application/json
```

**Request Body:**
```json
{
  "merchantTag": "AE_REST_001",
  "settlementDate": "2026-06-11",
  "bankUserId": "MERCURY_AANI123"
}
```

**Success Response (200):**
```json
{
  "status": "DISPATCHED",
  "merchantTag": "AE_REST_001",
  "settlementDate": "2026-06-11",
  "simulatorResponse": {
    "status": "ACCEPTED",
    "receivedTransactionCount": 3,
    "checksumValid": true,
    ...
  }
}
```

**curl:**
```bash
curl --location 'http://demo.ctrmv.com:4016/api/v1/settlements/dispatch' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer jfx2ozaJQpszIuek2b5NHagH93Z61sNVk_lYTWyVAlk' \
--data '{
  "merchantTag": "AE_REST_001",
  "settlementDate": "2026-06-11",
  "bankUserId": "MERCURY_AANI123"
}'
```

---

### 2. Receive Settlement File — `settlement_simulator`

| Property | Value |
|----------|-------|
| Method | `POST` |
| URL | `http://demo.ctrmv.com:4015/dispatch-transactions` |
| App | `apps/settlement_simulator` |

**Request Body (full settlement payload):**
```json
{
  "fileType": "QR_Payment_Settlement",
  "version": "1.1",
  "settlementDate": "2026-06-11",
  "merchantTag": "AE_REST_001",
  "bankUserId": "MERCURY_AANI123",
  "merchant_id": "AE_REST_001",
  "totalTransactionCount": 2,
  "grossSettlementAmount": { "value": "700", "currency": "AED" },
  "mdrCharges":            { "value": "14",  "currency": "AED" },
  "taxOnMdr":              { "value": "0.70", "currency": "AED" },
  "netSettlementAmount":   { "value": "685.30", "currency": "AED" },
  "mismatchDetected": false,
  "generatedTimestamp": "2026-06-11T23:59:59+04:00",
  "transactions": [
    {
      "transactionId": "aani_pay_001",
      "qrId": "AANI9876543210000000",
      "terminal_id": "90080001",
      "transactionAmount": { "value": "500", "currency": "AED" },
      "transactionStatus": "SUCCESS",
      "transactionTime": "2026-06-11T12:35:00+04:00",
      "mdrCharge":        { "value": "10",   "currency": "AED" },
      "taxOnMdr":         { "value": "0.50", "currency": "AED" },
      "netReceivedAmount":{ "value": "489.50", "currency": "AED" }
    }
  ],
  "footer": {
    "totalRowCount": 2,
    "checksum": "<SHA256_HEX>",
    "fileGeneratedBy": "MercuryUPI System",
    "endOfFile": true
  }
}
```

**Success Response (200):**
```json
{
  "status": "ACCEPTED",
  "merchantTag": "AE_REST_001",
  "settlementDate": "2026-06-11",
  "bankUserId": "MERCURY_AANI123",
  "receivedTransactionCount": 2,
  "reportedTransactionCount": 2,
  "checksumValid": true,
  "grossSettlementAmount": { "value": "700", "currency": "AED" },
  "netSettlementAmount": { "value": "685.30", "currency": "AED" },
  "receivedAt": "2026-06-15T11:34:28Z",
  "fileType": "QR_Payment_Settlement",
  "version": "1.1"
}
```

**curl (direct):**
```bash
curl --location 'http://demo.ctrmv.com:4015/dispatch-transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer jfx2ozaJQpszIuek2b5NHagH93Z61sNVk_lYTWyVAlk' \
--data '{ ... full payload above ... }'
```

---

### 3. Settlement Summary — `settlement_simulator`

| Property | Value |
|----------|-------|
| Method | `POST` |
| URL | `http://demo.ctrmv.com:4015/api/v1/upi/settlement-summary` |
| App | `apps/settlement_simulator` |

**Request Body:**
```json
{
  "type": "qr_payment_settlement_summary",
  "merchantTag": "AE_REST_001",
  "bankUserId": "Mercury_UAE_PARTNER_001",
  "settlementDate": "2026-06-11",
  "totalTransactionCount": "3",
  "grossSettlementAmount": { "value": "66.00", "currency": "AED" },
  "mdrCharges":            { "value": "0.33",  "currency": "AED" },
  "taxOnMdr":              { "value": "0.06",  "currency": "AED" },
  "netSettlementAmount":   { "value": "65.61", "currency": "AED" },
  "settlementTimestamp": "2026-06-11T21:29:59+04:00"
}
```

**Success Response — merchant found (200):**
```json
{
  "settlementStatus": "COMPLETED",
  "mismatchDetected": false,
  "transactionMismatchDetails": {
    "mismatchCount": 0,
    "discrepancyAmount": { "value": "0", "currency": "AED" },
    "resolutionFileRequired": false
  }
}
```

**Failure Response — merchant not found (404):**
```json
{
  "settlementStatus": "FAILED",
  "mismatchDetected": true,
  "transactionMismatchDetails": {
    "mismatchCount": -1,
    "discrepancyAmount": { "value": "0", "currency": "AED" },
    "resolutionFileRequired": false
  },
  "error": "Merchant not found"
}
```

---

## Amount Calculations

| Field | Formula |
|-------|---------|
| `grossSettlementAmount` | Sum of `foreign_amount` for all SUCCESS transactions |
| `mdrCharges` | `gross × 2%` |
| `taxOnMdr` | `MDR × 5%` |
| `netSettlementAmount` | `gross − MDR − tax` |

Per transaction:
- MDR and tax are only applied to `SUCCESS` transactions
- `FAILED` transactions have `mdrCharge = 0`, `taxOnMdr = 0`, `netReceivedAmount = 0`

---

## Checksum (SHA256)

The checksum is calculated by the dispatcher and verified by the simulator.

**What goes into the checksum:**
```
#FileType,QR_Payment_Settlement
#Version,1.1
#SettlementDate,<date>
#MerchantTag,<tag>
#BankUserID,<bankUserId>
#MID,<merchant_code>
#TotalTransactionCount,<count>
#GrossSettlementAmount,<value>,<currency>
#MDRCharges,<value>,<currency>
#TaxOnMDR,<value>,<currency>
#NetSettlementAmount,<value>,<currency>
#MismatchDetected,NO
<transactionId>,<qrId>,<terminal_id>,<amount>,<currency>,<status>,<time>,<mdr>,<tax>,<net>
... (one row per transaction)
#TotalRowCount,<count>
#FileGeneratedBy,MercuryUPI System
```

`#Checksum` and `#EndOfFile` lines are **excluded** from the hash input.

---

## Merchant Validation

Both endpoints validate merchants against the `merchants` table.

**Matching logic (underscores are stripped for fuzzy match):**

```sql
SELECT * FROM merchants
WHERE merchant_code = 'AE_REST_001'
   OR REPLACE(merchant_code, '_', '') = 'AEREST001'
LIMIT 1;
```

This means `"AE_REST_001"` and `"AEREST001"` both resolve to the same merchant.

**Currently seeded merchant:**

| Field | Value |
|-------|-------|
| `merchant_code` | `AE_REST_001` |
| `merchant_vpa` | `restaurant@mercury` |
| `brand_name` | Dubai Fine Dining |
| `corridor` | UAE |
| `currency` | AED |
| `status` | ACTIVE |

---

## File Layout

```
apps/
├── upi_settlement/                          # Sender (port 4016)
│   └── lib/upi_settlement/
│       ├── router.ex                        # Routes POST /api/v1/settlements/dispatch
│       ├── dispatcher.ex                    # Core: DB fetch, payload build, checksum, HTTP POST
│       └── controllers/
│           └── dispatch_controller.ex       # HTTP handler
│
└── settlement_simulator/                    # Receiver (port 4015)
    └── lib/settlement_simulator/
        ├── router.ex                        # Routes both endpoints
        └── controllers/
            ├── settlement_controller.ex     # POST /api/v1/upi/settlement-summary
            └── dispatch_transactions_controller.ex  # POST /dispatch-transactions
```

---

## Configuration

`config/dev.exs`:
```elixir
# settlement_simulator DB
config :settlement_simulator, SettlementSimulator.Repo,
  database: "upi_pps", hostname: "localhost", ...

# simulator URL used by upi_settlement dispatcher
config :upi_settlement, :simulator_dispatch_url,
  "http://localhost:4015/dispatch-transactions"
```

---

## Log Output (Example)

```
[info]  [UpiSettlement] POST /api/v1/settlements/dispatch → 200 (55ms)
[debug] QUERY OK source="merchants" db=2.3ms
[debug] QUERY OK source="transactions" db=2.0ms
[info]  [DispatchTxn] ── Incoming /dispatch-transactions request ──
[info]  [DispatchTxn] Step 1: JSON body parsed successfully
[info]  [DispatchTxn] Step 2: Bearer token present — auth passed
[info]  [DispatchTxn] Step 3: Payload fields validated — merchantTag=AE_REST_001, settlementDate=2026-06-11
[info]  [DispatchTxn] Step 4: Transaction count — reported=3, received=3
[info]  [DispatchTxn] Step 5: Verifying SHA256 checksum...
[info]  [DispatchTxn] Step 5: Checksum VALID ✓
[info]  [DispatchTxn] Step 6: Amounts — gross=66.00 AED, net=65.61 AED
[info]  [DispatchTxn] Step 7: Building acknowledgment response...
[info]  [DispatchTxn] Step 8: Sending 200 ACCEPTED response to caller
[info]  [SettlementSimulator] POST /dispatch-transactions → 200 (4ms)
```
