Files
baya-monorepo/dev/post-phase/refinement/refinement-phase-8-external-rails.md
T
2026-07-10 20:59:47 +03:30

7.7 KiB
Raw Blame History

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 (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 § 816, 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

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 + 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 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 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.
  • 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.
  • 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 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 (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.