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 |
|
|
|
---|---|---|---|
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');
}
Updated 3 days ago