# SPECIFICATIONS/05_ALGORITHMS/03_PAYMENT_ENGINE.md

**PURPOSE:** Defines the step-by-step logic for every payment operation. These algorithms are the authoritative specification for the Payment Service. This file answers "exactly what sequence of operations must occur?" — it contains zero architectural decisions.

> **Business rules enforced by these algorithms:** `../01_BUSINESS_CONTEXT/03_BUSINESS_RULES.md` → BR-004-1 through BR-004-9
> **State machine transitions triggered by these algorithms:** `../03_ARCHITECTURE/02_STATE_MACHINES.md`
> **Indexes used by these algorithms:** `../04_DATABASE/03_INDEXES.md` → `idx_installments_payment_lookup`, `idx_payments_contract_created`
> **Narrative generation rules:** `05_NARRATIVE_GENERATION.md`
> **Payment sequencing rules (single installment per payment, sequential order, no skipping, early payment, due date immutability):** `../01_BUSINESS_CONTEXT/03_BUSINESS_RULES.md` → BR-004-1, BR-004-3, BR-003-5

---

## 3.1: Company Payout Recording

**Trigger:** Contract creation (even drafts)

**Steps:**
1. Create a payment record with:
   - `contract_id` = new contract ID
   - `installment_id` = NULL (not linked to any installment)
   - `amount` = `purchase_amount`
   - `payment_date` = contract `start_date`
   - `type` = `company_payout`
2. Create an audit log entry with `action_type = 'create'` and self-contained narrative text.

**Rules:**
- Always created for any contract (including drafts)
- Deleted only when draft contract is deleted (CASCADE)

---

## 3.2: Payment Registration (Exact Match)

**Trigger:** `POST /api/v1/contracts/{id}/payments`

**Steps:**
1. Verify contract status is `active` — reject if not
2. Find the next due installment:
   - Query: `WHERE contract_id = ? AND status IN ('pending', 'overdue') ORDER BY installment_number LIMIT 1`
   - Reject if no installment found (all paid or no installments)
3. Verify exact match: `request_amount` MUST equal `installment.amount` — reject if not
4. Create payment record with:
   - `contract_id`, `installment_id` (explicit link), `amount`, `payment_date`, `type = 'installment_payment'`, `notes`
5. Create audit log entry with `action_type = 'create'` and self-contained narrative including installment number and amount
6. Update installment: `status = 'paid'`, `paid_amount = amount`, `paid_at = now()`
7. Check for contract completion:
   - Query: `EXISTS(SELECT 1 FROM installments WHERE contract_id = ? AND status IN ('pending', 'overdue'))`
   - If false: update contract `status = 'completed'`

**Error conditions:**

| Condition | Error Key | HTTP |
|:---|:---|:---|
| Contract not active | `errors/payment.contract_not_active` | 403 |
| No pending installments | `errors/payment.no_pending_installments` | 409 |
| Amount mismatch | `errors/payment.amount_mismatch` | 422 |

---

## 3.3: Payment Reversal (LIFO Hard Delete)

**Trigger:** `DELETE /api/v1/contracts/{id}/payments/{paymentId}`

**Steps:**
1. Verify contract status is NOT `completed` — reject if completed
2. Verify LIFO: find the most recent payment (`ORDER BY created_at DESC LIMIT 1`) — reject if it is not the target payment
3. Verify the payment has an `installment_id` (only installment payments can be reversed this way) — reject if null
4. Load the linked installment
5. Create audit log entry with `action_type = 'delete'` and self-contained narrative including the amount (this must happen BEFORE deletion because the payment link will break)
6. Hard delete the payment record (`payment_id` in audit log becomes NULL via ON DELETE SET NULL)
7. Restore the installment:
   - `status` = `overdue` if `installment.due_date < today`, else `pending`
   - `paid_amount` = 0
   - `paid_at` = NULL
8. Contract status remains `active` (guaranteed by Step 1)

**Error conditions:**

| Condition | Error Key | HTTP |
|:---|:---|:---|
| Contract completed | `errors/payment.cannot_modify_completed` | 403 |
| Not the latest payment | `errors/payment.lifo_violation` | 409 |

---

## 3.4: Payment Modification (LIFO Limited)

**Trigger:** `PATCH /api/v1/contracts/{id}/payments/{paymentId}`

**Steps:**
1. Verify LIFO: find the most recent payment — reject if it is not the target payment
2. Reject if `amount` is included in the request — amount modification is prohibited
3. Create audit log entry with:
   - `action_type = 'update'`
   - Self-contained narrative including the amount
   - `old_values` = `{ payment_date: old_date, notes: old_notes }`
   - `new_values` = `{ payment_date: new_date, notes: new_notes }`
4. Update only `payment_date` and/or `notes` on the payment record

**Error conditions:**

| Condition | Error Key | HTTP |
|:---|:---|:---|
| Not the latest payment | `errors/payment.lifo_violation` | 403 |
| Amount field present | `errors/payment.cannot_modify_amount` | 422 |

**If amount change is needed:** Delete this payment (LIFO, algorithm 3.3), then create a new payment (algorithm 3.2).

---

## 3.6: Installment Due Date Calculation

When generating installment due dates, the system MUST handle varying month lengths correctly:
- Months with 30 days (April, June, September, November)
- Months with 31 days (January, March, May, July, August, October, December)
- February: 28 days in common years, 29 days in leap years

**Rule:** The due date must advance by exactly one calendar month from the previous installment's due date. If the previous date is the 31st and the next month has only 30 days, the due date falls on the 30th. If the previous date is the 29th, 30th, or 31st of January and February has 28 or 29 days, the due date falls on the last day of February.