{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["sub-heading","yes","details","no","admonition"]},"type":"markdown"},"seo":{"title":"Data validation","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":"data-validation","__idx":0},"children":["Data validation"]},{"$$mdtype":"Tag","name":"SubHeading","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Collect and validate the data entered by your customers."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["By validating data prior to transaction initiation, you can:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Reduce the risk of fraudulent payments."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Offer a better user experience, by providing clear feedback that helps customers complete forms correctly."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Ensure you meet PCI and regulatory requirements."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"validation-methods","__idx":2},"children":["Validation methods"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There are three key validation methods:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getValue()"]},": Retrieves current field values synchronously."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validate()"]},": Validates field data and returns results."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validateWhenSubmit()"]},": Validates component data during submission."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Validation can happen:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Automatically on user input (when field is touched and value changes)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["When ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["submit()"]}," is called."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["When you call validation methods directly."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following table shows the compatibility of these methods with the different components."]},{"$$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":"Component name"},"children":["Component name"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"getValue()"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getValue()"]}]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"validate()"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validate()"]}]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"validateWhenSubmit()"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validateWhenSubmit()"]}]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Address"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Billing address"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card brand selector"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Returns null"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card consent"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Returns Boolean"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card CVC"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card expiry date"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Cardholder name"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card number"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card-on-file"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Returns null"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card submit"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Returns null"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Click-once"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Returns null"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Country selection"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Dynamic card image"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Returns null"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["New card"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Returns null"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Postcode"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Pre-fill billing address checkbox"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Returns Boolean"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"validation-result-structure","__idx":3},"children":["Validation result structure"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"data class ValidationResult(\n    /**\n     * Whether the validation passed\n     */\n    val isValid: Boolean,\n    \n    /**\n     * Error message if validation failed\n     */\n    val errorMessage: String? = null,\n    \n    /**\n     * Error code if validation failed (e.g., \"CN01\", \"CN02\")\n     * Used for specific error handling and internationalisation\n     */\n    val errorCode: String? = null,\n    \n    /**\n     * Component ID for tracking which component the validation result belongs to\n     */\n    val componentId: String? = null,\n    \n    /**\n     * Detailed validation errors (for complex validations like Kount)\n     * Maps field names to validation errors\n     */\n    val validationErrors: Map<String, ValidationFieldError>? = null\n) {\n    companion object {\n        fun success(): ValidationResult {\n            return ValidationResult(isValid = true)\n        }\n        \n        fun error(errorMessage: String, componentId: String? = null): ValidationResult {\n            return ValidationResult(\n                isValid = false,\n                errorMessage = errorMessage,\n                componentId = componentId\n            )\n        }\n        \n        fun error(errorCode: String, errorMessage: String, componentId: String? = null): ValidationResult {\n            return ValidationResult(\n                isValid = false,\n                errorMessage = errorMessage,\n                errorCode = errorCode,\n                componentId = componentId\n            )\n        }\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"common-validation-scenarios","__idx":4},"children":["Common validation scenarios"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"get-values-before-payment-processing","__idx":5},"children":["Get values before payment processing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getValue()"]}," to collect form data before processing payments."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"class PaymentValidationManager(\n    private val cardNumberComponent: CardNumberComponent,\n    private val expiryDateComponent: CardExpiryDateComponent,\n    private val cvcComponent: CardCvcComponent,\n    private val holderNameComponent: CardHolderNameComponent,\n    private val billingAddressComponent: BillingAddressComponent?\n) {\n    \n    suspend fun processPayment(): PaymentResult {\n        // Get individual card field values\n        val cardNumber = cardNumberComponent.getValue()\n        val expiryDate = expiryDateComponent.getValue()\n        val cvc = cvcComponent.getValue()\n        val holderName = holderNameComponent.getValue()\n        \n        // Get complex component values\n        val addressData = billingAddressComponent?.getValue()\n        \n        Log.d(\"Payment\", \"Payment data collected: cardNumber=${cardNumber?.let { \"****${it.takeLast(4)}\" }}, expiryDate=$expiryDate\")\n        \n        // Validate all collected data\n        if (!validateCollectedData(cardNumber, expiryDate, cvc, holderName, addressData)) {\n            return PaymentResult.ValidationFailed(\"Invalid payment data\")\n        }\n        \n        // Submit payment with collected data\n        return submitPayment(cardNumber, expiryDate, cvc, holderName, addressData)\n    }\n    \n    private fun validateCollectedData(\n        cardNumber: String?,\n        expiryDate: String?,\n        cvc: String?,\n        holderName: String?,\n        addressData: AddressData?\n    ): Boolean {\n        return cardNumber?.isNotEmpty() == true &&\n               expiryDate?.isNotEmpty() == true &&\n               cvc?.isNotEmpty() == true &&\n               holderName?.isNotEmpty() == true\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-all-components-before-submission","__idx":6},"children":["Validate all components before submission"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validate()"]}," to check individual components and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validateWhenSubmit()"]}," for submission validation."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"class FormValidationExample {\n    \n    fun validateAllFieldsBeforeSubmission(\n        cardNumber: CardNumberComponent,\n        expiry: CardExpiryDateComponent,\n        cvc: CardCvcComponent,\n        holderName: CardHolderNameComponent\n    ): Boolean {\n        \n        val results = mutableListOf<ValidationResult>()\n        \n        // Validate individual components\n        results.addAll(cardNumber.validate())\n        results.addAll(expiry.validate())\n        results.addAll(cvc.validate())\n        results.addAll(holderName.validate())\n        \n        // Check if all validations passed\n        val isAllValid = results.all { it.isValid }\n        \n        if (!isAllValid) {\n            Log.w(\"Validation\", \"Form validation failed:\")\n            results.filter { !it.isValid }.forEach { result ->\n                Log.w(\"Validation\", \"- ${result.errorCode}: ${result.errorMessage}\")\n            }\n        }\n        \n        return isAllValid\n    }\n    \n    fun validateForSubmission(components: List<Component<*>>): List<ValidationResult> {\n        return components.flatMap { component ->\n            component.validateWhenSubmit()\n        }\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"card-number-validation","__idx":7},"children":["Card number validation"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"built-in-validation-rules","__idx":8},"children":["Built-in validation rules"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Card number validation follows this priority order:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Required field validation (CN01): The field can't be empty."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Format validation (CN02): The field must contain only numeric characters."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Length validation (CN03): The field must meet card brand length requirements."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Brand detection (CN04): The card brand must be recognisable."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Brand acceptance (CN05): The card brand must be in accepted list."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Luhn algorithm (CN06): The field must pass checksum validation."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Card restrictions (CN07): The card must match the configured restrictions (owner type and funding source)."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"data class CardNumberValidations(\n    /**\n     * Error message when card number is required but not provided - REQUIRED (CN01)\n     */\n    var required: String = \"Card number is required\",\n    \n    /**\n     * Error message when card number contains non-numeric characters - INVALID (CN02) \n     */\n    var invalid: String = \"Please enter a valid card number\",\n    \n    /**\n     * Error message when card number is too short for detected card brand - TOO_SHORT (CN03)\n     */\n    var tooShort: String = \"Card number is too short\",\n    \n    /**\n     * Error message when card type is not recognised - TYPE_NOT_RECOGNIZED (CN04)\n     */\n    var typeNotRecognized: String = \"Card type not recognised\",\n    \n    /**\n     * Error message when card brand is not supported - TYPE_NOT_SUPPORTED (CN05)\n     */\n    var typeNotSupported: String = \"Card type is not supported\",\n    \n    /**\n     * Error message when card number fails the Luhn check algorithm - LUHN_FAILED (CN06)\n     */\n    var luhnFailed: String = \"Please enter a valid card number\",\n    \n    /**\n     * Error message when card fails restrictions validation - RESTRICTIONS_NOT_MET (CN07)\n     * Example: \"Corporate, Credit cards are not accepted\"\n     */\n    var restrictionsNotMet: String = \"This card type is not accepted\"\n)\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"configuration-example","__idx":9},"children":["Configuration example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"val cardNumberConfig = CardNumberComponentConfig(\n    label = \"Card Number\",\n    placeholder = \"1234 5678 9012 3456\",\n    validationEnabled = true,\n    formatCardNumber = true,\n    acceptedCardBrands = listOf(CardBrand.VISA, CardBrand.MASTERCARD, CardBrand.AMEX),\n    validations = CardNumberValidations(\n        required = \"Please enter your card number\",\n        invalid = \"Please enter a valid card number\",\n        tooShort = \"Your card number is incomplete\",\n        typeNotRecognized = \"Please enter a valid card number\",\n        typeNotSupported = \"This card type is not accepted\",\n        luhnFailed = \"Please check your card number\",\n        restrictionsNotMet = \"This card type is not accepted for this transaction\"\n    ),\n    \n    // Callbacks\n    onCardBrandChanged = { cardBrand ->\n        Log.d(\"CardNumber\", \"Card brand detected: $cardBrand\")\n    },\n    onCardBrandCannotRecognised = {\n        Log.w(\"CardNumber\", \"Unable to recognise card brand\")\n    },\n    \n    // Validation callbacks  \n    onValidationPassed = { results ->\n        Log.d(\"CardNumber\", \"Validation passed\")\n    },\n    onValidationFailed = { results ->\n        results.forEach { result ->\n            Log.w(\"CardNumber\", \"Validation failed: ${result.errorMessage}\")\n        }\n    }\n)\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"card-restrictions-vs-accepted-card-brands","__idx":10},"children":["Card restrictions vs accepted card brands"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK provides two ways to control what cards are accepted:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["acceptedCardBrands"]}," (component-level): Validates against specific card brands (Visa, Mastercard, etc.). This is configured in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CardNumberComponentConfig"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["restrictions"]}," (SDK-level): Validates against card characteristics like owner type (Corporate/Consumer) and funding source (Credit/Debit/Prepaid). This is configured in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PxpSdkConfig"]}," and/or returned from the session."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Both validations work together. If a card passes brand validation but fails restriction validation, it will be rejected with a validation error."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"// SDK-level restrictions (in PxpSdkConfig)\nval sdkConfig = PxpSdkConfig(\n    // ... other config\n    restrictions = Restrictions(\n        card = RestrictionsCard(\n            ownerTypes = listOf(RestrictionOwnerType.CONSUMER),\n            fundingSources = listOf(RestrictionFundingSource.CREDIT, RestrictionFundingSource.DEBIT)\n        )\n    )\n)\n\n// Component-level brand acceptance (in CardNumberComponentConfig)\nval cardNumberConfig = CardNumberComponentConfig(\n    acceptedCardBrands = listOf(CardBrand.VISA, CardBrand.MASTERCARD, CardBrand.AMEX)\n)\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In this example:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A corporate credit card would be rejected (fails owner type restriction with CN07 error)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A consumer prepaid Visa would be rejected (fails funding source restriction with CN07 error)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A consumer credit Discover card would be rejected (fails brand acceptance with CN05 error)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A consumer credit Visa would be accepted (passes both validations)."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"restriction-validation-errors","__idx":11},"children":["Restriction validation errors"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When a card fails restriction validation, the SDK returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CN07"]}," error with a message indicating which restrictions weren't met. For example ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"Corporate, Credit cards are not accepted\""]}," if both owner type and funding source restrictions fail. The error message can be customised via the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["restrictionsNotMet"]}," property in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CardNumberValidations"]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If restrictions are specified in both the session (via backend Sessions API) and the SDK config, they are merged using a union approach. Session restrictions are applied first, followed by SDK-only values."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"card-brand-detection-and-validation","__idx":12},"children":["Card brand detection and validation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK automatically detects card brands based on number prefixes:"]},{"$$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":"Card brand"},"children":["Card brand"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Prefix patterns"},"children":["Prefix patterns"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Length range"},"children":["Length range"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Visa"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["4"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["13-16"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Mastercard"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["51-58"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["22-27"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["16"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["American Express"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["34"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["37"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["15"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Discover"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["6011"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["644-649"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["65"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["16-19"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["JCB"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["3528"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["16-19"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Diners"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["30"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["36"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["38"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["39"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["14-19"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["China UnionPay"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["62"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["16-19"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"luhn-algorithm-validation","__idx":13},"children":["Luhn algorithm validation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK uses the Luhn algorithm to validate card number checksums:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"object LuhnAlgorithm {\n    fun isValid(cardNumber: String): Boolean {\n        if (cardNumber.isEmpty()) return false\n        \n        val digits = cardNumber.map { it.toString().toInt() }\n        val checksum = digits.reversed()\n            .mapIndexed { index, digit ->\n                if (index % 2 == 1) {\n                    val doubled = digit * 2\n                    if (doubled > 9) doubled - 9 else doubled\n                } else {\n                    digit\n                }\n            }\n            .sum()\n        \n        return checksum % 10 == 0\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"using-validation-in-your-app","__idx":14},"children":["Using validation in your app"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"class PaymentValidator {\n    \n    fun validateCardData(\n        cardNumber: String,\n        expiryMonth: String,\n        expiryYear: String,\n        cvc: String\n    ): ValidationResult {\n        \n        // Validate card number\n        val cardValidation = validateCardNumber(cardNumber)\n        if (!cardValidation.isValid) {\n            return cardValidation\n        }\n        \n        // Validate expiry date\n        val expiryValidation = validateExpiryDate(expiryMonth, expiryYear)\n        if (!expiryValidation.isValid) {\n            return expiryValidation\n        }\n        \n        // Validate CVC\n        val cvcValidation = validateCvc(cvc)\n        if (!cvcValidation.isValid) {\n            return cvcValidation\n        }\n        \n        return ValidationResult.success()\n    }\n    \n    private fun validateCardNumber(cardNumber: String): ValidationResult {\n        val digitsOnly = cardNumber.replace(\"\\\\s\".toRegex(), \"\")\n        \n        // Check length\n        if (!digitsOnly.matches(\"^[0-9]{13,19}$\".toRegex())) {\n            return ValidationResult.error(\n                errorCode = \"CN02\",\n                errorMessage = \"Please enter a valid card number\"\n            )\n        }\n        \n        // Luhn algorithm check\n        if (!LuhnAlgorithm.isValid(digitsOnly)) {\n            return ValidationResult.error(\n                errorCode = \"CN06\",\n                errorMessage = \"Please check your card number\"\n            )\n        }\n        \n        return ValidationResult.success()\n    }\n    \n    private fun validateExpiryDate(month: String, year: String): ValidationResult {\n        val monthInt = month.toIntOrNull() ?: return ValidationResult.error(\n            errorCode = \"ED01\",\n            errorMessage = \"Invalid expiry month\"\n        )\n        \n        val yearInt = year.toIntOrNull() ?: return ValidationResult.error(\n            errorCode = \"ED02\", \n            errorMessage = \"Invalid expiry year\"\n        )\n        \n        if (monthInt < 1 || monthInt > 12) {\n            return ValidationResult.error(\n                errorCode = \"ED01\",\n                errorMessage = \"Expiry month must be between 1 and 12\"\n            )\n        }\n        \n        val currentYear = Calendar.getInstance().get(Calendar.YEAR)\n        val currentMonth = Calendar.getInstance().get(Calendar.MONTH) + 1\n        val fullYear = if (yearInt < 100) 2000 + yearInt else yearInt\n        \n        if (fullYear < currentYear || (fullYear == currentYear && monthInt < currentMonth)) {\n            return ValidationResult.error(\n                errorCode = \"ED03\",\n                errorMessage = \"Card has expired\"\n            )\n        }\n        \n        return ValidationResult.success()\n    }\n    \n    private fun validateCvc(cvc: String): ValidationResult {\n        if (cvc.isEmpty()) {\n            return ValidationResult.error(\n                errorCode = \"CVC01\",\n                errorMessage = \"CVC is required\"\n            )\n        }\n        \n        if (!cvc.matches(\"^[0-9]{3,4}$\".toRegex())) {\n            return ValidationResult.error(\n                errorCode = \"CVC02\",\n                errorMessage = \"CVC must be 3 or 4 digits\"\n            )\n        }\n        \n        return ValidationResult.success()\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"expiry-date-validation","__idx":15},"children":["Expiry date validation"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"built-in-validation-rules-1","__idx":16},"children":["Built-in validation rules"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Expiry date validation uses error codes ED01-ED05:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Format validation (ED01): The field must contain only numeric characters."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Month validation (ED02): The month must be between 01-12."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Pattern validation (ED03): The field must match MM/YY format."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Expiry check (ED04): The date can't be in the past."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Required validation (ED05): The field can't be empty."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"data class CardExpiryDateValidations(\n    /** Invalid format - non-numeric characters (ED01) */\n    var invalidFormat: String = \"Expiry date must contain only digits\",\n\n    /** Invalid month - not between 01-12 (ED02) */\n    var invalidMonth: String = \"Invalid month. Please enter a month between 01 and 12\",\n\n    /** Invalid format pattern (ED03) */\n    var invalidFormatPattern: String = \"Invalid expiry date format. Please use \",\n\n    /** Card expired (ED04) */\n    var expired: String = \"The card expiry date you entered has already passed. Please enter a valid expiration date\",\n\n    /** Required field empty (ED05) */\n    var required: String = \"Please enter expiry date\"\n)\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"configuration-example-1","__idx":17},"children":["Configuration example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"val expiryDateConfig = CardExpiryDateComponentConfig(\n    label = \"Expiry Date\",\n    formatOptions = ExpiryDateFormat.SHORT_SLASH, // MM/YY\n    validations = CardExpiryDateValidations(\n        required = \"Please provide your card expiry date\",\n        invalidFormat = \"Only numbers are allowed (no letters or symbols)\",\n        expired = \"Your card expiry date has passed\",\n        invalidMonth = \"Month must be between 01-12\"\n    ),\n    \n    // Validation callbacks\n    onValidationPassed = { \n        Log.d(\"ExpiryDate\", \"Validation passed\")\n    },\n    onValidationFailed = { results ->\n        results.forEach { result ->\n            Log.w(\"ExpiryDate\", \"Validation failed: ${result.errorMessage}\")\n        }\n    }\n)\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"cvc-validation","__idx":18},"children":["CVC validation"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"built-in-validation-rules-2","__idx":19},"children":["Built-in validation rules"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["CVC validation includes these checks:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Required validation: The field can't be empty."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Format validation: The field must contain only numeric characters."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Length validation: The field must be 3-4 digits depending on the card brand."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"// CVC validation is handled automatically by the component\n// The length depends on the detected card brand:\n// - Visa, Mastercard, Discover: 3 digits\n// - American Express: 4 digits\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"configuration-example-2","__idx":20},"children":["Configuration example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"val cvcConfig = CardCvcComponentConfig(\n    label = \"CVC\",\n    placeholder = \"123\",\n    \n    // Validation callbacks\n    onValidationPassed = { \n        Log.d(\"CVC\", \"CVC validation passed\")\n    },\n    onValidationFailed = { results ->\n        results.forEach { result ->\n            Log.w(\"CVC\", \"CVC validation failed: ${result.errorMessage}\")\n        }\n    }\n)\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"custom-validation-with-oncustomvalidation","__idx":21},"children":["Custom validation with onCustomValidation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]}," callback enables you to implement custom business rule validation after standard SDK component validation passes. This allows you to validate business-specific requirements before payment submission proceeds."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"when-to-use-custom-validation","__idx":22},"children":["When to use custom validation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]}," to:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Validate transaction amount limits or thresholds."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Check customer eligibility for specific payment methods."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Verify inventory availability before charging."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Implement fraud prevention rules based on order data."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Enforce payment policies based on customer account status."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Validate promotional code or discount eligibility."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validation-flow-order","__idx":23},"children":["Validation flow order"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK follows a specific validation order to ensure all checks are performed systematically:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Standard SDK validation:"]}," Card number, CVV, expiry date, billing address, and other component fields are validated."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Custom validation:"]}," Your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]}," callback is invoked (if provided)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Payment submission:"]}," If both standard and custom validation pass, the payment submission proceeds."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]}," callback is only called after all standard SDK component validations have passed successfully. If any standard validation fails, custom validation is not executed."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"configuration-example-3","__idx":24},"children":["Configuration example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"val cardSubmitConfig = CardSubmitComponentConfig(\n    // Component references\n    cardNumberComponent = cardNumberComponent,\n    cardExpiryDateComponent = cardExpiryComponent,\n    cardCvcComponent = cardCvcComponent,\n    cardHolderNameComponent = cardHolderNameComponent,\n    \n    // Custom validation callback\n    onCustomValidation = {\n        Log.d(\"Validation\", \"Running custom business rule validation\")\n        \n        // All custom validation checks\n        val validationPassed = validateCustomRules()\n        \n        if (!validationPassed) {\n            Log.w(\"Validation\", \"Custom validation failed\")\n        }\n        \n        validationPassed // Return true to proceed, false to block\n    },\n    \n    // Other callbacks\n    onValidation = { results ->\n        // Standard validation results\n        val allValid = results.all { it.isValid }\n        Log.d(\"Validation\", \"Standard validation: ${if (allValid) \"Passed\" else \"Failed\"}\")\n    },\n    \n    onSubmitError = { error ->\n        Log.e(\"Payment\", \"Submission error: ${error.message}\")\n    }\n)\n\nprivate fun validateCustomRules(): Boolean {\n    // Implement your custom validation logic\n    return true\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"complete-validation-example","__idx":25},"children":["Complete validation example"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This example includes a comprehensive validation flow with multiple business rules:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"class PaymentValidationManager(\n    private val checkout: PxpCheckout,\n    private val customerRepository: CustomerRepository,\n    private val inventoryService: InventoryService\n) {\n    \n    fun createCardSubmitConfig(): CardSubmitComponentConfig {\n        return CardSubmitComponentConfig(\n            // Component references\n            cardNumberComponent = cardNumberComponent,\n            cardExpiryDateComponent = expiryComponent,\n            cardCvcComponent = cvcComponent,\n            \n            // Custom validation with comprehensive business rules\n            onCustomValidation = {\n                Log.d(\"Payment\", \"Starting custom validation checks...\")\n                \n                try {\n                    // Check 1: Validate transaction amount\n                    if (!validateTransactionAmount()) {\n                        showValidationError(\"Transaction amount validation failed\")\n                        return@CardSubmitComponentConfig false\n                    }\n                    \n                    // Check 2: Verify customer eligibility\n                    if (!validateCustomerEligibility()) {\n                        showValidationError(\"Customer not eligible for this payment method\")\n                        return@CardSubmitComponentConfig false\n                    }\n                    \n                    // Check 3: Validate inventory availability\n                    if (!validateInventoryAvailability()) {\n                        showValidationError(\"Items no longer available\")\n                        return@CardSubmitComponentConfig false\n                    }\n                    \n                    // Check 4: Verify payment limits\n                    if (!validatePaymentLimits()) {\n                        showValidationError(\"Payment exceeds allowed limits\")\n                        return@CardSubmitComponentConfig false\n                    }\n                    \n                    Log.d(\"Payment\", \"All custom validation checks passed\")\n                    return@CardSubmitComponentConfig true\n                    \n                } catch (e: Exception) {\n                    Log.e(\"Payment\", \"Custom validation error: ${e.message}\", e)\n                    showValidationError(\"Validation error occurred\")\n                    return@CardSubmitComponentConfig false\n                }\n            },\n            \n            // Standard validation callback\n            onValidation = { results ->\n                val failedValidations = results.filter { !it.isValid }\n                \n                if (failedValidations.isNotEmpty()) {\n                    Log.w(\"Payment\", \"Standard validation failed:\")\n                    failedValidations.forEach { result ->\n                        Log.w(\"Payment\", \"  - ${result.componentId}: ${result.errorMessage}\")\n                    }\n                } else {\n                    Log.d(\"Payment\", \"Standard validation passed - proceeding to custom validation\")\n                }\n            },\n            \n            onSubmitError = { error ->\n                Log.e(\"Payment\", \"Payment submission error: ${error.message}\")\n                handlePaymentError(error)\n            },\n            \n            onPostAuthorisation = { result ->\n                when (result) {\n                    is MerchantSubmitResult -> handlePaymentSuccess(result)\n                    is FailedSubmitResult -> handlePaymentFailure(result)\n                    else -> handleUnknownResult(result)\n                }\n            }\n        )\n    }\n    \n    private fun validateTransactionAmount(): Boolean {\n        val amount = checkout.getSdkConfig().transactionData.amount\n        val currency = checkout.getSdkConfig().transactionData.currency\n        \n        // Example: Check minimum and maximum amounts\n        val minAmount = 1.0\n        val maxAmount = 10000.0\n        \n        if (amount < minAmount) {\n            Log.w(\"Payment\", \"Amount $amount $currency is below minimum $minAmount\")\n            return false\n        }\n        \n        if (amount > maxAmount) {\n            Log.w(\"Payment\", \"Amount $amount $currency exceeds maximum $maxAmount\")\n            return false\n        }\n        \n        Log.d(\"Payment\", \"Transaction amount validation passed: $amount $currency\")\n        return true\n    }\n    \n    private fun validateCustomerEligibility(): Boolean {\n        val customer = customerRepository.getCurrentCustomer()\n        \n        // Example: Check customer account status\n        if (customer.accountStatus == AccountStatus.SUSPENDED) {\n            Log.w(\"Payment\", \"Customer account is suspended\")\n            return false\n        }\n        \n        if (customer.accountStatus == AccountStatus.PAYMENT_RESTRICTED) {\n            Log.w(\"Payment\", \"Customer has payment restrictions\")\n            return false\n        }\n        \n        // Example: Check customer tier limits\n        val amount = checkout.getSdkConfig().transactionData.amount\n        val tierLimit = customer.tier.maxTransactionAmount\n        \n        if (amount > tierLimit) {\n            Log.w(\"Payment\", \"Amount exceeds customer tier limit: $tierLimit\")\n            return false\n        }\n        \n        Log.d(\"Payment\", \"Customer eligibility validation passed\")\n        return true\n    }\n    \n    private fun validateInventoryAvailability(): Boolean {\n        val cartItems = getCartItems()\n        \n        // Check if all items are still in stock\n        val unavailableItems = cartItems.filter { item ->\n            !inventoryService.isAvailable(item.productId, item.quantity)\n        }\n        \n        if (unavailableItems.isNotEmpty()) {\n            Log.w(\"Payment\", \"Items not available: ${unavailableItems.map { it.name }}\")\n            return false\n        }\n        \n        Log.d(\"Payment\", \"Inventory availability validation passed\")\n        return true\n    }\n    \n    private fun validatePaymentLimits(): Boolean {\n        val customer = customerRepository.getCurrentCustomer()\n        val amount = checkout.getSdkConfig().transactionData.amount\n        \n        // Example: Check daily transaction limit\n        val todaysTotal = customerRepository.getTodaysTransactionTotal(customer.id)\n        val dailyLimit = 50000.0\n        \n        if (todaysTotal + amount > dailyLimit) {\n            Log.w(\"Payment\", \"Would exceed daily limit: ${todaysTotal + amount} > $dailyLimit\")\n            return false\n        }\n        \n        Log.d(\"Payment\", \"Payment limits validation passed\")\n        return true\n    }\n    \n    private fun showValidationError(message: String) {\n        // Display error to user\n        Log.e(\"Payment\", \"Validation error: $message\")\n        // Update UI to show error message\n    }\n    \n    private fun handlePaymentError(error: BaseSdkException) {\n        // Handle payment submission error\n    }\n    \n    private fun handlePaymentSuccess(result: MerchantSubmitResult) {\n        // Handle successful payment\n    }\n    \n    private fun handlePaymentFailure(result: FailedSubmitResult) {\n        // Handle failed payment\n    }\n    \n    private fun handleUnknownResult(result: SubmitResult) {\n        // Handle unknown result type\n    }\n    \n    private fun getCartItems(): List<CartItem> {\n        // Return cart items\n        return emptyList()\n    }\n}\n\ndata class CartItem(\n    val productId: String,\n    val name: String,\n    val quantity: Int\n)\n\nenum class AccountStatus {\n    ACTIVE,\n    SUSPENDED,\n    PAYMENT_RESTRICTED\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"displaying-custom-validation-errors","__idx":26},"children":["Displaying custom validation errors"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When custom validation fails, you should provide clear feedback to the user:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"class ValidationErrorHandler {\n    \n    fun setupCustomValidation(\n        cardSubmitConfig: CardSubmitComponentConfig,\n        errorDisplay: ErrorDisplayView\n    ) {\n        cardSubmitConfig.onCustomValidation = {\n            val validationResult = performCustomValidation()\n            \n            if (!validationResult.isValid) {\n                // Show user-friendly error message\n                errorDisplay.showError(\n                    title = \"Payment Validation Failed\",\n                    message = validationResult.userMessage,\n                    actionButton = \"Update Details\"\n                )\n                \n                // Log technical details for debugging\n                Log.w(\"Payment\", \"Custom validation failed: ${validationResult.technicalMessage}\")\n                \n                // Track validation failure for analytics\n                trackValidationFailure(validationResult)\n                \n                false // Block submission\n            } else {\n                // Clear any previous errors\n                errorDisplay.clearErrors()\n                true // Allow submission\n            }\n        }\n    }\n    \n    private fun performCustomValidation(): CustomValidationResult {\n        // Implement validation logic\n        return CustomValidationResult(\n            isValid = true,\n            userMessage = \"\",\n            technicalMessage = \"\"\n        )\n    }\n    \n    private fun trackValidationFailure(result: CustomValidationResult) {\n        // Track analytics event\n    }\n}\n\ndata class CustomValidationResult(\n    val isValid: Boolean,\n    val userMessage: String,\n    val technicalMessage: String\n)\n\ninterface ErrorDisplayView {\n    fun showError(title: String, message: String, actionButton: String)\n    fun clearErrors()\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"best-practices","__idx":27},"children":["Best practices"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"real-time-validation","__idx":28},"children":["Real-time validation"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"class FormValidationManager {\n    \n    fun setupRealTimeValidation(\n        cardNumber: CardNumberComponent,\n        expiry: CardExpiryDateComponent,\n        cvc: CardCvcComponent\n    ) {\n        // Validate as user types\n        cardNumber.config.onValidationFailed = { results ->\n            showFieldError(cardNumber, results.firstOrNull()?.errorMessage)\n        }\n        \n        cardNumber.config.onValidationPassed = {\n            hideFieldError(cardNumber)\n        }\n        \n        // Similar for other components\n        expiry.config.onValidationFailed = { results ->\n            showFieldError(expiry, results.firstOrNull()?.errorMessage)\n        }\n        \n        cvc.config.onValidationFailed = { results ->\n            showFieldError(cvc, results.firstOrNull()?.errorMessage)\n        }\n    }\n    \n    private fun showFieldError(component: Component<*>, message: String?) {\n        // Update UI to show error state\n        Log.w(\"Validation\", \"Field ${component.componentType}: $message\")\n    }\n    \n    private fun hideFieldError(component: Component<*>) {\n        // Update UI to hide error state\n        Log.d(\"Validation\", \"Field ${component.componentType} is valid\")\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"form-submission-validation","__idx":29},"children":["Form submission validation"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"suspend fun validateAndSubmitPayment(\n    cardNumber: CardNumberComponent,\n    expiry: CardExpiryDateComponent,\n    cvc: CardCvcComponent,\n    holderName: CardHolderNameComponent\n): PaymentResult {\n    \n    // Validate all components before submission\n    val allResults = mutableListOf<ValidationResult>()\n    allResults.addAll(cardNumber.validateWhenSubmit())\n    allResults.addAll(expiry.validateWhenSubmit())\n    allResults.addAll(cvc.validateWhenSubmit())\n    allResults.addAll(holderName.validateWhenSubmit())\n    \n    val failedResults = allResults.filter { !it.isValid }\n    \n    if (failedResults.isNotEmpty()) {\n        Log.w(\"Payment\", \"Form validation failed:\")\n        failedResults.forEach { result ->\n            Log.w(\"Payment\", \"- ${result.errorCode}: ${result.errorMessage}\")\n        }\n        return PaymentResult.ValidationFailed(failedResults)\n    }\n    \n    // All validations passed, proceed with payment\n    return processPayment()\n}\n","lang":"kotlin"},"children":[]}]},"headings":[{"value":"Data validation","id":"data-validation","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"Validation methods","id":"validation-methods","depth":2},{"value":"Validation result structure","id":"validation-result-structure","depth":2},{"value":"Common validation scenarios","id":"common-validation-scenarios","depth":2},{"value":"Get values before payment processing","id":"get-values-before-payment-processing","depth":3},{"value":"Validate all components before submission","id":"validate-all-components-before-submission","depth":3},{"value":"Card number validation","id":"card-number-validation","depth":2},{"value":"Built-in validation rules","id":"built-in-validation-rules","depth":3},{"value":"Configuration example","id":"configuration-example","depth":3},{"value":"Card restrictions vs accepted card brands","id":"card-restrictions-vs-accepted-card-brands","depth":3},{"value":"Restriction validation errors","id":"restriction-validation-errors","depth":4},{"value":"Card brand detection and validation","id":"card-brand-detection-and-validation","depth":3},{"value":"Luhn algorithm validation","id":"luhn-algorithm-validation","depth":3},{"value":"Using validation in your app","id":"using-validation-in-your-app","depth":3},{"value":"Expiry date validation","id":"expiry-date-validation","depth":2},{"value":"Built-in validation rules","id":"built-in-validation-rules-1","depth":3},{"value":"Configuration example","id":"configuration-example-1","depth":3},{"value":"CVC validation","id":"cvc-validation","depth":2},{"value":"Built-in validation rules","id":"built-in-validation-rules-2","depth":3},{"value":"Configuration example","id":"configuration-example-2","depth":3},{"value":"Custom validation with onCustomValidation","id":"custom-validation-with-oncustomvalidation","depth":2},{"value":"When to use custom validation","id":"when-to-use-custom-validation","depth":3},{"value":"Validation flow order","id":"validation-flow-order","depth":3},{"value":"Configuration example","id":"configuration-example-3","depth":3},{"value":"Complete validation example","id":"complete-validation-example","depth":3},{"value":"Displaying custom validation errors","id":"displaying-custom-validation-errors","depth":3},{"value":"Best practices","id":"best-practices","depth":2},{"value":"Real-time validation","id":"real-time-validation","depth":3},{"value":"Form submission validation","id":"form-submission-validation","depth":3}],"frontmatter":{"seo":{"title":"Data validation"}},"lastModified":"2026-06-12T11:56:36.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/components/android/card/data-validation","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}