@economist/web-apple-pay 0.1.1-beta.0 → 1.0.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 (45) hide show
  1. package/dist/index.js +349 -1203
  2. package/dist/src/apple-pay-button.d.ts +4 -33
  3. package/dist/src/apple-pay-button.js +349 -1191
  4. package/dist/src/apple-pay-modal.js +319 -65
  5. package/dist/src/index.d.ts +1 -3
  6. package/dist/src/index.js +349 -1203
  7. package/dist/src/utils/pricing.js +6 -2
  8. package/package.json +4 -10
  9. package/README.md +0 -186
  10. package/dist/src/apple-pay-button.stories.js +0 -1934
  11. package/dist/src/apple-pay-modal.stories.js +0 -1778
  12. package/dist/src/clients/payment-checkout/apple-session.d.ts +0 -18
  13. package/dist/src/clients/payment-checkout/apple-session.js +0 -243
  14. package/dist/src/clients/payment-checkout/billing-contact-details.d.ts +0 -21
  15. package/dist/src/clients/payment-checkout/billing-contact-details.js +0 -109
  16. package/dist/src/clients/payment-checkout/config.d.ts +0 -9
  17. package/dist/src/clients/payment-checkout/config.js +0 -23
  18. package/dist/src/clients/payment-checkout/contact-validation.d.ts +0 -21
  19. package/dist/src/clients/payment-checkout/contact-validation.js +0 -50
  20. package/dist/src/clients/payment-checkout/detects.d.ts +0 -3
  21. package/dist/src/clients/payment-checkout/detects.js +0 -56
  22. package/dist/src/clients/payment-checkout/index.d.ts +0 -5
  23. package/dist/src/clients/payment-checkout/index.js +0 -83
  24. package/dist/src/clients/payment-checkout/messages.d.ts +0 -33
  25. package/dist/src/clients/payment-checkout/messages.js +0 -19
  26. package/dist/src/clients/payment-checkout/payment-handler.d.ts +0 -36
  27. package/dist/src/clients/payment-checkout/payment-handler.js +0 -389
  28. package/dist/src/clients/payment-checkout/session.d.ts +0 -2
  29. package/dist/src/clients/payment-checkout/session.js +0 -67
  30. package/dist/src/clients/payment-checkout/shipping-contact-details.d.ts +0 -10
  31. package/dist/src/clients/payment-checkout/shipping-contact-details.js +0 -83
  32. package/dist/src/clients/payment-checkout/types.d.ts +0 -118
  33. package/dist/src/clients/payment-checkout/types.js +0 -14
  34. package/dist/src/stories/fixtures/offer.d.ts +0 -2
  35. package/dist/src/stories/fixtures/offer.js +0 -28
  36. package/dist/src/stories/utils/story-env.d.ts +0 -11
  37. package/dist/src/stories/utils/story-env.js +0 -148
  38. package/dist/src/utils/billing-countries.d.ts +0 -19
  39. package/dist/src/utils/billing-countries.js +0 -263
  40. package/dist/src/utils/expressCheckoutRedirect.d.ts +0 -55
  41. package/dist/src/utils/expressCheckoutRedirect.js +0 -46
  42. package/dist/src/utils/expressCheckoutTracking.d.ts +0 -64
  43. package/dist/src/utils/expressCheckoutTracking.js +0 -84
  44. package/dist/src/utils/recaptcha.d.ts +0 -22
  45. package/dist/src/utils/recaptcha.js +0 -54
