# 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 |