Skip to main content

Widget v3 – How to Build

The Purse widget allows you to quickly build a secure and dynamic payment interface using your configured experiences.
Although the Purse API can be used directly, the Widget Library greatly simplifies frontend integration across multiple PSPs.

Installation​

First, include the Purse Checkout script in your HTML page. This script loads all necessary components to initialize the checkout.

Using API V1

<script src="https://cdn.purse-sandbox.com/dropin/v3-latest/purse.js"></script>

Using API V2
You can recover the script from the widget url in the client session API response.

<script src="{widget_url}"></script>

Widget Purpose​

The widget orchestrates one or more payment providers and handles rendering, session management, and submission.
Even if your experience only includes a single payment method, the widget will handle it cleanly and consistently.

You’ll use the WidgetManager to build and mount the widgets associated with a payment session or wallet session.

Initializing the Widget Manager​

Using API V1​

const env = {
apiKey: "YOUR_API_KEY",
entityId: "YOUR_ENTITY_ID",
environment: "sandbox", // or 'production'
};

const manager = UpStreamPay.WidgetManager.buildForCredentials(env);
await manager.setPaymentSession(session);

Using API V2​

const widgetManager = await UpStreamPay
.WidgetManager
.buildForWidgetData(clientSession.widget.data);

The widget.data and widget.js_url values are returned by the /v2/client-sessions API.

Creating Widget Instances​

Once the manager is initialized, create the widgets you need:

const payment = await manager.createWidget({
name: "my_unique_payment_widget_name",
interface: "PAYMENT",
});

const button = await manager.createWidget({
name: "my_unique_pay_button_widget_name",
interface: "PAY_BUTTON",
});

await payment.mount("widget-payment");
await button.mount("widget-button");

Example Implementation​

let widgetManager;
let widgetPaymentContainer;
let widgetAmountContainer;
let widgetButtonContainer;

async function createWidgetManager() {
const credentials = {
apiKey: apiKey,
entityId: entityId,
environment: id,
};

widgetManager = UpStreamPay.WidgetManager.buildForCredentials(credentials);
await createPaymentWidget();
await createAmountWidget();
await createButtonWidget();
}

async function createButtonWidget() {
const button = await widgetManager.createWidget({ interface: 'PAY_BUTTON' });
await button.mount('widget-button');
}

async function createPaymentWidget() {
const widget = await widgetManager.createWidget({
interface: 'PAYMENT',
ui: {
layout: { name: 'ACCORDION' }
}
});
await widget.mount('widget-payment');
}

async function createAmountWidget() {
const widget = await widgetManager.createWidget({ interface: 'AMOUNT' });
await widget.mount('widget-amount');
}
<h1>Let's test Purse</h1>
<div class="main">
<div id="widget-payment" bind:this={widgetPaymentContainer} />
</div>
<div class="secondary">
<div id="widget-amount" bind:this={widgetAmountContainer} />
<div id="widget-button" bind:this={widgetButtonContainer} />
</div>
Information

Once your widget is displayed correctly, move on to the payment validation step.

Callbacks overrides​

Hook into the Validation Process​

You may want to execute logic before or after payment validation (e.g. saving an order in your system).
You can pass preValidate and postValidate callbacks to the widget manager.

widgetManager = await UpStreamPay.WidgetManager.buildForWidgetData(widgetData, [], {
preValidate: async () => console.log('Prevalidate working'),
postValidate: async () => console.log('PostValidate working'),
});
  • If preValidate throws an error or rejects, the validation is cancelled.
  • If postValidate fails, the validation still proceeds normally.

Redirection Mode​

By default, the widget will follow the redirection after the payment is validated. However, you can override this behavior by setting a redirectionHandler callback in the buildFor... method.

widgetManager.buildForCredentials({
hooks: {
redirectionHandler(redirectionParams: RedirectionHandlerParams) {
if (redirectionParams.type === 'redirection') {
window.location.href = redirectionParams.url;
} else if (redirectionParams.type === 'jsonFormSubmit') {
const form = document.createElement('form');
form.method = redirectionParams.method || 'POST';
form.action = redirectionParams.url;
if (redirectionParams.JSONPayload) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'payload';
input.value = redirectionParams.JSONPayload;
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
} else if (redirectionParams.type === 'htmlFormSubmit') {
const container = document.getElementById(redirectionParams.containerId);
if (container) {
const form = document.createElement('form');
form.method = 'POST';
form.action = redirectionParams.url;
form.innerHTML = redirectionParams.responseForm;
container.appendChild(form);
}
}
}
}
});

The following redirection types are supported:

type RedirectionHandlerParams =
| {
type: 'redirection';
url: string;
challengedInPopup?: true; // the 3DS challenge was already shown in a popup
}
| {
type: 'jsonFormSubmit';
url: string;
JSONPayload?: string;
method?: 'GET' | 'POST';
}
| { type: 'htmlFormSubmit'; containerId: string; responseForm: any };

In-browser 3D Secure challenge in a popup​

Some partners run their 3D Secure challenge in the browser instead of redirecting: the challenge is drawn in a modal inside the widget. If you embed the widget in a narrow iframe, that modal ends up cramped. Set delegate3DSChallenge to show the challenge in a popup instead. It works with or without redirectionHandler.

widgetManager.buildForCredentials({
hooks: {
delegate3DSChallenge: true,
redirectionHandler(redirectionParams: RedirectionHandlerParams) {
if (redirectionParams.type === 'redirection' && redirectionParams.challengedInPopup) {
// The challenge was already shown in a popup: follow the redirection in place.
window.location.href = redirectionParams.url;
return;
}
// ... other types
}
}
});

With the option on:

  • The widget opens a popup on a page it generates, which hosts the bank's authentication page. Once the buyer has authenticated, the popup closes by itself.
  • If the browser blocks the popup, the challenge shows in a dialog inside the widget instead. An iframe embedding the widget needs allow-popups if it is sandboxed.
  • If the buyer closes the popup before authenticating, the authentication fails and the widget follows the payment redirection.
  • The final redirection goes through redirectionHandler when you set one, as a redirection with challengedInPopup: true. Without a handler, the widget follows it in its own window.
  • When no challenge is needed, no popup opens and redirectionHandler receives the redirection as usual.

Do not open your own popup on the Pay click for this flow: the widget opens its own, and the buyer would get two windows. Applies to Lyra (PayZen, Sogecommerce, Systempay, Scellius) and Axepta card payments; other partners already redirect for their challenge.