cleanup phases 6
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# Handoff — after backend-phase-12 (BNPL: provider-financed installments)
|
||||
|
||||
**BNPL checkout is live.** A family can now pay for a booking with a provider-financed installment plan
|
||||
(SnappPay / Digipay / Tara / Torob Pay). The decisive, verified truth: an Iranian provider-financed BNPL order
|
||||
**settles the full booking amount to Balinyaar in one lump, net of the provider's merchant commission**, and the
|
||||
provider owns the customer's installments and **100% of default risk**. So in our books a BNPL order is **a card
|
||||
payment that lands net-of-fee** — one `bnpl_transactions` row (1:1 with its `payment_transaction`), a forward-only
|
||||
`eligible → token_issued → verified → settled → reverted` state machine, and a settle that posts the card-capture
|
||||
ledger legs **plus** a `bnpl_fee_expense` leg so escrow reflects the *net* cash. **The nurse's payout is invariant
|
||||
to payment method.** We do **not** model the customer's repayment schedule.
|
||||
|
||||
## What the frontend (f11-b12) can now build
|
||||
- **"Pay with installments" option at checkout** — `POST checkout_bnpl/eligibility` returns
|
||||
`eligible`/`not_eligible`/`ceiling_exceeded` + the plan summary ("4 interest-free installments, provider-financed",
|
||||
`installmentCount`, `creditCeilingIrr`). On anything but `eligible`, **fall back to card**.
|
||||
- **Start the plan + provider handoff** — `POST checkout_bnpl/initiate` → `token_issued` + `externalPaymentToken`
|
||||
+ `redirectUrl` (send the customer to the provider). Carries an `Idempotency-Key` header.
|
||||
- **Order status** — `GET checkout_bnpl/{id}` (customer, own order only): status, gross/net/commission, the
|
||||
**non-instant** `settledAt`, and the revert audit + async customer ETA.
|
||||
- **Admin BNPL console** — `POST admin_bnpl/{id}/verify|settle`, `POST admin_bnpl/{id}/revert` (full or partial),
|
||||
`GET admin_bnpl/{id}`. The revert surfaces `expectedCustomerRefundEta` (~7–10 business days) and opens a
|
||||
`refund_channel='bnpl_revert'` refund (visible via the b11 `GET refunds/{id}/status`).
|
||||
|
||||
## Live endpoints / contract
|
||||
- Contract: **`dev/contracts/domains/bnpl.md`** (enums, DTO shapes, IRR digit-strings, nullable `settled_at`,
|
||||
failure codes, the net-of-fee settle + customer↔provider↔Balinyaar refund routing). Machine schema:
|
||||
`dev/contracts/openapi/swagger.v1.json` **refreshed**.
|
||||
- All money is IRR integer, on the wire as a **digit-string**; `settled_at` is **nullable** (not instant);
|
||||
`expectedCustomerRefundEta` is a **date**.
|
||||
- Checkout is customer-scoped + **rate-limited**; the webhook is anonymous (signature-verified) + rate-limited;
|
||||
admin endpoints are behind the admin policy + rate-limited.
|
||||
|
||||
## What is mocked / waiting
|
||||
- **The provider + currency are mocked** behind **`IBnplProvider`** (per `provider_code` via
|
||||
**`IBnplProviderResolver`**) and **`ICurrencyNormalizer`** — deterministic, no network; the settle commission is
|
||||
a configurable mock % (`Seams:Bnpl:CommissionRate`, default 10%). See `reports/mocks-registry.md` for the exact
|
||||
real-provider steps (SnappPay/Digipay verb sets, encrypted creds).
|
||||
- **Settlement timing is not modelled as instant** — `settled_at` is nullable and read from the settlement
|
||||
(`Seams:Bnpl:SettlementInstant` toggles the mock). **b13 must not assume BNPL cash funds a payout.**
|
||||
- **`bnpl_settlement_entries`** (tranched settlement) is **DEFERRED — modeled-but-not-built**; adding it later is a
|
||||
purely additive migration.
|
||||
- **Multi-provider routing / failover** is DEFERRED — one active route, config-driven selection.
|
||||
- The revert reuses the **b11 refund path**; `provider_commission_reversed_amount` is left null there (reconciled
|
||||
from the provider response later).
|
||||
|
||||
## Notes for the next backend phases
|
||||
- **b13 (payouts):** the `settled_at`-gates-payout coupling lives here — add the
|
||||
`require_bnpl_settlement_for_payout` config flag and gate a BNPL booking's payout on `bnpl_transactions.settled_at`
|
||||
actually being set (never pay a nurse before Balinyaar holds the cash). The `nurse_payable` accrual is already
|
||||
identical to the card path, so payout amounts need no BNPL-specific logic.
|
||||
- **Shared conversion:** b10's booking-creation was extracted to `Features/Bookings/BookingConversion` — both the
|
||||
card capture and the BNPL settle use it. Reuse it, don't fork.
|
||||
Reference in New Issue
Block a user