Apple Pay JS v3 in Angular: postMessage origin mismatch and onpaymentauthorized never fires

Description:

I’m integrating Apple Pay JS (version 3) into an Angular application. Here are the key details:

Environment:

Angular (latest) Apple Pay JS v3 Chrome (confirmed window.ApplePaySession is available) application region is in US. I'm in Taiwan and using my iPhone Taiwan account to scan the QR Code/

Implemented Handlers:

onvalidatemerchant onpaymentmethodselected onpaymentauthorized oncancel

Observed Behavior:

When I click the Apple Pay button, the console logs:

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://applepay.cdn-apple.com') does not match the recipient window's origin ('https://{our-domain-name}') Despite this, the QR code still appears.

Scanning the QR code with an iPhone 13 Pro running iOS 18.4.1 brings up the Apple Pay sheet with the correct amount, but payment never completes.

In the browser, none of my Angular event handlers fire except oncancel.

Questions:

What causes the postMessage origin mismatch with Apple’s CDN frame, and how should my application handle it? Why doesn’t onpaymentauthorized ever fire, and how can I complete the payment flow so that session.completePayment() succeeds?

Any guidance or sample code snippets for a proper merchant-validation and payment-completion sequence in this setup would be greatly appreciated.

my code

onApplePayButtonClicked() { if (!ApplePaySession) { console.error('[ApplePay] ApplePaySession is not supported'); return; }

// Define ApplePayPaymentRequest
const request : ApplePayJS.ApplePayPaymentRequest = {
  countryCode: this.currencyCode,
  currencyCode: Constants.CountryCodeUS,
  merchantCapabilities: this.merchantCapabilities,
  supportedNetworks: this.supportedNetworks,
  total: {
    label: this.label,
    type: "final" as ApplePayJS.ApplePayLineItemType,
    amount: this.orderAmount.toString(),
  },
};


// Create ApplePaySession
const session = new ApplePaySession(3, request);


session.onvalidatemerchant = async event => {
  console.info('[ApplePay] onvalidatemerchant', event);
  try {
    const merchantSession = await fetch(`${this.paymentUrl}/api/applepay/validatemerchant`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        PKeyCompany: this.paymentAppleMerchantId,
        ValidationUrl: event.validationURL
      })
    }).then((r) => r.json());
    session.completeMerchantValidation(merchantSession);
  } catch (error) {
    console.error('[ApplePay] onvalidatemerchant MerchantValidation error', error);
    session.abort();
  }
};


session.onpaymentauthorized = (event) => {
  console.info('[ApplePay] paymentauthorized', event);
  const token = event.payment.token;
  this.paymentTokenEmitted.emit({
    token: JSON.stringify(token),
    paymentType: PaymentOptionType.ApplePay
  });
  session.completePayment(ApplePaySession.STATUS_SUCCESS);
};


session.onpaymentmethodselected = (event) => {
  console.info('[ApplePay] paymentmethodselected', event);
  const update: ApplePayJS.ApplePayPaymentMethodUpdate = {
    newTotal: request.total
  };
  session.completePaymentMethodSelection(update);
};


session.oncancel = (event) => {
  console.error('[ApplePay] oncancel', event);
  this.errorEmitted.emit({ error: 'Apple Pay cancel' });
};


session.begin();

}

Answered by DTS Engineer in 893925022

Hi @TonyTsai,

You wrote:

[...] What causes the postMessage origin mismatch with Apple’s CDN frame, and how should my application handle it? [...]

You do not need to fix this. Here is what's happening:

  • Apple Pay JS internally injects an <iframe> from apple pay.cdn-apple.com into your page.
  • It uses postMessage internally for cross-frame communication.
  • Chrome logs this warning when the internal handshake negotiates original—it's Apple Pay's SDK doing this, not your code.
  • It doesn't break the session and isn't actionable from your side.

Note: Don't try to intercept or filter window.addEventListener('message', ...) for Apple's CDN messages—that'll break a lot of things.

Then, you wrote:

[...] Why doesn’t onpaymentauthorized ever fire, and how can I complete the payment flow so that session.completePayment() succeeds? [...]

In the ApplePayPaymentRequest object, your countryCode and currencyCode are swapped:

// ❌ YOUR CODE (SWAPPED)
const request: ApplePayJS.ApplePayPaymentRequest = {
  countryCode: this.currencyCode,        // Wrong! This holds a currency value (e.g. "USD")
  currencyCode: Constants.CountryCodeUS, // Wrong! This holds a country value (e.g. "US")
  ...
};

// ✅ CORRECT
const request: ApplePayJS.ApplePayPaymentRequest = {
  countryCode: 'US',    // e.g. "US", "TW", "GB" -- ISO 3166-1 alpha-2
  currencyCode: 'USD',  // e.g. "USD", "TWD" -- ISO 4217
  ...
};

When Apple Pay receives an invalid countryCode or currencyCode, the session may silently fail during or after the iPhone-side authorization step -- which is exactly what you've described.

You onpaymentauthorized handler is incorrect—the logic is in the wrong order:

// ❌ YOUR CODE -- completes payment before processing it
session.onpaymentauthorized = (event) => {
  const token = event.payment.token;
  this.paymentTokenEmitted.emit({ token: JSON.stringify(token), ... });

  // You're telling Apple "SUCCESS" immediately,
  // before your backend has actually charged anything!
  session.completePayment(ApplePaySession.STATUS_SUCCESS);
};