package/dist/index.js CHANGED
@@ -36,10 +36,14 @@ function formatPrice(price, currencyCode, shouldFixDecimalPlaces = true, roundUp
36
36
  const points = numberOfDecimalPoints(price);
37
37
  const calculatedPrice = () => {
38
38
  if (roundUp) {
39
- return (Math.ceil(price * PRICE_CONSTANTS.CENTS_MULTIPLIER) / PRICE_CONSTANTS.CENTS_MULTIPLIER).toFixed(PRICE_CONSTANTS.DECIMAL_PLACES);
39
+ return (Math.ceil(price * PRICE_CONSTANTS.CENTS_MULTIPLIER) / PRICE_CONSTANTS.CENTS_MULTIPLIER).toFixed(
40
+ PRICE_CONSTANTS.DECIMAL_PLACES
41
+ );
40
42
  }
41
43
  if (shouldFixDecimalPlaces) {
42
- return (Math.round(price * PRICE_CONSTANTS.CENTS_MULTIPLIER) / PRICE_CONSTANTS.CENTS_MULTIPLIER).toFixed(PRICE_CONSTANTS.DECIMAL_PLACES);
44
+ return (Math.round(price * PRICE_CONSTANTS.CENTS_MULTIPLIER) / PRICE_CONSTANTS.CENTS_MULTIPLIER).toFixed(
45
+ PRICE_CONSTANTS.DECIMAL_PLACES
46
+ );
43
47
  }
44
48
  return price;
45
49
  };
@@ -76,7 +80,306 @@ function ensureApplePayLogoSymbol() {
76
80
  }
77
81
 
78
82
  // 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';
83
+ var apple_pay_modal_template_default = `<style>
84
+ .apm-overlay {
85
+ position: fixed;
86
+ top: 0;
87
+ left: 0;
88
+ width: 100%;
89
+ height: 100%;
90
+ z-index: 97;
91
+ background: rgba(13, 13, 13, 0.7);
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ padding: var(--wall-gutter);
96
+ box-sizing: border-box;
97
+ }
98
+
99
+ .apm {
100
+ position: relative;
101
+ background: #fff;
102
+ border-radius: 8px 8px 0 0;
103
+ border: 1px solid #d9d9d9;
104
+ padding: 24px;
105
+ width: 400px;
106
+ max-width: 400px;
107
+ box-sizing: border-box;
108
+ font-family: var(--wall-type-system-sans);
109
+ box-shadow: 0 4px 16px 0 rgba(13, 13, 13, 0.15);
110
+ }
111
+
112
+ .apm__close {
113
+ position: absolute;
114
+ top: 24px;
115
+ right: 21px;
116
+ background: none;
117
+ border: none;
118
+ cursor: pointer;
119
+ padding: 0;
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ }
124
+
125
+ .apm__close:focus {
126
+ outline: solid 2px var(--focus-border-color, #121212);
127
+ border-radius: 0.25rem;
128
+ }
129
+
130
+ .apm__offer-name {
131
+ font-family: var(--wall-type-system-sans);
132
+ font-size: 1.4375rem;
133
+ font-weight: 500;
134
+ color: #0d0d0d;
135
+ line-height: 28px;
136
+ font-style: normal;
137
+ margin: 0 1.5rem 0.5rem 0;
138
+ }
139
+
140
+ .apm__due-today {
141
+ color: #1a1a1a;
142
+ font-family: var(--wall-type-system-sans);
143
+ font-size: 15px;
144
+ font-style: normal;
145
+ font-weight: 400;
146
+ line-height: 20px;
147
+ margin: 0 0 1rem 0;
148
+ }
149
+
150
+ .apm__rule {
151
+ border: none;
152
+ border-top: 1px solid #d9d9d9;
153
+ margin: 0 0 1rem 0;
154
+ }
155
+
156
+ .apm__terms {
157
+ color: #1a1a1a;
158
+ font-family: var(--wall-type-system-sans);
159
+ font-size: 15px;
160
+ font-style: normal;
161
+ font-weight: 400;
162
+ line-height: 1.333;
163
+ margin: 0 0 1rem 0;
164
+ }
165
+
166
+ .apm__terms-bullets {
167
+ list-style: disc outside;
168
+ padding-left: 1.25rem;
169
+ margin: 0 0 0.75rem 0;
170
+ }
171
+
172
+ .apm__terms-bullets li {
173
+ margin-bottom: 0.375rem;
174
+ }
175
+
176
+ .apm__terms-legal {
177
+ margin: 0;
178
+ }
179
+
180
+ .apm__checkbox-label {
181
+ display: flex;
182
+ align-items: flex-start;
183
+ gap: 12px;
184
+ font-family: var(--wall-type-system-sans);
185
+ font-size: 0.9375rem;
186
+ color: #0d0d0d;
187
+ line-height: 1.4;
188
+ cursor: pointer;
189
+ margin: 0 0 8px 0;
190
+ }
191
+
192
+ .apm__checkbox-wrapper {
193
+ position: relative;
194
+ flex-shrink: 0;
195
+ width: 20px;
196
+ height: 20px;
197
+ margin-top: 1px;
198
+ }
199
+
200
+ .apm__checkbox-input {
201
+ position: absolute;
202
+ opacity: 0;
203
+ width: 100%;
204
+ height: 100%;
205
+ cursor: pointer;
206
+ margin: 0;
207
+ z-index: 1;
208
+ }
209
+
210
+ .apm__checkbox-custom {
211
+ position: absolute;
212
+ top: 0;
213
+ left: 0;
214
+ width: 20px;
215
+ height: 20px;
216
+ pointer-events: none;
217
+ }
218
+
219
+ .apm__checkbox-custom svg {
220
+ display: none;
221
+ width: 20px;
222
+ height: 20px;
223
+ }
224
+
225
+ .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-unchecked {
226
+ display: block;
227
+ }
228
+
229
+ .apm__checkbox-input:checked ~ .apm__checkbox-custom .svg-checked {
230
+ display: block;
231
+ }
232
+
233
+ .apm__checkbox-label--error .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-unchecked {
234
+ display: none;
235
+ }
236
+
237
+ .apm__checkbox-label--error .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-error {
238
+ display: block;
239
+ }
240
+
241
+ .apm__checkbox-input:focus-visible ~ .apm__checkbox-custom {
242
+ outline: solid 2px #c91d42;
243
+ outline-offset: 2px;
244
+ border-radius: 4px;
245
+ }
246
+
247
+ .apm__checkbox-label a {
248
+ color: #1a1a1a;
249
+ font-weight: 700;
250
+ text-decoration: underline;
251
+ }
252
+
253
+ .apm__error {
254
+ display: none;
255
+ align-items: center;
256
+ gap: 12px;
257
+ color: #0d0d0d;
258
+ font-family: var(--wall-type-system-sans);
259
+ font-size: 17px;
260
+ font-style: normal;
261
+ font-weight: 500;
262
+ line-height: 24px;
263
+ margin-bottom: 16px;
264
+ }
265
+
266
+ .apm__error--visible {
267
+ display: flex;
268
+ }
269
+
270
+ .apm__cta {
271
+ display: flex;
272
+ width: 100%;
273
+ height: 44px;
274
+ padding: 6px 16px;
275
+ justify-content: center;
276
+ align-items: center;
277
+ gap: 6px;
278
+ border-radius: 4px;
279
+ background: #000;
280
+ border: none;
281
+ cursor: pointer;
282
+ color: #fff;
283
+ text-align: center;
284
+ font-family: 'SF Pro', var(--wall-type-system-sans, system-ui, sans-serif);
285
+ font-size: 20px;
286
+ font-style: normal;
287
+ font-weight: 500;
288
+ line-height: normal;
289
+ }
290
+
291
+ .apm__cta svg {
292
+ flex-shrink: 0;
293
+ display: block;
294
+ }
295
+
296
+ @media (min-width: 360px) and (max-width: 600px) {
297
+ .apm {
298
+ width: 390px;
299
+ max-width: 390px;
300
+ }
301
+ }
302
+
303
+ @media (min-width: 601px) {
304
+ .apm {
305
+ border-radius: 8px;
306
+ }
307
+ }
308
+
309
+ @media (max-width: 359px) {
310
+ .apm {
311
+ width: 320px;
312
+ max-width: 320px;
313
+ }
314
+ }
315
+
316
+ @media (max-width: 419px) {
317
+ .apm__cta {
318
+ font-size: 18px;
319
+ }
320
+ }
321
+ </style>
322
+
323
+ <div class="apm-overlay" id="apple-pay-modal-overlay">
324
+ <div class="apm" role="dialog" aria-modal="true" aria-labelledby="apple-pay-modal-offer-name" id="apple-pay-modal" tabindex="-1">
325
+ <button class="apm__close" id="apple-pay-modal-close" aria-label="Close">
326
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
327
+ <g clip-path="url(#clip0_15712_93054)">
328
+ <rect width="24" height="24" fill="white" fill-opacity="0.01" />
329
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H24V24H0V0Z" fill="white" fill-opacity="0.01" />
330
+ <path fill-rule="evenodd" clip-rule="evenodd" 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" fill="#333333" />
331
+ </g>
332
+ <defs>
333
+ <clipPath id="clip0_15712_93054">
334
+ <rect width="24" height="24" fill="white" />
335
+ </clipPath>
336
+ </defs>
337
+ </svg>
338
+ </button>
339
+ <p class="apm__offer-name" id="apple-pay-modal-offer-name"></p>
340
+ <p class="apm__due-today" id="apple-pay-modal-due-today"></p>
341
+ <hr class="apm__rule" />
342
+ <div class="apm__terms" id="apple-pay-modal-terms"></div>
343
+ <label class="apm__checkbox-label" id="apple-pay-modal-checkbox-label" for="apple-pay-modal-checkbox">
344
+ <div class="apm__checkbox-wrapper">
345
+ <input class="apm__checkbox-input" type="checkbox" id="apple-pay-modal-checkbox" />
346
+ <span class="apm__checkbox-custom">
347
+ <svg class="svg-unchecked" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
348
+ <path 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" fill="white" stroke="#595959" />
349
+ </svg>
350
+ <svg class="svg-checked" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
351
+ <g clip-path="url(#clip0_20014_9555)">
352
+ <path 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" fill="#333333" />
353
+ <path d="M10.2857 17L6 12.6814L7.20857 11.4635L10.2857 14.5557L16.7914 8L18 9.22649L10.2857 17Z" fill="white" />
354
+ </g>
355
+ <defs>
356
+ <clipPath id="clip0_20014_9555">
357
+ <path 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" fill="white" />
358
+ </clipPath>
359
+ </defs>
360
+ </svg>
361
+ <svg class="svg-error" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
362
+ <path 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" fill="white" stroke="#C91D42" stroke-width="2" />
363
+ </svg>
364
+ </span>
365
+ </div>
366
+ <span id="apple-pay-modal-checkbox-text"></span>
367
+ </label>
368
+ <div class="apm__error" id="apple-pay-modal-error" role="alert">
369
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
370
+ <path fill-rule="evenodd" clip-rule="evenodd" 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" fill="#C91D42" />
371
+ </svg>
372
+ Please accept the terms to continue
373
+ </div>
374
+ <button class="apm__cta" id="apple-pay-modal-cta">
375
+ Continue with
376
+ <svg width="51" height="22" aria-label="Apple Pay" role="img">
377
+ <use href="#apple-pay-logo" />
378
+ </svg>
379
+ </button>
380
+ </div>
381
+ </div>
382
+ `;
80
383
 
81
384
  // src/apple-pay-modal.ts
82
385
  function getRenewalDateText(unit, duration) {
@@ -101,11 +404,7 @@ function getRenewalDateText(unit, duration) {
101
404
  default:
102
405
  break;
103
406
  }
104
- return date.toLocaleString("en-US", {
105
- year: "numeric",
106
- month: "long",
107
- day: "numeric"
108
- });
407
+ return date.toLocaleString("en-US", { year: "numeric", month: "long", day: "numeric" });
109
408
  }
110
409
  var MIN_TERM_DURATION_PATTERN = /^(\d+)\s+(\w+)s?$/;
111
410
  function getMinTermDurationUnits(minTermContractLength) {
@@ -142,12 +441,6 @@ function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
142
441
  }
143
442
  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
443
  }
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
444
  function renewalPeriodText(unit, duration) {
152
445
  switch (unit) {
153
446
  case "week":
@@ -321,70 +614,36 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
321
614
  this.remove();
322
615
  return;
323
616
  }
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
- );
617
+ const offerNameElement = this.querySelector("#apple-pay-modal-offer-name");
618
+ const dueTodayElement = this.querySelector("#apple-pay-modal-due-today");
619
+ const termsElement = this.querySelector("#apple-pay-modal-terms");
333
620
  if (offerNameElement) {
334
621
  offerNameElement.textContent = getApplePayProductName(offer);
335
622
  }
