Skip to content

Customisation

Learn about customisation options for the Apple Pay component for iOS applications.

Overview

The Apple Pay component comes with responsive and accessible default styling that follows Apple's design guidelines, but is designed to be customisable to match your brand and application requirements.

You can find Apple's official recommendations around button styling in their PKPaymentButton documentation and PassKit framework reference.

Default styling

The default implementation uses Apple's native PKPaymentButton component with the iOS SDK:

let config = ApplePayButtonComponentConfig()

// Basic styling configuration
config.buttonType = .buy
config.buttonStyle = .black
config.buttonRadius = 4.0

Default button types

TypeDescriptionUse case
.plainApple Pay logo only.
  • Minimal interfaces
  • Custom layouts
.buyStandard purchase button.
  • E-commerce
  • Retail
.setUpSetup button.
  • Account setup
  • Configuration
.inStoreIn-store payment button.
  • Point of sale
  • Retail locations
.donateDonation button.
  • Charities
  • Crowdfunding
.checkoutCheckout button.
  • Shopping cart
  • Final payment
.bookBooking button.
  • Travel
  • Reservations
.subscribeSubscribe button.
  • Newsletters
  • Services
.reloadReload button.
  • Gift cards
  • Prepaid
.addMoneyAdd money button.
  • Wallet top-up
  • Account credits
.topUpTop-up button.
  • Prepaid
  • Credits
.orderOrder button.
  • Food delivery
  • Services
.rentRent button.
  • Rentals
  • Subscriptions
.supportSupport button.
  • Support
  • Help
.contributeContribution button.
  • Subscriptions
  • Memberships
.tipTip button.
  • Gratuities
  • Tips

Default button styles

StyleDescription
.blackBlack background with white text.
.whiteWhite background with black text.
.whiteOutlineWhite background with a black border and text.
.automaticAdapts to the system's appearance (iOS 13+).

Custom styling implementation

When you need more control over the button appearance, you can customize various properties:

let config = ApplePayButtonComponentConfig()

// Button appearance customization
config.buttonType = .buy
config.buttonStyle = .black
config.buttonRadius = 8.0

// Optional: Custom SwiftUI content for advanced customization
config.customContent = {
    return AnyView(
        HStack(spacing: 8) {
            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(8)
    )
}

Configuration options

The Apple Pay component accepts the main configuration class that controls its appearance and behaviour.

ApplePayButtonComponentConfig

// Definition
class ApplePayButtonComponentConfig: BaseComponentConfig {
    // Basic payment configuration
    var merchantDisplayName: String
    var paymentDescription: String
    var currencyCode: String
    var countryCode: String
    var supportedNetworks: [PaymentNetwork]
    var merchantCapabilities: [MerchantCapability]
    
    // Button appearance
    var buttonType: ApplePaymentButtonType = .plain
    var buttonStyle: ApplePaymentButtonStyle = .black
    var buttonRadius: CGFloat = 4.0
    
    // Payment items
    var totalPaymentItem: ApplePayPaymentSummaryItem?
    var paymentItems: [ApplePayPaymentSummaryItem]?
    
    // Contact fields
    var requiredBillingContactFields: [ContactField]?
    var requiredShippingContactFields: [ContactField]?
    
    // Shipping configuration
    var shippingMethods: [ApplePayShippingMethod]?
    var shippingType: ShippingType?
    
    // Advanced features
    var recurringRequest: ApplePayRecurringPaymentRequest?
    var deferredPaymentRequest: ApplePayDeferredPaymentRequest?
    var automaticReloadPaymentRequest: ApplePayAutomaticReloadPaymentRequest?
    
    // Custom content
    var customContent: (() -> AnyView)?
    
    // Consent component
    weak var applePayConsentComponent: ApplePayConsentComponent?
    
    // Event handlers
    var onPreAuthorisation: (() async -> ApplePayTransactionInitData?)?
    var onPostAuthorisation: ((BaseSubmitResult) -> Void)?
    var onShippingContactSelected: ((PKContact) async -> PKPaymentRequestShippingContactUpdate)?
    var onShippingMethodSelected: ((PKShippingMethod) -> PKPaymentRequestShippingMethodUpdate)?
    var onPaymentMethodSelected: ((PKPaymentMethod) -> PKPaymentRequestPaymentMethodUpdate)?
    var onCouponCodeChanged: ((String) async -> PKPaymentRequestCouponCodeUpdate)?
    var onError: ((Error) -> Void)?
    var onCancel: ((Error?) -> Void)?
}

Example configuration

let config = ApplePayButtonComponentConfig()

// Required fields
config.merchantDisplayName = "Acme Store"
config.paymentDescription = "Product Purchase"
config.currencyCode = "USD"
config.countryCode = "US"
config.supportedNetworks = [.visa, .masterCard, .amex]
config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]

