# Troubleshooting

Learn how to diagnose and fix common issues with the card iOS SDK components.

## Exception types and error codes

### SDK exceptions

The card iOS SDK throws specific exceptions for different error scenarios:

| Exception type | Error code | Description | Prevention |
|  --- | --- | --- | --- |
| Network failures | `SDK0500` | Network or transport failure before authorisation completes. | Check device connectivity and implement retry logic for transient failures. |
| Validation exceptions | `SDK0501` | SDK-side validation failed on submit. | Validate all required fields before submission and implement field-level validation callbacks. |
| Authentication pre-initiation | `SDK0502` | 3DS pre-initiation failed. | Verify provider ID and Portal 3DS configuration. Check `onPreInitiateAuthentication` implementation. |
| Authentication rejected | `SDK0503` | Authentication rejected. Please try again or contact your bank. | Verify 3DS configuration in Portal and check authentication callbacks. |
| Challenge flow | `SDK0505` | 3DS challenge flow failed. | Verify merchant configuration and challenge window settings in `onPreAuthentication`. |
| Authentication configuration | `SDK0507` | Authentication configuration invalid. | Ensure both `onPreInitiateAuthentication` and `onPreAuthentication` are set together, or neither. |
| Token vault failures | `SDK0304` | Token vault operation failed (retrieve, update, or delete). | Verify session data, `ownerId`, and `merchantShopperId` alignment. |
| Checkout initialisation | Various | `PxpCheckout.initialize` or `create` rejected configuration. | Validate `CheckoutConfig`, `SessionData`, and component type pairings. |
| Mismatched 3DS callbacks | Custom exception | Only one of the 3DS callback pair is set. | Set both `onPreInitiateAuthentication` and `onPreAuthentication`, or clear both for non-3DS flow. |


## Error handling best practices

### Comprehensive error handler


```swift
import PXPCheckoutSDK

var submitConfig = CardSubmitComponentConfig()

submitConfig.onSubmitError = { error in
    // Log error for debugging
    print("Card submit error: \(error.errorCode) — \(error.errorMessage)")
    
    // Handle specific error types
    switch error.errorCode {
    case "SDK0500":
        showUserMessage("Network connection issue. Please check your internet and try again.")
        offerRetryOption()
        
    case "SDK0501":
        showUserMessage("Please check all required fields are completed correctly.")
        highlightValidationErrors()
        
    case "SDK0502":
        showUserMessage("Payment system temporarily unavailable. Please try again in a few moments.")
        logCriticalError("3DS pre-initiation failed", error: error)
        
    case "SDK0503":
        showUserMessage("Authentication was rejected. Please try again or contact your bank.")
        logCriticalError("3DS authentication rejected", error: error)
        
    case "SDK0505":
        showUserMessage("Authentication failed. Please try again.")
        trackEvent("3ds_challenge_failed")
        
    case "SDK0507":
        showUserMessage("Payment configuration error. Please contact support.")
        logCriticalError("Authentication configuration invalid", error: error)
        
    case let code where code.hasPrefix("SDK03"):
        showUserMessage("Card tokenisation failed. Please check your card details.")
        logTokenVaultError(error)
        
    default:
        showUserMessage("Payment failed. Please try again or contact support.")
        logUnknownError(error)
    }
}

submitConfig.onPostAuthorisation = { result in
    switch result {
    case let authorised as AuthorisedSubmitResult:
        print("Payment successful: \(authorised.provider.message)")
        navigateToSuccessScreen()
        
    case let refused as RefusedSubmitResult:
        print("Payment declined: \(refused.stateData?.message ?? "Unknown")")
        handlePaymentDecline(refused)
        
    case let failed as FailedSubmitResult:
        print("Payment failed: HTTP \(failed.httpStatusCode ?? 0), \(failed.errorReason)")
        handlePaymentFailure(failed)
        
    default:
        print("Unknown result type")
    }
}

func showUserMessage(_ message: String) {
    DispatchQueue.main.async {
        // Show user-friendly error message in your UI
        let alert = UIAlertController(title: "Payment Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        // Present alert to user
    }
}

func logCriticalError(_ context: String, error: BaseSdkException) {
    // Send to error monitoring service
    print("Critical error: \(context) - \(error.errorMessage)")
}

func offerRetryOption() {
    // Show retry button in UI
    print("Offering retry option to user")
}
```