336
623
  if (dueTodayElement) {
337
624
  const taxSuffix = offer.price.purchase === 0 || offer.price.tax?.inclusive ? "" : "*";
338
- const duePrice = formatPrice(
339
- offer.price.purchase,
340
- offer.price.currency_code
341
- );
625
+ const duePrice = formatPrice(offer.price.purchase, offer.price.currency_code);
342
626
  dueTodayElement.innerHTML = duePrice ? `Due today: <strong>${duePrice}${taxSuffix}</strong>` : "";
343
627
  }
344
628
  if (termsElement) {
345
- termsElement.innerHTML = renderTermsHtml(
346
- generateApplePayModalTerms(offer, country)
347
- );
629
+ termsElement.innerHTML = renderTermsHtml(generateApplePayModalTerms(offer, country));
348
630
  }
349
631
  const isUS = country === "US";
350
632
  const isLoggedIn = this.dataset.loggedIn === "true";
351
633
  const isOfferNYT = offer.product?.offer_type === "bogof_nyt";
352
634
  const isAutoRenewal = offer.subscription_options?.auto_renewal ?? true;
353
- const checkboxTextElement = this.querySelector(
354
- "#apple-pay-modal-checkbox-text"
355
- );
635
+ const checkboxTextElement = this.querySelector("#apple-pay-modal-checkbox-text");
356
636
  if (checkboxTextElement) {
357
- checkboxTextElement.innerHTML = getCheckboxTermsHtml(
358
- isOfferNYT,
359
- isLoggedIn,
360
- isAutoRenewal
361
- );
637
+ checkboxTextElement.innerHTML = getCheckboxTermsHtml(isOfferNYT, isLoggedIn, isAutoRenewal);
362
638
  }
363
- const checkboxLabel = this.querySelector(
364
- "#apple-pay-modal-checkbox-label"
365
- );
366
- const checkbox = this.querySelector(
367
- "#apple-pay-modal-checkbox"
368
- );
639
+ const checkboxLabel = this.querySelector("#apple-pay-modal-checkbox-label");
640
+ const checkbox = this.querySelector("#apple-pay-modal-checkbox");
369
641
  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
- );
642
+ const errorElement = this.querySelector("#apple-pay-modal-error");
376
643
  if (!isUS) {
377
644
  checkboxLabel?.remove();
378
645
  errorElement?.remove();
379
- if (passiveTermsElement) {
380
- passiveTermsElement.innerHTML = getPassiveTermsHtml(
381
- isOfferNYT,
382
- isLoggedIn,
383
- isAutoRenewal
384
- );
385
- }
386
646
  } else {
387
- passiveTermsElement?.remove();
388
647
  checkbox?.addEventListener("change", () => {
389
648
  if (checkbox.checked) {
390
649
  errorElement?.classList.remove("apm__error--visible");
@@ -407,10 +666,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
407
666
  );
408
667
  this.close();
409
668
  });
410
- this.querySelector("#apple-pay-modal-close")?.addEventListener(
411
- "click",
412
- () => this.close()
413
- );
669
+ this.querySelector("#apple-pay-modal-close")?.addEventListener("click", () => this.close());
414
670
  }
415
671
  open() {
416
672
  this.#triggerElement = document.activeElement;
@@ -428,9 +684,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
428
684
  }
429
685
  close() {
430
686
  if (this.#keyHandler) {
431
- document.removeEventListener("keydown", this.#keyHandler, {
432
- capture: true
433
- });
687
+ document.removeEventListener("keydown", this.#keyHandler, { capture: true });
434
688
  this.#keyHandler = null;
435
689
  }
436
690
  this.remove();
@@ -439,909 +693,10 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
439
693
  };
440
694
 
441
695
  // 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
