@economist/web-apple-pay 0.1.3-beta.7 → 0.1.3-beta.9

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
@@ -78,6 +78,62 @@ function ensureApplePayLogoSymbol() {
78
78
  // src/templates/apple-pay-modal.template.html
79
79
  var apple_pay_modal_template_default = '<style>\n .apm-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 97;\n background: rgba(13, 13, 13, 0.7);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--wall-gutter, 2rem);\n box-sizing: border-box;\n }\n\n .apm {\n position: relative;\n background: #fff;\n border-radius: 0.5rem 0.5rem 0 0;\n border: 0.0625rem solid #d9d9d9;\n padding: var(--mb-spacing-lg, 1.5rem);\n width: 25rem;\n max-width: 25rem;\n box-sizing: border-box;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n box-shadow: 0 0.25rem 1rem 0 rgba(13, 13, 13, 0.15);\n }\n\n .apm__close {\n position: absolute;\n top: 1.5rem;\n right: 1.3125rem;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .apm__close:focus {\n outline: solid 0.125rem var(--focus-border-color, #121212);\n border-radius: 0.25rem;\n }\n\n .apm__offer-name {\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-title-3-size, 1.25rem);\n font-weight: 500;\n color: var(--mb-colour-greyscale-london-5, #0d0d0d);\n line-height: var(--mb-typestyle-title-3-leading, 1.75rem);\n font-style: normal;\n margin: 0 1.5rem 0.5rem 0;\n }\n\n .apm__due-today {\n color: #1a1a1a;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n font-style: normal;\n font-weight: 400;\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n margin: 0 0 0.75rem 0;\n }\n\n .apm__rule {\n border: none;\n border-top: 0.0625rem solid #d9d9d9;\n margin: 0 0 1rem 0;\n }\n\n .apm__terms {\n color: #1a1a1a;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n font-style: normal;\n font-weight: 400;\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n margin: 0 0 1rem 0;\n }\n\n .apm__terms-bullets {\n list-style: disc outside;\n padding-left: 1.25rem;\n margin: 0 0 0.75rem 0;\n }\n\n .apm__terms-bullets li {\n margin-bottom: 0.375rem;\n }\n\n .apm__terms-legal {\n margin: 0;\n }\n\n .apm__passive-terms {\n font-family: var(--wall-type-system-sans);\n font-size: 0.9375rem;\n color: #0d0d0d;\n line-height: 1.4;\n margin: 0 0 8px 0;\n }\n\n .apm__passive-terms a {\n color: #1a1a1a;\n font-weight: 700;\n text-decoration: underline;\n }\n\n .apm__checkbox-label {\n display: flex;\n align-items: flex-start;\n gap: var(--mb-spacing-sm, 0.5rem);\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n color: var(--mb-colour-greyscale-london-20, #1a1a1a);\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n cursor: pointer;\n margin: 0 0 1.5rem 0;\n }\n\n .apm__checkbox-label--error {\n margin-bottom: 0.25rem;\n }\n\n .apm__checkbox-wrapper {\n position: relative;\n flex-shrink: 0;\n width: 1.25rem;\n height: 1.25rem;\n margin-top: 0.0625rem;\n }\n\n .apm__checkbox-input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n cursor: pointer;\n margin: 0;\n z-index: 1;\n }\n\n .apm__checkbox-custom {\n position: absolute;\n top: 0;\n left: 0;\n width: 1.25rem;\n height: 1.25rem;\n pointer-events: none;\n }\n\n .apm__checkbox-custom svg {\n display: none;\n width: 1.25rem;\n height: 1.25rem;\n }\n\n .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-unchecked {\n display: block;\n }\n\n .apm__checkbox-input:checked ~ .apm__checkbox-custom .svg-checked {\n display: block;\n }\n\n .apm__checkbox-label--error\n .apm__checkbox-input:not(:checked)\n ~ .apm__checkbox-custom\n .svg-unchecked {\n display: none;\n }\n\n .apm__checkbox-label--error\n .apm__checkbox-input:not(:checked)\n ~ .apm__checkbox-custom\n .svg-error {\n display: block;\n }\n\n .apm__checkbox-input:focus-visible ~ .apm__checkbox-custom {\n outline: solid 0.125rem #c91d42;\n outline-offset: 0.125rem;\n border-radius: 0.25rem;\n }\n\n .apm__checkbox-label a {\n color: #1a1a1a;\n font-weight: 700;\n text-decoration: underline;\n }\n\n .apm__error {\n display: none;\n align-items: center;\n gap: 0.75rem;\n color: var(--mb-colour-greyscale-london-5, #0d0d0d);\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: 1.0625rem;\n font-style: normal;\n font-weight: 500;\n line-height: 1.5rem;\n margin-bottom: 1rem;\n }\n\n .apm__error--visible {\n display: flex;\n }\n\n .apm__cta {\n display: flex;\n width: 100%;\n height: 2.75rem;\n padding: 0.375rem 1rem;\n justify-content: center;\n align-items: center;\n gap: 0.375rem;\n border-radius: 0.25rem;\n background: #000;\n border: none;\n cursor: pointer;\n color: #fff;\n text-align: center;\n font-family: var(\n --mb-typeface-sans,\n var(--wall-type-system-sans, system-ui, sans-serif)\n );\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 500;\n line-height: normal;\n }\n\n .apm__cta svg {\n flex-shrink: 0;\n display: block;\n }\n\n @media (min-width: 22.5rem) and (max-width: 37.5rem) {\n .apm {\n width: 24.375rem;\n max-width: 24.375rem;\n }\n }\n\n @media (min-width: 37.5625rem) {\n .apm {\n border-radius: 0.5rem;\n }\n }\n\n @media (max-width: 22.4375rem) {\n .apm {\n width: 20rem;\n max-width: 20rem;\n }\n }\n\n @media (max-width: 26.1875rem) {\n .apm__cta {\n font-size: 1.125rem;\n }\n }\n</style>\n\n<div class="apm-overlay" id="apple-pay-modal-overlay">\n <div\n class="apm"\n role="dialog"\n aria-modal="true"\n aria-labelledby="apple-pay-modal-offer-name"\n id="apple-pay-modal"\n tabindex="-1"\n >\n <button class="apm__close" id="apple-pay-modal-close" aria-label="Close">\n <svg\n aria-hidden="true"\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n >\n <g clip-path="url(#clip0_15712_93054)">\n <rect width="24" height="24" fill="white" fill-opacity="0.01" />\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M0 0H24V24H0V0Z"\n fill="white"\n fill-opacity="0.01"\n />\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M18.75 6.60964L17.3904 5.25L12 10.6404L6.60964 5.25L5.25 6.60964L10.6404 12L5.25 17.3904L6.60964 18.75L12 13.3596L17.3904 18.75L18.75 17.3904L13.3596 12L18.75 6.60964Z"\n fill="#333333"\n />\n </g>\n <defs>\n <clipPath id="clip0_15712_93054">\n <rect width="24" height="24" fill="white" />\n </clipPath>\n </defs>\n </svg>\n </button>\n <p class="apm__offer-name" id="apple-pay-modal-offer-name"></p>\n <p class="apm__due-today" id="apple-pay-modal-due-today"></p>\n <hr class="apm__rule" />\n <div class="apm__terms" id="apple-pay-modal-terms"></div>\n <p class="apm__passive-terms" id="apple-pay-modal-passive-terms"></p>\n <label\n class="apm__checkbox-label"\n id="apple-pay-modal-checkbox-label"\n for="apple-pay-modal-checkbox"\n >\n <div class="apm__checkbox-wrapper">\n <input\n class="apm__checkbox-input"\n type="checkbox"\n id="apple-pay-modal-checkbox"\n />\n <span class="apm__checkbox-custom">\n <svg\n aria-hidden="true"\n class="svg-unchecked"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M6 2.5H18C19.933 2.5 21.5 4.067 21.5 6V18C21.5 19.933 19.933 21.5 18 21.5H6C4.067 21.5 2.5 19.933 2.5 18V6C2.5 4.067 4.067 2.5 6 2.5Z"\n fill="white"\n stroke="#595959"\n />\n </svg>\n <svg\n aria-hidden="true"\n class="svg-checked"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <g clip-path="url(#clip0_20014_9555)">\n <path\n d="M2 6C2 3.79086 3.79086 2 6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6Z"\n fill="#333333"\n />\n <path\n d="M10.2857 17L6 12.6814L7.20857 11.4635L10.2857 14.5557L16.7914 8L18 9.22649L10.2857 17Z"\n fill="white"\n />\n </g>\n <defs>\n <clipPath id="clip0_20014_9555">\n <path\n d="M2 6C2 3.79086 3.79086 2 6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6Z"\n fill="white"\n />\n </clipPath>\n </defs>\n </svg>\n <svg\n aria-hidden="true"\n class="svg-error"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M6 2.5H18C19.933 2.5 21.5 4.067 21.5 6V18C21.5 19.933 19.933 21.5 18 21.5H6C4.067 21.5 2.5 19.933 2.5 18V6C2.5 4.067 4.067 2.5 6 2.5Z"\n fill="white"\n stroke="#C91D42"\n stroke-width="2"\n />\n </svg>\n </span>\n </div>\n <span id="apple-pay-modal-checkbox-text"></span>\n </label>\n <div class="apm__error" id="apple-pay-modal-error" role="alert">\n <svg\n aria-hidden="true"\n width="20"\n height="20"\n viewBox="0 0 20 20"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2ZM0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10ZM11 13C11 13.5523 10.5523 14 10 14C9.44772 14 9 13.5523 9 13C9 12.4477 9.44772 12 10 12C10.5523 12 11 12.4477 11 13ZM10 6C10.5523 6 11 6.44772 11 7V10C11 10.5523 10.5523 11 10 11C9.44772 11 9 10.5523 9 10V7C9 6.44772 9.44772 6 10 6Z"\n fill="#C91D42"\n />\n </svg>\n Please accept the terms to continue\n </div>\n <button class="apm__cta" id="apple-pay-modal-cta">\n Continue with\n <svg width="51" height="22" aria-label="Apple Pay" role="img">\n <use href="#apple-pay-logo" />\n </svg>\n </button>\n </div>\n</div>\n\n';
80
80
 
81
+ // src/clients/payment-checkout/config.ts
82
+ var runtimeConfig = null;
83
+ var configureApplePay = (config) => {
84
+ runtimeConfig = {
85
+ ...config
86
+ };
87
+ };
88
+ var getApplePayConfig = () => runtimeConfig;
89
+
90
+ // src/utils/recaptcha.ts
91
+ var loadRecaptchaScript = (siteKey) => {
92
+ return new Promise((resolve, reject) => {
93
+ if (typeof grecaptcha !== "undefined") return resolve();
94
+ const existingScript = document.querySelector(
95
+ 'script[src*="recaptcha/api.js"]'
96
+ );
97
+ if (existingScript) {
98
+ existingScript.addEventListener("load", () => resolve());
99
+ existingScript.addEventListener(
100
+ "error",
101
+ () => reject(new Error("Failed to load existing reCAPTCHA script"))
102
+ );
103
+ return;
104
+ }
105
+ const script = document.createElement("script");
106
+ script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
107
+ script.async = true;
108
+ script.defer = true;
109
+ script.onload = () => resolve();
110
+ script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
111
+ document.head.appendChild(script);
112
+ });
113
+ };
114
+ var getRecaptchaToken = async (action) => {
115
+ const siteKey = getApplePayConfig()?.recaptchaSiteKey;
116
+ if (!siteKey) {
117
+ throw new Error(
118
+ "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
119
+ );
120
+ }
121
+ await loadRecaptchaScript(siteKey);
122
+ return new Promise((resolve, reject) => {
123
+ grecaptcha.ready(() => {
124
+ grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
125
+ });
126
+ });
127
+ };
128
+ var getPurchaseRecaptchaTokens = async () => {
129
+ const [checkEligibility, newBasket, registerUser] = await Promise.all([
130
+ getRecaptchaToken("checkEligibility"),
131
+ getRecaptchaToken("newBasket"),
132
+ getRecaptchaToken("registerUser")
133
+ ]);
134
+ return { checkEligibility, newBasket, registerUser };
135
+ };
136
+
81
137
  // src/apple-pay-modal.ts
82
138
  function getRenewalDateText(unit, duration) {
83
139
  const now = /* @__PURE__ */ new Date();
@@ -297,6 +353,8 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
297
353
  #country = "";
298
354
  #triggerElement = null;
299
355
  #keyHandler = null;
356
+ #recaptchaTokens = null;
357
+ #recaptchaTokensPromise = null;
300
358
  set offer(value) {
301
359
  this.#offer = value;
302
360
  }
@@ -391,6 +449,23 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
391
449
  }
392
450
  });