## Troubleshooting common issues

### Card components or fields don't appear

The symptoms of this are:

- A blank area where the field or component should render.
- `create` succeeds but nothing visible in the view hierarchy.
- Wrong field behaviour (e.g., card number field showing in the expiry slot).


#### Diagnostic steps


```swift
// Step 1: Verify component creation
do {
    let cardNumber = try pxpCheckout.create(
        .cardNumber,
        componentConfig: CardNumberComponentConfig(label: "Card number")
    ) as! CardNumberComponent
    
    print("✅ Card number component created successfully")
    print("Component type: \(type(of: cardNumber))")
    
} catch {
    print("❌ Component creation failed: \(error)")
    let nsError = error as NSError
    print("Domain: \(nsError.domain), Code: \(nsError.code)")
    print("User info: \(nsError.userInfo)")
}

// Step 2: Verify component is rendered
struct PaymentView: View {
    var body: some View {
        VStack {
            // Make sure buildContent() is called
            cardNumber.buildContent()
                .onAppear {
                    print("Card number field appeared in view hierarchy")
                }
        }
    }
}

// Step 3: Check component configuration
func validateComponentConfiguration() {
    print("=== Component Configuration Validation ===")
    
    // Verify correct component type and config pairing
    let expectedPairings: [(ComponentType, String)] = [
        (.cardNumber, "CardNumberComponentConfig"),
        (.cardExpiryDate, "CardExpiryDateComponentConfig"),
        (.cardCvc, "CardCvcComponentConfig"),
        (.cardHolderName, "CardHolderNameComponentConfig")
    ]
    
    for (type, configName) in expectedPairings {
        print("\(type) → \(configName)")
    }
}
```

#### Solutions


```swift
// Solution 1: Ensure buildContent() is called
struct CorrectImplementation: View {
    let cardNumber: CardNumberComponent
    let cardExpiry: CardExpiryDateComponent
    let cardCvc: CardCvcComponent
    
    var body: some View {
        VStack(spacing: 16) {
            cardNumber.buildContent()
            cardExpiry.buildContent()
            cardCvc.buildContent()
        }
        .padding()
    }
}

// Solution 2: Use correct component type and config pairing
func createComponentsCorrectly() throws {
    // Correct: .cardNumber with CardNumberComponentConfig
    let cardNumber = try pxpCheckout.create(
        .cardNumber,
        componentConfig: CardNumberComponentConfig(label: "Card number")
    ) as! CardNumberComponent
    
    // Incorrect: .cardNumber with CardExpiryDateComponentConfig
    // This will fail or produce unexpected behaviour
}

// Solution 3: Verify all required components are linked
func linkComponentsToSubmit() {
    var submitConfig = CardSubmitComponentConfig()
    
    // Link all required components
    submitConfig.cardNumberComponent = cardNumber
    submitConfig.cardExpiryDateComponent = cardExpiry
    submitConfig.cardCvcComponent = cardCvc
    
    // Verify linkage
    print("Card number linked: \(submitConfig.cardNumberComponent != nil)")
    print("Card expiry linked: \(submitConfig.cardExpiryDateComponent != nil)")
    print("CVC linked: \(submitConfig.cardCvcComponent != nil)")
}
```

### Checkout initialisation or create throws

The symptoms of this are:

- `PxpCheckout.initialize` throws before any UI appears.
- `create` throws when building a field or component.
- Error messages about invalid configuration or session data.


#### Diagnostic steps


