Data validation

Collect data entered by customers and validate it.

Overview

By validating data prior to transaction initiation, you can:

  • Reduce the risk of fraudulent payments.
  • Offer a better user experience, by providing clear feedback that helps customers complete forms correctly.
  • Ensure you meet PCI and regulatory requirements.

Validation methods

There are three key validation methods:

  • getValueAsync(): Retrieves current field values asynchronously.
  • handleValidation(): Validates field data and returns results.
  • validate(): Validates component data (for complex components).

Valication can happen:

  • Automatically on user input (validationOnChange, validationOnBlur).
  • When submitAsync() is called.
  • When you call validation methods directly.

The following table shows the compatibility of these methods with the different components.

Component name

getValueAsync()

handleValidation()

validate()

Address

Billing address

Card brand selector

Card consent

Card CVC

Card expiry date

Cardholder name

Card number

Card-on-file

Card submit

Click-once

Country selection

Dynamic card image

New card

Postcode

Pre-fill billing address checkbox

Common validation scenarios

Get values before payment processing

Use getValueAsync() to collect form data before processing payments.

async function processPayment() {
  // Get individual card field values
  const cardNumber = await cardNumberComponent.getValueAsync();
  const expiryDate = await expiryDateComponent.getValueAsync();
  const cvc = await cvcComponent.getValueAsync();
  const holderName = await holderNameComponent.getValueAsync();
  
  // Get complex component values
  const addressData = await billingAddressComponent.getValueAsync();
  
  console.log('Payment data collected:', {
    cardNumber: cardNumber ? '****' + cardNumber.slice(-4) : null,
    expiryDate,
    addressData
  });
  
  // Submit payment with collected data
  const paymentResult = await cardSubmitComponent.submitAsync();
  return paymentResult;
}

Validate individual fields

Check specific fields manually when needed.

async function validateCardNumber() {
  // Get current card number value
  const cardNumber = await cardNumberComponent.getValueAsync();
  
  // Check if field has a value
  if (!cardNumber) {
    showFieldError('cardNumber', 'Card number is required');
    return false;
  }
  
  // Run validation
  const validationResults = await cardNumberComponent.handleValidation();
  
  // Check if validation passed
  const isValid = validationResults.every(result => result.valid);
  
  if (!isValid) {
    console.log('Card number validation failed:', validationResults);
    showFieldError('cardNumber', 'Please enter a valid card number');
    return false;
  }
  
  // Clear any existing errors
  clearFieldError('cardNumber');
  return true;
}

async function validateExpiryDate() {
  const expiryDate = await expiryDateComponent.getValueAsync();
  
  if (!expiryDate) {
    showFieldError('expiryDate', 'Expiry date is required');
    return false;
  }
  
  const validationResults = await expiryDateComponent.handleValidation();
  const isValid = validationResults.every(result => result.valid);
  
  if (!isValid) {
    // Get specific error message
    const error = validationResults.find(result => !result.valid);
    const errorMessage = error?.errors ? Object.values(error.errors)[0]?.message : 'Invalid expiry date';
    showFieldError('expiryDate', errorMessage);
    return false;
  }
  
  clearFieldError('expiryDate');
  return true;
}

Validate all fields before submission

Ensure all required fields are valid before allowing payment submission.

async function handleFormSubmit() {
  console.log('Starting form validation...');
  
  // Step 1: Check if required fields have values
  const cardNumber = await cardNumberComponent.getValueAsync();
  const expiryDate = await expiryDateComponent.getValueAsync();
  const cvc = await cvcComponent.getValueAsync();
  
  const missingFields = [];
  if (!cardNumber) missingFields.push('Card number');
  if (!expiryDate) missingFields.push('Expiry date');
  if (!cvc) missingFields.push('Security code');
  
  if (missingFields.length > 0) {
    alert(`Please complete these required fields: ${missingFields.join(', ')}`);
    return false;
  }
  
  // Step 2: Validate all fields
  try {
    const validationPromises = [
      cardNumberComponent.handleValidation(),
      expiryDateComponent.handleValidation(),
      cvcComponent.handleValidation()
    ];
    
    // Add cardholder name if required
    if (holderNameComponent) {
      validationPromises.push(holderNameComponent.handleValidation());
    }
    
    // Add billing address if configured
    if (billingAddressComponent) {
      const addressData = await billingAddressComponent.getValueAsync();
      validationPromises.push(billingAddressComponent.validate(addressData));
    }
    
    const allValidationResults = await Promise.all(validationPromises);
    const flatResults = allValidationResults.flat();
    
    // Check if all validations passed
    const allValid = flatResults.every(result => result.valid);
    
    if (allValid) {
      console.log('All validations passed, processing payment...');
      await processPayment();
      return true;
    } else {
      console.log('Validation failed:', flatResults.filter(result => !result.valid));
      alert('Please fix the errors in your payment details');
      
      // Show specific field errors
      displayValidationErrors(flatResults);
      return false;
    }
    
  } catch (error) {
    console.error('Validation error:', error);
    alert('Unable to validate payment details. Please try again.');
    return false;
  }
}

