BNPL Direct Integration

Initiate a Buy Now, Pay Later payment from your own server

Overview

BNPL from your own server is a redirect flow, and it uses the same authState pattern as every other CrissCross method that needs the customer to go somewhere. There is no BNPL-specific endpoint and no SDK.

The shape is: create a session, initiate the payment, send the customer to the URL you get back, and wait for the webhook.

Pre-requisites

  • BNPL enabled on your account, with a settlement option agreed. See Buy Now, Pay Later.
  • A session created with integrationType: "direct" and an HTTPS redirectUrl.
  • The session amount within your BNPL limits, and a ZAR / ZAF session.

The flow

1

Create a checkout session

curl -X POST https://api.crisscross.money/v1/checkout/session \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"merchantId": "YOUR_MERCHANT_ID",
"merchantReference": "ORDER67890",
"amount": 450000,
"currency": "ZAR",
"integrationType": "direct",
"redirectUrl": "https://merchant.com/redirect",
"payerDetails": {
"emailAddress": "[email protected]",
"location": "ZAF",
"fullName": "Siyanda Mthembu",
"phoneNumber": "0723456789"
}
}'

amount is in minor units — the example is R4 500.00, a basket size where an instalment plan is worth offering.

Confirm BNPL is eligible

Do not offer BNPL in your UI without checking. Eligibility depends on your configuration and on the session’s amount, currency and payer location:

curl -G https://api.crisscross.money/v1/payment/available-methods \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d sessionId={sessionId}

Offer BNPL only if an entry with "type": "bnpl" is present.

Initiate the payment

curl -X POST https://api.crisscross.money/v1/payment \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"paymentMethodId": "bnpl",
"sessionId": "01951c8a-7c3d-7e1f-9d4a-2b3c4d5e6f70",
"paymentDetails": {
"type": "bnpl",
"payerEmail": "[email protected]",
"payerMobileNumber": "0723456789"
}
}'
FieldRequiredNotes
paymentMethodIdyesAlways the literal string "bnpl"
paymentDetails.typeyesAlways "bnpl"
paymentDetails.payerEmailyesUsed to identify the customer in the instalment flow
paymentDetails.payerMobileNumbernoSpeeds up the flow when supplied

You do not send the instalment term. The customer chooses it, between two and six months, inside the flow.

Redirect the customer

BNPL always returns PENDING with a redirect authState — there is no synchronous success, because the customer has not chosen a plan yet:

{
"transactionId": "9f3e4b2c-1a6d-4e88-9d3a-ff1234567890",
"status": "PENDING",
"message": "Authorization required",
"authState": {
"type": "redirect",
"redirectUrl": "https://bnpl.crisscross.money/plan/9f3e4b2c-1a6d-4e88-9d3a-ff1234567890"
}
}

Send the customer to authState.redirectUrl as returned. Do not rebuild it, embed it in an iframe, or open it in a background tab — the flow collects card details and must run as a top-level navigation.

Store transactionId against your order before redirecting. It is how you match the webhook.

Handle the return

The customer comes back to the session’s redirectUrl with ?status=completed, ?status=failed or ?status=cancelled.

This is a display signal only. ?status=completed means the customer finished the flow, not that the payment is settled — and ?status=cancelled is the ordinary outcome of a customer who was not approved or who declined the terms, which is not an error. See When a customer is not approved.

Confirm with the webhook

The webhook is authoritative. Match on transactionId, sessionId or merchantReference, verify its signature, and only then fulfil. The payload carries paymentMethodId: "bnpl" and the instalment term the customer chose. See Webhook Events.

A customer who abandons the flow entirely still produces a webhook when the session expires.

Cancelling before completion

A BNPL payment that has not been completed can be cancelled — for example if the customer returns to your site and empties their basket:

curl -X POST https://api.crisscross.money/v1/payment/{transactionId}/cancel \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Cancelling produces a webhook. A payment the customer has already completed cannot be cancelled — refund it instead.

Polling

Prefer the webhook. If you need to check state directly — reconciling after an outage, or driving an order status page — read the transaction rather than polling in a tight loop:

curl -G https://api.crisscross.money/v1/payment/{transactionId} \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

A customer can sit in the instalment flow for several minutes, so a PENDING BNPL transaction is normal and not a sign of a problem.

Things that will bite you

  • There is no synchronous success. Any BNPL initiation that returns anything other than PENDING with a redirect is a failure to initiate, not a completed payment.
  • ?status=cancelled is expected traffic, not an incident. Affordability declines arrive this way.
  • Do not iframe the redirect. It collects card details and needs a top-level navigation.
  • Settlement is not one entry per order under instalment settlement. Reconcile on the transaction. See Settlement Reporting.

Additional resources