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
    });
  }
};