Skip to content

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.

ParameterDescription
identityContains providerProtocolType, paymentMethodType, and optionally id — identifies which button was clicked
optionsThe 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) => void

Fired 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, onCancel does 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) => void

Fired for provider SDK errors, network failures, or when onClick rejects. If omitted, errors are still tracked in telemetry. The PaymentError object contains:

FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description
providerProtocolTypestringProtocol where the error occurred
paymentMethodTypestringPayment 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's onError does not prevent other providers from rendering. Each failure triggers a separate onError call.

CollanaPay SDK Documentation