Implement callbacks to customise your Apple Pay payment flow for iOS applications.
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.
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 | Description |
|---|---|
resultBaseSubmitResult | The payment processing result from PXP's backend. |
result.transactionIdString | The unique identifier for the transaction. |
result.submitResultTypeString | The type of result: Authorised, Declined, or Exception. |
result.timestampDate | The date and time of the transaction. |
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.")
}
}
}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.
This callback receives no parameters.
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
)
)
)
}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.
| Parameter | Description |
|---|---|
errorError | The error object containing details about what went wrong. |
error.localizedDescriptionString | A human-readable error description. |
error.domainString | The error domain. |
error.codeInt | The error code for programmatic handling. |
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()
}
}
}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.
| Parameter | Description |
|---|---|
errorError? | The error object indicating cancellation reason (optional). |
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)
}
}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.
| Parameter | Description |
|---|---|
contactPKContact | Customer's selected shipping contact information. |
contact.postalAddressCNPostalAddress | Shipping address details. |
contact.namePersonNameComponents | The contact name. |
contact.phoneNumberCNPhoneNumber | The phone number. |
contact.emailAddressString | The email address. |
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: []
)
}
}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.
| Parameter | Description |
|---|---|
methodPKShippingMethod | The selected shipping method. |
method.identifierString | A unique identifier for the shipping method. |
method.labelString | The display name (e.g., "Standard Shipping"). |
method.detailString | Additional details (e.g., "5-7 business days"). |
method.amountNSDecimalNumber | The shipping cost. |
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)
}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.
| Parameter | Description |
|---|---|
paymentMethodPKPaymentMethod | The selected payment method. |
paymentMethod.displayNameString | The card display name (e.g., "Visa ••••1234"). |
paymentMethod.networkPKPaymentNetwork | The card network (visa, masterCard, amex, discover). |
paymentMethod.typePKPaymentMethodType | The card type. |
paymentMethod.paymentPassPKPaymentPass | The card details. |
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)
}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.
| Parameter | Description |
|---|---|
couponCodeString | The coupon code entered by the customer. |
@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: []
)
}
}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 codePayment 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 suffixEvent 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
)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.")
}
}
}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()
}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"
)
)
)
}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
)
)
}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)
])
}
}