-
459
- // src/clients/payment-checkout/detects.ts
460
- var isAppleDeviceOrSafari = () => {
461
- const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
462
- const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
463
- return isIOSOrMacTouchDevice || isSafariBrowser;
464
- };
465
- var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
466
- var isApplePayAvailable = async () => {
467
- console.log("[web-apple-pay] isApplePayAvailable: checking availability");
468
- if (bypassApplePayChecks()) {
469
- console.log("[web-apple-pay] isApplePayAvailable: bypass active \u2192 true");
470
- return true;
471
- }
472
- if (!isAppleDeviceOrSafari()) {
473
- console.log("[web-apple-pay] isApplePayAvailable: not Apple device or Safari \u2192 false");
474
- return false;
475
- }
476
- const applePaySession = window.ApplePaySession;
477
- if (!applePaySession) {
478
- console.log("[web-apple-pay] isApplePayAvailable: ApplePaySession not found on window \u2192 false");
479
- return false;
480
- }
481
- try {
482
- if (typeof applePaySession.canMakePayments !== "function") {
483
- console.log("[web-apple-pay] isApplePayAvailable: canMakePayments is not a function \u2192 false");
484
- return false;
485
- }
486
- if (!applePaySession.canMakePayments()) {
487
- console.log("[web-apple-pay] isApplePayAvailable: canMakePayments() returned false \u2192 false");
488
- return false;
489
- }
490
- const merchantId = getApplePayConfig()?.merchantId;
491
- if (!merchantId) {
492
- console.warn(
493
- "[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
494
- );
495
- return false;
496
- }
497
- console.log("[web-apple-pay] isApplePayAvailable : calling canMakePaymentsWithActiveCard", { merchantId });
498
- const result = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
499
- console.log("[web-apple-pay] isApplePayAvailable: canMakePaymentsWithActiveCard \u2192", result);
500
- return result;
501
- } catch (error) {
502
- console.error("Error checking Apple Pay active card status:", error);
503
- return false;
504
- }
505
- };
506
-
507
- // src/clients/payment-checkout/types.ts
508
- var ApiError = class extends Error {
509
- status;
510
- body;
511
- constructor(status, body) {
512
- super(`API request failed with status ${status}`);
513
- this.name = "ApiError";
514
- this.status = status;
515
- this.body = body;
516
- }
517
- };
518
-
519
- // src/clients/payment-checkout/index.ts
520
- var getWalletApiBaseUrl = () => {
521
- const config = getApplePayConfig();
522
- if (!config?.walletApiBaseUrl) {
523
- throw new Error(
524
- "[web-apple-pay] Missing walletApiBaseUrl. Call configureApplePay({ merchantId, walletApiBaseUrl, ... }) before making wallet API requests."
525
- );
526
- }
527
- return config.walletApiBaseUrl;
528
- };
529
- var apiFetch = async (path, options = {}) => {
530
- const baseUrl = getWalletApiBaseUrl();
531
- const method = (options.method ?? "GET").toUpperCase();
532
- const hasBody = options.body !== void 0 && options.body !== null;
533
- const headers = new Headers(options.headers);
534
- if (!headers.has("Accept")) {
535
- headers.set("Accept", "application/json");
536
- }
537
- if (!headers.has("Content-Type")) {
538
- if (hasBody || method !== "GET" && method !== "HEAD") {
539
- headers.set("Content-Type", "application/json");
540
- }
541
- }
542
- const response = await fetch(`${baseUrl}${path}`, {
543
- ...options,
544
- headers
545
- });
546
- if (!response.ok) {
547
- const contentType = response.headers.get("content-type") || "";
548
- if (contentType.includes("application/json")) {
549
- const errorBody = await response.json();
550
- throw new ApiError(response.status, errorBody);
551
- }
552
- const errorText = await response.text();
553
- throw new Error(
554
- `API request failed: ${response.status} ${response.statusText} - ${errorText}`
555
- );
556
- }
557
- return response.json();
558
- };
559
- var getPaymentOptions = (data) => {
560
- const { sku, countryCode, currencyCode, checkoutUrl } = data;
561
- const params = new URLSearchParams({ checkoutUrl });
562
- return apiFetch(
563
- `/v1/wallet/payment-options/sku/${sku}/${countryCode}/${currencyCode}?${params}`,
564
- { method: "GET" }
565
- );
566
- };
567
- var validateAppleMerchant = (data) => {
568
- return apiFetch("/v1/wallet/validate-merchant", {
569
- method: "POST",
570
- body: JSON.stringify(data)
571
- });
572
- };
573
- var performPurchase = (data) => {
574
- return apiFetch("/v1/wallet/purchase", {
575
- method: "POST",
576
- body: JSON.stringify(data),
577
- credentials: "include"
578
- });
579
- };
580
-
581
- // src/clients/payment-checkout/session.ts
582
- var getExpressCheckoutSession = () => {
583
- return apiFetch("/v1/wallet/session", {
584
- method: "GET",
585
- credentials: "include"
586
- });
587
- };
588
-
589
- // src/utils/expressCheckoutRedirect.ts
590
- var captureOriginUrl = () => window.location.href;
591
- var buildQueryString = () => {
592
- const countryCode = new URLSearchParams(window.location.search).get(
593
- "country"
594
- );
595
- return countryCode ? `&country=${encodeURIComponent(countryCode)}` : "";
596
- };
597
- var decodeSku = (sku) => {
598
- try {
599
- return atob(sku.substring(sku.indexOf("-") + 1));
600
- } catch {
601
- return sku;
602
- }
603
- };
604
- var redirectToCheckoutComplete = (params) => {
605
- const url = new URL("/checkout/complete", window.location.origin);
606
- url.searchParams.set("basketId", params.basketId);
607
- if (params.queryString) {
608
- const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
609
- new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
610
- }
611
- url.searchParams.set("lmo_offer", params.skuId);
612
- url.searchParams.set("status", params.status);
613
- url.searchParams.set("redirectToCheckout", "true");
614
- url.searchParams.set("returnUrl", params.returnUrl);
615
- return url.pathname + url.search;
616
- };
617
- var redirectToCheckoutFallback = (params) => {
618
- const url = new URL(window.location.href);
619
- url.pathname = "/checkout";
620
- url.searchParams.set("lmo_offer", params.skuId);
621
- if (params.inlineError)
622
- url.searchParams.set("inlineError", params.inlineError);
623
- if (params.email) url.searchParams.set("email", params.email);
624
- if (params.emailError) url.searchParams.set("emailError", params.emailError);
625
- url.searchParams.set("returnUrl", params.returnUrl);
626
- return url.pathname + url.search;
627
- };
628
-
629
- // src/utils/recaptcha.ts
630
- var loadRecaptchaScript = (siteKey) => {
631
- return new Promise((resolve, reject) => {
632
- if (typeof grecaptcha !== "undefined") return resolve();
633
- const existingScript = document.querySelector(
634
- 'script[src*="recaptcha/api.js"]'
635
- );
636
- if (existingScript) {
637
- existingScript.addEventListener("load", () => resolve());
638
- existingScript.addEventListener(
639
- "error",
640
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
641
- );
642
- return;
643
- }
644
- const script = document.createElement("script");
645
- script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
646
- script.async = true;
647
- script.defer = true;
648
- script.onload = () => resolve();
649
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
650
- document.head.appendChild(script);
651
- });
652
- };
653
- var getRecaptchaToken = async (action) => {
654
- const siteKey = getApplePayConfig()?.recaptchaSiteKey;
655
- if (!siteKey) {
656
- throw new Error(
657
- "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
658
- );
659
- }
660
- await loadRecaptchaScript(siteKey);
661
- return new Promise((resolve, reject) => {
662
- grecaptcha.ready(() => {
663
- grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
664
- });
665
- });
666
- };
667
- var getPurchaseRecaptchaTokens = async () => {
668
- const [checkEligibility, newBasket, registerUser] = await Promise.all([
669
- getRecaptchaToken("checkEligibility"),
670
- getRecaptchaToken("newBasket"),
671
- getRecaptchaToken("registerUser")
672
- ]);
673
- return { checkEligibility, newBasket, registerUser };
674
- };
675
-
676
- // src/utils/expressCheckoutTracking.ts
677
- var EXPRESS_CHECKOUT_EVENTS = {
678
- /** Wallet button displayed */
679
- IMPRESSION: "express_wallet_impression",
680
- /** Wallet button clicked */
681
- BUTTON_CLICK: "express_wallet_click",
682
- /** Apple Pay sheet opened */
683
- SHEET_OPEN: "wallet_payment_sheet_open",
684
- /** Payment authorised by the user in the Apple Pay sheet */
685
- AUTHORISED: "wallet_payment_authorised",
686
- /** Purchase completed — BFF returned success */
687
- SUCCESS: "wallet_payment_success",
688
- /** Payment failed — BFF returned error */
689
- FAILURE: "wallet_payment_failure",
690
- /** Redirect to standard checkout (any fallback path) */
691
- FALLBACK_CHECKOUT: "fallback_checkout"
692
- };
693
- var getDevice = () => {
694
- const ua = navigator.userAgent;
695
- if (/tablet|ipad/i.test(ua)) return "tablet";
696
- if (/mobile|iphone|android/i.test(ua)) return "mobile";
697
- return "desktop";
698
- };
699
- var emitTedlEvent = (event) => {
700
- window.tedl = window.tedl ?? {};
701
- window.tedl.events = window.tedl.events ?? [];
702
- window.tedl.events.push(event);
703
- };
704
- var track = (action, extra) => {
705
- emitTedlEvent({
706
- name: "user.journey",
707
- type: "checkout",
708
- action,
709
- info: "applepay",
710
- ...extra
711
- });
712
- };
713
- var trackExpressWalletImpression = (skuId, { entryPoint, country }) => {
714
- track(EXPRESS_CHECKOUT_EVENTS.IMPRESSION, {
715
- wallet_type: "apple_pay",
716
- entry_point: entryPoint,
717
- sku_id: skuId,
718
- country,
719
- device: getDevice()
720
- });
721
- };
722
- var trackExpressWalletClick = (skuId) => {
723
- track(EXPRESS_CHECKOUT_EVENTS.BUTTON_CLICK, { sku_id: skuId });
724
- };
725
- var trackWalletPaymentSheetOpen = (skuId) => {
726
- track(EXPRESS_CHECKOUT_EVENTS.SHEET_OPEN, { sku_id: skuId });
727
- };
728
- var trackWalletPaymentAuthorised = (skuId) => {
729
- track(EXPRESS_CHECKOUT_EVENTS.AUTHORISED, { sku_id: skuId });
730
- };
731
- var trackWalletPaymentSuccess = (skuId, basketId) => {
732
- track(EXPRESS_CHECKOUT_EVENTS.SUCCESS, {
733
- sku_id: skuId,
734
- basket_id: basketId
735
- });
736
- };
737
- var trackWalletPaymentFailure = (skuId, reason) => {
738
- track(EXPRESS_CHECKOUT_EVENTS.FAILURE, {
739
- sku_id: skuId,
740
- failure_reason: reason
741
- });
742
- };
743
- var trackFallbackCheckout = (skuId) => {
744
- track(EXPRESS_CHECKOUT_EVENTS.FALLBACK_CHECKOUT, { sku_id: skuId });
745
- };
746
- var trackValidationFieldError = (field, errorType) => {
747
- track(`validation_${field}_${errorType}`, {});
748
- };
749
-
750
- // src/clients/payment-checkout/messages.ts
751
- var MSG_MISSING_REQUIRED_FIELD = "Missing required field";
752
- var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a valid email address.";
753
- var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
754
- var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
755
- var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
756
- var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
757
- var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
758
- var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
759
-
760
- // src/clients/payment-checkout/apple-session.ts
761
- function startApplePaySession(config) {
762
- console.log("[web-apple-pay] startApplePaySession", {
763
- skuId: config.skuId,
764
- countryCode: config.countryCode,
765
- currencyCode: config.currencyCode,
766
- totalAmount: config.totalAmount,
767
- userLoggedIn: config.userLoggedIn
768
- });
769
- const paymentRequest = {
770
- countryCode: config.countryCode,
771
- currencyCode: config.currencyCode,
772
- merchantCapabilities: ["supports3DS"],
773
- supportedNetworks: config.supportedNetworks,
774
- total: {
775
- label: config.totalLabel,
776
- amount: config.totalAmount
777
- },
778
- requiredBillingContactFields: ["postalAddress", "name"],
779
- // Only request email from Apple Pay sheet if user is NOT logged in
780
- ...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
781
- };
782
- const session = new ApplePaySession(3, paymentRequest);
783
- session.onvalidatemerchant = async (event) => {
784
- console.log("[web-apple-pay] onvalidatemerchant: received", { validationURL: event.validationURL });
785
- try {
786
- const validationHost = new URL(event.validationURL).host;
787
- if (!validationHost.endsWith("apple.com")) {
788
- console.warn("[web-apple-pay] onvalidatemerchant: invalid host, aborting", { validationHost });
789
- trackFallbackCheckout(config.skuId);
790
- session.abort();
791
- window.location.href = redirectToCheckoutFallback({
792
- skuId: config.skuId,
793
- returnUrl: config.returnUrl,
794
- inlineError: MSG_PAYMENT_GENERIC_ERROR
795
- });
796
- return;
797
- }
798
- const recaptchaToken = await getRecaptchaToken("verifyMerchant");
799
- console.log("[web-apple-pay] onvalidatemerchant: calling validateAppleMerchant");
800
- const response = await validateAppleMerchant({
801
- validationURL: event.validationURL,
802
- recaptchaToken
803
- });
804
- if (!response?.merchantSession) {
805
- console.warn("[web-apple-pay] onvalidatemerchant: no merchantSession in response, aborting");
806
- trackFallbackCheckout(config.skuId);
807
- session.abort();
808
- window.location.href = redirectToCheckoutFallback({
809
- skuId: config.skuId,
810
- returnUrl: config.returnUrl,
811
- inlineError: MSG_PAYMENT_GENERIC_ERROR
812
- });
813
- return;
814
- }
815
- console.log("[web-apple-pay] onvalidatemerchant: merchant validated successfully");
816
- session.completeMerchantValidation(response.merchantSession);
817
- } catch (err) {
818
- console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
819
- trackFallbackCheckout(config.skuId);
820
- session.abort();
821
- window.location.href = redirectToCheckoutFallback({
822
- skuId: config.skuId,
823
- returnUrl: config.returnUrl,
824
- inlineError: MSG_PAYMENT_GENERIC_ERROR
825
- });
826
- }
827
- };
828
- session.onpaymentauthorized = (event) => {
829
- console.log("[web-apple-pay] onpaymentauthorized: payment authorized by user", { skuId: config.skuId });
830
- trackWalletPaymentAuthorised(config.skuId);
831
- config.onPaymentAuthorized(event, session);
832
- };
833
- session.oncancel = () => {
834
- console.log("[web-apple-pay] oncancel: payment sheet cancelled by user", { skuId: config.skuId });
835
- config.onCancel?.();
836
- };
837
- console.log("[web-apple-pay] startApplePaySession: beginning session");
838
- trackWalletPaymentSheetOpen(config.skuId);
839
- session.begin();
840
- }
841
-
842
- // src/clients/payment-checkout/contact-validation.ts
843
- var pushFieldError = (errors, billingOrShippingContact = "billing", field, errorType, errorText) => {
844
- const errorCode = billingOrShippingContact === "billing" ? "billingContactInvalid" : "shippingContactInvalid";
845
- errors.push(new ApplePayError(errorCode, field, errorText));
846
- trackValidationFieldError(field, errorType);
847
- return "";
848
- };
849
- var pushRequiredFieldError = (errors, billingOrShippingContact = "billing", field) => {
850
- return pushFieldError(
851
- errors,
852
- billingOrShippingContact,
853
- field,
854
- "required",
855
- MSG_MISSING_REQUIRED_FIELD
856
- );
857
- };
858
- var ContactValidationError = class extends Error {
859
- constructor(errors) {
860
- super(MSG_CONTACT_VALIDATION_INTERNAL);
861
- this.errors = errors;
862
- }
863
- };
864
-
865
- // src/clients/payment-checkout/billing-contact-details.ts
866
- var countriesWithoutCityField = ["KY", "CK", "NR", "TC"];
867
- var countriesRequiringAllFields = ["US", "CA", "GB"];
868
- var resolveCountryConfig = (country) => {
869
- const hasCityField = !countriesWithoutCityField.includes(country);
870
- const requiresStateAndPostcode = countriesRequiringAllFields.includes(country);
871
- return {
872
- hasCityField,
873
- requiresStateAndPostcode,
874
- // if there is no city field, we require state and use it in place of city
875
- requiresStateField: !hasCityField || requiresStateAndPostcode
876
- };
877
- };
878
- var validateAddressFields = (errors, contact, { hasCityField, requiresStateAndPostcode, requiresStateField }) => {
879
- const locality = !hasCityField ? contact.administrativeArea : contact.locality;
880
- if (!contact.addressLines?.at(0)?.trim()) {
881
- pushRequiredFieldError(errors, "billing", "addressLines");
882
- }
883
- if (!locality?.trim() && hasCityField) {
884
- pushRequiredFieldError(errors, "billing", "locality");
885
- }
886
- if (!contact.administrativeArea?.trim() && requiresStateField) {
887
- pushRequiredFieldError(errors, "billing", "administrativeArea");
888
- }
889
- if (!contact.postalCode?.trim() && requiresStateAndPostcode) {
890
- pushRequiredFieldError(errors, "billing", "postalCode");
891
- }
892
- };
893
- var validateNameFields = (errors, contact) => {
894
- const firstName = contact.givenName?.trim() || "";
895
- const lastName = contact.familyName?.trim() || "";
896
- if (!firstName || !lastName) {
897
- pushRequiredFieldError(errors, "billing", "name");
898
- }
899
- return { firstName, lastName };
900
- };
901
- var validateCountrySupport = (errors, country, supportedBillingCountries) => {
902
- if (!supportedBillingCountries.includes(country)) {
903
- pushFieldError(
904
- errors,
905
- "billing",
906
- "countryCode",
907
- "not-supported",
908
- MSG_UNSUPPORTED_BILLING_COUNTRY
909
- );
910
- }
911
- };
912
- var billingContactDetails = (contact, supportedBillingCountries) => {
913
- const errors = [];
914
- const country = contact.countryCode?.toUpperCase() || pushRequiredFieldError(errors, "billing", "country");
915
- const config = resolveCountryConfig(country);
916
- validateAddressFields(errors, contact, config);
917
- const { firstName, lastName } = validateNameFields(errors, contact);
918
- validateCountrySupport(errors, country, supportedBillingCountries);
919
- if (errors.length > 0) {
920
- throw new ContactValidationError(errors);
921
- }
922
- return { firstName, lastName };
923
- };
924
-
925
- // src/clients/payment-checkout/shipping-contact-details.ts
926
- var EMAIL_FORMAT_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
927
- var shippingContactDetails = (shippingContact) => {
928
- const errors = [];
929
- let emailAddress = "";
930
- if (shippingContact?.emailAddress === void 0 || shippingContact?.emailAddress === "") {
931
- pushRequiredFieldError(errors, "shipping", "emailAddress");
932
- } else {
933
- emailAddress = shippingContact.emailAddress.trim();
934
- if (emailAddress.length > 80) {
935
- pushFieldError(
936
- errors,
937
- "shipping",
938
- "emailAddress",
939
- "format",
940
- MSG_EMAIL_TOO_LONG
941
- );
942
- } else if (!EMAIL_FORMAT_REGEX.test(emailAddress)) {
943
- pushFieldError(
944
- errors,
945
- "shipping",
946
- "emailAddress",
947
- "format",
948
- MSG_EMAIL_WRONG_FORMAT
949
- );
950
- }
951
- }
952
- if (errors.length > 0) {
953
- throw new ContactValidationError(errors);
954
- }
955
- return { emailAddress };
956
- };
957
-
958
- // src/clients/payment-checkout/payment-handler.ts
959
- var MSG_ELIGIBILITY = "Something went wrong, please try again";
960
- var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
961
- var failApplePaySession = (session, errors = []) => {
962
- session.completePayment({
963
- status: ApplePaySession.STATUS_FAILURE,
964
- errors
965
- });
966
- };
967
- function parseWalletApiError(err) {
968
- const body = err instanceof ApiError ? err.body : err;
969
- if (body && typeof body === "object" && "errorType" in body && "message" in body) {
970
- return body;
971
- }
972
- return null;
973
- }
974
- function validateContacts(event, sessionData, supportedBillingCountries) {
975
- billingContactDetails(
976
- event.payment.billingContact,
977
- supportedBillingCountries
978
- );
979
- if (!sessionData.loggedIn) {
980
- return shippingContactDetails(event.payment.shippingContact).emailAddress;
981
- }
982
- return void 0;
983
- }
984
- function redirectFallback(skuId, returnUrl, options) {
985
- trackFallbackCheckout(skuId);
986
- window.location.href = redirectToCheckoutFallback({
987
- skuId,
988
- returnUrl,
989
- ...options
990
- });
991
- }
992
- var ERROR_HANDLERS = {
993
- EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
994
- emailError: MSG_EMAIL_COLLISION,
995
- email: validatedEmail
996
- }),
997
- PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
998
- // Matches sma-checkout catch-all: always redirect with generic inlineError.
999
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
1000
- ),
1001
- ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
1002
- REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
1003
- VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1004
- };
1005
- function handleStructuredError(ctx) {
1006
- const handler = ERROR_HANDLERS[ctx.walletError.errorType];
1007
- if (handler) {
1008
- handler(ctx);
1009
- } else {
1010
- redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1011
- }
1012
- }
1013
- async function handlePaymentAuthorized(event, session, sessionData, config) {
1014
- const { skuId, returnUrl, queryString } = config;
1015
- let validatedEmail;
1016
- console.log("[web-apple-pay] handlePaymentAuthorized: starting", { skuId, userLoggedIn: sessionData.loggedIn });
1017
- try {
1018
- console.log("[web-apple-pay] handlePaymentAuthorized: step 1 \u2014 validating contacts");
1019
- validatedEmail = validateContacts(
1020
- event,
1021
- sessionData,
1022
- config.supportedBillingCountries
1023
- );
1024
- console.log("[web-apple-pay] handlePaymentAuthorized: contacts valid", { validatedEmail });
1025
- console.log("[web-apple-pay] handlePaymentAuthorized: step 2 \u2014 calling performPurchase", { skuId });
1026
- const result = await performPurchase({
1027
- applePayToken: event.payment.token,
1028
- skuId: config.skuId,
1029
- billingContact: event.payment.billingContact,
1030
- ...event.payment.shippingContact && {
1031
- shippingContact: event.payment.shippingContact
1032
- },
1033
- recaptchaTokens: config.recaptchaTokens
1034
- });
1035
- console.log("[web-apple-pay] handlePaymentAuthorized: performPurchase succeeded", { basketId: result.basketId });
1036
- console.log("[web-apple-pay] handlePaymentAuthorized: step 3 \u2014 completing payment and redirecting");
1037
- trackWalletPaymentSuccess(skuId, result.basketId);
1038
- session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1039
- const userStatus = sessionData.loggedIn ? "lex" : "nex";
1040
- window.location.href = redirectToCheckoutComplete({
1041
- basketId: result.basketId,
1042
- status: userStatus,
1043
- skuId: decodeSku(skuId),
1044
- returnUrl,
1045
- queryString
1046
- });
1047
- } catch (err) {
1048
- if (err instanceof ContactValidationError) {
1049
- console.warn("[web-apple-pay] handlePaymentAuthorized: contact validation failed", err.errors);
1050
- failApplePaySession(session, err.errors);
1051
- return;
1052
- }
1053
- console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1054
- const walletError = parseWalletApiError(err);
1055
- console.error("[web-apple-pay] handlePaymentAuthorized: BFF/network error", {
1056
- skuId,
1057
- errorType: walletError?.errorType ?? "UNKNOWN",
1058
- message: walletError?.message
1059
- });
1060
- trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1061
- failApplePaySession(session);
1062
- if (!walletError) {
1063
- console.warn("[web-apple-pay] handlePaymentAuthorized: unstructured error, using generic fallback");
1064
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1065
- return;
1066
- }
1067
- console.log("[web-apple-pay] handlePaymentAuthorized: dispatching structured error handler", { errorType: walletError.errorType });
1068
- handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1069
- }
1070
- }
1071
-
1072
- // src/data/price-zones.json
1073
- var price_zones_default = {
1074
- "AS-AS": [
1075
- "BD",
1076
- "BT",
1077
- "CK",
1078
- "FJ",
1079
- "ID",
1080
- "KH",
1081
- "KI",
1082
- "LA",
1083
- "LK",
1084
- "MH",
1085
- "MM",
1086
- "MN",
1087
- "MV",
1088
- "NC",
1089
- "NF",
1090
- "NP",
1091
- "NR",
1092
- "PF",
1093
- "PG",
1094
- "PK",
1095
- "PW",
1096
- "SB",
1097
- "TH",
1098
- "TL",
1099
- "TO",
1100
- "TV",
1101
- "VN",
1102
- "VU",
1103
- "WS"
1104
- ],
1105
- "AS-AU": ["AU"],
1106
- "AS-CN": ["CN"],
1107
- "AS-HK": ["HK", "MO"],
1108
- "AS-IN": ["IN"],
1109
- "AS-JP": ["JP"],
1110
- "AS-KR": ["KR"],
1111
- "AS-MY": ["MY"],
1112
- "AS-NZ": ["NZ"],
1113
- "AS-PH": ["PH"],
1114
- "AS-SG": ["BN", "SG"],
1115
- "AS-TW": ["TW"],
1116
- "CE-AF": [
1117
- "DZ",
1118
- "AO",
1119
- "BJ",
1120
- "BW",
1121
- "BF",
1122
- "BI",
1123
- "CM",
1124
- "CV",
1125
- "CF",
1126
- "TD",
1127
- "CG",
1128
- "CD",
1129
- "CI",
1130
- "DJ",
1131
- "EG",
1132
- "GQ",
1133
- "ER",
1134
- "ET",
1135
- "GA",
1136
- "GM",
1137
- "GH",
1138
- "GN",
1139
- "GW",
1140
- "IL",
1141
- "JO",
1142
- "KE",
1143
- "LB",
1144
- "LS",
1145
- "LR",
1146
- "LY",
1147
- "MG",
1148
- "MW",
1149
- "ML",
1150
- "MR",
1151
- "MU",
1152
- "MA",
1153
- "MZ",
1154
- "NA",
1155
- "NE",
1156
- "NG",
1157
- "PS",
1158
- "RE",
1159
- "RW",
1160
- "ST",
1161
- "SN",
1162
- "SC",
1163
- "SL",
1164
- "ZA",
1165
- "SZ",
1166
- "TZ",
1167
- "TG",
1168
- "TN",
1169
- "UG",
1170
- "ZM",
1171
- "ZW"
1172
- ],
1173
- "CE-CH": ["LI", "CH"],
1174
- "CE-DK": ["FO", "GL", "DK"],
1175
- "CE-EU": [
1176
- "AL",
1177
- "AD",
1178
- "AM",
1179
- "AT",
1180
- "AZ",
1181
- "BY",
1182
- "BE",
1183
- "BA",
1184
- "BG",
1185
- "HR",
1186
- "CY",
1187
- "CZ",
1188
- "EE",
1189
- "FI",
1190
- "FR",
1191
- "GE",
1192
- "DE",
1193
- "GI",
1194
- "GR",
1195
- "HU",
1196
- "IS",
1197
- "IE",
1198
- "IT",
1199
- "KZ",
1200
- "XK",
1201
- "KG",
1202
- "LV",
1203
- "LT",
1204
- "LU",
1205
- "MT",
1206
- "MD",
1207
- "MC",
1208
- "ME",
1209
- "NL",
1210
- "PL",
1211
- "PT",
1212
- "MK",
1213
- "RO",
1214
- "RS",
1215
- "SK",
1216
- "SI",
1217
- "ES",
1218
- "TJ",
1219
- "TR",
1220
- "TM",
1221
- "UA",
1222
- "UZ"
1223
- ],
1224
- "CE-ME": ["AE", "BH", "KW", "OM", "QA", "SA"],
1225
- "CE-NO": ["NO"],
1226
- "CE-SE": ["SE"],
1227
- "LA-LA": [
1228
- "AI",
1229
- "AG",
1230
- "AR",
1231
- "AW",
1232
- "BS",
1233
- "BB",
1234
- "BZ",
1235
- "BM",
1236
- "BO",
1237
- "BR",
1238
- "KY",
1239
- "CL",
1240
- "CO",
1241
- "CR",
1242
- "DM",
1243
- "DO",
1244
- "EC",
1245
- "SV",
1246
- "FK",
1247
- "GF",
1248
- "GD",
1249
- "GT",
1250
- "GY",
1251
- "HT",
1252
- "HN",
1253
- "JM",
1254
- "MQ",
1255
- "MX",
1256
- "MS",
1257
- "NI",
1258
- "MP",
1259
- "PA",
1260
- "PY",
1261
- "PE",
1262
- "KN",
1263
- "LC",
1264
- "VC",
1265
- "SR",
1266
- "TT",
1267
- "TC",
1268
- "UY",
1269
- "VG",
1270
- "VI"
1271
- ],
1272
- "NA-CA": ["CA"],
1273
- "NA-US": ["AS", "GU", "FM", "PR", "US"],
1274
- "UK-UK": ["GB"]
1275
- };
1276
-
1277
- // src/data/eea-countries.json
1278
- var eea_countries_default = [
1279
- "AT",
1280
- "BE",
1281
- "BG",
1282
- "HR",
1283
- "CY",
1284
- "CZ",
1285
- "DK",
1286
- "EE",
1287
- "FO",
1288
- "FI",
1289
- "FR",
1290
- "DE",
1291
- "GR",
1292
- "GL",
1293
- "HU",
1294
- "IS",
1295
- "IE",
1296
- "IT",
1297
- "LV",
1298
- "LI",
1299
- "LT",
1300
- "LU",
1301
- "MT",
1302
- "NL",
1303
- "NO",
1304
- "PL",
1305
- "PT",
1306
- "RO",
1307
- "SK",
1308
- "SI",
1309
- "ES",
1310
- "SE"
1311
- ];
1312
-
1313
- // src/utils/billing-countries.ts
1314
- var priceZones = price_zones_default;
1315
- var eeaCountries = eea_countries_default;
1316
- var ALL_COUNTRY_CODES = Object.values(priceZones).flat();
1317
- var getZoneByCountry = (country) => Object.keys(priceZones).find((key) => priceZones[key].includes(country)) ?? null;
1318
- var getSupportedBillingCountries = (offerCountry, offerVariant) => {
1319
- if (offerVariant === "bundle" || offerVariant === "insider_print") {
1320
- return [...ALL_COUNTRY_CODES];
1321
- }
1322
- const zone = getZoneByCountry(offerCountry);
1323
- if (!zone) {
1324
- return [...ALL_COUNTRY_CODES];
1325
- }
1326
- const zoneCountries = priceZones[zone];
1327
- if (eeaCountries.includes(offerCountry)) {
1328
- return Array.from(/* @__PURE__ */ new Set([...zoneCountries, ...eeaCountries]));
1329
- }
1330
- return [...zoneCountries];
1331
- };
696
+ 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(--wall-type-system-sans, system-ui, sans-serif);\n font-size: 17px;\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 class="apple-pay-btn" type="button" aria-label="Pay with Apple Pay" data-analytics="paywall:apple-pay-checkout">\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';
1332
697
 
