Skip to content

Events

Implement callbacks to customise your Apple Pay payment flow for iOS applications.

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 shipping calculations and address validation in real-time.
  • Support recurring, deferred, and automatic reload payment types.
  • Handle iOS-specific Apple Pay requirements and device capabilities.

All events are optional and can be mixed and matched based on your business needs.

Supported events

onPostAuthorisation

This callback is triggered after the customer authorises the payment with Touch ID, Face ID, or passcode. It receives the final transaction result from the payment processing system.

You can use it to:

  • Navigate customers to a success view controller with order confirmation.
  • Update stock levels for purchased items.
  • Send order confirmation emails to customers.
  • Record successful transactions for business intelligence.
  • Award points or update customer loyalty status.
  • Activate paid services or subscriptions.
  • Initiate delivery of digital products or licenses.

Event data

Event dataDescription
result
BaseSubmitResult
The payment processing result from PXP's backend.
result.transactionId
String
The unique identifier for the transaction.
result.submitResultType
String
The type of result: Authorised, Declined, or Exception.
result.timestamp
Date
The date and time of the transaction.

Example implementation

config.onPostAuthorisation = { result in
    if let authorizedResult = result as? AuthorisedSubmitResult {
        print("Payment authorized successfully")
        print("Transaction ID: \(authorizedResult.provider.code)")
        
        // Update inventory
        updateInventory(transactionId: authorizedResult.provider.code)
        
        // Send confirmation email (if contact info available)
        if let email = getCustomerEmail() {
            sendConfirmationEmail(email: email, transactionId: authorizedResult.provider.code)
        }
        
        // Track analytics
        trackPurchase(transactionId: authorizedResult.provider.code)
        
        // Navigate to success screen
        DispatchQueue.main.async {
            self.navigateToSuccess(transactionId: authorizedResult.provider.code)
        }
        
    } else if let failedResult = result as? FailedSubmitResult {
        print("Payment failed: \(failedResult.errorReason)")
        
        DispatchQueue.main.async {
            self.showErrorMessage("Payment failed. Please try again or use a different payment method.")
        }
    }
}

onPreAuthorisation

This callback is triggered before payment authorisation to allow you to provide additional transaction data or perform validation.

You can use it to:

  • Integrate with Kount or other fraud detection services.
  • Perform AVS (Address Verification System) checks.
  • Enable 3DS authentication for specific transactions.
  • Apply business rules based on transaction amount or customer history.
  • Check product availability before processing payment.
  • Apply last-minute discounts or validate coupon codes.
  • Verify customer identity for high-value transactions.

Event data

This callback receives no parameters.

Example implementation

config.onPreAuthorisation = {
    // Perform pre-payment validation
    let deviceSessionId = await getKountSessionId()
    let isHighRisk = await checkCustomerRiskProfile()
    
    return ApplePayTransactionInitData(
        threeDSecureData: isHighRisk ? ThreeDSecureData(
            threeDSecureVersion: "2.1.0",
            electronicCommerceIndicator: .eci5,
            cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
            directoryServerTransactionId: "8ac7ca4f-6068-4978-8b5e-c23c0c6d2260",
            threeDSecureTransactionStatus: "Y"
        ) : nil,
        
        identityVerification: IdentityVerification(
            nameVerification: true
        ),
        
        addressVerification: AddressVerification(
            countryCode: "US",
            houseNumberOrName: "123 Main St",
            postalCode: "10001"
        ),
        
        riskScreeningData: RiskScreeningData(
            performRiskScreening: true,
            deviceSessionId: deviceSessionId,
            transaction: TransactionRiskData(
                customerTier: "premium",
                orderType: "recurring",
                forceThreeDS: isHighRisk
            )
        )
    )
}

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 in iOS alerts or custom views.
  • Offer alternative payment methods.
  • Implement automatic retry for transient errors.
  • Alert support teams for critical errors.
  • Track error rates for business intelligence.
  • Handle iOS-specific Apple Pay issues.

Event data

