Files
baya-monorepo/archive/build-chain/working-context/backend/handoff/after-refinement-phase-8.md
T
2026-08-02 18:48:32 +03:30

4.0 KiB

After refinement-phase-8 — External rails go real (config-selected vendor adapters)

For the frontend / next backend phase. Backend-owned; frontend reads.

What changed for a client

Nothing user-facing changed by default — the mocks stay the default registration, so every existing flow behaves exactly as before. This phase makes each vendor rail swappable to real by config, not on by default.

One new endpoint (backend-to-backend, not for the browser): POST /api/v1/webhooks/payouts/{provider} — the async PAYA/SATNA reconciliation callback (signature-authenticated, anonymous, webhook rate policy). It flips a submitted payout to paid/failed. No client calls it.

How a rail goes real (ops)

Set the rail's Seams:{rail}:Provider + its credentials (user-secrets/env) and restart — no code change, no deploy of new binaries. Provider tokens (mock stays default; a typo falls closed to mock):

Rail Key Real value Also needs
SMS (launch-critical) Seams:Sms:Provider kavenegar Seams:Sms:{ApiKey,SenderLine,OtpTemplate}
Shahkar / e-KYC / شبا Seams:{Shahkar,IdentityKyc,BankOwnership}:Provider finnotech Seams:Finnotech:{BaseUrl,ClientId,AccessToken}
Geocoding Seams:Geocoding:Provider neshan Seams:Geocoding:{ApiKey}
Object storage Seams:ObjectStorage:Provider s3 Seams:ObjectStorage:{ServiceUrl,Bucket,Region,AccessKey,SecretKey}
Card PSP (+ HMAC webhook + تسهیم) Seams:Payments:Provider zarinpal Seams:Payments:{MerchantId,CallbackUrl,WebhookSigningSecrets}
BNPL Seams:Bnpl:Provider real Seams:Bnpl:Providers:{snapppay,digipay}:* (creds via gateway config)
Payout rail Seams:BankTransfer:Provider jibit Seams:BankTransfer:{ApiKey,SourceSettlementAccount} + webhook secret
Moadian Seams:Moadian:Provider moadian Seams:Moadian:{MemoryId,AccessToken} + signing cert

Two hard rules baked in:

  • SMS real ⇒ the OTP is never logged. The Development OTP-in-logs bridge (GET dev/last_otp) runs only while the mock SMS sender is selected. Once Seams:Sms:Provider=kavenegar, the code only leaves the process over the SMS wire.
  • Money callbacks fail closed. The payout reconciliation + PSP webhook verify a per-provider HMAC over the raw body; an invalid signature mutates nothing. The confirm path still re-verifies the amount server-side.

Behavioural notes the next phase should know

  • Payout rail is async now (when real). A real JibitBankTransferProvider accepts a transfer as submitted; the ledger posts only when the reconciliation callback confirms paid. The ExecutePayoutBatch handler already handled this (MarkSubmitted first, ledger on paid) — it was unchanged.
  • bookings/convert is Dev/Testing only. IPaymentCaptureSimulator is out of the production registration (prod = fail-closed DisabledPaymentCaptureSimulator). Production converts via the b10 payment webhook confirm calling ConvertRequestToBooking directly — do not build a client convert flow.
  • balinyaar BNPL = in-house. In real BNPL mode provider_code=balinyaar resolves to the deterministic net-of-fee model (no external API); tara/torobpay are unbuilt and rejected cleanly.
  • New cron: MoadianReconciliationJob (6 h) walks pending/submitted invoices to registered — registered the phase-7 way (IRecurringJob + one AddSingleton), no migration.

Follow-ups carried forward

  • Per-code BNPL revert (the b11 refund path uses the SnappPay default); SMS.ir/Ghasedak adapters; Finnotech/Moadian token exchange + Moadian signing cert; the refund-settlement poll (BNPL processing → succeeded, pairs with Moadian — the confirm command exists, the poll job is the remaining wiring); a dedicated center-settlement payout (deferred per 6.6 — MoR centers settle via a تسهیم split leg, non-MoR have no separate money path).
  • Redis stays the >1-instance gate; Elasticsearch is never MVP.