Appearance
Callback Lifecycle
Every payment button shares the same lifecycle. The callbacks in CallbackHandlers map to distinct stages.
Flow Diagram
Stage 1 — onClick
ts
onClick: (identity: ProviderIdentity, options?: Record<string, unknown>) => Promise<string>Triggered when the user clicks a payment button.
| Parameter | Description |
|---|---|
identity | Contains providerProtocolType, paymentMethodType, and optionally id — identifies which button was clicked |
options | The configured provider options object (e.g. totalAmount, currencyCode). Shape varies by provider. |
What to do here: Call your backend to create a transaction via the collana pay API. Return the checkoutData string from the API response — the SDK handles decoding and processing internally.
js
onClick: async (identity, options) => {
const resp = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider: identity, options }),
});
const data = await resp.json();
return data.checkoutData;
},Advanced: Identifying a specific button with identity.id
By default, identity.providerProtocolType and identity.paymentMethodType are enough to route your backend call. However, if you render the same provider in multiple locations (e.g. a PayPal button in both the cart drawer and the checkout page), you may need to distinguish which specific button was clicked.
Set the optional id field on a ProviderButtonConfig entry, and it will be forwarded through to the identity object in every callback:
js
CollanaPay.ExpressButtons({
providers: [
{
providerProtocolType: 'PayPal',
paymentMethodType: 'PayPalExpress',
id: 'cart-paypal', // <-- integrator-defined identifier
container: '#cart-paypal-btn',
options: { intent: 'capture', currencyCode: 'EUR' },
},
{
providerProtocolType: 'PayPal',
paymentMethodType: 'PayPalExpress',
id: 'checkout-paypal', // <-- different id, same provider
container: '#checkout-paypal-btn',
options: { intent: 'capture', currencyCode: 'EUR' },
},
],
onClick: async (identity, options) => {
console.log(identity.id); // 'cart-paypal' or 'checkout-paypal'
const resp = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider: identity, options }),
});
const data = await resp.json();
return data.checkoutData;
},
onCancel: (identity) => {
console.log('Cancelled from:', identity.id); // 'cart-paypal' or 'checkout-paypal'
},
// ...
}).render();id is purely informational — the SDK passes it through unchanged and does not use it internally.
See the Backend API docs for the expected request and response shapes when creating a transaction server-side.
If onClick throws or returns a rejected promise, the payment flow is aborted. The onError callback will fire.
Stage 2 — Provider SDK Processes Payment
After onClick resolves, the SDK processes the transmitted checkoutData and hands off to the provider SDK. The provider SDK takes over: it shows the wallet payment sheet, handles authentication, consent forms, 3DS challenges, etc.
Stage 3 — Payment Completion (Redirect + Server Callback)
When the provider confirms payment approval, the SDK navigates the user to your UriSuccess confirmation page (supplied when the express transaction was created — no additional configuration is needed on the frontend).
No client-side callback is fired for approval — the success signal is the redirect to your confirmation page. However, the payment is not yet fully confirmed at this point. The PrepareInteraction is finalised asynchronously in the collana pay backend, after which the standard flow (Reservation, Capture, …) runs and the collana pay backend sends asynchronous server-to-server callbacks to your backend.
These callbacks are the authoritative confirmation that the payment was successfully processed. Your backend should use them to update order status, trigger fulfillment, etc.
TIP
Do not rely solely on the redirect to confirm a payment — always wait for the server-to-server callback from collana pay before treating the order as paid.
Stage 4 — onCancel (optional)
ts
onCancel?: (identity: ProviderIdentity) => voidFired when the user dismisses the payment dialog without completing the payment. If omitted, the cancellation is still tracked in telemetry.
Recommended actions: Restore any reserved inventory, show a user-friendly "payment was cancelled" message, and allow the user to try again.
In addition to firing onCancel in the browser, the collana pay backend sends an asynchronous server-to-server cancel callback to your backend, so order state stays in sync even if the browser is closed before onCancel runs.
Provider note: Amazon Pay redirects the buyer away from your page to complete checkout. If the buyer cancels there,
onCanceldoes not fire — instead, the collana pay backend sends the server-to-server cancel callback as usual.
Stage 5 — onError (optional)
ts
onError?: (identity: ProviderIdentity, error: PaymentError) => voidFired for provider SDK errors, network failures, or when onClick rejects. If omitted, errors are still tracked in telemetry. The PaymentError object contains:
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code |
message | string | Human-readable description |
providerProtocolType | string | Protocol where the error occurred |
paymentMethodType | string | Payment method where the error occurred |
Recommended actions: Log the error for monitoring, show a user-friendly error message, and allow the user to retry.
Because providers render via
Promise.allSettled, one provider'sonErrordoes not prevent other providers from rendering. Each failure triggers a separateonErrorcall.