ParameterDescription
error
Error
The error object containing details about what went wrong.
error.localizedDescription
String
A human-readable error description.
error.domain
String
The error domain.
error.code
Int
The error code for programmatic handling.

Example implementation

config.onError = { error in
    print("Apple Pay error: \(error.localizedDescription)")
    
    // Log error for debugging
    logError(type: "apple-pay-error", details: [
        "message": error.localizedDescription,
        "domain": error.domain,
        "code": String(error.code),
        "timestamp": ISO8601DateFormatter().string(from: Date())
    ])
    
    DispatchQueue.main.async {
        // Handle different error types
        if error.localizedDescription.contains("cancelled") {
            // Silent handling - user intentionally cancelled
            return
        } else if error.localizedDescription.contains("Network") {
            self.showUserMessage("Network error. Please check your connection and try again.")
            // Offer offline payment options
        } else if error.localizedDescription.contains("Merchant validation") {
            self.showUserMessage("Payment system temporarily unavailable. Please try again later.")
            // Alert support team
            self.notifySupport("Apple Pay merchant validation failed")
        } else if let applePayError = error as? ApplePayNotAvailableException {
            self.showUserMessage("Apple Pay is not available on this device.")
            self.showAlternativePaymentMethods()
        } else {
            self.showUserMessage("Payment failed. Please try again or use a different payment method.")
            // Show alternative payment options
            self.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 optimization.
  • Show helpful messages or alternative options.
  • Save the customer's cart for later completion.
  • Trigger email campaigns for abandoned checkouts.
  • Test different messaging for cancelled transactions.
  • Offer live chat or support for confused customers.
  • Guide users to different payment methods.

Event data

ParameterDescription
error
Error?
The error object indicating cancellation reason (optional).

Example implementation

config.onCancel = { error in
    print("Apple Pay cancelled: \(error?.localizedDescription ?? "User cancelled")")
    
    // Track cancellation for analytics
    trackEvent("apple-pay-cancelled", parameters: [
        "reason": error?.localizedDescription ?? "unknown",
        "timestamp": ISO8601DateFormatter().string(from: Date()),
        "cartValue": getCurrentCartValue()
    ])
    
    // Preserve cart for later
    saveCartForLater()
    
    DispatchQueue.main.async {
        // Show helpful message
        self.showMessage("No worries! Your items are saved. You can complete your purchase anytime.", type: .info)
        
        // Offer alternatives after a brief delay
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            self.showAlternativePaymentOptions()
        }
        
        // Optional: Schedule abandoned cart email sequence
        self.scheduleAbandonedCartEmail(email: self.customerEmail, delayMinutes: 30)
    }
}

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.
  • Check if delivery is available to specific locations.
  • Update tax rates based on shipping destination.
  • Show available delivery options for the location.
  • Handle customs and duties for cross-border orders.
  • Offer expedited shipping based on location proximity.

Event data

ParameterDescription
contact
PKContact
Customer's selected shipping contact information.
contact.postalAddress
CNPostalAddress
Shipping address details.
contact.name
PersonNameComponents
The contact name.
contact.phoneNumber
CNPhoneNumber
The phone number.
contact.emailAddress
String
The email address.

Example implementation

config.onShippingContactSelected = { contact in
    print("Shipping contact selected: \(contact)")
    
    do {
        // Validate shipping address
        let isValidAddress = try await validateAddress(contact.postalAddress)
        if !isValidAddress {
            return PKPaymentRequestShippingContactUpdate(
                errors: [PKPaymentRequest.paymentShippingAddressInvalidError(
                    withKey: PKContactField.postalAddress,
                    localizedDescription: "Please enter a valid shipping address"
                )],
                shippingMethods: [],
                paymentSummaryItems: []
            )
        }
        
        // Calculate shipping and tax
        let shippingCost = try await calculateShipping(address: contact.postalAddress)
        let taxAmount = try await calculateTax(address: contact.postalAddress, baseAmount: baseAmount)
        let newTotal = baseAmount + taxAmount + shippingCost
        
        // Get available shipping methods
        let shippingMethods = try await getShippingMethods(for: contact.postalAddress)
        
        let summaryItems = [
            PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(value: baseAmount)),
            PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(value: taxAmount)),
            PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(value: shippingCost)),
            PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(value: newTotal))
        ]
        
        return PKPaymentRequestShippingContactUpdate(
            errors: [],
            shippingMethods: shippingMethods,
            paymentSummaryItems: summaryItems
        )
        
    } catch {
        print("Shipping calculation failed: \(error)")
        return PKPaymentRequestShippingContactUpdate(
            errors: [PKPaymentRequest.paymentShippingAddressUnserviceableError(
                withLocalizedDescription: "Unable to calculate shipping. Please try again."
            )],
            shippingMethods: [],
            paymentSummaryItems: []
        )
    }
}

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.
  • Offer shipping insurance for valuable items.
  • Update packaging options based on shipping speed.
  • Calculate and offer carbon offset for shipping.

