{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["sub-heading"]},"type":"markdown"},"seo":{"title":"Troubleshooting","description":"Transform your commerce with PXP's unified platform—seamless payments, real-time insights, and global growth in one powerful integration.","lang":"en-UK","siteUrl":"https://developer.pxp.io","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"troubleshooting","__idx":0},"children":["Troubleshooting"]},{"$$mdtype":"Tag","name":"SubHeading","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Learn how to diagnose and fix common issues with the card iOS SDK components."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"exception-types-and-error-codes","__idx":1},"children":["Exception types and error codes"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"sdk-exceptions","__idx":2},"children":["SDK exceptions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The card iOS SDK throws specific exceptions for different error scenarios:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Exception type"},"children":["Exception type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Error code"},"children":["Error code"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Prevention"},"children":["Prevention"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Network failures"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0500"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Network or transport failure before authorisation completes."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Check device connectivity and implement retry logic for transient failures."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Validation exceptions"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0501"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["SDK-side validation failed on submit."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Validate all required fields before submission and implement field-level validation callbacks."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Authentication pre-initiation"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0502"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["3DS pre-initiation failed."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify provider ID and Portal 3DS configuration. Check ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPreInitiateAuthentication"]}," implementation."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Authentication rejected"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0503"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Authentication rejected. Please try again or contact your bank."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify 3DS configuration in Portal and check authentication callbacks."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Challenge flow"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0505"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["3DS challenge flow failed."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify merchant configuration and challenge window settings in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPreAuthentication"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Authentication configuration"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0507"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Authentication configuration invalid."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Ensure both ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPreInitiateAuthentication"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPreAuthentication"]}," are set together, or neither."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Token vault failures"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0304"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Token vault operation failed (retrieve, update, or delete)."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify session data, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ownerId"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantShopperId"]}," alignment."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Checkout initialisation"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Various"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PxpCheckout.initialize"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create"]}," rejected configuration."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Validate ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutConfig"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SessionData"]},", and component type pairings."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Mismatched 3DS callbacks"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Custom exception"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Only one of the 3DS callback pair is set."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Set both ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPreInitiateAuthentication"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPreAuthentication"]},", or clear both for non-3DS flow."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"error-handling-best-practices","__idx":3},"children":["Error handling best practices"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"comprehensive-error-handler","__idx":4},"children":["Comprehensive error handler"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"import PXPCheckoutSDK\n\nvar submitConfig = CardSubmitComponentConfig()\n\nsubmitConfig.onSubmitError = { error in\n    // Log error for debugging\n    print(\"Card submit error: \\(error.errorCode) — \\(error.errorMessage)\")\n    \n    // Handle specific error types\n    switch error.errorCode {\n    case \"SDK0500\":\n        showUserMessage(\"Network connection issue. Please check your internet and try again.\")\n        offerRetryOption()\n        \n    case \"SDK0501\":\n        showUserMessage(\"Please check all required fields are completed correctly.\")\n        highlightValidationErrors()\n        \n    case \"SDK0502\":\n        showUserMessage(\"Payment system temporarily unavailable. Please try again in a few moments.\")\n        logCriticalError(\"3DS pre-initiation failed\", error: error)\n        \n    case \"SDK0503\":\n        showUserMessage(\"Authentication was rejected. Please try again or contact your bank.\")\n        logCriticalError(\"3DS authentication rejected\", error: error)\n        \n    case \"SDK0505\":\n        showUserMessage(\"Authentication failed. Please try again.\")\n        trackEvent(\"3ds_challenge_failed\")\n        \n    case \"SDK0507\":\n        showUserMessage(\"Payment configuration error. Please contact support.\")\n        logCriticalError(\"Authentication configuration invalid\", error: error)\n        \n    case let code where code.hasPrefix(\"SDK03\"):\n        showUserMessage(\"Card tokenisation failed. Please check your card details.\")\n        logTokenVaultError(error)\n        \n    default:\n        showUserMessage(\"Payment failed. Please try again or contact support.\")\n        logUnknownError(error)\n    }\n}\n\nsubmitConfig.onPostAuthorisation = { result in\n    switch result {\n    case let authorised as AuthorisedSubmitResult:\n        print(\"Payment successful: \\(authorised.provider.message)\")\n        navigateToSuccessScreen()\n        \n    case let refused as RefusedSubmitResult:\n        print(\"Payment declined: \\(refused.stateData?.message ?? \"Unknown\")\")\n        handlePaymentDecline(refused)\n        \n    case let failed as FailedSubmitResult:\n        print(\"Payment failed: HTTP \\(failed.httpStatusCode ?? 0), \\(failed.errorReason)\")\n        handlePaymentFailure(failed)\n        \n    default:\n        print(\"Unknown result type\")\n    }\n}\n\nfunc showUserMessage(_ message: String) {\n    DispatchQueue.main.async {\n        // Show user-friendly error message in your UI\n        let alert = UIAlertController(title: \"Payment Error\", message: message, preferredStyle: .alert)\n        alert.addAction(UIAlertAction(title: \"OK\", style: .default))\n        // Present alert to user\n    }\n}\n\nfunc logCriticalError(_ context: String, error: BaseSdkException) {\n    // Send to error monitoring service\n    print(\"Critical error: \\(context) - \\(error.errorMessage)\")\n}\n\nfunc offerRetryOption() {\n    // Show retry button in UI\n    print(\"Offering retry option to user\")\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"troubleshooting-common-issues","__idx":5},"children":["Troubleshooting common issues"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"card-components-or-fields-dont-appear","__idx":6},"children":["Card components or fields don't appear"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The symptoms of this are:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A blank area where the field or component should render."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create"]}," succeeds but nothing visible in the view hierarchy."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Wrong field behaviour (e.g., card number field showing in the expiry slot)."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"diagnostic-steps","__idx":7},"children":["Diagnostic steps"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Step 1: Verify component creation\ndo {\n    let cardNumber = try pxpCheckout.create(\n        .cardNumber,\n        componentConfig: CardNumberComponentConfig(label: \"Card number\")\n    ) as! CardNumberComponent\n    \n    print(\"✅ Card number component created successfully\")\n    print(\"Component type: \\(type(of: cardNumber))\")\n    \n} catch {\n    print(\"❌ Component creation failed: \\(error)\")\n    let nsError = error as NSError\n    print(\"Domain: \\(nsError.domain), Code: \\(nsError.code)\")\n    print(\"User info: \\(nsError.userInfo)\")\n}\n\n// Step 2: Verify component is rendered\nstruct PaymentView: View {\n    var body: some View {\n        VStack {\n            // Make sure buildContent() is called\n            cardNumber.buildContent()\n                .onAppear {\n                    print(\"Card number field appeared in view hierarchy\")\n                }\n        }\n    }\n}\n\n// Step 3: Check component configuration\nfunc validateComponentConfiguration() {\n    print(\"=== Component Configuration Validation ===\")\n    \n    // Verify correct component type and config pairing\n    let expectedPairings: [(ComponentType, String)] = [\n        (.cardNumber, \"CardNumberComponentConfig\"),\n        (.cardExpiryDate, \"CardExpiryDateComponentConfig\"),\n        (.cardCvc, \"CardCvcComponentConfig\"),\n        (.cardHolderName, \"CardHolderNameComponentConfig\")\n    ]\n    \n    for (type, configName) in expectedPairings {\n        print(\"\\(type) → \\(configName)\")\n    }\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"solutions","__idx":8},"children":["Solutions"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Solution 1: Ensure buildContent() is called\nstruct CorrectImplementation: View {\n    let cardNumber: CardNumberComponent\n    let cardExpiry: CardExpiryDateComponent\n    let cardCvc: CardCvcComponent\n    \n    var body: some View {\n        VStack(spacing: 16) {\n            cardNumber.buildContent()\n            cardExpiry.buildContent()\n            cardCvc.buildContent()\n        }\n        .padding()\n    }\n}\n\n// Solution 2: Use correct component type and config pairing\nfunc createComponentsCorrectly() throws {\n    // Correct: .cardNumber with CardNumberComponentConfig\n    let cardNumber = try pxpCheckout.create(\n        .cardNumber,\n        componentConfig: CardNumberComponentConfig(label: \"Card number\")\n    ) as! CardNumberComponent\n    \n    // Incorrect: .cardNumber with CardExpiryDateComponentConfig\n    // This will fail or produce unexpected behaviour\n}\n\n// Solution 3: Verify all required components are linked\nfunc linkComponentsToSubmit() {\n    var submitConfig = CardSubmitComponentConfig()\n    \n    // Link all required components\n    submitConfig.cardNumberComponent = cardNumber\n    submitConfig.cardExpiryDateComponent = cardExpiry\n    submitConfig.cardCvcComponent = cardCvc\n    \n    // Verify linkage\n    print(\"Card number linked: \\(submitConfig.cardNumberComponent != nil)\")\n    print(\"Card expiry linked: \\(submitConfig.cardExpiryDateComponent != nil)\")\n    print(\"CVC linked: \\(submitConfig.cardCvcComponent != nil)\")\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"checkout-initialisation-or-create-throws","__idx":9},"children":["Checkout initialisation or create throws"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The symptoms of this are:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PxpCheckout.initialize"]}," throws before any UI appears."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create"]}," throws when building a field or component."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Error messages about invalid configuration or session data."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"diagnostic-steps-1","__idx":10},"children":["Diagnostic steps"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Step 1: Detailed error logging\nfunc initializeCheckoutWithDiagnostics() {\n    do {\n        let sessionData = SessionData(\n            sessionId: \"your-session-id\",\n            hmacKey: \"your-hmac-key\",\n            encryptionKey: \"your-encryption-key\"\n        )\n        \n        let transactionData = TransactionData(\n            amount: Decimal(string: \"99.99\")!,\n            currency: \"USD\",\n            entryType: .ecom,\n            intent: TransactionIntentData(card: .authorisation),\n            merchantTransactionId: \"order-\\(UUID().uuidString)\",\n            merchantTransactionDate: { Date() }\n        )\n        \n        let checkoutConfig = CheckoutConfig(\n            environment: .test,\n            session: sessionData,\n            transactionData: transactionData,\n            merchantShopperId: \"shopper-123\",\n            ownerType: \"MerchantGroup\",\n            ownerId: \"your-owner-id\"\n        )\n        \n        let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)\n        print(\"✅ Checkout initialised successfully\")\n        \n    } catch {\n        print(\"❌ Checkout initialisation failed\")\n        let nsError = error as NSError\n        print(\"Domain: \\(nsError.domain)\")\n        print(\"Code: \\(nsError.code)\")\n        print(\"User info: \\(nsError.userInfo)\")\n        print(\"Localized description: \\(error.localizedDescription)\")\n    }\n}\n\n// Step 2: Validate configuration before initialisation\nfunc validateCheckoutConfiguration(_ config: CheckoutConfig) -> [String] {\n    var errors: [String] = []\n    \n    // Validate session data\n    if config.session.sessionId.isEmpty {\n        errors.append(\"Session ID is empty\")\n    }\n    if config.session.hmacKey.isEmpty {\n        errors.append(\"HMAC key is empty\")\n    }\n    if config.session.encryptionKey.isEmpty {\n        errors.append(\"Encryption key is empty\")\n    }\n    \n    // Validate transaction data\n    if config.transactionData.amount <= 0 {\n        errors.append(\"Transaction amount must be greater than zero\")\n    }\n    if config.transactionData.currency.isEmpty {\n        errors.append(\"Currency code is required\")\n    }\n    \n    // Validate merchant data\n    if config.merchantShopperId.isEmpty {\n        errors.append(\"Merchant shopper ID is required\")\n    }\n    if config.ownerId.isEmpty {\n        errors.append(\"Owner ID is required\")\n    }\n    \n    return errors\n}\n\n// Step 3: Test with minimal configuration\nfunc testMinimalConfiguration() {\n    let minimalConfig = CheckoutConfig(\n        environment: .test,\n        session: SessionData(\n            sessionId: \"test-session\",\n            hmacKey: \"test-hmac-key\",\n            encryptionKey: \"test-encryption-key\"\n        ),\n        transactionData: TransactionData(\n            amount: Decimal(string: \"1.00\")!,\n            currency: \"USD\",\n            entryType: .ecom,\n            intent: TransactionIntentData(card: .authorisation),\n            merchantTransactionId: \"test-\\(UUID().uuidString)\",\n            merchantTransactionDate: { Date() }\n        ),\n        merchantShopperId: \"test-shopper\",\n        ownerType: \"MerchantGroup\",\n        ownerId: \"test-owner\"\n    )\n    \n    do {\n        let pxpCheckout = try PxpCheckout.initialize(config: minimalConfig)\n        print(\"✅ Minimal configuration works\")\n    } catch {\n        print(\"❌ Even minimal configuration fails: \\(error)\")\n    }\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"solutions-1","__idx":11},"children":["Solutions"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Solution 1: Refresh session data from backend\nfunc refreshSessionData() async throws -> SessionData {\n    // Fetch fresh session data from your backend\n    let response = try await fetchSessionFromBackend()\n    \n    return SessionData(\n        sessionId: response.sessionId,\n        hmacKey: response.hmacKey,\n        encryptionKey: response.encryptionKey\n    )\n}\n\n// Solution 2: Validate configuration before initialisation\nfunc initializeWithValidation() async {\n    do {\n        // Get fresh session data\n        let sessionData = try await refreshSessionData()\n        \n        let checkoutConfig = CheckoutConfig(\n            environment: .test,\n            session: sessionData,\n            transactionData: createTransactionData(),\n            merchantShopperId: \"shopper-123\",\n            ownerType: \"MerchantGroup\",\n            ownerId: \"your-owner-id\"\n        )\n        \n        // Validate before initialising\n        let validationErrors = validateCheckoutConfiguration(checkoutConfig)\n        guard validationErrors.isEmpty else {\n            print(\"Configuration validation failed: \\(validationErrors)\")\n            return\n        }\n        \n        let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)\n        print(\"Checkout initialised successfully\")\n        \n    } catch {\n        print(\"Failed to initialise: \\(error)\")\n        handleInitialisationError(error)\n    }\n}\n\n// Solution 3: Implement proper error recovery\nfunc handleInitialisationError(_ error: Error) {\n    if let nsError = error as NSError {\n        switch nsError.code {\n        case 401:\n            // Unauthorised - refresh session\n            Task {\n                try? await refreshSessionData()\n                // Retry initialisation\n            }\n        case 403:\n            // Forbidden - check bundle ID in Portal\n            showConfigurationError(\"App not authorised. Please check Unity Portal configuration.\")\n        default:\n            showConfigurationError(\"Initialisation failed: \\(error.localizedDescription)\")\n        }\n    }\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"submit-never-calls-onpostauthorisation","__idx":12},"children":["Submit never calls onPostAuthorisation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The symptoms of this are:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The customer taps pay but nothing happens."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["No logs from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onPostAuthorisation"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Submit appears to hang or do nothing."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"diagnostic-steps-2","__idx":13},"children":["Diagnostic steps"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Step 1: Verify all required callbacks are implemented\nfunc verifySubmitCallbacks(_ config: CardSubmitComponentConfig) {\n    print(\"=== Submit Callback Verification ===\")\n    \n    // Check required callbacks\n    print(\"onPreAuthorisation set: \\(config.onPreAuthorisation != nil ? \"✅\" : \"❌ REQUIRED\")\")\n    print(\"onPostAuthorisation set: \\(config.onPostAuthorisation != nil ? \"✅\" : \"⚠️  Recommended\")\")\n    print(\"onSubmitError set: \\(config.onSubmitError != nil ? \"✅\" : \"⚠️  Recommended\")\")\n    \n    // Check component linkage\n    print(\"cardNumberComponent linked: \\(config.cardNumberComponent != nil ? \"✅\" : \"❌\")\")\n    print(\"cardExpiryDateComponent linked: \\(config.cardExpiryDateComponent != nil ? \"✅\" : \"❌\")\")\n    print(\"cardCvcComponent linked: \\(config.cardCvcComponent != nil ? \"✅\" : \"❌\")\")\n}\n\n// Step 2: Add comprehensive logging to all callbacks\nvar submitConfig = CardSubmitComponentConfig()\n\nsubmitConfig.onStartSubmit = {\n    print(\"🔵 Submit started\")\n}\n\nsubmitConfig.onPreTokenisation = {\n    print(\"🔵 Pre-tokenisation callback\")\n    return true\n}\n\nsubmitConfig.onPostTokenisation = { result in\n    print(\"🔵 Post-tokenisation: \\(result)\")\n}\n\nsubmitConfig.onPreAuthorisation = { preAuth in\n    print(\"🔵 Pre-authorisation callback\")\n    print(\"Gateway token: \\(preAuth?.gatewayTokenId ?? \"nil\")\")\n    // This is an async closure - you CAN use await here\n    return TransactionInitiationData()\n}\n\nsubmitConfig.onPostAuthorisation = { result in\n    print(\"🔵 Post-authorisation callback\")\n    print(\"Result type: \\(type(of: result))\")\n}\n\nsubmitConfig.onSubmitError = { error in\n    print(\"🔴 Submit error: \\(error.errorCode) - \\(error.errorMessage)\")\n}\n\n// Step 3: Verify field validation states\nfunc checkFieldValidation() {\n    // Note: getValue() is internal and not accessible\n    // Validation state is managed internally by the SDK\n    \n    print(\"=== Field Validation States ===\")\n    print(\"Submit button will be enabled when all fields are valid\")\n    print(\"Use onValidation callback to monitor validation state\")\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"solutions-2","__idx":14},"children":["Solutions"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Solution 1: Implement required callbacks\nvar submitConfig = CardSubmitComponentConfig()\n\n// REQUIRED: onPreAuthorisation must be implemented\nsubmitConfig.onPreAuthorisation = { preAuth in\n    print(\"Pre-authorisation data: \\(String(describing: preAuth))\")\n    \n    // This is an async closure - you CAN use await here\n    // Return transaction initiation data\n    return TransactionInitiationData(\n        psd2Data: PSD2Data(scaExemption: .lowValue)\n    )\n}\n\n// REQUIRED: onPostAuthorisation handles final result\nsubmitConfig.onPostAuthorisation = { result in\n    switch result {\n    case is AuthorisedSubmitResult, is CapturedSubmitResult:\n        print(\"Payment successful\")\n        navigateToSuccessScreen()\n    case let refused as RefusedSubmitResult:\n        print(\"Payment declined: \\(refused.stateData?.message ?? \"Unknown\")\")\n        showDeclineMessage(refused)\n    case let failed as FailedSubmitResult:\n        print(\"Payment failed: \\(failed.errorReason)\")\n        showFailureMessage(failed)\n    default:\n        print(\"Unknown result type\")\n    }\n}\n\n// Solution 2: Verify component linkage\nfunc createSubmitWithProperLinkage() throws -> CardSubmitComponent {\n    // Create field components first\n    let cardNumber = try pxpCheckout.create(\n        .cardNumber,\n        componentConfig: CardNumberComponentConfig(label: \"Card number\")\n    ) as! CardNumberComponent\n    \n    let cardExpiry = try pxpCheckout.create(\n        .cardExpiryDate,\n        componentConfig: CardExpiryDateComponentConfig(label: \"Expiry\")\n    ) as! CardExpiryDateComponent\n    \n    let cardCvc = try pxpCheckout.create(\n        .cardCvc,\n        componentConfig: CardCvcComponentConfig(label: \"CVC\")\n    ) as! CardCvcComponent\n    \n    // Link components to submit\n    var submitConfig = CardSubmitComponentConfig()\n    submitConfig.cardNumberComponent = cardNumber\n    submitConfig.cardExpiryDateComponent = cardExpiry\n    submitConfig.cardCvcComponent = cardCvc\n    \n    // Implement callbacks\n    submitConfig.onPreAuthorisation = { _ in\n        // This is an async closure - you CAN use await here\n        return TransactionInitiationData()\n    }\n    submitConfig.onPostAuthorisation = { _ in }\n    \n    // Create submit component\n    return try pxpCheckout.create(.cardSubmit, componentConfig: submitConfig) as! CardSubmitComponent\n}\n\n// Solution 3: Handle async operations correctly\nsubmitConfig.onPreAuthorisation = { preAuth in\n    print(\"Pre-authorisation callback\")\n    \n    // This is an async closure - you CAN use await here for async operations\n    return TransactionInitiationData()\n}\n\nsubmitConfig.onPostAuthorisation = { result in\n    // Move heavy work off main thread\n    Task {\n        await processPaymentResult(result)\n    }\n    \n    // Update UI on main thread\n    DispatchQueue.main.async {\n        updateUIForResult(result)\n    }\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validation-errors-or-pay-button-stays-disabled","__idx":15},"children":["Validation errors or pay button stays disabled"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The symptoms of this are:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Submit button stays disabled when ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["disableUntilValidated"]}," is used."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSubmitError"]}," fires with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0501"]}," after tapping pay."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Fields appear filled but validation fails."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"diagnostic-steps-3","__idx":16},"children":["Diagnostic steps"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Step 1: Add validation callback logging\nsubmitConfig.onValidation = { results in\n    print(\"=== Validation Results ===\")\n    for (index, result) in results.enumerated() {\n        print(\"Field \\(index):\")\n        print(\"  Valid: \\(result.valid ? \"✅\" : \"❌\")\")\n        \n        if let errors = result.errors, !errors.isEmpty {\n            print(\"  Errors:\")\n            for (code, message) in errors {\n                print(\"    [\\(code)] \\(message)\")\n            }\n        }\n    }\n}\n\n// Step 2: Check individual field validation\nvar cardNumberConfig = CardNumberComponentConfig(label: \"Card number\")\ncardNumberConfig.onValidationPassed = { _ in\n    print(\"✅ Card number validation passed\")\n}\ncardNumberConfig.onValidationFailed = { _ in\n    print(\"❌ Card number validation failed\")\n}\n\nvar cardExpiryConfig = CardExpiryDateComponentConfig(label: \"Expiry\")\ncardExpiryConfig.onValidationPassed = { _ in\n    print(\"✅ Expiry validation passed\")\n}\ncardExpiryConfig.onValidationFailed = { _ in\n    print(\"❌ Expiry validation failed\")\n}\n\n// Step 3: Manually trigger validation\nfunc testFieldValidation() {\n    print(\"=== Manual Validation Test ===\")\n    \n    // Note: getValue() is internal and not accessible\n    // Use onValidation callback to inspect validation results\n    print(\"Monitor validation via onValidation callback\")\n    print(\"Enable validationOnChange for real-time validation\")\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"solutions-3","__idx":17},"children":["Solutions"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Solution 1: Enable real-time validation\nvar cardNumberConfig = CardNumberComponentConfig(\n    label: \"Card number\",\n    validationOnChange: true,  // Enable real-time validation\n    onValidationPassed: { _ in\n        print(\"Card number valid\")\n    },\n    onValidationFailed: { _ in\n        print(\"Card number invalid\")\n    }\n)\n\nvar cardExpiryConfig = CardExpiryDateComponentConfig(\n    label: \"Expiry\",\n    validationOnChange: true\n)\n\nvar cardCvcConfig = CardCvcComponentConfig(\n    label: \"CVC\",\n    validationOnChange: true\n)\n\n// Solution 2: Handle validation with disableUntilValidated\nvar submitConfig = CardSubmitComponentConfig()\nsubmitConfig.disableUntilValidated = true\n\nsubmitConfig.onValidation = { results in\n    let allValid = results.allSatisfy { $0.valid }\n    print(\"All fields valid: \\(allValid ? \"✅\" : \"❌\")\")\n    \n    if !allValid {\n        // Show which fields are invalid\n        for (index, result) in results.enumerated() where !result.valid {\n            print(\"Field \\(index) is invalid\")\n            if let errors = result.errors {\n                for (code, message) in errors {\n                    print(\"  Error [\\(code)]: \\(message)\")\n                }\n            }\n        }\n    }\n}\n\n// Solution 3: Include billing address validation when using AVS\nif submitConfig.avsRequest == true {\n    print(\"⚠️  AVS enabled - billing address fields must also be valid\")\n    \n    // Ensure billing address components are created and linked\n    let billing = try pxpCheckout.create(\n        .billingAddress,\n        componentConfig: BillingAddressComponentConfig()\n    ) as! BillingAddressComponent\n    \n    submitConfig.billingAddressComponents = BillingAddressComponents(\n        billingAddressComponent: billing\n    )\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3ds-doesnt-start-or-fails-mid-flow","__idx":18},"children":["3DS doesn't start or fails mid-flow"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The symptoms of this are:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Challenge UI never appears."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Flow aborts with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0502"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SDK0511"]}," errors."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Exception mentioning authentication configuration."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"diagnostic-steps-4","__idx":19},"children":["Diagnostic steps"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Step 1: Verify 3DS configuration\nfunc verify3DSConfiguration(_ config: CardSubmitComponentConfig) {\n    print(\"=== 3DS Configuration Verification ===\")\n    \n    let preInitiateSet = config.onPreInitiateAuthentication != nil\n    let preAuthSet = config.onPreAuthentication != nil\n    \n    print(\"onPreInitiateAuthentication set: \\(preInitiateSet ? \"✅\" : \"❌\")\")\n    print(\"onPreAuthentication set: \\(preAuthSet ? \"✅\" : \"❌\")\")\n    \n    if preInitiateSet != preAuthSet {\n        print(\"⚠️  WARNING: 3DS callbacks must be set together or not at all\")\n    }\n    \n    if preInitiateSet && preAuthSet {\n        print(\"✅ 3DS callbacks properly configured\")\n    } else if !preInitiateSet && !preAuthSet {\n        print(\"ℹ️  Non-3DS flow (no authentication callbacks set)\")\n    }\n}\n\n// Step 2: Add detailed 3DS logging\nvar submitConfig = CardSubmitComponentConfig()\n\nsubmitConfig.onPreInitiateAuthentication = {\n    print(\"🔐 3DS Pre-initiation started\")\n    \n    let authData = PreInitiateIntegratedAuthenticationData(\n        providerId: \"your_provider_id\",\n        requestorAuthenticationIndicator: .paymentTransaction,\n        timeout: 120\n    )\n    \n    print(\"Provider ID: \\(authData.providerId)\")\n    print(\"Timeout: \\(authData.timeout)\")\n    \n    // This is an async closure - you CAN use await here\n    return authData\n}\n\nsubmitConfig.onPostInitiateAuthentication = { result in\n    print(\"🔐 3DS Pre-initiation completed\")\n    \n    if let failed = result as? FailedAuthenticationResult {\n        print(\"❌ Pre-initiation failed: \\(failed.errorReason)\")\n        print(\"Error code: \\(failed.errorCode)\")\n    } else {\n        print(\"✅ Pre-initiation successful\")\n    }\n}\n\nsubmitConfig.onPreAuthentication = {\n    print(\"🔐 3DS Authentication started\")\n    \n    let authData = InitiateIntegratedAuthenticationData(\n        merchantCountryNumericCode: \"840\",\n        merchantLegalName: \"Your Company Ltd\",\n        challengeWindowSize: .size5,\n        requestorChallengeIndicator: .noPreference,\n        timeout: 300\n    )\n    \n    print(\"Merchant country: \\(authData.merchantCountryNumericCode)\")\n    print(\"Challenge window: \\(authData.challengeWindowSize)\")\n    \n    // This is an async closure - you CAN use await here\n    return authData\n}\n\nsubmitConfig.onPostAuthentication = { result in\n    print(\"🔐 3DS Authentication completed\")\n    \n    if result is FailedAuthenticationResult {\n        print(\"❌ Authentication failed\")\n    } else {\n        print(\"✅ Authentication successful\")\n    }\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"solutions-4","__idx":20},"children":["Solutions"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Solution 1: Verify Unity Portal 3DS configuration\n// 1. Log in to Unity Portal\n// 2. Go to Merchant setup > Merchant groups\n// 3. Select your merchant group\n// 4. Click Services tab\n// 5. Edit Card service\n// 6. Click Configure modules\n// 7. Enable \"ThreeD secure service\"\n\n// Solution 2: Implement complete 3DS callback set\nvar submitConfig = CardSubmitComponentConfig()\n\n// Both 3DS callbacks must be set together\nsubmitConfig.onPreInitiateAuthentication = {\n    // This is an async closure - you CAN use await here\n    return PreInitiateIntegratedAuthenticationData(\n        providerId: \"your_3ds_provider_id\",\n        requestorAuthenticationIndicator: .paymentTransaction,\n        timeout: 120\n    )\n}\n\nsubmitConfig.onPreAuthentication = {\n    // This is an async closure - you CAN use await here\n    return InitiateIntegratedAuthenticationData(\n        merchantCountryNumericCode: \"840\",\n        merchantLegalName: \"Your Company Ltd\",\n        challengeWindowSize: .size5,\n        requestorChallengeIndicator: .noPreference,\n        timeout: 300\n    )\n}\n\n// Optional but recommended\nsubmitConfig.onPostInitiateAuthentication = { result in\n    if let failed = result as? FailedAuthenticationResult {\n        print(\"3DS pre-initiation failed: \\(failed.errorReason)\")\n    }\n}\n\nsubmitConfig.onPostAuthentication = { result in\n    if result is FailedAuthenticationResult {\n        print(\"3DS authentication failed\")\n    }\n}\n\n// Required for all flows\nsubmitConfig.onPreAuthorisation = { _ in\n    // This is an async closure - you CAN use await here\n    return TransactionInitiationData()\n}\n\nsubmitConfig.onPostAuthorisation = { result in\n    // Handle final result\n}\n\n// Solution 3: Implement conditional 3DS\nlet transactionAmount: Decimal = 150.00\nlet requiresStrong3DS = transactionAmount > 100.00\n\nsubmitConfig.onPreInitiateAuthentication = {\n    // Return nil to skip 3DS for low amounts\n    guard requiresStrong3DS else {\n        print(\"Skipping 3DS for low-value transaction\")\n        return nil\n    }\n    \n    // This is an async closure - you CAN use await here\n    return PreInitiateIntegratedAuthenticationData(\n        providerId: \"your_provider_id\",\n        requestorAuthenticationIndicator: .paymentTransaction,\n        timeout: 120\n    )\n}\n\n// If conditional, still set both callbacks\nsubmitConfig.onPreAuthentication = {\n    // This is an async closure - you CAN use await here\n    return InitiateIntegratedAuthenticationData(\n        merchantCountryNumericCode: \"840\",\n        merchantLegalName: \"Your Company Ltd\",\n        challengeWindowSize: .size5,\n        requestorChallengeIndicator: .noPreference,\n        timeout: 300\n    )\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"debugging-tools-and-techniques","__idx":21},"children":["Debugging tools and techniques"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To get more detailed information about errors, use this debugging setup:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Enable comprehensive logging\nclass CardPaymentDebugger {\n    static func enableDebugMode(_ config: inout CardSubmitComponentConfig) {\n        print(\"🔧 Card Payment Debug Mode Enabled\")\n        logEnvironmentInfo()\n        \n        // Wrap existing callbacks with logging\n        let originalOnSubmitError = config.onSubmitError\n        config.onSubmitError = { error in\n            logError(error)\n            originalOnSubmitError?(error)\n        }\n        \n        let originalOnPostAuthorisation = config.onPostAuthorisation\n        config.onPostAuthorisation = { result in\n            logAuthorisationResult(result)\n            originalOnPostAuthorisation?(result)\n        }\n    }\n    \n    static func logEnvironmentInfo() {\n        print(\"=== Card Payment Environment ===\")\n        print(\"Device Model: \\(UIDevice.current.model)\")\n        print(\"iOS Version: \\(UIDevice.current.systemVersion)\")\n        print(\"Bundle ID: \\(Bundle.main.bundleIdentifier ?? \"Unknown\")\")\n        print(\"App Version: \\(Bundle.main.infoDictionary?[\"CFBundleShortVersionString\"] ?? \"Unknown\")\")\n        print(\"Build: \\(Bundle.main.infoDictionary?[\"CFBundleVersion\"] ?? \"Unknown\")\")\n    }\n    \n    static func logError(_ error: BaseSdkException) {\n        print(\"=== Payment Error Debug ===\")\n        print(\"Error Code: \\(error.errorCode)\")\n        print(\"Error Message: \\(error.errorMessage)\")\n        \n        if let userInfo = (error as NSError).userInfo as? [String: Any] {\n            print(\"Additional info:\")\n            for (key, value) in userInfo {\n                print(\"  \\(key): \\(value)\")\n            }\n        }\n    }\n    \n    static func logAuthorisationResult(_ result: SubmitResult) {\n        print(\"=== Authorisation Result Debug ===\")\n        print(\"Result type: \\(type(of: result))\")\n        \n        switch result {\n        case let authorised as AuthorisedSubmitResult:\n            print(\"State: Authorised\")\n            print(\"Transaction ID: \\(authorised.fundingData.transactionId)\")\n            print(\"Provider message: \\(authorised.provider.message)\")\n            \n        case let captured as CapturedSubmitResult:\n            print(\"State: Captured\")\n            print(\"Transaction ID: \\(captured.fundingData.transactionId)\")\n            print(\"Provider message: \\(captured.provider.message)\")\n            \n        case let refused as RefusedSubmitResult:\n            print(\"State: Refused\")\n            print(\"Decline reason: \\(refused.stateData?.message ?? \"Unknown\")\")\n            print(\"Decline code: \\(refused.stateData?.code ?? \"Unknown\")\")\n            if let advice = refused.provider.merchantAdvice {\n                print(\"Merchant advice: \\(advice.message)\")\n            }\n            \n        case let failed as FailedSubmitResult:\n            print(\"State: Failed\")\n            print(\"HTTP Status: \\(failed.httpStatusCode ?? 0)\")\n            print(\"Error code: \\(failed.errorCode ?? \"Unknown\")\")\n            print(\"Error reason: \\(failed.errorReason)\")\n            print(\"Correlation ID: \\(failed.correlationId ?? \"None\")\")\n            \n        default:\n            print(\"Unknown result type\")\n        }\n    }\n    \n    static func logFieldValues(\n        cardNumber: CardNumberComponent?,\n        expiry: CardExpiryDateComponent?,\n        cvc: CardCvcComponent?\n    ) {\n        print(\"=== Field Values Debug ===\")\n        // Note: getValue() is internal and not accessible\n        print(\"Use onValidation callback to monitor field states\")\n        print(\"Enable validationOnChange for real-time monitoring\")\n    }\n}\n\n// Usage in your implementation\nvar submitConfig = CardSubmitComponentConfig()\nCardPaymentDebugger.enableDebugMode(&submitConfig)\n\n// Before submission\nCardPaymentDebugger.logFieldValues(\n    cardNumber: cardNumber,\n    expiry: cardExpiry,\n    cvc: cardCvc\n)\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prevention-and-best-practices","__idx":22},"children":["Prevention and best practices"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"proactive-error-prevention","__idx":23},"children":["Proactive error prevention"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Pre-flight checks before initialising payment\nfunc performPaymentPreflightChecks() async -> Bool {\n    let checks: [(String, () async -> Bool, String)] = [\n        (\"Session validity\", {\n            return self.validateSessionData()\n        }, \"Session data is invalid or expired\"),\n        \n        (\"Network connectivity\", {\n            return await self.checkNetworkConnectivity()\n        }, \"Network connection required\"),\n        \n        (\"Component configuration\", {\n            return self.validateComponentConfiguration()\n        }, \"Component configuration is invalid\"),\n        \n        (\"3DS configuration\", {\n            return self.validate3DSConfiguration()\n        }, \"3DS configuration is invalid\")\n    ]\n    \n    var allPassed = true\n    print(\"=== Payment Pre-flight Checks ===\")\n    \n    for (name, test, failureMessage) in checks {\n        let passed = await test()\n        print(\"\\(passed ? \"✅\" : \"❌\") \\(name): \\(passed ? \"PASSED\" : failureMessage)\")\n        if !passed {\n            allPassed = false\n        }\n    }\n    \n    return allPassed\n}\n\nprivate func validateSessionData() -> Bool {\n    guard !checkoutConfig.session.sessionId.isEmpty,\n          !checkoutConfig.session.hmacKey.isEmpty,\n          !checkoutConfig.session.encryptionKey.isEmpty else {\n        return false\n    }\n    return true\n}\n\nprivate func checkNetworkConnectivity() async -> Bool {\n    guard let url = URL(string: \"https://api.pxp.io/health\") else { return false }\n    \n    do {\n        let (_, response) = try await URLSession.shared.data(from: url)\n        return (response as? HTTPURLResponse)?.statusCode == 200\n    } catch {\n        return false\n    }\n}\n\nprivate func validateComponentConfiguration() -> Bool {\n    // Check all required components are created and linked\n    return submitConfig.cardNumberComponent != nil &&\n           submitConfig.cardExpiryDateComponent != nil &&\n           submitConfig.cardCvcComponent != nil\n}\n\nprivate func validate3DSConfiguration() -> Bool {\n    let preInitSet = submitConfig.onPreInitiateAuthentication != nil\n    let preAuthSet = submitConfig.onPreAuthentication != nil\n    \n    // Either both set or neither set\n    return preInitSet == preAuthSet\n}\n\n// Use before payment\nfunc initiatePayment() async {\n    let preflightPassed = await performPaymentPreflightChecks()\n    \n    if preflightPassed {\n        await submitPayment()\n    } else {\n        showPreflightFailureMessage()\n    }\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"monitoring-and-alerting","__idx":24},"children":["Monitoring and alerting"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"// Error monitoring setup\nclass PaymentErrorMonitor {\n    static func captureError(_ error: Error, context: [String: Any] = [:]) {\n        var errorData: [String: Any] = [\n            \"error_message\": error.localizedDescription,\n            \"error_type\": String(describing: type(of: error)),\n            \"device_model\": UIDevice.current.model,\n            \"ios_version\": UIDevice.current.systemVersion,\n            \"bundle_id\": Bundle.main.bundleIdentifier ?? \"Unknown\",\n            \"timestamp\": ISO8601DateFormatter().string(from: Date())\n        ]\n        \n        // Add context\n        errorData.merge(context) { _, new in new }\n        \n        // Add SDK-specific data\n        if let sdkError = error as? BaseSdkException {\n            errorData[\"error_code\"] = sdkError.errorCode\n            errorData[\"sdk_error\"] = true\n        }\n        \n        // Send to your error tracking service\n        print(\"📊 Error captured: \\(errorData)\")\n        // Example: Crashlytics.record(error: error, userInfo: errorData)\n    }\n    \n    static func trackPaymentEvent(_ eventType: String, data: [String: Any] = [:]) {\n        var eventData = data\n        eventData[\"event_type\"] = eventType\n        eventData[\"timestamp\"] = ISO8601DateFormatter().string(from: Date())\n        eventData[\"component\"] = \"CardPayment\"\n        \n        print(\"📈 Event tracked: \\(eventData)\")\n        // Send to your analytics service\n    }\n}\n\n// Integration with card submit\nvar submitConfig = CardSubmitComponentConfig()\n\nsubmitConfig.onSubmitError = { error in\n    PaymentErrorMonitor.captureError(error, context: [\n        \"flow_step\": \"card_submit\",\n        \"transaction_amount\": transactionData.amount,\n        \"currency\": transactionData.currency\n    ])\n}\n\nsubmitConfig.onPostAuthorisation = { result in\n    let eventType: String\n    switch result {\n    case is AuthorisedSubmitResult, is CapturedSubmitResult:\n        eventType = \"payment_success\"\n    case is RefusedSubmitResult:\n        eventType = \"payment_declined\"\n    case is FailedSubmitResult:\n        eventType = \"payment_failed\"\n    default:\n        eventType = \"payment_unknown\"\n    }\n    \n    PaymentErrorMonitor.trackPaymentEvent(eventType, data: [\n        \"result_type\": String(describing: type(of: result))\n    ])\n}\n","lang":"swift"},"children":[]}]},"headings":[{"value":"Troubleshooting","id":"troubleshooting","depth":1},{"value":"Exception types and error codes","id":"exception-types-and-error-codes","depth":2},{"value":"SDK exceptions","id":"sdk-exceptions","depth":3},{"value":"Error handling best practices","id":"error-handling-best-practices","depth":2},{"value":"Comprehensive error handler","id":"comprehensive-error-handler","depth":3},{"value":"Troubleshooting common issues","id":"troubleshooting-common-issues","depth":2},{"value":"Card components or fields don't appear","id":"card-components-or-fields-dont-appear","depth":3},{"value":"Diagnostic steps","id":"diagnostic-steps","depth":4},{"value":"Solutions","id":"solutions","depth":4},{"value":"Checkout initialisation or create throws","id":"checkout-initialisation-or-create-throws","depth":3},{"value":"Diagnostic steps","id":"diagnostic-steps-1","depth":4},{"value":"Solutions","id":"solutions-1","depth":4},{"value":"Submit never calls onPostAuthorisation","id":"submit-never-calls-onpostauthorisation","depth":3},{"value":"Diagnostic steps","id":"diagnostic-steps-2","depth":4},{"value":"Solutions","id":"solutions-2","depth":4},{"value":"Validation errors or pay button stays disabled","id":"validation-errors-or-pay-button-stays-disabled","depth":3},{"value":"Diagnostic steps","id":"diagnostic-steps-3","depth":4},{"value":"Solutions","id":"solutions-3","depth":4},{"value":"3DS doesn't start or fails mid-flow","id":"3ds-doesnt-start-or-fails-mid-flow","depth":3},{"value":"Diagnostic steps","id":"diagnostic-steps-4","depth":4},{"value":"Solutions","id":"solutions-4","depth":4},{"value":"Debugging tools and techniques","id":"debugging-tools-and-techniques","depth":2},{"value":"Prevention and best practices","id":"prevention-and-best-practices","depth":2},{"value":"Proactive error prevention","id":"proactive-error-prevention","depth":3},{"value":"Monitoring and alerting","id":"monitoring-and-alerting","depth":3}],"frontmatter":{"seo":{"title":"Troubleshooting"}},"lastModified":"2026-05-18T12:44:42.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/components/ios/card/troubleshooting","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}