```swift
// Step 1: Detailed error logging
func initializeCheckoutWithDiagnostics() {
    do {
        let sessionData = SessionData(
            sessionId: "your-session-id",
            hmacKey: "your-hmac-key",
            encryptionKey: "your-encryption-key"
        )
        
        let transactionData = TransactionData(
            amount: Decimal(string: "99.99")!,
            currency: "USD",
            entryType: .ecom,
            intent: TransactionIntentData(card: .authorisation),
            merchantTransactionId: "order-\(UUID().uuidString)",
            merchantTransactionDate: { Date() }
        )
        
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: sessionData,
            transactionData: transactionData,
            merchantShopperId: "shopper-123",
            ownerType: "MerchantGroup",
            ownerId: "your-owner-id"
        )
        
        let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
        print("✅ Checkout initialised successfully")
        
    } catch {
        print("❌ Checkout initialisation failed")
        let nsError = error as NSError
        print("Domain: \(nsError.domain)")
        print("Code: \(nsError.code)")
        print("User info: \(nsError.userInfo)")
        print("Localized description: \(error.localizedDescription)")
    }
}

// Step 2: Validate configuration before initialisation
func validateCheckoutConfiguration(_ config: CheckoutConfig) -> [String] {
    var errors: [String] = []
    
    // Validate session data
    if config.session.sessionId.isEmpty {
        errors.append("Session ID is empty")
    }
    if config.session.hmacKey.isEmpty {
        errors.append("HMAC key is empty")
    }
    if config.session.encryptionKey.isEmpty {
        errors.append("Encryption key is empty")
    }
    
    // Validate transaction data
    if config.transactionData.amount <= 0 {
        errors.append("Transaction amount must be greater than zero")
    }
    if config.transactionData.currency.isEmpty {
        errors.append("Currency code is required")
    }
    
    // Validate merchant data
    if config.merchantShopperId.isEmpty {
        errors.append("Merchant shopper ID is required")
    }
    if config.ownerId.isEmpty {
        errors.append("Owner ID is required")
    }
    
    return errors
}

// Step 3: Test with minimal configuration
func testMinimalConfiguration() {
    let minimalConfig = CheckoutConfig(
        environment: .test,
        session: SessionData(
            sessionId: "test-session",
            hmacKey: "test-hmac-key",
            encryptionKey: "test-encryption-key"
        ),
        transactionData: TransactionData(
            amount: Decimal(string: "1.00")!,
            currency: "USD",
            entryType: .ecom,
            intent: TransactionIntentData(card: .authorisation),
            merchantTransactionId: "test-\(UUID().uuidString)",
            merchantTransactionDate: { Date() }
        ),
        merchantShopperId: "test-shopper",
        ownerType: "MerchantGroup",
        ownerId: "test-owner"
    )
    
    do {
        let pxpCheckout = try PxpCheckout.initialize(config: minimalConfig)
        print("✅ Minimal configuration works")
    } catch {
        print("❌ Even minimal configuration fails: \(error)")
    }
}
```

#### Solutions


```swift
// Solution 1: Refresh session data from backend
func refreshSessionData() async throws -> SessionData {
    // Fetch fresh session data from your backend
    let response = try await fetchSessionFromBackend()
    
    return SessionData(
        sessionId: response.sessionId,
        hmacKey: response.hmacKey,
        encryptionKey: response.encryptionKey
    )
}

// Solution 2: Validate configuration before initialisation
func initializeWithValidation() async {
    do {
        // Get fresh session data
        let sessionData = try await refreshSessionData()
        
        let checkoutConfig = CheckoutConfig(
            environment: .test,
            session: sessionData,
            transactionData: createTransactionData(),
            merchantShopperId: "shopper-123",
            ownerType: "MerchantGroup",
            ownerId: "your-owner-id"
        )
        
        // Validate before initialising
        let validationErrors = validateCheckoutConfiguration(checkoutConfig)
        guard validationErrors.isEmpty else {
            print("Configuration validation failed: \(validationErrors)")
            return
        }
        
        let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
        print("Checkout initialised successfully")
        
    } catch {
        print("Failed to initialise: \(error)")
        handleInitialisationError(error)
    }
}

// Solution 3: Implement proper error recovery
func handleInitialisationError(_ error: Error) {
    if let nsError = error as NSError {
        switch nsError.code {
        case 401:
            // Unauthorised - refresh session
            Task {
                try? await refreshSessionData()
                // Retry initialisation
            }
        case 403:
            // Forbidden - check bundle ID in Portal
            showConfigurationError("App not authorised. Please check Unity Portal configuration.")
        default:
            showConfigurationError("Initialisation failed: \(error.localizedDescription)")
        }
    }
}
```

### Submit never calls onPostAuthorisation

The symptoms of this are:

- The customer taps pay but nothing happens.
- No logs from `onPostAuthorisation`.
- Submit appears to hang or do nothing.


#### Diagnostic steps


