# Refinement Phase 8 — External rails go real (SMS → trust/identity → money)

> **Mission:** swap the in-process mocks for real vendors, in impact order. **`ISmsSender` is launch-critical —
> OTP delivery is a log statement today, so no real user can ever log in.** After that: identity/KYC/geocoding/
> object-storage, then the money rails (PSP, BNPL, payout, tax). Each swap is an *adapter behind an existing
> seam*, not a redesign — the seam shapes (idempotency keys, server-side re-verify, upsert-first webhooks) are
> already the handler behavior.
>
> **Track:** backend (integrations) · **Depends on:** [Phase 6](refinement-phase-6-money-correctness.md) (do 6.1
> before real BNPL/manual refunds), [Phase 7](refinement-phase-7-unattended-ops.md) (scheduler for the
> reconciliation/Moadian polls) · **Unlocks:** a real, transacting platform
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md).**

## 1. Context

This phase **is** the server audit's **post-phase-5 (trust rails)** + **post-phase-6 (money rails)**. Full
per-seam "make it real" steps live in **[../server/post-phase-backend-plan.md](../server/post-phase-backend-plan.md)**
§ post-phase-5/6, the topology in **[../server/runtime-services.md](../server/runtime-services.md)** § 8–16, and
the exact swap recipe (package → options → implement → register) for every seam in
**[../../shared-working-context/reports/mocks-registry.md](../../shared-working-context/reports/mocks-registry.md)**.

Every vendor dependency is a deterministic in-process mock today. This phase makes them real, one seam at a
time, config-selected — handlers don't change.

## 2. Required reading

- **[../server/post-phase-backend-plan.md](../server/post-phase-backend-plan.md)** § post-phase-5 (5.1–5.6) and
  § post-phase-6 (6.1–6.6). These are the spec.
- **[../../shared-working-context/reports/mocks-registry.md](../../shared-working-context/reports/mocks-registry.md)**
  — the per-row make-it-real steps (packages, config keys, the exact methods to implement).
- **[../server/runtime-services.md](../server/runtime-services.md)** § 8–16 — vendor defaults + deployment
  notes per rail.

## 3. Scope — swap the seams, in impact order

### 3.1 Trust & identity rails (post-phase-5)
- **5.1 — real SMS gateway behind `ISmsSender` (LAUNCH-CRITICAL, do first).** Kavenegar/Ghasedak/SMS.ir;
  template/pattern OTP send; `Seams:Sms:{ApiKey,SenderLine,BaseUrl}`. This **replaces the Development OTP-in-logs
  bridge** from [Phase 0](refinement-phase-0-bring-up.md). Keep the per-phone resend window + `otp` rate policy.
  **No real user can log in until this ships.**
- **5.2 — real Shahkar + e-KYC** (`IShahkarVerifier`, `IIdentityKycProvider`) via a Finnotech-class bridge; keep
  shared-SIM a handled failure; persist real vendor refs.
- **5.3 — real استعلام شبا** (`IBankAccountOwnershipVerifier`) — the b13 first-payout gate; bundle with 5.2's
  vendor family.
- **5.4 — real geocoder** (`IGeocoder`) behind Neshan (address coordinates / EVV distance). REQ-008's user pin
  reduces but doesn't remove the need.
- **5.5 — real object storage** (`IObjectStorage`) — MinIO/S3/ArvanCloud with presigned PUT/GET; makes the b6
  signed-URL contract and REQ-006 avatars real (local disk + `file://` today).
- **5.6 — document manual MoH/INO/eNamad** (`ICredentialVerifier`, `ILicenseVerificationService`) as the
  intended MVP state (no public B2B API exists) — mark the registry rows so they stop reading as debt.

### 3.2 Money rails (post-phase-6) — **do [Phase 6](refinement-phase-6-money-correctness.md) 6.1 first**
- **6.1 — real PSP/IPG + webhook signatures + تسهیم** (`IPaymentProvider`, `IWebhookVerifier`,
  `ISettlementSplitProvider`) — ZarinPal/Sadad/Vandar/Jibit; mandatory server-side `verify` re-check; per-provider
  HMAC on the raw body; provider factory per gateway row; persist full responses. (High risk — Shaparak
  certification lead time.)