function displayValidationErrors(validationResults) {
  validationResults.forEach(result => {
    if (!result.valid && result.errors) {
      Object.entries(result.errors).forEach(([fieldName, error]) => {
        showFieldError(fieldName, error.message);
      });
    }
  });
}

Validate fields on user input

Configure components to validate as users type or when they leave fields.

// Card number with real-time validation
const cardNumberComponent = new CardNumberComponent(sdkConfig, {
  validationOnChange: true,  // Validate as user types
  validationOnBlur: true,    // Validate when user leaves field
  onValidation: (results) => {
    const isValid = results.every(result => result.valid);
    
    if (!isValid) {
      // Show error immediately
      const error = results.find(result => !result.valid);
      const errorMessage = getFirstErrorMessage(error);
      showFieldError('cardNumber', errorMessage);
      
      // Disable submit button
      disableSubmitButton();
    } else {
      // Clear error and potentially enable submit
      clearFieldError('cardNumber');
      checkIfFormValid();
    }
  }
});

// CVC with immediate feedback
const cvcComponent = new CardCvcComponent(sdkConfig, {
  validationOnChange: true,
  onValidation: (results) => {
    const isValid = results.every(result => result.valid);
    
    // Update visual indicator
    const cvcField = document.getElementById('cvc-field');
    cvcField.classList.toggle('is-valid', isValid);
    cvcField.classList.toggle('is-invalid', !isValid);
    
    if (!isValid) {
      showFieldError('cvc', 'Please enter a valid security code');
    } else {
      clearFieldError('cvc');
    }
  }
});

function getFirstErrorMessage(validationResult) {
  if (validationResult.errors) {
    const firstError = Object.values(validationResult.errors)[0];
    return firstError?.message || 'Invalid input';
  }
  return 'Invalid input';
}

function checkIfFormValid() {
  // Enable submit button only if all fields are valid
  const allFieldsValid = document.querySelectorAll('.is-invalid').length === 0;
  const submitButton = document.getElementById('submit-button');
  submitButton.disabled = !allFieldsValid;
}

Validate based on payment method

Validate different fields depending on the payment method selected.

async function validateBasedOnPaymentType() {
  const paymentType = getSelectedPaymentType();
  console.log('Validating for payment type:', paymentType);
  
  if (paymentType === 'new-card') {
    // Validate all card fields for new card
    return await validateAllCardFields();
    
  } else if (paymentType === 'saved-card') {
    // Only validate CVC for saved cards
    const cvcValidation = await cvcComponent.handleValidation();
    const isValid = cvcValidation.every(result => result.valid);
    
    if (!isValid) {
      showFieldError('cvc', 'Please enter the security code for your saved card');
    }
    
    return isValid;
    
  } else if (paymentType === 'paypal') {
    // No card validation needed for PayPal
    return true;
    
  } else if (paymentType === 'apple-pay') {
    // Apple Pay handles its own validation
    return await validateApplePayAvailability();
  }
  
  return false;
}

async function validateAllCardFields() {
  const validations = await Promise.all([
    cardNumberComponent.handleValidation(),
    expiryDateComponent.handleValidation(),
    cvcComponent.handleValidation(),
    holderNameComponent.handleValidation()
  ]);
  
  const allValid = validations.flat().every(result => result.valid);
  
  if (!allValid) {
    showError('Please complete all card details correctly');
  }
  
  return allValid;
}

function getSelectedPaymentType() {
  const selectedRadio = document.querySelector('input[name="payment-type"]:checked');
  return selectedRadio?.value || 'new-card';
}

Validate billing addresses for different countries

Apply country-specific validation rules for billing addresses.

async function validateAddressByCountry() {
  const addressData = await billingAddressComponent.getValueAsync();
  
  if (!addressData.countryCode) {
    showError('Please select a country');
    return false;
  }
  
  console.log('Validating address for country:', addressData.countryCode);
  
  // Country-specific validation rules
  switch (addressData.countryCode) {
    case 'US':
      return validateUSAddress(addressData);
    case 'GB':
      return validateUKAddress(addressData);
    case 'CA':
      return validateCanadianAddress(addressData);
    case 'DE':
      return validateGermanAddress(addressData);
    default:
      return validateGenericAddress(addressData);
  }
}

