# Implementation

Learn how to use the Apple Pay component for iOS in your iOS application.

## Overview

Every component follows the same four-step lifecycle:

1. Initialise the PXP Checkout SDK with your configuration.
2. Create the Apple Pay component with your specific configuration.
3. Display the component in your view using `buildContent()`.
4. Handle payment results and lifecycle events.


## Before you start

To use the Apple Pay component, you first need to:

* [Install Components for iOS](/guides/checkout/components/ios/install).
* Complete the [Apple Pay onboarding](/guides/checkout/components/ios/apple-pay/onboarding) process in the Unity Portal.
* Configure your iOS app with Apple Pay entitlements.
* Ensure your merchant certificate is properly configured.


### Device and OS compatibility

Apple Pay for iOS has specific requirements for optimal functionality.

#### Supported iOS versions

- iOS 14.0+ as minimum deployment target for `PXPCheckoutSDK`.
- iOS 15.0+ for coupon code support.


#### Device requirements

- iPhone: iPhone 6 or later with Touch ID or Face ID.
- iPad: iPad Pro, iPad Air 2, iPad (5th generation) or later, iPad mini 3 or later.
- Apple Watch: When paired with compatible iPhone.


#### Configuration requirements

- The customer must have a supported payment method in their Wallet app.
- The device must have Touch ID, Face ID, or passcode enabled.
- App must have proper Apple Pay entitlements configured.


## Step 1: Configure app entitlements

First, ensure your iOS app has the proper Apple Pay entitlements configured.

### Entitlements.plist

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.developer.in-app-payments</key>
    <array>
        <string>merchant.com.yourcompany.yourapp</string>
    </array>
</dict>
</plist>
```

### Info.plist privacy descriptions

```xml
<key>NSFaceIDUsageDescription</key>
<string>Use Face ID to authenticate Apple Pay transactions</string>
<key>NSContactsUsageDescription</key>
<string>Access contacts for shipping and billing information</string>
```

## Step 2: Initialise the iOS SDK

Import the PXP Checkout SDK and initialise it with Apple Pay support.

```swift
import PXPCheckoutSDK

let sessionData = SessionData(
    sessionId: "your-session-id",
    hmacKey: "your-hmac-key",
    encryptionKey: "your-encryption-key",
    allowedFundingTypes: AllowedFundingType(
        wallets: Wallets(
            applePay: ApplePay(merchantId: "merchant.com.yourcompany.yourapp")
        )
    )
)

let checkoutConfig = CheckoutConfig(
    environment: .test,
    session: sessionData,
    transactionData: TransactionData(
        amount: 25.00,
        currency: "USD",
        entryType: .ecom,
        intent: TransactionIntentData(card: .purchase),
        merchantTransactionId: UUID().uuidString,
        merchantTransactionDate: { Date() }
    ),
    merchantShopperId: "shopper-id",
    ownerType: "MerchantGroup",
    ownerId: "your-owner-id",
    onGetShopper: { async in
        TransactionShopper(id: "shopper-id", email: "customer@example.com")
    }
)

let checkout = try PxpCheckout.initialize(config: checkoutConfig)
```

`session.allowedFundingTypes.wallets.applePay.merchantId` is required to create `.applePayButton` (validated in `BasePxpCheckout.validateFundingTypeSupport`).

### Configuration parameters

| Property | Description |
|  --- | --- |
| `environment`Environment | The environment type.Possible values:`.test`: For sandbox`.live`: For production |
| `session`SessionData | Details about the checkout session. |
| `session.sessionId`String | The unique session identifier. |
| `session.hmacKey`String | HMAC key from your session response. |
| `session.encryptionKey`String | Encryption key from your session response. |
| `session.allowedFundingTypes.wallets.applePay.merchantId`String | Your Apple Pay merchant identifier. Required to create `.applePayButton`. |
| `ownerId`String | The identifier of the owner related to the `ownerType`. |
| `ownerType`String? | The type of owner (e.g. `"MerchantGroup"`). |
| `merchantShopperId`String | A unique identifier for this shopper. |
| `transactionData`TransactionData | Details about the transaction. |
| `transactionData.currency`String | The currency code, in ISO 4217 format. |
| `transactionData.amount`Decimal | The transaction amount. |
| `transactionData.entryType`EntryType | The entry type.Possible values:`.ecom`: E-commerce transactions`.moto`: Mail order/telephone order |
| `transactionData.intent`TransactionIntentData | The transaction intent. Apple Pay reads `intent.card`.Card intent values include:`.authorisation``.purchase``.verification``.estimatedAuthorisation``.payout` |
| `transactionData.merchantTransactionId`String | A unique identifier for this transaction. |
| `transactionData.merchantTransactionDate`() -> Date | A closure returning the date and time of the transaction. |
| `onGetShopper`() async -> TransactionShopper? | Async callback returning shopper details (including email). |
| `restrictions`Restrictions? | Optional card restrictions for owner types and funding sources. When both session and config restrictions are provided, they are merged as a union. See [Card restrictions](#card-restrictions) for details. |
| `kountDisabled`Bool | Whether to disable the Kount fraud detection service. Defaults to `false` (fraud detection enabled). |


### Card restrictions

You can restrict which cards are accepted in Apple Pay based on owner type (corporate or consumer) and funding source (credit, debit, or prepaid). Restrictions can be set in two places:

- **Session-level**: returned from the backend in `SessionData.restrictions`.
- **Config-level**: passed directly to `CheckoutConfig.restrictions`.


When both are provided, they're merged as a union (session values first, then config values not already present).

#### Structure

```swift
let restrictions = Restrictions(
    card: Restrictions.Card(
        ownerTypes: [.corporate, .consumer],    // Optional
        fundingSources: [.credit, .debit]       // Optional
    )
)

