7.7 KiB
Refinement Phase 8 — External rails go real (SMS → trust/identity → money)
Mission: swap the in-process mocks for real vendors, in impact order.
ISmsSenderis 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 (do 6.1 before real BNPL/manual refunds), Phase 7 (scheduler for the reconciliation/Moadian polls) · Unlocks: a real, transacting platform Before you start, read ../../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 § post-phase-5/6, the topology in ../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.
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 § 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 — the per-row make-it-real steps (packages, config keys, the exact methods to implement).
- ../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. Keep the per-phone resend window +otprate 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 6.1 first
- 6.1 — real PSP/IPG + webhook signatures + تسهیم (
IPaymentProvider,IWebhookVerifier,ISettlementSplitProvider) — ZarinPal/Sadad/Vandar/Jibit; mandatory server-sideverifyre-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 viaICurrencyNormalizer; per-contract commission from the settle response. (Do Phase 6 §6.1 revert-clearing first.) Also settle thebalinyaarin-house provider decision from Phase 3 REQ-022. - 6.3 — real PAYA/SATNA payout rail (
IBankTransferProvider) + the asyncsubmitted → paid/failedreconciliation callback (the mock settles instantly). Wire the weekly trigger via Phase 7. - 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; thepending → submitted → registered/failedpoll registered under Phase 7. - 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
verifyre-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 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 testgreen. - 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. IPaymentCaptureSimulatoris 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 topaid; an invoice reachesregisteredat Moadian.
8. Hand off & document
- Update
server/CLAUDE.md(seam registrations), the mocks-registry (rows → 🟢), and ../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.