393
451
  }
452
+ if (this.#recaptchaTokensPromise) {
453
+ if (cta) {
454
+ cta.setAttribute("disabled", "");
455
+ cta.setAttribute("aria-disabled", "true");
456
+ cta.setAttribute("aria-busy", "true");
457
+ }
458
+ this.#recaptchaTokensPromise.then((tokens) => {
459
+ this.#recaptchaTokens = tokens;
460
+ if (cta) {
461
+ cta.removeAttribute("disabled");
462
+ cta.removeAttribute("aria-disabled");
463
+ cta.removeAttribute("aria-busy");
464
+ }
465
+ }).catch(() => {
466
+ console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
467
+ });
468
+ }
394
469
  cta?.addEventListener("click", () => {
395
470
  if (isUS && checkbox && !checkbox.checked) {
396
471
  errorElement?.classList.add("apm__error--visible");
@@ -401,7 +476,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
401
476
  new CustomEvent("apple-pay-confirmed", {
402
477
  bubbles: true,
403
478
  composed: true,
404
- detail: { skuId: offer.sku_id, country }
479
+ detail: { skuId: offer.sku_id, country, recaptchaTokens: this.#recaptchaTokens }
405
480
  })
406
481
  );
407
482
  this.close();
@@ -412,6 +487,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
412
487
  );
413
488
  }
414
489
  open() {
490
+ this.#recaptchaTokensPromise = getPurchaseRecaptchaTokens();
415
491
  this.#triggerElement = document.activeElement;
416
492
  document.body.appendChild(this);
417
493
  this.#keyHandler = (event) => {
@@ -524,15 +600,6 @@ var apple_pay_button_template_default = `<style>
524
600
  </div>
525
601
  `;
526
602
 
527
- // src/clients/payment-checkout/config.ts
528
- var runtimeConfig = null;
529
- var configureApplePay = (config) => {
530
- runtimeConfig = {
531
- ...config
532
- };
533
- };
534
- var getApplePayConfig = () => runtimeConfig;
535
-
536
603
  // src/clients/payment-checkout/detects.ts
537
604
  var isAppleDeviceOrSafari = () => {
538
605
  const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
@@ -702,53 +769,6 @@ var redirectToCheckoutFallback = (params) => {
702
769
  return url.pathname + url.search;
703
770
  };
704
771
 
705
- // src/utils/recaptcha.ts
706
- var loadRecaptchaScript = (siteKey) => {
707
- return new Promise((resolve, reject) => {
708
- if (typeof grecaptcha !== "undefined") return resolve();
709
- const existingScript = document.querySelector(
710
- 'script[src*="recaptcha/api.js"]'
711
- );
712
- if (existingScript) {
713
- existingScript.addEventListener("load", () => resolve());
714
- existingScript.addEventListener(
715
- "error",
716
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
717
- );
718
- return;
719
- }
720
- const script = document.createElement("script");
721
- script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
722
- script.async = true;
723
- script.defer = true;
724
- script.onload = () => resolve();
725
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
726
- document.head.appendChild(script);
727
- });
728
- };
729
- var getRecaptchaToken = async (action) => {
730
- const siteKey = getApplePayConfig()?.recaptchaSiteKey;
731
- if (!siteKey) {
732
- throw new Error(
733
- "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
734
- );
735
- }
736
- await loadRecaptchaScript(siteKey);
737
- return new Promise((resolve, reject) => {
738
- grecaptcha.ready(() => {
739
- grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
740
- });
741
- });
742
- };
743
- var getPurchaseRecaptchaTokens = async () => {
744
- const [checkEligibility, newBasket, registerUser] = await Promise.all([
745
- getRecaptchaToken("checkEligibility"),
746
- getRecaptchaToken("newBasket"),
747
- getRecaptchaToken("registerUser")
748
- ]);
749
- return { checkEligibility, newBasket, registerUser };
750
- };
751
-
752
772
  // src/utils/expressCheckoutTracking.ts
753
773
  var EXPRESS_CHECKOUT_EVENTS = {
754
774
  /** Wallet button displayed */
@@ -1451,7 +1471,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1451
1471
  #returnUrl = "";
1452
1472
  #supportedBillingCountries = [];
1453
1473
  #entryPoint = "";
1454
- #recaptchaTokens = null;
1474
+ #applePayConfirmedHandler = null;
1455
1475
  // ─── Public API ─────────────────────────────────────────────────────────────
1456
1476
  get offer() {
1457
1477
  return this.#offer;
@@ -1516,6 +1536,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1516
1536
  this.#paymentOptions = null;
1517
1537
  this.#returnUrl = "";
1518
1538
  this.#supportedBillingCountries = [];
1539
+ if (this.#applePayConfirmedHandler) {
1540
+ document.removeEventListener("apple-pay-confirmed", this.#applePayConfirmedHandler);
1541
+ this.#applePayConfirmedHandler = null;
1542
+ }
1519
1543
  this.querySelector(SEL_CONTAINER)?.remove();
1520
1544
  }
1521
1545
  // ─── Render pipeline ────────────────────────────────────────────────────────
@@ -1623,10 +1647,12 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1623
1647
  if (button.classList.contains("wap-loading")) return;
1624
1648
  this.#openModal();
1625
1649
  });
1626
- document.addEventListener("apple-pay-confirmed", (event) => {
1627
- const { skuId, country } = event.detail;
1628
- this.#initiateApplePay(skuId, country);
1629
- }, { once: true });
1650
+ this.#applePayConfirmedHandler = (event) => {
1651
+ const { skuId, country, recaptchaTokens } = event.detail;
1652
+ this.#initiateApplePay(skuId, country, recaptchaTokens);
1653
+ this.#applePayConfirmedHandler = null;
1654
+ };
1655
+ document.addEventListener("apple-pay-confirmed", this.#applePayConfirmedHandler, { once: true });
1630
1656
  }
1631
1657
  #finishLoading() {
1632
1658
  console.debug("[web-apple-pay] #finishLoading: all checks passed, enabling button");
@@ -1661,10 +1687,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1661
1687
  return;
1662
1688
  }
1663
1689
  trackExpressWalletClick(this.#offer.sku_id);
1664
- getPurchaseRecaptchaTokens().then((tokens) => {
1665
- this.#recaptchaTokens = tokens;
1666
- }).catch(() => {
1667
- });
1668
1690
  ApplePayModal.define();
1669
1691
  const modal = document.createElement(ApplePayModal.tag);
1670
1692
  modal.offer = this.#offer;
@@ -1716,11 +1738,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1716
1738
  })
