# Pishon Educational Management — Finance, Payments, Settlement, Subscription & Licensing
## Architecture & Conflict Report (§0.3)

Status: **Baseline audit complete — validated against live code at 2026-09-11.**
Baseline test suite: **314 passing (1105 assertions)** — must stay green after every phase.

This report maps the requested architecture (§1–§199 of the master brief) onto the EXISTING
codebase. Rule zero from the brief: **do not destroy / rebuild / duplicate existing
functionality.** Every planned construct below is either *extended* or *new*, never a copy.

---

## 1. What exists today (audit inventory — REUSE, do not rebuild)

### 1.1 Tenant isolation (mature, proven — reuse everywhere)
- `HasSchoolId` trait on every school-scoped model + global scope.
- `App\Services\Tenant\TenantContext` (current schoolId by session/route) + `TenantResolver`.
- Migrations enforce `school_id` FK `constrained()->cascadeOnDelete()` on every table.
- Covered by `FinanceSystemTest` 2-school isolation tests.

### 1.2 School finance system (existing, working)
Models: `FeeCategory`, `FeeStructure`, `Invoice`, `InvoiceItem`, `Payment`, `PaymentTransaction`,
`PaymentReminder`, `Refund`, `StudentCredit`, `FinanceAdjustment`, `Expense`, `ExpenseCategory`,
`RecurringExpense`, `BankAccount`, `OtherIncome`, `IncomeCategory`.

Services:
- `Finance\InvoiceService` — invoice lifecycle / balances.
- `Finance\PaymentService` — `record()` / `recordForStaff()`; returns `{payment, credit, change}`;
  handles overpayment→credit, reversal semantics. **Webhook confirmation must be routed through this.**
- `Finance\OnlinePaymentService` — **STUB**: only checks `config('services.paystack.secret')`-style
  existence and creates a `PaymentTransaction` with unique `TXN-…` reference. No gateway call.
- `Finance\ReportService`, `Finance\FinanceSettings`, `Finance\FinanceAudit`, `Finance\InvoiceNumberService`.

Controllers (school UI): `FinanceController`, `FinanceDashboardController`, `FinanceReportController`,
`FinanceSettingsController`, `InvoiceController`, `PaymentController`, `ExpenseController`,
`IncomeController`, `BankAccountController`.

Portal: `Portal\PortalPaymentController` — `fees`, `claimTransfer`, `payOnline` (stub → gateway),
`receipt`. Guarded by `portal.user` + `school.access` + `gate('fees')`.

Scheduled (`routes/console.php`): `finance:mark-overdue`, `finance:generate-recurring`,
`finance:send-reminders`.

`payment_transactions` already carries: `reference`, `provider`, `provider_reference`,
`amount`, `currency`, `status`, `channel`, `meta`, `paid_at`, `payment_id`. **Reuse; do NOT
create a parallel `provider_transactions` table.**

### 1.3 Platform-side (exists, manual-only — EXTEND for billing/licensing)
- `School` fields: `status` (SchoolStatus), `plan_id`, `trial_ends_at`, `settings`.
- `Plan` (`price`, `billing_period`, `features` array, `max_students/teachers/classes`, `allows()`).
- `Subscription` / `SubscriptionService` — manual `startTrial/activatePlan/renewSubscription/
  cancelSubscription/checkExpiredTrials/canAccess/getUsageLimits`. **No invoicing, no ledger,
  no entitlement enforcement beyond `allows()` + middleware.**
- Platform admin at `/platform` (guard `platform.admin`): `PlanController`, `SubscriptionController`,
  `DashboardController`, `SchoolController`, `UserController`, `SettingsController`.

### 1.4 RBAC, middleware, routes (reuse, extend)
- Custom RBAC: `roles` + `permissions` + `role_permissions`; `permission:` middleware
  (`EnsureHasPermission`), `school.role:` (`EnsureSchoolRole`).
- Bootstrap (`bootstrap/app.php`): aliases incl. `school.resolve`, `school.active`, `school.access`,
  `school.feature`, `permission`, `platform.admin`, `portal.user`, `teacher.portal`.
  **No `api` routes and no CSRF-exemption helper exist yet — webhooks need one.**
- `config/services.php` has **no** gateway stanza yet (must add, keep env-driven).
- Route groups: `web.php` (home + portal), `platform.php`, `school.php`, `public.php`, `account.php`.
  Finance school group already has `permission:` guards + a FINANCE sidebar group (~10 links).

### 1.5 Other facts
- dompdf installed (receipts/PDF). No excel/2FA packages. No Spatie/Nova.
- `FinanceAudit` + `ActivityLog` exist for audit trails; timestamps on lifecycles.
- Scheduler wired; queue not central yet (email + notification available).

---

## 2. What is missing (gaps this architecture fills)