let checkoutConfig = CheckoutConfig(
    // ... other parameters
    restrictions: restrictions
)
```

#### Owner types

| Value | Description |
|  --- | --- |
| `.corporate` | Corporate/business cards |
| `.consumer` | Consumer/personal cards |


#### Funding sources

| Value | Description |
|  --- | --- |
| `.credit` | Credit cards |
| `.debit` | Debit cards |
| `.prepaid` | Prepaid cards |


#### Example: Accept only consumer credit and debit cards

```swift
let checkoutConfig = CheckoutConfig(
    environment: .test,
    session: sessionData,
    transactionData: transactionData,
    merchantShopperId: "shopper-id",
    ownerId: "your-owner-id",
    restrictions: Restrictions(
        card: Restrictions.Card(
            ownerTypes: [.consumer],
            fundingSources: [.credit, .debit]
        )
    )
)
```

If `ownerTypes` or `fundingSources` is `nil`, no restriction is applied for that dimension. Setting both to `nil` means all cards are accepted.

## Step 3: Create the component configuration

Next, create the Apple Pay component configuration with your specific requirements. Set all callbacks on `ApplePayButtonComponentConfig` before `checkout.create(.applePayButton, componentConfig:)`. Component `config` isn't a public post-create mutation surface.

```swift
private func createApplePayConfiguration() -> ApplePayButtonComponentConfig {
    let config = ApplePayButtonComponentConfig()
    
    // Basic configuration
    config.paymentDescription = "Purchase from Your Store"
    config.currencyCode = "USD"
    config.countryCode = "US"
    config.supportedNetworks = [.visa, .masterCard, .amex]
    config.merchantCapabilities = [.threeDSecure, .emv]
    
    // Button styling
    config.buttonType = .buy
    config.buttonStyle = .black
    config.buttonRadius = 8.0
    
    // Payment items
    config.totalPaymentItem = ApplePayPaymentSummaryItem(
        amount: 25.00,
        type: .final,
        label: "Your Store"  // merchant-facing total label
    )
    
    config.paymentItems = [
        ApplePayPaymentSummaryItem(amount: 20.00, type: .final, label: "Product"),
        ApplePayPaymentSummaryItem(amount: 3.00, type: .final, label: "Tax"),
        ApplePayPaymentSummaryItem(amount: 2.00, type: .final, label: "Shipping")
    ]
    
    // Contact fields
    config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
    config.requiredShippingContactFields = [.postalAddress, .name, .phoneNumber]
    
    // Shipping methods
    config.shippingMethods = [
        ApplePayShippingMethod(
            amount: 2.00,
            detail: "5-7 business days",
            identifier: "standard",
            label: "Standard Shipping"
        ),
        ApplePayShippingMethod(
            amount: 5.00,
            detail: "2-3 business days",
            identifier: "express",
            label: "Express Shipping"
        )
    ]
    
    // Event handlers — set before create()
    config.onPreAuthorisation = { async in
        ApplePayTransactionInitData(riskScreeningData: nil)
    }
    
    config.onPostAuthorisation = { submitResult, applePayResult in
        if let success = submitResult as? MerchantSubmitResult {
            _ = success.systemTransactionId
            _ = applePayResult.shippingContact
        } else if let failed = submitResult as? FailedSubmitResult {
            _ = failed.errorReason
        }
    }
    
    config.onShippingAddressChange = { contact in
        ApplePayRequestUpdate(
            totalPaymentItem: ApplePayPaymentSummaryItem(amount: 25.00, type: .final, label: "Your Store"),
            paymentSummaryItems: [/* … */],
            shippingMethods: [/* … */]
        )
    }
    
    config.onError = { exception in
        print(exception.errorCode, exception.errorMessage)
    }
    
    config.onCancel = { exception in
        print(exception.errorMessage)
    }
    
    if #available(iOS 15.0, *) {
        config.supportsCouponCode = true
        config.onCouponSelected = { couponCode in
            ApplePayRequestUpdate(paymentSummaryItems: [/* … */])
        }
    }
    
    return config
}
```

### Configuration parameters

| Parameter | Description |
|  --- | --- |
| `paymentDescription`String (≤ 128 characters) | A description of the payment that appears to customers. |
| `currencyCode`String | The currency code in ISO 4217 format (e.g., "USD"). |
| `countryCode`String | The merchant's country code in ISO 3166-1 alpha-2 format (e.g., "US"). |
| `supportedNetworks`[PaymentNetwork] | Supported card networks.Possible values:`.visa``.masterCard``.amex``.discover``.jcb``.chinaUnionPay` |
| `merchantCapabilities`[MerchantCapability] | Payment processing capabilities.Possible values:`.threeDSecure`: 3D Secure support`.emv`: EMV support`.credit`: Credit card support`.debit`: Debit card support |
| `buttonType`ApplePaymentButtonType | The button type.Possible values:`.plain`: Apple Pay logo only`.buy`: Purchase button`.pay`: Payment button`.donate`: Donation button`.checkout`: Checkout button`.book`: Booking button`.subscribe`: Subscription button |
| `buttonStyle`ApplePaymentButtonStyle | The button style.Possible values:`.black`: Black background with white text`.white`: White background with black text`.whiteOutline`: White background with black border`.automatic`: Adapts to system appearance (iOS 13+) |
| `buttonRadius`CGFloat | The button corner radius (default: 4.0). |
| `totalPaymentItem`ApplePayPaymentSummaryItem | The total payment amount display. The `label` is the merchant-facing name shown in the Apple Pay sheet. |
| `paymentItems`[ApplePayPaymentSummaryItem] | Individual line items for the payment. |
| `requiredBillingContactFields`[ContactField] | Required billing contact fields.Possible values:`.postalAddress``.name``.emailAddress``.phoneNumber` |
| `requiredShippingContactFields`[ContactField] | Required shipping contact fields. |
| `shippingMethods`[ApplePayShippingMethod] | Available shipping methods. |
| `supportsCouponCode`Bool? | Enable coupon code entry in the Apple Pay sheet (iOS 15.0+). Set `true` when using `onCouponSelected`. |
| `couponCode`String? | Optional pre-filled coupon code (iOS 15.0+). |


### Callback reference

| Callback | Signature |
|  --- | --- |
| `onPreAuthorisation` | `() async -> ApplePayTransactionInitData?` |
| `onPostAuthorisation` | `(BaseSubmitResult, ApplePayResult) -> Void` |
| `onError` | `(BaseSdkException) -> Void` |
| `onCancel` | `(BaseSdkException) -> Void` |
| `onShippingAddressChange` | `(ApplePayContact?) -> ApplePayRequestUpdate?` |
| `onShippingOptionChange` | `(ApplePayShippingMethod?) -> ApplePayRequestUpdate?` |
| `onPaymentMethodChange` | `(ApplePayPaymentMethod?) -> ApplePayRequestUpdate?` |
| `onCouponSelected` | `(String) -> ApplePayRequestUpdate?` |


## Step 4: Setup the view layout

Create your view layout to include the Apple Pay button container.

### Using Storyboard

```swift
@IBOutlet weak var paymentSummaryView: UIView!
@IBOutlet weak var applePayContainer: UIView!
@IBOutlet weak var errorMessageLabel: UILabel!
@IBOutlet weak var successMessageLabel: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()
    setupUI()
    initializeSDK()
}