1717
1739
  };
1718
1740
  }
1719
- async #initiateApplePay(skuId, country) {
1741
+ #initiateApplePay(skuId, country, recaptchaTokens) {
1720
1742
  try {
1721
1743
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1722
1744
  const paymentOptions = this.#paymentOptions;
1723
- const recaptchaTokens = this.#recaptchaTokens ?? await getPurchaseRecaptchaTokens();
1724
1745
  startApplePaySession(
1725
1746
  this.#buildSessionParams(
1726
1747
  skuId,
@@ -78,6 +78,57 @@ function ensureApplePayLogoSymbol() {
78
78
  // src/templates/apple-pay-modal.template.html
79
79
  var apple_pay_modal_template_default = '<style>\n .apm-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 97;\n background: rgba(13, 13, 13, 0.7);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--wall-gutter, 2rem);\n box-sizing: border-box;\n }\n\n .apm {\n position: relative;\n background: #fff;\n border-radius: 0.5rem 0.5rem 0 0;\n border: 0.0625rem solid #d9d9d9;\n padding: var(--mb-spacing-lg, 1.5rem);\n width: 25rem;\n max-width: 25rem;\n box-sizing: border-box;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n box-shadow: 0 0.25rem 1rem 0 rgba(13, 13, 13, 0.15);\n }\n\n .apm__close {\n position: absolute;\n top: 1.5rem;\n right: 1.3125rem;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .apm__close:focus {\n outline: solid 0.125rem var(--focus-border-color, #121212);\n border-radius: 0.25rem;\n }\n\n .apm__offer-name {\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-title-3-size, 1.25rem);\n font-weight: 500;\n color: var(--mb-colour-greyscale-london-5, #0d0d0d);\n line-height: var(--mb-typestyle-title-3-leading, 1.75rem);\n font-style: normal;\n margin: 0 1.5rem 0.5rem 0;\n }\n\n .apm__due-today {\n color: #1a1a1a;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n font-style: normal;\n font-weight: 400;\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n margin: 0 0 0.75rem 0;\n }\n\n .apm__rule {\n border: none;\n border-top: 0.0625rem solid #d9d9d9;\n margin: 0 0 1rem 0;\n }\n\n .apm__terms {\n color: #1a1a1a;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n font-style: normal;\n font-weight: 400;\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n margin: 0 0 1rem 0;\n }\n\n .apm__terms-bullets {\n list-style: disc outside;\n padding-left: 1.25rem;\n margin: 0 0 0.75rem 0;\n }\n\n .apm__terms-bullets li {\n margin-bottom: 0.375rem;\n }\n\n .apm__terms-legal {\n margin: 0;\n }\n\n .apm__passive-terms {\n font-family: var(--wall-type-system-sans);\n font-size: 0.9375rem;\n color: #0d0d0d;\n line-height: 1.4;\n margin: 0 0 8px 0;\n }\n\n .apm__passive-terms a {\n color: #1a1a1a;\n font-weight: 700;\n text-decoration: underline;\n }\n\n .apm__checkbox-label {\n display: flex;\n align-items: flex-start;\n gap: var(--mb-spacing-sm, 0.5rem);\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n color: var(--mb-colour-greyscale-london-20, #1a1a1a);\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n cursor: pointer;\n margin: 0 0 1.5rem 0;\n }\n\n .apm__checkbox-label--error {\n margin-bottom: 0.25rem;\n }\n\n .apm__checkbox-wrapper {\n position: relative;\n flex-shrink: 0;\n width: 1.25rem;\n height: 1.25rem;\n margin-top: 0.0625rem;\n }\n\n .apm__checkbox-input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n cursor: pointer;\n margin: 0;\n z-index: 1;\n }\n\n .apm__checkbox-custom {\n position: absolute;\n top: 0;\n left: 0;\n width: 1.25rem;\n height: 1.25rem;\n pointer-events: none;\n }\n\n .apm__checkbox-custom svg {\n display: none;\n width: 1.25rem;\n height: 1.25rem;\n }\n\n .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-unchecked {\n display: block;\n }\n\n .apm__checkbox-input:checked ~ .apm__checkbox-custom .svg-checked {\n display: block;\n }\n\n .apm__checkbox-label--error\n .apm__checkbox-input:not(:checked)\n ~ .apm__checkbox-custom\n .svg-unchecked {\n display: none;\n }\n\n .apm__checkbox-label--error\n .apm__checkbox-input:not(:checked)\n ~ .apm__checkbox-custom\n .svg-error {\n display: block;\n }\n\n .apm__checkbox-input:focus-visible ~ .apm__checkbox-custom {\n outline: solid 0.125rem #c91d42;\n outline-offset: 0.125rem;\n border-radius: 0.25rem;\n }\n\n .apm__checkbox-label a {\n color: #1a1a1a;\n font-weight: 700;\n text-decoration: underline;\n }\n\n .apm__error {\n display: none;\n align-items: center;\n gap: 0.75rem;\n color: var(--mb-colour-greyscale-london-5, #0d0d0d);\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: 1.0625rem;\n font-style: normal;\n font-weight: 500;\n line-height: 1.5rem;\n margin-bottom: 1rem;\n }\n\n .apm__error--visible {\n display: flex;\n }\n\n .apm__cta {\n display: flex;\n width: 100%;\n height: 2.75rem;\n padding: 0.375rem 1rem;\n justify-content: center;\n align-items: center;\n gap: 0.375rem;\n border-radius: 0.25rem;\n background: #000;\n border: none;\n cursor: pointer;\n color: #fff;\n text-align: center;\n font-family: var(\n --mb-typeface-sans,\n var(--wall-type-system-sans, system-ui, sans-serif)\n );\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 500;\n line-height: normal;\n }\n\n .apm__cta svg {\n flex-shrink: 0;\n display: block;\n }\n\n @media (min-width: 22.5rem) and (max-width: 37.5rem) {\n .apm {\n width: 24.375rem;\n max-width: 24.375rem;\n }\n }\n\n @media (min-width: 37.5625rem) {\n .apm {\n border-radius: 0.5rem;\n }\n }\n\n @media (max-width: 22.4375rem) {\n .apm {\n width: 20rem;\n max-width: 20rem;\n }\n }\n\n @media (max-width: 26.1875rem) {\n .apm__cta {\n font-size: 1.125rem;\n }\n }\n</style>\n\n<div class="apm-overlay" id="apple-pay-modal-overlay">\n <div\n class="apm"\n role="dialog"\n aria-modal="true"\n aria-labelledby="apple-pay-modal-offer-name"\n id="apple-pay-modal"\n tabindex="-1"\n >\n <button class="apm__close" id="apple-pay-modal-close" aria-label="Close">\n <svg\n aria-hidden="true"\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n >\n <g clip-path="url(#clip0_15712_93054)">\n <rect width="24" height="24" fill="white" fill-opacity="0.01" />\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M0 0H24V24H0V0Z"\n fill="white"\n fill-opacity="0.01"\n />\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M18.75 6.60964L17.3904 5.25L12 10.6404L6.60964 5.25L5.25 6.60964L10.6404 12L5.25 17.3904L6.60964 18.75L12 13.3596L17.3904 18.75L18.75 17.3904L13.3596 12L18.75 6.60964Z"\n fill="#333333"\n />\n </g>\n <defs>\n <clipPath id="clip0_15712_93054">\n <rect width="24" height="24" fill="white" />\n </clipPath>\n </defs>\n </svg>\n </button>\n <p class="apm__offer-name" id="apple-pay-modal-offer-name"></p>\n <p class="apm__due-today" id="apple-pay-modal-due-today"></p>\n <hr class="apm__rule" />\n <div class="apm__terms" id="apple-pay-modal-terms"></div>\n <p class="apm__passive-terms" id="apple-pay-modal-passive-terms"></p>\n <label\n class="apm__checkbox-label"\n id="apple-pay-modal-checkbox-label"\n for="apple-pay-modal-checkbox"\n >\n <div class="apm__checkbox-wrapper">\n <input\n class="apm__checkbox-input"\n type="checkbox"\n id="apple-pay-modal-checkbox"\n />\n <span class="apm__checkbox-custom">\n <svg\n aria-hidden="true"\n class="svg-unchecked"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M6 2.5H18C19.933 2.5 21.5 4.067 21.5 6V18C21.5 19.933 19.933 21.5 18 21.5H6C4.067 21.5 2.5 19.933 2.5 18V6C2.5 4.067 4.067 2.5 6 2.5Z"\n fill="white"\n stroke="#595959"\n />\n </svg>\n <svg\n aria-hidden="true"\n class="svg-checked"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <g clip-path="url(#clip0_20014_9555)">\n <path\n d="M2 6C2 3.79086 3.79086 2 6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6Z"\n fill="#333333"\n />\n <path\n d="M10.2857 17L6 12.6814L7.20857 11.4635L10.2857 14.5557L16.7914 8L18 9.22649L10.2857 17Z"\n fill="white"\n />\n </g>\n <defs>\n <clipPath id="clip0_20014_9555">\n <path\n d="M2 6C2 3.79086 3.79086 2 6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6Z"\n fill="white"\n />\n </clipPath>\n </defs>\n </svg>\n <svg\n aria-hidden="true"\n class="svg-error"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M6 2.5H18C19.933 2.5 21.5 4.067 21.5 6V18C21.5 19.933 19.933 21.5 18 21.5H6C4.067 21.5 2.5 19.933 2.5 18V6C2.5 4.067 4.067 2.5 6 2.5Z"\n fill="white"\n stroke="#C91D42"\n stroke-width="2"\n />\n </svg>\n </span>\n </div>\n <span id="apple-pay-modal-checkbox-text"></span>\n </label>\n <div class="apm__error" id="apple-pay-modal-error" role="alert">\n <svg\n aria-hidden="true"\n width="20"\n height="20"\n viewBox="0 0 20 20"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2ZM0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10ZM11 13C11 13.5523 10.5523 14 10 14C9.44772 14 9 13.5523 9 13C9 12.4477 9.44772 12 10 12C10.5523 12 11 12.4477 11 13ZM10 6C10.5523 6 11 6.44772 11 7V10C11 10.5523 10.5523 11 10 11C9.44772 11 9 10.5523 9 10V7C9 6.44772 9.44772 6 10 6Z"\n fill="#C91D42"\n />\n </svg>\n Please accept the terms to continue\n </div>\n <button class="apm__cta" id="apple-pay-modal-cta">\n Continue with\n <svg width="51" height="22" aria-label="Apple Pay" role="img">\n <use href="#apple-pay-logo" />\n </svg>\n </button>\n </div>\n</div>\n\n';
80
80
 
81
+ // src/clients/payment-checkout/config.ts
82
+ var runtimeConfig = null;
83
+ var getApplePayConfig = () => runtimeConfig;
84
+
85
+ // src/utils/recaptcha.ts
86
+ var loadRecaptchaScript = (siteKey) => {
87
+ return new Promise((resolve, reject) => {
88
+ if (typeof grecaptcha !== "undefined") return resolve();
89
+ const existingScript = document.querySelector(
90
+ 'script[src*="recaptcha/api.js"]'
91
+ );
92
+ if (existingScript) {
93
+ existingScript.addEventListener("load", () => resolve());
94
+ existingScript.addEventListener(
95
+ "error",
96
+ () => reject(new Error("Failed to load existing reCAPTCHA script"))
97
+ );
98
+ return;
99
+ }
100
+ const script = document.createElement("script");
101
+ script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
102
+ script.async = true;
103
+ script.defer = true;
104
+ script.onload = () => resolve();
105
+ script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
106
+ document.head.appendChild(script);
107
+ });
108
+ };
109
+ var getRecaptchaToken = async (action) => {
110
+ const siteKey = getApplePayConfig()?.recaptchaSiteKey;
111
+ if (!siteKey) {
112
+ throw new Error(
113
+ "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
114
+ );
115
+ }
116
+ await loadRecaptchaScript(siteKey);
117
+ return new Promise((resolve, reject) => {
118
+ grecaptcha.ready(() => {
119
+ grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
120
+ });
121
+ });
122
+ };
123
+ var getPurchaseRecaptchaTokens = async () => {
124
+ const [checkEligibility, newBasket, registerUser] = await Promise.all([
125
+ getRecaptchaToken("checkEligibility"),
126
+ getRecaptchaToken("newBasket"),
127
+ getRecaptchaToken("registerUser")
128
+ ]);
129
+ return { checkEligibility, newBasket, registerUser };
130
+ };
131
+
81
132
  // src/apple-pay-modal.ts
82
133
  function getRenewalDateText(unit, duration) {
83
134
  const now = /* @__PURE__ */ new Date();
@@ -297,6 +348,8 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
297
348
  #country = "";
298
349
  #triggerElement = null;
299
350
  #keyHandler = null;
351
+ #recaptchaTokens = null;
352
+ #recaptchaTokensPromise = null;
300
353
  set offer(value) {
301
354
  this.#offer = value;
302
355
  }
@@ -391,6 +444,23 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
391
444
  }
392
445
  });
393
446
  }
