# 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 code | Description | Common causes |
|  --- | --- | --- |
| `REQUIRED_FIELD` | A required field is missing. | Missing mandatory configuration. |
| `INVALID_AMOUNT_FORMAT` | The amount format is invalid. | Non-decimal string amount. |
| `INVALID_CURRENCY_CODE` | The currency code is invalid. | Non-ISO 4217 currency format. |
| `INVALID_COUNTRY_CODE` | The country code is invalid. | Non-ISO 3166-1 alpha-2 format. |
| `INVALID_MERCHANT_CAPABILITIES` | Merchant capabilities invalid. | Unsupported capability values. |
| `INVALID_PAYMENT_NETWORKS` | Payment networks invalid. | Unsupported network values. |
| `MISSING_MERCHANT_ID` | The merchant identifier is missing. | Apple Pay merchant ID not configured. |
| `INVALID_CONTACT_FIELDS` | Contact fields invalid. | Unsupported contact field values. |
| `INVALID_SHIPPING_METHODS` | Shipping methods invalid. | Malformed shipping method data. |
| `INVALID_LINE_ITEMS` | Line items invalid. | Malformed display items. |
| `APPLE_PAY_NOT_AVAILABLE` | Apple Pay not available. | Unsupported browser/device. |
| `HTTPS_REQUIRED` | HTTPS connection required. | Non-secure context. |
| `DOMAIN_NOT_REGISTERED` | Domain not registered with Apple. | Domain validation failure. |


### Validation example

```typescript
// 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.

```typescript
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(),
            userIp: "192.168.1.100",
            account: {
              id: "user_12345678",
              creationDateTime: "2024-01-15T10:30:00.000Z"
            },
            items: [{
              price: 140.00,
              quantity: 1,
              category: "Electronics"
            }],
            fulfillments: [{
              type: "Shipped",
              shipping: {
                shippingMethod: "Express"
              },
              recipientPerson: {
                phoneNumber: "+1234567890",
                email: "customer@example.com"
              }
            }]
          },
          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(),
          userIp: "192.168.1.100",
          account: {
            id: "user_12345678",
            creationDateTime: "2024-01-15T10:30:00.000Z"
          },
          fulfillments: [{
            type: "Shipped",
            recipientPerson: {
              phoneNumber: "+1234567890"
            }
          }]
        }
      };
      
    } 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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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;
}
```