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

Methodpayment_methodRegion
KlarnaklarnaEU / UK / US
AffirmaffirmUS / Canada
Afterpay / Clearpayafterpay_clearpayUS / 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

POST /v1/payments requires_action BNPL approval flow credit decision — seconds succeeded failed redirect approved declined — final provider pays you the FULL amount up front settles through your normal payout customer repays the provider in installments

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 succeeded or failed, 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:

POST /v1/payments — Klarna
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 — pendingrefund.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

ScenarioWhat the API does
Customer not approvedpayment.failed with the normalized failure_code. Final — offer another payment method; don't retry BNPL for the same basket.
Application abandonedThe 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 limitsAmount 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 supported400 invalid_request_error with currency_not_supported at create — match programs to the customer's market, as hosted checkout does automatically.