```swift
// Step 1: Verify all required callbacks are implemented
func verifySubmitCallbacks(_ config: CardSubmitComponentConfig) {
    print("=== Submit Callback Verification ===")
    
    // Check required callbacks
    print("onPreAuthorisation set: \(config.onPreAuthorisation != nil ? "✅" : "❌ REQUIRED")")
    print("onPostAuthorisation set: \(config.onPostAuthorisation != nil ? "✅" : "⚠️  Recommended")")
    print("onSubmitError set: \(config.onSubmitError != nil ? "✅" : "⚠️  Recommended")")
    
    // Check component linkage
    print("cardNumberComponent linked: \(config.cardNumberComponent != nil ? "✅" : "❌")")
    print("cardExpiryDateComponent linked: \(config.cardExpiryDateComponent != nil ? "✅" : "❌")")
    print("cardCvcComponent linked: \(config.cardCvcComponent != nil ? "✅" : "❌")")
}

// Step 2: Add comprehensive logging to all callbacks
var submitConfig = CardSubmitComponentConfig()

submitConfig.onStartSubmit = {
    print("🔵 Submit started")
}

submitConfig.onPreTokenisation = {
    print("🔵 Pre-tokenisation callback")
    return true
}

submitConfig.onPostTokenisation = { result in
    print("🔵 Post-tokenisation: \(result)")
}

submitConfig.onPreAuthorisation = { preAuth in
    print("🔵 Pre-authorisation callback")
    print("Gateway token: \(preAuth?.gatewayTokenId ?? "nil")")
    // This is an async closure - you CAN use await here
    return TransactionInitiationData()
}

submitConfig.onPostAuthorisation = { result in
    print("🔵 Post-authorisation callback")
    print("Result type: \(type(of: result))")
}

submitConfig.onSubmitError = { error in
    print("🔴 Submit error: \(error.errorCode) - \(error.errorMessage)")
}

// Step 3: Verify field validation states
func checkFieldValidation() {
    // Note: getValue() is internal and not accessible
    // Validation state is managed internally by the SDK
    
    print("=== Field Validation States ===")
    print("Submit button will be enabled when all fields are valid")
    print("Use onValidation callback to monitor validation state")
}
```

#### Solutions


```swift
// Solution 1: Implement required callbacks
var submitConfig = CardSubmitComponentConfig()

// REQUIRED: onPreAuthorisation must be implemented
submitConfig.onPreAuthorisation = { preAuth in
    print("Pre-authorisation data: \(String(describing: preAuth))")
    
    // This is an async closure - you CAN use await here
    // Return transaction initiation data
    return TransactionInitiationData(
        psd2Data: PSD2Data(scaExemption: .lowValue)
    )
}

// REQUIRED: onPostAuthorisation handles final result
submitConfig.onPostAuthorisation = { result in
    switch result {
    case is AuthorisedSubmitResult, is CapturedSubmitResult:
        print("Payment successful")
        navigateToSuccessScreen()
    case let refused as RefusedSubmitResult:
        print("Payment declined: \(refused.stateData?.message ?? "Unknown")")
        showDeclineMessage(refused)
    case let failed as FailedSubmitResult:
        print("Payment failed: \(failed.errorReason)")
        showFailureMessage(failed)
    default:
        print("Unknown result type")
    }
}

// Solution 2: Verify component linkage
func createSubmitWithProperLinkage() throws -> CardSubmitComponent {
    // Create field components first
    let cardNumber = try pxpCheckout.create(
        .cardNumber,
        componentConfig: CardNumberComponentConfig(label: "Card number")
    ) as! CardNumberComponent
    
    let cardExpiry = try pxpCheckout.create(
        .cardExpiryDate,
        componentConfig: CardExpiryDateComponentConfig(label: "Expiry")
    ) as! CardExpiryDateComponent
    
    let cardCvc = try pxpCheckout.create(
        .cardCvc,
        componentConfig: CardCvcComponentConfig(label: "CVC")
    ) as! CardCvcComponent
    
    // Link components to submit
    var submitConfig = CardSubmitComponentConfig()
    submitConfig.cardNumberComponent = cardNumber
    submitConfig.cardExpiryDateComponent = cardExpiry
    submitConfig.cardCvcComponent = cardCvc
    
    // Implement callbacks
    submitConfig.onPreAuthorisation = { _ in
        // This is an async closure - you CAN use await here
        return TransactionInitiationData()
    }
    submitConfig.onPostAuthorisation = { _ in }
    
    // Create submit component
    return try pxpCheckout.create(.cardSubmit, componentConfig: submitConfig) as! CardSubmitComponent
}

// Solution 3: Handle async operations correctly
submitConfig.onPreAuthorisation = { preAuth in
    print("Pre-authorisation callback")
    
    // This is an async closure - you CAN use await here for async operations
    return TransactionInitiationData()
}

submitConfig.onPostAuthorisation = { result in
    // Move heavy work off main thread
    Task {
        await processPaymentResult(result)
    }
    
    // Update UI on main thread
    DispatchQueue.main.async {
        updateUIForResult(result)
    }
}
```

