Hooks
Hooks let you tap into the payment flow to add custom behavior like loading states, analytics tracking, or custom 3D Secure handling.
const checkout = await Purse.createHeadlessCheckout('your-session-data', {
onBeforeValidate: async (splits) => { /* your code */ },
onAfterValidate: async () => { /* your code */ },
redirectionHandler: async (data) => { /* your code */ }
});
For complete type definitions, see PurseHeadlessCheckoutHooks interface.
Show a Loading Indicator During Payment
Use onBeforeValidate and onAfterValidate to control UI state.
const checkout = await Purse.createHeadlessCheckout('your-session-data', {
onBeforeValidate: async () => {
// Disable submit button and show loader
document.querySelector('#pay-button').disabled = true;
document.querySelector('#loader').classList.remove('hidden');
},
onAfterValidate: async () => {
// Hide loader (payment succeeded)
document.querySelector('#loader').classList.add('hidden');
}
});
When onAfterValidate runs, the payment has already been processed. Errors here won't stop the payment.
Track Payment Events in Analytics
Send payment events to your analytics platform.
const checkout = await Purse.createHeadlessCheckout('your-session-data', {
onBeforeValidate: async (splits) => {
// Track payment start
gtag('event', 'begin_checkout', {
value: splits.reduce((sum, s) => sum + s.amount, 0) / 100,
currency: 'EUR'
});
},
onAfterValidate: async () => {
// Track payment success
gtag('event', 'purchase', { transaction_id: 'your-transaction-id' });
}
});
Validate Data Before Submitting
Block the payment if validation fails.
const checkout = await Purse.createHeadlessCheckout('your-session-data', {
onBeforeValidate: async () => {
// Check terms acceptance
const termsAccepted = document.querySelector('#terms-checkbox').checked;
if (!termsAccepted) {
throw new Error('Please accept terms and conditions');
}
}
});
Throwing an error in onBeforeValidate stops the payment. The API call won't be made.
Handle 3D Secure Authentication
Customize how 3D Secure authentication is displayed to users.
Default Behavior (Full Page Redirect)
const checkout = await Purse.createHeadlessCheckout('your-session-data', {
redirectionHandler: async ({ url, type, method, JSONPayload }) => {
if (type === 'jsonFormSubmit') {
// Create and auto-submit form (full page redirect)
const form = document.createElement('form');
form.method = method;
form.action = url;
const data = JSON.parse(JSONPayload || '{}');
Object.entries(data).forEach(([key, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = String(value);
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
}
});
Custom Behavior (Modal or iFrame)
const checkout = await Purse.createHeadlessCheckout('your-session-data', {
redirectionHandler: async ({ url, type }) => {
if (type === 'redirection') {
// Open 3DS in a centered popup window
const width = 500;
const height = 600;
const left = (screen.width - width) / 2;
const top = (screen.height - height) / 2;
window.open(
url,
'3DS Authentication',
`width=${width},height=${height},left=${left},top=${top}`
);
}
}
});
If 3D Secure is required, redirectionHandler runs instead and the user must complete authentication first. Plan your redirect flow accordingly.
Complete Example
Here's a full integration combining loading states, analytics, and 3D Secure handling:
const checkout = await Purse.createHeadlessCheckout('your-session-data', {
// Before payment: Validate and prepare UI
onBeforeValidate: async (splits) => {
// Validate terms acceptance
if (!document.querySelector('#terms').checked) {
throw new Error('Please accept terms and conditions');
}
// Update UI
document.querySelector('#pay-button').disabled = true;
document.querySelector('#loader').classList.remove('hidden');
// Track event
gtag('event', 'begin_checkout', {
value: splits.reduce((sum, s) => sum + s.amount, 0) / 100
});
},
// After payment: Cleanup and track success
onAfterValidate: async () => {
document.querySelector('#loader').classList.add('hidden');
gtag('event', 'purchase');
},
// Handle 3DS if required
redirectionHandler: async ({ url, type, method, JSONPayload }) => {
if (type === 'jsonFormSubmit') {
const form = document.createElement('form');
form.method = method;
form.action = url;
const data = JSON.parse(JSONPayload || '{}');
Object.entries(data).forEach(([key, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = String(value);
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
}
}
);
Best Practices
- Keep hooks fast: They run during the payment flow. Avoid slow operations.
- Use
async/await: Always return promises from hooks. - Validate in
onBeforeValidate: This is your last chance to stop a payment. - Handle errors gracefully: In
onAfterValidate, remember the payment already succeeded.
Hook Reference
| Hook | When | Blocks Payment | Use For |
|---|---|---|---|
onBeforeValidate | Before API call | ✅ Yes | Validation, loading states, analytics |
onAfterValidate | After payment succeeds | ❌ No | Cleanup, success tracking |
redirectionHandler | When 3DS needed | ❌ No | Custom 3DS UI |
See PurseHeadlessCheckoutHooks for parameter types and RedirectionHandlerParams for redirection types.