{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["sub-heading","yes","no"]},"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 data entered by customers and validate it."]}]},{"$$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":["getValueAsync()"]},": Retrieves current field values asynchronously."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["handleValidation()"]},": Validates field data and returns results."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validate()"]},": Validates component data (for complex components)."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Valication can happen:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Automatically on user input (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validationOnChange"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validationOnBlur"]},")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["When ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["submitAsync()"]}," 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":{"align":"left","data-label":"Component name"},"children":["Component name"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"getValueAsync()"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getValueAsync()"]}]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"handleValidation()"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["handleValidation()"]}]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"validate()"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validate()"]}]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Address"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Billing address"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Card brand selector"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Card consent"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Card CVC"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Card expiry date"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Cardholder name"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Card number"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Card-on-file"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Card submit"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Click-once"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Country selection"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Dynamic card image"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["New card"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Postcode"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"Yes","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Pre-fill billing address checkbox"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"No","attributes":{},"children":[]}]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"common-validation-scenarios","__idx":3},"children":["Common validation scenarios"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"get-values-before-payment-processing","__idx":4},"children":["Get values before payment processing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getValueAsync()"]}," to collect form data before processing payments."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"async function processPayment() {\n  // Get individual card field values\n  const cardNumber = await cardNumberComponent.getValueAsync();\n  const expiryDate = await expiryDateComponent.getValueAsync();\n  const cvc = await cvcComponent.getValueAsync();\n  const holderName = await holderNameComponent.getValueAsync();\n  \n  // Get complex component values\n  const addressData = await billingAddressComponent.getValueAsync();\n  \n  console.log('Payment data collected:', {\n    cardNumber: cardNumber ? '****' + cardNumber.slice(-4) : null,\n    expiryDate,\n    addressData\n  });\n  \n  // Submit payment with collected data\n  const paymentResult = await cardSubmitComponent.submitAsync();\n  return paymentResult;\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-individual-fields","__idx":5},"children":["Validate individual fields"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Check specific fields manually when needed."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"async function validateCardNumber() {\n  // Get current card number value\n  const cardNumber = await cardNumberComponent.getValueAsync();\n  \n  // Check if field has a value\n  if (!cardNumber) {\n    showFieldError('cardNumber', 'Card number is required');\n    return false;\n  }\n  \n  // Run validation\n  const validationResults = await cardNumberComponent.handleValidation();\n  \n  // Check if validation passed\n  const isValid = validationResults.every(result => result.valid);\n  \n  if (!isValid) {\n    console.log('Card number validation failed:', validationResults);\n    showFieldError('cardNumber', 'Please enter a valid card number');\n    return false;\n  }\n  \n  // Clear any existing errors\n  clearFieldError('cardNumber');\n  return true;\n}\n\nasync function validateExpiryDate() {\n  const expiryDate = await expiryDateComponent.getValueAsync();\n  \n  if (!expiryDate) {\n    showFieldError('expiryDate', 'Expiry date is required');\n    return false;\n  }\n  \n  const validationResults = await expiryDateComponent.handleValidation();\n  const isValid = validationResults.every(result => result.valid);\n  \n  if (!isValid) {\n    // Get specific error message\n    const error = validationResults.find(result => !result.valid);\n    const errorMessage = error?.errors ? Object.values(error.errors)[0]?.message : 'Invalid expiry date';\n    showFieldError('expiryDate', errorMessage);\n    return false;\n  }\n  \n  clearFieldError('expiryDate');\n  return true;\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-all-fields-before-submission","__idx":6},"children":["Validate all fields before submission"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Ensure all required fields are valid before allowing payment submission."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"async function handleFormSubmit() {\n  console.log('Starting form validation...');\n  \n  // Step 1: Check if required fields have values\n  const cardNumber = await cardNumberComponent.getValueAsync();\n  const expiryDate = await expiryDateComponent.getValueAsync();\n  const cvc = await cvcComponent.getValueAsync();\n  \n  const missingFields = [];\n  if (!cardNumber) missingFields.push('Card number');\n  if (!expiryDate) missingFields.push('Expiry date');\n  if (!cvc) missingFields.push('Security code');\n  \n  if (missingFields.length > 0) {\n    alert(`Please complete these required fields: ${missingFields.join(', ')}`);\n    return false;\n  }\n  \n  // Step 2: Validate all fields\n  try {\n    const validationPromises = [\n      cardNumberComponent.handleValidation(),\n      expiryDateComponent.handleValidation(),\n      cvcComponent.handleValidation()\n    ];\n    \n    // Add cardholder name if required\n    if (holderNameComponent) {\n      validationPromises.push(holderNameComponent.handleValidation());\n    }\n    \n    // Add billing address if configured\n    if (billingAddressComponent) {\n      const addressData = await billingAddressComponent.getValueAsync();\n      validationPromises.push(billingAddressComponent.validate(addressData));\n    }\n    \n    const allValidationResults = await Promise.all(validationPromises);\n    const flatResults = allValidationResults.flat();\n    \n    // Check if all validations passed\n    const allValid = flatResults.every(result => result.valid);\n    \n    if (allValid) {\n      console.log('All validations passed, processing payment...');\n      await processPayment();\n      return true;\n    } else {\n      console.log('Validation failed:', flatResults.filter(result => !result.valid));\n      alert('Please fix the errors in your payment details');\n      \n      // Show specific field errors\n      displayValidationErrors(flatResults);\n      return false;\n    }\n    \n  } catch (error) {\n    console.error('Validation error:', error);\n    alert('Unable to validate payment details. Please try again.');\n    return false;\n  }\n}\n\nfunction displayValidationErrors(validationResults) {\n  validationResults.forEach(result => {\n    if (!result.valid && result.errors) {\n      Object.entries(result.errors).forEach(([fieldName, error]) => {\n        showFieldError(fieldName, error.message);\n      });\n    }\n  });\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-fields-on-user-input","__idx":7},"children":["Validate fields on user input"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure components to validate as users type or when they leave fields."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Card number with real-time validation\nconst cardNumberComponent = new CardNumberComponent(sdkConfig, {\n  validationOnChange: true,  // Validate as user types\n  validationOnBlur: true,    // Validate when user leaves field\n  onValidation: (results) => {\n    const isValid = results.every(result => result.valid);\n    \n    if (!isValid) {\n      // Show error immediately\n      const error = results.find(result => !result.valid);\n      const errorMessage = getFirstErrorMessage(error);\n      showFieldError('cardNumber', errorMessage);\n      \n      // Disable submit button\n      disableSubmitButton();\n    } else {\n      // Clear error and potentially enable submit\n      clearFieldError('cardNumber');\n      checkIfFormValid();\n    }\n  }\n});\n\n// CVC with immediate feedback\nconst cvcComponent = new CardCvcComponent(sdkConfig, {\n  validationOnChange: true,\n  onValidation: (results) => {\n    const isValid = results.every(result => result.valid);\n    \n    // Update visual indicator\n    const cvcField = document.getElementById('cvc-field');\n    cvcField.classList.toggle('is-valid', isValid);\n    cvcField.classList.toggle('is-invalid', !isValid);\n    \n    if (!isValid) {\n      showFieldError('cvc', 'Please enter a valid security code');\n    } else {\n      clearFieldError('cvc');\n    }\n  }\n});\n\nfunction getFirstErrorMessage(validationResult) {\n  if (validationResult.errors) {\n    const firstError = Object.values(validationResult.errors)[0];\n    return firstError?.message || 'Invalid input';\n  }\n  return 'Invalid input';\n}\n\nfunction checkIfFormValid() {\n  // Enable submit button only if all fields are valid\n  const allFieldsValid = document.querySelectorAll('.is-invalid').length === 0;\n  const submitButton = document.getElementById('submit-button');\n  submitButton.disabled = !allFieldsValid;\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-based-on-payment-method","__idx":8},"children":["Validate based on payment method"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Validate different fields depending on the payment method selected."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"async function validateBasedOnPaymentType() {\n  const paymentType = getSelectedPaymentType();\n  console.log('Validating for payment type:', paymentType);\n  \n  if (paymentType === 'new-card') {\n    // Validate all card fields for new card\n    return await validateAllCardFields();\n    \n  } else if (paymentType === 'saved-card') {\n    // Only validate CVC for saved cards\n    const cvcValidation = await cvcComponent.handleValidation();\n    const isValid = cvcValidation.every(result => result.valid);\n    \n    if (!isValid) {\n      showFieldError('cvc', 'Please enter the security code for your saved card');\n    }\n    \n    return isValid;\n    \n  } else if (paymentType === 'paypal') {\n    // No card validation needed for PayPal\n    return true;\n    \n  } else if (paymentType === 'apple-pay') {\n    // Apple Pay handles its own validation\n    return await validateApplePayAvailability();\n  }\n  \n  return false;\n}\n\nasync function validateAllCardFields() {\n  const validations = await Promise.all([\n    cardNumberComponent.handleValidation(),\n    expiryDateComponent.handleValidation(),\n    cvcComponent.handleValidation(),\n    holderNameComponent.handleValidation()\n  ]);\n  \n  const allValid = validations.flat().every(result => result.valid);\n  \n  if (!allValid) {\n    showError('Please complete all card details correctly');\n  }\n  \n  return allValid;\n}\n\nfunction getSelectedPaymentType() {\n  const selectedRadio = document.querySelector('input[name=\"payment-type\"]:checked');\n  return selectedRadio?.value || 'new-card';\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-billing-addresses-for-different-countries","__idx":9},"children":["Validate billing addresses for different countries"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Apply country-specific validation rules for billing addresses."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"async function validateAddressByCountry() {\n  const addressData = await billingAddressComponent.getValueAsync();\n  \n  if (!addressData.countryCode) {\n    showError('Please select a country');\n    return false;\n  }\n  \n  console.log('Validating address for country:', addressData.countryCode);\n  \n  // Country-specific validation rules\n  switch (addressData.countryCode) {\n    case 'US':\n      return validateUSAddress(addressData);\n    case 'GB':\n      return validateUKAddress(addressData);\n    case 'CA':\n      return validateCanadianAddress(addressData);\n    case 'DE':\n      return validateGermanAddress(addressData);\n    default:\n      return validateGenericAddress(addressData);\n  }\n}\n\nfunction validateUSAddress(addressData) {\n  // US: Require ZIP code in 5 or 9 digit format\n  const zipRegex = /^\\d{5}(-\\d{4})?$/;\n  \n  if (!addressData.postalCode) {\n    showFieldError('postalCode', 'ZIP code is required');\n    return false;\n  }\n  \n  if (!zipRegex.test(addressData.postalCode)) {\n    showFieldError('postalCode', 'Please enter a valid US ZIP code (e.g., 12345 or 12345-6789)');\n    return false;\n  }\n  \n  // Require street address\n  if (!addressData.houseNumberOrName || addressData.houseNumberOrName.trim().length < 5) {\n    showFieldError('address', 'Please enter a complete street address');\n    return false;\n  }\n  \n  clearFieldError('postalCode');\n  clearFieldError('address');\n  return true;\n}\n\nfunction validateUKAddress(addressData) {\n  // UK: Validate postcode format\n  const postcodeRegex = /^[A-Z]{1,2}\\d[A-Z\\d]?\\s?\\d[A-Z]{2}$/i;\n  \n  if (!addressData.postalCode) {\n    showFieldError('postalCode', 'Postcode is required');\n    return false;\n  }\n  \n  if (!postcodeRegex.test(addressData.postalCode)) {\n    showFieldError('postalCode', 'Please enter a valid UK postcode (e.g., SW1A 1AA)');\n    return false;\n  }\n  \n  clearFieldError('postalCode');\n  return true;\n}\n\nfunction validateCanadianAddress(addressData) {\n  // Canadian postal code: A1A 1A1 format\n  const postalCodeRegex = /^[A-Z]\\d[A-Z]\\s?\\d[A-Z]\\d$/i;\n  \n  if (!addressData.postalCode) {\n    showFieldError('postalCode', 'Postal code is required');\n    return false;\n  }\n  \n  if (!postalCodeRegex.test(addressData.postalCode)) {\n    showFieldError('postalCode', 'Please enter a valid Canadian postal code (e.g., K1A 0A6)');\n    return false;\n  }\n  \n  clearFieldError('postalCode');\n  return true;\n}\n\nfunction validateGenericAddress(addressData) {\n  // Basic validation for other countries\n  if (!addressData.houseNumberOrName) {\n    showFieldError('address', 'Address is required');\n    return false;\n  }\n  \n  if (!addressData.postalCode) {\n    showFieldError('postalCode', 'Postal code is required');\n    return false;\n  }\n  \n  clearFieldError('address');\n  clearFieldError('postalCode');\n  return true;\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-merchant-and-sdk-fields-simultaneously","__idx":10},"children":["Validate merchant and SDK fields simultaneously"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]}," callback to validate your merchant-owned fields (shipping address, email, terms acceptance) alongside SDK components (billing address) on the first submit attempt. This eliminates the two-phase validation flow where customers see merchant errors first, then SDK errors after a second submission."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"the-problem-with-sequential-validation","__idx":11},"children":["The problem with sequential validation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Without ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]},", validation happens in two phases:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Customer clicks submit"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Merchant validates own fields (shipping, email, etc.)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If merchant validation fails → errors shown, process stops"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If merchant validation passes → SDK validates billing address"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If SDK validation fails → errors shown, customer must submit again"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This creates a poor user experience where customers believe the form is complete after fixing the first set of errors, only to discover hidden errors on the second attempt."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"the-solution-simultaneous-validation","__idx":12},"children":["The solution: simultaneous validation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["With ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]},", all validation happens at once:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Customer clicks submit"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Merchant and SDK validation run simultaneously"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["All errors displayed together on first attempt"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Customer fixes all issues in one go"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Form submits successfully"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Create billing address component\nconst billingAddress = sdk.create('billing-address', {\n  required: true\n});\nbillingAddress.mount('billing-address-container');\n\n// Create card submit with onCustomValidation\nconst cardSubmit = sdk.create('card-submit', {\n  submitText: 'Complete purchase',\n  disableUntilValidated: true,\n  avsRequest: true,\n  cardNumberComponent: cardNumber,\n  cardExpiryDateComponent: cardExpiry,\n  cardCvcComponent: cardCvc,\n  billingAddressComponents: {\n    billingAddressComponent: billingAddress\n  },\n  // Validate all merchant fields alongside SDK billing address\n  onCustomValidation: async () => {\n    let allValid = true;\n    \n    // Clear previous errors\n    clearAllErrors();\n    \n    // Validate shipping address\n    const shippingAddress = document.getElementById('shipping-address').value;\n    if (!shippingAddress || shippingAddress.trim().length < 5) {\n      showFieldError('shipping-address', 'Please enter a complete shipping address');\n      allValid = false;\n    }\n    \n    // Validate email\n    const email = document.getElementById('email').value;\n    const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n    if (!email || !emailRegex.test(email)) {\n      showFieldError('email', 'Please enter a valid email address');\n      allValid = false;\n    }\n    \n    // Validate phone number\n    const phone = document.getElementById('phone').value;\n    const phoneRegex = /^\\+?[\\d\\s\\-()]{10,}$/;\n    if (!phone || !phoneRegex.test(phone)) {\n      showFieldError('phone', 'Please enter a valid phone number');\n      allValid = false;\n    }\n    \n    // Validate terms acceptance\n    const termsAccepted = document.getElementById('terms-checkbox').checked;\n    if (!termsAccepted) {\n      showFieldError('terms', 'You must accept the terms and conditions to proceed');\n      allValid = false;\n    }\n    \n    // Validate privacy policy acceptance\n    const privacyAccepted = document.getElementById('privacy-checkbox').checked;\n    if (!privacyAccepted) {\n      showFieldError('privacy', 'You must accept the privacy policy to proceed');\n      allValid = false;\n    }\n    \n    // Return false to prevent submission if any validation failed\n    // SDK will still validate billing address and display those errors too\n    return allValid;\n  },\n  onPreAuthorisation: async (data) => {\n    // Proceed with transaction\n    return {\n      psd2Data: {}\n    };\n  },\n  onPostAuthorisation: (result) => {\n    console.log('Payment completed:', result.merchantTransactionId);\n    window.location.href = '/success';\n  },\n  onSubmitError: (error) => {\n    console.error('Payment failed:', error.message);\n    showErrorMessage('Payment failed. Please check your details and try again.');\n  }\n});\n\n// Helper functions for error display\nfunction showFieldError(fieldId, message) {\n  const errorElement = document.getElementById(`${fieldId}-error`);\n  if (errorElement) {\n    errorElement.textContent = message;\n    errorElement.style.display = 'block';\n  }\n  \n  const fieldElement = document.getElementById(fieldId);\n  if (fieldElement) {\n    fieldElement.classList.add('field-error');\n  }\n}\n\nfunction clearAllErrors() {\n  document.querySelectorAll('.error-message').forEach(el => {\n    el.textContent = '';\n    el.style.display = 'none';\n  });\n  \n  document.querySelectorAll('.field-error').forEach(el => {\n    el.classList.remove('field-error');\n  });\n}\n\nfunction showErrorMessage(message) {\n  const errorContainer = document.getElementById('general-error');\n  if (errorContainer) {\n    errorContainer.textContent = message;\n    errorContainer.style.display = 'block';\n  }\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"validation-with-different-card-component-modes","__idx":13},"children":["Validation with different card component modes"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]}," callback works with all card integration modes:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"new-card-with-billing-address","__idx":14},"children":["New card with billing address"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"const cardSubmit = sdk.create('card-submit', {\n  cardNumberComponent: cardNumber,\n  cardExpiryDateComponent: cardExpiry,\n  cardCvcComponent: cardCvc,\n  billingAddressComponents: {\n    billingAddressComponent: billingAddress\n  },\n  avsRequest: true,\n  onCustomValidation: async () => {\n    // Validate merchant fields for new card flow\n    return await validateShippingAndContactInfo();\n  }\n});\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"click-once-card-on-file-with-cvc-re-entry","__idx":15},"children":["Click-once (card on file with CVC re-entry)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"const clickOnce = sdk.create('click-once', {\n  isCvcRequired: true,\n  cardSubmitComponentConfig: {\n    billingAddressComponents: {\n      billingAddressComponent: billingAddress\n    },\n    avsRequest: true,\n    onCustomValidation: async () => {\n      // Validate merchant fields for click-once flow\n      return await validateShippingAndContactInfo();\n    }\n  }\n});\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"card-on-file-saved-cards","__idx":16},"children":["Card on file (saved cards)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"const cardOnFile = sdk.create('card-on-file', {\n  isCvcRequired: true\n});\n\nconst cardSubmit = sdk.create('card-submit', {\n  useCardOnFile: true,\n  cardOnFileComponent: cardOnFile,\n  billingAddressComponents: {\n    billingAddressComponent: billingAddress\n  },\n  avsRequest: true,\n  onCustomValidation: async () => {\n    // Validate merchant fields for card-on-file flow\n    return await validateShippingAndContactInfo();\n  }\n});\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"best-practices","__idx":17},"children":["Best practices"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCustomValidation"]},", follow these best practices:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Always return a boolean"]},": Return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["true"]}," to proceed, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["false"]}," to prevent submission"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Display all errors at once"]},": Show validation errors for all invalid fields simultaneously"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Clear previous errors"]},": Remove old error messages before re-validating"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Use async validation"]},": Return a Promise if you need to perform async operations (like API calls)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Provide clear error messages"]},": Help users understand exactly what they need to fix"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Visual feedback"]},": Use CSS classes to highlight invalid fields"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Accessibility"]},": Ensure error messages are accessible to screen readers"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"onCustomValidation: async () => {\n  // Clear all previous errors first\n  clearAllErrors();\n  \n  // Track validation state\n  const validationResults = {\n    shipping: false,\n    email: false,\n    terms: false\n  };\n  \n  // Validate shipping address\n  const shippingAddress = getShippingAddress();\n  if (await validateAddressWithAPI(shippingAddress)) {\n    validationResults.shipping = true;\n  } else {\n    showFieldError('shipping-address', 'Invalid shipping address. Please verify and try again.');\n  }\n  \n  // Validate email\n  const email = getEmail();\n  if (isValidEmail(email)) {\n    validationResults.email = true;\n  } else {\n    showFieldError('email', 'Please enter a valid email address (e.g., user@example.com).');\n  }\n  \n  // Validate terms\n  if (isTermsAccepted()) {\n    validationResults.terms = true;\n  } else {\n    showFieldError('terms', 'Please read and accept the terms and conditions.');\n    // Set focus to terms checkbox for accessibility\n    document.getElementById('terms-checkbox')?.focus();\n  }\n  \n  // Return overall validation result\n  return validationResults.shipping && validationResults.email && validationResults.terms;\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"create-custom-validation-rules","__idx":18},"children":["Create custom validation rules"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement your own validation rules based on business requirements."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"async function validateBusinessRules() {\n  const cardNumber = await cardNumberComponent.getValueAsync();\n  const amount = getCurrentTransactionAmount();\n  const addressData = await billingAddressComponent.getValueAsync();\n  \n  // Rule 1: Block certain card types for high-value transactions\n  if (amount > 1000) {\n    const cardType = detectCardType(cardNumber);\n    const blockedCards = ['DISCOVER', 'DINERS'];\n    \n    if (blockedCards.includes(cardType)) {\n      showError(`${cardType} cards are not accepted for transactions over $1000`);\n      return false;\n    }\n  }\n  \n  // Rule 2: Require address verification for international cards\n  const cardCountry = detectCardIssuingCountry(cardNumber);\n  const billingCountry = addressData.countryCode;\n  \n  if (cardCountry !== billingCountry) {\n    const isAddressVerified = await performAddressVerification(addressData);\n    if (!isAddressVerified) {\n      showError('Address verification required for international cards');\n      return false;\n    }\n  }\n  \n  // Rule 3: Velocity check - limit transactions per day\n  const dailyTransactionCount = await getDailyTransactionCount();\n  if (dailyTransactionCount >= 5) {\n    showError('Daily transaction limit reached. Please try again tomorrow.');\n    return false;\n  }\n  \n  // Rule 4: Amount validation\n  if (amount < 1) {\n    showError('Transaction amount must be at least $1.00');\n    return false;\n  }\n  \n  if (amount > 10000) {\n    showError('Transaction amount cannot exceed $10,000. Please contact support for larger transactions.');\n    return false;\n  }\n  \n  return true;\n}\n\nfunction detectCardType(cardNumber) {\n  if (!cardNumber) return 'UNKNOWN';\n  \n  const patterns = {\n    'VISA': /^4/,\n    'MASTERCARD': /^5[1-5]/,\n    'AMEX': /^3[47]/,\n    'DISCOVER': /^6(?:011|5)/,\n    'DINERS': /^3[0689]/\n  };\n  \n  for (const [type, pattern] of Object.entries(patterns)) {\n    if (pattern.test(cardNumber)) {\n      return type;\n    }\n  }\n  \n  return 'UNKNOWN';\n}\n\nasync function performAddressVerification(addressData) {\n  try {\n    // This would integrate with your address verification service\n    const response = await fetch('/api/verify-address', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(addressData)\n    });\n    \n    const result = await response.json();\n    return result.verified === true;\n  } catch (error) {\n    console.error('Address verification failed:', error);\n    // Fail open - allow transaction if verification service is down\n    return true;\n  }\n}\n\nfunction getCurrentTransactionAmount() {\n  // Get amount from your application state\n  return parseFloat(document.getElementById('amount-input')?.value || '0');\n}\n","lang":"typescript"},"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":"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 individual fields","id":"validate-individual-fields","depth":3},{"value":"Validate all fields before submission","id":"validate-all-fields-before-submission","depth":3},{"value":"Validate fields on user input","id":"validate-fields-on-user-input","depth":3},{"value":"Validate based on payment method","id":"validate-based-on-payment-method","depth":3},{"value":"Validate billing addresses for different countries","id":"validate-billing-addresses-for-different-countries","depth":3},{"value":"Validate merchant and SDK fields simultaneously","id":"validate-merchant-and-sdk-fields-simultaneously","depth":3},{"value":"The problem with sequential validation","id":"the-problem-with-sequential-validation","depth":4},{"value":"The solution: simultaneous validation","id":"the-solution-simultaneous-validation","depth":4},{"value":"Validation with different card component modes","id":"validation-with-different-card-component-modes","depth":4},{"value":"New card with billing address","id":"new-card-with-billing-address","depth":5},{"value":"Click-once (card on file with CVC re-entry)","id":"click-once-card-on-file-with-cvc-re-entry","depth":5},{"value":"Card on file (saved cards)","id":"card-on-file-saved-cards","depth":5},{"value":"Best practices","id":"best-practices","depth":4},{"value":"Create custom validation rules","id":"create-custom-validation-rules","depth":3}],"frontmatter":{"seo":{"title":"Data validation"}},"lastModified":"2026-02-26T12:14:32.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/components/web/card/data-validation","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}