### Validation errors or pay button stays disabled

The symptoms of this are:

- Submit button stays disabled when `disableUntilValidated` is used.
- `onSubmitError` fires with `SDK0501` after tapping pay.
- Fields appear filled but validation fails.


#### Diagnostic steps


```swift
// Step 1: Add validation callback logging
submitConfig.onValidation = { results in
    print("=== Validation Results ===")
    for (index, result) in results.enumerated() {
        print("Field \(index):")
        print("  Valid: \(result.valid ? "✅" : "❌")")
        
        if let errors = result.errors, !errors.isEmpty {
            print("  Errors:")
            for (code, message) in errors {
                print("    [\(code)] \(message)")
            }
        }
    }
}

// Step 2: Check individual field validation
var cardNumberConfig = CardNumberComponentConfig(label: "Card number")
cardNumberConfig.onValidationPassed = { _ in
    print("✅ Card number validation passed")
}
cardNumberConfig.onValidationFailed = { _ in
    print("❌ Card number validation failed")
}

var cardExpiryConfig = CardExpiryDateComponentConfig(label: "Expiry")
cardExpiryConfig.onValidationPassed = { _ in
    print("✅ Expiry validation passed")
}
cardExpiryConfig.onValidationFailed = { _ in
    print("❌ Expiry validation failed")
}

// Step 3: Manually trigger validation
func testFieldValidation() {
    print("=== Manual Validation Test ===")
    
    // Note: getValue() is internal and not accessible
    // Use onValidation callback to inspect validation results
    print("Monitor validation via onValidation callback")
    print("Enable validationOnChange for real-time validation")
}
```

#### Solutions


```swift
// Solution 1: Enable real-time validation
var cardNumberConfig = CardNumberComponentConfig(
    label: "Card number",
    validationOnChange: true,  // Enable real-time validation
    onValidationPassed: { _ in
        print("Card number valid")
    },
    onValidationFailed: { _ in
        print("Card number invalid")
    }
)

var cardExpiryConfig = CardExpiryDateComponentConfig(
    label: "Expiry",
    validationOnChange: true
)

var cardCvcConfig = CardCvcComponentConfig(
    label: "CVC",
    validationOnChange: true
)

// Solution 2: Handle validation with disableUntilValidated
var submitConfig = CardSubmitComponentConfig()
submitConfig.disableUntilValidated = true

submitConfig.onValidation = { results in
    let allValid = results.allSatisfy { $0.valid }
    print("All fields valid: \(allValid ? "✅" : "❌")")
    
    if !allValid {
        // Show which fields are invalid
        for (index, result) in results.enumerated() where !result.valid {
            print("Field \(index) is invalid")
            if let errors = result.errors {
                for (code, message) in errors {
                    print("  Error [\(code)]: \(message)")
                }
            }
        }
    }
}

// Solution 3: Include billing address validation when using AVS
if submitConfig.avsRequest == true {
    print("⚠️  AVS enabled - billing address fields must also be valid")
    
    // Ensure billing address components are created and linked
    let billing = try pxpCheckout.create(
        .billingAddress,
        componentConfig: BillingAddressComponentConfig()
    ) as! BillingAddressComponent
    
    submitConfig.billingAddressComponents = BillingAddressComponents(
        billingAddressComponent: billing
    )
}
```

### 3DS doesn't start or fails mid-flow

The symptoms of this are:

- Challenge UI never appears.
- Flow aborts with `SDK0502` to `SDK0511` errors.
- Exception mentioning authentication configuration.


#### Diagnostic steps