// ✅ CORRECT -- process server-side first, then complete
session.onpaymentauthorized = async (event) => {
  console.info('[ApplePay] paymentauthorized', event);
  const token = event.payment.token;

  try {
    // Send token to YOUR backend to charge the customer
    const result = await fetch(`${this.paymentUrl}/api/applepay/processpayment`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token })
    }).then(r => r.json());

    if (result.success) {
      session.completePayment(ApplePaySession.STATUS_SUCCESS); // ✅ Tell Apple it worked
      this.paymentTokenEmitted.emit({ token: JSON.stringify(token), ... });
    } else {
      session.completePayment(ApplePaySession.STATUS_FAILURE); // ✅ Tell Apple it failed
    }
  } catch (error) {
    console.error('[ApplePay] payment processing error', error);
    session.completePayment(ApplePaySession.STATUS_FAILURE);
  }
};

Important: Apple Pay requires session.completePayment() to be called within 30 seconds of onpaymentauthorized firing, or the session will time out and show an error on the iPhone.

And earlier in your response, you wrote:

[...] application region is in US. I'm in Taiwan and using my iPhone Taiwan account to scan the QR Code [...]

For testing, you should use a US Apple Pay account with a US card to validate your US-region integration. Taiwan accounts may work in production depending on the card network, but are unreliable for debugging.

That said, the swapped countryCode/currencyCode is the primary culprit—fix that first and then re-test with a US account.

Cheers,

Paris X Pinkney |  WWDR | DTS Engineer

Were you able to get this resolved? I'm running into a similar issue and haven't had any luck thus far.

Experiencing the same issue but not in Angular env. Storefront hosting iFrame to site that hosts the cert and is validated via Apple. Any ideas on resolving?

I'm having the same problem too (exept my environment is vanilla js+html).

Did anyone find a cause or solution to this?

Hi @TonyTsai,

You wrote:

[...] What causes the postMessage origin mismatch with Apple’s CDN frame, and how should my application handle it? [...]

You do not need to fix this. Here is what's happening:

  • Apple Pay JS internally injects an <iframe> from apple pay.cdn-apple.com into your page.
  • It uses postMessage internally for cross-frame communication.
  • Chrome logs this warning when the internal handshake negotiates original—it's Apple Pay's SDK doing this, not your code.
  • It doesn't break the session and isn't actionable from your side.

Note: Don't try to intercept or filter window.addEventListener('message', ...) for Apple's CDN messages—that'll break a lot of things.

Then, you wrote:

[...] Why doesn’t onpaymentauthorized ever fire, and how can I complete the payment flow so that session.completePayment() succeeds? [...]

In the ApplePayPaymentRequest object, your countryCode and currencyCode are swapped:

// ❌ YOUR CODE (SWAPPED)
const request: ApplePayJS.ApplePayPaymentRequest = {
  countryCode: this.currencyCode,        // Wrong! This holds a currency value (e.g. "USD")
  currencyCode: Constants.CountryCodeUS, // Wrong! This holds a country value (e.g. "US")
  ...
};

// ✅ CORRECT
const request: ApplePayJS.ApplePayPaymentRequest = {
  countryCode: 'US',    // e.g. "US", "TW", "GB" -- ISO 3166-1 alpha-2
  currencyCode: 'USD',  // e.g. "USD", "TWD" -- ISO 4217
  ...
};

When Apple Pay receives an invalid countryCode or currencyCode, the session may silently fail during or after the iPhone-side authorization step -- which is exactly what you've described.

You onpaymentauthorized handler is incorrect—the logic is in the wrong order:

// ❌ YOUR CODE -- completes payment before processing it
session.onpaymentauthorized = (event) => {
  const token = event.payment.token;
  this.paymentTokenEmitted.emit({ token: JSON.stringify(token), ... });

  // You're telling Apple "SUCCESS" immediately,
  // before your backend has actually charged anything!
  session.completePayment(ApplePaySession.STATUS_SUCCESS);
};

// ✅ CORRECT -- process server-side first, then complete
session.onpaymentauthorized = async (event) => {
  console.info('[ApplePay] paymentauthorized', event);
  const token = event.payment.token;

  try {
    // Send token to YOUR backend to charge the customer
    const result = await fetch(`${this.paymentUrl}/api/applepay/processpayment`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token })
    }).then(r => r.json());

    if (result.success) {
      session.completePayment(ApplePaySession.STATUS_SUCCESS); // ✅ Tell Apple it worked
      this.paymentTokenEmitted.emit({ token: JSON.stringify(token), ... });
    } else {
      session.completePayment(ApplePaySession.STATUS_FAILURE); // ✅ Tell Apple it failed
    }
  } catch (error) {
    console.error('[ApplePay] payment processing error', error);
    session.completePayment(ApplePaySession.STATUS_FAILURE);
  }
};

Important: Apple Pay requires session.completePayment() to be called within 30 seconds of onpaymentauthorized firing, or the session will time out and show an error on the iPhone.

And earlier in your response, you wrote:

[...] application region is in US. I'm in Taiwan and using my iPhone Taiwan account to scan the QR Code [...]

For testing, you should use a US Apple Pay account with a US card to validate your US-region integration. Taiwan accounts may work in production depending on the card network, but are unreliable for debugging.

That said, the swapped countryCode/currencyCode is the primary culprit—fix that first and then re-test with a US account.

Cheers,

Paris X Pinkney |  WWDR | DTS Engineer

Apple Pay JS v3 in Angular: postMessage origin mismatch and onpaymentauthorized never fires
 
 
Q