# 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.