@economist/web-apple-pay 1.7.8 → 1.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -575,10 +575,9 @@ var loadRecaptchaScript = (siteKey) => {
575
575
  );
576
576
  if (existingScript) {
577
577
  existingScript.addEventListener("load", () => resolve());
578
- existingScript.addEventListener(
579
- "error",
580
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
581
- );
578
+ existingScript.addEventListener("error", () => {
579
+ reject(new Error("Failed to load existing reCAPTCHA script"));
580
+ });
582
581
  return;
583
582
  }
584
583
  const script = document.createElement("script");
@@ -586,7 +585,9 @@ var loadRecaptchaScript = (siteKey) => {
586
585
  script.async = true;
587
586
  script.defer = true;
588
587
  script.onload = () => resolve();
589
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
588
+ script.onerror = () => {
589
+ reject(new Error("Failed to load reCAPTCHA script"));
590
+ };
590
591
  document.head.appendChild(script);
591
592
  });
592
593
  };
@@ -835,17 +836,14 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
835
836
  #country = "";
836
837
  #triggerElement = null;
837
838
  #keyHandler = null;
838
- #recaptchaTokens = null;
839
- #recaptchaTokensPromise = null;
839
+ #isClosed = false;
840
840
  /**
841
- * Synchronous callback invoked directly from the CTA click handler once the
842
- * user confirms. The caller (ApplePayButton) must call startApplePaySession
843
- * inside this callback to preserve the browser's user-gesture chain — Safari
844
- * requires new ApplePaySession() to execute within the original click stack.
845
- *
846
- * reCAPTCHA tokens are guaranteed non-null here: the CTA remains disabled
847
- * until the token pre-fetch resolves, so this callback is never reached
848
- * without valid tokens.
841
+ * Callback invoked synchronously from the CTA click handler.
842
+ * Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
843
+ * by the time this is called. The caller (ApplePayButton) must call
844
+ * new ApplePaySession() synchronously inside this callback to stay within
845
+ * Safari's user-gesture window. The token promise resolves before
846
+ * onPaymentAuthorized fires (user still needs to authenticate on the sheet).
849
847
  */
850
848
  onConfirm = null;
851
849
  set offer(value) {
@@ -942,28 +940,32 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
942
940
  }
943
941
  });
944
942
  }
