Implement callbacks to customise your Apple Pay payment flow for Web.
Components emit events based on user interaction or validation. You can use these to implement callback functions, which allow you to inject your own business logic and user experience customisations into the payment flow at critical moments. They ensure that while the SDK handles the complex technical aspects of payment processing, you retain full control over the customer experience and can seamlessly integrate payments into your broader business workflows and systems.
Callbacks enable you to:
- Validate business rules before payments proceed.
- Display custom error, failure, or success messages.
- Tailor user interfaces to match your brand's look and feel.
- Integrate with your own systems for fraud detection or customer management.
- Control exactly how your customers experience both successful and failed transactions.
- Handle Apple Pay specific browser and device requirements.
The Apple Pay component requires an SDK initialisation callback to gather shopper information during transaction processing:
onGetShopper(required): Called to retrieve current shopper data (ID, email, name, etc.) for transaction submission to the backend.
This callback ensures the Apple Pay component always uses the latest customer data from your application state, forms, or APIs when processing payments.
All Apple Pay-specific events are optional and can be mixed and matched based on your business needs.
The SDK initialisation callback (onGetShopper) works alongside Apple Pay-specific callbacks to provide a complete payment experience. SDK callbacks handle backend transaction data, while Apple Pay callbacks handle the payment sheet interactions.
This callback is triggered after the customer authorises the payment with Touch ID, Face ID, or passcode, providing transaction identifiers.
You can use it to:
- Retrieve full authorisation result from your backend using the transaction identifiers.
- Redirect customers to a success page with order confirmation.
- Update stock levels for purchased items.
- Send order confirmation emails to customers.
- Record successful transactions for business intelligence.
| Event data | Description |
|---|---|
dataobject | Object containing transaction identifiers. |
data.merchantTransactionIdstring | Your unique identifier for the transaction. Use this to retrieve full authorisation details from the Unity backend. |
data.systemTransactionIdstring | The unique PXP system transaction identifier. |
onPostAuthorisation: async (data) => {
console.log('Payment authorisation completed');
console.log('Merchant Transaction ID:', data.merchantTransactionId);
console.log('System Transaction ID:', data.systemTransactionId);
// Get authorisation result from merchant backend
const authorisationResult = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
if (authorisationResult.status === 'Authorised') {
// Update inventory
await updateInventory(data.merchantTransactionId);
// Send confirmation email
await sendConfirmationEmail(
authorisationResult.customerEmail,
data.merchantTransactionId
);
// Track analytics
trackPurchase(data.merchantTransactionId, authorisationResult.amount);
// Redirect to success page
window.location.href = `/payment-success?txn=${data.merchantTransactionId}`;
} else if (authorisationResult.status === 'Declined') {
console.error('Payment declined:', authorisationResult.reason);
showErrorMessage('Payment declined. Please try again or use a different payment method.');
} else {
console.error('Payment exception:', authorisationResult.reason);
showErrorMessage('Payment processing error. Please contact support.');
}
}This callback is triggered before payment authorisation, allowing you to provide authorisation configuration or control whether to proceed.
You can use it to:
- Update the authorisation decision in your backend.
- Integrate with Kount or other fraud detection services.
- Perform AVS (Address Verification System) checks.
- Apply business rules based on transaction amount or customer history.
- Check product availability before processing payment.
- Control whether to proceed with authorisation by returning null.
After evaluating transaction details, use the Modify session API to update the authorisation decision on your backend before returning from this callback. Set "authorisation": true in the session data to proceed with the transaction.
This callback receives no parameters.
To proceed with authorisation:
onPreAuthorisation: async () => {
// Perform pre-payment validation
const deviceSessionId = await getKountSessionId();
const isHighRisk = await checkCustomerRiskProfile();
const customerTier = await getCustomerTier();
// Get billing address if AVS is enabled
const billingAddress = await getBillingAddress();
// Update authorisation decision to true in backend via Modify Session API
await updateSessionAuthorisationDecision(true);
return {
addressVerification: billingAddress ? {
countryCode: billingAddress.countryCode,
houseNumberOrName: billingAddress.address,
postalCode: billingAddress.postalCode,
city: billingAddress.city,
state: billingAddress.state
} : undefined,
riskScreeningData: {
deviceSessionId: deviceSessionId,
performRiskScreening: true,
customData: {
customerTier: customerTier,
orderType: 'ecommerce',
previousTransactionCount: await getPreviousTransactionCount(),
riskScore: isHighRisk ? 'high' : 'low'
}
}
};
}If you don't want to proceed with authorisation:
onPreAuthorisation: async () => {
// Check if authorisation should proceed
const shouldProceed = await checkAuthorizationDecision();
if (!shouldProceed) {
// Update authorisation decision to false in backend via Modify Session API
await updateSessionAuthorisationDecision(false);
// Stop the authorisation process
return null;
}
// Update authorisation decision to true in backend via Modify Session API
await updateSessionAuthorisationDecision(true);
// Return authorisation configuration
return {
riskScreeningData: {
performRiskScreening: true
}
};
}This callback is triggered when an error occurs during the Apple Pay payment process.
You can use it to:
- Log errors for debugging and monitoring.
- Display user-friendly error messages
- Offer alternative payment methods.
- Implement automatic retry for transient errors.
| Parameter | Description |
|---|---|
errorError | The error object containing details about what went wrong. |
error.messagestring | A human-readable error description. |
error.namestring | The error type. |
error.codestring | The error code for programmatic handling. |
error.stackstring | The stack trace for debugging. |
onError: (error) => {
console.error('Apple Pay error:', error);
// Log error for debugging
logError('apple-pay-error', {
message: error.message,
code: error.code,
timestamp: new Date().toISOString()
});
// Handle different error types
if (error.message.includes('User cancelled')) {
// Silent handling - user intentionally cancelled
return;
} else if (error.message.includes('Network')) {
showUserMessage('Network error. Please check your connection and try again.');
// Offer offline payment options
} else if (error.message.includes('Merchant validation')) {
showUserMessage('Payment system temporarily unavailable. Please try again later.');
// Alert support team
notifySupport('Apple Pay merchant validation failed');
} else {
showUserMessage('Payment failed. Please try again or use a different payment method.');
// Show alternative payment options
showAlternativePaymentMethods();
}
}This callback is triggered when the customer cancels the Apple Pay payment flow.
You can use it to:
- Track cancellation rates for conversion optimisation.
- Show helpful messages or alternative options.
- Save the customer's cart for later completion.
- Trigger email campaigns for abandoned checkouts.
| Parameter | Description |
|---|---|
errorError | Error object indicating cancellation reason (optional). |
error.messagestring | The cancellation reason. |
error.codestring | The cancellation code, for tracking purposes. |
onCancel: (error) => {
console.log('Apple Pay cancelled:', error);
// Track cancellation for analytics
trackEvent('apple-pay-cancelled', {
reason: error?.message || 'unknown',
timestamp: new Date().toISOString(),
cartValue: getCurrentCartValue()
});
// Preserve cart for later
saveCartForLater();
// Show helpful message
showMessage('No worries! Your items are saved. You can complete your purchase anytime.', 'info');
// Offer alternatives
setTimeout(() => {
showAlternativePaymentOptions();
}, 2000);
// Optional: Trigger abandoned cart email sequence
scheduleAbandonedCartEmail(customer.email, 30); // 30 minutes delay
}This callback is triggered when the customer selects or changes their shipping address in the Apple Pay sheet. Use this to calculate shipping costs and validate delivery availability.
You can use it to:
- Calculate shipping costs based on distance, weight, and destination.
- Verify addresses against postal service databases.
- Update tax rates based on the shipping destination.
- Show available delivery options for the location.
When the customer changes their shipping address, use the Modify session API to update the transaction amount on your backend. Update the amounts object with the new currencyCode and transactionValue to reflect updated shipping costs and taxes.
| Parameter | Description |
|---|---|
contactApplePayPaymentContact | The customer's shipping contact information. |
contact.postalAddressobject | The shipping address details. |
contact.postalAddress.streetstring | The street address. |
contact.postalAddress.citystring | The city name. |
contact.postalAddress.statestring | The state or province. |
contact.postalAddress.postalCodestring | The ZIP or postal code. |
contact.postalAddress.countrystring | The country name. |
contact.postalAddress.countryCodestring | The ISO country code. |
contact.nameobject | The contact name. |
contact.name.givenNamestring | The first name. |
contact.name.familyNamestring | The last name. |
contact.phoneNumberstring | The phone number. |
contact.emailAddressstring | The email address. |
onShippingContactSelected: async (contact) => {
console.log('Shipping contact selected:', contact);
try {
// Validate shipping address
const isValidAddress = await validateAddress(contact.postalAddress);
if (!isValidAddress) {
return {
errors: [{
code: 'shippingContactInvalid',
contactField: 'postalAddress',
message: 'Please enter a valid shipping address'
}]
};
}
// Calculate shipping and tax
const shippingCost = await calculateShipping(contact.postalAddress);
const taxAmount = await calculateTax(contact.postalAddress, baseAmount);
const newTotal = baseAmount + taxAmount + shippingCost;
// Get available shipping methods
const shippingMethods = await getShippingMethods(contact.postalAddress);
return {
newTotal: {
label: 'Total',
amount: newTotal.toFixed(2)
},
newLineItems: [
{ label: 'Subtotal', amount: baseAmount.toFixed(2) },
{ label: 'Tax', amount: taxAmount.toFixed(2) },
{ label: 'Shipping', amount: shippingCost.toFixed(2) }
],
newShippingMethods: shippingMethods
};
} catch (error) {
console.error('Shipping calculation failed:', error);
return {
errors: [{
code: 'shippingContactInvalid',
message: 'Unable to calculate shipping. Please try again.'
}]
};
}
}This callback is triggered when the customer selects a different shipping method in the Apple Pay sheet.
You can use it to:
- Update the total price based on the shipping method selection.
- Show the expected delivery dates for the selected method.
- Highlight benefits of premium shipping options.
- Check real-time availability for express options.
When the customer selects a different shipping method, use the Modify session API to update the transaction amount on your backend. Update the amounts object with the new currencyCode and transactionValue to reflect the selected shipping method cost.
| Parameter | Description |
|---|---|
methodApplePayShippingMethod | The selected shipping method. |
method.identifierstring | A unique identifier for the shipping method. |
method.labelstring | The display name (e.g., "Standard Shipping"). |
method.detailstring | Additional details (e.g., "5-7 business days"). |
method.amountstring | The shipping cost as a decimal string. |
onShippingMethodSelected: async (method) => {
console.log('Shipping method selected:', method);
// Update total based on selected shipping method
const baseAmount = 20.00;
const tax = 5.00;
const shippingCost = parseFloat(method.amount);
// Add insurance for express shipping
let insurance = 0;
if (method.identifier === 'express' && baseAmount > 50) {
insurance = 2.99;
}
// Calculate carbon offset for eco-conscious customers
const carbonOffset = method.identifier === 'standard' ? 0.50 : 0;
const newTotal = baseAmount + tax + shippingCost + insurance + carbonOffset;
// Track shipping method selection
trackEvent('shipping-method-selected', {
method: method.identifier,
cost: shippingCost,
orderValue: baseAmount
});
return {
newTotal: {
label: 'Total',
amount: newTotal.toFixed(2)
},
newLineItems: [
{ label: 'Subtotal', amount: baseAmount.toFixed(2) },
{ label: 'Tax', amount: tax.toFixed(2) },
{ label: `Shipping (${method.label})`, amount: shippingCost.toFixed(2) },
...(insurance > 0 ? [{ label: 'Shipping Insurance', amount: insurance.toFixed(2) }] : []),
...(carbonOffset > 0 ? [{ label: 'Carbon Offset', amount: carbonOffset.toFixed(2) }] : [])
]
};
}This callback is triggered when the customer changes their payment method (different card) within the Apple Pay sheet.
You can use it to:
- Apply different fees based on the card type.
- Offer different rewards based on the payment method.
- Apply additional verification for high-risk cards.
- Route payments through optimal payment processors.
| Parameter | Description |
|---|---|
paymentMethodApplePayPaymentMethod | The selected payment method. |
paymentMethod.displayNamestring | The card display name (e.g., "Visa ••••1234"). |
paymentMethod.networkstring | The card network (visa, masterCard, amex, discover). |
paymentMethod.typestring | The card type. |
paymentMethod.paymentPassobject | The card details. |
paymentMethod.paymentPass.primaryAccountIdentifierstring | The primary account identifier. |
paymentMethod.paymentPass.primaryAccountNumberSuffixstring | The last four digits of the card. |
paymentMethod.paymentPass.deviceAccountIdentifierstring | The device-specific account ID. |
paymentMethod.paymentPass.deviceAccountNumberSuffixstring | The device account suffix. |
onPaymentMethodSelected: async (paymentMethod) => {
console.log('Payment method selected:', paymentMethod);
const baseAmount = 20.00;
const tax = 5.00;
let processingFee = 0;
let reward = 0;
// Apply different fees based on card type
if (paymentMethod.type === 'credit') {
processingFee = 2.99; // Credit card processing fee
} else if (paymentMethod.type === 'debit') {
processingFee = 0.99; // Lower fee for debit
}
// Offer rewards for specific networks
if (paymentMethod.network === 'amex') {
reward = 1.00; // American Express cashback offer
}
// Track payment method selection
trackEvent('payment-method-selected', {
network: paymentMethod.network,
type: paymentMethod.type,
lastFour: paymentMethod.paymentPass?.primaryAccountNumberSuffix
});
const newTotal = baseAmount + tax + processingFee - reward;
return {
newTotal: {
label: 'Total',
amount: Math.max(0, newTotal).toFixed(2)
},
newLineItems: [
{ label: 'Subtotal', amount: baseAmount.toFixed(2) },
{ label: 'Tax', amount: tax.toFixed(2) },
...(processingFee > 0 ? [{ label: `${paymentMethod.type} Fee`, amount: processingFee.toFixed(2) }] : []),
...(reward > 0 ? [{ label: `${paymentMethod.network} Reward`, amount: `-${reward.toFixed(2)}` }] : [])
]
};
}This callback is triggered when the customer enters or changes a coupon code in the Apple Pay sheet (available on supported Safari versions).
You can use it to:
- Apply percentage or fixed amount discounts.
- Validate customer-specific coupon codes.
- Track effectiveness of marketing campaigns.
- Apply discounts to clear specific inventory.
After validating the coupon code, use the Modify session API to update the transaction amount on your backend. Update the amounts object with the new currencyCode and transactionValue to reflect the applied discount.
| Parameter | Description |
|---|---|
couponCodestring | The coupon code entered by the customer. |
onCouponCodeChanged: async (couponCode) => {
console.log('Coupon code entered:', couponCode);
try {
// Validate coupon code
const discount = await validateCouponCode(couponCode);
if (discount.valid && !discount.expired) {
const baseAmount = 20.00;
const tax = 5.00;
// Calculate discount amount
let discountAmount = 0;
if (discount.type === 'percentage') {
discountAmount = baseAmount * (discount.value / 100);
} else if (discount.type === 'fixed') {
discountAmount = discount.value;
}
// Apply minimum purchase requirement
if (discount.minimumPurchase && baseAmount < discount.minimumPurchase) {
return {
errors: [{
code: 'couponCodeInvalid',
message: `Minimum purchase of $${discount.minimumPurchase} required`
}]
};
}
// Cap discount at maximum amount
discountAmount = Math.min(discountAmount, discount.maxDiscount || discountAmount);
const newTotal = Math.max(0, baseAmount + tax - discountAmount);
// Track coupon usage
trackEvent('coupon-applied', {
code: couponCode,
discountType: discount.type,
discountAmount: discountAmount,
orderValue: baseAmount
});
return {
newTotal: {
label: 'Total',
amount: newTotal.toFixed(2)
},
newLineItems: [
{ label: 'Subtotal', amount: baseAmount.toFixed(2) },
{ label: 'Tax', amount: tax.toFixed(2) },
{ label: `Discount (${couponCode})`, amount: `-${discountAmount.toFixed(2)}` }
]
};
} else {
// Handle invalid or expired coupon
const errorMessage = discount.expired
? 'This coupon code has expired'
: 'Invalid coupon code';
return {
errors: [{
code: 'couponCodeInvalid',
message: errorMessage
}]
};
}
} catch (error) {
console.error('Coupon validation failed:', error);
return {
errors: [{
code: 'couponCodeInvalid',
message: 'Unable to validate coupon code. Please try again.'
}]
};
}
}The contact object contains customer address and contact information:
interface ApplePayContact {
postalAddress?: {
street?: string;
city?: string;
state?: string;
postalCode?: string;
country?: string;
countryCode?: string;
};
name?: {
givenName?: string;
familyName?: string;
};
phoneNumber?: string;
emailAddress?: string;
}The payment method object contains information about the selected payment card:
interface ApplePayPaymentMethod {
displayName?: string;
network?: string;
type?: 'debit' | 'credit' | 'prepaid' | 'store';
paymentPass?: {
primaryAccountIdentifier?: string;
primaryAccountNumberSuffix?: string;
deviceAccountIdentifier?: string;
deviceAccountNumberSuffix?: string;
};
}Event callbacks can return update objects to modify the Apple Pay sheet:
interface ApplePayUpdateResponse {
newTotal?: {
label: string;
amount: string;
};
newLineItems?: Array<{
label: string;
amount: string;
}>;
newShippingMethods?: Array<{
identifier: string;
label: string;
detail?: string;
amount: string;
}>;
errors?: Array<{
code: string;
contactField?: string;
message: string;
}>;
}Event callbacks should handle errors gracefully and provide appropriate feedback to customers:
const applePayConfig = {
onShippingContactSelected: async (contact) => {
try {
// Validate shipping address
const isValid = await validateShippingAddress(contact.postalAddress);
if (!isValid) {
return {
errors: [{
code: 'shippingContactInvalid',
contactField: 'postalAddress',
message: 'We cannot ship to this address'
}]
};
}
// Calculate shipping
const shippingCost = await calculateShipping(contact.postalAddress);
return {
newTotal: {
label: 'Total',
amount: (baseAmount + shippingCost).toFixed(2)
}
};
} catch (error) {
console.error('Shipping calculation failed:', error);
return {
errors: [{
code: 'shippingContactInvalid',
message: 'Unable to calculate shipping. Please try again.'
}]
};
}
},
onPostAuthorisation: async (data) => {
try {
// Retrieve authorisation result from backend
const result = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
if (result.status === 'Authorised') {
// Handle success
window.location.href = `/payment-success?txn=${data.merchantTransactionId}`;
} else {
// Handle declined payment
showUserMessage('Payment declined. Please try again or use a different payment method.');
}
} catch (error) {
console.error('Failed to retrieve authorisation result:', error);
showUserMessage('Unable to confirm payment status. Please contact support.');
}
},
onError: (error) => {
// Log error for debugging
console.error('Apple Pay error:', error);
// Show user-friendly message
if (error.message.includes('Network')) {
showUserMessage('Network error. Please check your connection and try again.');
} else if (error.message.includes('Merchant validation')) {
showUserMessage('Payment system temporarily unavailable. Please try again later.');
} else {
showUserMessage('Payment failed. Please try again or use a different payment method.');
}
}
};Always check Apple Pay availability before initialising:
// Check if Apple Pay is available in the browser
if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
// Initialise Apple Pay component
const applePayComponent = pxpCheckoutSdk.create('apple-pay-button', applePayConfig);
} else {
// Show alternative payment methods
console.log('Apple Pay not available in this browser');
}For recurring payments, use the onPreAuthorisation callback to set up subscription data:
onPreAuthorisation: async () => {
// Check if transaction should proceed
const transactionDecision = await getAuthorisationDecision();
if (!transactionDecision) {
// Update authorisation decision to false in backend via Modify Session API
await updateSessionAuthorisationDecision(false);
return null; // Do not proceed
}
// Update authorisation decision to true in backend via Modify Session API
await updateSessionAuthorisationDecision(true);
return {
riskScreeningData: {
deviceSessionId: await getDeviceSessionId(),
performRiskScreening: true,
customData: {
isRecurring: true,
recurringFrequency: 'monthly',
subscriptionId: await getSubscriptionId()
}
}
};
}Integrate with fraud detection services in the onPreAuthorisation callback:
onPreAuthorisation: async () => {
// Check if transaction should proceed
const transactionDecision = await getAuthorisationDecision();
if (!transactionDecision) {
// Update authorisation decision to false in backend via Modify Session API
await updateSessionAuthorisationDecision(false);
return null; // Do not proceed
}
// Get cart items for risk analysis
const cartItems = await getCartItems();
const shippingAddress = await getShippingAddress();
const customer = await getCustomer();
// Update authorisation decision to true in backend via Modify Session API
await updateSessionAuthorisationDecision(true);
return {
riskScreeningData: {
performRiskScreening: true,
deviceSessionId: await getDeviceSessionId(),
items: cartItems.map(item => ({
price: item.price,
quantity: item.quantity,
category: item.category,
sku: item.sku
})),
fulfillments: [{
type: 'SHIPPING',
recipientPerson: {
name: {
first: customer.firstName,
family: customer.lastName
},
email: customer.email,
phoneNumber: customer.phone,
address: {
line1: shippingAddress.street,
city: shippingAddress.city,
region: shippingAddress.state,
countryCode: shippingAddress.countryCode,
postalCode: shippingAddress.postalCode
}
}
}],
transaction: {
subtotal: cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0)
}
}
};
}The Web SDK supports two rendering methods with consistent event handling:
// Official Apple Pay SDK method (recommended)
const officialConfig = {
usingCss: false, // Use Apple's official button
onPostAuthorisation: async (data) => {
// Retrieve authorisation result from backend
const result = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
// Handle authorisation result
}
};
// Custom CSS method (more restrictive browser support)
const cssConfig = {
usingCss: true, // Use custom styled button
style: {
template: '<button class="custom-apple-pay">Pay with Apple Pay</button>',
templateCSS: '.custom-apple-pay { /* custom styles */ }'
},
onPostAuthorisation: async (data) => {
// Same event handling regardless of rendering method
const result = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
// Handle authorisation result
}
};