Skip to content

API-only integration

Build the payment page yourself, with no checkout of ours in it: your page offers the methods, shows the QR code or the cash reference, and sends the customer to the card page. The API tells you, at each step, what to show.

If your customer has chosen already, create and confirm in one call, with the method in payment_method_data[type] and confirm: true (and a return_url for card). It comes back requires_action with its next_action: go to step 3.

To let them choose on your page, create it without a method. Its payment_method_types lists the methods that can take this payment now: offer those. It also comes back with next_action.redirect_to_url, the hosted checkout link; you do not have to send anyone there. GET /v1/payment_methods lists what your account can take in general, and why a method cannot take a payment now.

Terminal window
curl https://api.example.com/v1/payment_intents/pi_01j9x2m8k4q7r5t3v6w8y0z2a4/confirm \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: order-1042-confirm-1" \
-H "Content-Type: application/json" \
-d '{"payment_method_data": {"type": "qrph"}}'

For card, add the return_url the customer is to come back to: {"payment_method_data": {"type": "card"}, "return_url": "https://shop.example.com/orders/1042/paid"}. A card confirmed without one is refused, and nothing is started.

The intent is requires_action, and next_action.type says what the customer is to do:

typeFieldsWhat to do
qrph_display_qr_codedata, expires_atRender data as a QR code for the customer to scan with any bank or e-wallet app, and show how long it has left.
cash_display_referencereference, amount, currency, expires_at, instructionsShow the reference, the exact amount and until when, with instructions. The customer pays it in cash at a collection location: see Accepting cash.
redirect_to_urlurl, return_urlSend the customer's whole page to url, our checkout's card page, where they type their card; it cannot be shown in a frame. Their bank may ask them to confirm it on its own page, which brings them back to the card page. However it went, they then come back to your return_url with payment_intent=<id> added, and never a status.

Build your integration to ignore a type it does not know, as for any field.

Listen for the webhooks, and, while the customer is on your page waiting, retrieve the intent every few seconds to update what you show:

StatusEventWhat to show
succeededpayment_intent.succeededPaid. Fulfil the order.
processingpayment_intent.processingThe customer paid; the outcome is not known yet. Not a failure.
requires_payment_methodpayment_intent.payment_failedThe attempt failed: last_payment_error says why, in words you can show. Offer to try again.
canceledpayment_intent.canceledCancelled.

A customer coming back to your return_url proves nothing: check the intent before you fulfil the order.

A failed attempt, a declined card or a code or reference that expired unpaid (payment_intent_payment_attempt_expired), leaves the intent requires_payment_method. Confirm it again for a new attempt, with the same method or another of its payment_method_types; a new method is priced when it is chosen. While an attempt is requires_action, confirming again answers the intent unchanged: the customer pays the code, reference or page they have, or it expires. To stop, cancel the intent; it cannot be cancelled once it is processing or succeeded.