// Payment items
config.totalPaymentItem = ApplePayPaymentSummaryItem(
    amount: 99.99,
    label: "Total",
    type: .final
)

config.paymentItems = [
    ApplePayPaymentSummaryItem(amount: 89.99, label: "Product", type: .final),
    ApplePayPaymentSummaryItem(amount: 10.00, label: "Tax", type: .final)
]

// Button styling
config.buttonType = .buy
config.buttonStyle = .black
config.buttonRadius = 8.0

// Contact fields
config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]

// Event handlers
config.onPreAuthorisation = {
    return ApplePayTransactionInitData(
        riskScreeningData: RiskScreeningData(
            performRiskScreening: true,
            deviceSessionId: "session-123"
        )
    )
}

config.onPostAuthorisation = { result in
    if let authorizedResult = result as? AuthorisedSubmitResult {
        print("Payment successful: \(authorizedResult.provider.code)")
    } else if let failedResult = result as? FailedSubmitResult {
        print("Payment failed: \(failedResult.errorReason)")
    }
}

config.onShippingContactSelected = { contact in
    let shippingCost = 5.99
    let tax = 2.00
    let total = 89.99 + shippingCost + tax
    
    return PKPaymentRequestShippingContactUpdate(
        errors: [],
        shippingMethods: [
            PKShippingMethod(label: "Standard Shipping", amount: NSDecimalNumber(value: shippingCost))
        ],
        paymentSummaryItems: [
            PKPaymentSummaryItem(label: "Product", amount: NSDecimalNumber(value: 89.99)),
            PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(value: shippingCost)),
            PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(value: tax)),
            PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(value: total))
        ]
    )
}

config.onError = { error in
    print("Apple Pay error: \(error)")
    // Show error to user
}

config.onCancel = { error in
    print("Apple Pay cancelled by user")
    // Handle cancellation
}
PropertyDescription
merchantDisplayName
stringrequired
The display name for your business.
paymentDescription
stringrequired
A description of what the payment is for.
currencyCode
stringrequired
The currency code (ISO 4217, e.g., "USD").
countryCode
stringrequired
The country code (ISO 3166-1 alpha-2, e.g., "US").
supportedNetworks
[PaymentNetwork]required
The supported payment networks.
merchantCapabilities
[MerchantCapability]required
The merchant capabilities.
buttonType
ApplePaymentButtonType
The button type. Defaults to .plain.
buttonStyle
ApplePaymentButtonStyle
The button style. Defaults to .black.
buttonRadius
CGFloat
The button corner radius. Defaults to 4.0.
totalPaymentItem
ApplePayPaymentSummaryItem?
The total payment amount display.
paymentItems
[ApplePayPaymentSummaryItem]?
The line items to display.
requiredBillingContactFields
[ContactField]?
Required billing contact fields.
requiredShippingContactFields
[ContactField]?
Required shipping contact fields.
shippingMethods
[ApplePayShippingMethod]?
Available shipping methods.
customContent
(() -> AnyView)?
Custom SwiftUI content for the button.
applePayConsentComponent
ApplePayConsentComponent?
Consent component for data processing.
onPreAuthorisation
(() async -> ApplePayTransactionInitData?)?
Event handler for before payment authorisation.
onPostAuthorisation
((BaseSubmitResult) -> Void)?
Event handler for after payment authorisation.
onShippingContactSelectedEvent handler for shipping address changes.
onShippingMethodSelectedEvent handler for shipping method changes.
onPaymentMethodSelectedEvent handler for payment method changes.
onCouponCodeChangedEvent handler for coupon code changes (iOS 15.0+).
onError
((Error) -> Void)?
Event handler for errors.
onCancel
((Error?) -> Void)?
Event handler for cancellation.

Custom SwiftUI implementation

When you need complete control over the button appearance, use custom SwiftUI content:

let config = ApplePayButtonComponentConfig()

// Basic configuration
config.merchantDisplayName = "Urban Fashion Boutique"
config.paymentDescription = "Designer Jacket Purchase"
config.currencyCode = "USD"
config.countryCode = "US"
config.supportedNetworks = [.visa, .masterCard, .amex]
config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]

