Skip to content

Data validation

Learn about built-in validation and implement additional scenarios for Apple Pay web.

Overview

The Apple Pay component includes comprehensive validation to ensure data integrity and compliance with Apple's requirements. All built-in validation is performed before creating payment requests and during the Apple Pay flow. If it fails, the SDK will throw an ApplePayValidationException with detailed error information.

You can also easily build custom validation, depending on your business needs.

Built-in validation

By default, the Apple Pay component validates that:

  • Fields marked as required are provided.
  • Payment request data meets Apple Pay specifications.
  • Currency codes, country codes, and amounts are formatted properly.
  • Merchant capabilities and supported networks are valid.
  • Contact fields and shipping methods are properly configured.
  • Browser and device compatibility requirements are met.

Error codes

The Apple Pay component returns structured error codes for different validation failures:

Error codeDescriptionCommon causes
REQUIRED_FIELDA required field is missing.Missing mandatory configuration.
INVALID_AMOUNT_FORMATThe amount format is invalid.Non-decimal string amount.
INVALID_CURRENCY_CODEThe currency code is invalid.Non-ISO 4217 currency format.
INVALID_COUNTRY_CODEThe country code is invalid.Non-ISO 3166-1 alpha-2 format.
INVALID_MERCHANT_CAPABILITIESMerchant capabilities invalid.Unsupported capability values.
INVALID_PAYMENT_NETWORKSPayment networks invalid.Unsupported network values.
MISSING_MERCHANT_IDThe merchant identifier is missing.Apple Pay merchant ID not configured.
INVALID_CONTACT_FIELDSContact fields invalid.Unsupported contact field values.
INVALID_SHIPPING_METHODSShipping methods invalid.Malformed shipping method data.
INVALID_LINE_ITEMSLine items invalid.Malformed display items.
APPLE_PAY_NOT_AVAILABLEApple Pay not available.Unsupported browser/device.
HTTPS_REQUIREDHTTPS connection required.Non-secure context.
DOMAIN_NOT_REGISTEREDDomain not registered with Apple.Domain validation failure.

Validation example

// Example of handling validation errors
const applePayConfig = {
  // ... configuration
  onError: (error) => {
    if (error.name === 'ApplePayValidationException') {
      const { validationResult } = error;
      
      // Handle specific validation errors
      if (validationResult.errors['paymentRequest.merchantCapabilities']) {
        console.error('Invalid merchant capabilities');
      }
      
      if (validationResult.errors['paymentRequest.total.amount']) {
        console.error('Invalid total amount format');
      }
      
      if (validationResult.errors['merchantId']) {
        console.error('Apple Pay merchant ID missing or invalid');
      }
      
      // Display user-friendly error messages
      Object.entries(validationResult.errors).forEach(([field, errorInfo]) => {
        console.error(`${field}: ${errorInfo.message}`);
      });
    }
  }
};

Custom validation

Pre-authorisation validation

Run validation before the Apple Pay authorisation to catch issues early and prevent failed payments.

const applePayComponent = pxpSdk.create('apple-pay-button', {
  onPreAuthorisation: async () => {
    try {
      // 1. Business logic validation
      const orderValidation = await validateOrder({
        cartItems: getCartItems(),
        customerLocation: getCustomerLocation(),
        paymentAmount: getOrderTotal()
      });
      
      if (!orderValidation.valid) {
        throw new Error(orderValidation.reason);
      }
      
      // 2. Apple Pay specific validation
      const applePayValidation = await validateApplePayRequirements({
        merchantId: getMerchantId(),
        domain: window.location.hostname,
        totalAmount: getOrderTotal()
      });
      
      if (!applePayValidation.valid) {
        throw new Error('Apple Pay requirements not met');
      }
      
      // 3. Security validation
      const securityCheck = await performSecurityValidation({
        customerIP: getClientIP(),
        deviceFingerprint: getDeviceFingerprint(),
        paymentHistory: getCustomerPaymentHistory()
      });
      
      if (securityCheck.riskLevel === 'HIGH') {
        // Return additional security data for transaction
        return {
          riskScreeningData: {
            performRiskScreening: true,
            deviceSessionId: await getDeviceSessionId(),
            riskScore: securityCheck.score
          },
          threeDSecureData: {
            requestThreeDS: true
          }
        };
      }
      
      // 4. Inventory validation
      const inventoryCheck = await validateInventory(getCartItems());
      if (!inventoryCheck.allAvailable) {
        throw new Error('Some items are no longer available');
      }
      
      // 5. Regulatory compliance
      const complianceCheck = await validateCompliance({
        customerCountry: getCustomerCountry(),
        orderAmount: getOrderTotal(),
        productTypes: getProductTypes()
      });
      
      if (!complianceCheck.compliant) {
        throw new Error('Order does not meet regulatory requirements');
      }
      
      // Return transaction initialisation data
      return {
        addressVerification: true,
        riskScreeningData: {
          performRiskScreening: true,
          deviceSessionId: await getDeviceSessionId()
        }
      };
      
    } catch (error) {
      console.error('Pre-authorisation validation failed:', error);
      // Returning null will cancel the Apple Pay flow
      return null;
    }
  }
});

