# 3DS payments

Integrate 3D Secure (3DS) into your Apple Pay checkout for iOS applications.

## Overview

By implementing 3DS authentication into your Apple Pay payment flow on iOS, you benefit from:

* **Additional security:** 3DS adds multiple layers of authentication and risk assessment.
* **Liability shift:** Successful 3DS authentication typically shifts fraud liability from merchant to card issuer.
* **Higher success rate:** Banks are more likely to approve 3DS-authenticated transactions.
* **Apple Pay integration:** 3DS works seamlessly with Apple Pay's built-in security features.


However, the 3DS payment flow may be longer than the non-3DS one due to the additional authentication steps. With Apple Pay on iOS, 3DS often runs in the background during the Apple Pay authorisation process.

## Payment flow

The Apple Pay 3D Secure flow on iOS is made up of seven key steps.

### Step 1: Apple Pay authorisation

The customer authorises payment using Touch ID, Face ID, or passcode in the Apple Pay sheet. The `onPreAuthorisation` callback in `ApplePayButtonComponent` is invoked with the Apple Pay payment token.

### Step 2: 3DS evaluation

The SDK evaluates whether 3DS authentication is required based on factors like transaction amount, risk assessment, merchant configuration, or regulatory requirements.

### Step 3: Payment token processing

The Apple Pay payment token is processed and prepared for 3DS authentication. The token contains encrypted payment data that will be used in the 3DS flow.

### Step 4: Pre-authorisation validation

This is where you can provide additional transaction data for 3DS processing. The `onPreAuthorisation` callback allows you to specify risk screening data, address verification, and 3DS configuration.

### Step 5: 3DS authentication

The 3DS server evaluates the transaction risk using both Apple Pay data and additional merchant data:

