@economist/web-apple-pay 0.1.1-beta.0

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.
Files changed (55) hide show
  1. package/README.md +186 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1642 -0
  4. package/dist/src/apple-pay-button.d.ts +45 -0
  5. package/dist/src/apple-pay-button.js +1621 -0
  6. package/dist/src/apple-pay-button.stories.js +1934 -0
  7. package/dist/src/apple-pay-logo.d.ts +1 -0
  8. package/dist/src/apple-pay-logo.js +14 -0
  9. package/dist/src/apple-pay-modal.d.ts +12 -0
  10. package/dist/src/apple-pay-modal.js +442 -0
  11. package/dist/src/apple-pay-modal.stories.js +1778 -0
  12. package/dist/src/clients/payment-checkout/apple-session.d.ts +18 -0
  13. package/dist/src/clients/payment-checkout/apple-session.js +243 -0
  14. package/dist/src/clients/payment-checkout/billing-contact-details.d.ts +21 -0
  15. package/dist/src/clients/payment-checkout/billing-contact-details.js +109 -0
  16. package/dist/src/clients/payment-checkout/config.d.ts +9 -0
  17. package/dist/src/clients/payment-checkout/config.js +23 -0
  18. package/dist/src/clients/payment-checkout/contact-validation.d.ts +21 -0
  19. package/dist/src/clients/payment-checkout/contact-validation.js +50 -0
  20. package/dist/src/clients/payment-checkout/detects.d.ts +3 -0
  21. package/dist/src/clients/payment-checkout/detects.js +56 -0
  22. package/dist/src/clients/payment-checkout/index.d.ts +5 -0
  23. package/dist/src/clients/payment-checkout/index.js +83 -0
  24. package/dist/src/clients/payment-checkout/messages.d.ts +33 -0
  25. package/dist/src/clients/payment-checkout/messages.js +19 -0
  26. package/dist/src/clients/payment-checkout/payment-handler.d.ts +36 -0
  27. package/dist/src/clients/payment-checkout/payment-handler.js +389 -0
  28. package/dist/src/clients/payment-checkout/session.d.ts +2 -0
  29. package/dist/src/clients/payment-checkout/session.js +67 -0
  30. package/dist/src/clients/payment-checkout/shipping-contact-details.d.ts +10 -0
  31. package/dist/src/clients/payment-checkout/shipping-contact-details.js +83 -0
  32. package/dist/src/clients/payment-checkout/types.d.ts +118 -0
  33. package/dist/src/clients/payment-checkout/types.js +14 -0
  34. package/dist/src/index.d.ts +7 -0
  35. package/dist/src/index.js +1642 -0
  36. package/dist/src/stories/fixtures/offer.d.ts +2 -0
  37. package/dist/src/stories/fixtures/offer.js +28 -0
  38. package/dist/src/stories/utils/story-env.d.ts +11 -0
  39. package/dist/src/stories/utils/story-env.js +148 -0
  40. package/dist/src/types/html-template.d.js +0 -0
  41. package/dist/src/types/offer.types.d.ts +73 -0
  42. package/dist/src/types/offer.types.js +0 -0
  43. package/dist/src/utils/billing-countries.d.ts +19 -0
  44. package/dist/src/utils/billing-countries.js +263 -0
  45. package/dist/src/utils/define.d.ts +1 -0
  46. package/dist/src/utils/define.js +9 -0
  47. package/dist/src/utils/expressCheckoutRedirect.d.ts +55 -0
  48. package/dist/src/utils/expressCheckoutRedirect.js +46 -0
  49. package/dist/src/utils/expressCheckoutTracking.d.ts +64 -0
  50. package/dist/src/utils/expressCheckoutTracking.js +84 -0
  51. package/dist/src/utils/pricing.d.ts +1 -0
  52. package/dist/src/utils/pricing.js +60 -0
  53. package/dist/src/utils/recaptcha.d.ts +22 -0
  54. package/dist/src/utils/recaptcha.js +54 -0
  55. package/package.json +64 -0