Post-authorisation validation

Run validation after Apple Pay authorisation to ensure payment integrity before completing the transaction.

const applePayConfig = {
  onPostAuthorisation: async (result, applePayResult) => {
    try {
      if (result instanceof AuthorisedSubmitResult) {
        // 1. Validate Apple Pay payment data
        const paymentValidation = await validateApplePayData({
          paymentToken: applePayResult.token,
          billingContact: applePayResult.billingContact,
          shippingContact: applePayResult.shippingContact
        });
        
        if (!paymentValidation.valid) {
          throw new Error('Apple Pay payment data validation failed');
        }
        
        // 2. Final inventory check
        const finalInventoryCheck = await reserveInventory(getOrderId());
        if (!finalInventoryCheck.success) {
          throw new Error('Inventory no longer available');
        }
        
        // 3. Validate billing address
        if (applePayResult.billingContact) {
          const billingValidation = await validateBillingAddress({
            address: applePayResult.billingContact.postalAddress,
            name: applePayResult.billingContact.name
          });
          
          if (!billingValidation.valid) {
            throw new Error('Billing address validation failed');
          }
        }
        
        // 4. Validate shipping address
        if (applePayResult.shippingContact) {
          const shippingValidation = await validateShippingAddress({
            address: applePayResult.shippingContact.postalAddress,
            restrictions: getShippingRestrictions()
          });
          
          if (!shippingValidation.valid) {
            throw new Error('Shipping address validation failed');
          }
        }
        
        // 5. Process successful payment
        await completeOrder({
          transactionId: result.provider.code,
          applePayData: applePayResult
        });
        
        // Redirect to success page
        window.location.href = '/payment-success';
        
      } else if (result instanceof FailedSubmitResult) {
        // Handle payment failure
        console.error('Payment failed:', result.errorReason);
        showError('Payment processing failed. Please try again.');
      }
      
    } catch (error) {
      console.error('Post-authorisation validation failed:', error);
      showError(error.message);
    }
  }
};

Shipping contact validation

Validate shipping addresses in real-time as customers select them in the Apple Pay sheet.

const applePayConfig = {
  onShippingContactSelected: async (contact) => {
    try {
      // 1. Validate address format
      const addressValidation = validateAddressFormat(contact.postalAddress);
      if (!addressValidation.valid) {
        return {
          errors: [{
            code: 'shippingContactInvalid',
            contactField: 'postalAddress',
            message: 'Please enter a complete address'
          }]
        };
      }
      
      // 2. Check shipping restrictions
      const shippingValidation = await validateShippingAvailability({
        countryCode: contact.postalAddress.countryCode,
        postalCode: contact.postalAddress.postalCode,
        cartItems: getCartItems()
      });
      
      if (!shippingValidation.available) {
        return {
          errors: [{
            code: 'shippingContactInvalid',
            contactField: 'postalAddress',
            message: 'Shipping not available to this location'
          }]
        };
      }
      
      // 3. Calculate shipping costs
      const shippingCost = await calculateShippingCost({
        address: contact.postalAddress,
        items: getCartItems(),
        expedited: false
      });
      
      // 4. Calculate taxes
      const tax = await calculateTax({
        address: contact.postalAddress,
        subtotal: getSubtotal()
      });
      
      // Return updated payment details
      const newTotal = getSubtotal() + shippingCost + tax;
      
      return {
        newTotal: {
          label: 'Total',
          amount: newTotal.toFixed(2)
        },
        newLineItems: [
          { label: 'Subtotal', amount: getSubtotal().toFixed(2) },
          { label: 'Shipping', amount: shippingCost.toFixed(2) },
          { label: 'Tax', amount: tax.toFixed(2) }
        ],
        newShippingMethods: await getAvailableShippingMethods(contact.postalAddress)
      };
      
    } catch (error) {
      console.error('Shipping contact validation failed:', error);
      return {
        errors: [{
          code: 'shippingContactInvalid',
          message: 'Unable to validate shipping address. Please try again.'
        }]
      };
    }
  }
};

Browser compatibility validation

Ensure Apple Pay requirements are met before showing the component.

function validateApplePayCompatibility() {
  const validation = {
    valid: true,
    errors: []
  };
  
  // Check if running on HTTPS
  if (window.location.protocol !== 'https:') {
    validation.valid = false;
    validation.errors.push({
      code: 'HTTPS_REQUIRED',
      message: 'Apple Pay requires HTTPS connection'
    });
  }
  
  // Check if Apple Pay is available
  if (!window.ApplePaySession) {
    validation.valid = false;
    validation.errors.push({
      code: 'APPLE_PAY_NOT_AVAILABLE',
      message: 'Apple Pay not supported in this browser'
    });
  }
  
  // Check if device can make payments
  if (window.ApplePaySession && !ApplePaySession.canMakePayments()) {
    validation.valid = false;
    validation.errors.push({
      code: 'APPLE_PAY_NOT_AVAILABLE',
      message: 'Apple Pay not available on this device'
    });
  }
  
  // Check Safari version for CSS method
  if (shouldUseCssMethod()) {
    const safariVersion = getSafariVersion();
    if (safariVersion && safariVersion < 11.1) {
      validation.valid = false;
      validation.errors.push({
        code: 'SAFARI_VERSION_INCOMPATIBLE',
        message: 'Safari 11.1+ required for CSS method'
      });
    }
  }
  
  return validation;
}