private func setupUI() {
    // Configure payment summary
    setupPaymentSummary()
    
    // Configure Apple Pay container
    applePayContainer.layer.cornerRadius = 8
    applePayContainer.backgroundColor = .systemBackground
    
    // Hide message labels initially
    errorMessageLabel.isHidden = true
    successMessageLabel.isHidden = true
}

private func setupPaymentSummary() {
    // Create payment summary views
    let stackView = UIStackView()
    stackView.axis = .vertical
    stackView.spacing = 8
    stackView.translatesAutoresizingMaskIntoConstraints = false
    
    // Add line items
    stackView.addArrangedSubview(createLineItem(label: "Premium T-Shirt", amount: "$20.00"))
    stackView.addArrangedSubview(createLineItem(label: "Sales Tax", amount: "$3.00"))
    stackView.addArrangedSubview(createLineItem(label: "Shipping", amount: "$2.00"))
    
    // Add separator
    let separator = UIView()
    separator.backgroundColor = .separator
    separator.heightAnchor.constraint(equalToConstant: 1).isActive = true
    stackView.addArrangedSubview(separator)
    
    // Add total
    let totalView = createLineItem(label: "Total", amount: "$25.00", isTotal: true)
    stackView.addArrangedSubview(totalView)
    
    paymentSummaryView.addSubview(stackView)
    
    NSLayoutConstraint.activate([
        stackView.topAnchor.constraint(equalTo: paymentSummaryView.topAnchor, constant: 16),
        stackView.leadingAnchor.constraint(equalTo: paymentSummaryView.leadingAnchor, constant: 16),
        stackView.trailingAnchor.constraint(equalTo: paymentSummaryView.trailingAnchor, constant: -16),
        stackView.bottomAnchor.constraint(equalTo: paymentSummaryView.bottomAnchor, constant: -16)
    ])
}

