# 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 data | Description |
|  --- | --- |
| `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

```typescript
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](/apis/session/other/modify-session) API to update the authorisation decision on your backend before returning from this callback. Set `"allowTransaction": true` in the session data to proceed with the transaction.

#### Event data

This callback receives no parameters.

#### Example implementation

**To proceed with authorisation:**

```typescript
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,
      userIp: "192.168.1.100",
      account: {
        id: "user_12345678",
        creationDateTime: "2024-01-15T10:30:00.000Z"
      },
      items: [{
        price: 140.00,
        quantity: 1,
        category: "Electronics"
      }],
      fulfillments: [{
        type: "Shipped",
        shipping: {
          shippingMethod: "Express"
        },
        recipientPerson: {
          phoneNumber: "+1234567890",
          email: "customer@example.com"
        }
      }]
    }
  };
}
```

**If you don't want to proceed with authorisation:**

```typescript
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,
      userIp: "192.168.1.100",
      account: {
        id: "user_12345678",
        creationDateTime: "2024-01-15T10:30:00.000Z"
      },
      fulfillments: [{
        type: "Shipped",
        recipientPerson: {
          phoneNumber: "+1234567890"
        }
      }]
    }
  };
}
```

### 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

| Parameter | Description |
|  --- | --- |
| `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

```typescript
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

| Parameter | Description |
|  --- | --- |
| `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

```typescript
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](/apis/session/other/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

| Parameter | Description |
|  --- | --- |
| `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

```typescript
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](/apis/session/other/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

| Parameter | Description |
|  --- | --- |
| `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

```typescript
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

| Parameter | Description |
|  --- | --- |
| `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

```typescript
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](/apis/session/other/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

| Parameter | Description |
|  --- | --- |
| `couponCode`string | The coupon code entered by the customer. |


#### Example implementation

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
// 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:

```typescript
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,
      userIp: "192.168.1.100",
      account: {
        id: "user_12345678",
        creationDateTime: "2024-01-15T10:30:00.000Z"
      },
      items: [{
        price: 9.99,
        quantity: 1,
        category: "Subscription"
      }],
      fulfillments: [{
        type: "Digital",
        recipientPerson: {
          phoneNumber: "+1234567890"
        }
      }],
      transaction: {
        subtotal: 9.99
      }
    }
  };
}
```

### Fraud detection

Integrate with fraud detection services in the `onPreAuthorisation` callback:

```typescript
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(),
      userIp: "192.168.1.100",
      account: {
        id: "user_12345678",
        creationDateTime: "2024-01-15T10:30:00.000Z"
      },
      items: cartItems.map(item => ({
        price: item.price,
        quantity: item.quantity,
        category: item.category,
        sku: item.sku
      })),
      fulfillments: [{
        type: 'Shipped',
        shipping: {
          shippingMethod: 'Standard'
        },
        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:

```typescript
// 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
  }
};
```