Event data

ParameterDescription
method
PKShippingMethod
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
NSDecimalNumber
The shipping cost.

Example implementation

config.onShippingMethodSelected = { method in
    print("Shipping method selected: \(method)")
    
    // Update total based on selected shipping method
    let baseAmount: Decimal = 20.00
    let tax: Decimal = 5.00
    let shippingCost = method.amount.decimalValue
    
    // Add insurance for express shipping
    var insurance: Decimal = 0
    if method.identifier == "express" && baseAmount > 50 {
        insurance = 2.99
    }
    
    // Calculate carbon offset for eco-conscious customers
    let carbonOffset: Decimal = method.identifier == "standard" ? 0.50 : 0
    
    let newTotal = baseAmount + tax + shippingCost + insurance + carbonOffset
    
    // Track shipping method selection
    trackEvent("shipping-method-selected", parameters: [
        "method": method.identifier ?? "",
        "cost": String(describing: shippingCost),
        "orderValue": String(describing: baseAmount)
    ])
    
    var summaryItems = [
        PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(decimal: baseAmount)),
        PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(decimal: tax)),
        PKPaymentSummaryItem(label: "Shipping (\(method.label))", amount: NSDecimalNumber(decimal: shippingCost))
    ]
    
    if insurance > 0 {
        summaryItems.append(PKPaymentSummaryItem(label: "Shipping Insurance", amount: NSDecimalNumber(decimal: insurance)))
    }
    
    if carbonOffset > 0 {
        summaryItems.append(PKPaymentSummaryItem(label: "Carbon Offset", amount: NSDecimalNumber(decimal: carbonOffset)))
    }
    
    summaryItems.append(PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(decimal: newTotal)))
    
    return PKPaymentRequestShippingMethodUpdate(paymentSummaryItems: summaryItems)
}

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.
  • Provide card-specific cashback or discount offers.
  • Implement card-specific fraud detection rules.
  • Handle different regulatory requirements by card network.

Event data

ParameterDescription
paymentMethod
PKPaymentMethod
The selected payment method.
paymentMethod.displayName
String
The card display name (e.g., "Visa ••••1234").
paymentMethod.network
PKPaymentNetwork
The card network (visa, masterCard, amex, discover).
paymentMethod.type
PKPaymentMethodType
The card type.
paymentMethod.paymentPass
PKPaymentPass
The card details.

Example implementation