private func createLineItem(label: String, amount: String, isTotal: Bool = false) -> UIView {
    let containerView = UIView()
    
    let labelView = UILabel()
    labelView.text = label
    labelView.font = isTotal ? .systemFont(ofSize: 18, weight: .semibold) : .systemFont(ofSize: 16)
    labelView.translatesAutoresizingMaskIntoConstraints = false
    
    let amountLabel = UILabel()
    amountLabel.text = amount
    amountLabel.font = isTotal ? .systemFont(ofSize: 18, weight: .semibold) : .systemFont(ofSize: 16)
    amountLabel.textAlignment = .right
    amountLabel.translatesAutoresizingMaskIntoConstraints = false
    
    containerView.addSubview(labelView)
    containerView.addSubview(amountLabel)
    
    NSLayoutConstraint.activate([
        labelView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        labelView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
        labelView.topAnchor.constraint(equalTo: containerView.topAnchor),
        labelView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
        
        amountLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        amountLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
        amountLabel.leadingAnchor.constraint(greaterThanOrEqualTo: labelView.trailingAnchor, constant: 8)
    ])
    
    return containerView
}
```

### Using SwiftUI

```swift
import SwiftUI
import PXPCheckoutSDK

struct CheckoutView: View {
    @State private var checkout: PxpCheckout?
    @State private var applePayComponent: BaseComponent?
    @State private var errorMessage: String = ""
    @State private var successMessage: String = ""
    @State private var showingError = false
    @State private var showingSuccess = false
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Complete your purchase")
                .font(.largeTitle)
                .fontWeight(.bold)
            
            // Payment summary
            VStack(spacing: 12) {
                HStack {
                    Text("Premium T-Shirt")
                    Spacer()
                    Text("$20.00")
                }
                
                HStack {
                    Text("Sales Tax")
                    Spacer()
                    Text("$3.00")
                }
                
                HStack {
                    Text("Shipping")
                    Spacer()
                    Text("$2.00")
                }
                
                Divider()
                
                HStack {
                    Text("Total")
                        .fontWeight(.semibold)
                    Spacer()
                    Text("$25.00")
                        .fontWeight(.semibold)
                }
                .font(.title3)
            }
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(12)
            
            // Error/Success Messages
            if showingError {
                Text(errorMessage)
                    .foregroundColor(.red)
                    .padding()
                    .background(Color.red.opacity(0.1))
                    .cornerRadius(8)
            }
            
            if showingSuccess {
                Text(successMessage)
                    .foregroundColor(.green)
                    .padding()
                    .background(Color.green.opacity(0.1))
                    .cornerRadius(8)
            }
            
            // Apple Pay button
            if let component = applePayComponent {
                component.buildContent()
                    .frame(height: 50)
            }
            
            Text("Or pay with credit card")
                .foregroundColor(.secondary)
                .font(.caption)
            
            Spacer()
        }
        .padding()
        .onAppear {
            initializeSDK()
        }
    }
    
    private func initializeSDK() {
        // Initialise SDK (same as UIKit example)
    }
}
```

## Step 5: Create and mount the component

Create the Apple Pay component and display it in your view. `create(.applePayButton, componentConfig:)` returns `BaseComponent`; keep it as `BaseComponent` or cast with `as? ApplePayButtonComponent` if you need the concrete type.

```swift
private func createAndMountApplePayComponent() {
    guard let checkout = checkout else {
        print("Checkout SDK not initialized")
        return
    }
    
    do {
        let config = createApplePayConfiguration()
        let component = try checkout.create(.applePayButton, componentConfig: config)
        applePayComponent = component // BaseComponent is sufficient for buildContent()
        
        // SwiftUI
        // component.buildContent().frame(height: 50)
        
        print("Apple Pay component created successfully")
        
    } catch {
        print("Failed to create Apple Pay component: \(error)")
        showError("Apple Pay is not available on this device")
    }
}
```

For UIKit hosts, wrap `buildContent()` in a `UIHostingController` rather than adding a `UIView` from `render()`:

```swift
private func mountComponent(_ component: BaseComponent) {
    let hostingController = UIHostingController(
        rootView: component.buildContent().frame(height: 50)
    )
    hostingController.view.translatesAutoresizingMaskIntoConstraints = false
    addChild(hostingController)
    applePayContainer.addSubview(hostingController.view)
    
    NSLayoutConstraint.activate([
        hostingController.view.topAnchor.constraint(equalTo: applePayContainer.topAnchor),
        hostingController.view.leadingAnchor.constraint(equalTo: applePayContainer.leadingAnchor),
        hostingController.view.trailingAnchor.constraint(equalTo: applePayContainer.trailingAnchor),
        hostingController.view.bottomAnchor.constraint(equalTo: applePayContainer.bottomAnchor)
    ])
    
    hostingController.didMove(toParent: self)
}
```

The SDK also ships `ApplePayButtonView` as a low-level PK button helper used internally by `ApplePayButtonComponent.buildView()`.

## Step 6: Handle payment results

Implement the event handlers to process payment results and user interactions.

```swift
// MARK: - Event Handlers