1333
698
  // src/apple-pay-button.ts
1334
699
  var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
1335
- var TOTAL_LABEL = "The Economist";
1336
- var DEFAULT_CURRENCY = "USD";
1337
- var DEFAULT_TOTAL = "0.00";
1338
- var ERROR_INITIATE = "Failed to initiate Apple Pay session:";
1339
- var SEL_CONTAINER = ".apple-pay-container";
1340
- var SEL_BUTTON = ".apple-pay-btn";
1341
- var APPLE_PAY_EXCLUDED_VARIANTS = [
1342
- "bundle",
1343
- "insider_print"
1344
- ];
1345
700
  var ApplePayButton = class _ApplePayButton extends HTMLElement {
1346
701
  static tag = "teg-apple-pay-button";
1347
702
  static define() {
@@ -1350,45 +705,27 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1350
705
  /**
1351
706
  * Creates and configures an ApplePayButton if the feature flag is present.
1352
707
  */
1353
- static createIfEnabled(offer, country, entryPoint) {
1354
- if (!offer) return null;
1355
- if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant))
708
+ static createIfEnabled(offer, country) {
709
+ if (!offer) {
1356
710
  return null;
1357
- if (!new URLSearchParams(window.location.search).has(FEATURE_APPLE_PAY_EXPRESS_CHECKOUT))
711
+ }
712
+ if (!new URLSearchParams(window.location.search).has(FEATURE_APPLE_PAY_EXPRESS_CHECKOUT)) {
1358
713
  return null;
714
+ }
1359
715
  _ApplePayButton.define();
1360
716
  const button = document.createElement(_ApplePayButton.tag);
1361
717
  button.offer = offer;
1362
718
  button.country = country;
1363
- button.entryPoint = entryPoint;
1364
719
  return button;
1365
720
  }
1366
- /**
1367
- * Fallback session used when the BFF is unreachable at page load.
1368
- * Treats the user as anonymous (not logged in, new-eligible).
1369
- * Matches the behaviour of sma-checkout when session fetch fails.
1370
- */
1371
- static #ANONYMOUS_SESSION = {
1372
- loggedIn: false,
1373
- userType: "new-eligible-user"
1374
- };
1375
- // ─── Private state ──────────────────────────────────────────────────────────
1376
721
  #offer = null;