config.onPaymentMethodSelected = { paymentMethod in
    print("Payment method selected: \(paymentMethod)")
    
    let baseAmount: Decimal = 20.00
    let tax: Decimal = 5.00
    var processingFee: Decimal = 0
    var reward: Decimal = 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", parameters: [
        "network": paymentMethod.network?.rawValue ?? "",
        "type": String(describing: paymentMethod.type),
        "lastFour": paymentMethod.paymentPass?.primaryAccountNumberSuffix ?? ""
    ])
    
    let newTotal = max(0, baseAmount + tax + processingFee - reward)
    
    var summaryItems = [
        PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(decimal: baseAmount)),
        PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(decimal: tax))
    ]
    
    if processingFee > 0 {
        let feeLabel = paymentMethod.type == .credit ? "Credit Fee" : "Debit Fee"
        summaryItems.append(PKPaymentSummaryItem(label: feeLabel, amount: NSDecimalNumber(decimal: processingFee)))
    }
    
    if reward > 0 {
        let networkName = paymentMethod.network?.rawValue.capitalized ?? "Card"
        summaryItems.append(PKPaymentSummaryItem(label: "\(networkName) Reward", amount: NSDecimalNumber(decimal: -reward)))
    }
    
    summaryItems.append(PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(decimal: newTotal)))
    
    return PKPaymentRequestPaymentMethodUpdate(paymentSummaryItems: summaryItems)
}

onCouponCodeChanged

This callback is triggered when the customer enters or changes a coupon code in the Apple Pay sheet (available on iOS 15.0+).

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.
  • Offer special discounts for new customers.
  • Apply volume-based discount codes.
  • Handle time-sensitive promotional codes.

Event data

ParameterDescription
couponCode
String
The coupon code entered by the customer.

Example implementation

@available(iOS 15.0, *)
config.onCouponCodeChanged = { couponCode in
    print("Coupon code entered: \(couponCode)")
    
    do {
        // Validate coupon code
        let discount = try await validateCouponCode(couponCode)
        
        if discount.valid && !discount.expired {
            let baseAmount: Decimal = 20.00
            let tax: Decimal = 5.00
            
            // Calculate discount amount
            var discountAmount: Decimal = 0
            if discount.type == "percentage" {
                discountAmount = baseAmount * (discount.value / 100)
            } else if discount.type == "fixed" {
                discountAmount = discount.value
            }
            
            // Apply minimum purchase requirement
            if let minimumPurchase = discount.minimumPurchase, baseAmount < minimumPurchase {
                return PKPaymentRequestCouponCodeUpdate(
                    errors: [PKPaymentRequest.paymentCouponCodeInvalidError(
                        localizedDescription: "Minimum purchase of $\(minimumPurchase) required"
                    )],
                    paymentSummaryItems: [],
                    shippingMethods: []
                )
            }
            
            // Cap discount at maximum amount
            if let maxDiscount = discount.maxDiscount {
                discountAmount = min(discountAmount, maxDiscount)
            }
            
            let newTotal = max(0, baseAmount + tax - discountAmount)
            
            // Track coupon usage
            trackEvent("coupon-applied", parameters: [
                "code": couponCode,
                "discountType": discount.type,
                "discountAmount": String(describing: discountAmount),
                "orderValue": String(describing: baseAmount)
            ])
            
            let summaryItems = [
                PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(decimal: baseAmount)),
                PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(decimal: tax)),
                PKPaymentSummaryItem(label: "Discount (\(couponCode))", amount: NSDecimalNumber(decimal: -discountAmount)),
                PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(decimal: newTotal))
            ]
            
            return PKPaymentRequestCouponCodeUpdate(
                errors: [],
                paymentSummaryItems: summaryItems,
                shippingMethods: []
            )
            
        } else {
            // Handle invalid or expired coupon
            let errorMessage = discount.expired 
                ? "This coupon code has expired" 
                : "Invalid coupon code"
                
            return PKPaymentRequestCouponCodeUpdate(
                errors: [PKPaymentRequest.paymentCouponCodeInvalidError(
                    localizedDescription: errorMessage
                )],
                paymentSummaryItems: [],
                shippingMethods: []
            )
        }
    } catch {
        print("Coupon validation failed: \(error)")
        return PKPaymentRequestCouponCodeUpdate(
            errors: [PKPaymentRequest.paymentCouponCodeInvalidError(
                localizedDescription: "Unable to validate coupon code. Please try again."
            )],
            paymentSummaryItems: [],
            shippingMethods: []
        )
    }
}

Event data structures

Contact object

The contact information is provided through iOS PKContact objects:

// PKContact properties
contact.postalAddress: CNPostalAddress? // Address information
contact.name: PersonNameComponents? // Name components
contact.phoneNumber: CNPhoneNumber? // Phone number
contact.emailAddress: String? // Email address

// CNPostalAddress properties
postalAddress.street: String // Street address
postalAddress.city: String // City name
postalAddress.state: String // State or province
postalAddress.postalCode: String // ZIP or postal code
postalAddress.country: String // Country name
postalAddress.isoCountryCode: String // ISO country code

Payment method object

Payment method information is provided through iOS PKPaymentMethod objects:

// PKPaymentMethod properties
paymentMethod.displayName: String? // Card display name
paymentMethod.network: PKPaymentNetwork? // Card network
paymentMethod.type: PKPaymentMethodType // Card type
paymentMethod.paymentPass: PKPaymentPass? // Card details

// PKPaymentPass properties
paymentPass.primaryAccountIdentifier: String // Primary account ID
paymentPass.primaryAccountNumberSuffix: String // Last four digits
paymentPass.deviceAccountIdentifier: String // Device-specific ID
paymentPass.deviceAccountNumberSuffix: String // Device account suffix

Update response objects

Event callbacks can return update objects to modify the Apple Pay sheet:

// Shipping contact update
PKPaymentRequestShippingContactUpdate(
    errors: [PKError], // Validation errors
    shippingMethods: [PKShippingMethod], // Available shipping options
    paymentSummaryItems: [PKPaymentSummaryItem] // Updated line items
)

// Shipping method update
PKPaymentRequestShippingMethodUpdate(
    paymentSummaryItems: [PKPaymentSummaryItem] // Updated line items
)

// Payment method update
PKPaymentRequestPaymentMethodUpdate(
    paymentSummaryItems: [PKPaymentSummaryItem] // Updated line items
)

// Coupon code update (iOS 15.0+)
PKPaymentRequestCouponCodeUpdate(
    errors: [PKError], // Validation errors
    paymentSummaryItems: [PKPaymentSummaryItem], // Updated line items
    shippingMethods: [PKShippingMethod] // Updated shipping options
)

Error handling in events

Event callbacks should handle errors gracefully and provide appropriate feedback to customers:

let applePayConfig = ApplePayButtonComponentConfig()

applePayConfig.onShippingContactSelected = { contact in
    do {
        // Validate shipping address
        let isValid = try await validateShippingAddress(contact.postalAddress)
        
        if !isValid {
            return PKPaymentRequestShippingContactUpdate(
                errors: [PKPaymentRequest.paymentShippingAddressInvalidError(
                    withKey: PKContactField.postalAddress,
                    localizedDescription: "We cannot ship to this address"
                )],
                shippingMethods: [],
                paymentSummaryItems: []
            )
        }
        
        // Calculate shipping
        let shippingCost = try await calculateShipping(address: contact.postalAddress)
        let summaryItems = [
            PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(value: baseAmount)),
            PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(value: shippingCost)),
            PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(value: baseAmount + shippingCost))
        ]
        
        return PKPaymentRequestShippingContactUpdate(
            errors: [],
            shippingMethods: getShippingMethods(),
            paymentSummaryItems: summaryItems
        )
        
    } catch {
        print("Shipping calculation failed: \(error)")
        return PKPaymentRequestShippingContactUpdate(
            errors: [PKPaymentRequest.paymentShippingAddressUnserviceableError(
                withLocalizedDescription: "Unable to calculate shipping. Please try again."
            )],
            shippingMethods: [],
            paymentSummaryItems: []
        )
    }
}

applePayConfig.onError = { error in
    // Log error for debugging
    print("Apple Pay error: \(error)")
    
    DispatchQueue.main.async {
        // Show user-friendly message
        if error.localizedDescription.contains("Network") {
            self.showUserMessage("Network error. Please check your connection and try again.")
        } else if error.localizedDescription.contains("Merchant validation") {
            self.showUserMessage("Payment system temporarily unavailable. Please try again later.")
        } else {
            self.showUserMessage("Payment failed. Please try again or use a different payment method.")
        }
    }
}