private func handlePreAuthorisation() async -> ApplePayTransactionInitData? {
    print("Pre-authorisation started")
    
    DispatchQueue.main.async {
        self.showMessage("Processing payment...", type: .info)
    }
    
    return ApplePayTransactionInitData(
        riskScreeningData: RiskScreeningData(
            performRiskScreening: true,
            userIp: "192.168.1.100",
            account: RiskScreeningAccount(
                id: "user_12345678",
                creationDateTime: ISO8601DateFormatter().date(from: "2024-01-15T10:30:00Z")
            ),
            items: [
                RiskScreeningItem(
                    price: 99.99,
                    quantity: 1,
                    category: "Electronics",
                    sku: "PROD-001"
                )
            ],
            fulfillments: [
                RiskScreeningFulfillment(
                    type: .shipped,
                    shipping: RiskScreeningShipping(shippingMethod: .express),
                    recipientPerson: RiskScreeningRecipientPerson(
                        phoneNumber: "+1234567890"
                    )
                )
            ]
        )
    )
}

private func handlePostAuthorisation(_ result: BaseSubmitResult, _ applePayResult: ApplePayResult) {
    DispatchQueue.main.async {
        if let success = result as? MerchantSubmitResult {
            self.showMessage("Payment successful! Redirecting...", type: .success)
            print("Transaction ID: \(success.systemTransactionId)")
            
            // Navigate to success screen after delay
            DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
                self.navigateToSuccessScreen(transactionId: success.systemTransactionId)
            }
            
        } else if let failed = result as? FailedSubmitResult {
            self.showMessage("Payment failed: \(failed.errorReason ?? "")", type: .error)
            
        } else if let unknown = result as? UnknownSubmitResult {
            self.showMessage("Unexpected result: \(unknown.stateData.message ?? "")", type: .error)
        }
    }
}

private func handleError(_ exception: BaseSdkException) {
    print("Apple Pay error: \(exception.errorMessage)")
    
    DispatchQueue.main.async {
        self.showMessage("Payment error: \(exception.errorMessage)", type: .error)
    }
}

private func handleCancellation(_ exception: BaseSdkException) {
    print("Payment cancelled by user")
    DispatchQueue.main.async {
        self.showMessage("Payment was cancelled", type: .info)
    }
}

// MARK: - Helper methods

private func getDeviceSessionId() async -> String {
    return UIDevice.current.identifierForVendor?.uuidString ?? "unknown-device"
}

private func showMessage(_ message: String, type: MessageType) {
    errorMessageLabel.isHidden = true
    successMessageLabel.isHidden = true
    
    switch type {
    case .error:
        errorMessageLabel.text = message
        errorMessageLabel.isHidden = false
    case .success:
        successMessageLabel.text = message
        successMessageLabel.isHidden = false
    case .info:
        successMessageLabel.text = message
        successMessageLabel.textColor = .systemBlue
        successMessageLabel.isHidden = false
    }
}

private func navigateToSuccessScreen(transactionId: String) {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    if let successVC = storyboard.instantiateViewController(withIdentifier: "SuccessViewController") as? SuccessViewController {
        successVC.transactionId = transactionId
        navigationController?.pushViewController(successVC, animated: true)
    }
}

enum MessageType {
    case error, success, info
}
```

## Step 7: Component lifecycle management

Properly manage the component lifecycle to prevent memory leaks and ensure clean transitions. Call `unmount()` on `PxpCheckout`, not on `ApplePayButtonComponent`.

```swift
class CheckoutViewController: UIViewController {
    
    // MARK: - Lifecycle
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // Clean up component if navigating away
        if isMovingFromParent || isBeingDismissed {
            cleanup()
        }
    }
    
    deinit {
        cleanup()
    }
    
    private func cleanup() {
        checkout?.unmount()
        applePayComponent = nil
        checkout = nil
        print("Apple Pay component cleaned up")
    }
    
    // MARK: - Error recovery
    
    private func retryComponentCreation() {
        cleanup()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.initializeSDK()
            self.createAndMountApplePayComponent()
        }
    }
}
```

## What's next?

### Customise the look and feel

You can configure the appearance and behaviour of the Apple Pay component to fit your brand. We've documented all configurable parameters in the [Customisation](/guides/checkout/components/ios/apple-pay/customisation) page.

```swift
// Custom button styling
config.buttonType = .buy
config.buttonStyle = .black
config.buttonRadius = 12.0

