Buy now pay later
The BNPL provider approves the customer for installments or deferred payment, pays you the full amount up front, and owns the repayment relationship — including the credit and most of the fraud risk. To your integration it's one more redirect method.
The BNPL methods
| Method | payment_method | Region |
|---|---|---|
| Klarna | klarna | EU / UK / US |
| Affirm | affirm | US / Canada |
| Afterpay / Clearpay | afterpay_clearpay | US / UK / AU / NZ |
One method id per program; the repayment products behind it (pay-in-4, monthly financing, pay-later) are the provider's menu, chosen by the customer inside the approval flow. You don't select or price them — and the installment schedule never appears in your API objects.
How BNPL works
The properties that matter to you:
- The credit decision happens in the redirect. The provider scores the customer in seconds — identity, history with the program, basket size. You see the verdict as
succeededorfailed, never the reasoning. - Approved means paid. The provider owes you the full amount from the moment the payment succeeds. Whether the customer keeps up with their installments is the provider's problem, not yours.
- Declines are final. A credit refusal is about the customer, not the plumbing — Finscale never fails over a BNPL decline to another provider or program.
Creating a BNPL payment
The standard redirect shape. customer.email matters more than usual — the provider uses it to recognize returning customers, which lifts approval rates:
curl https://api.finscale.dev/v1/payments \
-H "Authorization: Bearer sk_test_51FinscaleDemo…" \
-H "Idempotency-Key: idem_ord_9f21_0716" \
-H "Content-Type: application/json" \
-d '{
"amount": 4900,
"currency": "EUR",
"payment_method": "klarna",
"reference": "ord_9f21_0716",
"customer": { "email": "anna@example.com" },
"return_url": "https://shop.example.com/checkout/return"
}'
The response is requires_action with a next_action.url; the approval flow runs there and the customer comes back to your return_url. Confirm on payment.succeeded, as with every redirect method.
Refunds against installment plans
POST /v1/refunds works unchanged — but the money mechanics differ from cards. Your refund goes to the provider, who adjusts the customer's plan:
- Full refund — the plan is canceled; installments already paid go back to the customer, unbilled ones evaporate.
- Partial refund — the plan re-amortizes over the remaining installments; the customer's future payments shrink.
Either way you see the normal refund lifecycle — pending → refund.succeeded — and the amount nets against your settlement. Refund promptly on returns: customers keep paying installments on merchandise they've sent back until your refund lands, which is the single biggest source of BNPL support tickets.
Disputes & risk ownership
The provider owns the credit risk outright and underwrites fraud at approval — an approved payment can't be clawed back because the customer stops paying or the account was compromised. What remains yours is merchant performance: a customer who never received the goods can still dispute. Those arrive as standard Dispute objects (dp_…, typically reason: "product_not_received") via dispute.created, and the evidence flow applies — proof of delivery wins these.
Settlement timing
The provider's payment confirms like any other method and the full amount joins your unified payout on the standard T+2 schedule — the customer's installment calendar has zero effect on your cash flow. One settlement report line per payment, like everything else; see Settlement & payouts.
Error scenarios
| Scenario | What the API does |
|---|---|
| Customer not approved | payment.failed with the normalized failure_code. Final — offer another payment method; don't retry BNPL for the same basket. |
| Application abandoned | The payment idles in requires_action until the session expires, then fails. Cancel it earlier with POST /v1/payments/{id}/cancel if the customer switches methods. |
| Basket outside program limits | Amount too small or too large for the program: payment.failed with failure_code: "limit_exceeded", or a decline inside the approval flow. |
| Currency or country not supported | 400 invalid_request_error with currency_not_supported at create — match programs to the customer's market, as hosted checkout does automatically. |
Related
- Payment methods overview — families, payload shapes, full catalog
- Disputes — defending product_not_received claims
- Refunds — the refund lifecycle these plans hang off
- Hosted checkout — shows BNPL only where it fits the market
- Method catalog on finscale.dev — regions and currencies