```swift
// Step 1: Verify 3DS configuration
func verify3DSConfiguration(_ config: CardSubmitComponentConfig) {
    print("=== 3DS Configuration Verification ===")
    
    let preInitiateSet = config.onPreInitiateAuthentication != nil
    let preAuthSet = config.onPreAuthentication != nil
    
    print("onPreInitiateAuthentication set: \(preInitiateSet ? "✅" : "❌")")
    print("onPreAuthentication set: \(preAuthSet ? "✅" : "❌")")
    
    if preInitiateSet != preAuthSet {
        print("⚠️  WARNING: 3DS callbacks must be set together or not at all")
    }
    
    if preInitiateSet && preAuthSet {
        print("✅ 3DS callbacks properly configured")
    } else if !preInitiateSet && !preAuthSet {
        print("ℹ️  Non-3DS flow (no authentication callbacks set)")
    }
}

// Step 2: Add detailed 3DS logging
var submitConfig = CardSubmitComponentConfig()

submitConfig.onPreInitiateAuthentication = {
    print("🔐 3DS Pre-initiation started")
    
    let authData = PreInitiateIntegratedAuthenticationData(
        providerId: "your_provider_id",
        requestorAuthenticationIndicator: .paymentTransaction,
        timeout: 120
    )
    
    print("Provider ID: \(authData.providerId)")
    print("Timeout: \(authData.timeout)")
    
    // This is an async closure - you CAN use await here
    return authData
}

submitConfig.onPostInitiateAuthentication = { result in
    print("🔐 3DS Pre-initiation completed")
    
    if let failed = result as? FailedAuthenticationResult {
        print("❌ Pre-initiation failed: \(failed.errorReason)")
        print("Error code: \(failed.errorCode)")
    } else {
        print("✅ Pre-initiation successful")
    }
}

submitConfig.onPreAuthentication = {
    print("🔐 3DS Authentication started")
    
    let authData = InitiateIntegratedAuthenticationData(
        merchantCountryNumericCode: "840",
        merchantLegalName: "Your Company Ltd",
        challengeWindowSize: .size5,
        requestorChallengeIndicator: .noPreference,
        timeout: 300
    )
    
    print("Merchant country: \(authData.merchantCountryNumericCode)")
    print("Challenge window: \(authData.challengeWindowSize)")
    
    // This is an async closure - you CAN use await here
    return authData
}

submitConfig.onPostAuthentication = { result in
    print("🔐 3DS Authentication completed")
    
    if result is FailedAuthenticationResult {
        print("❌ Authentication failed")
    } else {
        print("✅ Authentication successful")
    }
}
```

#### Solutions


```swift
// Solution 1: Verify Unity Portal 3DS configuration
// 1. Log in to Unity Portal
// 2. Go to Merchant setup > Merchant groups
// 3. Select your merchant group
// 4. Click Services tab
// 5. Edit Card service
// 6. Click Configure modules
// 7. Enable "ThreeD secure service"

// Solution 2: Implement complete 3DS callback set
var submitConfig = CardSubmitComponentConfig()

// Both 3DS callbacks must be set together
submitConfig.onPreInitiateAuthentication = {
    // This is an async closure - you CAN use await here
    return PreInitiateIntegratedAuthenticationData(
        providerId: "your_3ds_provider_id",
        requestorAuthenticationIndicator: .paymentTransaction,
        timeout: 120
    )
}

submitConfig.onPreAuthentication = {
    // This is an async closure - you CAN use await here
    return InitiateIntegratedAuthenticationData(
        merchantCountryNumericCode: "840",
        merchantLegalName: "Your Company Ltd",
        challengeWindowSize: .size5,
        requestorChallengeIndicator: .noPreference,
        timeout: 300
    )
}

// Optional but recommended
submitConfig.onPostInitiateAuthentication = { result in
    if let failed = result as? FailedAuthenticationResult {
        print("3DS pre-initiation failed: \(failed.errorReason)")
    }
}

submitConfig.onPostAuthentication = { result in
    if result is FailedAuthenticationResult {
        print("3DS authentication failed")
    }
}

// Required for all flows
submitConfig.onPreAuthorisation = { _ in
    // This is an async closure - you CAN use await here
    return TransactionInitiationData()
}

submitConfig.onPostAuthorisation = { result in
    // Handle final result
}

// Solution 3: Implement conditional 3DS
let transactionAmount: Decimal = 150.00
let requiresStrong3DS = transactionAmount > 100.00

submitConfig.onPreInitiateAuthentication = {
    // Return nil to skip 3DS for low amounts
    guard requiresStrong3DS else {
        print("Skipping 3DS for low-value transaction")
        return nil
    }
    
    // This is an async closure - you CAN use await here
    return PreInitiateIntegratedAuthenticationData(
        providerId: "your_provider_id",
        requestorAuthenticationIndicator: .paymentTransaction,
        timeout: 120
    )
}

// If conditional, still set both callbacks
submitConfig.onPreAuthentication = {
    // This is an async closure - you CAN use await here
    return InitiateIntegratedAuthenticationData(
        merchantCountryNumericCode: "840",
        merchantLegalName: "Your Company Ltd",
        challengeWindowSize: .size5,
        requestorChallengeIndicator: .noPreference,
        timeout: 300
    )
}
```