1377
722
  #country = "";
1378
- #rendering = false;
1379
- #readyPromise = null;
1380
- #sessionData = null;
1381
- #paymentOptions = null;
1382
- #returnUrl = "";
1383
- #supportedBillingCountries = [];
1384
- #entryPoint = "";
1385
- // ─── Public API ─────────────────────────────────────────────────────────────
1386
723
  get offer() {
1387
724
  return this.#offer;
1388
725
  }
1389
726
  set offer(value) {
1390
727
  this.#offer = value;
1391
- this.#updateDisabledState();
728
+ this.updateDisabledState();
1392
729
  }
1393
730
  get country() {
1394
731
  return this.#country;
@@ -1396,156 +733,38 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1396
733
  set country(value) {
1397
734
  this.#country = value;
1398
735
  }
1399
- get entryPoint() {
1400
- return this.#entryPoint;
1401
- }
1402
- set entryPoint(value) {
1403
- this.#entryPoint = value;
1404
- }
1405
- /**
1406
- * Awaitable contract for the component's current render lifecycle.
1407
- *
1408
- * - If called **after** the element has been appended to the DOM, resolves
1409
- * once `connectedCallback` finishes — whether Apple Pay rendered
1410
- * successfully or was hidden (unavailable / suppressed by session).
1411
- * - If called **before** the element is appended (or if it has never been
1412
- * connected), resolves immediately to `undefined`. It does **not** wait
1413
- * for a future connection, so always `await whenReady()` after `appendChild`:
1414
- *
1415
- * ```ts
1416
- * container.appendChild(btn); // browser fires connectedCallback
1417
- * await btn.whenReady(); // waits for render to finish
1418
- * if (btn.style.display !== 'none') { /* available *\/ }
1419
- * ```
1420
- */
1421
- whenReady() {
1422
- return this.#readyPromise ?? Promise.resolve();
1423
- }
1424
- // ─── Lifecycle ──────────────────────────────────────────────────────────────
1425
- async connectedCallback() {
1426
- if (this.#rendering || this.querySelector(SEL_CONTAINER)) {
736
+ connectedCallback() {
737
+ if (this.querySelector(".apple-pay-container")) {
1427
738
  return;
1428
739
  }
1429
- this.#rendering = true;
1430
- this.#readyPromise = this.#run();
1431
- await this.#readyPromise;
1432
- }
1433
- /**
1434
- * Resets all render state when the element is removed from the DOM.
1435
- *
1436
- * A shared library component cannot assume it won't be moved in the DOM
1437
- * (e.g. lazy panels, SPA route changes). Resetting on disconnect means
1438
- * reattach always triggers a fresh `#run()` — new session check, new payment
1439
- * options fetch, fresh event listeners — preventing stale data from persisting
1440
- * and eliminating the implicit reliance on the DOM as a render-state flag.
1441
- */
1442
- disconnectedCallback() {
1443
- this.#rendering = false;
1444
- this.#readyPromise = null;
1445
- this.#sessionData = null;
1446
- this.#paymentOptions = null;
1447
- this.#returnUrl = "";
1448
- this.#supportedBillingCountries = [];
1449
- this.querySelector(SEL_CONTAINER)?.remove();
1450
- }
1451
- // ─── Render pipeline ────────────────────────────────────────────────────────
1452
- async #run() {
1453
- try {
1454
- if (await this.#checkAvailability()) return;
1455
- if (await this.#checkSession()) return;
1456
- if (this.#checkOffer()) return;
1457
- this.#captureReturnUrl();
1458
- this.#computeBillingCountries();
1459
- if (await this.#fetchAndCacheOptions()) return;
1460
- this.#renderTemplate();
1461
- } catch {
1462
- this.style.display = "none";
1463
- } finally {
1464
- this.#rendering = false;
1465
- }
1466
- }
1467
- /** Returns `true` (hide) when Apple Pay is unavailable on this device/browser. */
1468
- async #checkAvailability() {
1469
- const available = await isApplePayAvailable();
1470
- if (!available) {
1471
- this.style.display = "none";
1472
- return true;
1473
- }
1474
- return false;
1475
- }
1476
- /**
1477
- * Returns `true` (hide) when the user already has an active subscription or is B2B.
1478
- * Session fetch errors are swallowed — the component continues as an anonymous user.
1479
- */
1480
- async #checkSession() {
1481
- this.#sessionData = await this.#fetchSession();
1482
- if (this.#sessionData?.loggedIn && (this.#sessionData.userType === "active-subscription" || this.#sessionData.userType === "b2b-user")) {
1483
- this.style.display = "none";
1484
- return true;
1485
- }
1486
- return false;
1487
- }
1488
- /** Returns `true` (hide) when no offer has been set before mount. */
1489
- #checkOffer() {
1490
- if (!this.#offer) {
1491
- this.style.display = "none";
1492
- return true;
1493
- }
1494
- return false;
1495
- }
1496
- /** Captures the current page URL once at mount for reuse across the session lifetime. */
1497
- #captureReturnUrl() {
1498
- this.#returnUrl = captureOriginUrl();
1499
- }
1500
- /**
1501
- * Computes supported billing countries once at mount.
1502
- * Mirrors sma-checkout `billingCountryOptions()` — stable for the element's
1503
- * connected lifetime; recalculated on reattach via `disconnectedCallback` reset.
1504
- */
1505
- #computeBillingCountries() {
1506
- this.#supportedBillingCountries = getSupportedBillingCountries(
1507
- this.#country,
1508
- this.#offer.product?.variant ?? ""
1509
- );
1510
- }
1511
- /**
1512
- * Returns `true` (hide) when the wallet BFF reports Apple Pay is unavailable
1513
- * for this offer. The response is cached and reused in `#initiateApplePay`.
1514
- */
1515
- async #fetchAndCacheOptions() {
1516
- this.#paymentOptions = await this.#fetchPaymentOptions();
1517
- console.log("[web-apple-pay] #fetchAndCacheOptions: fetched payment options", { paymentOptions: this.#paymentOptions });
1518
- if (!this.#paymentOptions?.options?.apple_pay) {
1519
- this.style.display = "none";
1520
- return true;
1521
- }
1522
- return false;
1523
- }
1524
- /** Injects the button template and wires click and confirm event listeners. */
1525
- #renderTemplate() {
1526
- this.style.removeProperty("display");
1527
740
  ensureApplePayLogoSymbol();
1528
741
  const template = document.createElement("template");
1529
742
  template.innerHTML = apple_pay_button_template_default;
1530
743
  this.appendChild(template.content.cloneNode(true));
1531
- this.#updateDisabledState();
1532
- trackExpressWalletImpression(this.#offer.sku_id, {
1533
- entryPoint: this.#entryPoint,
1534
- country: this.#country
1535
- });
1536
- const button = this.querySelector(SEL_BUTTON);
744
+ this.updateDisabledState();
745
+ const button = this.querySelector(".apple-pay-btn");
1537
746
  button.addEventListener("click", () => {
1538
- this.#openModal();
1539
- });
1540
- this.addEventListener("apple-pay-confirmed", (event) => {
1541
- const { skuId, country } = event.detail;
1542
- this.#initiateApplePay(skuId, country);
747
+ if (!this.#offer) {
748
+ return;
749
+ }
750
+ if (document.querySelector(ApplePayModal.tag)) {
751
+ return;
752
+ }
753
+ ApplePayModal.define();
754
+ const modal = document.createElement(ApplePayModal.tag);
755
+ modal.offer = this.#offer;
756
+ modal.country = this.#country;
757
+ if (this.dataset.loggedIn !== void 0) {
758
+ modal.dataset.loggedIn = this.dataset.loggedIn;
759
+ }
760
+ modal.open();
1543
761
  });
