Skip to content

Events

Implement callbacks to customise your Apple Pay payment flow for Web.

Overview

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.

SDK data callbacks

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.

Apple Pay-specific events

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.

Optional Apple Pay events

onPostAuthorisation

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

Event dataDescription
data
object
Object containing transaction identifiers.
data.merchantTransactionId
string
Your unique identifier for the transaction. Use this to retrieve full authorisation details from the Unity backend.
data.systemTransactionId
string
The unique PXP system transaction identifier.

Example implementation

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.');
  }
}

onPreAuthorisation

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.

Event data

This callback receives no parameters.

Example implementation

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
    }
  };
}

onError

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.

Event data

ParameterDescription
error
Error
The error object containing details about what went wrong.
error.message
string
A human-readable error description.
error.name
string
The error type.
error.code
string
The error code for programmatic handling.
error.stack
string
The stack trace for debugging.

Example implementation

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();
  }
}

onCancel

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.

Event data

ParameterDescription
error
Error
Error object indicating cancellation reason (optional).
error.message
string
The cancellation reason.
error.code
string
The cancellation code, for tracking purposes.

Example implementation

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
}

onShippingContactSelected

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.

Event data

ParameterDescription
contact
ApplePayPaymentContact
The customer's shipping contact information.
contact.postalAddress
object
The shipping address details.
contact.postalAddress.street
string
The street address.
contact.postalAddress.city
string
The city name.
contact.postalAddress.state
string
The state or province.
contact.postalAddress.postalCode
string
The ZIP or postal code.
contact.postalAddress.country
string
The country name.
contact.postalAddress.countryCode
string
The ISO country code.
contact.name
object
The contact name.
contact.name.givenName
string
The first name.
contact.name.familyName
string
The last name.
contact.phoneNumber
string
The phone number.
contact.emailAddress
string
The email address.

Example implementation

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.'
      }]
    };
  }
}

onShippingMethodSelected

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.

Event data

ParameterDescription
method
ApplePayShippingMethod
The selected shipping method.
method.identifier
string
A unique identifier for the shipping method.
method.label
string
The display name (e.g., "Standard Shipping").
method.detail
string
Additional details (e.g., "5-7 business days").
method.amount
string
The shipping cost as a decimal string.

Example implementation

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) }] : [])
    ]
  };
}

onPaymentMethodSelected

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.

Event data

ParameterDescription
paymentMethod
ApplePayPaymentMethod
The selected payment method.
paymentMethod.displayName
string
The card display name (e.g., "Visa ••••1234").
paymentMethod.network
string
The card network (visa, masterCard, amex, discover).
paymentMethod.type
string
The card type.
paymentMethod.paymentPass
object
The card details.
paymentMethod.paymentPass.primaryAccountIdentifier
string
The primary account identifier.
paymentMethod.paymentPass.primaryAccountNumberSuffix
string
The last four digits of the card.
paymentMethod.paymentPass.deviceAccountIdentifier
string
The device-specific account ID.
paymentMethod.paymentPass.deviceAccountNumberSuffix
string
The device account suffix.

Example implementation

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)}` }] : [])
    ]
  };
}

onCouponCodeChanged

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.

Event data

ParameterDescription
couponCode
string
The coupon code entered by the customer.

Example implementation

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.'
      }]
    };
  }
}

Event data structures

Contact object

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;
}

Payment method object

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;
  };
}

Update response object

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;
  }>;
}

Error handling in events

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.');
    }
  }
};

Advanced event usage

Browser compatibility checking

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');
}

Recurring payments

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()
      }
    }
  };
}

Fraud detection

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)
      }
    }
  };
}

Handling different rendering methods

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
  }
};