create mvp path
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# bnpl — provider-financed installments
|
||||
|
||||
> Client seam `client/src/services/bnpl/` · `USE_BNPL_MOCK = true` (**mock is primary**) · 9 server ops
|
||||
> Last verified: 2026-07-30 against commit `d3ec723` and swagger.v1.json (2026-07-29).
|
||||
|
||||
The second checkout rail. **Balinyaar does not finance anything** — a provider (SnappPay, Digipay, …) pays
|
||||
the platform net of its commission and carries the customer's installments itself. Card checkout is
|
||||
[payment.md](payment.md).
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Path | Auth | Verdict |
|
||||
| --- | --- | --- | --- |
|
||||
| POST | `/api/v1/checkout_bnpl/eligibility` | `[Authorize]` · `sensitive` | wired |
|
||||
| POST | `/api/v1/checkout_bnpl/initiate` | `[Authorize]` · `sensitive` | wired · **`Idempotency-Key`** |
|
||||
| GET | `/api/v1/checkout_bnpl/{id}` | `[Authorize]` · `sensitive` | wired |
|
||||
| GET | `/api/v1/checkout_bnpl/by_request/{bookingRequestId}` | `[Authorize]` · `sensitive` | wired |
|
||||
| POST | `/api/v1/webhooks_bnpl/{provider}` | **anonymous** · `webhook` 120/min | server-only — the provider calls it |
|
||||
| GET | `/api/v1/admin_bnpl/{id}` | admin · `sensitive` | **unwired** — no console screen |
|
||||
| POST | `/api/v1/admin_bnpl/{id}/verify` | admin · `sensitive` | **unwired** |
|
||||
| POST | `/api/v1/admin_bnpl/{id}/settle` | admin · `sensitive` | **unwired** |
|
||||
| POST | `/api/v1/admin_bnpl/{id}/revert` | admin · `sensitive` | **unwired** — the reversal is driven from [refunds.md](refunds.md) instead |
|
||||
|
||||
### Phantom — 3
|
||||
|
||||
All three are REQ-022's deferred half. Written real-shaped so the swap is one line.
|
||||
|
||||
| Client call | REQ | Note |
|
||||
| --- | --- | --- |
|
||||
| `GET /api/v1/checkout_bnpl/options/{bookingRequestId}` | REQ-022 | The D1/D2 provider + plan list, per-plan monthly / down-payment / total |
|
||||
| `GET /api/v1/checkout_bnpl/schedule/{id}` | REQ-022 | The D4 repayment schedule |
|
||||
| `GET /api/v1/checkout_bnpl/wallet_installments` | REQ-024 | The D5 wallet installment list |
|
||||
|
||||
REQ-022 was **partially** delivered: `balinyaar` was added to the `provider_code` enum; `options` and
|
||||
`schedule` were deferred. `BnplEligibilityDto` carries a single `planSummary` + `installmentCount`, not a
|
||||
list of plans — which is exactly why the client needs `options`.
|
||||
|
||||
## The money shape
|
||||
|
||||
Two facts that make BNPL different from card, and both are easy to get wrong:
|
||||
|
||||
1. **The card payment is recorded net of the provider's fee.** The provider deducts its commission before
|
||||
remitting, so the platform receives `orderAmount − bnplCommission`. `settledAmountIrr` and
|
||||
`bnplCommissionIrr` are both on `BnplOrderStatusDto`, and the handler reads the **actual deducted
|
||||
amount from the settlement response** — never a rate from config. `Seams:Bnpl:CommissionRate` tunes the
|
||||
*mock* only.
|
||||
2. **Settlement is not necessarily instant.** `settledAt` is nullable, modelling the deferred / T+1–3 /
|
||||
weekly reality. A null `settledAt` on a `settled` order is normal, not an inconsistency.
|
||||
|
||||
`BnplStatus` is **forward-only**. A reversal is `reverted`, with `revertTransactionId`,
|
||||
`revertedAmountIrr`, `revertedAt` and — when the provider returns it — `providerCommissionReversedAmount`,
|
||||
which the reconciliation needs and which most providers do not send. See [refunds.md](refunds.md) for the
|
||||
`bnpl_revert` refund channel.
|
||||
|
||||
## Shape rules the JSON does not express
|
||||
|
||||
- **Eligibility accepts the credit-check inputs** `{ nationalId, mobile, consent }` (REQ-023, delivered).
|
||||
Consent is **required** when the KYC inquiry runs — it is a legal precondition, not a checkbox.
|
||||
- **`eligibilityStatus` distinguishes three outcomes**, and the third is not a failure:
|
||||
`not_eligible` (provider declined) vs `ceiling_exceeded` (order above `creditCeilingIrr` — offer card
|
||||
instead) vs `eligible`. The UI must fall back to card, not show an error, on either negative.
|
||||
- **`bookingId` is on the settled order** (REQ-024, confirmed) so the wallet can link an installment plan
|
||||
to its booking.
|
||||
- **D5 installment status is provider-reported, not ledger-derived.** The platform does not track the
|
||||
customer's repayment; whatever the provider says is the truth. Never compute an installment state from
|
||||
Balinyaar's own ledger.
|
||||
- **`currency` is on the wire and matters.** `Seams:Bnpl:WireCurrency` is `IRR` by default; SnappPay and
|
||||
Digipay speak Rial. Conversion happens **only** inside the adapter via `ICurrencyNormalizer`.
|
||||
- Provider credentials proper live in the encrypted `payment_gateways.config_json`; only non-secret
|
||||
connection facts (base URL, sandbox flag, merchant handle) come from `Seams:Bnpl:Providers`.
|
||||
- `Seams:Bnpl:NotEligibleMobile` (`09120000099`) is the designated test mobile that returns
|
||||
`not_eligible`, so the fall-back-to-card path is testable.
|
||||
|
||||
## Enums
|
||||
|
||||
| Vocabulary | Values |
|
||||
| --- | --- |
|
||||
| `BnplStatus` | `eligible` `token_issued` `verified` `settled` `reverted` `cancelled` `failed` |
|
||||
| `BnplEligibilityStatus` | `eligible` `not_eligible` `ceiling_exceeded` |
|
||||
| `ProviderCode` | `snapppay` `digipay` `tara` `torobpay` `balinyaar` |
|
||||
| `BnplInstallmentStatus` *(D5, provider-reported)* | `paid` `due_soon` `upcoming` `overdue` |
|
||||
| `BnplHandoffOutcome` *(client, from the return URL)* | `success` `failure` |
|
||||
|
||||
The first three are verified identical to `Entities/Bnpl/BnplStatus.cs`,
|
||||
`BnplEligibilityStatus.cs` and `BnplProviderCodes.cs`. Note `snapppay` has **three** `p`s.
|
||||
|
||||
## Open REQs
|
||||
|
||||
| REQ | Status | Effect |
|
||||
| --- | --- | --- |
|
||||
| REQ-022 | partially delivered | `options` and `schedule` deferred → 3 phantom routes. D1/D2/D4 are mock-only |
|
||||
| REQ-024 | partially delivered | `bookingId` confirmed present; the wallet installment list is deferred |
|
||||
Reference in New Issue
Block a user