// Custom SwiftUI button
config.customContent = {
    return AnyView(
        HStack(spacing: 12) {
            // Apple Pay logo
            Image(systemName: "applelogo")
                .font(.title2)
                .foregroundColor(.white)
            
            // Custom text
            VStack(alignment: .leading, spacing: 2) {
                Text("Pay with")
                    .font(.caption)
                    .foregroundColor(.white.opacity(0.8))
                Text("Apple Pay")
                    .font(.headline)
                    .fontWeight(.semibold)
                    .foregroundColor(.white)
            }
            
            Spacer()
            
            // Security indicator
            Image(systemName: "lock.shield.fill")
                .font(.title3)
                .foregroundColor(.green)
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 12)
        .frame(maxWidth: .infinity, minHeight: 52)
        .background(
            LinearGradient(
                gradient: Gradient(colors: [
                    Color.black,
                    Color.gray.opacity(0.8)
                ]),
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
        )
        .overlay(
            RoundedRectangle(cornerRadius: 12)
                .stroke(Color.white.opacity(0.2), lineWidth: 1)
        )
        .cornerRadius(12)
        .shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4)
    )
}

Examples

Basic customisation with native button

import UIKit
import PXPCheckoutSDK

class BasicCustomizationViewController: UIViewController {
    
    private var applePayComponent: ApplePayButtonComponent?
    private var checkout: PxpCheckout?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupBasicApplePay()
    }
    
    private func setupBasicApplePay() {
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: SessionConfig(sessionId: "your-session-id"),
            transactionData: TransactionData(
                amount: 129.99,
                currency: "USD",
                entryType: .ecom,
                intent: .sale,
                merchantTransactionId: "order-\(Int(Date().timeIntervalSince1970))",
                merchantTransactionDate: Date()
            )
        )
        
        do {
            checkout = try PxpCheckout.initialize(config: checkoutConfig)
            let applePayConfig = createBasicApplePayConfig()
            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 createBasicApplePayConfig() -> ApplePayButtonComponentConfig {
        let config = ApplePayButtonComponentConfig()
        
        // Basic configuration
        config.merchantDisplayName = "Acme Electronics Store"
        config.paymentDescription = "Wireless Headphones 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: 129.99,
            label: "Total",
            type: .final
        )
        
        config.paymentItems = [
            ApplePayPaymentSummaryItem(amount: 119.99, label: "Wireless Headphones", type: .final),
            ApplePayPaymentSummaryItem(amount: 10.00, label: "Tax", type: .final)
        ]
        
        // Contact fields
        config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
        config.requiredShippingContactFields = [.postalAddress, .name, .phoneNumber]
        
        // Event handlers
        config.onPostAuthorisation = { [weak self] result in
            DispatchQueue.main.async {
                if let authorizedResult = result as? AuthorisedSubmitResult {
                    print("Payment successful: \(authorizedResult.provider.code)")
                    self?.navigateToOrderConfirmation(transactionId: authorizedResult.provider.code)
                } else if let failedResult = result as? FailedSubmitResult {
                    print("Payment failed: \(failedResult.errorReason)")
                    self?.showErrorMessage("Payment was declined. Please try a different payment method.")
                }
            }
        }
        
        config.onError = { [weak self] error in
            print("Apple Pay Error: \(error)")
            DispatchQueue.main.async {
                self?.showErrorMessage("Payment failed. Please try again.")
            }
        }
        
        config.onCancel = { error in
            print("User cancelled Apple Pay")
            // Track cancellation analytics
        }
        
        return config
    }
    
    private func setupConstraints(for componentView: UIView) {
        componentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            componentView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            componentView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            componentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            componentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            componentView.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func navigateToOrderConfirmation(transactionId: String) {
        // Navigate to order confirmation screen
        let alert = UIAlertController(title: "Order Successful", message: "Order ID: \(transactionId)", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func showErrorMessage(_ message: String) {
        let alert = UIAlertController(title: "Payment Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Custom SwiftUI implementation with shipping

import SwiftUI
import PXPCheckoutSDK

struct CustomApplePayView: View {
    @State private var applePayComponent: ApplePayButtonComponent?
    @State private var checkout: PxpCheckout?
    @State private var showingOrderDetails = false
    @State private var orderTotal: Double = 89.97
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Outdoor Adventure Gear")
                .font(.largeTitle)
                .fontWeight(.bold)
            
            VStack(spacing: 10) {
                HStack {
                    Text("Camping Tent")
                    Spacer()
                    Text("$69.99")
                }
                HStack {
                    Text("Sleeping Bag")
                    Spacer()
                    Text("$14.99")
                }
                HStack {
                    Text("Tax")
                    Spacer()
                    Text("$4.99")
                }
                Divider()
                HStack {
                    Text("Total")
                        .fontWeight(.semibold)
                    Spacer()
                    Text("$\(orderTotal, specifier: "%.2f")")
                        .fontWeight(.semibold)
                }
            }
            .padding()
            .background(Color.gray.opacity(0.1))
            .cornerRadius(10)
            
            // Custom Apple Pay Button
            ApplePayButtonWrapper(
                onComponentCreated: { component in
                    self.applePayComponent = component
                }
            )
            .frame(height: 52)
            .cornerRadius(12)
            
            Button("Show Order Details") {
                showingOrderDetails = true
            }
            .foregroundColor(.blue)
        }
        .padding()
        .onAppear {
            setupCustomApplePay()
        }
        .sheet(isPresented: $showingOrderDetails) {
            OrderDetailsView()
        }
    }
    
    private func setupCustomApplePay() {
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: SessionConfig(sessionId: "your-session-id"),
            transactionData: TransactionData(
                amount: orderTotal,
                currency: "USD",
                entryType: .ecom,
                intent: .sale,
                merchantTransactionId: "camping-\(Int(Date().timeIntervalSince1970))",
                merchantTransactionDate: Date()
            )
        )
        
        do {
            checkout = try PxpCheckout.initialize(config: checkoutConfig)
        } catch {
            print("Failed to initialize: \(error)")
        }
    }
}

struct ApplePayButtonWrapper: UIViewRepresentable {
    let onComponentCreated: (ApplePayButtonComponent) -> Void
    
    func makeUIView(context: Context) -> UIView {
        let containerView = UIView()
        
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: SessionConfig(sessionId: "your-session-id"),
            transactionData: TransactionData(
                amount: 89.97,
                currency: "USD",
                entryType: .ecom,
                intent: .sale,
                merchantTransactionId: "order-\(Int(Date().timeIntervalSince1970))",
                merchantTransactionDate: Date()
            )
        )
        
        do {
            let checkout = try PxpCheckout.initialize(config: checkoutConfig)
            let config = createCustomApplePayConfig()
            let component = try checkout.create(.applePayButton, componentConfig: config)
            
            if let componentView = component.render() {
                containerView.addSubview(componentView)
                componentView.translatesAutoresizingMaskIntoConstraints = false
                
                NSLayoutConstraint.activate([
                    componentView.topAnchor.constraint(equalTo: containerView.topAnchor),
                    componentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
                    componentView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
                    componentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
                ])
            }
            
            onComponentCreated(component)
        } catch {
            print("Failed to create Apple Pay component: \(error)")
        }
        
        return containerView
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // Updates handled by the component
    }
    
    private func createCustomApplePayConfig() -> ApplePayButtonComponentConfig {
        let config = ApplePayButtonComponentConfig()
        
        // Basic configuration
        config.merchantDisplayName = "Outdoor Adventure Gear"
        config.paymentDescription = "Camping Equipment Order"
        config.currencyCode = "USD"
        config.countryCode = "US"
        config.supportedNetworks = [.visa, .masterCard, .amex, .discover]
        config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]
        
        // Custom SwiftUI button
        config.customContent = {
            return AnyView(
                HStack(spacing: 12) {
                    // Apple logo
                    Image(systemName: "applelogo")
                        .font(.title2)
                        .foregroundColor(.white)
                    
                    // Button text
                    VStack(alignment: .leading, spacing: 2) {
                        Text("Buy with")
                            .font(.caption)
                            .foregroundColor(.white.opacity(0.8))
                        Text("Apple Pay")
                            .font(.headline)
                            .fontWeight(.semibold)
                            .foregroundColor(.white)
                    }
                    
                    Spacer()
                    
                    // Animated shimmer effect
                    Rectangle()
                        .frame(width: 2, height: 20)
                        .foregroundColor(.white.opacity(0.6))
                        .animation(
                            Animation.linear(duration: 1.5)
                                .repeatForever(autoreverses: false),
                            value: UUID()
                        )
                }
                .padding(.horizontal, 16)
                .padding(.vertical, 12)
                .frame(maxWidth: .infinity, minHeight: 52)
                .background(
                    LinearGradient(
                        gradient: Gradient(colors: [
                            Color(red: 0.1, green: 0.1, blue: 0.1),
                            Color(red: 0.3, green: 0.3, blue: 0.3)
                        ]),
                        startPoint: .topLeading,
                        endPoint: .bottomTrailing
                    )
                )
                .overlay(
                    RoundedRectangle(cornerRadius: 12)
                        .stroke(
                            LinearGradient(
                                gradient: Gradient(colors: [
                                    Color.white.opacity(0.2),
                                    Color.clear
                                ]),
                                startPoint: .topLeading,
                                endPoint: .bottomTrailing
                            ),
                            lineWidth: 1
                        )
                )
                .cornerRadius(12)
                .shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4)
            )
        }
        
        // Payment items
        config.totalPaymentItem = ApplePayPaymentSummaryItem(
            amount: 89.97,
            label: "Total",
            type: .final
        )
        
        config.paymentItems = [
            ApplePayPaymentSummaryItem(amount: 69.99, label: "Camping Tent", type: .final),
            ApplePayPaymentSummaryItem(amount: 14.99, label: "Sleeping Bag", type: .final),
            ApplePayPaymentSummaryItem(amount: 4.99, label: "Tax", type: .final)
        ]
        
        // Contact fields
        config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
        config.requiredShippingContactFields = [.postalAddress, .name, .phoneNumber]
        
        // Shipping methods
        config.shippingMethods = [
            ApplePayShippingMethod(
                amount: 0.00,
                detail: "5-7 business days",
                identifier: "standard",
                label: "Standard Shipping"
            ),
            ApplePayShippingMethod(
                amount: 12.99,
                detail: "2-3 business days",
                identifier: "express",
                label: "Express Shipping"
            )
        ]
        
        // Event handlers
        config.onShippingMethodSelected = { method in
            let baseAmount = 84.98 // Subtotal + tax
            let shippingCost = method.amount.doubleValue
            let newTotal = baseAmount + shippingCost
            
            return PKPaymentRequestShippingMethodUpdate(
                paymentSummaryItems: [
                    PKPaymentSummaryItem(label: "Camping Tent", amount: NSDecimalNumber(value: 69.99)),
                    PKPaymentSummaryItem(label: "Sleeping Bag", amount: NSDecimalNumber(value: 14.99)),
                    PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(value: 4.99)),
                    PKPaymentSummaryItem(label: "Shipping (\(method.label))", amount: method.amount),
                    PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(value: newTotal))
                ]
            )
        }
        
        config.onPostAuthorisation = { result in
            DispatchQueue.main.async {
                if let authorizedResult = result as? AuthorisedSubmitResult {
                    print("Order successful: \(authorizedResult.provider.code)")
                    // Send order confirmation and navigate
                } else if let failedResult = result as? FailedSubmitResult {
                    print("Order failed: \(failedResult.errorReason)")
                    // Show error notification
                }
            }
        }
        
        config.onError = { error in
            print("Apple Pay Error: \(error)")
            // Handle error
        }
        
        return config
    }
}