| # | Gap | Phase |
|---|---|---|
| G1 | Real payment gateway integration (abstraction, init→redirect→verify→webhook) | 2 |
| G2 | Settlement profiles (per-school disbursement bank + currency) | 3 |
| G3 | Service-charge engine (who pays, who splits, snapshot on txn) | 4 |
| G4 | Platform subscription billing (invoices, platform ledger, snapshot pricing, billing run) | 5 |
| G5 | Licensing / entitlements (module gating, license units) | 6 |
| G6 | Academic-year accounting (cashbook, journal, trial balance) | 8 |
| G7 | Payroll | 9 |

---

## 3. Critical architecture rules adopted from the brief (binding)

1. **Two worlds, never mixed.** School Finance (tenant-scoped, `school_id`) vs Pishon Platform
   Finance (platform-scoped, no `school_id` OR explicit `platform.*` scope). No table, service, or
   report may span both. Payment provider charges (school's own) ≠ Pishon service charge.
2. **Snapshot pricing.** Historical transactions keep the amounts/rules that were true at the
   time. Changing a fee / charge rule / plan price NEVER rewrites prior transactions.
3. **Server-side verification only.** Callback params from the browser are untrusted; confirm via
   the provider (Paystack verify) and via signed webhooks; compare amount, currency, reference,
   school, invoice server-side before touching money.
4. **Idempotency.** Every webhook repeats harmlessly (unique references + status guards + DB
   transaction; second dispatch is a no-op or 200-conflict).
5. **Secrets.** Gateway keys stored encrypted at rest (`PaymentGatewayConfig`) + `config/services.php`
   env fallback; webhook secrets compare with `hash_equals`; never logged/echoed.
6. **No silent deletions.** Money events use reversals/refunds/adjustments (sign-flipped ledger
   entries), never `DELETE`. Confirmed payments are immutable; corrections are new records.
7. **Atomic money movement.** Webhook confirmation wraps `PaymentService` + invoice/credit updates
   in a DB transaction.
8. **Payments infrastructure is provider-agnostic.** Gateway manager resolves a driver by
   `provider` slug (paystack|flutterwave|…). Drivers use Laravel `Http` (testable via `Http::fake`);
   real keys optional — system stays functional (stub-safe) when no gateway is configured.
9. **Nigeria-first, future-ready.** NGN default; amount in kobo internally for comparison safety.

---

## 4. Conflict map — planned vs existing (and resolutions)

| Planned concept | Existing construct | Decision |
|---|---|---|
| `provider_transactions` | `payment_transactions` | **DO NOT CREATE.** Extend `payment_transactions` (already provider-aware). |
| `subscription_ledger` both worlds | split | Platform ledger (Pishon money) is `platform_transactions` + `platform_ledger_entries`; school cashbook is `cashbook_entries`. Separate tables. |
| Gateway keys in `config/services.php` | `OnlinePaymentService` stub reads `config('services.paystack.secret')` | Keep env fallback AND new per-school `payment_gateway_configs` (encrypted). Resolver: school config wins, else env (`config/services.php` stanza added). |
| Webhook URL | no API/webhook routes | New CSRF-exempt webhook route group (`routes/webhooks.php` + `bootstrap/app.php` `withMiddleware` exception). Signed, rate-limited. |
| Settlement profile bank info | existing `BankAccount` (school ops/banking) | `school_settlement_profiles` is a *disbursement* profile for gateway settlements, referencing a `BankAccount`; does not replace it. |
| Service charge | none | `service_charge_rules` snapshot + `ChargeEngine`. |
| Subscription billing | manual `Subscription`/`SubscriptionService` | Keep manual ops; ADD `subscription_invoices` + billing run; `SubscriptionService` gains automated `billForPeriod()`. |
| Entitlements | `Plan.features` + `school.feature` middleware | keep as source-of-truth; add `subscription_entitlements` / `license_units` rows per subscription for auditable history; middleware unchanged or extended only. |
| Cashbook | no ledger anywhere | new `cashbook_entries` (per school) recording every money event via `CashbookService`; backfill not required for old data. |
| `require_verification` | `PaymentService` already handles | reuse. |

---

## 5. New tables (all follow existing conventions: anonymous class, `school_id` constrained cascade, snake_case, named index)

- `payment_gateway_configs` — school_id, provider, mode(live|test), public_key, secret_key (encrypted),
  webhook_secret (encrypted), is_active, config(json).
- `school_settlement_profiles` — school_id, bank_account_id, settlement_currency, is_default.
- `service_charge_rules` — school_id, name, charge_type(percent|fixed), charge_to(parent|school|split),
  charge_value, gateway_fee_handling(charge_parent|absorb|split), applies_to(online), is_active.
- (Phase 5) `subscription_invoices` — school_id, subscription_id, period_start/end, line_items(json),
  base_amount, service_charge, gateway_fee, total, currency, status, paid_at, payment gateway refs. **platform-scoped money.**
- (Phase 5) `platform_transactions` + `platform_ledger_entries` — Pishon money (no school isolation beyond reporting link).
- (Phase 6) `subscription_entitlements` / `license_units`.
- (Phase 8) `cashbook_entries`.

`payment_transactions` gains (Phase 2): `intent`(payment|subscription), `amount_verified_at`,
`webhook_signature_verified`, `processed_at`, `attempts`. Nullable/defaulted; existing rows untouched.

---

## 6. New services / controllers / routes / permissions (map)

Services:
- `Payments\Contracts\PaymentGateway` (interface: initialize/redirectUrl/verify/webhookValid/handleWebhookConfirm).
- `Payments\GatewayManager` (resolver, active-gateway resolution).
- `Payments\Drivers\PaystackDriver` (live), `Payments\Drivers\FlutterwaveDriver` (scaffold).
- `Payments\ChargeEngine` (Phase 4).
- `Finance\CashbookService` (Phase 8).

Controllers:
- `Payments\PaymentWebhookController` (route `/api/webhooks/payments/{provider}`, CSRF-exempt, rate-limited, signature-verified, idempotent).
- `School\FinanceGatewayController` (school UI to set up keys/active gateway).
- `School\SettlementProfileController` (school UI, Phase 3).
- `Platform\PlatformPaymentsController` (platform ops view of Pishon money, Phase 5).

Routes (naming):
- `school.finance.gateways.*` + `school.finance.settlement.*` inside existing finance group (sidebar: add NEW top-level "Online Payments" group; do not reorder existing).
- Portal: extend existing `payOnline` to redirect (`portal.fees.pay-online` → gateway request), add callback + success/failure views.

Permissions (seed only, additive): `configure_gateways` (school_admin), `view_gateway_payments`.

---

## 7. Security model (webhooks/pay)

- Route is public + CSRF-exempt but **only** for `/api/webhooks/payments/{provider}`.
  Rate limit (e.g. 60/min) + throttle. Signature verified via provider secret each hit.
- Server-side amount/currency/school/invoice checks before confirmation (reject mismatch).
- Unique `reference` on `PaymentTransaction` is the idempotency key; status guards
  (`initialized|pending` → `success|failed` only); second event short-circuits.
- Driver HTTP via `Http` facade + `retry(3)`; timeouts configured.
- Secrets: `encrypted` cast columns; school config never included in serialized output/logs.

---

## 8. Phased build order (each phase ends GREEN on the 314 baseline + new tests)

1. **Phase 2 — Commercial Payment Infrastructure** (this work): migrations, models, GatewayManager,
   Paystack driver, webhook, portal payOnline wiring, school gateway-config UI, permission seeding,
   tests. *(Provider-agnostic; system functional with no gateway configured.)*
2. **Phase 3 — Settlement Engine**: `school_settlement_profiles`, settlement dashboard, payouts
   tracking in `payment_transactions.meta`.
3. **Phase 4 — Service Charge Engine**: `service_charge_rules`, `ChargeEngine`, snapshot at txn time,
   dashboard + receipt disclosure.
4. **Phase 5 — Platform Subscription & Billing**: `subscription_invoices`, billing run command,
   `SubscriptionService.billForPeriod()`, subscription payment via same gateway (Pishon money path),
   platform ops UI. **Never touches school money.**
5. **Phase 6 — Licensing & Entitlements**: entitlement rows + module gating enforcement.
6. **Phase 8 — Cashbook / Accounting & Reporting**: `cashbook_entries`, journal, trial balance,
   reconcile vs existing reports.
7. **Phase 9 — Payroll** (separate EPS).

---

## 9. Risks & mitigations

- **Webhook spoofing before keys configured** → gate expects 401 on missing secret; ops UI warns.
- **Stub regression** → GatewayManager returns `null` gateways gracefully; portal falls back to
  transfer-claim flow (existing) when no gateway configured — preserve current UX.
- **Tenant bleed in new code** → all new models use `HasSchoolId`; tests assert 2-school isolation.
- **Fee/pricing drift** → snapshots; no rewrite of historical rows (explicit prohibition).
- **Double confirmation** → idempotency + transactions (tested).
- **Breakage of existing finance UI/permissions** → additive only; verify via full suite.
- **SQLite vs MySQL** (`decimal` casts, encrypted cast) → tests run on sqlite :memory:; verify migration
  runs on both.

---

## 10. Immediate next action

Implement **Phase 2** per §8 step 1. Deliverables after this report:
(a) `payment_gateway_configs` (encrypted), (b) add `intent`/verification columns to
`payment_transactions`, (c) models, (d) `GatewayManager` + `PaystackDriver` + `FlutterwaveDriver`
scaffold, (e) webhook controller + CSRF-exempt route + `config/services.php` stanza, (f) portal
payOnline redirect + receipt, (g) school gateway-config UI + `configure_gateways` permission,
(h) `PaymentGatewayManagerTest` + `PaymentWebhookTest` + isolation tests, (i) full suite green.