Advanced event usage

Device compatibility checking

Always check Apple Pay availability before initialising:

import PassKit

// Check if Apple Pay is available on the device
if PKPaymentAuthorizationController.canMakePayments() {
    // Check if user has cards set up
    let supportedNetworks: [PKPaymentNetwork] = [.visa, .masterCard, .amex]
    if PKPaymentAuthorizationController.canMakePayments(usingNetworks: supportedNetworks) {
        // Initialise Apple Pay component
        let applePayComponent = try checkout.create(.applePayButton, componentConfig: applePayConfig)
    } else {
        // Show setup Apple Pay message
        print("Apple Pay is available but no cards are set up")
        showSetupApplePayMessage()
    }
} else {
    // Show alternative payment methods
    print("Apple Pay not available on this device")
    showAlternativePaymentMethods()
}

Recurring payments

For recurring payments, use the onPreAuthorisation callback to set up subscription data:

applePayConfig.onPreAuthorisation = {
    return ApplePayTransactionInitData(
        riskScreeningData: RiskScreeningData(
            performRiskScreening: true,
            deviceSessionId: await getDeviceSessionId(),
            transaction: TransactionRiskData(
                isRecurring: true,
                recurringFrequency: "monthly",
                subscriptionType: "premium"
            )
        )
    )
}

Fraud detection

Integrate with fraud detection services in the onPreAuthorisation callback:

applePayConfig.onPreAuthorisation = {
    let riskScore = await calculateRiskScore()
    
    if riskScore > 0.8 {
        // High risk - require additional verification
        return ApplePayTransactionInitData(
            threeDSecureData: ThreeDSecureData(
                threeDSecureVersion: "2.1.0",
                electronicCommerceIndicator: .eci5,
                cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
                directoryServerTransactionId: "8ac7ca4f-6068-4978-8b5e-c23c0c6d2260",
                threeDSecureTransactionStatus: "Y"
            )
        )
    }
    
    return ApplePayTransactionInitData(
        riskScreeningData: RiskScreeningData(
            riskScore: riskScore
        )
    )
}

Complete implementation example

Here's a comprehensive example showing all event callbacks working together:

import UIKit
import PXPCheckoutSDK
import PassKit