struct OrderDetailsView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Order Details")
                    .font(.largeTitle)
                    .padding()
                
                Text("Your camping gear order is being processed.")
                    .padding()
                
                Spacer()
            }
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Done") {
                        // Dismiss view
                    }
                }
            }
        }
    }
}

Subscription service with recurring payments

import UIKit
import PXPCheckoutSDK

class SubscriptionViewController: UIViewController {
    
    @IBOutlet weak var subscriptionPlanLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    @IBOutlet weak var featuresStackView: UIStackView!
    @IBOutlet weak var applePayContainer: UIView!
    
    private var applePayComponent: ApplePayButtonComponent?
    private var checkout: PxpCheckout?
    private let subscriptionPrice: Double = 14.99
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSubscriptionUI()
        setupRecurringApplePay()
    }
    
    private func setupSubscriptionUI() {
        subscriptionPlanLabel.text = "StreamMax Premium"
        priceLabel.text = "$\(subscriptionPrice, specifier: "%.2f")/month"
        
        // Add feature list
        let features = [
            "Ad-free streaming",
            "4K Ultra HD quality",
            "Download for offline viewing",
            "Stream on up to 4 devices",
            "Exclusive content access"
        ]
        
        features.forEach { feature in
            let featureView = createFeatureView(text: feature)
            featuresStackView.addArrangedSubview(featureView)
        }
    }
    
    private func createFeatureView(text: String) -> UIView {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 8
        stackView.alignment = .center
        
        let checkmark = UIImageView(image: UIImage(systemName: "checkmark.circle.fill"))
        checkmark.tintColor = .systemGreen
        checkmark.widthAnchor.constraint(equalToConstant: 20).isActive = true
        checkmark.heightAnchor.constraint(equalToConstant: 20).isActive = true
        
        let label = UILabel()
        label.text = text
        label.font = UIFont.systemFont(ofSize: 16)
        label.textColor = .label
        
        stackView.addArrangedSubview(checkmark)
        stackView.addArrangedSubview(label)
        
        return stackView
    }
    
    private func setupRecurringApplePay() {
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: SessionConfig(sessionId: "your-session-id"),
            transactionData: TransactionData(
                amount: subscriptionPrice,
                currency: "USD",
                entryType: .ecom,
                intent: .sale,
                merchantTransactionId: "subscription-\(Int(Date().timeIntervalSince1970))",
                merchantTransactionDate: Date()
            )
        )
        
        do {
            checkout = try PxpCheckout.initialize(config: checkoutConfig)
            let subscriptionConfig = createSubscriptionApplePayConfig()
            applePayComponent = try checkout?.create(.applePayButton, componentConfig: subscriptionConfig)
            
            if let componentView = applePayComponent?.render() {
                applePayContainer.addSubview(componentView)
                setupApplePayConstraints(for: componentView)
            }
        } catch {
            print("Failed to initialize: \(error)")
        }
    }
    
    private func createSubscriptionApplePayConfig() -> ApplePayButtonComponentConfig {
        let config = ApplePayButtonComponentConfig()
        
        // Basic configuration
        config.merchantDisplayName = "StreamMax Entertainment"
        config.paymentDescription = "Premium Streaming Subscription"
        config.currencyCode = "USD"
        config.countryCode = "US"
        config.supportedNetworks = [.visa, .masterCard, .amex, .discover]
        config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]
        
        // Subscription-specific button styling
        config.buttonType = .subscribe
        config.buttonStyle = .black
        config.buttonRadius = 8.0
        
        // Custom gradient button using SwiftUI
        config.customContent = {
            return AnyView(
                HStack(spacing: 10) {
                    Image(systemName: "applelogo")
                        .font(.title2)
                        .foregroundColor(.white)
                    
                    VStack(alignment: .leading, spacing: 2) {
                        Text("Subscribe with")
                            .font(.caption)
                            .foregroundColor(.white.opacity(0.9))
                        Text("Apple Pay")
                            .font(.headline)
                            .fontWeight(.semibold)
                            .foregroundColor(.white)
                    }
                    
                    Spacer()
                    
                    VStack(alignment: .trailing, spacing: 2) {
                        Text("$14.99")
                            .font(.headline)
                            .fontWeight(.bold)
                            .foregroundColor(.white)
                        Text("per month")
                            .font(.caption2)
                            .foregroundColor(.white.opacity(0.8))
                    }
                }
                .padding(.horizontal, 16)
                .padding(.vertical, 12)
                .frame(maxWidth: .infinity, minHeight: 48)
                .background(
                    LinearGradient(
                        gradient: Gradient(colors: [
                            Color(red: 1.0, green: 0.42, blue: 0.21), // FF6B35
                            Color(red: 0.97, green: 0.58, blue: 0.12)  // F7931E
                        ]),
                        startPoint: .leading,
                        endPoint: .trailing
                    )
                )
                .cornerRadius(8)
                .shadow(color: Color(red: 1.0, green: 0.42, blue: 0.21).opacity(0.3), radius: 4, x: 0, y: 2)
            )
        }
        
        // Payment items
        config.totalPaymentItem = ApplePayPaymentSummaryItem(
            amount: subscriptionPrice,
            label: "First Month",
            type: .final
        )
        
        config.paymentItems = [
            ApplePayPaymentSummaryItem(amount: 12.99, label: "Premium Subscription", type: .final),
            ApplePayPaymentSummaryItem(amount: 2.00, label: "Tax", type: .final)
        ]
        
        // Recurring payment configuration
        config.recurringRequest = ApplePayRecurringPaymentRequest(
            paymentDescription: "StreamMax Premium Monthly Subscription",
            regularBilling: ApplePayRecurringPaymentSummaryItem(
                label: "Monthly Premium Subscription",
                amount: NSDecimalNumber(value: subscriptionPrice)
            ),
            managementURL: "https://streammax.com/manage-subscription",
            tokenNotificationURL: "https://api.streammax.com/webhooks/subscription"
        )
        
        // Configure recurring schedule
        let calendar = Calendar.current
        let nextMonth = calendar.date(byAdding: .month, value: 1, to: Date()) ?? Date()
        
        config.recurringRequest?.regularBilling.startDate = nextMonth
        config.recurringRequest?.regularBilling.intervalUnit = .month
        config.recurringRequest?.regularBilling.intervalCount = 1
        
        // Contact fields
        config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
        
        // Event handlers
        config.onPreAuthorisation = { [weak self] in
            // Get user consent for recurring payments
            let userConsent = await self?.getUserSubscriptionConsent() ?? false
            guard userConsent else {
                throw SubscriptionError.userDeclinedTerms
            }
            
            return ApplePayTransactionInitData(
                riskScreeningData: RiskScreeningData(
                    performRiskScreening: true,
                    deviceSessionId: await self?.getDeviceFingerprint() ?? "",
                    transaction: TransactionRiskData(
                        subscriptionType: "premium",
                        userTier: "new_subscriber"
                    )
                )
            )
        }
        
        config.onPostAuthorisation = { [weak self] result in
            DispatchQueue.main.async {
                if let authorizedResult = result as? AuthorisedSubmitResult {
                    print("Subscription setup successful: \(authorizedResult.provider.code)")
                    
                    // Create subscription record
                    self?.createSubscription(
                        transactionId: authorizedResult.provider.code,
                        subscriptionPlan: "premium",
                        billingCycle: "monthly",
                        nextBillingDate: nextMonth
                    )
                    
                    // Navigate to welcome screen
                    self?.navigateToWelcomeScreen(subscriptionId: authorizedResult.provider.code)
                    
                } else if let failedResult = result as? FailedSubmitResult {
                    print("Subscription setup failed: \(failedResult.errorReason)")
                    self?.showError("Subscription setup failed. Please try again.")
                }
            }
        }
        
        config.onError = { [weak self] error in
            print("Subscription Error: \(error)")
            DispatchQueue.main.async {
                self?.showError("Unable to set up subscription. Please try again or contact support.")
            }
        }
        
        return config
    }
    
    // Helper methods
    private func getUserSubscriptionConsent() async -> Bool {
        return await withCheckedContinuation { continuation in
            DispatchQueue.main.async {
                let alert = UIAlertController(
                    title: "Subscription Terms",
                    message: "You will be charged $14.99 monthly starting 30 days from today. You can cancel anytime in your account settings.",
                    preferredStyle: .alert
                )
                
                alert.addAction(UIAlertAction(title: "Accept & Subscribe", style: .default) { _ in
                    continuation.resume(returning: true)
                })
                
                alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
                    continuation.resume(returning: false)
                })
                
                self.present(alert, animated: true)
            }
        }
    }
    
    private func getDeviceFingerprint() async -> String {
        return "device_\(UIDevice.current.identifierForVendor?.uuidString ?? "unknown")_\(Int(Date().timeIntervalSince1970))"
    }
    
    private func createSubscription(transactionId: String, subscriptionPlan: String, billingCycle: String, nextBillingDate: Date) {
        // Store subscription details
        let subscriptionData: [String: Any] = [
            "transactionId": transactionId,
            "plan": subscriptionPlan,
            "billingCycle": billingCycle,
            "nextBillingDate": nextBillingDate,
            "createdAt": Date()
        ]
        
        UserDefaults.standard.set(subscriptionData, forKey: "subscription_\(transactionId)")
        print("Subscription created: \(subscriptionData)")
    }
    
    private func navigateToWelcomeScreen(subscriptionId: String) {
        let alert = UIAlertController(
            title: "Welcome to StreamMax Premium!",
            message: "Your subscription is now active. Subscription ID: \(subscriptionId)",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Start Streaming", style: .default))
        present(alert, animated: true)
    }
    
    private func showError(_ message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func setupApplePayConstraints(for componentView: UIView) {
        componentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            componentView.topAnchor.constraint(equalTo: applePayContainer.topAnchor),
            componentView.leadingAnchor.constraint(equalTo: applePayContainer.leadingAnchor),
            componentView.trailingAnchor.constraint(equalTo: applePayContainer.trailingAnchor),
            componentView.bottomAnchor.constraint(equalTo: applePayContainer.bottomAnchor),
            componentView.heightAnchor.constraint(equalToConstant: 48)
        ])
    }
}