## Debugging tools and techniques

To get more detailed information about errors, use this debugging setup:


```swift
// Enable comprehensive logging
class CardPaymentDebugger {
    static func enableDebugMode(_ config: inout CardSubmitComponentConfig) {
        print("🔧 Card Payment Debug Mode Enabled")
        logEnvironmentInfo()
        
        // Wrap existing callbacks with logging
        let originalOnSubmitError = config.onSubmitError
        config.onSubmitError = { error in
            logError(error)
            originalOnSubmitError?(error)
        }
        
        let originalOnPostAuthorisation = config.onPostAuthorisation
        config.onPostAuthorisation = { result in
            logAuthorisationResult(result)
            originalOnPostAuthorisation?(result)
        }
    }
    
    static func logEnvironmentInfo() {
        print("=== Card Payment Environment ===")
        print("Device Model: \(UIDevice.current.model)")
        print("iOS Version: \(UIDevice.current.systemVersion)")
        print("Bundle ID: \(Bundle.main.bundleIdentifier ?? "Unknown")")
        print("App Version: \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "Unknown")")
        print("Build: \(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "Unknown")")
    }
    
    static func logError(_ error: BaseSdkException) {
        print("=== Payment Error Debug ===")
        print("Error Code: \(error.errorCode)")
        print("Error Message: \(error.errorMessage)")
        
        if let userInfo = (error as NSError).userInfo as? [String: Any] {
            print("Additional info:")
            for (key, value) in userInfo {
                print("  \(key): \(value)")
            }
        }
    }
    
    static func logAuthorisationResult(_ result: SubmitResult) {
        print("=== Authorisation Result Debug ===")
        print("Result type: \(type(of: result))")
        
        switch result {
        case let authorised as AuthorisedSubmitResult:
            print("State: Authorised")
            print("Transaction ID: \(authorised.fundingData.transactionId)")
            print("Provider message: \(authorised.provider.message)")
            
        case let captured as CapturedSubmitResult:
            print("State: Captured")
            print("Transaction ID: \(captured.fundingData.transactionId)")
            print("Provider message: \(captured.provider.message)")
            
        case let refused as RefusedSubmitResult:
            print("State: Refused")
            print("Decline reason: \(refused.stateData?.message ?? "Unknown")")
            print("Decline code: \(refused.stateData?.code ?? "Unknown")")
            if let advice = refused.provider.merchantAdvice {
                print("Merchant advice: \(advice.message)")
            }
            
        case let failed as FailedSubmitResult:
            print("State: Failed")
            print("HTTP Status: \(failed.httpStatusCode ?? 0)")
            print("Error code: \(failed.errorCode ?? "Unknown")")
            print("Error reason: \(failed.errorReason)")
            print("Correlation ID: \(failed.correlationId ?? "None")")
            
        default:
            print("Unknown result type")
        }
    }
    
    static func logFieldValues(
        cardNumber: CardNumberComponent?,
        expiry: CardExpiryDateComponent?,
        cvc: CardCvcComponent?
    ) {
        print("=== Field Values Debug ===")
        // Note: getValue() is internal and not accessible
        print("Use onValidation callback to monitor field states")
        print("Enable validationOnChange for real-time monitoring")
    }
}

// Usage in your implementation
var submitConfig = CardSubmitComponentConfig()
CardPaymentDebugger.enableDebugMode(&submitConfig)

// Before submission
CardPaymentDebugger.logFieldValues(
    cardNumber: cardNumber,
    expiry: cardExpiry,
    cvc: cardCvc
)
```

## Prevention and best practices

### Proactive error prevention