945
- if (this.#recaptchaTokensPromise) {
946
- if (cta) {
947
- cta.setAttribute("disabled", "");
948
- cta.setAttribute("aria-disabled", "true");
949
- cta.setAttribute("aria-busy", "true");
943
+ cta?.addEventListener("click", () => {
944
+ if (cta.disabled) return;
945
+ if (isUS && checkbox && !checkbox.checked) {
946
+ errorElement?.classList.add("apm__error--visible");
947
+ checkboxLabel?.classList.add("apm__checkbox-label--error");
948
+ return;
950
949
  }
951
- this.#recaptchaTokensPromise.then((tokens) => {
952
- this.#recaptchaTokens = tokens;
953
- if (cta) {
954
- cta.removeAttribute("disabled");
955
- cta.removeAttribute("aria-disabled");
956
- cta.removeAttribute("aria-busy");
957
- }
958
- }).catch(() => {
959
- if (cta) {
960
- cta.removeAttribute("aria-busy");
961
- }
950
+ errorElement?.classList.remove("apm__error--visible");
951
+ this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
952
+ cta.disabled = true;
953
+ cta.setAttribute("aria-busy", "true");
954
+ const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
955
+ if (!this.#isClosed && this.onConfirm) {
956
+ this.onConfirm(recaptchaTokensPromise);
957
+ this.close();
958
+ }
959
+ recaptchaTokensPromise.catch(() => {
960
+ if (this.#isClosed) return;
961
+ cta.disabled = false;
962
+ cta.removeAttribute("aria-busy");
962
963
  const existingError = this.querySelector("#apple-pay-modal-error");
963
964
  if (existingError) {
964
965
  const icon = existingError.querySelector("svg");
965
966
  existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
966
967
  if (icon) existingError.prepend(icon);
968
+ existingError.setAttribute("role", "alert");
967
969
  existingError.classList.add("apm__error--visible");
968
970
  } else {
969
971
  const errorNotice = document.createElement("div");
@@ -973,25 +975,14 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
973
975
  cta?.insertAdjacentElement("beforebegin", errorNotice);
974
976
  }
975
977
  });
976
- }
977
- cta?.addEventListener("click", () => {
978
- if (isUS && checkbox && !checkbox.checked) {
979
- errorElement?.classList.add("apm__error--visible");
980
- checkboxLabel?.classList.add("apm__checkbox-label--error");
981
- return;
982
- }
983
- if (this.#recaptchaTokens) {
984
- this.onConfirm?.(this.#recaptchaTokens);
985
- this.close();
986
- }
987
978
  });
988
979
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
989
980
  "click",
990
981
  () => this.close()
991
982
  );
992
983
  }
993
- open(recaptchaTokensPromise) {
994
- this.#recaptchaTokensPromise = recaptchaTokensPromise ?? getPurchaseRecaptchaTokens();
984
+ open() {
985
+ this.#isClosed = false;
995
986
  this.#triggerElement = document.activeElement;
996
987
  document.body.appendChild(this);
997
988
  this.#keyHandler = (event) => {
@@ -1006,6 +997,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
1006
997
  this.querySelector("#apple-pay-modal")?.focus();
1007
998
  }
1008
999
  close() {
1000
+ this.#isClosed = true;
1009
1001
  if (this.#keyHandler) {
1010
1002
  document.removeEventListener("keydown", this.#keyHandler, {
1011
1003
  capture: true
@@ -1120,7 +1112,7 @@ var isAppleDeviceOrSafari = () => {
1120
1112
  const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
1121
1113
  return isIOSOrMacTouchDevice || isSafariBrowser;
1122
1114
  };
1123
- var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
1115
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null;
1124
1116
  var isApplePayAvailable = async () => {
1125
1117
  if (bypassApplePayChecks()) {
1126
1118
  return true;
@@ -1418,7 +1410,10 @@ function startApplePaySession(config) {
1418
1410
  };
1419
1411
  session.onpaymentauthorized = (event) => {
1420
1412
  trackWalletPaymentAuthorised(config.skuId);
1421
- config.onPaymentAuthorized(event, session);
1413
+ config.onPaymentAuthorized(event, session).catch(() => {
1414
+ session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
1415
+ redirectToFallbackCheckout(config);
1416
+ });
1422
1417
  };
1423
1418
  session.oncancel = () => {
1424
1419
  config.onCancel?.();
@@ -1969,7 +1964,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1969
1964
  #returnUrl = "";
1970
1965
  #supportedBillingCountries = [];
1971
1966
  #entryPoint = "";
1972
- #recaptchaTokensPromise = null;
1973
1967
  // ─── Public API ─────────────────────────────────────────────────────────────
1974
1968
  get offer() {
1975
1969
  return this.#offer;
@@ -2034,15 +2028,14 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2034
2028
  this.#paymentOptions = null;
2035
2029
  this.#returnUrl = "";
2036
2030
  this.#supportedBillingCountries = [];
2037
- this.#recaptchaTokensPromise = null;
2038
2031
  this.querySelector(SEL_CONTAINER)?.remove();
2039
2032
  }
2040
2033
  // ─── Render pipeline ────────────────────────────────────────────────────────
2041
2034
  async #run() {
2042
2035
  try {
2036
+ this.#renderTemplate();
2043
2037
  if (await this.#checkAvailability()) return;
2044
2038
  if (this.#checkOffer()) return;
2045
- this.#renderTemplate();
2046
2039
  if (await this.#checkSession()) return;
2047
2040
  this.#captureReturnUrl();
2048
2041
  this.#computeBillingCountries();
@@ -2060,6 +2053,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2060
2053
  const available = await isApplePayAvailable();
2061
2054
  if (!available) {
2062
2055
  this.style.display = "none";
2056
+ this.querySelector(SEL_CONTAINER)?.remove();
2063
2057
  return true;
2064
2058
  }
2065
2059
  return false;
@@ -2081,6 +2075,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2081
2075
  #checkOffer() {
2082
2076
  if (!this.#offer) {
2083
2077
  this.style.display = "none";
2078
+ this.querySelector(SEL_CONTAINER)?.remove();
2084
2079
  return true;
2085
2080
  }
2086
2081
  return false;
@@ -2135,7 +2130,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2135
2130
  if (button) {
2136
2131
  button.classList.remove("wap-loading");
2137
2132
  this.#updateDisabledState();
2138
- this.#scheduleRecaptchaTokens();
2139
2133
  trackExpressWalletImpression(this.#offer.sku_id, {
2140
2134
  entryPoint: this.#entryPoint,
2141
2135
  country: this.#country
@@ -2172,27 +2166,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2172
2166
  }
2173
2167
  const skuId = this.#offer.sku_id;
2174
2168
  const country = this.#country;
2175
- modal.onConfirm = (recaptchaTokens) => {
2176
- this.#initiateApplePay(skuId, country, recaptchaTokens);
2177
- this.#scheduleRecaptchaTokens();
2169
+ modal.onConfirm = (recaptchaTokensPromise) => {
2170
+ this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
2178
2171
  };
2179
- modal.open(this.#recaptchaTokensPromise);
2180
- }
2181
- /**
2182
- * Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
2183
- * If the fetch rejects, the field is cleared so the next `modal.open()` call
2184
- * falls back to a new attempt via the modal's own null-fallback path — preventing
2185
- * a single transient failure from permanently disabling the CTA on all retries.
2186
- * The identity guard prevents a late rejection from clobbering a newer attempt.
2187
- */
2188
- #scheduleRecaptchaTokens() {
2189
- const attempt = getPurchaseRecaptchaTokens();
2190
- attempt.catch(() => {
2191
- if (this.#recaptchaTokensPromise === attempt) {
2192
- this.#recaptchaTokensPromise = null;
2193
- }
2194
- });
2195
- this.#recaptchaTokensPromise = attempt;
2172
+ modal.open();
2196
2173
  }
2197
2174
  async #fetchSession() {
2198
2175
  try {
@@ -2213,7 +2190,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2213
2190
  return null;
2214
2191
  }
2215
2192
  }
2216
- #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokens) {
2193
+ #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
2217
2194
  const offer = this.#offer;
2218
2195
  const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
2219
2196
  return {
@@ -2225,30 +2202,41 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2225
2202
  skuId,
2226
2203
  returnUrl: this.#returnUrl,
2227
2204
  userLoggedIn: sessionData.loggedIn,
2228
- onPaymentAuthorized: (event, session) => handlePaymentAuthorized(event, session, sessionData, {
2229
- skuId,
2230
- country,
2231
- currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
2232
- returnUrl: this.#returnUrl,
2233
- supportedBillingCountries: this.#supportedBillingCountries,
2234
- recaptchaTokens
2235
- })
2205
+ onPaymentAuthorized: async (event, session) => {
2206
+ const recaptchaTokens = await recaptchaTokensPromise;
2207
+ return handlePaymentAuthorized(event, session, sessionData, {
2208
+ skuId,
2209
+ country,
2210
+ currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
2211
+ returnUrl: this.#returnUrl,
2212
+ supportedBillingCountries: this.#supportedBillingCountries,
2213
+ recaptchaTokens
2214
+ });
2215
+ }
2236
2216
  };
2237
2217
  }
2238
- #initiateApplePay(skuId, country, recaptchaTokens) {
2218
+ #initiateApplePay(skuId, country, recaptchaTokensPromise) {
2239
2219
  try {
2240
2220
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
2241
2221
  const paymentOptions = this.#paymentOptions;
2222
+ if (!paymentOptions) {
2223
+ return;
2224
+ }
2242
2225
  startApplePaySession(
2243
2226
  this.#buildSessionParams(
2244
2227
  skuId,
2245
2228
  country,
2246
2229
  sessionData,
2247
2230
  paymentOptions,
2248
- recaptchaTokens
2231
+ recaptchaTokensPromise
2249
2232
  )
2250
2233
  );
2251
2234
  } catch {
2235
+ redirectToFallbackCheckout({
2236
+ returnUrl: this.#returnUrl,
2237
+ skuId,
2238
+ countryCode: country
2239
+ });
2252
2240
  }
2253
2241
  }
2254
2242
  };
@@ -570,10 +570,9 @@ var loadRecaptchaScript = (siteKey) => {
570
570
  );
571
571
  if (existingScript) {
572
572
  existingScript.addEventListener("load", () => resolve());
573
- existingScript.addEventListener(
574
- "error",
575
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
576
- );
573
+ existingScript.addEventListener("error", () => {
574
+ reject(new Error("Failed to load existing reCAPTCHA script"));
575
+ });
577
576
  return;
578
577
  }
579
578
  const script = document.createElement("script");
@@ -581,7 +580,9 @@ var loadRecaptchaScript = (siteKey) => {
581
580
  script.async = true;
582
581
  script.defer = true;
583
582
  script.onload = () => resolve();
584
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
583
+ script.onerror = () => {
584
+ reject(new Error("Failed to load reCAPTCHA script"));
585
+ };
585
586
  document.head.appendChild(script);
586
587
  });
587
588
  };
@@ -830,17 +831,14 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
830
831
  #country = "";
831
832
  #triggerElement = null;
832
833
  #keyHandler = null;
833
- #recaptchaTokens = null;
834
- #recaptchaTokensPromise = null;
834
+ #isClosed = false;
835
835
  /**
836
- * Synchronous callback invoked directly from the CTA click handler once the
837
- * user confirms. The caller (ApplePayButton) must call startApplePaySession
838
- * inside this callback to preserve the browser's user-gesture chain — Safari
839
- * requires new ApplePaySession() to execute within the original click stack.
840
- *
841
- * reCAPTCHA tokens are guaranteed non-null here: the CTA remains disabled
842
- * until the token pre-fetch resolves, so this callback is never reached
843
- * without valid tokens.
836
+ * Callback invoked synchronously from the CTA click handler.
837
+ * Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
838
+ * by the time this is called. The caller (ApplePayButton) must call
839
+ * new ApplePaySession() synchronously inside this callback to stay within
840
+ * Safari's user-gesture window. The token promise resolves before
841
+ * onPaymentAuthorized fires (user still needs to authenticate on the sheet).
844
842
  */
845
843
  onConfirm = null;
846
844
  set offer(value) {
@@ -937,28 +935,32 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
937
935
  }
938
936
  });
939
937
  }
940
- if (this.#recaptchaTokensPromise) {
941
- if (cta) {
942
- cta.setAttribute("disabled", "");
943
- cta.setAttribute("aria-disabled", "true");
944
- cta.setAttribute("aria-busy", "true");
938
+ cta?.addEventListener("click", () => {
939
+ if (cta.disabled) return;
940
+ if (isUS && checkbox && !checkbox.checked) {
941
+ errorElement?.classList.add("apm__error--visible");
942
+ checkboxLabel?.classList.add("apm__checkbox-label--error");
943
+ return;
945
944
  }
946
- this.#recaptchaTokensPromise.then((tokens) => {
947
- this.#recaptchaTokens = tokens;
948
- if (cta) {
949
- cta.removeAttribute("disabled");
950
- cta.removeAttribute("aria-disabled");
951
- cta.removeAttribute("aria-busy");
952
- }
953
- }).catch(() => {
954
- if (cta) {
955
- cta.removeAttribute("aria-busy");
956
- }
945
+ errorElement?.classList.remove("apm__error--visible");
946
+ this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
947
+ cta.disabled = true;
948
+ cta.setAttribute("aria-busy", "true");
949
+ const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
950
+ if (!this.#isClosed && this.onConfirm) {
951
+ this.onConfirm(recaptchaTokensPromise);
952
+ this.close();
953
+ }
954
+ recaptchaTokensPromise.catch(() => {
955
+ if (this.#isClosed) return;
956
+ cta.disabled = false;
957
+ cta.removeAttribute("aria-busy");
957
958
  const existingError = this.querySelector("#apple-pay-modal-error");
958
959
  if (existingError) {
959
960
  const icon = existingError.querySelector("svg");
960
961
  existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
961
962
  if (icon) existingError.prepend(icon);
963
+ existingError.setAttribute("role", "alert");
962
964
  existingError.classList.add("apm__error--visible");
963
965
  } else {
964
966
  const errorNotice = document.createElement("div");
@@ -968,25 +970,14 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
968
970
  cta?.insertAdjacentElement("beforebegin", errorNotice);
969
971
  }
970
972
  });
971
- }
972
- cta?.addEventListener("click", () => {
973
- if (isUS && checkbox && !checkbox.checked) {
974
- errorElement?.classList.add("apm__error--visible");
975
- checkboxLabel?.classList.add("apm__checkbox-label--error");
976
- return;
977
- }
978
- if (this.#recaptchaTokens) {
979
- this.onConfirm?.(this.#recaptchaTokens);
980
- this.close();
981
- }
982
973
  });
983
974
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
984
975
  "click",
985
976
  () => this.close()
986
977
  );
987
978
  }
988
- open(recaptchaTokensPromise) {
989
- this.#recaptchaTokensPromise = recaptchaTokensPromise ?? getPurchaseRecaptchaTokens();
979
+ open() {
980
+ this.#isClosed = false;
990
981
  this.#triggerElement = document.activeElement;
991
982
  document.body.appendChild(this);
992
983
  this.#keyHandler = (event) => {
@@ -1001,6 +992,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
1001
992
  this.querySelector("#apple-pay-modal")?.focus();
1002
993
  }
1003
994
  close() {
995
+ this.#isClosed = true;
1004
996
  if (this.#keyHandler) {
1005
997
  document.removeEventListener("keydown", this.#keyHandler, {
1006
998
  capture: true
@@ -1115,7 +1107,7 @@ var isAppleDeviceOrSafari = () => {
1115
1107
  const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
1116
1108
  return isIOSOrMacTouchDevice || isSafariBrowser;
1117
1109
  };
1118
- var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
1110
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null;
1119
1111
  var isApplePayAvailable = async () => {
1120
1112
  if (bypassApplePayChecks()) {
1121
1113
  return true;
@@ -1413,7 +1405,10 @@ function startApplePaySession(config) {
1413
1405
  };
1414
1406
  session.onpaymentauthorized = (event) => {
1415
1407
  trackWalletPaymentAuthorised(config.skuId);
1416
- config.onPaymentAuthorized(event, session);
1408
+ config.onPaymentAuthorized(event, session).catch(() => {
1409
+ session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
1410
+ redirectToFallbackCheckout(config);
1411
+ });
1417
1412
  };
1418
1413
  session.oncancel = () => {
1419
1414
  config.onCancel?.();
@@ -1964,7 +1959,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1964
1959
  #returnUrl = "";
1965
1960
  #supportedBillingCountries = [];
1966
1961
  #entryPoint = "";
1967
- #recaptchaTokensPromise = null;
1968
1962
  // ─── Public API ─────────────────────────────────────────────────────────────
1969
1963
  get offer() {
1970
1964
  return this.#offer;
@@ -2029,15 +2023,14 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2029
2023
  this.#paymentOptions = null;
2030
2024
  this.#returnUrl = "";
2031
2025
  this.#supportedBillingCountries = [];
2032
- this.#recaptchaTokensPromise = null;
2033
2026
  this.querySelector(SEL_CONTAINER)?.remove();
2034
2027
  }
2035
2028
  // ─── Render pipeline ────────────────────────────────────────────────────────
2036
2029
  async #run() {
2037
2030
  try {
2031
+ this.#renderTemplate();
2038
2032
  if (await this.#checkAvailability()) return;
2039
2033
  if (this.#checkOffer()) return;
2040
- this.#renderTemplate();
2041
2034
  if (await this.#checkSession()) return;
2042
2035
  this.#captureReturnUrl();
2043
2036
  this.#computeBillingCountries();
@@ -2055,6 +2048,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2055
2048
  const available = await isApplePayAvailable();
2056
2049
  if (!available) {
2057
2050
  this.style.display = "none";
2051
+ this.querySelector(SEL_CONTAINER)?.remove();
2058
2052
  return true;
2059
2053
  }
2060
2054
  return false;
@@ -2076,6 +2070,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2076
2070
  #checkOffer() {
2077
2071
  if (!this.#offer) {
2078
2072
  this.style.display = "none";
2073
+ this.querySelector(SEL_CONTAINER)?.remove();
2079
2074
  return true;
2080
2075
  }
2081
2076
  return false;
@@ -2130,7 +2125,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2130
2125
  if (button) {
2131
2126
  button.classList.remove("wap-loading");
2132
2127
  this.#updateDisabledState();
2133
- this.#scheduleRecaptchaTokens();
2134
2128
  trackExpressWalletImpression(this.#offer.sku_id, {
2135
2129
  entryPoint: this.#entryPoint,
2136
2130
  country: this.#country
@@ -2167,27 +2161,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2167
2161
  }
2168
2162
  const skuId = this.#offer.sku_id;
2169
2163
  const country = this.#country;
2170
- modal.onConfirm = (recaptchaTokens) => {
2171
- this.#initiateApplePay(skuId, country, recaptchaTokens);
2172
- this.#scheduleRecaptchaTokens();
2164
+ modal.onConfirm = (recaptchaTokensPromise) => {
2165
+ this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
2173
2166
  };
2174
- modal.open(this.#recaptchaTokensPromise);
2175
- }
2176
- /**
2177
- * Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
2178
- * If the fetch rejects, the field is cleared so the next `modal.open()` call
2179
- * falls back to a new attempt via the modal's own null-fallback path — preventing
2180
- * a single transient failure from permanently disabling the CTA on all retries.
2181
- * The identity guard prevents a late rejection from clobbering a newer attempt.
2182
- */
2183
- #scheduleRecaptchaTokens() {
2184
- const attempt = getPurchaseRecaptchaTokens();
2185
- attempt.catch(() => {
2186
- if (this.#recaptchaTokensPromise === attempt) {
2187
- this.#recaptchaTokensPromise = null;
2188
- }
2189
- });
2190
- this.#recaptchaTokensPromise = attempt;
2167
+ modal.open();
2191
2168
  }
2192
2169
  async #fetchSession() {
2193
2170
  try {
@@ -2208,7 +2185,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2208
2185
  return null;
2209
2186
  }
2210
2187
  }
2211
- #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokens) {
2188
+ #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
2212
2189
  const offer = this.#offer;
2213
2190
  const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
2214
2191
  return {
@@ -2220,30 +2197,41 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2220
2197
  skuId,
2221
2198
  returnUrl: this.#returnUrl,
2222
2199
  userLoggedIn: sessionData.loggedIn,
2223
- onPaymentAuthorized: (event, session) => handlePaymentAuthorized(event, session, sessionData, {
2224
- skuId,
2225
- country,
2226
- currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
2227
- returnUrl: this.#returnUrl,
2228
- supportedBillingCountries: this.#supportedBillingCountries,
2229
- recaptchaTokens
2230
- })
2200
+ onPaymentAuthorized: async (event, session) => {
2201
+ const recaptchaTokens = await recaptchaTokensPromise;
2202
+ return handlePaymentAuthorized(event, session, sessionData, {
2203
+ skuId,
2204
+ country,
2205
+ currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
2206
+ returnUrl: this.#returnUrl,
2207
+ supportedBillingCountries: this.#supportedBillingCountries,
2208
+ recaptchaTokens
2209
+ });
2210
+ }
2231
2211
  };
2232
2212
  }
2233
- #initiateApplePay(skuId, country, recaptchaTokens) {
2213
+ #initiateApplePay(skuId, country, recaptchaTokensPromise) {
2234
2214
  try {
2235
2215
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
2236
2216
  const paymentOptions = this.#paymentOptions;
2217
+ if (!paymentOptions) {
2218
+ return;
2219
+ }
2237
2220
  startApplePaySession(
2238
2221
  this.#buildSessionParams(
2239
2222
  skuId,
2240
2223
  country,
2241
2224
  sessionData,
2242
2225
  paymentOptions,
2243
- recaptchaTokens
2226
+ recaptchaTokensPromise
2244
2227
  )
2245
2228
  );
2246
2229
  } catch {
2230
+ redirectToFallbackCheckout({
2231
+ returnUrl: this.#returnUrl,
2232
+ skuId,
2233
+ countryCode: country
2234
+ });
2247
2235
  }
2248
2236
  }
2249
2237
  };