5.5 KiB
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 commitd3ec723and 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.
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 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:
- The card payment is recorded net of the provider's fee. The provider deducts its commission before
remitting, so the platform receives
orderAmount − bnplCommission.settledAmountIrrandbnplCommissionIrrare both onBnplOrderStatusDto, and the handler reads the actual deducted amount from the settlement response — never a rate from config.Seams:Bnpl:CommissionRatetunes the mock only. - Settlement is not necessarily instant.
settledAtis nullable, modelling the deferred / T+1–3 / weekly reality. A nullsettledAton asettledorder 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 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. eligibilityStatusdistinguishes three outcomes, and the third is not a failure:not_eligible(provider declined) vsceiling_exceeded(order abovecreditCeilingIrr— offer card instead) vseligible. The UI must fall back to card, not show an error, on either negative.bookingIdis 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.
currencyis on the wire and matters.Seams:Bnpl:WireCurrencyisIRRby default; SnappPay and Digipay speak Rial. Conversion happens only inside the adapter viaICurrencyNormalizer.- Provider credentials proper live in the encrypted
payment_gateways.config_json; only non-secret connection facts (base URL, sandbox flag, merchant handle) come fromSeams:Bnpl:Providers. Seams:Bnpl:NotEligibleMobile(09120000099) is the designated test mobile that returnsnot_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 ps.
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 |