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

112 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)** § 816, 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.15.6) and
§ post-phase-6 (6.16.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)** § 816 — 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.