Skip to content

Troubleshooting

Learn how to diagnose and fix common issues with the Apple Pay web component.

Exception types and error codes

Component exceptions

The Apple Pay web component throws specific exceptions for different error scenarios:

ExceptionDescriptionPrevention
ApplePaySdkLoadFailedExceptionThe 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.
ApplePayNotAvailableExceptionApple 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.
ApplePayRequiresAppleDeviceExceptionThe CSS method was used on non-Apple device.Use device detection before enabling the CSS method.
ApplePayRequiresSafariExceptionThe CSS method was used on a non-Safari browser.Check browser type before enabling the CSS method.
ApplePaySafariVersionIncompatibleExceptionThe Safari version is too old for the CSS method.Check the Safari version compatibility.
ApplePayRequiresHttpsExceptionThe CSS method was used on a non-HTTPS connection.Ensure HTTPS for all Apple Pay implementations.
ApplePayMerchantIdentifierRequiredExceptionThe merchant ID is missing from the configuration.Validate the configuration before initialisation.
ApplePayMerchantValidationFailedExceptionMerchant validation with Apple failed. For example, due to an invalid merchant ID or certificate issues.Verify Apple Developer Console setup.
ApplePayPaymentFailedExceptionPayment processing failed. For example, due to network issues, invalid payment data, or server errors.Implement retry logic and validation.
ApplePaySessionCancelledExceptionThe customiser cancelled the Apple Pay session.Handle gracefully and don't treat this as an error.
ApplePayValidationExceptionPayment request validation failed.Validate all required fields before submission.
ApplePayPaymentAddressInvalidExceptionShipping/billing address validation failed. For example, due to an invalid address format or restricted delivery area.Implement address validation and clear error messages.
ApplePayPaymentMethodInvalidExceptionPayment method validation failed. For example, due to an unsupported card type or expired payment method.Check supported networks and card validity.
ApplePayCouponCodeInvalidExceptionCoupon code validation failed.Implement real-time coupon validation.

Error handling best practices

Comprehensive error handler

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

Troubleshooting common issues

Apple Pay button isn't showing

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.

Diagnostic steps

// 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');

Solutions

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

Merchant validation failures

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.

Diagnostic steps

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

Solutions

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

Payment authorisation failures

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.

Diagnostic steps

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

Solutions

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

CSS method issues

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.

Diagnostic steps

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

Solutions

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

Debugging tools and techniques

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

Prevention and best practices

Proactive error prevention

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

Monitoring and alerting

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