Learn how to diagnose and fix common issues with the Apple Pay web component.
The Apple Pay web component throws specific exceptions for different error scenarios:
| Exception | Description | Prevention |
|---|---|---|
ApplePaySdkLoadFailedException | The Apple Pay SDK failed to load. For example, due to network issues, blocked resources, or an incorrect SDK URL. | Verify network connectivity and check CSP policies. |
ApplePayNotAvailableException | Apple Pay isn't available on device. For example, due to an unsupported browser, no Apple Pay setup, or region restrictions. | Check device compatibility before showing button. |
ApplePayRequiresAppleDeviceException | The CSS method was used on non-Apple device. | Use device detection before enabling the CSS method. |
ApplePayRequiresSafariException | The CSS method was used on a non-Safari browser. | Check browser type before enabling the CSS method. |
ApplePaySafariVersionIncompatibleException | The Safari version is too old for the CSS method. | Check the Safari version compatibility. |
ApplePayRequiresHttpsException | The CSS method was used on a non-HTTPS connection. | Ensure HTTPS for all Apple Pay implementations. |
ApplePayMerchantIdentifierRequiredException | The merchant ID is missing from the configuration. | Validate the configuration before initialisation. |
ApplePayMerchantValidationFailedException | Merchant validation with Apple failed. For example, due to an invalid merchant ID or certificate issues. | Verify Apple Developer Console setup. |
ApplePayPaymentFailedException | Payment processing failed. For example, due to network issues, invalid payment data, or server errors. | Implement retry logic and validation. |
ApplePaySessionCancelledException | The customiser cancelled the Apple Pay session. | Handle gracefully and don't treat this as an error. |
ApplePayValidationException | Payment request validation failed. | Validate all required fields before submission. |
ApplePayPaymentAddressInvalidException | Shipping/billing address validation failed. For example, due to an invalid address format or restricted delivery area. | Implement address validation and clear error messages. |
ApplePayPaymentMethodInvalidException | Payment method validation failed. For example, due to an unsupported card type or expired payment method. | Check supported networks and card validity. |
ApplePayCouponCodeInvalidException | Coupon code validation failed. | Implement real-time coupon validation. |
const config = {
onError: (error) => {
// Log error for debugging
console.error('Apple Pay error:', {
name: error.name,
message: error.message,
code: error.code,
stack: error.stack,
timestamp: new Date().toISOString()
});
// Handle specific error types
switch (error.constructor.name) {
case 'ApplePayNotAvailableException':
showUserMessage('Apple Pay is not available on this device. Please use an alternative payment method.');
showAlternativePaymentMethods();
break;
case 'ApplePayMerchantValidationFailedException':
showUserMessage('Payment system temporarily unavailable. Please try again in a few moments.');
logCriticalError('Merchant validation failed', error);
break;
case 'ApplePayRequiresHttpsException':
showUserMessage('Secure connection required. Please ensure you are using HTTPS.');
redirectToHttps();
break;
case 'ApplePaySessionCancelledException':
// User cancelled - don't show error
trackEvent('apple_pay_cancelled');
break;
case 'ApplePayPaymentFailedException':
showUserMessage('Payment failed. Please check your payment information and try again.');
offerRetryOption();
break;
default:
showUserMessage('An unexpected error occurred. Please try again or contact support.');
logUnknownError(error);
}
}
};
function showUserMessage(message) {
// Show user-friendly error message
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
document.getElementById('payment-container').appendChild(errorDiv);
}
function logCriticalError(context, error) {
// Send to error monitoring service
errorReporting.captureException(error, { context });
}The symptoms of this are:
- The button element is hidden or not rendered.
- There's an empty container where the button should appear.
- No Apple Pay option is visible to customers.
// Step 1: Check Apple Pay availability
console.log('Checking Apple Pay availability...');
if (!window.ApplePaySession) {
console.error('ApplePaySession not available - not supported browser');
return;
}
if (!ApplePaySession.canMakePayments()) {
console.error('Apple Pay cannot make payments - no setup or unsupported device');
return;
}
// Step 2: Check specific networks
const supportedNetworks = ['visa', 'masterCard', 'amex', 'discover'];
if (!ApplePaySession.canMakePayments(supportedNetworks)) {
console.warn('Apple Pay available but no supported cards configured');
}
// Step 3: Verify merchant configuration
const merchantId = sdkConfig.session.allowedFundingTypes.wallets.applePay.merchantId;
if (!merchantId) {
console.error('Merchant ID is missing');
return;
}
if (!merchantId.startsWith('merchant.')) {
console.error('Invalid merchant ID format:', merchantId);
return;
}
// Step 4: Check HTTPS requirement
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
console.error('Apple Pay requires HTTPS in production');
return;
}
console.log('All Apple Pay requirements met');// Solution: Graceful fallback
function initializeApplePay() {
if (checkApplePayAvailability()) {
renderApplePayButton();
} else {
renderAlternativePaymentMethods();
}
}
function checkApplePayAvailability() {
return window.ApplePaySession &&
ApplePaySession.canMakePayments() &&
isHttps() &&
hasMerchantId();
}
function renderAlternativePaymentMethods() {
const container = document.getElementById('payment-buttons');
container.innerHTML = `
<button class="credit-card-button">Pay with Credit Card</button>
<button class="paypal-button">Pay with PayPal</button>
`;
}The symptoms of this are:
- An error occurs during the Apple Pay session initialisation.
- "Merchant validation failed" error messages are displayed.
- The payment sheet doesn't appear after button tap.
// Check merchant ID configuration
const diagnostics = {
merchantId: sdkConfig.session.allowedFundingTypes.wallets.applePay.merchantId,
domain: window.location.hostname,
protocol: window.location.protocol,
userAgent: navigator.userAgent
};
console.log('Merchant Validation Diagnostics:', diagnostics);
// Test merchant validation endpoint
async function testMerchantValidation() {
try {
const testURL = 'https://apple-pay-gateway.apple.com/paymentservices/startSession';
const response = await fetch('/api/validate-merchant', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
validationURL: testURL,
merchantId: diagnostics.merchantId,
displayName: 'Test Store',
domain: diagnostics.domain
})
});
if (!response.ok) {
console.error('Merchant validation test failed:', response.status);
} else {
console.log('Merchant validation test passed');
}
} catch (error) {
console.error('Merchant validation test error:', error);
}
}Verify the Apple Developer Console setup:
// Checklist for Apple Developer Console
const merchantSetupChecklist = {
merchantIdCreated: true, // Created in Apple Developer Console
merchantIdFormat: 'merchant.com.yourcompany.store', // Correct format
certificateGenerated: true, // Payment processing certificate created
certificateInstalled: true, // Certificate installed on server
domainRegistered: true, // Domain registered for merchant ID
domainVerified: true // Domain verification file uploaded
};
console.log('Merchant setup checklist:', merchantSetupChecklist);Perform server-side merchant validation:
// Example server-side validation (Node.js)
app.post('/api/validate-merchant', async (req, res) => {
const { validationURL, merchantId, displayName, domain } = req.body;
try {
// Load merchant certificate and key
const merchantCert = fs.readFileSync('path/to/merchant.crt');
const merchantKey = fs.readFileSync('path/to/merchant.key');
// Create validation payload
const payload = {
merchantIdentifier: merchantId,
displayName: displayName,
initiative: 'web',
initiativeContext: domain
};
// Make request to Apple
const response = await axios.post(validationURL, payload, {
httpsAgent: new https.Agent({
cert: merchantCert,
key: merchantKey
}),
headers: {
'Content-Type': 'application/json'
}
});
res.json(response.data);
} catch (error) {
console.error('Merchant validation failed:', error);
res.status(500).json({ error: 'Merchant validation failed' });
}
});Implement client-side retry logic:
async function validateMerchantWithRetry(validationURL, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const merchantSession = await this.unityService.createPaymentMethod(
PaymentMethod.ApplePay,
validationURL,
this.config.merchantDisplayName,
window.location.hostname
);
return merchantSession;
} catch (error) {
console.warn(`Merchant validation attempt ${attempt} failed:`, error);
if (attempt === maxRetries) {
throw new Error(`Merchant validation failed after ${maxRetries} attempts`);
}
// Wait before retrying (exponential backoff)
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}The symptoms of this are:
- The Apple Pay sheet appears but payment fails.
- The customer sees a generic error message.
- The transaction doesn't complete successfully.
// Enhanced payment authorisation with detailed logging
this.applePaySession.onpaymentauthorized = async (event) => {
console.log('Payment authorisation started:', {
paymentToken: event.payment.token ? 'Present' : 'Missing',
billingContact: event.payment.billingContact ? 'Present' : 'Missing',
shippingContact: event.payment.shippingContact ? 'Present' : 'Missing',
paymentMethod: event.payment.paymentMethod
});
try {
// Validate payment data
this.validatePaymentData(event.payment);
// Process payment
const result = await this.processPaymentWithDiagnostics(event.payment);
if (result.success) {
console.log('Payment successful:', result.transactionId);
this.applePaySession.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
} else {
console.error('Payment failed:', result.error);
this.applePaySession.completePayment({ status: ApplePaySession.STATUS_FAILURE });
}
} catch (error) {
console.error('Payment authorisation error:', error);
this.applePaySession.completePayment({ status: ApplePaySession.STATUS_FAILURE });
}
};Perform Enhanced validation:
function validatePaymentData(payment) {
const validations = [
{
check: () => payment.token && payment.token.paymentData,
error: 'Payment token is missing or invalid'
},
{
check: () => payment.token.transactionIdentifier,
error: 'Transaction identifier is missing'
},
{
check: () => payment.billingContact,
error: 'Billing contact is required'
}
];
for (const validation of validations) {
if (!validation.check()) {
throw new Error(validation.error);
}
}
}
async function processPaymentWithDiagnostics(payment) {
const startTime = Date.now();
try {
// Create transaction request with additional metadata
const transactionRequest = {
merchantTransactionId: generateUniqueId(),
amount: this.config.paymentRequest.total.amount,
currency: this.config.paymentRequest.currencyCode,
paymentToken: payment.token.paymentData,
billingContact: payment.billingContact,
shippingContact: payment.shippingContact,
metadata: {
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
sessionId: this.sessionId
}
};
const result = await this.unityService.transactionAsync(transactionRequest);
const processingTime = Date.now() - startTime;
console.log('Payment processing completed:', {
success: result.success,
processingTime: `${processingTime}ms`,
transactionId: result.transactionId
});
return result;
} catch (error) {
const processingTime = Date.now() - startTime;
console.error('Payment processing failed:', {
error: error.message,
processingTime: `${processingTime}ms`,
stack: error.stack
});
throw error;
}
}Implement network error handling:
async function processPaymentWithRetry(payment, maxRetries = 2) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await this.processPayment(payment);
} catch (error) {
console.warn(`Payment attempt ${attempt} failed:`, error.message);
if (this.isRetryableError(error) && attempt < maxRetries) {
console.log(`Retrying payment (attempt ${attempt + 1}/${maxRetries})`);
await this.delay(1000 * attempt); // Progressive delay
continue;
}
throw error;
}
}
}
function isRetryableError(error) {
const retryableErrors = [
'Network error',
'Timeout',
'Service temporarily unavailable',
'Rate limit exceeded'
];
return retryableErrors.some(retryableError =>
error.message.toLowerCase().includes(retryableError.toLowerCase())
);
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}The symptoms of this are:
- The custom styled button doesn't trigger Apple Pay.
- The button appears but clicking it has no effect.
- There are errors about browser compatibility.
function diagnoseCSSMethodCompatibility() {
const diagnostics = {
browser: getBrowserInfo(),
device: getDeviceInfo(),
https: location.protocol === 'https:',
applePaySupport: !!window.ApplePaySession,
canMakePayments: window.ApplePaySession ? ApplePaySession.canMakePayments() : false
};
console.log('CSS method compatibility:', diagnostics);
const requirements = [
{
name: 'HTTPS required',
met: diagnostics.https || location.hostname === 'localhost',
required: true
},
{
name: 'Apple device',
met: diagnostics.device.isApple,
required: true
},
{
name: 'Safari browser',
met: diagnostics.browser.isSafari,
required: true
},
{
name: 'Safari version 11.1+',
met: diagnostics.browser.version >= 11.1,
required: true
}
];
const unmetRequirements = requirements.filter(req => req.required && !req.met);
if (unmetRequirements.length > 0) {
console.error('CSS method requirements not met:', unmetRequirements);
return false;
}
return true;
}
function getBrowserInfo() {
const userAgent = navigator.userAgent;
const isSafari = /Safari/.test(userAgent) && !(/Chrome|Chromium|Edge|OPR/.test(userAgent));
let version = 0;
if (isSafari) {
const versionMatch = userAgent.match(/Version\/(\d+)\.(\d+)/);
if (versionMatch) {
version = parseFloat(`${versionMatch[1]}.${versionMatch[2]}`);
}
}
return { isSafari, version };
}
function getDeviceInfo() {
const userAgent = navigator.userAgent;
const isApple = /Mac|iPhone|iPad|iPod/.test(userAgent);
return { isApple };
}Implement browser detection and fallback:
function initializeApplePayButton() {
if (shouldUseCSSMethod()) {
if (diagnoseCSSMethodCompatibility()) {
initializeCSSMethod();
} else {
showCompatibilityError();
}
} else {
initializeOfficialSDK();
}
}
function shouldUseCSSMethod() {
// Only use CSS method when specifically required
return config.usingCss === true;
}
function initializeCSSMethod() {
const config = {
usingCss: true,
style: {
template: `
<button class="apple-pay-button" type="button" data-apple-pay="true">
<span class="apple-pay-icon"></span>
<span class="apple-pay-text">Pay with Apple Pay</span>
</button>
`,
templateCSS: `
.apple-pay-button {
background: #000;
color: #fff;
border: none;
border-radius: 4px;
height: 44px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
}
.apple-pay-button:hover {
background: #333;
}
.apple-pay-button:active {
background: #555;
}
.apple-pay-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.apple-pay-icon {
margin-right: 8px;
}
`
}
};
const applePayComponent = new ApplePayButtonComponent(sdkConfig, config);
applePayComponent.mount('apple-pay-container');
}
function showCompatibilityError() {
const errorMessage = `
<div class="apple-pay-error">
<h4>Apple Pay Not Available</h4>
<p>Apple Pay with custom styling requires:</p>
<ul>
<li>Safari browser</li>
<li>Apple device (Mac, iPhone, iPad)</li>
<li>HTTPS connection</li>
<li>Safari version 11.1 or later</li>
</ul>
<p>Please use a supported browser or device.</p>
</div>
`;
document.getElementById('apple-pay-container').innerHTML = errorMessage;
}Implement progressive enhancement:
function initializeWithProgressiveEnhancement() {
// Start with most compatible method
if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
try {
// Try official SDK first
initializeOfficialSDK();
} catch (error) {
console.warn('Official SDK failed, trying CSS method:', error);
if (diagnoseCSSMethodCompatibility()) {
initializeCSSMethod();
} else {
showAlternativePaymentMethods();
}
}
} else {
showAlternativePaymentMethods();
}
}To get more detailed information about errors, use this snippet:
// Enable comprehensive logging
window.applePayDebug = {
logLevel: 'verbose',
logPaymentData: false, // Never log actual payment tokens
logNetworkRequests: true
};
// Debug helper functions
const debugApplePay = {
checkEnvironment() {
console.group('🔍 Apple Pay Environment Check');
console.log('Protocol:', location.protocol);
console.log('Hostname:', location.hostname);
console.log('User Agent:', navigator.userAgent);
console.log('Apple Pay Session Available:', !!window.ApplePaySession);
console.log('Can Make Payments:', window.ApplePaySession ? ApplePaySession.canMakePayments() : false);
console.groupEnd();
},
logPaymentRequest(request) {
console.group('📋 Payment Request');
console.log('Country Code:', request.countryCode);
console.log('Currency Code:', request.currencyCode);
console.log('Total Amount:', request.total.amount);
console.log('Supported Networks:', request.supportedNetworks);
console.log('Merchant Capabilities:', request.merchantCapabilities);
console.groupEnd();
},
monitorSession(session) {
const originalCompletePayment = session.completePayment;
session.completePayment = function(result) {
console.log('💳 Payment Completed with status:', result.status);
return originalCompletePayment.call(this, result);
};
}
};
// Use in your implementation
debugApplePay.checkEnvironment();
debugApplePay.logPaymentRequest(paymentRequest);
debugApplePay.monitorSession(applePaySession);// Pre-flight checks before initialising Apple Pay
async function performPreflightChecks() {
const checks = [
{
name: 'HTTPS check',
test: () => location.protocol === 'https:' || location.hostname === 'localhost',
fix: 'Ensure your site is served over HTTPS'
},
{
name: 'Apple Pay support',
test: () => !!window.ApplePaySession,
fix: 'Apple Pay is not supported in this browser'
},
{
name: 'Payment capability',
test: () => ApplePaySession.canMakePayments(),
fix: 'Apple Pay is not set up on this device'
},
{
name: 'Merchant ID',
test: () => !!config.merchantId && config.merchantId.startsWith('merchant.'),
fix: 'Verify merchant ID configuration'
},
{
name: 'Network connectivity',
test: async () => {
try {
await fetch('/api/health-check', { method: 'HEAD' });
return true;
} catch {
return false;
}
},
fix: 'Check network connection'
}
];
const results = [];
for (const check of checks) {
const passed = await check.test();
results.push({ name: check.name, passed, fix: check.fix });
if (!passed) {
console.warn(`❌ ${check.name}: ${check.fix}`);
} else {
console.log(`✅ ${check.name}`);
}
}
return results.every(result => result.passed);
}
// Use before initialisation
async function initializeApplePay() {
const preflightPassed = await performPreflightChecks();
if (preflightPassed) {
// Safe to initialise Apple Pay
const applePayComponent = new ApplePayButtonComponent(sdkConfig, config);
applePayComponent.mount('apple-pay-container');
} else {
// Show alternative payment methods
showFallbackPaymentOptions();
}
}// Error monitoring setup
const errorMonitoring = {
captureError(error, context) {
// Send to your error tracking service
const errorData = {
message: error.message,
stack: error.stack,
context: context,
userAgent: navigator.userAgent,
url: location.href,
timestamp: new Date().toISOString(),
userId: getCurrentUserId(),
sessionId: getSessionId()
};
// Example: Send to Sentry, LogRocket, etc.
this.sendToErrorService(errorData);
},
trackApplePayEvent(eventType, data) {
// Track Apple Pay specific events
const eventData = {
type: eventType,
data: data,
timestamp: new Date().toISOString(),
sessionId: getSessionId()
};
this.sendToAnalytics(eventData);
}
};
// Integration with Apple Pay component
const config = {
onError: (error) => {
errorMonitoring.captureError(error, { component: 'ApplePay' });
},
onPaymentAuthorized: (payment) => {
errorMonitoring.trackApplePayEvent('payment_authorized', {
amount: payment.total.amount,
currency: payment.currencyCode
});
}
};