1544
762
  }
1545
- // ─── Private helpers ────────────────────────────────────────────────────────
1546
- #updateDisabledState() {
1547
- const button = this.querySelector(SEL_BUTTON);
1548
- if (!button) return;
763
+ updateDisabledState() {
764
+ const button = this.querySelector(".apple-pay-btn");
765
+ if (!button) {
766
+ return;
767
+ }
1549
768
  if (this.#offer) {
1550
769
  button.removeAttribute("disabled");
1551
770
  button.removeAttribute("aria-disabled");
@@ -1554,77 +773,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1554
773
  button.setAttribute("disabled", "");
1555
774
  button.setAttribute("aria-disabled", "true");
1556
775
  }
1557
- #openModal() {
1558
- if (!this.#offer || document.querySelector(ApplePayModal.tag)) return;
1559
- trackExpressWalletClick(this.#offer.sku_id);
1560
- ApplePayModal.define();
1561
- const modal = document.createElement(ApplePayModal.tag);
1562
- modal.offer = this.#offer;
1563
- modal.country = this.#country;
1564
- if (this.#sessionData?.loggedIn) {
1565
- modal.dataset.loggedIn = "true";
1566
- }
1567
- modal.open();
1568
- }
1569
- async #fetchSession() {
1570
- try {
1571
- return await getExpressCheckoutSession();
1572
- } catch {
1573
- return null;
1574
- }
1575
- }
1576
- async #fetchPaymentOptions() {
1577
- try {
1578
- return await getPaymentOptions({
1579
- sku: this.#offer.sku_id,
1580
- countryCode: this.#country,
1581
- currencyCode: this.#offer.price?.currency_code ?? DEFAULT_CURRENCY,
1582
- checkoutUrl: this.#returnUrl
1583
- });
1584
- } catch {
1585
- return null;
1586
- }
1587
- }
1588
- #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokens) {
1589
- const offer = this.#offer;
1590
- const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
1591
- return {
1592
- countryCode: country,
1593
- currencyCode: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1594
- supportedNetworks: paymentOptions.options.apple_pay.settings.supported_networks,
1595
- totalAmount,
1596
- totalLabel: TOTAL_LABEL,
1597
- skuId,
1598
- returnUrl: this.#returnUrl,
1599
- userLoggedIn: sessionData.loggedIn,
1600
- onPaymentAuthorized: (event, session) => handlePaymentAuthorized(event, session, sessionData, {
1601
- skuId,
1602
- country,
1603
- returnUrl: this.#returnUrl,
1604
- queryString: buildQueryString(),
1605
- supportedBillingCountries: this.#supportedBillingCountries,
1606
- recaptchaTokens
1607
- })
1608
- };
1609
- }
1610
- async #initiateApplePay(skuId, country) {
1611
- try {
1612
- const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1613
- const paymentOptions = this.#paymentOptions;
1614
- const recaptchaTokens = await getPurchaseRecaptchaTokens();
1615
- startApplePaySession(
1616
- this.#buildSessionParams(
1617
- skuId,
1618
- country,
1619
- sessionData,
1620
- paymentOptions,
1621
- recaptchaTokens
1622
- )
1623
- );
1624
- } catch (error) {
1625
- console.error(ERROR_INITIATE, error);
1626
- }
1627
- }
1628
776
  };
1629
777
 
1630
778
  // src/index.ts
@@ -1633,10 +781,8 @@ function defineApplePayElements() {
1633
781
  ApplePayModal.define();
1634
782
  }
1635
783
  export {
1636
- APPLE_PAY_EXCLUDED_VARIANTS,
1637
784
  ApplePayButton,
1638
785
  ApplePayModal,
1639
- configureApplePay,
1640
786
  defineApplePayElements,
1641
787
  ensureApplePayLogoSymbol
1642
788
  };