enum SubscriptionError: Error {
    case userDeclinedTerms
}

Dynamic state management

import UIKit
import PXPCheckoutSDK

class DynamicApplePayManager: NSObject {
    
    enum PaymentState {
        case idle, loading, success, error
    }
    
    private var component: ApplePayButtonComponent?
    private var containerView: UIView
    private var state: PaymentState = .idle
    private var stateIndicator: UIView?
    
    init(containerView: UIView) {
        self.containerView = containerView
        super.init()
    }
    
    func create(theme: Theme = .light) -> DynamicApplePayManager {
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: SessionConfig(sessionId: "your-session-id"),
            transactionData: TransactionData(
                amount: 189.99,
                currency: "USD",
                entryType: .ecom,
                intent: .sale,
                merchantTransactionId: "gaming-\(Int(Date().timeIntervalSince1970))",
                merchantTransactionDate: Date()
            )
        )
        
        do {
            let checkout = try PxpCheckout.initialize(config: checkoutConfig)
            let config = createDynamicConfig(theme: theme)
            component = try checkout.create(.applePayButton, componentConfig: config)
        } catch {
            print("Failed to create component: \(error)")
        }
        
        return self
    }
    
    func mount() -> DynamicApplePayManager {
        guard let component = component else { return self }
        
        if let componentView = component.render() {
            containerView.addSubview(componentView)
            setupConstraints(for: componentView)
            setupStateIndicator()
        }
        
        return self
    }
    
    private func createDynamicConfig(theme: Theme) -> ApplePayButtonComponentConfig {
        let config = ApplePayButtonComponentConfig()
        
        // Basic configuration
        config.merchantDisplayName = "TechGadget Pro Store"
        config.paymentDescription = "Wireless Gaming Headset Purchase"
        config.currencyCode = "USD"
        config.countryCode = "US"
        config.supportedNetworks = [.visa, .masterCard, .amex, .discover]
        config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]
        
        // Dynamic button with state management
        config.customContent = { [weak self] in
            return AnyView(self?.createDynamicButton(theme: theme) ?? EmptyView().eraseToAnyView())
        }
        
        // Payment items
        config.totalPaymentItem = ApplePayPaymentSummaryItem(
            amount: 189.99,
            label: "Total",
            type: .final
        )
        
        config.paymentItems = [
            ApplePayPaymentSummaryItem(amount: 169.99, label: "Gaming Headset Pro X", type: .final),
            ApplePayPaymentSummaryItem(amount: 12.99, label: "Extended Warranty", type: .final),
            ApplePayPaymentSummaryItem(amount: 7.01, label: "Tax", type: .final)
        ]
        
        // Contact fields
        config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
        
        // Event handlers with state management
        config.onPostAuthorisation = { [weak self] result in
            DispatchQueue.main.async {
                if let _ = result as? AuthorisedSubmitResult {
                    self?.setState(.success)
                    DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                        // Navigate to success screen
                    }
                } else {
                    self?.setState(.error)
                    DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
                        self?.setState(.idle)
                    }
                }
            }
        }
        
        config.onError = { [weak self] error in
            DispatchQueue.main.async {
                self?.setState(.error)
                DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
                    self?.setState(.idle)
                }
            }
        }
        
        return config
    }
    
    private func createDynamicButton(theme: Theme) -> some View {
        let colors = theme.colors
        
        return HStack(spacing: 12) {
            // Apple Pay logo
            Image(systemName: "applelogo")
                .font(.title2)
                .foregroundColor(.white)
            
            // Button text
            Text("Pay")
                .font(.headline)
                .fontWeight(.semibold)
                .foregroundColor(.white)
            
            Spacer()
            
            // Dynamic state indicator
            Group {
                switch state {
                case .idle:
                    EmptyView()
                case .loading:
                    ProgressView()
                        .progressViewStyle(CircularProgressViewStyle(tint: .white))
                        .scaleEffect(0.8)
                case .success:
                    Image(systemName: "checkmark.circle.fill")
                        .foregroundColor(.green)
                        .font(.title3)
                case .error:
                    Image(systemName: "xmark.circle.fill")
                        .foregroundColor(.red)
                        .font(.title3)
                }
            }
            .frame(width: 20, height: 20)
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 12)
        .frame(maxWidth: .infinity, minHeight: 52)
        .background(backgroundColor(for: state, theme: theme))
        .cornerRadius(8)
        .onTapGesture {
            if state == .idle {
                setState(.loading)
            }
        }
    }
    
    private func backgroundColor(for state: PaymentState, theme: Theme) -> Color {
        switch state {
        case .idle:
            return theme.colors.background
        case .loading:
            return theme.colors.background.opacity(0.8)
        case .success:
            return Color.green
        case .error:
            return Color.red
        }
    }
    
    private func setState(_ newState: PaymentState) {
        state = newState
        updateButtonAppearance()
    }
    
    private func setupStateIndicator() {
        // Additional state indicators if needed
    }
    
    private func updateButtonAppearance() {
        // Trigger view update through component refresh
    }
    
    private func setupConstraints(for componentView: UIView) {
        componentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            componentView.topAnchor.constraint(equalTo: containerView.topAnchor),
            componentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            componentView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            componentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            componentView.heightAnchor.constraint(equalToConstant: 52)
        ])
    }
}

// Theme support
struct Theme {
    let colors: ThemeColors
    
    static let light = Theme(colors: ThemeColors(
        background: Color.black,
        text: Color.white,
        hover: Color.gray,
        success: Color.green,
        error: Color.red
    ))
    
    static let dark = Theme(colors: ThemeColors(
        background: Color(red: 0.11, green: 0.11, blue: 0.12),
        text: Color.white,
        hover: Color(red: 0.17, green: 0.17, blue: 0.18),
        success: Color(red: 0.19, green: 0.82, blue: 0.35),
        error: Color(red: 1.0, green: 0.27, blue: 0.23)
    ))
}

struct ThemeColors {
    let background: Color
    let text: Color
    let hover: Color
    let success: Color
    let error: Color
}

// Extension for type erasure
extension View {
    func eraseToAnyView() -> AnyView {
        return AnyView(self)
    }
}

// Usage
// let applePayManager = DynamicApplePayManager(containerView: applePayContainerView)
// applePayManager.create(theme: .dark).mount()