- **6.2 — real BNPL adapters** (`IBnplProvider`/`IBnplProviderResolver`) — SnappPay/Digipay; Toman↔IRR only via
  `ICurrencyNormalizer`; per-contract commission from the settle response. (Do Phase 6 §6.1 revert-clearing
  first.) Also settle the **`balinyaar` in-house provider** decision from [Phase 3](refinement-phase-3-contract-batch.md) REQ-022.
- **6.3 — real PAYA/SATNA payout rail** (`IBankTransferProvider`) + the async `submitted → paid/failed`
  reconciliation callback (the mock settles instantly). Wire the weekly trigger via
  [Phase 7](refinement-phase-7-unattended-ops.md).
- **6.4 — remove `IPaymentCaptureSimulator`** (b10's real capture superseded it; move it into the test host or
  gate to Development/Testing).
- **6.5 — real Moadian submission + reconciliation** (`IMoadianClient`) — enrollment + signing cert; the
  `pending → submitted → registered/failed` poll registered under [Phase 7](refinement-phase-7-unattended-ops.md).
- **6.6 — decide the partner-center settlement rail** (resolver exists; no money path pays a center yet) — a
  product decision, then optionally a center-settlement ledger account + payout reusing the b13 machinery.

## 4. Mocks & seams

This phase retires the vendor mocks one row at a time (each config-selected, so a partial rollout is fine —
e.g. real SMS + real geocoder while payments stay mocked in a pre-launch environment). The manual MoH/INO/eNamad
seams (5.6) **stay** as the intended MVP mechanism.

## 5. Critical rules

- **5.1 SMS is the gate to a first real session** — sequence it first; nothing else matters if no one can log
  in. Once it ships, **the OTP must never be logged** (remove the Development log/echo bridge).
- **Never trust a payment callback alone** — the mandatory server-side `verify` re-check + per-provider HMAC are
  non-negotiable on the real PSP swap.
- **Money-rail swaps are irreversible-transfer territory** — the DB uniques (`UNIQUE(booking_id)` payout link,
  filtered payment uniques), forward-only status machines, and idempotency keys are the backstops; keep them.
- **Do [Phase 6](refinement-phase-6-money-correctness.md) 6.1 before real BNPL/manual refunds** or real money
  strands ledger state.
- **Currency conversion happens only in the adapter** via `ICurrencyNormalizer` — never internally.

## 6. Definition of Done

- [ ] Real SMS delivers OTPs to a real handset; the Development OTP-in-logs/echo bridge is removed; a real user
      can complete login end-to-end.
- [ ] Each swapped seam is config-selected, persists real vendor responses, and its handler is unchanged;
      `dotnet build`/`dotnet test` green.
- [ ] Money rails: a real card capture confirms via server-side verify + signature; a real payout reconciles
      `submitted → paid`; an invoice registers with Moadian; the BNPL revert-clearing (Phase 6) completes.
- [ ] `IPaymentCaptureSimulator` is out of the production registration.
- [ ] The registry rows for swapped seams are 🟢; manual-review seams (5.6) are marked "manual = intended MVP".

## 7. How to test

- Request an OTP → receive a real SMS → log in (no code in the logs).
- (Sandbox) run a real card payment → server-side verify + signature check pass → booking converts; a signed
  webhook with a bad signature is rejected.
- Run a payout batch against the real rail → it moves to `submitted`, then the async callback flips it to
  `paid`; an invoice reaches `registered` at Moadian.

## 8. Hand off & document

- Update `server/CLAUDE.md` (seam registrations), the mocks-registry (rows → 🟢), and
  [../server/runtime-services.md](../server/runtime-services.md) (which services are now live). Record vendor
  accounts/config keys in the deploy docs (never the secrets). Save a memory note per rail that goes live.
