57 lines
4.0 KiB
Markdown
57 lines
4.0 KiB
Markdown
# 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.
|