447
+ if (this.#recaptchaTokensPromise) {
448
+ if (cta) {
449
+ cta.setAttribute("disabled", "");
450
+ cta.setAttribute("aria-disabled", "true");
451
+ cta.setAttribute("aria-busy", "true");
452
+ }
453
+ this.#recaptchaTokensPromise.then((tokens) => {
454
+ this.#recaptchaTokens = tokens;
455
+ if (cta) {
456
+ cta.removeAttribute("disabled");
457
+ cta.removeAttribute("aria-disabled");
458
+ cta.removeAttribute("aria-busy");
459
+ }
460
+ }).catch(() => {
461
+ console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
462
+ });
463
+ }
394
464
  cta?.addEventListener("click", () => {
395
465
  if (isUS && checkbox && !checkbox.checked) {
396
466
  errorElement?.classList.add("apm__error--visible");
@@ -401,7 +471,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
401
471
  new CustomEvent("apple-pay-confirmed", {
402
472
  bubbles: true,
403
473
  composed: true,
404
- detail: { skuId: offer.sku_id, country }
474
+ detail: { skuId: offer.sku_id, country, recaptchaTokens: this.#recaptchaTokens }
405
475
  })
406
476
  );
407
477
  this.close();
@@ -412,6 +482,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
412
482
  );
413
483
  }
414
484
  open() {
485
+ this.#recaptchaTokensPromise = getPurchaseRecaptchaTokens();
415
486
  this.#triggerElement = document.activeElement;
416
487
  document.body.appendChild(this);
417
488
  this.#keyHandler = (event) => {
@@ -524,10 +595,6 @@ var apple_pay_button_template_default = `<style>
524
595
  </div>
525
596
  `;
526
597
 
527
- // src/clients/payment-checkout/config.ts
528
- var runtimeConfig = null;
529
- var getApplePayConfig = () => runtimeConfig;
530
-
531
598
  // src/clients/payment-checkout/detects.ts
532
599
  var isAppleDeviceOrSafari = () => {
533
600
  const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
@@ -697,53 +764,6 @@ var redirectToCheckoutFallback = (params) => {
697
764
  return url.pathname + url.search;
698
765
  };
699
766
 
700
- // src/utils/recaptcha.ts
701
- var loadRecaptchaScript = (siteKey) => {
702
- return new Promise((resolve, reject) => {
703
- if (typeof grecaptcha !== "undefined") return resolve();
704
- const existingScript = document.querySelector(
705
- 'script[src*="recaptcha/api.js"]'
706
- );
707
- if (existingScript) {
708
- existingScript.addEventListener("load", () => resolve());
709
- existingScript.addEventListener(
710
- "error",
711
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
712
- );
713
- return;
714
- }
715
- const script = document.createElement("script");
716
- script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
717
- script.async = true;
718
- script.defer = true;
719
- script.onload = () => resolve();
720
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
721
- document.head.appendChild(script);
722
- });
723
- };
724
- var getRecaptchaToken = async (action) => {
725
- const siteKey = getApplePayConfig()?.recaptchaSiteKey;
726
- if (!siteKey) {
727
- throw new Error(
728
- "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
729
- );
730
- }
731
- await loadRecaptchaScript(siteKey);
732
- return new Promise((resolve, reject) => {
733
- grecaptcha.ready(() => {
734
- grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
735
- });
736
- });
737
- };
738
- var getPurchaseRecaptchaTokens = async () => {
739
- const [checkEligibility, newBasket, registerUser] = await Promise.all([
740
- getRecaptchaToken("checkEligibility"),
741
- getRecaptchaToken("newBasket"),
742
- getRecaptchaToken("registerUser")
743
- ]);
744
- return { checkEligibility, newBasket, registerUser };
745
- };
746
-
747
767
  // src/utils/expressCheckoutTracking.ts
748
768
  var EXPRESS_CHECKOUT_EVENTS = {
749
769
  /** Wallet button displayed */
@@ -1446,7 +1466,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1446
1466
  #returnUrl = "";
1447
1467
  #supportedBillingCountries = [];
1448
1468
  #entryPoint = "";
1449
- #recaptchaTokens = null;
1469
+ #applePayConfirmedHandler = null;
1450
1470
  // ─── Public API ─────────────────────────────────────────────────────────────
1451
1471
  get offer() {
1452
1472
  return this.#offer;
@@ -1511,6 +1531,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1511
1531
  this.#paymentOptions = null;
1512
1532
  this.#returnUrl = "";
1513
1533
  this.#supportedBillingCountries = [];
1534
+ if (this.#applePayConfirmedHandler) {
1535
+ document.removeEventListener("apple-pay-confirmed", this.#applePayConfirmedHandler);
1536
+ this.#applePayConfirmedHandler = null;
1537
+ }
1514
1538
  this.querySelector(SEL_CONTAINER)?.remove();
1515
1539
  }
1516
1540
  // ─── Render pipeline ────────────────────────────────────────────────────────
@@ -1618,10 +1642,12 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1618
1642
  if (button.classList.contains("wap-loading")) return;
1619
1643
  this.#openModal();
1620
1644
  });
1621
- document.addEventListener("apple-pay-confirmed", (event) => {
1622
- const { skuId, country } = event.detail;
1623
- this.#initiateApplePay(skuId, country);
1624
- }, { once: true });
1645
+ this.#applePayConfirmedHandler = (event) => {
1646
+ const { skuId, country, recaptchaTokens } = event.detail;
1647
+ this.#initiateApplePay(skuId, country, recaptchaTokens);
1648
+ this.#applePayConfirmedHandler = null;
1649
+ };
1650
+ document.addEventListener("apple-pay-confirmed", this.#applePayConfirmedHandler, { once: true });
1625
1651
  }
1626
1652
  #finishLoading() {
1627
1653
  console.debug("[web-apple-pay] #finishLoading: all checks passed, enabling button");
@@ -1656,10 +1682,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1656
1682
  return;
1657
1683
  }
1658
1684
  trackExpressWalletClick(this.#offer.sku_id);
1659
- getPurchaseRecaptchaTokens().then((tokens) => {
1660
- this.#recaptchaTokens = tokens;
1661
- }).catch(() => {
1662
- });
1663
1685
  ApplePayModal.define();
1664
1686
  const modal = document.createElement(ApplePayModal.tag);
1665
1687
  modal.offer = this.#offer;
@@ -1711,11 +1733,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1711
1733
  })
1712
1734
  };
1713
1735
  }
1714
- async #initiateApplePay(skuId, country) {
1736
+ #initiateApplePay(skuId, country, recaptchaTokens) {
1715
1737
  try {
1716
1738
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1717
1739
  const paymentOptions = this.#paymentOptions;
1718
- const recaptchaTokens = this.#recaptchaTokens ?? await getPurchaseRecaptchaTokens();
1719
1740
  startApplePaySession(
1720
1741
  this.#buildSessionParams(
1721
1742
  skuId,