// Custom SwiftUI content
config.customContent = {
    return AnyView(
        HStack {
            Image(systemName: "applelogo")
                .foregroundColor(.white)
            Text("Buy with Apple Pay")
                .foregroundColor(.white)
                .fontWeight(.semibold)
        }
        .frame(maxWidth: .infinity, minHeight: 50)
        .background(
            LinearGradient(
                gradient: Gradient(colors: [Color.black, Color.gray]),
                startPoint: .leading,
                endPoint: .trailing
            )
        )
        .cornerRadius(12)
    )
}
```

### Add more event handling

The Apple Pay component emits events based on user interaction, shipping changes, and payment method updates. For more information about all the available events, see the [Events](/guides/checkout/components/ios/apple-pay/events) page.

### Add error handling and validation

Error handling is crucial for payment components because they deal with sensitive financial data and complex validation rules. For more details about error handling, see the [Data validation](/guides/checkout/components/ios/apple-pay/data-validation) page.

```swift
// Comprehensive error handling
config.onError = { exception in
    DispatchQueue.main.async {
        print("Error [\(exception.errorCode)]: \(exception.errorMessage)")
    }
}
```

## Complete UIKit example

```swift
import UIKit
import SwiftUI
import PXPCheckoutSDK
import PassKit

class CompleteCheckoutViewController: UIViewController {
    
    // MARK: - Outlets
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var paymentSummaryView: UIView!
    @IBOutlet weak var applePayContainer: UIView!
    @IBOutlet weak var errorMessageLabel: UILabel!
    @IBOutlet weak var successMessageLabel: UILabel!
    @IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
    