* **Frictionless flow:** If the transaction is low-risk (enhanced by Apple Pay's built-in security), authentication completes automatically.
* **Challenge flow:** If additional verification is needed, the customer may complete a 3DS challenge, though this is less common with Apple Pay due to its inherent security.


### Step 6: Transaction authorisation

The SDK sends the authorisation request to the payment gateway, including both the Apple Pay token and 3DS authentication data. This combines Apple Pay's security with 3DS protection.

### Step 7: Authorisation result

You receive the final authorisation response from the payment gateway. The transaction is either approved or declined, with both Apple Pay and 3DS authentication confirmation.

## Implementation

### Before you start

To use 3D Secure with Apple Pay in your iOS application, you first need to:

- Enable 3DS in the Unity Portal:
  - Go to **Merchant setup > Merchant groups**.
  - Select a merchant group.
  - Click the **Services** tab.
  - Click **Edit** in the *Card service* row.
  - Click **Configure modules** and enable *ThreeD secure service*.
- Configure Apple Pay:
  - Set up your Apple Pay merchant identifier.
  - Add Apple Pay capability to your iOS app.
  - Configure your Apple Developer account with merchant IDs.
- Get 3DS credentials from your payment processor:
  - `acquirerProfileId`: Your acquirer profile identifier.
  - `providerId`: Your 3DS provider identifier.
  - Test credentials for the sandbox environment.


### Step 1: Configure your SDK

Set up your `CheckoutConfig` to include both Apple Pay and 3DS-friendly data.


```swift
let checkoutConfig = CheckoutConfig(
    environment: .test,
    session: SessionConfig(
        sessionId: "your-session-id",
        allowedFundingTypes: AllowedFundingTypes(
            wallets: WalletConfig(
                applePay: ApplePayConfig(
                    merchantId: "merchant.com.yourcompany"
                )
            )
        )
    ),
    transactionData: TransactionData(
        amount: 99.99,
        currency: "USD",
        entryType: .ecom,
        intent: .sale,
        merchantTransactionId: "order-123",
        merchantTransactionDate: Date(),
        // Include 3DS-friendly shopper data
        shopper: Shopper(
            email: "customer@example.com",
            firstName: "John",
            lastName: "Doe"
        )
    )
)
```

### Step 2: Implement Apple Pay callbacks

Configure the Apple Pay component with 3DS capabilities.


```swift
let applePayConfig = ApplePayButtonComponentConfig()

// Basic payment configuration
applePayConfig.currencyCode = "USD"
applePayConfig.countryCode = "US"

// Supported payment networks
applePayConfig.supportedNetworks = [.visa, .masterCard, .amex, .discover]

// Merchant capabilities - IMPORTANT: Include .threeDSecure
applePayConfig.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]

// Contact field requirements for 3DS
applePayConfig.requiredBillingContactFields = [.postalAddress, .name, .phoneNumber, .emailAddress]
applePayConfig.requiredShippingContactFields = [.postalAddress, .name, .phoneNumber]

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

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

// Button appearance
applePayConfig.buttonType = .buy
applePayConfig.buttonStyle = .black
applePayConfig.buttonRadius = 8.0

// REQUIRED: Provide 3DS configuration and additional transaction data
applePayConfig.onPreAuthorisation = {
    let logMessage = "onPreAuthorisation: Called before authorisation to update transaction details"
    print(logMessage)
    
    return ApplePayTransactionInitData(
        // 3DS configuration
        threeDSecureData: ThreeDSecureData(
            threeDSecureVersion: "2.1.0",
            electronicCommerceIndicator: .eci5, // Authenticated
            cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
            directoryServerTransactionId: "8ac7ca4f-6068-4978-8b5e-c23c0c6d2260",
            threeDSecureTransactionStatus: "Y"
        ),
        
        // Identity verification
        identityVerification: IdentityVerification(
            nameVerification: true
        ),
        
        // Address verification (helps with 3DS risk assessment)
        addressVerification: AddressVerification(
            countryCode: "US",
            houseNumberOrName: "123 Main St",
            postalCode: "10001"
        ),
        
        // Risk screening data for fraud detection
        riskScreeningData: RiskScreeningData(
            performRiskScreening: true,
            deviceSessionId: generateDeviceSessionId(),
            items: [
                Item(
                    name: "Product Name",
                    price: 89.99,
                    quantity: 1,
                    category: "Electronics"
                )
            ],
            fulfillments: [
                Fulfillment(
                    type: "SHIPPING",
                    address: Address(
                        countryCode: "US",
                        postalCode: "10001",
                        city: "New York",
                        state: "NY"
                    )
                )
            ]
        )
    )
}

// REQUIRED: Handle the final transaction result
applePayConfig.onPostAuthorisation = { result in
    if let authorizedResult = result as? AuthorisedSubmitResult {
        print("Payment successful with Apple Pay + 3DS!")
        print("Transaction ID: \(authorizedResult.provider.code)")
        
        // Check if 3DS was used
        if let threeDSData = authorizedResult.threeDSData {
            print("3DS authentication confirmed")
            print("ECI: \(threeDSData.electronicCommerceIndicator?.rawValue ?? "N/A")")
            
            // Check liability shift
            if threeDSData.electronicCommerceIndicator == .eci5 || 
               threeDSData.electronicCommerceIndicator == .eci6 {
                print("Liability shift achieved - protected from chargebacks")
            }
        }
        
        // Navigate to success screen
        DispatchQueue.main.async {
            // Present success view controller or update UI
            self.showPaymentSuccess(transactionId: authorizedResult.provider.code)
        }
        
    } else if let failedResult = result as? FailedSubmitResult {
        print("Payment failed: \(failedResult.errorReason)")
        
        DispatchQueue.main.async {
            self.showError("Payment failed. Please try again.")
        }
    }
}

// OPTIONAL: Handle Apple Pay errors
applePayConfig.onError = { error in
    print("Apple Pay error: \(error.localizedDescription)")
    
    DispatchQueue.main.async {
        // Handle specific 3DS + Apple Pay errors
        if error.localizedDescription.contains("3DS") {
            self.showError("Payment verification failed. Please try again.")
        } else if error.localizedDescription.contains("Apple Pay") {
            self.showError("Apple Pay is not available. Please try a different payment method.")
        } else {
            self.showError("Payment failed. Please try again.")
        }
    }
}

// OPTIONAL: Handle cancellation
applePayConfig.onCancel = { error in
    print("Apple Pay cancelled by user")
    // User closed Apple Pay sheet
}
```

### Step 3: Handle common scenarios

#### Conditional 3DS based on amount

Use 3DS only for transactions above a certain amount.


```swift
applePayConfig.onPreAuthorisation = {
    let transactionAmount = checkoutConfig.transactionData.amount
    
    // Only use 3DS for amounts over $100
    if transactionAmount > 100 {
        return ApplePayTransactionInitData(
            threeDSecureData: ThreeDSecureData(
                threeDSecureVersion: "2.1.0",
                electronicCommerceIndicator: .eci5,
                cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
                directoryServerTransactionId: "8ac7ca4f-6068-4978-8b5e-c23c0c6d2260",
                threeDSecureTransactionStatus: "Y"
            ),
            addressVerification: AddressVerification(
                countryCode: "US",
                houseNumberOrName: "123 Main St",
                postalCode: "10001"
            ),
            riskScreeningData: RiskScreeningData(
                performRiskScreening: true,
                deviceSessionId: generateDeviceSessionId()
            )
        )
    }
    
    // For smaller amounts, just provide basic data
    return ApplePayTransactionInitData(
        addressVerification: AddressVerification(
            countryCode: "US",
            houseNumberOrName: "123 Main St",
            postalCode: "10001"
        )
    )
}
```

#### Different transaction types

Handle different types of Apple Pay transactions with appropriate 3DS settings.


```swift
func get3DSConfig(for transactionType: TransactionType) -> ThreeDSecureData {
    let baseVersion = "2.1.0"
    let dsTransactionId = "8ac7ca4f-6068-4978-8b5e-c23c0c6d2260"
    
    switch transactionType {
    case .payment:
        return ThreeDSecureData(
            threeDSecureVersion: baseVersion,
            electronicCommerceIndicator: .eci5, // Authenticated
            cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
            directoryServerTransactionId: dsTransactionId,
            threeDSecureTransactionStatus: "Y"
        )
    case .recurring:
        return ThreeDSecureData(
            threeDSecureVersion: baseVersion,
            electronicCommerceIndicator: .eci6, // Recurring authenticated
            cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
            directoryServerTransactionId: dsTransactionId,
            threeDSecureTransactionStatus: "Y"
        )
    case .addCard:
        return ThreeDSecureData(
            threeDSecureVersion: baseVersion,
            electronicCommerceIndicator: .eci1, // Card verification
            cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
            directoryServerTransactionId: dsTransactionId,
            threeDSecureTransactionStatus: "Y"
        )
    }
}

enum TransactionType {
    case payment, recurring, addCard
}

applePayConfig.onPreAuthorisation = {
    return ApplePayTransactionInitData(
        threeDSecureData: get3DSConfig(for: .payment),
        addressVerification: AddressVerification(
            countryCode: "US",
            houseNumberOrName: "123 Main St",
            postalCode: "10001"
        ),
        riskScreeningData: RiskScreeningData(
            performRiskScreening: true,
            deviceSessionId: generateDeviceSessionId()
        )
    )
}
```

#### Apple Pay specific risk assessment

Leverage Apple Pay's built-in security for better risk assessment.


```swift
applePayConfig.onPreAuthorisation = {
    return ApplePayTransactionInitData(
        threeDSecureData: ThreeDSecureData(
            threeDSecureVersion: "2.1.0",
            electronicCommerceIndicator: .eci5,
            cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
            directoryServerTransactionId: "8ac7ca4f-6068-4978-8b5e-c23c0c6d2260",
            threeDSecureTransactionStatus: "Y" // Request no challenge (Apple Pay provides security)
        ),
        
        // Enhanced risk data
        riskScreeningData: RiskScreeningData(
            performRiskScreening: true,
            deviceSessionId: generateDeviceSessionId(),
            items: getCartItems(),
            fulfillments: [
                Fulfillment(
                    type: "SHIPPING",
                    address: getShippingAddress()
                )
            ],
            transaction: TransactionRiskData(
                deviceChannel: "IOS_APP",
                isRecurring: false,
                customerType: "EXISTING",
                // Apple Pay provides additional security context
                paymentMethod: "APPLE_PAY",
                deviceAuthentication: "BIOMETRIC", // Touch ID/Face ID used
                walletProvider: "APPLE"
            )
        )
    )
}
```

### Step 4: Handle errors

Implement comprehensive error handling for Apple Pay + 3DS scenarios.


```swift
applePayConfig.onError = { error in
    print("Apple Pay + 3DS error: \(error.localizedDescription)")
    
    DispatchQueue.main.async {
        // Handle specific error types
        if let applePayError = error as? ApplePayValidationException {
            self.showError("Apple Pay configuration error. Please contact support.")
        } else if let paymentError = error as? ApplePayPaymentFailedException {
            self.showError("Payment processing failed. Please try again.")
        } else if let sessionError = error as? ApplePaySessionCancelledException {
            print("User cancelled Apple Pay")
            // Don't show error for user cancellation
        } else if error.localizedDescription.contains("3DS") {
            self.showError("Payment verification failed. Please try again.")
        } else if error.localizedDescription.contains("Network") {
            self.showError("Connection failed. Please check your internet and try again.")
        } else {
            self.showError("Payment failed. Please try again.")
        }
    }
}

applePayConfig.onPostAuthorisation = { result in
    if let failedResult = result as? FailedSubmitResult {
        print("Transaction failed: \(failedResult.errorReason)")
        
        DispatchQueue.main.async {
            // Handle specific failure scenarios
            switch failedResult.errorCode {
            case "AUTHENTICATION_FAILED":
                self.showError("Payment verification failed. Please try again.")
            case "CHALLENGE_TIMEOUT":
                self.showError("Verification timed out. Please try again.")
            case "AUTHENTICATION_REJECTED":
                self.showError("Payment was rejected by your bank.")
            case "INSUFFICIENT_FUNDS":
                self.showError("Insufficient funds. Please try a different payment method.")
            default:
                self.showError("Payment failed. Please try again or contact support.")
            }
        }
    }
}
```

### Example

Here's a complete example of Apple Pay with 3DS implementation for iOS.


```swift
import UIKit
import PXPCheckoutSDK

class CheckoutViewController: UIViewController {
    
    private var applePayComponent: ApplePayButtonComponent?
    private var checkout: PxpCheckout?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupApplePayWith3DS()
    }
    
    private func setupApplePayWith3DS() {
        // Initialise the SDK
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: SessionConfig(
                sessionId: "your-session-id",
                allowedFundingTypes: AllowedFundingTypes(
                    wallets: WalletConfig(
                        applePay: ApplePayConfig(
                            merchantId: "merchant.com.yourcompany"
                        )
                    )
                )
            ),
            transactionData: TransactionData(
                amount: 150.00,
                currency: "USD",
                entryType: .ecom,
                intent: .sale,
                merchantTransactionId: "order-\(Int(Date().timeIntervalSince1970))",
                merchantTransactionDate: Date(),
                shopper: Shopper(
                    email: "customer@example.com",
                    firstName: "John",
                    lastName: "Doe"
                )
            )
        )
        
        do {
            checkout = try PxpCheckout.initialize(config: checkoutConfig)
            
            // Create Apple Pay component with 3DS
            let applePayConfig = createApplePayConfig()
            applePayComponent = try checkout?.create(.applePayButton, componentConfig: applePayConfig)
            
            // Add the component to your view
            if let componentView = applePayComponent?.render() {
                view.addSubview(componentView)
                setupConstraints(for: componentView)
            }
            
        } catch {
            print("Failed to initialise checkout: \(error)")
        }
    }
    
    private func createApplePayConfig() -> ApplePayButtonComponentConfig {
        let config = ApplePayButtonComponentConfig()
        
        // Basic configuration
        config.currencyCode = "USD"
        config.countryCode = "US"
        config.supportedNetworks = [.visa, .masterCard, .amex, .discover]
        config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]
        
        // Contact fields for 3DS
        config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
        config.requiredShippingContactFields = [.postalAddress, .name]
        
        // Payment items
        config.totalPaymentItem = ApplePayPaymentSummaryItem(
            amount: 150.00,
            label: "Total",
            type: .final
        )
        
        config.paymentItems = [
            ApplePayPaymentSummaryItem(amount: 140.00, label: "Premium Product", type: .final),
            ApplePayPaymentSummaryItem(amount: 10.00, label: "Tax", type: .final)
        ]
        
        // Button appearance
        config.buttonType = .buy
        config.buttonStyle = .black
        config.buttonRadius = 8.0
        
        // Step 1: Provide 3DS configuration
        config.onPreAuthorisation = { [weak self] in
            print("Configuring 3DS for Apple Pay transaction")
            
            return ApplePayTransactionInitData(
                // 3DS Authentication configuration
                threeDSecureData: ThreeDSecureData(
                    threeDSecureVersion: "2.1.0",
                    electronicCommerceIndicator: .eci5, // Authenticated
                    cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
                    directoryServerTransactionId: "8ac7ca4f-6068-4978-8b5e-c23c0c6d2260",
                    threeDSecureTransactionStatus: "Y"
                ),
                
                // Identity verification
                identityVerification: IdentityVerification(
                    nameVerification: true
                ),
                
                // Address verification
                addressVerification: AddressVerification(
                    countryCode: "US",
                    houseNumberOrName: "123 Main St",
                    postalCode: "10001"
                ),
                
                // Enhanced risk assessment
                riskScreeningData: RiskScreeningData(
                    performRiskScreening: true,
                    deviceSessionId: self?.generateDeviceSessionId() ?? "",
                    items: [
                        Item(
                            name: "Premium Product",
                            category: "Electronics",
                            price: 140.00,
                            quantity: 1,
                            sku: "PREM-001"
                        )
                    ],
                    fulfillments: [
                        Fulfillment(
                            type: "SHIPPING",
                            address: Address(
                                countryCode: "US",
                                postalCode: "10001",
                                city: "New York",
                                state: "NY"
                            )
                        )
                    ],
                    transaction: TransactionRiskData(
                        deviceChannel: "IOS_APP",
                        isRecurring: false,
                        customerType: "EXISTING",
                        paymentMethod: "APPLE_PAY",
                        deviceAuthentication: "BIOMETRIC",
                        walletProvider: "APPLE"
                    )
                )
            )
        }
        
        // Step 2: Handle successful/failed authorisation
        config.onPostAuthorisation = { [weak self] result in
            print("Apple Pay + 3DS result: \(result)")
            
            DispatchQueue.main.async {
                if let authorizedResult = result as? AuthorisedSubmitResult {
                    print("Payment successful!")
                    print("Transaction ID: \(authorizedResult.provider.code)")
                    
                    // Log 3DS authentication details
                    if let threeDSData = authorizedResult.threeDSData {
                        print("3DS Authentication Details:")
                        print("Version: \(threeDSData.threeDSecureVersion ?? "N/A")")
                        print("ECI: \(threeDSData.electronicCommerceIndicator?.rawValue ?? "N/A")")
                        print("CAVV: \(threeDSData.cardHolderAuthenticationVerificationValue ?? "N/A")")
                        print("DS Trans ID: \(threeDSData.directoryServerTransactionId ?? "N/A")")
                        
                        // Check liability shift
                        if threeDSData.electronicCommerceIndicator == .eci5 || 
                           threeDSData.electronicCommerceIndicator == .eci6 {
                            print("Liability shift achieved - protected from chargebacks")
                        }
                    }
                    
                    // Store transaction details
                    self?.storeTransactionRecord(
                        transactionId: authorizedResult.provider.code,
                        amount: 150.00,
                        currency: "USD",
                        paymentMethod: "APPLE_PAY",
                        threeDSStatus: authorizedResult.threeDSData != nil ? "AUTHENTICATED" : "NOT_REQUIRED"
                    )
                    
                    // Navigate to success screen
                    self?.showPaymentSuccess(transactionId: authorizedResult.provider.code)
                    
                } else if let failedResult = result as? FailedSubmitResult {
                    print("Payment failed: \(failedResult.errorReason)")
                    
                    // Handle specific failure scenarios
                    switch failedResult.errorCode {
                    case "AUTHENTICATION_FAILED":
                        self?.showError("Payment verification failed. Please try again.")
                    case "AUTHENTICATION_REJECTED":
                        self?.showError("Payment was rejected by your bank. Please try a different card.")
                    case "INSUFFICIENT_FUNDS":
                        self?.showError("Insufficient funds. Please try a different payment method.")
                    case "CARD_EXPIRED":
                        self?.showError("Card expired. Please update your payment method in Apple Pay.")
                    default:
                        self?.showError("Payment failed. Please try again or contact support.")
                    }
                }
            }
        }
        
        // Step 3: Error handling
        config.onError = { [weak self] error in
            print("Apple Pay + 3DS Error: \(error.localizedDescription)")
            
            DispatchQueue.main.async {
                if let _ = error as? ApplePayValidationException {
                    self?.showError("Apple Pay configuration error. Please contact support.")
                } else if let _ = error as? ApplePayPaymentFailedException {
                    self?.showError("Payment processing failed. Please try again.")
                } else if error.localizedDescription.contains("3DS") {
                    self?.showError("Payment verification failed. Please try again.")
                } else if error.localizedDescription.contains("Network") {
                    self?.showError("Connection failed. Please check your internet and try again.")
                } else {
                    self?.showError("An unexpected error occurred. Please try again.")
                }
            }
        }
        
        // Step 4: Handle cancellation
        config.onCancel = { error in
            print("Apple Pay cancelled by user")
            // User closed Apple Pay sheet - no error message needed
        }
        
        return config
    }
    
    // Helper functions
    private func generateDeviceSessionId() -> String {
        return "device_\(UUID().uuidString.replacingOccurrences(of: "-", with: ""))_\(Int(Date().timeIntervalSince1970))"
    }
    
    private func showError(_ message: String) {
        let alert = UIAlertController(title: "Payment Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func showPaymentSuccess(transactionId: String) {
        let alert = UIAlertController(title: "Payment Successful", 
                                    message: "Transaction ID: \(transactionId)", 
                                    preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func storeTransactionRecord(transactionId: String, amount: Double, currency: String, paymentMethod: String, threeDSStatus: String) {
        let transactionData: [String: Any] = [
            "transactionId": transactionId,
            "amount": amount,
            "currency": currency,
            "paymentMethod": paymentMethod,
            "threeDSStatus": threeDSStatus,
            "timestamp": Date().timeIntervalSince1970
        ]
        
        // Store transaction details for audit/reconciliation
        UserDefaults.standard.set(transactionData, forKey: "last_transaction")
        
        // Send to analytics if needed
        print("Transaction stored: \(transactionData)")
    }
    
    private func setupConstraints(for componentView: UIView) {
        componentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            componentView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            componentView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            componentView.widthAnchor.constraint(equalToConstant: 280),
            componentView.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
}
```

## Callback data

This section describes the data received by the different callbacks as part of the Apple Pay 3DS flow on iOS.

### onPreAuthorisation

The `onPreAuthorisation` callback receives no parameters for Apple Pay. It should return transaction initialisation data with 3DS configuration enabled.

#### Return data structure


```swift
ApplePayTransactionInitData(
    // 3D Secure configuration (required for 3DS flow)
    threeDSecureData: ThreeDSecureData(
        attemptN3d: false,
        challengeIndicator: "04",
        cardHolderName: "John Doe",
        email: "customer@example.com",
        homePhone: "+1234567890",
        mobilePhone: "+1234567890",
        workPhone: "+1234567890",
        billAddrCity: "New York",
        billAddrCountry: "840",
        billAddrLine1: "123 Main St",
        billAddrLine2: "Apt 4B",
        billAddrPostCode: "10001",
        billAddrState: "NY",
        shipAddrCity: "New York",
        shipAddrCountry: "840",
        shipAddrLine1: "123 Main St",
        shipAddrLine2: "Apt 4B",
        shipAddrPostCode: "10001",
        shipAddrState: "NY"
    ),
    
    // Identity verification (optional)
    identityVerification: IdentityVerification(
        nameVerification: true
    ),
    
    // Address verification (optional but recommended with 3DS)
    addressVerification: AddressVerification(
        countryCode: "US",
        houseNumberOrName: "123 Main St",
        postalCode: "10001"
    ),
    
    // Risk screening data (enhanced with 3DS)
    riskScreeningData: RiskScreeningData(
        performRiskScreening: true,
        deviceSessionId: "device_abc123_1704067200000",
        items: [
            Item(
                name: "Product Name",
                price: 99.99,
                quantity: 1
            )
        ],
        transaction: TransactionRiskData(
            paymentMethod: "APPLE_PAY",
            deviceAuthentication: "BIOMETRIC",
            walletProvider: "APPLE",
            authenticationMethod: "3DS_V2"
        )
    )
)
```

Here's an example of what to return:


```swift
config.onPreAuthorisation = {
    print("Preparing Apple Pay 3DS transaction")
    
    // Get customer information for 3DS authentication
    let customerInfo = await getCustomerInformation()
    let shippingInfo = await getShippingInformation()
    
    return ApplePayTransactionInitData(
        // 3DS configuration - required for 3DS flow
        threeDSecureData: ThreeDSecureData(
            attemptN3d: false,
            challengeIndicator: "04", // Request authentication
            cardHolderName: customerInfo.fullName,
            email: customerInfo.email,
            homePhone: customerInfo.phone,
            mobilePhone: customerInfo.mobile,
            workPhone: customerInfo.workPhone,
            
            // Billing address information
            billAddrCity: customerInfo.billingAddress.city,
            billAddrCountry: "840", // US country code
            billAddrLine1: customerInfo.billingAddress.line1,
            billAddrLine2: customerInfo.billingAddress.line2,
            billAddrPostCode: customerInfo.billingAddress.postalCode,
            billAddrState: customerInfo.billingAddress.state,
            
            // Shipping address information
            shipAddrCity: shippingInfo.city,
            shipAddrCountry: "840",
            shipAddrLine1: shippingInfo.line1,
            shipAddrLine2: shippingInfo.line2,
            shipAddrPostCode: shippingInfo.postalCode,
            shipAddrState: shippingInfo.state
        ),
        
        // Identity verification
        identityVerification: IdentityVerification(
            nameVerification: true
        ),
        
        // Address verification
        addressVerification: AddressVerification(
            countryCode: "US",
            houseNumberOrName: customerInfo.billingAddress.line1,
            postalCode: customerInfo.billingAddress.postalCode
        ),
        
        // Enhanced risk screening for 3DS
        riskScreeningData: RiskScreeningData(
            performRiskScreening: true,
            deviceSessionId: generateDeviceSessionId(),
            items: [
                Item(
                    name: "Premium Product",
                    category: "Electronics",
                    price: 99.99,
                    quantity: 1,
                    sku: "PROD-3DS-001"
                )
            ],
            fulfillments: [
                Fulfillment(
                    type: "SHIPPING",
                    address: Address(
                        countryCode: "US",
                        postalCode: shippingInfo.postalCode
                    ),
                    estimatedDelivery: "2024-01-15"
                )
            ],
            transaction: TransactionRiskData(
                deviceChannel: "IOS_APP",
                paymentMethod: "APPLE_PAY",
                deviceAuthentication: "BIOMETRIC", // Touch ID/Face ID
                walletProvider: "APPLE",
                authenticationMethod: "3DS_V2",
                processingType: "3ds",
                securityLevel: "MAXIMUM", // Due to Apple Pay + 3DS
                riskReduction: "APPLE_PAY_3DS",
                
                // Apple Pay with 3DS provides maximum security
                applePayWith3DS: ApplePaySecurityFeatures(
                    deviceBoundToken: true,
                    biometricAuthentication: true,
                    hardwareSecurityModule: true,
                    tokenization: true,
                    threeDSecureProtection: true
                ),
                
                // Enhanced 3DS context for iOS
                threeDSContext: ThreeDSContext(
                    deviceInfo: DeviceInfo(
                        platform: "iOS",
                        version: UIDevice.current.systemVersion,
                        model: UIDevice.current.model,
                        screenSize: "\(Int(UIScreen.main.bounds.width))x\(Int(UIScreen.main.bounds.height))",
                        locale: Locale.current.identifier,
                        timeZone: TimeZone.current.identifier
                    ),
                    customerAuthenticationInfo: CustomerAuthInfo(
                        authMethod: "biometric",
                        authTimestamp: Date().toISOString(),
                        authData: "apple_pay_biometric",
                        deviceId: UIDevice.current.identifierForVendor?.uuidString
                    )
                )
            )
        )
    )
}
```

### onPostAuthorisation

The `onPostAuthorisation` callback receives the transaction result (`BaseSubmitResult`).

#### BaseSubmitResult types

The result can be one of several types:

**AuthorisedSubmitResult** - Successful 3DS authentication and authorisation:


```swift
AuthorisedSubmitResult(
    provider: ProviderResult(
        code: "AUTH123456",
        message: "Transaction approved with 3DS authentication"
    ),
    transactionId: "txn_abc123def456",
    threeDSecureResult: ThreeDSecureResult(
        status: "authenticated_and_authorized",
        eci: "05", // Electronic Commerce Indicator
        cavv: "jJ81HADVRtXfCBATEp01CJUAAAA=", // Cardholder Authentication Verification Value
        xid: "Nmp3NmJhZGI2MjAzOTI5Mw==",
        version: "2.1.0",
        directoryServerTransactionId: "c5b808e7-1de1-4069-a17b-f70d3b3b1645",
        acsTransactionId: "13c701a3-5a88-4c45-89e9-ef65e50b8bf9",
        challengeResult: "completed_successfully",
        challengeCompletionIndicator: "Y"
    ),
    riskResult: RiskResult(
        riskScore: 15,
        riskLevel: "LOW",
        riskFactors: ["apple_pay_3ds_protection"]
    )
)
```

**DeclinedSubmitResult** - 3DS authentication failed or transaction declined:


```swift
DeclinedSubmitResult(
    errorReason: "3DS authentication failed",
    errorCode: "3DS_AUTH_FAILED",
    threeDSecureResult: ThreeDSecureResult(
        status: "authentication_failed",
        eci: "07",
        version: "2.1.0",
        challengeResult: "abandoned_by_consumer",
        challengeCompletionIndicator: "N"
    ),
    declineReason: "Issuer declined after failed 3DS challenge"
)
```

**FailedSubmitResult** - Technical error during 3DS processing:


```swift
FailedSubmitResult(
    errorReason: "3DS server timeout",
    errorCode: "3DS_TIMEOUT",
    errorDetails: "Authentication request timed out",
    errorType: "NETWORK_ERROR"
)
```

#### Example implementation


```swift
config.onPostAuthorisation = { result in
    print("3DS Apple Pay transaction completed: \(result)")
    
    DispatchQueue.main.async {
        if let authorizedResult = result as? AuthorisedSubmitResult {
            // Successful 3DS authentication and authorisation
            print("3DS authentication successful!")
            print("Transaction ID: \(authorizedResult.transactionId)")
            print("Authorisation code: \(authorizedResult.provider.code)")
            print("3DS status: \(authorizedResult.threeDSecureResult.status)")
            print("ECI: \(authorizedResult.threeDSecureResult.eci)")
            print("CAVV: \(authorizedResult.threeDSecureResult.cavv)")
            
            // Store 3DS authentication results for compliance
            self.storeThreeDSResults(ThreeDSComplianceData(
                transactionId: authorizedResult.transactionId,
                eci: authorizedResult.threeDSecureResult.eci,
                cavv: authorizedResult.threeDSecureResult.cavv,
                xid: authorizedResult.threeDSecureResult.xid,
                version: authorizedResult.threeDSecureResult.version,
                challengeResult: authorizedResult.threeDSecureResult.challengeResult
            ))
            
            // Process order with high confidence
            self.processOrder(OrderData(
                transactionId: authorizedResult.transactionId,
                paymentMethod: "APPLE_PAY_3DS",
                securityLevel: "MAXIMUM",
                riskScore: authorizedResult.riskResult.riskScore,
                threeDSAuthenticated: true
            ))
            
            // Show success message
            self.showSuccessMessage("Payment successful with enhanced security!")
            
            // Navigate to order confirmation
            self.navigateToOrderConfirmation(transactionId: authorizedResult.transactionId)
            
        } else if let declinedResult = result as? DeclinedSubmitResult {
            // 3DS authentication failed or transaction declined
            print("3DS authentication or transaction failed")
            print("Error: \(declinedResult.errorReason)")
            print("3DS Status: \(declinedResult.threeDSecureResult?.status ?? "unknown")")
            
            // Handle specific 3DS failure scenarios
            if let challengeResult = declinedResult.threeDSecureResult?.challengeResult {
                switch challengeResult {
                case "abandoned_by_consumer":
                    self.showErrorMessage("Authentication was cancelled. Please try again.")
                case "authentication_failed":
                    self.showErrorMessage("Authentication failed. Please verify your identity and try again.")
                default:
                    self.showErrorMessage("Payment was declined. Please try a different payment method.")
                }
            } else {
                self.showErrorMessage("Payment was declined. Please try a different payment method.")
            }
            
            // Log for analysis
            self.logPaymentFailure(PaymentFailureData(
                errorCode: declinedResult.errorCode,
                errorReason: declinedResult.errorReason,
                threeDSStatus: declinedResult.threeDSecureResult?.status,
                challengeResult: declinedResult.threeDSecureResult?.challengeResult
            ))
            
        } else if let failedResult = result as? FailedSubmitResult {
            // Technical error during 3DS processing
            print("3DS processing error: \(failedResult.errorReason)")
            
            switch failedResult.errorCode {
            case "3DS_TIMEOUT":
                self.showErrorMessage("Authentication timed out. Please try again.")
            case "3DS_SERVER_ERROR":
                self.showErrorMessage("Authentication service unavailable. Please try again later.")
            default:
                self.showErrorMessage("Payment processing error. Please try again or contact support.")
            }
            
            // Log technical errors
            self.logTechnicalError(TechnicalErrorData(
                errorCode: failedResult.errorCode,
                errorReason: failedResult.errorReason,
                errorType: failedResult.errorType,
                timestamp: Date()
            ))
        }
    }
}
```

### onError

The `onError` callback receives error information when the Apple Pay 3DS process encounters issues:


```swift
config.onError = { error in
    print("Apple Pay 3DS error: \(error)")
    
    DispatchQueue.main.async {
        if let validationError = error as? ApplePayValidationException {
            // Validation errors specific to 3DS setup
            if validationError.localizedDescription.contains("3DS") {
                self.showErrorMessage("3D Secure configuration error. Please contact support.")
            } else {
                self.showErrorMessage("Payment validation failed. Please check your information.")
            }
        } else if let networkError = error as? URLError {
            // Network errors during 3DS authentication
            self.showErrorMessage("Connection error during authentication. Please check your internet and try again.")
        } else if let pkError = error as? PKPaymentError {
            // Apple Pay specific errors with 3DS context
            switch pkError.code {
            case .paymentNotAllowed:
                self.showErrorMessage("Payment not allowed. Please check your settings.")
            case .paymentNetworkNotSupported:
                self.showErrorMessage("Card network not supported for 3D Secure. Please try a different card.")
            case .deviceCannotMakePayments:
                self.showErrorMessage("Apple Pay is not available on this device.")
            default:
                self.showErrorMessage("3D Secure authentication unavailable. Please try a different payment method.")
            }
        } else {
            // Generic errors
            self.showErrorMessage("Payment error occurred. Please try again.")
        }
        
        // Log all errors for debugging
        self.logError(ErrorData(
            error: error.localizedDescription,
            type: String(describing: type(of: error)),
            context: "3ds_apple_pay_ios",
            timestamp: Date()
        ))
    }
}
```

### onCancel

The `onCancel` callback is triggered when the user cancels the Apple Pay process:


```swift
config.onCancel = { error in
    print("Apple Pay 3DS cancelled by user")
    
    DispatchQueue.main.async {
        // Track cancellation for analytics
        self.trackEvent("apple_pay_3ds_cancelled", parameters: [
            "step": error != nil ? "authentication" : "payment_sheet",
            "reason": error?.localizedDescription ?? "user_cancelled",
            "platform": "ios"
        ])
        
        // Show cancellation message
        self.showInfoMessage("Payment was cancelled. You can try again anytime.")
        
        // Reset payment form state
        self.resetPaymentForm()
    }
}
```

### PKPaymentAuthorizationControllerDelegate methods

When using 3DS with Apple Pay on iOS, you may also need to implement additional delegate methods for enhanced functionality:

#### Shipping contact updates with 3DS context


```swift
func paymentAuthorizationController(
    _ controller: PKPaymentAuthorizationController,
    didSelectShippingContact contact: PKContact,
    handler completion: @escaping (PKPaymentRequestShippingContactUpdate) -> Void
) {
    // Calculate shipping with 3DS risk assessment
    let shippingCost = calculateShippingWithRiskAssessment(for: contact)
    let tax = calculateTaxWithCompliance(for: contact)
    let total = baseAmount + shippingCost + tax
    
    // Enhanced shipping update for 3DS
    let update = PKPaymentRequestShippingContactUpdate(
        errors: [],
        shippingMethods: [
            PKShippingMethod(
                label: "Standard Shipping (3DS Secure)",
                amount: NSDecimalNumber(value: shippingCost),
                type: .final,
                identifier: "standard_3ds",
                detail: "5-7 business days with enhanced security"
            )
        ],
        paymentSummaryItems: [
            PKPaymentSummaryItem(label: "Product", amount: NSDecimalNumber(value: baseAmount)),
            PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(value: tax)),
            PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(value: shippingCost)),
            PKPaymentSummaryItem(label: "Your Store (3DS Secure)", amount: NSDecimalNumber(value: total))
        ]
    )
    
    completion(update)
}
```

#### Payment method selection with 3DS validation


```swift
@available(iOS 15.0, *)
func paymentAuthorizationController(
    _ controller: PKPaymentAuthorizationController,
    didSelectPaymentMethod paymentMethod: PKPaymentMethod,
    handler completion: @escaping (PKPaymentRequestPaymentMethodUpdate) -> Void
) {
    // Validate payment method for 3DS compatibility
    let isThreeDSCompatible = validate3DSCompatibility(for: paymentMethod)
    
    let update = PKPaymentRequestPaymentMethodUpdate(
        errors: isThreeDSCompatible ? [] : [
            PKPaymentError(.paymentMethodInvalidError, 
                          userInfo: [NSLocalizedDescriptionKey: "This card does not support 3D Secure authentication"])
        ],
        paymentSummaryItems: [
            PKPaymentSummaryItem(label: "Product", amount: NSDecimalNumber(value: 99.99)),
            PKPaymentSummaryItem(label: "3DS Processing Fee", amount: NSDecimalNumber(value: 0.00)),
            PKPaymentSummaryItem(label: "Total (3DS Secure)", amount: NSDecimalNumber(value: 99.99))
        ]
    )
    
    completion(update)
}

private func validate3DSCompatibility(for paymentMethod: PKPaymentMethod) -> Bool {
    // Check if the payment network supports 3DS
    let threeDSNetworks: [PKPaymentNetwork] = [.visa, .masterCard, .amex]
    return threeDSNetworks.contains(paymentMethod.network)
}
```

#### Helper methods for 3DS data handling


```swift
// MARK: - 3DS Helper Methods

private func generateDeviceSessionId() -> String {
    let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? "unknown"
    let timestamp = Int(Date().timeIntervalSince1970)
    return "ios_3ds_\(deviceId)_\(timestamp)"
}

private func getCustomerInformation() async -> CustomerInfo {
    // Retrieve customer information for 3DS authentication
    return CustomerInfo(
        fullName: "John Doe",
        email: "customer@example.com",
        phone: "+1234567890",
        mobile: "+1234567890",
        workPhone: "+1234567890",
        billingAddress: Address(
            line1: "123 Main St",
            line2: "Apt 4B",
            city: "New York",
            state: "NY",
            postalCode: "10001",
            country: "US"
        )
    )
}

private func getShippingInformation() async -> Address {
    // Retrieve shipping information for 3DS
    return Address(
        line1: "123 Main St",
        line2: "Apt 4B",
        city: "New York",
        state: "NY",
        postalCode: "10001",
        country: "US"
    )
}

private func storeThreeDSResults(_ data: ThreeDSComplianceData) {
    // Store 3DS results for compliance and dispute resolution
    ComplianceManager.shared.store3DSResults(data)
}

private func processOrder(_ orderData: OrderData) {
    // Process order with 3DS authentication confidence
    OrderManager.shared.processSecureOrder(orderData)
}

private func showSuccessMessage(_ message: String) {
    let alert = UIAlertController(title: "Success", message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default))
    present(alert, animated: true)
}

private func showErrorMessage(_ message: String) {
    let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default))
    present(alert, animated: true)
}

private func showInfoMessage(_ message: String) {
    let alert = UIAlertController(title: "Information", message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default))
    present(alert, animated: true)
}
```