Headless Checkout Changelog
This release introduces Edenred Meal Voucher as a new payment method, offering buyers more flexibility at checkout. Key reliability improvements include preventing duplicate authorizations for Adyen Apple Pay, ensuring Google Pay remains active after double-clicks, and securing your payment forms by addressing potential data leaks. We've also enhanced how Lyra and Axepta 3DS challenges are presented and fortified the security of dynamically loaded external scripts.
Your buyers can now pay with Edenred Meal Vouchers. This new payment method uses a secondary authentication flow where buyers log into their MyEdenred account. Integrators can initiate this flow using the getSecondaryToken method.
// Initiate the Edenred authentication flow
const tokenResult = await headlessCheckout.getSecondaryToken('edenred', 'mealvoucher');
if (tokenResult && tokenResult.authorizeUrl) {
// Open the authorizeUrl in a new window or redirect the buyer
const authWindow = window.open(tokenResult.authorizeUrl, '_blank');
// After successful authentication, the buyer will be redirected back.
// Extract the 'code' from the URL and complete the flow.
const code = getCodeFromRedirectUrl(); // Implement your logic to get the code
const finalToken = await headlessCheckout.getSecondaryToken('edenred', 'mealvoucher', { code });
console.log('Edenred token:', finalToken);
}
The 3DS challenge flow for Lyra and Axepta now offers two primary improvements:
- Challenges can be hosted in a dedicated SDK-owned popup, preventing the ACS from being cramped within an iframe. If the browser blocks the popup, the challenge gracefully falls back to an in-document dialog.
- Integrators can delegate the 3DS challenge handling to their own redirection handler using the new
hooks.delegate3DSChallengeoption. This allows for fine-grained control over the challenge presentation, especially in embedded contexts. A newRedirectionHandler.hostsChallengeproperty indicates this delegation.
// Example: Delegating 3DS challenge handling
const checkout = createHeadlessCheckout({
// ... other configuration ...
hooks: {
redirectionHandler: {
hostsChallenge: true,
redirectToUrl: async (redirection) => {
if (redirection.challengedInPopup) {
// Handle the 3DS challenge form submission in your custom popup logic
// For example, open a new window, display the form, and wait for its result.
console.log('Handling 3DS challenge in custom popup:', redirection.jsonFormSubmit);
return Promise.resolve(); // Or resolve with a status
}
// Handle other redirections as usual
window.location.href = redirection.url;
}
}
}
});
To mitigate risks from potentially compromised API responses, the SDK now validates the hostname of dynamically loaded external resources (such as those for ACI, SDPX, Riverty, and Sequra). Before injecting scripts or iframes, the SDK checks that their src attribute points to a known and allowed partner domain. This prevents unexpected or malicious URLs from executing code within your payment origin. For Riverty, which uses merchant-owned subdomains, the hostname is validated for a valid shape. Sequra's identification iframe also now includes a sandbox attribute for additional security.
Previously, Adyen's Apple Pay component could trigger its own payment authorization while the SDK was simultaneously sending the token for validation via the /validate endpoint. This resulted in two authorizations for a single payment, leading to reconciliation issues. The component is now configured to delegate the payment submission entirely to the SDK's /validate call, ensuring only one authorization occurs per transaction and the Apple Pay sheet closes correctly based on the actual payment outcome.
Previously, when integrating Apple Pay via Adyen, card networks like CB and Mastercard might have been incorrectly greyed out in the Apple Pay sheet, even if supported by the merchant's Adyen account. This fix ensures that all accepted card networks are properly communicated to Apple Pay, allowing buyers to select their CB and Mastercard cards.
To prevent potential security vulnerabilities, the SDK now strictly verifies the source of messages received during the Axepta 3DS challenge. It will only process 3DS messages originating from the specific iframe it creates for the challenge, ignoring any messages from other potentially malicious frames on the page.
Previously, rapidly clicking the Google Pay button could lead to a This method can only be called one at a time error, which would fatally disable the payment method for the remainder of the session. The SDK now safely guards against re-entrant calls, ignoring subsequent clicks while the payment sheet is opening and ensuring the method remains available for retry.
If the PayPal SDK fails to open the authentication popup (e.g., due to a browser popup blocker), the method is no longer permanently disabled. The SDK now correctly classifies this error as recoverable, allowing buyers to retry the payment, for both PayPal Wallet and BNPL.
holderName and prevent excessive paymentTokens emissions.When using token.getHostedFields().render() for saved cards, the card form no longer incorrectly requires the holderName field, which previously caused MissingTargetFromConfig errors and prevented the CVV iframe from booting. Additionally, the SDK now correctly subscribes to payment method state changes, preventing multiple identical paymentTokens or paymentMethods emissions when a method is deactivated.
init and validate calls now provide more detailed error diagnostics, and validate() throws on 4xx responses.This release includes several security and correctness fixes for the card form:
- Sensitive card expiration date (
expDate) values are now obfuscated in logs and monitoring events, similar toholderName, preventing accidental data exposure. - Raw
holderNamevalues are no longer included inCHECKOUT_PRE_VALIDATION_FAILEDpayloads to analytics systems. - The
validateUi()method now correctly maps validation statuses to the card form's internal field keys (cardNumber,holderName), ensuring accurate feedback for card validation.
For integrators using Secure Fields, the event dictionary for Secure Fields (covering events like FIELD_BLUR, FIELD_FOCUS, and various log codes) has been updated in the documentation, providing clarity on event payloads and meanings.
To prevent payment rejections due to formatting issues, gift card numbers (for secondaries like Ogloba, Buybox, Illcado, Maxxing, ANCV) now automatically remove leading, trailing, and internal whitespace. This normalization occurs at both the input field level (for a cleaner buyer experience) and when constructing the payment payload, ensuring only clean, validated numbers reach the API.
Unhandled cross-origin errors (e.g., Script error.) are now properly reported with their actual message and stack trace instead of a generic message. Additionally, to prevent log flooding, these errors are capped to report only once per page, providing clearer and more actionable insights into unexpected client-side issues.
An important fix ensures that Mastercard and Cartes Bancaires cards are now correctly displayed and selectable within the Apple Pay sheet when processing payments via Adyen.
A critical fix for Adyen Apple Pay ensures that payments now correctly result in a single authorization, preventing previous issues with duplicate charges and improving payment reconciliation.
This release introduces new payment integrations for SeQura and Lyra white-label credit cards, alongside crucial updates to improve the reliability and security of payment forms. Buyers will experience more accurate Apple Pay availability, and card forms will adapt better to different screen sizes.
holderName input padding restored; American Express card forms use correct labels and placeholders.This release significantly enhances payment reliability and debugging capabilities for your checkout. We've introduced more detailed logging for payment failures and ensured that uncaught SDK errors are properly reported. Additionally, buyers will benefit from improved stability for Apple Pay and PayPal, and a more secure checkout element that no longer exposes sensitive data in the DOM.
The HEADLESS_CHECKOUT_PAYMENT_ERROR event now includes a results array within its payload. This array provides per-attempt details for failed payments, including the payment method, partner, status, plugin_status, and a capped description of the failure. This helps you distinguish between different failure reasons.
interface HeadlessCheckoutPaymentErrorEventDetail {
// ... other existing fields
results: Array<{
method?: string;
partner?: string;
status?: string;
plugin_status?: string;
description?: string;
}>;
}
element.on('HEADLESS_CHECKOUT_PAYMENT_ERROR', (event) => {
console.error('Payment failed:', event.detail.results);
});
/init request.countryCode is now correctly derived from the locale, resolving previous payment failures. Payment sheet failures are also now properly reported instead of being silent.Previously, the SDK was swallowing its own unhandled promise rejections and errors, making it difficult to detect issues. Now, uncaught errors originating from the SDK will correctly appear in your browser's console and any window.onerror or window.onunhandledrejection handlers you have implemented. This provides better visibility into potential client-side issues.
/validate calls and incorrect redirections.The SDK now guards against re-entrant calls to submitPayment. This prevents buyers from accidentally initiating multiple validations, which previously could lead to unnecessary API calls and being redirected to an error page even after a successful payment. A new HEADLESS_CHECKOUT_SUBMIT_BLOCKED event is dispatched when a submission is blocked.
<purse-partner-ui> element.The PaymentElement.on() method now returns an unsubscribe function. Integrators should use this function to clean up event listeners when the element is unmounted to prevent memory leaks and ensure correct behavior across session updates.
const unsubscribe = element.on('ready', () => {
// Handle ready event
});
// When the component unmounts or the element is no longer needed
unsubscribe();
You can now pass extraProps to customize input attributes for hosted fields, allowing granular control over keyboard types and other HTML input properties.
interface HostedFieldOptions {
extraProps?: {
inputmode?: string;
autocomplete?: string;
autocorrect?: 'on' | 'off';
spellcheck?: boolean;
// ... other HTMLInputElement attributes
};
}
The PayPal payment flow is now more robust in webview environments.
We've added Stripe Google Pay as a new payment method and upgraded the PayPal SDK to V6 for improved performance and reliability. You now have more control over the display of the CVV field for tokenized payments, alongside several stability enhancements.
merchant_specifics.pay_token_no_cvv setting.The SDK now correctly respects this setting, ensuring the CVV field is hidden when configured, and the payment element's hasUI() method accurately reflects this state.
onOptionsUpdate lifecycle hook.The new onOptionsUpdate hook allows plugins to dynamically update their UI or behavior when the Payment Element's options change at runtime. This is particularly useful for payment methods with imperatively built UIs, such as Google Pay.
interface Plugin {
// ... other methods
onOptionsUpdate?: (context: Context) => Promise<void>;
}
LOYALTY_ACCOUNT_NOT_FOUND error.Previously, these were reported as a generic FAILED_TO_INIT_SECONDARY_TOKEN.
Previously, tokens with a disabled CVV would not correctly fulfill, preventing the payment from being submitted. The SDK now correctly handles merchant_specifics.pay_token_no_cvv and hostedForm.noCVV to hide the CVV input and ensure the token form validates correctly.
Adds Adyen Apple Pay and a normalized partnerError payload. Apple Pay and PayPal initialization failures now surface instead of leaving a dead button.
partnerError payloads are normalized and no longer typed against partner SDK internals.Input in the Purse Vault card form is preserved during a session update unless the payment method configuration itself changes.
apiPaths passed to a bundle that enforces its own API URLs now log a console warning instead of being silently ignored.SDK errors and init failures — NOT_AUTHORIZED, for example — previously left the button active or froze the checkout silently.
onAmountUpdate calls no longer exhaust the session step budget and reach SESSION_MAX_STEPS_REACHED.The build target is pinned explicitly again. A dependency bump had picked up a narrower default that stopped downleveling ES2022 syntax.
Validation error keys are renamed. Favorite token management moves to a dedicated endpoint, and getPaymentElement() warns instead of crashing on secondary methods.
*CannotBeEmpty* to *Required*.low impactMigration guide
The FieldValidationErrors enum and the matching configuration fields are renamed. Deprecated cannotBeEmpty keys still resolve, but will be removed in a future major.
// Before
const config = { cardHolderNameCannotBeEmptyError: 'Cardholder name is mandatory.' };
// After
const config = { cardHolderNameRequiredError: 'Cardholder name is mandatory.' };
getPaymentElement() on a secondary method warns and returns undefined instead of throwing an opaque TypeError.Secondary methods — gift cards, vouchers — use getSecondaryToken() or take().
paymentMethod not provided.Tokens from a previous initialization are cleared. CHECKOUT_API_VALIDATION_SUCCEEDED now carries a per-partner-method statuses map.
interface CheckoutApiValidationSucceededEventPayload {
statuses: Record<string, { state: string; code?: string; description?: string }>;
}
plugin_result.logs is absent.PluginInitFailed or partnerError instead of being swallowed.save_token: false overrides any conflicting save_token_mode.formDataChanged is logged only once every field is valid, and holderName masking drops the last character; Corrected event categories for VALIDATION_SUCCESS, VALIDATION_FAILED, PAYMENT_ERROR, EXPIRED_SESSION, and CLIENT_HOOK_ERROR.Stripe Apple Pay is now available. The getPaymentElement() method is now more flexible, allowing optional method and partner parameters, and provides clearer error messages for incompatible hosted fields requests. Additionally, payment tokens now expose isFavorite, setAsFavorite(), and createdAt properties for enhanced management.
Integrators can now offer Apple Pay via Stripe. The button height is automatically clamped to Stripe's supported range of 40-55px as per Stripe's constraints.
getPaymentElement() method now features significantly improved resolution logic and optional parameters.The method and partner parameters are now optional for getPaymentElement():
- When
methodis omitted, the SDK automatically selects the first hosted fields-compatible primary method ifhostedFieldsis requested, or the first primary method in the session otherwise. - When
partneris omitted, the SDK uses the first primary method matching the given method name across all partners. A warning is emitted if multiple partners expose the same method. - Requesting
hostedFieldsfor an incompatible payment method will now throw aMETHOD_DOES_NOT_SUPPORT_HOSTED_FIELDSerror instead of silently failing.
isFavorite and a setAsFavorite() method.The PurseHeadlessCheckoutPrimaryToken type now includes:
interface PurseHeadlessCheckoutPrimaryToken {
isFavorite: Readable<boolean>;
setAsFavorite(): Promise<void>;
// ... other properties
}
Integrators can use these to display and manage a buyer's preferred tokens.
supportsHostedFields boolean property.The PurseHeadlessCheckoutPrimaryMethod and PurseHeadlessCheckoutPrimaryToken types now include:
interface PurseHeadlessCheckoutPrimaryMethod {
supportsHostedFields: boolean;
// ... other properties
}
interface PurseHeadlessCheckoutPrimaryToken {
supportsHostedFields: boolean;
// ... other properties
}
This property indicates whether the method or token is compatible with hosted fields.
createdAt timestamp.The PaymentToken type now includes a createdAt: string property, allowing integrators to sort or display tokens by their creation date.
When the Ingenico API returns incomplete data during initialization, the widgets now gracefully fall back to default payment networks (e.g., Mastercard/Visa) and continue to function, preventing a complete checkout blockage.
This fix ensures that payment methods declaring hosted fields support in their plugin are correctly identified, even when the session vault doesn't explicitly state hosted-fields support.
xPayButton) are now correctly forwarded by getPaymentElement().Integrators can now use the xPayButton property in PurseHeadlessCheckoutGetPaymentElementOptions to customise the appearance of Apple Pay and Google Pay buttons.
checkout.getPaymentElement({
partner: 'adyen',
method: 'googlepay',
xPayButton: {
google: {
buttonColor: 'black',
buttonType: 'buy'
}
}
});
This ensures that if your backend defines a custom template for a payment method, the generic plugin will be used even if a partner-specific plugin exists for that method.
methodSelected and methodUnselected removed from PaymentElementEventName. For payment methods that open a modal (Apple Pay, Google Pay, Lyra), replace with modalIn and modalOut.// Before
element.on('methodSelected', handler)
// After
element.on('modalIn', handler)
additionalAssets: PurseHeadlessCheckoutPaymentItemBase now exposes additionalAssets: CardSchemeAsset[] — one entry per supported card scheme (URL + label). iconUrl is still present.// method.additionalAssets: Array<{ url: string; label: string }>
method.additionalAssets // [{ url: 'https://...', label: 'Visa' }, ...]
hideHolderName: New option in HostedFieldsOptions to hide the cardholder name field.method.setOptions({
hostedForm: { hideHolderName: true },
});
disableMaxWidth: New option to override the button's default max-width constraint.method.setOptions({
xPayButton: { paypal: { disableMaxWidth: true } },
});
getElementInstance() internally, fixing silent skips when rendering via getHostedFields().item.name instead of the masked card number.This release enhances the reliability of Google Pay payments for our Ingenico/Worldline merchants by correcting configuration defaults, preventing silent tokenization failures. Additionally, we've fixed an issue ensuring that payment tokens saved in the wallet display their correct names.
name field for saved payment tokens and ensures the editTokenName API path is correct.We've added new capabilities for a smoother payment experience and enhanced developer tooling. Buyers can now customize their PayPal button appearance, while developers benefit from a streamlined way to create PaymentElement instances and readily access payment method icons.
The new disableMaxWidth option can be passed in XPayButtonUIOptions['paypal'] to prevent the PayPal button from expanding to its container's full width.
interface XPayButtonUIOptions {
paypal?: {
disableMaxWidth?: boolean;
// ... other PayPal button options
};
}
// Example usage:
checkout.getPaymentElement({
partner: 'paypal',
method: 'paypal',
paypal: {
disableMaxWidth: true,
},
});
PaymentElement directly from the checkout instance.A new shortcut method, checkout.getPaymentElement(), simplifies the creation and rendering of a single payment method's UI.
// Before: More verbose for a single method
// const checkout = new PurseHeadlessCheckout(...);
// const paymentMethods = await checkout.getPaymentMethods();
// const specificMethod = paymentMethods.find(
// (pm) => pm.partner === 'ingenico' && pm.method === 'creditcard'
// );
// if (specificMethod) {
// const el = specificMethod.getPaymentElement({ theme: {} });
// el.appendTo('#container');
// }
// After: Simplified approach
const el = checkout.getPaymentElement({
partner: 'ingenico',
method: 'creditcard',
theme: { /* ... */ },
hostedForm: { /* ... */ },
hostedFields: { /* ... */ },
});
el.on('ready', () => console.log('PaymentElement is ready.'));
el.appendTo('#container');
iconUrl.Integrators can now easily display official icons for payment methods and secondary tokens by accessing the new iconUrl property.
interface PaymentMethod {
// ... existing properties
iconUrl: string | null;
}
interface BuiltSecondaryToken {
// ... existing properties
iconUrl: string | null;
}
// Example usage:
const paymentMethods = await checkout.getPaymentMethods();
paymentMethods.forEach((method) => {
if (method.iconUrl) {
console.log(`Icon for ${method.name}: ${method.iconUrl}`);
}
});
Dedicated mappers ensure accurate data extraction for secondary tokens, including those from partners like Illicado, Easy2play giftcard, Mybeezbox, and Ogloba. This includes migrating from deprecated payment_data.card to payment_data.cards[0] for card-based secondary tokens.
currencyCode has been removed from BuiltSecondaryToken, PurseHeadlessCheckoutSecondaryToken, and PartialSecondaryToken types. Integrators should now retrieve the currency directly from the payment session, which serves as the single source of truth.
widget_data when available.This release expands payment options by introducing Apple Pay and Google Pay for Sogecommerce, PayPal BNPL, and Younited Installments. We've also enhanced Oney card support to correctly collect birthdates for private cards and added a noCVV mode for more flexible card form integrations.
This prevents 'ghost sessions' with Alma when buyers close the popup prematurely, ensuring session consistency between Purse and Alma.
For Oney private cards, the card form dynamically adapts to collect the buyer's birthdate instead of the CVV. This ensures accurate data collection for Oney payments.
noCVV mode for card forms.Integrators can now configure the card form to optionally collect the CVV by setting the noCVV flag on PaymentElement initialization. This allows for use cases where CVV collection is not always required for specific card brands or payment flows.
const paymentElement = purse.initPaymentElement({
// ... other config
card: {
noCVV: true, // CVV field becomes optional
},
});
Payment methods compatible with OFF_SESSION registrations are now correctly displayed and activated, allowing for payments even with zero-amount initial requests.
onBeforeValidate hook.This resolves an issue where the CVV field could be duplicated or misbehave when quickly switching between a saved card token form and a new card entry form, especially on slow networks (SDK-11545).
SCREAMING_SNAKE_CASE.This prevents unknown plugins from occupying slots in xPay displays when a template is not properly configured (SDK-11514).