    // MARK: - Properties
    private var checkout: PxpCheckout?
    private var applePayComponent: BaseComponent?
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        initializeSDK()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if isMovingFromParent || isBeingDismissed {
            cleanupComponent()
        }
    }
    
    deinit {
        cleanupComponent()
    }
    
    // MARK: - Setup
    private func setupUI() {
        titleLabel.text = "Complete your purchase"
        titleLabel.font = UIFont.systemFont(ofSize: 28, weight: .bold)
        
        setupPaymentSummary()
        
        applePayContainer.layer.cornerRadius = 8
        applePayContainer.backgroundColor = .systemBackground
        
        errorMessageLabel.isHidden = true
        successMessageLabel.isHidden = true
        loadingIndicator.isHidden = true
        
        view.backgroundColor = .systemBackground
    }
    
    private func setupPaymentSummary() {
        paymentSummaryView.backgroundColor = UIColor.systemGray6
        paymentSummaryView.layer.cornerRadius = 12
        
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.spacing = 12
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        // Add line items
        stackView.addArrangedSubview(createLineItem(label: "Premium T-Shirt", amount: "$20.00"))
        stackView.addArrangedSubview(createLineItem(label: "Sales Tax", amount: "$3.00"))
        stackView.addArrangedSubview(createLineItem(label: "Shipping", amount: "$2.00"))
        
        // Add separator
        let separator = UIView()
        separator.backgroundColor = .separator
        separator.heightAnchor.constraint(equalToConstant: 1).isActive = true
        stackView.addArrangedSubview(separator)
        
        // Add total
        stackView.addArrangedSubview(createLineItem(label: "Total", amount: "$25.00", isTotal: true))
        
        paymentSummaryView.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: paymentSummaryView.topAnchor, constant: 16),
            stackView.leadingAnchor.constraint(equalTo: paymentSummaryView.leadingAnchor, constant: 16),
            stackView.trailingAnchor.constraint(equalTo: paymentSummaryView.trailingAnchor, constant: -16),
            stackView.bottomAnchor.constraint(equalTo: paymentSummaryView.bottomAnchor, constant: -16)
        ])
    }
    
    private func createLineItem(label: String, amount: String, isTotal: Bool = false) -> UIView {
        let containerView = UIView()
        
        let labelView = UILabel()
        labelView.text = label
        labelView.font = isTotal ? .systemFont(ofSize: 18, weight: .semibold) : .systemFont(ofSize: 16)
        labelView.translatesAutoresizingMaskIntoConstraints = false
        
        let amountLabel = UILabel()
        amountLabel.text = amount
        amountLabel.font = isTotal ? .systemFont(ofSize: 18, weight: .semibold) : .systemFont(ofSize: 16)
        amountLabel.textAlignment = .right
        amountLabel.translatesAutoresizingMaskIntoConstraints = false
        
        containerView.addSubview(labelView)
        containerView.addSubview(amountLabel)
        
        NSLayoutConstraint.activate([
            labelView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            labelView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
            labelView.topAnchor.constraint(equalTo: containerView.topAnchor),
            labelView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            
            amountLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            amountLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
            amountLabel.leadingAnchor.constraint(greaterThanOrEqualTo: labelView.trailingAnchor, constant: 8)
        ])
        
        return containerView
    }
    
    // MARK: - SDK Initialisation
    private func initializeSDK() {
        let sessionData = SessionData(
            sessionId: "your-session-id",
            hmacKey: "your-hmac-key",
            encryptionKey: "your-encryption-key",
            allowedFundingTypes: AllowedFundingType(
                wallets: Wallets(
                    applePay: ApplePay(merchantId: "merchant.com.yourcompany.yourapp")
                )
            )
        )

        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: sessionData,
            transactionData: TransactionData(
                amount: 25.00,
                currency: "USD",
                entryType: .ecom,
                intent: TransactionIntentData(card: .purchase),
                merchantTransactionId: UUID().uuidString,
                merchantTransactionDate: { Date() }
            ),
            merchantShopperId: "shopper-id",
            ownerType: "MerchantGroup",
            ownerId: "your-owner-id",
            kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
            onGetShopper: { async in
                TransactionShopper(id: "shopper-id", email: "customer@example.com")
            }
        )
        
        do {
            checkout = try PxpCheckout.initialize(config: checkoutConfig)
            createAndMountApplePayComponent()
        } catch {
            print("Failed to initialise SDK: \(error)")
            showMessage("Failed to initialise payment system", type: .error)
        }
    }
    
    private func createAndMountApplePayComponent() {
        guard let checkout = checkout else { return }
        
        // Check if Apple Pay is available
        guard PKPaymentAuthorizationController.canMakePayments() else {
            showMessage("Apple Pay is not available on this device", type: .error)
            return
        }
        
        do {
            let config = createApplePayConfiguration()
            let component = try checkout.create(.applePayButton, componentConfig: config)
            applePayComponent = component
            
            mountComponent(component)
            
        } catch {
            print("Failed to create Apple Pay component: \(error)")
            showMessage("Apple Pay is not available", type: .error)
        }
    }
    
    private func createApplePayConfiguration() -> ApplePayButtonComponentConfig {
        let config = ApplePayButtonComponentConfig()
        
        // Basic configuration
        config.paymentDescription = "Premium T-Shirt Purchase"
        config.currencyCode = "USD"
        config.countryCode = "US"
        config.supportedNetworks = [.visa, .masterCard, .amex, .discover]
        config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]
        
        // Button styling
        config.buttonType = .buy
        config.buttonStyle = .black
        config.buttonRadius = 8.0
        
        // Payment items
        config.totalPaymentItem = ApplePayPaymentSummaryItem(
            amount: 25.00,
            type: .final,
            label: "Your Store Name"
        )
        
        config.paymentItems = [
            ApplePayPaymentSummaryItem(amount: 20.00, type: .final, label: "Premium T-Shirt"),
            ApplePayPaymentSummaryItem(amount: 3.00, type: .final, label: "Sales Tax"),
            ApplePayPaymentSummaryItem(amount: 2.00, type: .final, label: "Shipping")
        ]
        
        // Contact fields
        config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
        config.requiredShippingContactFields = [.postalAddress, .name, .phoneNumber]
        
        // Shipping methods
        config.shippingMethods = [
            ApplePayShippingMethod(
                amount: 2.00,
                detail: "5-7 business days",
                identifier: "standard",
                label: "Standard Shipping"
            ),
            ApplePayShippingMethod(
                amount: 5.00,
                detail: "2-3 business days", 
                identifier: "express",
                label: "Express Shipping"
            )
        ]
        
        // Event handlers
        config.onPreAuthorisation = { [weak self] in
            return await self?.handlePreAuthorisation()
        }
        
        config.onPostAuthorisation = { [weak self] result, applePayResult in
            self?.handlePostAuthorisation(result, applePayResult)
        }
        
        config.onShippingAddressChange = { [weak self] contact in
            return self?.handleShippingAddressChange(contact)
        }
        
        config.onShippingOptionChange = { [weak self] method in
            return self?.handleShippingOptionChange(method)
        }
        
        config.onError = { [weak self] exception in
            self?.handleError(exception)
        }
        
        config.onCancel = { [weak self] exception in
            self?.handleCancellation(exception)
        }
        
        return config
    }
    
    private func mountComponent(_ component: BaseComponent) {
        applePayContainer.subviews.forEach { $0.removeFromSuperview() }
        let hostingController = UIHostingController(
            rootView: component.buildContent().frame(height: 50)
        )
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        addChild(hostingController)
        applePayContainer.addSubview(hostingController.view)
        
        NSLayoutConstraint.activate([
            hostingController.view.topAnchor.constraint(equalTo: applePayContainer.topAnchor),
            hostingController.view.leadingAnchor.constraint(equalTo: applePayContainer.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: applePayContainer.trailingAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: applePayContainer.bottomAnchor)
        ])
        
        hostingController.didMove(toParent: self)
    }
    
    // MARK: - Event Handlers
    private func handlePreAuthorisation() async -> ApplePayTransactionInitData? {
        DispatchQueue.main.async {
            self.showMessage("Processing payment...", type: .info)
            self.loadingIndicator.startAnimating()
            self.loadingIndicator.isHidden = false
        }
        
        return ApplePayTransactionInitData(
            riskScreeningData: RiskScreeningData(
                performRiskScreening: true,
                userIp: "192.168.1.100",
                account: RiskScreeningAccount(
                    id: "user_12345678",
                    creationDateTime: ISO8601DateFormatter().date(from: "2024-01-15T10:30:00Z")
                ),
                fulfillments: [
                    RiskScreeningFulfillment(
                        type: .shipped,
                        shipping: RiskScreeningShipping(shippingMethod: .express),
                        recipientPerson: RiskScreeningRecipientPerson(
                            phoneNumber: "+1234567890"
                        )
                    )
                ]
            )
        )
    }
    
    private func handlePostAuthorisation(_ result: BaseSubmitResult, _ applePayResult: ApplePayResult) {
        DispatchQueue.main.async {
            self.loadingIndicator.stopAnimating()
            self.loadingIndicator.isHidden = true
            
            if let success = result as? MerchantSubmitResult {
                self.showMessage("Payment successful! Redirecting...", type: .success)
                print("Transaction ID: \(success.systemTransactionId)")
                
                DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
                    self.navigateToSuccessScreen(transactionId: success.systemTransactionId)
                }
                
            } else if let failed = result as? FailedSubmitResult {
                self.showMessage("Payment failed: \(failed.errorReason ?? "")", type: .error)
                
            } else if let unknown = result as? UnknownSubmitResult {
                self.showMessage("Unexpected result: \(unknown.stateData.message ?? "")", type: .error)
            }
        }
    }
    
    private func handleShippingAddressChange(_ contact: ApplePayContact?) -> ApplePayRequestUpdate? {
        let shippingCost = calculateShippingCost(for: contact)
        let tax = calculateTax(for: contact)
        let newTotal = 20.00 + shippingCost + tax
        
        return ApplePayRequestUpdate(
            totalPaymentItem: ApplePayPaymentSummaryItem(amount: newTotal, type: .final, label: "Your Store Name"),
            paymentSummaryItems: [
                ApplePayPaymentSummaryItem(amount: 20.00, type: .final, label: "Premium T-Shirt"),
                ApplePayPaymentSummaryItem(amount: tax, type: .final, label: "Sales Tax"),
                ApplePayPaymentSummaryItem(amount: shippingCost, type: .final, label: "Shipping")
            ],
            shippingMethods: [
                ApplePayShippingMethod(
                    amount: shippingCost,
                    detail: "5-7 business days",
                    identifier: "standard",
                    label: "Standard Shipping"
                )
            ]
        )
    }
    
    private func handleShippingOptionChange(_ method: ApplePayShippingMethod?) -> ApplePayRequestUpdate? {
        let baseAmount = 20.00
        let tax = 3.00
        let shippingCost = method?.amount ?? 2.00
        let newTotal = baseAmount + tax + shippingCost
        
        return ApplePayRequestUpdate(
            paymentSummaryItems: [
                ApplePayPaymentSummaryItem(amount: baseAmount, type: .final, label: "Premium T-Shirt"),
                ApplePayPaymentSummaryItem(amount: tax, type: .final, label: "Sales Tax"),
                ApplePayPaymentSummaryItem(amount: shippingCost, type: .final, label: "Shipping"),
                ApplePayPaymentSummaryItem(amount: newTotal, type: .final, label: "Your Store Name")
            ]
        )
    }
    
    private func handleError(_ exception: BaseSdkException) {
        DispatchQueue.main.async {
            self.loadingIndicator.stopAnimating()
            self.loadingIndicator.isHidden = true
            self.showMessage("Payment error: \(exception.errorMessage)", type: .error)
        }
    }
    
    private func handleCancellation(_ exception: BaseSdkException) {
        DispatchQueue.main.async {
            self.loadingIndicator.stopAnimating()
            self.loadingIndicator.isHidden = true
            self.showMessage("Payment was cancelled", type: .info)
        }
    }
    
    // MARK: - Helper Methods
    private func showMessage(_ message: String, type: MessageType) {
        errorMessageLabel.isHidden = true
        successMessageLabel.isHidden = true
        
        switch type {
        case .error:
            errorMessageLabel.text = message
            errorMessageLabel.isHidden = false
        case .success:
            successMessageLabel.text = message
            successMessageLabel.textColor = .systemGreen
            successMessageLabel.isHidden = false
        case .info:
            successMessageLabel.text = message
            successMessageLabel.textColor = .systemBlue
            successMessageLabel.isHidden = false
        }
    }
    
    private func calculateShippingCost(for contact: ApplePayContact?) -> Decimal {
        guard let countryCode = contact?.countryCode else { return 5.00 }
        if countryCode == "US" {
            return contact?.administrativeArea == "CA" ? 7.99 : 4.99
        }
        return 15.99
    }
    
    private func calculateTax(for contact: ApplePayContact?) -> Decimal {
        guard contact?.countryCode == "US" else { return 0.00 }
        
        let taxRates: [String: Decimal] = ["CA": 0.0875, "NY": 0.08, "TX": 0.0625]
        let rate = taxRates[contact?.administrativeArea ?? ""] ?? 0.06
        return 20.00 * rate
    }
    
    private func navigateToSuccessScreen(transactionId: String) {
        let alert = UIAlertController(
            title: "Payment Successful",
            message: "Transaction ID: \(transactionId)",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
            self.navigationController?.popViewController(animated: true)
        })
        present(alert, animated: true)
    }
    
    private func cleanupComponent() {
        checkout?.unmount()
        applePayComponent = nil
        checkout = nil
    }
    
    enum MessageType {
        case error, success, info
    }
}
```