function validateUSAddress(addressData) {
  // US: Require ZIP code in 5 or 9 digit format
  const zipRegex = /^\d{5}(-\d{4})?$/;
  
  if (!addressData.postalCode) {
    showFieldError('postalCode', 'ZIP code is required');
    return false;
  }
  
  if (!zipRegex.test(addressData.postalCode)) {
    showFieldError('postalCode', 'Please enter a valid US ZIP code (e.g., 12345 or 12345-6789)');
    return false;
  }
  
  // Require street address
  if (!addressData.houseNumberOrName || addressData.houseNumberOrName.trim().length < 5) {
    showFieldError('address', 'Please enter a complete street address');
    return false;
  }
  
  clearFieldError('postalCode');
  clearFieldError('address');
  return true;
}

function validateUKAddress(addressData) {
  // UK: Validate postcode format
  const postcodeRegex = /^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/i;
  
  if (!addressData.postalCode) {
    showFieldError('postalCode', 'Postcode is required');
    return false;
  }
  
  if (!postcodeRegex.test(addressData.postalCode)) {
    showFieldError('postalCode', 'Please enter a valid UK postcode (e.g., SW1A 1AA)');
    return false;
  }
  
  clearFieldError('postalCode');
  return true;
}

function validateCanadianAddress(addressData) {
  // Canadian postal code: A1A 1A1 format
  const postalCodeRegex = /^[A-Z]\d[A-Z]\s?\d[A-Z]\d$/i;
  
  if (!addressData.postalCode) {
    showFieldError('postalCode', 'Postal code is required');
    return false;
  }
  
  if (!postalCodeRegex.test(addressData.postalCode)) {
    showFieldError('postalCode', 'Please enter a valid Canadian postal code (e.g., K1A 0A6)');
    return false;
  }
  
  clearFieldError('postalCode');
  return true;
}

function validateGenericAddress(addressData) {
  // Basic validation for other countries
  if (!addressData.houseNumberOrName) {
    showFieldError('address', 'Address is required');
    return false;
  }
  
  if (!addressData.postalCode) {
    showFieldError('postalCode', 'Postal code is required');
    return false;
  }
  
  clearFieldError('address');
  clearFieldError('postalCode');
  return true;
}

Create custom validation rules

Implement your own validation rules based on business requirements.

async function validateBusinessRules() {
  const cardNumber = await cardNumberComponent.getValueAsync();
  const amount = getCurrentTransactionAmount();
  const addressData = await billingAddressComponent.getValueAsync();
  
  // Rule 1: Block certain card types for high-value transactions
  if (amount > 1000) {
    const cardType = detectCardType(cardNumber);
    const blockedCards = ['DISCOVER', 'DINERS'];
    
    if (blockedCards.includes(cardType)) {
      showError(`${cardType} cards are not accepted for transactions over $1000`);
      return false;
    }
  }
  
  // Rule 2: Require address verification for international cards
  const cardCountry = detectCardIssuingCountry(cardNumber);
  const billingCountry = addressData.countryCode;
  
  if (cardCountry !== billingCountry) {
    const isAddressVerified = await performAddressVerification(addressData);
    if (!isAddressVerified) {
      showError('Address verification required for international cards');
      return false;
    }
  }
  
  // Rule 3: Velocity check - limit transactions per day
  const dailyTransactionCount = await getDailyTransactionCount();
  if (dailyTransactionCount >= 5) {
    showError('Daily transaction limit reached. Please try again tomorrow.');
    return false;
  }
  
  // Rule 4: Amount validation
  if (amount < 1) {
    showError('Transaction amount must be at least $1.00');
    return false;
  }
  
  if (amount > 10000) {
    showError('Transaction amount cannot exceed $10,000. Please contact support for larger transactions.');
    return false;
  }
  
  return true;
}

function detectCardType(cardNumber) {
  if (!cardNumber) return 'UNKNOWN';
  
  const patterns = {
    'VISA': /^4/,
    'MASTERCARD': /^5[1-5]/,
    'AMEX': /^3[47]/,
    'DISCOVER': /^6(?:011|5)/,
    'DINERS': /^3[0689]/
  };
  
  for (const [type, pattern] of Object.entries(patterns)) {
    if (pattern.test(cardNumber)) {
      return type;
    }
  }
  
  return 'UNKNOWN';
}

async function performAddressVerification(addressData) {
  try {
    // This would integrate with your address verification service
    const response = await fetch('/api/verify-address', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(addressData)
    });
    
    const result = await response.json();
    return result.verified === true;
  } catch (error) {
    console.error('Address verification failed:', error);
    // Fail open - allow transaction if verification service is down
    return true;
  }
}

function getCurrentTransactionAmount() {
  // Get amount from your application state
  return parseFloat(document.getElementById('amount-input')?.value || '0');
}