// Use validation before initialising
const compatibilityCheck = validateApplePayCompatibility();
if (compatibilityCheck.valid) {
  // Initialise Apple Pay component
  initializeApplePay();
} else {
  // Show alternative payment methods
  showAlternativePaymentMethods();
  console.warn('Apple Pay not available:', compatibilityCheck.errors);
}

Validate geographic restrictions

Check in real-time if any cart items are restricted in the customer's country to prevent compliance violations.

function validateGeographicRestrictions(customerCountry, cartItems) {
  const restrictedItems = cartItems.filter(item => 
    item.restrictions?.countries?.includes(customerCountry) ||
    item.restrictions?.applePayRestricted?.includes(customerCountry)
  );
  
  // Check Apple Pay specific restrictions
  const applePayRestrictions = getApplePayCountryRestrictions();
  const applePayBlocked = !applePayRestrictions.allowedCountries.includes(customerCountry);
  
  return {
    valid: restrictedItems.length === 0 && !applePayBlocked,
    restrictedItems: restrictedItems,
    applePayBlocked: applePayBlocked,
    message: applePayBlocked ? 
      'Apple Pay not available in your country' : 
      `Some items cannot be shipped to ${customerCountry}`
  };
}

Calculate a risk score

Calculate a comprehensive fraud risk score based on multiple behavioural and historical factors, with Apple Pay specific considerations.

async function calculateFraudScore(transactionData, applePayData) {
  const factors = {
    velocityScore: await checkPaymentVelocity(transactionData.customerEmail),
    locationScore: await validateLocation(transactionData.ipAddress),
    deviceScore: await analyzeDeviceFingerprint(transactionData.deviceData),
    historyScore: await analyzePaymentHistory(transactionData.customerId),
    applePayScore: await analyzeApplePayData(applePayData)
  };
  
  // Apple Pay specific risk factors
  const applePayFactors = {
    deviceAccountScore: applePayData.paymentMethod?.deviceAccountIdentifier ? 0.1 : 0.3,
    biometricScore: applePayData.biometricAuthentication ? 0.1 : 0.4,
    walletScore: await analyzeWalletHistory(applePayData.paymentMethod?.primaryAccountIdentifier)
  };
  
  // Weight Apple Pay factors (generally lower risk)
  const baseScore = Object.values(factors).reduce((sum, score) => sum + score, 0) / Object.keys(factors).length;
  const applePayAdjustment = Object.values(applePayFactors).reduce((sum, score) => sum + score, 0) / Object.keys(applePayFactors).length;
  
  const totalScore = (baseScore * 0.7) + (applePayAdjustment * 0.3); // Apple Pay generally reduces risk
  
  return {
    score: totalScore,
    riskLevel: totalScore > 0.8 ? 'HIGH' : totalScore > 0.5 ? 'MEDIUM' : 'LOW',
    factors: { ...factors, ...applePayFactors },
    applePayAdvantage: baseScore - totalScore // How much Apple Pay reduced the risk
  };
}

Validate payment amounts

Ensure payment amounts meet Apple Pay requirements and business rules.

function validatePaymentAmounts(paymentRequest) {
  const validation = {
    valid: true,
    errors: []
  };
  
  // Validate total amount format
  const totalAmount = parseFloat(paymentRequest.total.amount);
  if (isNaN(totalAmount) || totalAmount <= 0) {
    validation.valid = false;
    validation.errors.push({
      field: 'total.amount',
      code: 'INVALID_AMOUNT',
      message: 'Total amount must be a positive number'
    });
  }
  
  // Validate line items sum up to total
  if (paymentRequest.lineItems && paymentRequest.lineItems.length > 0) {
    const lineItemsSum = paymentRequest.lineItems.reduce((sum, item) => {
      return sum + parseFloat(item.amount);
    }, 0);
    
    const difference = Math.abs(lineItemsSum - totalAmount);
    if (difference > 0.01) { // Allow for small rounding differences
      validation.valid = false;
      validation.errors.push({
        field: 'lineItems',
        code: 'AMOUNT_MISMATCH',
        message: 'Line items do not sum to total amount'
      });
    }
  }
  
  // Check minimum/maximum amounts
  const minAmount = 0.50; // Apple Pay minimum
  const maxAmount = 10000.00; // Business rule maximum
  
  if (totalAmount < minAmount) {
    validation.valid = false;
    validation.errors.push({
      field: 'total.amount',
      code: 'AMOUNT_TOO_LOW',
      message: `Minimum amount is ${minAmount}`
    });
  }
  
  if (totalAmount > maxAmount) {
    validation.valid = false;
    validation.errors.push({
      field: 'total.amount',
      code: 'AMOUNT_TOO_HIGH',
      message: `Maximum amount is ${maxAmount}`
    });
  }
  
  return validation;
}