@@ -0,0 +1,1934 @@
1
+ // src/utils/define.ts
2
+ var define = (component, instance) => {
3
+ if (!window.customElements.get(component)) {
4
+ window.customElements.define(component, instance);
5
+ }
6
+ };
7
+
8
+ // src/utils/pricing.ts
9
+ var PRICE_CONSTANTS = {
10
+ CENTS_MULTIPLIER: 100,
11
+ DECIMAL_PLACES: 2,
12
+ NO_DECIMAL_PLACES: 0
13
+ };
14
+ function numberOfDecimalPoints(value) {
15
+ if (!value) {
16
+ return PRICE_CONSTANTS.NO_DECIMAL_PLACES;
17
+ }
18
+ return Number(value.toFixed(PRICE_CONSTANTS.DECIMAL_PLACES)) % 1 === 0 ? PRICE_CONSTANTS.NO_DECIMAL_PLACES : PRICE_CONSTANTS.DECIMAL_PLACES;
19
+ }
20
+ var priceFormatters = {
21
+ CAD: (price) => `C$${price}`,
22
+ SEK: (price) => `${price}Kr`,
23
+ AUD: (price) => `A$${price}`,
24
+ NZD: (price) => `NZ$${price}`,
25
+ HKD: (price) => `HK$${price}`,
26
+ DKK: (price) => `${price}Kr`,
27
+ NOK: (price) => `${price}Kr`,
28
+ CHF: (price) => `${price}Fr`,
29
+ SGD: (price) => `S$${price}`,
30
+ TWD: (price) => `TW$${price}`
31
+ };
32
+ function formatPrice(price, currencyCode, shouldFixDecimalPlaces = true, roundUp = false) {
33
+ if (price === void 0) {
34
+ return "";
35
+ }
36
+ const points = numberOfDecimalPoints(price);
37
+ const calculatedPrice = () => {
38
+ if (roundUp) {
39
+ return (Math.ceil(price * PRICE_CONSTANTS.CENTS_MULTIPLIER) / PRICE_CONSTANTS.CENTS_MULTIPLIER).toFixed(PRICE_CONSTANTS.DECIMAL_PLACES);
40
+ }
41
+ if (shouldFixDecimalPlaces) {
42
+ return (Math.round(price * PRICE_CONSTANTS.CENTS_MULTIPLIER) / PRICE_CONSTANTS.CENTS_MULTIPLIER).toFixed(PRICE_CONSTANTS.DECIMAL_PLACES);
43
+ }
44
+ return price;
45
+ };
46
+ const priceFixDecimal = calculatedPrice();
47
+ const priceFormatter = priceFormatters[currencyCode];
48
+ if (priceFormatter) {
49
+ const withCommas = Number(priceFixDecimal).toLocaleString("en", {
50
+ minimumFractionDigits: points
51
+ });
52
+ return priceFormatter(withCommas);
53
+ }
54
+ try {
55
+ return Number(priceFixDecimal).toLocaleString("en", {
56
+ style: "currency",
57
+ currency: currencyCode,
58
+ minimumFractionDigits: points,
59
+ currencyDisplay: "narrowSymbol"
60
+ });
61
+ } catch {
62
+ return `${currencyCode} ${priceFixDecimal}`;
63
+ }
64
+ }
65
+
66
+ // src/apple-pay-logo.ts
67
+ var LOGO_DEFS_ID = "apple-pay-logo-defs";
68
+ var LOGO_PATH = "M10.2651 4.21143C10.4299 4.21143 10.7106 4.23763 11.1074 4.29004C11.5042 4.34245 11.9385 4.49219 12.4102 4.73926C12.8893 4.97884 13.3236 5.37939 13.7129 5.94092C13.6904 5.96338 13.5819 6.04574 13.3872 6.18799C13.1925 6.32275 12.9754 6.5249 12.7358 6.79443C12.4963 7.05648 12.2866 7.39339 12.1069 7.80518C11.9272 8.20947 11.8374 8.69238 11.8374 9.25391C11.8374 9.89779 11.9497 10.4443 12.1743 10.8936C12.4064 11.3428 12.6722 11.7059 12.9717 11.9829C13.2786 12.2524 13.5482 12.4508 13.7803 12.5781C14.0199 12.7054 14.1471 12.7728 14.1621 12.7803C14.1546 12.8102 14.0573 13.076 13.8701 13.5776C13.6904 14.0793 13.391 14.637 12.9717 15.251C12.6048 15.7826 12.208 16.2729 11.7812 16.7222C11.362 17.1714 10.8566 17.396 10.2651 17.396C9.86833 17.396 9.54264 17.3398 9.28809 17.2275C9.03353 17.1077 8.77148 16.9917 8.50195 16.8794C8.23242 16.7596 7.8693 16.6997 7.4126 16.6997C6.97087 16.6997 6.60026 16.7596 6.30078 16.8794C6.00879 16.9992 5.72803 17.119 5.4585 17.2388C5.19645 17.3586 4.88574 17.4185 4.52637 17.4185C3.97982 17.4185 3.50065 17.2013 3.08887 16.7671C2.67708 16.3328 2.25407 15.8125 1.81982 15.2061C1.3182 14.4873 0.887695 13.6113 0.52832 12.5781C0.176432 11.5374 0.000488281 10.4893 0.000488281 9.43359C0.000488281 8.30306 0.213867 7.35596 0.640625 6.59229C1.06738 5.82113 1.61393 5.24089 2.28027 4.85156C2.9541 4.45475 3.65039 4.25635 4.36914 4.25635C4.75098 4.25635 5.11035 4.31999 5.44727 4.44727C5.78418 4.56706 6.09863 4.69059 6.39062 4.81787C6.6901 4.94515 6.95964 5.00879 7.19922 5.00879C7.43132 5.00879 7.70085 4.94141 8.00781 4.80664C8.31478 4.67188 8.65918 4.54085 9.04102 4.41357C9.42285 4.27881 9.83089 4.21143 10.2651 4.21143ZM9.64746 2.78516C9.35547 3.13704 8.98861 3.43278 8.54688 3.67236C8.10514 3.90446 7.68587 4.02051 7.28906 4.02051C7.20671 4.02051 7.12809 4.01302 7.05322 3.99805C7.04574 3.97559 7.03825 3.93441 7.03076 3.87451C7.02327 3.81462 7.01953 3.75098 7.01953 3.68359C7.01953 3.23438 7.11686 2.80013 7.31152 2.38086C7.50618 1.9541 7.72705 1.60221 7.97412 1.3252C8.28857 0.950846 8.68538 0.640137 9.16455 0.393066C9.64372 0.145996 10.1004 0.014974 10.5347 0C10.5571 0.0973307 10.5684 0.213379 10.5684 0.348145C10.5684 0.797363 10.4823 1.23535 10.3101 1.66211C10.1379 2.08138 9.91699 2.45573 9.64746 2.78516ZM17.6885 11.5112V9.71436H22.0459C23.2064 9.71436 24.1048 9.41488 24.7412 8.81592C25.3776 8.20947 25.6958 7.37467 25.6958 6.31152V6.28906C25.6958 5.21842 25.3776 4.38363 24.7412 3.78467C24.1048 3.18571 23.2064 2.88623 22.0459 2.88623H17.6885V1.08936H22.5625C23.5807 1.08936 24.4792 1.30648 25.2578 1.74072C26.0365 2.17497 26.6466 2.78141 27.0884 3.56006C27.5376 4.3387 27.7622 5.24089 27.7622 6.2666V6.28906C27.7622 7.31478 27.5376 8.2207 27.0884 9.00684C26.6466 9.79297 26.0365 10.4069 25.2578 10.8486C24.4792 11.2904 23.5807 11.5112 22.5625 11.5112H17.6885ZM16.6777 17.2949V1.08936H18.6992V17.2949H16.6777ZM32.6699 17.5195C31.9062 17.5195 31.2249 17.3735 30.626 17.0815C30.0345 16.7896 29.5703 16.3778 29.2334 15.8462C28.904 15.3071 28.7393 14.682 28.7393 13.9707V13.9482C28.7393 13.2594 28.9077 12.668 29.2446 12.1738C29.5815 11.6797 30.0682 11.2904 30.7046 11.0059C31.3485 10.7139 32.1234 10.5417 33.0293 10.4893L37.3979 10.2422V11.7695L33.2539 12.0278C32.4303 12.0728 31.8052 12.2599 31.3784 12.5894C30.9517 12.9188 30.7383 13.3643 30.7383 13.9258V13.9482C30.7383 14.5322 30.9554 14.9927 31.3896 15.3296C31.8239 15.659 32.3929 15.8237 33.0967 15.8237C33.7331 15.8237 34.2983 15.6927 34.7925 15.4307C35.2941 15.1686 35.6909 14.813 35.9829 14.3638C36.2749 13.9071 36.4209 13.3979 36.4209 12.8364V9.27637C36.4209 8.55762 36.1963 7.99609 35.7471 7.5918C35.2979 7.1875 34.6502 6.98535 33.8042 6.98535C33.063 6.98535 32.4603 7.13883 31.9961 7.4458C31.5394 7.74528 31.2474 8.14958 31.1201 8.65869L31.0977 8.7373H29.1885L29.1997 8.625C29.2746 7.98861 29.5067 7.41585 29.896 6.90674C30.2928 6.39762 30.8244 5.99707 31.4907 5.70508C32.1646 5.4056 32.9469 5.25586 33.8379 5.25586C34.7812 5.25586 35.5898 5.41683 36.2637 5.73877C36.945 6.05322 37.4653 6.50244 37.8247 7.08643C38.1916 7.66292 38.375 8.35173 38.375 9.15283V17.2949H36.4209V15.4307H36.3311C36.0915 15.8574 35.7845 16.228 35.4102 16.5425C35.0433 16.8569 34.6278 17.1003 34.1636 17.2725C33.6994 17.4372 33.2015 17.5195 32.6699 17.5195ZM42.1484 21.3267C41.9837 21.3267 41.8115 21.3192 41.6318 21.3042C41.4521 21.2892 41.2987 21.2705 41.1714 21.248V19.6421C41.2612 19.6646 41.366 19.6795 41.4858 19.687C41.6056 19.702 41.7292 19.7095 41.8564 19.7095C42.4554 19.7095 42.9383 19.5897 43.3052 19.3501C43.672 19.1105 43.9678 18.6576 44.1924 17.9912L44.4282 17.3062L40.0371 5.48047H42.126L45.6187 15.981L45.0796 15.2734H45.7759L45.248 15.981L48.7407 5.48047H50.7959L46.1802 18.0249C45.8807 18.826 45.555 19.4661 45.2031 19.9453C44.8512 20.432 44.432 20.7839 43.9453 21.001C43.4587 21.2181 42.8597 21.3267 42.1484 21.3267Z";
69
+ function ensureApplePayLogoSymbol() {
70
+ if (document.getElementById(LOGO_DEFS_ID)) {
71
+ return;
72
+ }
73
+ const tmp = document.createElement("div");
74
+ tmp.innerHTML = `<svg aria-hidden="true" style="display:none" id="${LOGO_DEFS_ID}"><defs><symbol id="apple-pay-logo" viewBox="0 0 51 22"><path d="${LOGO_PATH}" fill="white"/></symbol></defs></svg>`;
75
+ document.body.prepend(tmp.firstElementChild);
76
+ }
77
+
78
+ // src/templates/apple-pay-modal.template.html
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
+
81
+ // src/apple-pay-modal.ts
82
+ function getRenewalDateText(unit, duration) {
83
+ const now = /* @__PURE__ */ new Date();
84
+ let date = new Date(now.getFullYear(), now.getMonth(), now.getDate());
85
+ switch (unit) {
86
+ case "week":
87
+ date.setDate(date.getDate() + duration * 7);
88
+ break;
89
+ case "month":
90
+ date.setMonth(date.getMonth() + duration);
91
+ if (date.getMonth() !== (now.getMonth() + duration) % 12) {
92
+ date = new Date(date.getFullYear(), date.getMonth(), 0);
93
+ }
94
+ break;
95
+ case "year":
96
+ date.setFullYear(date.getFullYear() + duration);
97
+ if (date.getMonth() !== now.getMonth()) {
98
+ date = new Date(date.getFullYear(), date.getMonth(), 0);
99
+ }
100
+ break;
101
+ default:
102
+ break;
103
+ }
104
+ return date.toLocaleString("en-US", {
105
+ year: "numeric",
106
+ month: "long",
107
+ day: "numeric"
108
+ });
109
+ }
110
+ var MIN_TERM_DURATION_PATTERN = /^(\d+)\s+(\w+)s?$/;
111
+ function getMinTermDurationUnits(minTermContractLength) {
112
+ if (!minTermContractLength) {
113
+ return { duration: "", unit: "" };
114
+ }
115
+ const result = minTermContractLength.split(MIN_TERM_DURATION_PATTERN);
116
+ const [, duration = "", rawUnit = ""] = result;
117
+ return { duration, unit: rawUnit.replace(/s$/, "") };
118
+ }
119
+ function getOfferTypeText(offerType) {
120
+ switch (offerType) {
121
+ case "student":
122
+ return "student";
123
+ case "academic":
124
+ return "teacher/faculty";
125
+ default:
126
+ return "";
127
+ }
128
+ }
129
+ var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
130
+ var PRIVACY_LINK = '<a href="https://www.economistgroup.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>';
131
+ function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
132
+ const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
133
+ if (isNYT) {
134
+ const base = `I accept The Economist ${TERMS_LINK} and auto-renewal terms above and acknowledge the ${PRIVACY_LINK}.`;
135
+ if (isLoggedIn) {
136
+ return base;
137
+ }
138
+ return `${base} I understand that by continuing, an account will be created for me if I don\u2019t already have one.`;
139
+ }
140
+ if (isLoggedIn) {
141
+ return `I accept the ${TERMS_LINK}${autoRenewalClause} and acknowledge the ${PRIVACY_LINK}.`;
142
+ }
143
+ return `I accept the ${TERMS_LINK}${autoRenewalClause} and acknowledge the ${PRIVACY_LINK}, and understand that by continuing, an account will be created for me if I don\u2019t already have one.`;
144
+ }
145
+ function getPassiveTermsHtml(isNYT, isLoggedIn, autoRenewal) {
146
+ const accountClause = isLoggedIn ? " " : "\u2019ll get an account and ";
147
+ const autoRenewalClause = autoRenewal ? "above auto-renewal terms, " : "";
148
+ const nytPrefix = isNYT ? " your subscription to The Economist," : "";
149
+ return `By purchasing${nytPrefix} you${accountClause}agree to the ${autoRenewalClause}${TERMS_LINK} and acknowledge the ${PRIVACY_LINK}.`;
150
+ }
151
+ function renewalPeriodText(unit, duration) {
152
+ switch (unit) {
153
+ case "week":
154
+ return duration === 1 ? "weekly" : `every ${duration} weeks`;
155
+ case "month":
156
+ return duration === 1 ? "monthly" : `every ${duration} months`;
157
+ case "year":
158
+ return duration === 1 ? "annually" : `every ${duration} years`;
159
+ default:
160
+ return "";
161
+ }
162
+ }
163
+ function buildTermsContext(offer, country) {
164
+ const isUS = country === "US";
165
+ const isCA = country === "CA";
166
+ const isTaxIncluded = offer.price.tax?.inclusive;
167
+ const unit = offer.subscription_options?.renewal_interval?.unit ?? "";
168
+ const duration = offer.subscription_options?.renewal_interval?.duration ?? 1;
169
+ const isMinTermContract = offer.subscription_options?.min_term_contract || false;
170
+ const renewalPriceText = formatPrice(
171
+ isMinTermContract ? offer.price.pre_discount : offer.subscription_options?.renewal_price,
172
+ offer.price.currency_code,
173
+ !isMinTermContract
174
+ );
175
+ const offerTypeText = getOfferTypeText(offer.product?.offer_type);
176
+ const verificationText = offerTypeText ? `, pending ${offerTypeText} verification` : "";
177
+ const renewalDateText = getRenewalDateText(unit, duration);
178
+ return {
179
+ isUS,
180
+ isCA,
181
+ unit,
182
+ duration,
183
+ taxSuffix: isTaxIncluded ? "" : "*",
184
+ taxFootnote: isTaxIncluded ? "" : " *Plus any applicable sales tax.",
185
+ periodText: renewalPeriodText(unit, duration),
186
+ renewalPriceText,
187
+ renewalDateText,
188
+ renewalDateStr: `on ${renewalDateText}${verificationText}`,
189
+ verificationText,
190
+ usCancelNote: "Cancel auto-renewal at any time in My Account. To avoid being charged you must cancel at least 24 hrs before your renewal date. You\u2019ll still have full access until the end of your current billing period, when the cancellation will take effect.",
191
+ cancelAnytime: "Cancel auto-renewal at any time."
192
+ };
193
+ }
194
+ function freeTrialTerms(offer, ctx) {
195
+ const trialUnit = offer.subscription_options?.trial_period?.unit ?? ctx.unit;
196
+ const trialDuration = offer.subscription_options?.trial_period?.duration ?? 1;
197
+ const trialDateStr = `on ${getRenewalDateText(trialUnit, trialDuration)}${ctx.verificationText}`;
198
+ if (ctx.isUS) {
199
+ return `Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${trialDateStr} when your free trial ends, until cancelled{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal until cancelled. ${ctx.usCancelNote}${ctx.taxFootnote}`;
200
+ }
201
+ return `Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${trialDateStr} when your free trial ends, until cancelled{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}${ctx.taxFootnote}`;
202
+ }
203
+ function nytTerms(ctx) {
204
+ const nytAccessText = "Your complimentary access to The New York Times is for one year only and does not auto-renew. We will send your instructions on how to redeem access to The New York Times by email.";
205
+ if (ctx.isUS) {
206
+ return `Your subscription to <em>The Economist</em> auto-renews ${ctx.periodText} until cancelled{{liSeparator}}Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.renewalDateStr}{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal until cancelled. ${ctx.usCancelNote}${ctx.taxFootnote}{{pSeparator}}${nytAccessText}`;
207
+ }
208
+ if (ctx.isCA) {
209
+ return `Your subscription to <em>The Economist</em> auto-renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.periodText} ${ctx.renewalDateStr} until cancelled.{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}${ctx.taxFootnote}{{pSeparator}}${nytAccessText}`;
210
+ }
211
+ return `Your subscription to <em>The Economist</em> auto-renews at ${ctx.renewalPriceText} ${ctx.periodText} ${ctx.renewalDateStr} until cancelled{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime} ${nytAccessText}`;
212
+ }
213
+ function minTermContractTerms(offer, ctx) {
214
+ const duePrice = formatPrice(offer.price.purchase, offer.price.currency_code);
215
+ const { duration: mtDuration, unit: mtUnit } = getMinTermDurationUnits(
216
+ offer.subscription_options?.min_term_contract_length
217
+ );
218
+ const contractLength = mtDuration && mtUnit ? `${mtDuration} ${mtUnit}` : "";
219
+ const initialContractText = contractLength ? `your initial ${contractLength} contract` : "your initial contract";
220
+ const mtRenewalDateText = mtUnit && mtDuration ? getRenewalDateText(mtUnit, Number(mtDuration)) : ctx.renewalDateText;
221
+ return `You\u2019ll be charged ${duePrice} a month for the duration of ${initialContractText}{{liSeparator}}Your subscription auto-renews annually at ${ctx.renewalPriceText} a month on ${mtRenewalDateText} until cancelled{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}`;
222
+ }
223
+ function paidTrialTerms(offer, ctx) {
224
+ const trialUnit = offer.subscription_options?.trial_period?.unit ?? ctx.unit;
225
+ const trialDuration = offer.subscription_options?.trial_period?.duration ?? ctx.duration;
226
+ const trialDateText = getRenewalDateText(trialUnit, trialDuration);
227
+ if (ctx.isUS) {
228
+ return `Your subscription auto-renews ${ctx.periodText} until cancelled{{liSeparator}}Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} on ${trialDateText} when your paid trial ends{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal. ${ctx.usCancelNote}${ctx.taxFootnote}`;
229
+ }
230
+ return `Your subscription auto-renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.periodText} on ${trialDateText} when your paid trial ends, until cancelled{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}${ctx.taxFootnote}`;
231
+ }
232
+ function autoRenewalTerms(ctx) {
233
+ if (ctx.isUS) {
234
+ return `Your subscription auto-renews ${ctx.periodText} until cancelled{{liSeparator}}Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.renewalDateStr}{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal until cancelled. ${ctx.usCancelNote}${ctx.taxFootnote}`;
235
+ }
236
+ return `Your subscription auto-renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.periodText} ${ctx.renewalDateStr} until cancelled{{liSeparator}}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}${ctx.taxFootnote}`;
237
+ }
238
+ function noAutoRenewalTerms(ctx) {
239
+ if (ctx.isUS || ctx.isCA) {
240
+ return `Your subscription does not renew automatically${ctx.taxFootnote}{{liSeparator}}`;
241
+ }
242
+ return `Your subscription does not renew automatically{{liSeparator}}`;
243
+ }
244
+ function generateApplePayModalTerms(offer, country) {
245
+ const ctx = buildTermsContext(offer, country);
246
+ const opts = offer.subscription_options;
247
+ if (opts?.free_trial) {
248
+ return freeTrialTerms(offer, ctx);
249
+ }
250
+ if (offer.product?.offer_type === "bogof_nyt") {
251
+ return nytTerms(ctx);
252
+ }
253
+ if (opts?.auto_renewal && (opts?.min_term_contract || false)) {
254
+ return minTermContractTerms(offer, ctx);
255
+ }
256
+ if (opts?.auto_renewal && !opts?.free_trial && opts?.variable_term) {
257
+ return paidTrialTerms(offer, ctx);
258
+ }
259
+ if (opts?.auto_renewal) {
260
+ return autoRenewalTerms(ctx);
261
+ }
262
+ return noAutoRenewalTerms(ctx);
263
+ }
264
+ function renderTermsHtml(termsText) {
265
+ const renewalTextLists = termsText.split("{{liSeparator}}");
266
+ const lastPart = renewalTextLists.pop() || "";
267
+ const renewalTextParagraphs = lastPart.split("{{pSeparator}}");
268
+ const ulHtml = `<ul class="apm__terms-bullets">${renewalTextLists.map((value) => `<li>${value}</li>`).join("")}</ul>`;
269
+ const paragraphHtml = renewalTextParagraphs.filter(Boolean).map((value) => `<p class="apm__terms-legal">${value}</p>`).join("");
270
+ return ulHtml + paragraphHtml;
271
+ }
272
+ function getApplePayProductName(offer) {
273
+ const unit = offer.subscription_options?.renewal_interval?.unit ?? "";
274
+ const duration = offer.subscription_options?.renewal_interval?.duration ?? 1;
275
+ let intervalLabel = "";
276
+ switch (unit) {
277
+ case "year":
278
+ intervalLabel = duration === 1 ? "Annual" : `${duration} year`;
279
+ break;
280
+ case "month":
281
+ intervalLabel = duration === 1 ? "Monthly" : `${duration} month`;
282
+ break;
283
+ case "week":
284
+ intervalLabel = duration === 12 ? "Quarterly" : duration === 4 ? "4-week" : duration === 1 ? "Weekly" : `${duration} week`;
285
+ break;
286
+ default:
287
+ intervalLabel = "";
288
+ }
289
+ return [intervalLabel, offer.display_name ?? ""].filter(Boolean).join(" ");
290
+ }
291
+ var ApplePayModal = class _ApplePayModal extends HTMLElement {
292
+ static tag = "teg-apple-pay-modal";
293
+ static define() {
294
+ define(_ApplePayModal.tag, _ApplePayModal);
295
+ }
296
+ #offer = null;
297
+ #country = "";
298
+ #triggerElement = null;
299
+ #keyHandler = null;
300
+ set offer(value) {
301
+ this.#offer = value;
302
+ }
303
+ get country() {
304
+ return this.#country;
305
+ }
306
+ set country(value) {
307
+ this.#country = value;
308
+ }
309
+ connectedCallback() {
310
+ if (this.querySelector(".apm-overlay")) {
311
+ return;
312
+ }
313
+ ensureApplePayLogoSymbol();
314
+ const template = document.createElement("template");
315
+ template.innerHTML = apple_pay_modal_template_default;
316
+ this.appendChild(template.content.cloneNode(true));
317
+ const offer = this.#offer;
318
+ const country = this.#country;
319
+ if (!offer) {
320
+ console.error("ApplePayModal: Missing or invalid offer data");
321
+ this.remove();
322
+ return;
323
+ }
324
+ const offerNameElement = this.querySelector(
325
+ "#apple-pay-modal-offer-name"
326
+ );
327
+ const dueTodayElement = this.querySelector(
328
+ "#apple-pay-modal-due-today"
329
+ );
330
+ const termsElement = this.querySelector(
331
+ "#apple-pay-modal-terms"
332
+ );
333
+ if (offerNameElement) {
334
+ offerNameElement.textContent = getApplePayProductName(offer);
335
+ }
336
+ if (dueTodayElement) {
337
+ const taxSuffix = offer.price.purchase === 0 || offer.price.tax?.inclusive ? "" : "*";
338
+ const duePrice = formatPrice(
339
+ offer.price.purchase,
340
+ offer.price.currency_code
341
+ );
342
+ dueTodayElement.innerHTML = duePrice ? `Due today: <strong>${duePrice}${taxSuffix}</strong>` : "";
343
+ }
344
+ if (termsElement) {
345
+ termsElement.innerHTML = renderTermsHtml(
346
+ generateApplePayModalTerms(offer, country)
347
+ );
348
+ }
349
+ const isUS = country === "US";
350
+ const isLoggedIn = this.dataset.loggedIn === "true";
351
+ const isOfferNYT = offer.product?.offer_type === "bogof_nyt";
352
+ const isAutoRenewal = offer.subscription_options?.auto_renewal ?? true;
353
+ const checkboxTextElement = this.querySelector(
354
+ "#apple-pay-modal-checkbox-text"
355
+ );
356
+ if (checkboxTextElement) {
357
+ checkboxTextElement.innerHTML = getCheckboxTermsHtml(
358
+ isOfferNYT,
359
+ isLoggedIn,
360
+ isAutoRenewal
361
+ );
362
+ }
363
+ const checkboxLabel = this.querySelector(
364
+ "#apple-pay-modal-checkbox-label"
365
+ );
366
+ const checkbox = this.querySelector(
367
+ "#apple-pay-modal-checkbox"
368
+ );
369
+ const cta = this.querySelector("#apple-pay-modal-cta");
370
+ const errorElement = this.querySelector(
371
+ "#apple-pay-modal-error"
372
+ );
373
+ const passiveTermsElement = this.querySelector(
374
+ "#apple-pay-modal-passive-terms"
375
+ );
376
+ if (!isUS) {
377
+ checkboxLabel?.remove();
378
+ errorElement?.remove();
379
+ if (passiveTermsElement) {
380
+ passiveTermsElement.innerHTML = getPassiveTermsHtml(
381
+ isOfferNYT,
382
+ isLoggedIn,
383
+ isAutoRenewal
384
+ );
385
+ }
386
+ } else {
387
+ passiveTermsElement?.remove();
388
+ checkbox?.addEventListener("change", () => {
389
+ if (checkbox.checked) {
390
+ errorElement?.classList.remove("apm__error--visible");
391
+ checkboxLabel?.classList.remove("apm__checkbox-label--error");
392
+ }
393
+ });
394
+ }
395
+ cta?.addEventListener("click", () => {
396
+ if (isUS && checkbox && !checkbox.checked) {
397
+ errorElement?.classList.add("apm__error--visible");
398
+ checkboxLabel?.classList.add("apm__checkbox-label--error");
399
+ return;
400
+ }
401
+ this.dispatchEvent(
402
+ new CustomEvent("apple-pay-confirmed", {
403
+ bubbles: true,
404
+ composed: true,
405
+ detail: { skuId: offer.sku_id, country }
406
+ })
407
+ );
408
+ this.close();
409
+ });
410
+ this.querySelector("#apple-pay-modal-close")?.addEventListener(
411
+ "click",
412
+ () => this.close()
413
+ );
414
+ }
415
+ open() {
416
+ this.#triggerElement = document.activeElement;
417
+ document.body.appendChild(this);
418
+ this.#keyHandler = (event) => {
419
+ if (event.key === "Escape") {
420
+ event.preventDefault();
421
+ event.stopPropagation();
422
+ event.stopImmediatePropagation();
423
+ this.close();
424
+ }
425
+ };
426
+ document.addEventListener("keydown", this.#keyHandler, { capture: true });
427
+ this.querySelector("#apple-pay-modal")?.focus();
428
+ }
429
+ close() {
430
+ if (this.#keyHandler) {
431
+ document.removeEventListener("keydown", this.#keyHandler, {
432
+ capture: true
433
+ });
434
+ this.#keyHandler = null;
435
+ }
436
+ this.remove();
437
+ this.#triggerElement?.focus();
438
+ }
439
+ };
440
+
441
+ // src/templates/apple-pay-button.template.html
442
+ var apple_pay_button_template_default = '<style>\n /* Use CSS variables for layout so parent walls can easily override placement and sizing */\n teg-apple-pay-button {\n display: block;\n margin: var(--apple-pay-margin, 0);\n width: 100%;\n height: 100%;\n }\n\n teg-apple-pay-button .apple-pay-container {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n width: 100%;\n height: 100%;\n }\n\n teg-apple-pay-button .apple-pay-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n width: 100%;\n min-width: 0;\n height: 100%;\n min-height: var(--apple-pay-height, 2.75rem);\n background-color: #000;\n color: #fff;\n border: none;\n border-radius: var(--apple-pay-border-radius, 1.375rem);\n cursor: pointer;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: 1.0625rem;\n font-weight: 500;\n }\n\n teg-apple-pay-button .apple-pay-btn svg {\n flex-shrink: 0;\n display: block;\n }\n</style>\n\n<div class="apple-pay-container" aria-label="Express checkout with Apple Pay">\n <button\n class="apple-pay-btn"\n type="button"\n aria-label="Pay with Apple Pay"\n data-analytics="paywall:apple-pay-checkout"\n >\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';
443
+
444
+ // src/clients/payment-checkout/config.ts
445
+ var runtimeConfig = null;
446
+ var configureApplePay = (config) => {
447
+ console.log("[web-apple-pay] configureApplePay called", {
448
+ merchantId: config.merchantId,
449
+ walletApiBaseUrl: config.walletApiBaseUrl,
450
+ debugBypassEnabled: config.debugBypassEnabled,
451
+ recaptchaSiteKey: config.recaptchaSiteKey ? "***" : void 0
452
+ });
453
+ runtimeConfig = {
454
+ ...config
455
+ };
456
+ };
457
+ var getApplePayConfig = () => runtimeConfig;
458
+ var resetApplePayConfig = () => {
459
+ console.log("[web-apple-pay] resetApplePayConfig called");
460
+ runtimeConfig = null;
461
+ };
462
+
463
+ // src/clients/payment-checkout/detects.ts
464
+ var isAppleDeviceOrSafari = () => {
465
+ const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
466
+ const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
467
+ return isIOSOrMacTouchDevice || isSafariBrowser;
468
+ };
469
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
470
+ var isApplePayAvailable = async () => {
471
+ console.log("[web-apple-pay] isApplePayAvailable: checking availability");
472
+ if (bypassApplePayChecks()) {
473
+ console.log("[web-apple-pay] isApplePayAvailable: bypass active \u2192 true");
474
+ return true;
475
+ }
476
+ if (!isAppleDeviceOrSafari()) {
477
+ console.log("[web-apple-pay] isApplePayAvailable: not Apple device or Safari \u2192 false");
478
+ return false;
479
+ }
480
+ const applePaySession = window.ApplePaySession;
481
+ if (!applePaySession) {
482
+ console.log("[web-apple-pay] isApplePayAvailable: ApplePaySession not found on window \u2192 false");
483
+ return false;
484
+ }
485
+ try {
486
+ if (typeof applePaySession.canMakePayments !== "function") {
487
+ console.log("[web-apple-pay] isApplePayAvailable: canMakePayments is not a function \u2192 false");
488
+ return false;
489
+ }
490
+ if (!applePaySession.canMakePayments()) {
491
+ console.log("[web-apple-pay] isApplePayAvailable: canMakePayments() returned false \u2192 false");
492
+ return false;
493
+ }
494
+ const merchantId = getApplePayConfig()?.merchantId;
495
+ if (!merchantId) {
496
+ console.warn(
497
+ "[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
498
+ );
499
+ return false;
500
+ }
501
+ console.log("[web-apple-pay] isApplePayAvailable : calling canMakePaymentsWithActiveCard", { merchantId });
502
+ const result = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
503
+ console.log("[web-apple-pay] isApplePayAvailable: canMakePaymentsWithActiveCard \u2192", result);
504
+ return result;
505
+ } catch (error) {
506
+ console.error("Error checking Apple Pay active card status:", error);
507
+ return false;
508
+ }
509
+ };
510
+
511
+ // src/clients/payment-checkout/types.ts
512
+ var ApiError = class extends Error {
513
+ status;
514
+ body;
515
+ constructor(status, body) {
516
+ super(`API request failed with status ${status}`);
517
+ this.name = "ApiError";
518
+ this.status = status;
519
+ this.body = body;
520
+ }
521
+ };
522
+
523
+ // src/clients/payment-checkout/index.ts
524
+ var getWalletApiBaseUrl = () => {
525
+ const config = getApplePayConfig();
526
+ if (!config?.walletApiBaseUrl) {
527
+ throw new Error(
528
+ "[web-apple-pay] Missing walletApiBaseUrl. Call configureApplePay({ merchantId, walletApiBaseUrl, ... }) before making wallet API requests."
529
+ );
530
+ }
531
+ return config.walletApiBaseUrl;
532
+ };
533
+ var apiFetch = async (path, options = {}) => {
534
+ const baseUrl = getWalletApiBaseUrl();
535
+ const method = (options.method ?? "GET").toUpperCase();
536
+ const hasBody = options.body !== void 0 && options.body !== null;
537
+ const headers = new Headers(options.headers);
538
+ if (!headers.has("Accept")) {
539
+ headers.set("Accept", "application/json");
540
+ }
541
+ if (!headers.has("Content-Type")) {
542
+ if (hasBody || method !== "GET" && method !== "HEAD") {
543
+ headers.set("Content-Type", "application/json");
544
+ }
545
+ }
546
+ const response = await fetch(`${baseUrl}${path}`, {
547
+ ...options,
548
+ headers
549
+ });
550
+ if (!response.ok) {
551
+ const contentType = response.headers.get("content-type") || "";
552
+ if (contentType.includes("application/json")) {
553
+ const errorBody = await response.json();
554
+ throw new ApiError(response.status, errorBody);
555
+ }
556
+ const errorText = await response.text();
557
+ throw new Error(
558
+ `API request failed: ${response.status} ${response.statusText} - ${errorText}`
559
+ );
560
+ }
561
+ return response.json();
562
+ };
563
+ var getPaymentOptions = (data) => {
564
+ const { sku, countryCode, currencyCode, checkoutUrl } = data;
565
+ const params = new URLSearchParams({ checkoutUrl });
566
+ return apiFetch(
567
+ `/v1/wallet/payment-options/sku/${sku}/${countryCode}/${currencyCode}?${params}`,
568
+ { method: "GET" }
569
+ );
570
+ };
571
+ var validateAppleMerchant = (data) => {
572
+ return apiFetch("/v1/wallet/validate-merchant", {
573
+ method: "POST",
574
+ body: JSON.stringify(data)
575
+ });
576
+ };
577
+ var performPurchase = (data) => {
578
+ return apiFetch("/v1/wallet/purchase", {
579
+ method: "POST",
580
+ body: JSON.stringify(data),
581
+ credentials: "include"
582
+ });
583
+ };
584
+
585
+ // src/clients/payment-checkout/session.ts
586
+ var getExpressCheckoutSession = () => {
587
+ return apiFetch("/v1/wallet/session", {
588
+ method: "GET",
589
+ credentials: "include"
590
+ });
591
+ };
592
+
593
+ // src/utils/expressCheckoutRedirect.ts
594
+ var captureOriginUrl = () => window.location.href;
595
+ var buildQueryString = () => {
596
+ const countryCode = new URLSearchParams(window.location.search).get(
597
+ "country"
598
+ );
599
+ return countryCode ? `&country=${encodeURIComponent(countryCode)}` : "";
600
+ };
601
+ var decodeSku = (sku) => {
602
+ try {
603
+ return atob(sku.substring(sku.indexOf("-") + 1));
604
+ } catch {
605
+ return sku;
606
+ }
607
+ };
608
+ var redirectToCheckoutComplete = (params) => {
609
+ const url = new URL("/checkout/complete", window.location.origin);
610
+ url.searchParams.set("basketId", params.basketId);
611
+ if (params.queryString) {
612
+ const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
613
+ new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
614
+ }
615
+ url.searchParams.set("lmo_offer", params.skuId);
616
+ url.searchParams.set("status", params.status);
617
+ url.searchParams.set("redirectToCheckout", "true");
618
+ url.searchParams.set("returnUrl", params.returnUrl);
619
+ return url.pathname + url.search;
620
+ };
621
+ var redirectToCheckoutFallback = (params) => {
622
+ const url = new URL(window.location.href);
623
+ url.pathname = "/checkout";
624
+ url.searchParams.set("lmo_offer", params.skuId);
625
+ if (params.inlineError)
626
+ url.searchParams.set("inlineError", params.inlineError);
627
+ if (params.email) url.searchParams.set("email", params.email);
628
+ if (params.emailError) url.searchParams.set("emailError", params.emailError);
629
+ url.searchParams.set("returnUrl", params.returnUrl);
630
+ return url.pathname + url.search;
631
+ };
632
+
633
+ // src/utils/recaptcha.ts
634
+ var loadRecaptchaScript = (siteKey) => {
635
+ return new Promise((resolve, reject) => {
636
+ if (typeof grecaptcha !== "undefined") return resolve();
637
+ const existingScript = document.querySelector(
638
+ 'script[src*="recaptcha/api.js"]'
639
+ );
640
+ if (existingScript) {
641
+ existingScript.addEventListener("load", () => resolve());
642
+ existingScript.addEventListener(
643
+ "error",
644
+ () => reject(new Error("Failed to load existing reCAPTCHA script"))
645
+ );
646
+ return;
647
+ }
648
+ const script = document.createElement("script");
649
+ script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
650
+ script.async = true;
651
+ script.defer = true;
652
+ script.onload = () => resolve();
653
+ script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
654
+ document.head.appendChild(script);
655
+ });
656
+ };
657
+ var getRecaptchaToken = async (action) => {
658
+ const siteKey = getApplePayConfig()?.recaptchaSiteKey;
659
+ if (!siteKey) {
660
+ throw new Error(
661
+ "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
662
+ );
663
+ }
664
+ await loadRecaptchaScript(siteKey);
665
+ return new Promise((resolve, reject) => {
666
+ grecaptcha.ready(() => {
667
+ grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
668
+ });
669
+ });
670
+ };
671
+ var getPurchaseRecaptchaTokens = async () => {
672
+ const [checkEligibility, newBasket, registerUser] = await Promise.all([
673
+ getRecaptchaToken("checkEligibility"),
674
+ getRecaptchaToken("newBasket"),
675
+ getRecaptchaToken("registerUser")
676
+ ]);
677
+ return { checkEligibility, newBasket, registerUser };
678
+ };
679
+
680
+ // src/utils/expressCheckoutTracking.ts
681
+ var EXPRESS_CHECKOUT_EVENTS = {
682
+ /** Wallet button displayed */
683
+ IMPRESSION: "express_wallet_impression",
684
+ /** Wallet button clicked */
685
+ BUTTON_CLICK: "express_wallet_click",
686
+ /** Apple Pay sheet opened */
687
+ SHEET_OPEN: "wallet_payment_sheet_open",
688
+ /** Payment authorised by the user in the Apple Pay sheet */
689
+ AUTHORISED: "wallet_payment_authorised",
690
+ /** Purchase completed — BFF returned success */
691
+ SUCCESS: "wallet_payment_success",
692
+ /** Payment failed — BFF returned error */
693
+ FAILURE: "wallet_payment_failure",
694
+ /** Redirect to standard checkout (any fallback path) */
695
+ FALLBACK_CHECKOUT: "fallback_checkout"
696
+ };
697
+ var getDevice = () => {
698
+ const ua = navigator.userAgent;
699
+ if (/tablet|ipad/i.test(ua)) return "tablet";
700
+ if (/mobile|iphone|android/i.test(ua)) return "mobile";
701
+ return "desktop";
702
+ };
703
+ var emitTedlEvent = (event) => {
704
+ window.tedl = window.tedl ?? {};
705
+ window.tedl.events = window.tedl.events ?? [];
706
+ window.tedl.events.push(event);
707
+ };
708
+ var track = (action, extra) => {
709
+ emitTedlEvent({
710
+ name: "user.journey",
711
+ type: "checkout",
712
+ action,
713
+ info: "applepay",
714
+ ...extra
715
+ });
716
+ };
717
+ var trackExpressWalletImpression = (skuId, { entryPoint, country }) => {
718
+ track(EXPRESS_CHECKOUT_EVENTS.IMPRESSION, {
719
+ wallet_type: "apple_pay",
720
+ entry_point: entryPoint,
721
+ sku_id: skuId,
722
+ country,
723
+ device: getDevice()
724
+ });
725
+ };
726
+ var trackExpressWalletClick = (skuId) => {
727
+ track(EXPRESS_CHECKOUT_EVENTS.BUTTON_CLICK, { sku_id: skuId });
728
+ };
729
+ var trackWalletPaymentSheetOpen = (skuId) => {
730
+ track(EXPRESS_CHECKOUT_EVENTS.SHEET_OPEN, { sku_id: skuId });
731
+ };
732
+ var trackWalletPaymentAuthorised = (skuId) => {
733
+ track(EXPRESS_CHECKOUT_EVENTS.AUTHORISED, { sku_id: skuId });
734
+ };
735
+ var trackWalletPaymentSuccess = (skuId, basketId) => {
736
+ track(EXPRESS_CHECKOUT_EVENTS.SUCCESS, {
737
+ sku_id: skuId,
738
+ basket_id: basketId
739
+ });
740
+ };
741
+ var trackWalletPaymentFailure = (skuId, reason) => {
742
+ track(EXPRESS_CHECKOUT_EVENTS.FAILURE, {
743
+ sku_id: skuId,
744
+ failure_reason: reason
745
+ });
746
+ };
747
+ var trackFallbackCheckout = (skuId) => {
748
+ track(EXPRESS_CHECKOUT_EVENTS.FALLBACK_CHECKOUT, { sku_id: skuId });
749
+ };
750
+ var trackValidationFieldError = (field, errorType) => {
751
+ track(`validation_${field}_${errorType}`, {});
752
+ };
753
+
754
+ // src/clients/payment-checkout/messages.ts
755
+ var MSG_MISSING_REQUIRED_FIELD = "Missing required field";
756
+ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a valid email address.";
757
+ var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
758
+ var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
759
+ var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
760
+ var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
761
+ var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
762
+ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
763
+
764
+ // src/clients/payment-checkout/apple-session.ts
765
+ function startApplePaySession(config) {
766
+ console.log("[web-apple-pay] startApplePaySession", {
767
+ skuId: config.skuId,
768
+ countryCode: config.countryCode,
769
+ currencyCode: config.currencyCode,
770
+ totalAmount: config.totalAmount,
771
+ userLoggedIn: config.userLoggedIn
772
+ });
773
+ const paymentRequest = {
774
+ countryCode: config.countryCode,
775
+ currencyCode: config.currencyCode,
776
+ merchantCapabilities: ["supports3DS"],
777
+ supportedNetworks: config.supportedNetworks,
778
+ total: {
779
+ label: config.totalLabel,
780
+ amount: config.totalAmount
781
+ },
782
+ requiredBillingContactFields: ["postalAddress", "name"],
783
+ // Only request email from Apple Pay sheet if user is NOT logged in
784
+ ...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
785
+ };
786
+ const session = new ApplePaySession(3, paymentRequest);
787
+ session.onvalidatemerchant = async (event) => {
788
+ console.log("[web-apple-pay] onvalidatemerchant: received", { validationURL: event.validationURL });
789
+ try {
790
+ const validationHost = new URL(event.validationURL).host;
791
+ if (!validationHost.endsWith("apple.com")) {
792
+ console.warn("[web-apple-pay] onvalidatemerchant: invalid host, aborting", { validationHost });
793
+ trackFallbackCheckout(config.skuId);
794
+ session.abort();
795
+ window.location.href = redirectToCheckoutFallback({
796
+ skuId: config.skuId,
797
+ returnUrl: config.returnUrl,
798
+ inlineError: MSG_PAYMENT_GENERIC_ERROR
799
+ });
800
+ return;
801
+ }
802
+ const recaptchaToken = await getRecaptchaToken("verifyMerchant");
803
+ console.log("[web-apple-pay] onvalidatemerchant: calling validateAppleMerchant");
804
+ const response = await validateAppleMerchant({
805
+ validationURL: event.validationURL,
806
+ recaptchaToken
807
+ });
808
+ if (!response?.merchantSession) {
809
+ console.warn("[web-apple-pay] onvalidatemerchant: no merchantSession in response, aborting");
810
+ trackFallbackCheckout(config.skuId);
811
+ session.abort();
812
+ window.location.href = redirectToCheckoutFallback({
813
+ skuId: config.skuId,
814
+ returnUrl: config.returnUrl,
815
+ inlineError: MSG_PAYMENT_GENERIC_ERROR
816
+ });
817
+ return;
818
+ }
819
+ console.log("[web-apple-pay] onvalidatemerchant: merchant validated successfully");
820
+ session.completeMerchantValidation(response.merchantSession);
821
+ } catch (err) {
822
+ console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
823
+ trackFallbackCheckout(config.skuId);
824
+ session.abort();
825
+ window.location.href = redirectToCheckoutFallback({
826
+ skuId: config.skuId,
827
+ returnUrl: config.returnUrl,
828
+ inlineError: MSG_PAYMENT_GENERIC_ERROR
829
+ });
830
+ }
831
+ };
832
+ session.onpaymentauthorized = (event) => {
833
+ console.log("[web-apple-pay] onpaymentauthorized: payment authorized by user", { skuId: config.skuId });
834
+ trackWalletPaymentAuthorised(config.skuId);
835
+ config.onPaymentAuthorized(event, session);
836
+ };
837
+ session.oncancel = () => {
838
+ console.log("[web-apple-pay] oncancel: payment sheet cancelled by user", { skuId: config.skuId });
839
+ config.onCancel?.();
840
+ };
841
+ console.log("[web-apple-pay] startApplePaySession: beginning session");
842
+ trackWalletPaymentSheetOpen(config.skuId);
843
+ session.begin();
844
+ }
845
+
846
+ // src/clients/payment-checkout/contact-validation.ts
847
+ var pushFieldError = (errors, billingOrShippingContact = "billing", field, errorType, errorText) => {
848
+ const errorCode = billingOrShippingContact === "billing" ? "billingContactInvalid" : "shippingContactInvalid";
849
+ errors.push(new ApplePayError(errorCode, field, errorText));
850
+ trackValidationFieldError(field, errorType);
851
+ return "";
852
+ };
853
+ var pushRequiredFieldError = (errors, billingOrShippingContact = "billing", field) => {
854
+ return pushFieldError(
855
+ errors,
856
+ billingOrShippingContact,
857
+ field,
858
+ "required",
859
+ MSG_MISSING_REQUIRED_FIELD
860
+ );
861
+ };
862
+ var ContactValidationError = class extends Error {
863
+ constructor(errors) {
864
+ super(MSG_CONTACT_VALIDATION_INTERNAL);
865
+ this.errors = errors;
866
+ }
867
+ };
868
+
869
+ // src/clients/payment-checkout/billing-contact-details.ts
870
+ var countriesWithoutCityField = ["KY", "CK", "NR", "TC"];
871
+ var countriesRequiringAllFields = ["US", "CA", "GB"];
872
+ var resolveCountryConfig = (country) => {
873
+ const hasCityField = !countriesWithoutCityField.includes(country);
874
+ const requiresStateAndPostcode = countriesRequiringAllFields.includes(country);
875
+ return {
876
+ hasCityField,
877
+ requiresStateAndPostcode,
878
+ // if there is no city field, we require state and use it in place of city
879
+ requiresStateField: !hasCityField || requiresStateAndPostcode
880
+ };
881
+ };
882
+ var validateAddressFields = (errors, contact, { hasCityField, requiresStateAndPostcode, requiresStateField }) => {
883
+ const locality = !hasCityField ? contact.administrativeArea : contact.locality;
884
+ if (!contact.addressLines?.at(0)?.trim()) {
885
+ pushRequiredFieldError(errors, "billing", "addressLines");
886
+ }
887
+ if (!locality?.trim() && hasCityField) {
888
+ pushRequiredFieldError(errors, "billing", "locality");
889
+ }
890
+ if (!contact.administrativeArea?.trim() && requiresStateField) {
891
+ pushRequiredFieldError(errors, "billing", "administrativeArea");
892
+ }
893
+ if (!contact.postalCode?.trim() && requiresStateAndPostcode) {
894
+ pushRequiredFieldError(errors, "billing", "postalCode");
895
+ }
896
+ };
897
+ var validateNameFields = (errors, contact) => {
898
+ const firstName = contact.givenName?.trim() || "";
899
+ const lastName = contact.familyName?.trim() || "";
900
+ if (!firstName || !lastName) {
901
+ pushRequiredFieldError(errors, "billing", "name");
902
+ }
903
+ return { firstName, lastName };
904
+ };
905
+ var validateCountrySupport = (errors, country, supportedBillingCountries) => {
906
+ if (!supportedBillingCountries.includes(country)) {
907
+ pushFieldError(
908
+ errors,
909
+ "billing",
910
+ "countryCode",
911
+ "not-supported",
912
+ MSG_UNSUPPORTED_BILLING_COUNTRY
913
+ );
914
+ }
915
+ };
916
+ var billingContactDetails = (contact, supportedBillingCountries) => {
917
+ const errors = [];
918
+ const country = contact.countryCode?.toUpperCase() || pushRequiredFieldError(errors, "billing", "country");
919
+ const config = resolveCountryConfig(country);
920
+ validateAddressFields(errors, contact, config);
921
+ const { firstName, lastName } = validateNameFields(errors, contact);
922
+ validateCountrySupport(errors, country, supportedBillingCountries);
923
+ if (errors.length > 0) {
924
+ throw new ContactValidationError(errors);
925
+ }
926
+ return { firstName, lastName };
927
+ };
928
+
929
+ // src/clients/payment-checkout/shipping-contact-details.ts
930
+ var EMAIL_FORMAT_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
931
+ var shippingContactDetails = (shippingContact) => {
932
+ const errors = [];
933
+ let emailAddress = "";
934
+ if (shippingContact?.emailAddress === void 0 || shippingContact?.emailAddress === "") {
935
+ pushRequiredFieldError(errors, "shipping", "emailAddress");
936
+ } else {
937
+ emailAddress = shippingContact.emailAddress.trim();
938
+ if (emailAddress.length > 80) {
939
+ pushFieldError(
940
+ errors,
941
+ "shipping",
942
+ "emailAddress",
943
+ "format",
944
+ MSG_EMAIL_TOO_LONG
945
+ );
946
+ } else if (!EMAIL_FORMAT_REGEX.test(emailAddress)) {
947
+ pushFieldError(
948
+ errors,
949
+ "shipping",
950
+ "emailAddress",
951
+ "format",
952
+ MSG_EMAIL_WRONG_FORMAT
953
+ );
954
+ }
955
+ }
956
+ if (errors.length > 0) {
957
+ throw new ContactValidationError(errors);
958
+ }
959
+ return { emailAddress };
960
+ };
961
+
962
+ // src/clients/payment-checkout/payment-handler.ts
963
+ var MSG_ELIGIBILITY = "Something went wrong, please try again";
964
+ var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
965
+ var failApplePaySession = (session, errors = []) => {
966
+ session.completePayment({
967
+ status: ApplePaySession.STATUS_FAILURE,
968
+ errors
969
+ });
970
+ };
971
+ function parseWalletApiError(err) {
972
+ const body = err instanceof ApiError ? err.body : err;
973
+ if (body && typeof body === "object" && "errorType" in body && "message" in body) {
974
+ return body;
975
+ }
976
+ return null;
977
+ }
978
+ function validateContacts(event, sessionData, supportedBillingCountries) {
979
+ billingContactDetails(
980
+ event.payment.billingContact,
981
+ supportedBillingCountries
982
+ );
983
+ if (!sessionData.loggedIn) {
984
+ return shippingContactDetails(event.payment.shippingContact).emailAddress;
985
+ }
986
+ return void 0;
987
+ }
988
+ function redirectFallback(skuId, returnUrl, options) {
989
+ trackFallbackCheckout(skuId);
990
+ window.location.href = redirectToCheckoutFallback({
991
+ skuId,
992
+ returnUrl,
993
+ ...options
994
+ });
995
+ }
996
+ var ERROR_HANDLERS = {
997
+ EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
998
+ emailError: MSG_EMAIL_COLLISION,
999
+ email: validatedEmail
1000
+ }),
1001
+ PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
1002
+ // Matches sma-checkout catch-all: always redirect with generic inlineError.
1003
+ redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
1004
+ ),
1005
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
1006
+ REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
1007
+ VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1008
+ };
1009
+ function handleStructuredError(ctx) {
1010
+ const handler = ERROR_HANDLERS[ctx.walletError.errorType];
1011
+ if (handler) {
1012
+ handler(ctx);
1013
+ } else {
1014
+ redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1015
+ }
1016
+ }
1017
+ async function handlePaymentAuthorized(event, session, sessionData, config) {
1018
+ const { skuId, returnUrl, queryString } = config;
1019
+ let validatedEmail;
1020
+ console.log("[web-apple-pay] handlePaymentAuthorized: starting", { skuId, userLoggedIn: sessionData.loggedIn });
1021
+ try {
1022
+ console.log("[web-apple-pay] handlePaymentAuthorized: step 1 \u2014 validating contacts");
1023
+ validatedEmail = validateContacts(
1024
+ event,
1025
+ sessionData,
1026
+ config.supportedBillingCountries
1027
+ );
1028
+ console.log("[web-apple-pay] handlePaymentAuthorized: contacts valid", { validatedEmail });
1029
+ console.log("[web-apple-pay] handlePaymentAuthorized: step 2 \u2014 calling performPurchase", { skuId });
1030
+ const result = await performPurchase({
1031
+ applePayToken: event.payment.token,
1032
+ skuId: config.skuId,
1033
+ billingContact: event.payment.billingContact,
1034
+ ...event.payment.shippingContact && {
1035
+ shippingContact: event.payment.shippingContact
1036
+ },
1037
+ recaptchaTokens: config.recaptchaTokens
1038
+ });
1039
+ console.log("[web-apple-pay] handlePaymentAuthorized: performPurchase succeeded", { basketId: result.basketId });
1040
+ console.log("[web-apple-pay] handlePaymentAuthorized: step 3 \u2014 completing payment and redirecting");
1041
+ trackWalletPaymentSuccess(skuId, result.basketId);
1042
+ session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1043
+ const userStatus = sessionData.loggedIn ? "lex" : "nex";
1044
+ window.location.href = redirectToCheckoutComplete({
1045
+ basketId: result.basketId,
1046
+ status: userStatus,
1047
+ skuId: decodeSku(skuId),
1048
+ returnUrl,
1049
+ queryString
1050
+ });
1051
+ } catch (err) {
1052
+ if (err instanceof ContactValidationError) {
1053
+ console.warn("[web-apple-pay] handlePaymentAuthorized: contact validation failed", err.errors);
1054
+ failApplePaySession(session, err.errors);
1055
+ return;
1056
+ }
1057
+ console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1058
+ const walletError = parseWalletApiError(err);
1059
+ console.error("[web-apple-pay] handlePaymentAuthorized: BFF/network error", {
1060
+ skuId,
1061
+ errorType: walletError?.errorType ?? "UNKNOWN",
1062
+ message: walletError?.message
1063
+ });
1064
+ trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1065
+ failApplePaySession(session);
1066
+ if (!walletError) {
1067
+ console.warn("[web-apple-pay] handlePaymentAuthorized: unstructured error, using generic fallback");
1068
+ redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1069
+ return;
1070
+ }
1071
+ console.log("[web-apple-pay] handlePaymentAuthorized: dispatching structured error handler", { errorType: walletError.errorType });
1072
+ handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1073
+ }
1074
+ }
1075
+
1076
+ // src/data/price-zones.json
1077
+ var price_zones_default = {
1078
+ "AS-AS": [
1079
+ "BD",
1080
+ "BT",
1081
+ "CK",
1082
+ "FJ",
1083
+ "ID",
1084
+ "KH",
1085
+ "KI",
1086
+ "LA",
1087
+ "LK",
1088
+ "MH",
1089
+ "MM",
1090
+ "MN",
1091
+ "MV",
1092
+ "NC",
1093
+ "NF",
1094
+ "NP",
1095
+ "NR",
1096
+ "PF",
1097
+ "PG",
1098
+ "PK",
1099
+ "PW",
1100
+ "SB",
1101
+ "TH",
1102
+ "TL",
1103
+ "TO",
1104
+ "TV",
1105
+ "VN",
1106
+ "VU",
1107
+ "WS"
1108
+ ],
1109
+ "AS-AU": ["AU"],
1110
+ "AS-CN": ["CN"],
1111
+ "AS-HK": ["HK", "MO"],
1112
+ "AS-IN": ["IN"],
1113
+ "AS-JP": ["JP"],
1114
+ "AS-KR": ["KR"],
1115
+ "AS-MY": ["MY"],
1116
+ "AS-NZ": ["NZ"],
1117
+ "AS-PH": ["PH"],
1118
+ "AS-SG": ["BN", "SG"],
1119
+ "AS-TW": ["TW"],
1120
+ "CE-AF": [
1121
+ "DZ",
1122
+ "AO",
1123
+ "BJ",
1124
+ "BW",
1125
+ "BF",
1126
+ "BI",
1127
+ "CM",
1128
+ "CV",
1129
+ "CF",
1130
+ "TD",
1131
+ "CG",
1132
+ "CD",
1133
+ "CI",
1134
+ "DJ",
1135
+ "EG",
1136
+ "GQ",
1137
+ "ER",
1138
+ "ET",
1139
+ "GA",
1140
+ "GM",
1141
+ "GH",
1142
+ "GN",
1143
+ "GW",
1144
+ "IL",
1145
+ "JO",
1146
+ "KE",
1147
+ "LB",
1148
+ "LS",
1149
+ "LR",
1150
+ "LY",
1151
+ "MG",
1152
+ "MW",
1153
+ "ML",
1154
+ "MR",
1155
+ "MU",
1156
+ "MA",
1157
+ "MZ",
1158
+ "NA",
1159
+ "NE",
1160
+ "NG",
1161
+ "PS",
1162
+ "RE",
1163
+ "RW",
1164
+ "ST",
1165
+ "SN",
1166
+ "SC",
1167
+ "SL",
1168
+ "ZA",
1169
+ "SZ",
1170
+ "TZ",
1171
+ "TG",
1172
+ "TN",
1173
+ "UG",
1174
+ "ZM",
1175
+ "ZW"
1176
+ ],
1177
+ "CE-CH": ["LI", "CH"],
1178
+ "CE-DK": ["FO", "GL", "DK"],
1179
+ "CE-EU": [
1180
+ "AL",
1181
+ "AD",
1182
+ "AM",
1183
+ "AT",
1184
+ "AZ",
1185
+ "BY",
1186
+ "BE",
1187
+ "BA",
1188
+ "BG",
1189
+ "HR",
1190
+ "CY",
1191
+ "CZ",
1192
+ "EE",
1193
+ "FI",
1194
+ "FR",
1195
+ "GE",
1196
+ "DE",
1197
+ "GI",
1198
+ "GR",
1199
+ "HU",
1200
+ "IS",
1201
+ "IE",
1202
+ "IT",
1203
+ "KZ",
1204
+ "XK",
1205
+ "KG",
1206
+ "LV",
1207
+ "LT",
1208
+ "LU",
1209
+ "MT",
1210
+ "MD",
1211
+ "MC",
1212
+ "ME",
1213
+ "NL",
1214
+ "PL",
1215
+ "PT",
1216
+ "MK",
1217
+ "RO",
1218
+ "RS",
1219
+ "SK",
1220
+ "SI",
1221
+ "ES",
1222
+ "TJ",
1223
+ "TR",
1224
+ "TM",
1225
+ "UA",
1226
+ "UZ"
1227
+ ],
1228
+ "CE-ME": ["AE", "BH", "KW", "OM", "QA", "SA"],
1229
+ "CE-NO": ["NO"],
1230
+ "CE-SE": ["SE"],
1231
+ "LA-LA": [
1232
+ "AI",
1233
+ "AG",
1234
+ "AR",
1235
+ "AW",
1236
+ "BS",
1237
+ "BB",
1238
+ "BZ",
1239
+ "BM",
1240
+ "BO",
1241
+ "BR",
1242
+ "KY",
1243
+ "CL",
1244
+ "CO",
1245
+ "CR",
1246
+ "DM",
1247
+ "DO",
1248
+ "EC",
1249
+ "SV",
1250
+ "FK",
1251
+ "GF",
1252
+ "GD",
1253
+ "GT",
1254
+ "GY",
1255
+ "HT",
1256
+ "HN",
1257
+ "JM",
1258
+ "MQ",
1259
+ "MX",
1260
+ "MS",
1261
+ "NI",
1262
+ "MP",
1263
+ "PA",
1264
+ "PY",
1265
+ "PE",
1266
+ "KN",
1267
+ "LC",
1268
+ "VC",
1269
+ "SR",
1270
+ "TT",
1271
+ "TC",
1272
+ "UY",
1273
+ "VG",
1274
+ "VI"
1275
+ ],
1276
+ "NA-CA": ["CA"],
1277
+ "NA-US": ["AS", "GU", "FM", "PR", "US"],
1278
+ "UK-UK": ["GB"]
1279
+ };
1280
+
1281
+ // src/data/eea-countries.json
1282
+ var eea_countries_default = [
1283
+ "AT",
1284
+ "BE",
1285
+ "BG",
1286
+ "HR",
1287
+ "CY",
1288
+ "CZ",
1289
+ "DK",
1290
+ "EE",
1291
+ "FO",
1292
+ "FI",
1293
+ "FR",
1294
+ "DE",
1295
+ "GR",
1296
+ "GL",
1297
+ "HU",
1298
+ "IS",
1299
+ "IE",
1300
+ "IT",
1301
+ "LV",
1302
+ "LI",
1303
+ "LT",
1304
+ "LU",
1305
+ "MT",
1306
+ "NL",
1307
+ "NO",
1308
+ "PL",
1309
+ "PT",
1310
+ "RO",
1311
+ "SK",
1312
+ "SI",
1313
+ "ES",
1314
+ "SE"
1315
+ ];
1316
+
1317
+ // src/utils/billing-countries.ts
1318
+ var priceZones = price_zones_default;
1319
+ var eeaCountries = eea_countries_default;
1320
+ var ALL_COUNTRY_CODES = Object.values(priceZones).flat();
1321
+ var getZoneByCountry = (country) => Object.keys(priceZones).find((key) => priceZones[key].includes(country)) ?? null;
1322
+ var getSupportedBillingCountries = (offerCountry, offerVariant) => {
1323
+ if (offerVariant === "bundle" || offerVariant === "insider_print") {
1324
+ return [...ALL_COUNTRY_CODES];
1325
+ }
1326
+ const zone = getZoneByCountry(offerCountry);
1327
+ if (!zone) {
1328
+ return [...ALL_COUNTRY_CODES];
1329
+ }
1330
+ const zoneCountries = priceZones[zone];
1331
+ if (eeaCountries.includes(offerCountry)) {
1332
+ return Array.from(/* @__PURE__ */ new Set([...zoneCountries, ...eeaCountries]));
1333
+ }
1334
+ return [...zoneCountries];
1335
+ };
1336
+
1337
+ // src/apple-pay-button.ts
1338
+ var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
1339
+ var TOTAL_LABEL = "The Economist";
1340
+ var DEFAULT_CURRENCY = "USD";
1341
+ var DEFAULT_TOTAL = "0.00";
1342
+ var ERROR_INITIATE = "Failed to initiate Apple Pay session:";
1343
+ var SEL_CONTAINER = ".apple-pay-container";
1344
+ var SEL_BUTTON = ".apple-pay-btn";
1345
+ var APPLE_PAY_EXCLUDED_VARIANTS = [
1346
+ "bundle",
1347
+ "insider_print"
1348
+ ];
1349
+ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1350
+ static tag = "teg-apple-pay-button";
1351
+ static define() {
1352
+ define(_ApplePayButton.tag, _ApplePayButton);
1353
+ }
1354
+ /**
1355
+ * Creates and configures an ApplePayButton if the feature flag is present.
1356
+ */
1357
+ static createIfEnabled(offer, country, entryPoint) {
1358
+ if (!offer) return null;
1359
+ if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant))
1360
+ return null;
1361
+ if (!new URLSearchParams(window.location.search).has(FEATURE_APPLE_PAY_EXPRESS_CHECKOUT))
1362
+ return null;
1363
+ _ApplePayButton.define();
1364
+ const button = document.createElement(_ApplePayButton.tag);
1365
+ button.offer = offer;
1366
+ button.country = country;
1367
+ button.entryPoint = entryPoint;
1368
+ return button;
1369
+ }
1370
+ /**
1371
+ * Fallback session used when the BFF is unreachable at page load.
1372
+ * Treats the user as anonymous (not logged in, new-eligible).
1373
+ * Matches the behaviour of sma-checkout when session fetch fails.
1374
+ */
1375
+ static #ANONYMOUS_SESSION = {
1376
+ loggedIn: false,
1377
+ userType: "new-eligible-user"
1378
+ };
1379
+ // ─── Private state ──────────────────────────────────────────────────────────
1380
+ #offer = null;
1381
+ #country = "";
1382
+ #rendering = false;
1383
+ #readyPromise = null;
1384
+ #sessionData = null;
1385
+ #paymentOptions = null;
1386
+ #returnUrl = "";
1387
+ #supportedBillingCountries = [];
1388
+ #entryPoint = "";
1389
+ // ─── Public API ─────────────────────────────────────────────────────────────
1390
+ get offer() {
1391
+ return this.#offer;
1392
+ }
1393
+ set offer(value) {
1394
+ this.#offer = value;
1395
+ this.#updateDisabledState();
1396
+ }
1397
+ get country() {
1398
+ return this.#country;
1399
+ }
1400
+ set country(value) {
1401
+ this.#country = value;
1402
+ }
1403
+ get entryPoint() {
1404
+ return this.#entryPoint;
1405
+ }
1406
+ set entryPoint(value) {
1407
+ this.#entryPoint = value;
1408
+ }
1409
+ /**
1410
+ * Awaitable contract for the component's current render lifecycle.
1411
+ *
1412
+ * - If called **after** the element has been appended to the DOM, resolves
1413
+ * once `connectedCallback` finishes — whether Apple Pay rendered
1414
+ * successfully or was hidden (unavailable / suppressed by session).
1415
+ * - If called **before** the element is appended (or if it has never been
1416
+ * connected), resolves immediately to `undefined`. It does **not** wait
1417
+ * for a future connection, so always `await whenReady()` after `appendChild`:
1418
+ *
1419
+ * ```ts
1420
+ * container.appendChild(btn); // browser fires connectedCallback
1421
+ * await btn.whenReady(); // waits for render to finish
1422
+ * if (btn.style.display !== 'none') { /* available *\/ }
1423
+ * ```
1424
+ */
1425
+ whenReady() {
1426
+ return this.#readyPromise ?? Promise.resolve();
1427
+ }
1428
+ // ─── Lifecycle ──────────────────────────────────────────────────────────────
1429
+ async connectedCallback() {
1430
+ if (this.#rendering || this.querySelector(SEL_CONTAINER)) {
1431
+ return;
1432
+ }
1433
+ this.#rendering = true;
1434
+ this.#readyPromise = this.#run();
1435
+ await this.#readyPromise;
1436
+ }
1437
+ /**
1438
+ * Resets all render state when the element is removed from the DOM.
1439
+ *
1440
+ * A shared library component cannot assume it won't be moved in the DOM
1441
+ * (e.g. lazy panels, SPA route changes). Resetting on disconnect means
1442
+ * reattach always triggers a fresh `#run()` — new session check, new payment
1443
+ * options fetch, fresh event listeners — preventing stale data from persisting
1444
+ * and eliminating the implicit reliance on the DOM as a render-state flag.
1445
+ */
1446
+ disconnectedCallback() {
1447
+ this.#rendering = false;
1448
+ this.#readyPromise = null;
1449
+ this.#sessionData = null;
1450
+ this.#paymentOptions = null;
1451
+ this.#returnUrl = "";
1452
+ this.#supportedBillingCountries = [];
1453
+ this.querySelector(SEL_CONTAINER)?.remove();
1454
+ }
1455
+ // ─── Render pipeline ────────────────────────────────────────────────────────
1456
+ async #run() {
1457
+ try {
1458
+ if (await this.#checkAvailability()) return;
1459
+ if (await this.#checkSession()) return;
1460
+ if (this.#checkOffer()) return;
1461
+ this.#captureReturnUrl();
1462
+ this.#computeBillingCountries();
1463
+ if (await this.#fetchAndCacheOptions()) return;
1464
+ this.#renderTemplate();
1465
+ } catch {
1466
+ this.style.display = "none";
1467
+ } finally {
1468
+ this.#rendering = false;
1469
+ }
1470
+ }
1471
+ /** Returns `true` (hide) when Apple Pay is unavailable on this device/browser. */
1472
+ async #checkAvailability() {
1473
+ const available = await isApplePayAvailable();
1474
+ if (!available) {
1475
+ this.style.display = "none";
1476
+ return true;
1477
+ }
1478
+ return false;
1479
+ }
1480
+ /**
1481
+ * Returns `true` (hide) when the user already has an active subscription or is B2B.
1482
+ * Session fetch errors are swallowed — the component continues as an anonymous user.
1483
+ */
1484
+ async #checkSession() {
1485
+ this.#sessionData = await this.#fetchSession();
1486
+ if (this.#sessionData?.loggedIn && (this.#sessionData.userType === "active-subscription" || this.#sessionData.userType === "b2b-user")) {
1487
+ this.style.display = "none";
1488
+ return true;
1489
+ }
1490
+ return false;
1491
+ }
1492
+ /** Returns `true` (hide) when no offer has been set before mount. */
1493
+ #checkOffer() {
1494
+ if (!this.#offer) {
1495
+ this.style.display = "none";
1496
+ return true;
1497
+ }
1498
+ return false;
1499
+ }
1500
+ /** Captures the current page URL once at mount for reuse across the session lifetime. */
1501
+ #captureReturnUrl() {
1502
+ this.#returnUrl = captureOriginUrl();
1503
+ }
1504
+ /**
1505
+ * Computes supported billing countries once at mount.
1506
+ * Mirrors sma-checkout `billingCountryOptions()` — stable for the element's
1507
+ * connected lifetime; recalculated on reattach via `disconnectedCallback` reset.
1508
+ */
1509
+ #computeBillingCountries() {
1510
+ this.#supportedBillingCountries = getSupportedBillingCountries(
1511
+ this.#country,
1512
+ this.#offer.product?.variant ?? ""
1513
+ );
1514
+ }
1515
+ /**
1516
+ * Returns `true` (hide) when the wallet BFF reports Apple Pay is unavailable
1517
+ * for this offer. The response is cached and reused in `#initiateApplePay`.
1518
+ */
1519
+ async #fetchAndCacheOptions() {
1520
+ this.#paymentOptions = await this.#fetchPaymentOptions();
1521
+ console.log("[web-apple-pay] #fetchAndCacheOptions: fetched payment options", { paymentOptions: this.#paymentOptions });
1522
+ if (!this.#paymentOptions?.options?.apple_pay) {
1523
+ this.style.display = "none";
1524
+ return true;
1525
+ }
1526
+ return false;
1527
+ }
1528
+ /** Injects the button template and wires click and confirm event listeners. */
1529
+ #renderTemplate() {
1530
+ this.style.removeProperty("display");
1531
+ ensureApplePayLogoSymbol();
1532
+ const template = document.createElement("template");
1533
+ template.innerHTML = apple_pay_button_template_default;
1534
+ this.appendChild(template.content.cloneNode(true));
1535
+ this.#updateDisabledState();
1536
+ trackExpressWalletImpression(this.#offer.sku_id, {
1537
+ entryPoint: this.#entryPoint,
1538
+ country: this.#country
1539
+ });
1540
+ const button = this.querySelector(SEL_BUTTON);
1541
+ button.addEventListener("click", () => {
1542
+ this.#openModal();
1543
+ });
1544
+ this.addEventListener("apple-pay-confirmed", (event) => {
1545
+ const { skuId, country } = event.detail;
1546
+ this.#initiateApplePay(skuId, country);
1547
+ });
1548
+ }
1549
+ // ─── Private helpers ────────────────────────────────────────────────────────
1550
+ #updateDisabledState() {
1551
+ const button = this.querySelector(SEL_BUTTON);
1552
+ if (!button) return;
1553
+ if (this.#offer) {
1554
+ button.removeAttribute("disabled");
1555
+ button.removeAttribute("aria-disabled");
1556
+ return;
1557
+ }
1558
+ button.setAttribute("disabled", "");
1559
+ button.setAttribute("aria-disabled", "true");
1560
+ }
1561
+ #openModal() {
1562
+ if (!this.#offer || document.querySelector(ApplePayModal.tag)) return;
1563
+ trackExpressWalletClick(this.#offer.sku_id);
1564
+ ApplePayModal.define();
1565
+ const modal = document.createElement(ApplePayModal.tag);
1566
+ modal.offer = this.#offer;
1567
+ modal.country = this.#country;
1568
+ if (this.#sessionData?.loggedIn) {
1569
+ modal.dataset.loggedIn = "true";
1570
+ }
1571
+ modal.open();
1572
+ }
1573
+ async #fetchSession() {
1574
+ try {
1575
+ return await getExpressCheckoutSession();
1576
+ } catch {
1577
+ return null;
1578
+ }
1579
+ }
1580
+ async #fetchPaymentOptions() {
1581
+ try {
1582
+ return await getPaymentOptions({
1583
+ sku: this.#offer.sku_id,
1584
+ countryCode: this.#country,
1585
+ currencyCode: this.#offer.price?.currency_code ?? DEFAULT_CURRENCY,
1586
+ checkoutUrl: this.#returnUrl
1587
+ });
1588
+ } catch {
1589
+ return null;
1590
+ }
1591
+ }
1592
+ #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokens) {
1593
+ const offer = this.#offer;
1594
+ const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
1595
+ return {
1596
+ countryCode: country,
1597
+ currencyCode: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1598
+ supportedNetworks: paymentOptions.options.apple_pay.settings.supported_networks,
1599
+ totalAmount,
1600
+ totalLabel: TOTAL_LABEL,
1601
+ skuId,
1602
+ returnUrl: this.#returnUrl,
1603
+ userLoggedIn: sessionData.loggedIn,
1604
+ onPaymentAuthorized: (event, session) => handlePaymentAuthorized(event, session, sessionData, {
1605
+ skuId,
1606
+ country,
1607
+ returnUrl: this.#returnUrl,
1608
+ queryString: buildQueryString(),
1609
+ supportedBillingCountries: this.#supportedBillingCountries,
1610
+ recaptchaTokens
1611
+ })
1612
+ };
1613
+ }
1614
+ async #initiateApplePay(skuId, country) {
1615
+ try {
1616
+ const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1617
+ const paymentOptions = this.#paymentOptions;
1618
+ const recaptchaTokens = await getPurchaseRecaptchaTokens();
1619
+ startApplePaySession(
1620
+ this.#buildSessionParams(
1621
+ skuId,
1622
+ country,
1623
+ sessionData,
1624
+ paymentOptions,
1625
+ recaptchaTokens
1626
+ )
1627
+ );
1628
+ } catch (error) {
1629
+ console.error(ERROR_INITIATE, error);
1630
+ }
1631
+ }
1632
+ };
1633
+
1634
+ // src/index.ts
1635
+ function defineApplePayElements() {
1636
+ ApplePayButton.define();
1637
+ ApplePayModal.define();
1638
+ }
1639
+
1640
+ // src/stories/fixtures/offer.ts
1641
+ var STORYBOOK_SAMPLE_OFFER = {
1642
+ sku_id: "TEG-APPLEPAY-STORY-001",
1643
+ display_name: "Economist Digital",
1644
+ price: {
1645
+ purchase: 12,
1646
+ pre_discount: 24,
1647
+ currency_code: "USD",
1648
+ discounted: true,
1649
+ tax: { inclusive: false }
1650
+ },
1651
+ product: {
1652
+ type: "subscription",
1653
+ variant: "digital",
1654
+ offer_type: "standard"
1655
+ },
1656
+ subscription_options: {
1657
+ free_trial: false,
1658
+ renewal_interval: { duration: 1, unit: "month" },
1659
+ renewal_price: 12,
1660
+ auto_renewal: true,
1661
+ variable_term: false,
1662
+ display_name: "Monthly"
1663
+ }
1664
+ };
1665
+
1666
+ // src/stories/utils/story-env.ts
1667
+ var STORYBOOK_WALLET_BASE_URL = "https://storybook-wallet.economist.com";
1668
+ var STORYBOOK_SAFARI_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15";
1669
+ var defaultSessionResponse = {
1670
+ loggedIn: false,
1671
+ userType: "new-eligible-user"
1672
+ };
1673
+ var resetStoryEnvironment = null;
1674
+ var createFetchMock = (sessionResponse, sessionStatus = 200) => {
1675
+ return async (input) => {
1676
+ const requestUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1677
+ if (requestUrl.endsWith("/v1/wallet/session")) {
1678
+ if (sessionStatus >= 400) {
1679
+ return new Response("Session initialization failed", {
1680
+ status: sessionStatus,
1681
+ statusText: "Session Init Error"
1682
+ });
1683
+ }
1684
+ return new Response(JSON.stringify(sessionResponse), {
1685
+ status: 200,
1686
+ headers: { "content-type": "application/json" }
1687
+ });
1688
+ }
1689
+ return new Response("Not Found", {
1690
+ status: 404,
1691
+ statusText: "Not Found"
1692
+ });
1693
+ };
1694
+ };
1695
+ var applyApplePayStoryEnvironment = ({
1696
+ applePaySupported,
1697
+ sessionResponse = defaultSessionResponse,
1698
+ sessionStatus = 200,
1699
+ debugBypassEnabled = false,
1700
+ bypassApplePayChecks: bypassApplePayChecks2 = false
1701
+ }) => {
1702
+ if (resetStoryEnvironment) {
1703
+ resetStoryEnvironment();
1704
+ resetStoryEnvironment = null;
1705
+ }
1706
+ const originalFetch = globalThis.fetch;
1707
+ const originalHref = window.location.href;
1708
+ const originalUserAgent = navigator.userAgent;
1709
+ const originalMaxTouchPoints = navigator.maxTouchPoints;
1710
+ const originalApplePaySessionDescriptor = Object.getOwnPropertyDescriptor(
1711
+ window,
1712
+ "ApplePaySession"
1713
+ );
1714
+ const mockApplePaySession = {
1715
+ canMakePayments: () => applePaySupported,
1716
+ canMakePaymentsWithActiveCard: async () => applePaySupported
1717
+ };
1718
+ Object.defineProperty(window, "ApplePaySession", {
1719
+ configurable: true,
1720
+ writable: true,
1721
+ value: mockApplePaySession
1722
+ });
1723
+ Object.defineProperty(navigator, "userAgent", {
1724
+ configurable: true,
1725
+ writable: true,
1726
+ value: STORYBOOK_SAFARI_UA
1727
+ });
1728
+ Object.defineProperty(navigator, "maxTouchPoints", {
1729
+ configurable: true,
1730
+ writable: true,
1731
+ value: 0
1732
+ });
1733
+ globalThis.fetch = createFetchMock(sessionResponse, sessionStatus);
1734
+ const nextUrl = new URL(window.location.href);
1735
+ if (bypassApplePayChecks2) {
1736
+ nextUrl.searchParams.set("BYPASS_APPLE_PAY_CHECKS", "1");
1737
+ } else {
1738
+ nextUrl.searchParams.delete("BYPASS_APPLE_PAY_CHECKS");
1739
+ }
1740
+ window.history.replaceState({}, "", nextUrl.toString());
1741
+ configureApplePay({
1742
+ merchantId: "merchant.com.economist.storybook",
1743
+ walletApiBaseUrl: STORYBOOK_WALLET_BASE_URL,
1744
+ debugBypassEnabled
1745
+ });
1746
+ resetStoryEnvironment = () => {
1747
+ resetApplePayConfig();
1748
+ globalThis.fetch = originalFetch;
1749
+ window.history.replaceState({}, "", originalHref);
1750
+ Object.defineProperty(navigator, "userAgent", {
1751
+ configurable: true,
1752
+ writable: true,
1753
+ value: originalUserAgent
1754
+ });
1755
+ Object.defineProperty(navigator, "maxTouchPoints", {
1756
+ configurable: true,
1757
+ writable: true,
1758
+ value: originalMaxTouchPoints
1759
+ });
1760
+ if (originalApplePaySessionDescriptor) {
1761
+ Object.defineProperty(
1762
+ window,
1763
+ "ApplePaySession",
1764
+ originalApplePaySessionDescriptor
1765
+ );
1766
+ return;
1767
+ }
1768
+ delete window.ApplePaySession;
1769
+ };
1770
+ };
1771
+ var withApplePayStoryEnvironment = (options, renderFn) => (args) => {
1772
+ applyApplePayStoryEnvironment(options);
1773
+ return renderFn(args);
1774
+ };
1775
+
1776
+ // src/apple-pay-button.stories.ts
1777
+ var APPLE_PAY_BUTTON_TAG = "teg-apple-pay-button";
1778
+ var renderButton = ({ country }) => {
1779
+ defineApplePayElements();
1780
+ const container = document.createElement("div");
1781
+ container.style.padding = "24px";
1782
+ const button = document.createElement(
1783
+ APPLE_PAY_BUTTON_TAG
1784
+ );
1785
+ button.offer = STORYBOOK_SAMPLE_OFFER;
1786
+ button.country = country;
1787
+ container.appendChild(button);
1788
+ return container;
1789
+ };
1790
+ var meta = {
1791
+ title: "Web Apple Pay/ApplePayButton",
1792
+ tags: ["autodocs"],
1793
+ parameters: {
1794
+ docs: {
1795
+ inlineStories: false
1796
+ }
1797
+ },
1798
+ args: {
1799
+ country: "US"
1800
+ },
1801
+ argTypes: {
1802
+ country: { control: "text" }
1803
+ },
1804
+ render: renderButton
1805
+ };
1806
+ var apple_pay_button_stories_default = meta;
1807
+ var DefaultEligible = {
1808
+ render: withApplePayStoryEnvironment(
1809
+ {
1810
+ applePaySupported: true,
1811
+ debugBypassEnabled: true,
1812
+ bypassApplePayChecks: true
1813
+ },
1814
+ renderButton
1815
+ )
1816
+ };
1817
+ var NotSupported = {
1818
+ parameters: {
1819
+ docs: {
1820
+ disable: true
1821
+ }
1822
+ },
1823
+ render: withApplePayStoryEnvironment(
1824
+ { applePaySupported: false },
1825
+ renderButton
1826
+ )
1827
+ };
1828
+ var SessionInitErrorFallback = {
1829
+ parameters: {
1830
+ docs: {
1831
+ disable: true
1832
+ }
1833
+ },
1834
+ render: withApplePayStoryEnvironment(
1835
+ {
1836
+ applePaySupported: true,
1837
+ sessionStatus: 500,
1838
+ debugBypassEnabled: true,
1839
+ bypassApplePayChecks: true
1840
+ },
1841
+ renderButton
1842
+ )
1843
+ };
1844
+ var LoggedInModalPropagation = {
1845
+ parameters: {
1846
+ docs: {
1847
+ disable: true
1848
+ }
1849
+ },
1850
+ render: withApplePayStoryEnvironment(
1851
+ {
1852
+ applePaySupported: true,
1853
+ sessionResponse: {
1854
+ loggedIn: true,
1855
+ emailAddress: "storybook-user@economist.com",
1856
+ contactId: "STORYBOOK-CONT-1",
1857
+ userType: "new-eligible-user"
1858
+ },
1859
+ debugBypassEnabled: true,
1860
+ bypassApplePayChecks: true
1861
+ },
1862
+ renderButton
1863
+ )
1864
+ };
1865
+ var EligibleCAFlow = {
1866
+ parameters: {
1867
+ docs: {
1868
+ disable: true
1869
+ }
1870
+ },
1871
+ args: {
1872
+ country: "CA"
1873
+ },
1874
+ render: withApplePayStoryEnvironment(
1875
+ {
1876
+ applePaySupported: true,
1877
+ debugBypassEnabled: true,
1878
+ bypassApplePayChecks: true
1879
+ },
1880
+ renderButton
1881
+ )
1882
+ };
1883
+ var ActiveSubscriberHidden = {
1884
+ parameters: {
1885
+ docs: {
1886
+ disable: true
1887
+ }
1888
+ },
1889
+ render: withApplePayStoryEnvironment(
1890
+ {
1891
+ applePaySupported: true,
1892
+ sessionResponse: {
1893
+ loggedIn: true,
1894
+ emailAddress: "subscriber@economist.com",
1895
+ contactId: "STORYBOOK-SUBSCRIBER-1",
1896
+ userType: "active-subscription"
1897
+ },
1898
+ debugBypassEnabled: true,
1899
+ bypassApplePayChecks: true
1900
+ },
1901
+ renderButton
1902
+ )
1903
+ };
1904
+ var B2BUserHidden = {
1905
+ parameters: {
1906
+ docs: {
1907
+ disable: true
1908
+ }
1909
+ },
1910
+ render: withApplePayStoryEnvironment(
1911
+ {
1912
+ applePaySupported: true,
1913
+ sessionResponse: {
1914
+ loggedIn: true,
1915
+ emailAddress: "b2b-user@economist.com",
1916
+ contactId: "STORYBOOK-B2B-1",
1917
+ userType: "b2b-user"
1918
+ },
1919
+ debugBypassEnabled: true,
1920
+ bypassApplePayChecks: true
1921
+ },
1922
+ renderButton
1923
+ )
1924
+ };
1925
+ export {
1926
+ ActiveSubscriberHidden,
1927
+ B2BUserHidden,
1928
+ DefaultEligible,
1929
+ EligibleCAFlow,
1930
+ LoggedInModalPropagation,
1931
+ NotSupported,
1932
+ SessionInitErrorFallback,
1933
+ apple_pay_button_stories_default as default
1934
+ };