class EventsViewController: UIViewController {
    private var applePayComponent: ApplePayButtonComponent?
    private var checkout: PxpCheckout?
    private let baseAmount: Decimal = 29.99
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupApplePayWithEvents()
    }
    
    private func setupApplePayWithEvents() {
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: SessionConfig(sessionId: "your-session-id"),
            transactionData: TransactionData(
                amount: Double(baseAmount),
                currency: "USD",
                entryType: .ecom,
                intent: .sale,
                merchantTransactionId: "order-\(Int(Date().timeIntervalSince1970))",
                merchantTransactionDate: Date()
            )
        )
        
        do {
            checkout = try PxpCheckout.initialize(config: checkoutConfig)
            let applePayConfig = createEventConfiguredApplePay()
            applePayComponent = try checkout?.create(.applePayButton, componentConfig: applePayConfig)
            
            if let componentView = applePayComponent?.render() {
                view.addSubview(componentView)
                setupConstraints(for: componentView)
            }
        } catch {
            print("Failed to initialize: \(error)")
        }
    }
    
    private func createEventConfiguredApplePay() -> ApplePayButtonComponentConfig {
        let config = ApplePayButtonComponentConfig()
        
        // Basic configuration
        config.currencyCode = "USD"
        config.countryCode = "US"
        config.supportedNetworks = [.visa, .masterCard, .amex]
        config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]
        config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
        config.requiredShippingContactFields = [.postalAddress, .name]
        
        // Payment items
        config.totalPaymentItem = ApplePayPaymentSummaryItem(
            amount: baseAmount,
            label: "Total",
            type: .final
        )
        
        // Button appearance
        config.buttonType = .buy
        config.buttonStyle = .black
        config.buttonRadius = 8.0
        
        // Event callbacks
        setupEventCallbacks(for: config)
        
        return config
    }
    
    private func setupEventCallbacks(for config: ApplePayButtonComponentConfig) {
        
        // Pre-authorisation callback
        config.onPreAuthorisation = { [weak self] in
            print("Pre-authorisation callback triggered")
            
            return ApplePayTransactionInitData(
                identityVerification: IdentityVerification(nameVerification: true),
                addressVerification: AddressVerification(
                    countryCode: "US",
                    houseNumberOrName: "123 Main St",
                    postalCode: "10001"
                ),
                riskScreeningData: RiskScreeningData(
                    performRiskScreening: true,
                    deviceSessionId: self?.generateDeviceSessionId() ?? ""
                )
            )
        }
        
        // Post-authorisation callback
        config.onPostAuthorisation = { [weak self] result in
            print("Post-authorisation callback triggered")
            
            DispatchQueue.main.async {
                if let authorizedResult = result as? AuthorisedSubmitResult {
                    self?.handleSuccessfulPayment(transactionId: authorizedResult.provider.code)
                } else if let failedResult = result as? FailedSubmitResult {
                    self?.handleFailedPayment(error: failedResult.errorReason)
                }
            }
        }
        
        // Error callback
        config.onError = { [weak self] error in
            print("❌ Error callback triggered: \(error.localizedDescription)")
            
            DispatchQueue.main.async {
                self?.handleError(error)
            }
        }
        
        // Cancel callback
        config.onCancel = { [weak self] error in
            print("🚫 Cancel callback triggered")
            
            DispatchQueue.main.async {
                self?.handleCancellation()
            }
        }
        
        // Shipping contact callback
        config.onShippingContactSelected = { [weak self] contact in
            print("📍 Shipping contact selected callback triggered")
            return await self?.handleShippingContactSelected(contact) ?? PKPaymentRequestShippingContactUpdate(
                errors: [],
                shippingMethods: [],
                paymentSummaryItems: []
            )
        }
        
        // Shipping method callback
        config.onShippingMethodSelected = { [weak self] method in
            print("🚚 Shipping method selected callback triggered")
            return self?.handleShippingMethodSelected(method) ?? PKPaymentRequestShippingMethodUpdate(paymentSummaryItems: [])
        }
        
        // Payment method callback
        config.onPaymentMethodSelected = { [weak self] paymentMethod in
            print("💳 Payment method selected callback triggered")
            return self?.handlePaymentMethodSelected(paymentMethod) ?? PKPaymentRequestPaymentMethodUpdate(paymentSummaryItems: [])
        }
        
        // Coupon code callback (iOS 15.0+)
        if #available(iOS 15.0, *) {
            config.onCouponCodeChanged = { [weak self] couponCode in
                print("🎟️ Coupon code changed callback triggered")
                return await self?.handleCouponCodeChanged(couponCode) ?? PKPaymentRequestCouponCodeUpdate(
                    errors: [],
                    paymentSummaryItems: [],
                    shippingMethods: []
                )
            }
        }
    }
    
    // Event handler implementations
    private func handleSuccessfulPayment(transactionId: String) {
        print("Payment successful: \(transactionId)")
        let alert = UIAlertController(title: "Success", message: "Payment completed successfully!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func handleFailedPayment(error: String) {
        print("Payment failed: \(error)")
        let alert = UIAlertController(title: "Payment Failed", message: error, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func handleError(_ error: Error) {
        print("Apple Pay error: \(error)")
        let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func handleCancellation() {
        print("Payment cancelled by user")
        // Optional: Show message or alternative options
    }
    
    private func handleShippingContactSelected(_ contact: PKContact) async -> PKPaymentRequestShippingContactUpdate {
        // Calculate shipping based on address
        let shippingCost: Decimal = 5.99
        let tax: Decimal = 2.40
        let total = baseAmount + shippingCost + tax
        
        let summaryItems = [
            PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(decimal: baseAmount)),
            PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(decimal: shippingCost)),
            PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(decimal: tax)),
            PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(decimal: total))
        ]
        
        let shippingMethods = [
            PKShippingMethod(label: "Standard Shipping", amount: NSDecimalNumber(decimal: 5.99)),
            PKShippingMethod(label: "Express Shipping", amount: NSDecimalNumber(decimal: 12.99))
        ]
        shippingMethods[0].identifier = "standard"
        shippingMethods[0].detail = "5-7 business days"
        shippingMethods[1].identifier = "express"
        shippingMethods[1].detail = "2-3 business days"
        
        return PKPaymentRequestShippingContactUpdate(
            errors: [],
            shippingMethods: shippingMethods,
            paymentSummaryItems: summaryItems
        )
    }
    
    private func handleShippingMethodSelected(_ method: PKShippingMethod) -> PKPaymentRequestShippingMethodUpdate {
        let tax: Decimal = 2.40
        let shippingCost = method.amount.decimalValue
        let total = baseAmount + shippingCost + tax
        
        let summaryItems = [
            PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(decimal: baseAmount)),
            PKPaymentSummaryItem(label: "Shipping (\(method.label))", amount: NSDecimalNumber(decimal: shippingCost)),
            PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(decimal: tax)),
            PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(decimal: total))
        ]
        
        return PKPaymentRequestShippingMethodUpdate(paymentSummaryItems: summaryItems)
    }
    
    private func handlePaymentMethodSelected(_ paymentMethod: PKPaymentMethod) -> PKPaymentRequestPaymentMethodUpdate {
        let tax: Decimal = 2.40
        let shipping: Decimal = 5.99
        
        // Apply different processing fees based on card type
        var processingFee: Decimal = 0
        if paymentMethod.type == .credit {
            processingFee = 2.99
        }
        
        let total = baseAmount + tax + shipping + processingFee
        
        var summaryItems = [
            PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(decimal: baseAmount)),
            PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(decimal: tax)),
            PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(decimal: shipping))
        ]
        
        if processingFee > 0 {
            summaryItems.append(PKPaymentSummaryItem(label: "Processing Fee", amount: NSDecimalNumber(decimal: processingFee)))
        }
        
        summaryItems.append(PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(decimal: total)))
        
        return PKPaymentRequestPaymentMethodUpdate(paymentSummaryItems: summaryItems)
    }
    
    @available(iOS 15.0, *)
    private func handleCouponCodeChanged(_ couponCode: String) async -> PKPaymentRequestCouponCodeUpdate {
        // Simulate coupon validation
        if couponCode.uppercased() == "SAVE10" {
            let discount: Decimal = 3.00
            let tax: Decimal = 2.40
            let shipping: Decimal = 5.99
            let total = max(0, baseAmount + tax + shipping - discount)
            
            let summaryItems = [
                PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(decimal: baseAmount)),
                PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(decimal: tax)),
                PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(decimal: shipping)),
                PKPaymentSummaryItem(label: "Discount (SAVE10)", amount: NSDecimalNumber(decimal: -discount)),
                PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(decimal: total))
            ]
            
            return PKPaymentRequestCouponCodeUpdate(
                errors: [],
                paymentSummaryItems: summaryItems,
                shippingMethods: []
            )
        } else {
            return PKPaymentRequestCouponCodeUpdate(
                errors: [PKPaymentRequest.paymentCouponCodeInvalidError(
                    localizedDescription: "Invalid coupon code"
                )],
                paymentSummaryItems: [],
                shippingMethods: []
            )
        }
    }
    
    // Helper methods
    private func generateDeviceSessionId() -> String {
        return "device_\(UUID().uuidString.replacingOccurrences(of: "-", with: ""))_\(Int(Date().timeIntervalSince1970))"
    }
    
    private func setupConstraints(for componentView: UIView) {
        componentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            componentView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            componentView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            componentView.widthAnchor.constraint(equalToConstant: 280),
            componentView.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
}