Skip to main content

Migrate to Headless Checkout

Use Headless Checkout when you want to replace Widget V3 with a custom checkout UI and keep control over the flow.

Choose Headless if

  • you want to own the full checkout layout
  • you need custom method selection screens
  • you want to handle split payments or secondary methods in your own UI
  • you need more control than Drop-in exposes

What changes from Widget V3

Widget V3Headless Checkout
WidgetManager + createWidget()createHeadlessCheckout(session)
built-in widget layoutsyour own UI around reactive stores
widget config for assets and translationsyour app handles presentation
widget lifecycle and navigationyour app controls the flow
widget-owned pay flowyour app wires its button to isPaymentFulfilled + submitPayment()

Migration steps

  1. Keep the client session flow and pass clientSession.widget.data to the SDK.
  2. Replace each Widget V3 screen with your own UI.
  3. Render available payment methods from paymentMethods.subscribe().
  4. Mount the selected method with getPaymentElement() into your container.
  5. Use isPaymentFulfilled to control your pay button.
  6. Call submitPayment() from that button when the customer confirms payment.
const checkout = await Purse.createHeadlessCheckout(clientSession.widget.data);
const methodsList = document.getElementById('methods');
const form = document.getElementById('form');
const payButton = document.getElementById('pay-button');

checkout.paymentMethods.subscribe((methods) => {
renderMethodList(methods);
});

function renderMethodList(methods) {
methodsList.innerHTML = '';

methods.forEach((method) => {
const item = document.createElement('button');
item.type = 'button';
item.textContent = `${method.partner} - ${method.method}`;
item.addEventListener('click', () => selectMethod(method));
methodsList.appendChild(item);
});
}

function selectMethod(method) {
form.innerHTML = '';
const element = method.getPaymentElement();
element.appendTo(form);
}

checkout.isPaymentFulfilled.subscribe((isFulfilled) => {
payButton.disabled = !isFulfilled;
});

payButton.addEventListener('click', async () => {
await checkout.submitPayment();
});

What you may need to rebuild

  • method cards, tabs, accordions, or custom filters
  • payment summary blocks
  • split-payment UI
  • custom loading and error states
  • any widget-specific navigation
If you use split payments

Headless Checkout is the right path when Widget V3 used vouchers, gift cards, or other secondary methods that need custom UI treatment.

Next steps