Learn about built-in validation and implement additional scenarios for Apple Pay web.
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.
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.
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. |
// 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}`);
});
}
}
};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;
}
}
});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);
}
}
};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.'
}]
};
}
}
};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);
}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 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
};
}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;
}