```swift
// Pre-flight checks before initialising payment
func performPaymentPreflightChecks() async -> Bool {
    let checks: [(String, () async -> Bool, String)] = [
        ("Session validity", {
            return self.validateSessionData()
        }, "Session data is invalid or expired"),
        
        ("Network connectivity", {
            return await self.checkNetworkConnectivity()
        }, "Network connection required"),
        
        ("Component configuration", {
            return self.validateComponentConfiguration()
        }, "Component configuration is invalid"),
        
        ("3DS configuration", {
            return self.validate3DSConfiguration()
        }, "3DS configuration is invalid")
    ]
    
    var allPassed = true
    print("=== Payment Pre-flight Checks ===")
    
    for (name, test, failureMessage) in checks {
        let passed = await test()
        print("\(passed ? "✅" : "❌") \(name): \(passed ? "PASSED" : failureMessage)")
        if !passed {
            allPassed = false
        }
    }
    
    return allPassed
}

private func validateSessionData() -> Bool {
    guard !checkoutConfig.session.sessionId.isEmpty,
          !checkoutConfig.session.hmacKey.isEmpty,
          !checkoutConfig.session.encryptionKey.isEmpty else {
        return false
    }
    return true
}

private func checkNetworkConnectivity() async -> Bool {
    guard let url = URL(string: "https://api.pxp.io/health") else { return false }
    
    do {
        let (_, response) = try await URLSession.shared.data(from: url)
        return (response as? HTTPURLResponse)?.statusCode == 200
    } catch {
        return false
    }
}

private func validateComponentConfiguration() -> Bool {
    // Check all required components are created and linked
    return submitConfig.cardNumberComponent != nil &&
           submitConfig.cardExpiryDateComponent != nil &&
           submitConfig.cardCvcComponent != nil
}

private func validate3DSConfiguration() -> Bool {
    let preInitSet = submitConfig.onPreInitiateAuthentication != nil
    let preAuthSet = submitConfig.onPreAuthentication != nil
    
    // Either both set or neither set
    return preInitSet == preAuthSet
}

// Use before payment
func initiatePayment() async {
    let preflightPassed = await performPaymentPreflightChecks()
    
    if preflightPassed {
        await submitPayment()
    } else {
        showPreflightFailureMessage()
    }
}
```

### Monitoring and alerting


```swift
// Error monitoring setup
class PaymentErrorMonitor {
    static func captureError(_ error: Error, context: [String: Any] = [:]) {
        var errorData: [String: Any] = [
            "error_message": error.localizedDescription,
            "error_type": String(describing: type(of: error)),
            "device_model": UIDevice.current.model,
            "ios_version": UIDevice.current.systemVersion,
            "bundle_id": Bundle.main.bundleIdentifier ?? "Unknown",
            "timestamp": ISO8601DateFormatter().string(from: Date())
        ]
        
        // Add context
        errorData.merge(context) { _, new in new }
        
        // Add SDK-specific data
        if let sdkError = error as? BaseSdkException {
            errorData["error_code"] = sdkError.errorCode
            errorData["sdk_error"] = true
        }
        
        // Send to your error tracking service
        print("📊 Error captured: \(errorData)")
        // Example: Crashlytics.record(error: error, userInfo: errorData)
    }
    
    static func trackPaymentEvent(_ eventType: String, data: [String: Any] = [:]) {
        var eventData = data
        eventData["event_type"] = eventType
        eventData["timestamp"] = ISO8601DateFormatter().string(from: Date())
        eventData["component"] = "CardPayment"
        
        print("📈 Event tracked: \(eventData)")
        // Send to your analytics service
    }
}

// Integration with card submit
var submitConfig = CardSubmitComponentConfig()

submitConfig.onSubmitError = { error in
    PaymentErrorMonitor.captureError(error, context: [
        "flow_step": "card_submit",
        "transaction_amount": transactionData.amount,
        "currency": transactionData.currency
    ])
}

submitConfig.onPostAuthorisation = { result in
    let eventType: String
    switch result {
    case is AuthorisedSubmitResult, is CapturedSubmitResult:
        eventType = "payment_success"
    case is RefusedSubmitResult:
        eventType = "payment_declined"
    case is FailedSubmitResult:
        eventType = "payment_failed"
    default:
        eventType = "payment_unknown"
    }
    
    PaymentErrorMonitor.trackPaymentEvent(eventType, data: [
        "result_type": String(describing: type(of: result))
    ])
}
```