@economist/web-apple-pay 0.1.3-beta.4 → 0.1.3-beta.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -71,12 +71,547 @@ function ensureApplePayLogoSymbol() {
71
71
  return;
72
72
  }
73
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>`;
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="currentColor"/></symbol></defs></svg>`;
75
75
  document.body.prepend(tmp.firstElementChild);
76
76
  }
77
77
 
78
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';
79
+ var apple_pay_modal_template_default = `<style>
80
+ .apm-overlay {
81
+ position: fixed;
82
+ top: 0;
83
+ left: 0;
84
+ width: 100%;
85
+ height: 100%;
86
+ z-index: 97;
87
+ background: rgba(13, 13, 13, 0.7);
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ padding: var(--wall-gutter, 2rem);
92
+ box-sizing: border-box;
93
+ }
94
+
95
+ .apm {
96
+ position: relative;
97
+ background: #fff;
98
+ border-radius: 0.5rem 0.5rem 0 0;
99
+ border: 0.0625rem solid #d9d9d9;
100
+ padding: var(--mb-spacing-lg, 1.5rem);
101
+ width: 25rem;
102
+ max-width: 25rem;
103
+ box-sizing: border-box;
104
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
105
+ box-shadow: 0 0.25rem 1rem 0 rgba(13, 13, 13, 0.15);
106
+ }
107
+
108
+ .apm__close {
109
+ position: absolute;
110
+ top: 1.5rem;
111
+ right: 1.3125rem;
112
+ background: transparent;
113
+ border: none;
114
+ cursor: pointer;
115
+ padding: 0;
116
+ width: 1.5rem;
117
+ height: 1.5rem;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ color: var(--mb-colour-greyscale-london-20, #1a1a1a);
122
+ transition:
123
+ background-color ease-in-out 0.2s,
124
+ color ease-in-out 0.2s;
125
+ }
126
+
127
+ .apm__close:hover {
128
+ color: var(--mb-colour-base-chicago-30, #333333);
129
+ }
130
+
131
+ .apm__close:active {
132
+ background-color: var(--mb-colour-base-chicago-90, #e6e6e6);
133
+ color: var(--mb-colour-base-chicago-30, #333333);
134
+ }
135
+
136
+ .apm__close:focus {
137
+ background-color: var(--mb-colour-base-chicago-90, #e6e6e6);
138
+ outline: solid 0.125rem var(--focus-border-color, #121212);
139
+ border-radius: 0.25rem;
140
+ }
141
+
142
+ .apm__offer-name {
143
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
144
+ font-size: var(--mb-typestyle-title-3-size, 1.25rem);
145
+ font-weight: 500;
146
+ color: var(--mb-colour-greyscale-london-5, #0d0d0d);
147
+ line-height: var(--mb-typestyle-title-3-leading, 1.75rem);
148
+ font-style: normal;
149
+ margin: 0 1.5rem 0.5rem 0;
150
+ }
151
+
152
+ .apm__due-today {
153
+ color: #1a1a1a;
154
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
155
+ font-size: var(--mb-typestyle-label-2-size, 0.9375rem);
156
+ font-style: normal;
157
+ font-weight: 400;
158
+ line-height: var(--mb-typestyle-label-2-leading, 1.25rem);
159
+ margin: 0 0 0.75rem 0;
160
+ }
161
+
162
+ .apm__rule {
163
+ border: none;
164
+ border-top: 0.0625rem solid #d9d9d9;
165
+ margin: 0 0 1rem 0;
166
+ }
167
+
168
+ .apm__terms {
169
+ color: #1a1a1a;
170
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
171
+ font-size: var(--mb-typestyle-label-2-size, 0.9375rem);
172
+ font-style: normal;
173
+ font-weight: 400;
174
+ line-height: var(--mb-typestyle-label-2-leading, 1.25rem);
175
+ margin: 0 0 1rem 0;
176
+ }
177
+
178
+ .apm__terms-bullets {
179
+ list-style: disc outside;
180
+ padding-left: 1.25rem;
181
+ margin: 0 0 0.75rem 0;
182
+ }
183
+
184
+ .apm__terms-bullets li {
185
+ margin-bottom: 0.375rem;
186
+ }
187
+
188
+ .apm__terms-legal {
189
+ margin: 0;
190
+ }
191
+
192
+ .apm__passive-terms {
193
+ font-family: var(--wall-type-system-sans);
194
+ font-size: 0.9375rem;
195
+ color: #0d0d0d;
196
+ line-height: 1.4;
197
+ margin: 0 0 8px 0;
198
+ }
199
+
200
+ .apm__passive-terms a {
201
+ color: #1a1a1a;
202
+ font-weight: 700;
203
+ text-decoration: underline;
204
+ }
205
+
206
+ .apm__checkbox-label {
207
+ display: flex;
208
+ align-items: flex-start;
209
+ gap: var(--mb-spacing-sm, 0.5rem);
210
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
211
+ font-size: var(--mb-typestyle-label-2-size, 0.9375rem);
212
+ color: var(--mb-colour-greyscale-london-20, #1a1a1a);
213
+ line-height: var(--mb-typestyle-label-2-leading, 1.25rem);
214
+ cursor: pointer;
215
+ margin: 0 0 1.5rem 0;
216
+ }
217
+
218
+ .apm__checkbox-label--error {
219
+ margin-bottom: 0.25rem;
220
+ }
221
+
222
+ .apm__checkbox-wrapper {
223
+ position: relative;
224
+ flex-shrink: 0;
225
+ width: 1.25rem;
226
+ height: 1.25rem;
227
+ margin-top: 0.0625rem;
228
+ }
229
+
230
+ .apm__checkbox-input {
231
+ position: absolute;
232
+ opacity: 0;
233
+ width: 100%;
234
+ height: 100%;
235
+ cursor: pointer;
236
+ margin: 0;
237
+ z-index: 1;
238
+ }
239
+
240
+ .apm__checkbox-custom {
241
+ position: absolute;
242
+ top: 0;
243
+ left: 0;
244
+ width: 1.25rem;
245
+ height: 1.25rem;
246
+ pointer-events: none;
247
+ }
248
+
249
+ .apm__checkbox-custom svg {
250
+ display: none;
251
+ width: 1.25rem;
252
+ height: 1.25rem;
253
+ }
254
+
255
+ .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-unchecked {
256
+ display: block;
257
+ }
258
+
259
+ .apm__checkbox-input:checked ~ .apm__checkbox-custom .svg-checked {
260
+ display: block;
261
+ }
262
+
263
+ .apm__checkbox-label--error
264
+ .apm__checkbox-input:not(:checked)
265
+ ~ .apm__checkbox-custom
266
+ .svg-unchecked {
267
+ display: none;
268
+ }
269
+
270
+ .apm__checkbox-label--error
271
+ .apm__checkbox-input:not(:checked)
272
+ ~ .apm__checkbox-custom
273
+ .svg-error {
274
+ display: block;
275
+ }
276
+
277
+ .apm__checkbox-input:focus-visible ~ .apm__checkbox-custom {
278
+ outline: solid 0.125rem #c91d42;
279
+ outline-offset: 0.125rem;
280
+ border-radius: 0.25rem;
281
+ }
282
+
283
+ .apm__checkbox-label a {
284
+ color: #1a1a1a;
285
+ font-weight: 700;
286
+ text-decoration: underline;
287
+ }
288
+
289
+ .apm__error {
290
+ display: none;
291
+ align-items: center;
292
+ gap: 0.75rem;
293
+ color: var(--mb-colour-greyscale-london-5, #0d0d0d);
294
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
295
+ font-size: 1.0625rem;
296
+ font-style: normal;
297
+ font-weight: 500;
298
+ line-height: 1.5rem;
299
+ margin-bottom: 1rem;
300
+ }
301
+
302
+ .apm__error--visible {
303
+ display: flex;
304
+ }
305
+
306
+ .apm__cta {
307
+ display: flex;
308
+ width: 100%;
309
+ height: 2.75rem;
310
+ padding: 0.375rem 1rem;
311
+ justify-content: center;
312
+ align-items: center;
313
+ gap: 0.375rem;
314
+ border-radius: 0.25rem;
315
+ background: #000;
316
+ border: none;
317
+ cursor: pointer;
318
+ color: #fff;
319
+ text-align: center;
320
+ font-size: 1.25rem;
321
+ font-style: normal;
322
+ font-weight: 500;
323
+ line-height: normal;
324
+ }
325
+
326
+ .apm__cta:disabled {
327
+ cursor: not-allowed;
328
+ }
329
+
330
+ .apm__cta[aria-busy="true"] {
331
+ position: relative;
332
+ cursor: not-allowed;
333
+ }
334
+
335
+ .apm__cta-content {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 0.375rem;
339
+ }
340
+
341
+ .apm__cta-label {
342
+ display: inline-flex;
343
+ align-items: center;
344
+ gap: 0.375rem;
345
+ }
346
+
347
+ .apm__cta-loading {
348
+ display: none;
349
+ }
350
+
351
+ .apm__cta[aria-busy="true"]::before {
352
+ content: '';
353
+ position: absolute;
354
+ top: 50%;
355
+ left: 50%;
356
+ width: 1.25rem;
357
+ height: 1.25rem;
358
+ border: 2px solid rgba(255, 255, 255, 0.25);
359
+ border-top-color: #fff;
360
+ border-radius: 50%;
361
+ animation: apm-cta-spinner 0.8s linear infinite;
362
+ }
363
+
364
+ @keyframes apm-cta-spinner {
365
+ from {
366
+ transform: translate(-50%, -50%) rotate(0deg);
367
+ }
368
+ to {
369
+ transform: translate(-50%, -50%) rotate(360deg);
370
+ }
371
+ }
372
+
373
+ .apm__cta svg {
374
+ flex-shrink: 0;
375
+ display: block;
376
+ }
377
+
378
+ @media (min-width: 22.5rem) and (max-width: 37.5rem) {
379
+ .apm {
380
+ width: 24.375rem;
381
+ max-width: 24.375rem;
382
+ }
383
+ }
384
+
385
+ @media (min-width: 37.5625rem) {
386
+ .apm {
387
+ border-radius: 0.5rem;
388
+ }
389
+ }
390
+
391
+ @media (max-width: 22.4375rem) {
392
+ .apm {
393
+ width: 20rem;
394
+ max-width: 20rem;
395
+ }
396
+ }
397
+
398
+ @media (max-width: 26.1875rem) {
399
+ .apm__cta {
400
+ font-size: 1.125rem;
401
+ }
402
+ }
403
+ </style>
404
+
405
+ <div class="apm-overlay" id="apple-pay-modal-overlay">
406
+ <div
407
+ class="apm"
408
+ role="dialog"
409
+ aria-modal="true"
410
+ aria-labelledby="apple-pay-modal-offer-name"
411
+ id="apple-pay-modal"
412
+ tabindex="-1"
413
+ >
414
+ <button class="apm__close" id="apple-pay-modal-close" aria-label="Close">
415
+ <svg
416
+ aria-hidden="true"
417
+ xmlns="http://www.w3.org/2000/svg"
418
+ width="24"
419
+ height="24"
420
+ viewBox="0 0 24 24"
421
+ fill="none"
422
+ >
423
+ <g clip-path="url(#clip0_15712_93054)">
424
+ <rect width="24" height="24" fill="white" fill-opacity="0.01" />
425
+ <path
426
+ fill-rule="evenodd"
427
+ clip-rule="evenodd"
428
+ d="M0 0H24V24H0V0Z"
429
+ fill="white"
430
+ fill-opacity="0.01"
431
+ />
432
+ <path
433
+ fill-rule="evenodd"
434
+ clip-rule="evenodd"
435
+ 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"
436
+ fill="currentColor"
437
+ />
438
+ </g>
439
+ <defs>
440
+ <clipPath id="clip0_15712_93054">
441
+ <rect width="24" height="24" fill="white" />
442
+ </clipPath>
443
+ </defs>
444
+ </svg>
445
+ </button>
446
+ <p class="apm__offer-name" id="apple-pay-modal-offer-name"></p>
447
+ <p class="apm__due-today" id="apple-pay-modal-due-today"></p>
448
+ <hr class="apm__rule" />
449
+ <div class="apm__terms" id="apple-pay-modal-terms"></div>
450
+ <p class="apm__passive-terms" id="apple-pay-modal-passive-terms"></p>
451
+ <label
452
+ class="apm__checkbox-label"
453
+ id="apple-pay-modal-checkbox-label"
454
+ for="apple-pay-modal-checkbox"
455
+ >
456
+ <div class="apm__checkbox-wrapper">
457
+ <input
458
+ class="apm__checkbox-input"
459
+ type="checkbox"
460
+ id="apple-pay-modal-checkbox"
461
+ />
462
+ <span class="apm__checkbox-custom">
463
+ <svg
464
+ aria-hidden="true"
465
+ class="svg-unchecked"
466
+ width="24"
467
+ height="24"
468
+ viewBox="0 0 24 24"
469
+ fill="none"
470
+ xmlns="http://www.w3.org/2000/svg"
471
+ >
472
+ <path
473
+ 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"
474
+ fill="white"
475
+ stroke="#595959"
476
+ />
477
+ </svg>
478
+ <svg
479
+ aria-hidden="true"
480
+ class="svg-checked"
481
+ width="24"
482
+ height="24"
483
+ viewBox="0 0 24 24"
484
+ fill="none"
485
+ xmlns="http://www.w3.org/2000/svg"
486
+ >
487
+ <g clip-path="url(#clip0_20014_9555)">
488
+ <path
489
+ 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"
490
+ fill="#333333"
491
+ />
492
+ <path
493
+ d="M10.2857 17L6 12.6814L7.20857 11.4635L10.2857 14.5557L16.7914 8L18 9.22649L10.2857 17Z"
494
+ fill="white"
495
+ />
496
+ </g>
497
+ <defs>
498
+ <clipPath id="clip0_20014_9555">
499
+ <path
500
+ 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"
501
+ fill="white"
502
+ />
503
+ </clipPath>
504
+ </defs>
505
+ </svg>
506
+ <svg
507
+ aria-hidden="true"
508
+ class="svg-error"
509
+ width="24"
510
+ height="24"
511
+ viewBox="0 0 24 24"
512
+ fill="none"
513
+ xmlns="http://www.w3.org/2000/svg"
514
+ >
515
+ <path
516
+ 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"
517
+ fill="white"
518
+ stroke="#C91D42"
519
+ stroke-width="2"
520
+ />
521
+ </svg>
522
+ </span>
523
+ </div>
524
+ <span id="apple-pay-modal-checkbox-text"></span>
525
+ </label>
526
+ <div class="apm__error" id="apple-pay-modal-error" role="alert">
527
+ <svg
528
+ aria-hidden="true"
529
+ width="20"
530
+ height="20"
531
+ viewBox="0 0 20 20"
532
+ fill="none"
533
+ xmlns="http://www.w3.org/2000/svg"
534
+ >
535
+ <path
536
+ fill-rule="evenodd"
537
+ clip-rule="evenodd"
538
+ 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"
539
+ fill="#C91D42"
540
+ />
541
+ </svg>
542
+ Please accept the terms to continue
543
+ </div>
544
+ <button class="apm__cta" id="apple-pay-modal-cta" aria-label="Continue with Apple Pay">
545
+ <span class="apm__cta-content">
546
+ <span class="apm__cta-label">
547
+ Continue with
548
+ <svg width="51" height="22" aria-hidden="true" role="img">
549
+ <use href="#apple-pay-logo" />
550
+ </svg>
551
+ </span>
552
+ </span>
553
+ <span class="apm__cta-loading" aria-hidden="true">Loading...</span>
554
+ </button>
555
+ </div>
556
+ </div>
557
+
558
+ `;
559
+
560
+ // src/clients/payment-checkout/config.ts
561
+ var runtimeConfig = null;
562
+ var configureApplePay = (config) => {
563
+ runtimeConfig = {
564
+ ...config
565
+ };
566
+ };
567
+ var getApplePayConfig = () => runtimeConfig;
568
+
569
+ // src/utils/recaptcha.ts
570
+ var loadRecaptchaScript = (siteKey) => {
571
+ return new Promise((resolve, reject) => {
572
+ if (typeof grecaptcha !== "undefined") return resolve();
573
+ const existingScript = document.querySelector(
574
+ 'script[src*="recaptcha/api.js"]'
575
+ );
576
+ if (existingScript) {
577
+ existingScript.addEventListener("load", () => resolve());
578
+ existingScript.addEventListener(
579
+ "error",
580
+ () => reject(new Error("Failed to load existing reCAPTCHA script"))
581
+ );
582
+ return;
583
+ }
584
+ const script = document.createElement("script");
585
+ script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
586
+ script.async = true;
587
+ script.defer = true;
588
+ script.onload = () => resolve();
589
+ script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
590
+ document.head.appendChild(script);
591
+ });
592
+ };
593
+ var getRecaptchaToken = async (action) => {
594
+ const siteKey = getApplePayConfig()?.recaptchaSiteKey;
595
+ if (!siteKey) {
596
+ throw new Error(
597
+ "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
598
+ );
599
+ }
600
+ await loadRecaptchaScript(siteKey);
601
+ return new Promise((resolve, reject) => {
602
+ grecaptcha.ready(() => {
603
+ grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
604
+ });
605
+ });
606
+ };
607
+ var getPurchaseRecaptchaTokens = async () => {
608
+ const [checkEligibility, newBasket, registerUser] = await Promise.all([
609
+ getRecaptchaToken("checkEligibility"),
610
+ getRecaptchaToken("newBasket"),
611
+ getRecaptchaToken("registerUser")
612
+ ]);
613
+ return { checkEligibility, newBasket, registerUser };
614
+ };
80
615
 
81
616
  // src/apple-pay-modal.ts
82
617
  function getRenewalDateText(unit, duration) {
@@ -128,6 +663,7 @@ function getOfferTypeText(offerType) {
128
663
  }
129
664
  var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
130
665
  var PRIVACY_LINK = '<a href="https://www.economistgroup.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>';
666
+ var MSG_RECAPTCHA_LOAD_ERROR = "Something went wrong. Please close and try again.";
131
667
  function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
132
668
  const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
133
669
  if (isNYT) {
@@ -160,6 +696,8 @@ function renewalPeriodText(unit, duration) {
160
696
  return "";
161
697
  }
162
698
  }
699
+ var LI_SEPARATOR = "\0LI_SEP\0";
700
+ var P_SEPARATOR = "\0P_SEP\0";
163
701
  function buildTermsContext(offer, country) {
164
702
  const isUS = country === "US";
165
703
  const isCA = country === "CA";
@@ -196,19 +734,19 @@ function freeTrialTerms(offer, ctx) {
196
734
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? 1;
197
735
  const trialDateStr = `on ${getRenewalDateText(trialUnit, trialDuration)}${ctx.verificationText}`;
198
736
  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}`;
737
+ return `Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${trialDateStr} when your free trial ends, until cancelled${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal until cancelled. ${ctx.usCancelNote}${ctx.taxFootnote}`;
200
738
  }
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}`;
739
+ return `Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${trialDateStr} when your free trial ends, until cancelled${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}${ctx.taxFootnote}`;
202
740
  }
203
741
  function nytTerms(ctx) {
204
742
  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
743
  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}`;
744
+ return `Your subscription to <em>The Economist</em> auto-renews ${ctx.periodText} until cancelled${LI_SEPARATOR}Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.renewalDateStr}${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal until cancelled. ${ctx.usCancelNote}${ctx.taxFootnote}${P_SEPARATOR}${nytAccessText}`;
207
745
  }
208
746
  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}`;
747
+ return `Your subscription to <em>The Economist</em> auto-renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.periodText} ${ctx.renewalDateStr} until cancelled.${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}${ctx.taxFootnote}${P_SEPARATOR}${nytAccessText}`;
210
748
  }
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}`;
749
+ return `Your subscription to <em>The Economist</em> auto-renews at ${ctx.renewalPriceText} ${ctx.periodText} ${ctx.renewalDateStr} until cancelled${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime} ${nytAccessText}`;
212
750
  }
213
751
  function minTermContractTerms(offer, ctx) {
214
752
  const duePrice = formatPrice(offer.price.purchase, offer.price.currency_code);
@@ -218,28 +756,28 @@ function minTermContractTerms(offer, ctx) {
218
756
  const contractLength = mtDuration && mtUnit ? `${mtDuration} ${mtUnit}` : "";
219
757
  const initialContractText = contractLength ? `your initial ${contractLength} contract` : "your initial contract";
220
758
  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}`;
759
+ return `You\u2019ll be charged ${duePrice} a month for the duration of ${initialContractText}${LI_SEPARATOR}Your subscription auto-renews annually at ${ctx.renewalPriceText} a month on ${mtRenewalDateText} until cancelled${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}`;
222
760
  }
223
761
  function paidTrialTerms(offer, ctx) {
224
762
  const trialUnit = offer.subscription_options?.trial_period?.unit ?? ctx.unit;
225
763
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? ctx.duration;
226
764
  const trialDateText = getRenewalDateText(trialUnit, trialDuration);
227
765
  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}`;
766
+ return `Your subscription auto-renews ${ctx.periodText} until cancelled${LI_SEPARATOR}Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} on ${trialDateText} when your paid trial ends${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal. ${ctx.usCancelNote}${ctx.taxFootnote}`;
229
767
  }
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}`;
768
+ return `Your subscription auto-renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.periodText} on ${trialDateText} when your paid trial ends, until cancelled${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}${ctx.taxFootnote}`;
231
769
  }
232
770
  function autoRenewalTerms(ctx) {
233
771
  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}`;
772
+ return `Your subscription auto-renews ${ctx.periodText} until cancelled${LI_SEPARATOR}Your subscription renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.renewalDateStr}${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal until cancelled. ${ctx.usCancelNote}${ctx.taxFootnote}`;
235
773
  }
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}`;
774
+ return `Your subscription auto-renews at ${ctx.renewalPriceText}${ctx.taxSuffix} ${ctx.periodText} ${ctx.renewalDateStr} until cancelled${LI_SEPARATOR}You\u2019ll be charged the rate in effect at each renewal. ${ctx.cancelAnytime}${ctx.taxFootnote}`;
237
775
  }
238
776
  function noAutoRenewalTerms(ctx) {
239
777
  if (ctx.isUS || ctx.isCA) {
240
- return `Your subscription does not renew automatically${ctx.taxFootnote}{{liSeparator}}`;
778
+ return `Your subscription does not renew automatically${ctx.taxFootnote}${LI_SEPARATOR}`;
241
779
  }
242
- return `Your subscription does not renew automatically{{liSeparator}}`;
780
+ return `Your subscription does not renew automatically${LI_SEPARATOR}`;
243
781
  }
244
782
  function generateApplePayModalTerms(offer, country) {
245
783
  const ctx = buildTermsContext(offer, country);
@@ -262,9 +800,9 @@ function generateApplePayModalTerms(offer, country) {
262
800
  return noAutoRenewalTerms(ctx);
263
801
  }
264
802
  function renderTermsHtml(termsText) {
265
- const renewalTextLists = termsText.split("{{liSeparator}}");
803
+ const renewalTextLists = termsText.split(LI_SEPARATOR);
266
804
  const lastPart = renewalTextLists.pop() || "";
267
- const renewalTextParagraphs = lastPart.split("{{pSeparator}}");
805
+ const renewalTextParagraphs = lastPart.split(P_SEPARATOR);
268
806
  const ulHtml = `<ul class="apm__terms-bullets">${renewalTextLists.map((value) => `<li>${value}</li>`).join("")}</ul>`;
269
807
  const paragraphHtml = renewalTextParagraphs.filter(Boolean).map((value) => `<p class="apm__terms-legal">${value}</p>`).join("");
270
808
  return ulHtml + paragraphHtml;
@@ -297,6 +835,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
297
835
  #country = "";
298
836
  #triggerElement = null;
299
837
  #keyHandler = null;
838
+ #recaptchaTokens = null;
839
+ #recaptchaTokensPromise = null;
840
+ /**
841
+ * Synchronous callback invoked directly from the CTA click handler once the
842
+ * user confirms. The caller (ApplePayButton) must call startApplePaySession
843
+ * inside this callback to preserve the browser's user-gesture chain — Safari
844
+ * requires new ApplePaySession() to execute within the original click stack.
845
+ *
846
+ * reCAPTCHA tokens are guaranteed non-null here: the CTA remains disabled
847
+ * until the token pre-fetch resolves, so this callback is never reached
848
+ * without valid tokens.
849
+ */
850
+ onConfirm = null;
300
851
  set offer(value) {
301
852
  this.#offer = value;
302
853
  }
@@ -317,7 +868,6 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
317
868
  const offer = this.#offer;
318
869
  const country = this.#country;
319
870
  if (!offer) {
320
- console.error("ApplePayModal: Missing or invalid offer data");
321
871
  this.remove();
322
872
  return;
323
873
  }
@@ -392,27 +942,56 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
392
942
  }
393
943
  });
394
944
  }
945
+ if (this.#recaptchaTokensPromise) {
946
+ if (cta) {
947
+ cta.setAttribute("disabled", "");
948
+ cta.setAttribute("aria-disabled", "true");
949
+ cta.setAttribute("aria-busy", "true");
950
+ }
951
+ this.#recaptchaTokensPromise.then((tokens) => {
952
+ this.#recaptchaTokens = tokens;
953
+ if (cta) {
954
+ cta.removeAttribute("disabled");
955
+ cta.removeAttribute("aria-disabled");
956
+ cta.removeAttribute("aria-busy");
957
+ }
958
+ }).catch(() => {
959
+ if (cta) {
960
+ cta.removeAttribute("aria-busy");
961
+ }
962
+ const existingError = this.querySelector("#apple-pay-modal-error");
963
+ if (existingError) {
964
+ const icon = existingError.querySelector("svg");
965
+ existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
966
+ if (icon) existingError.prepend(icon);
967
+ existingError.classList.add("apm__error--visible");
968
+ } else {
969
+ const errorNotice = document.createElement("div");
970
+ errorNotice.setAttribute("role", "alert");
971
+ errorNotice.className = "apm__error apm__error--visible";
972
+ errorNotice.textContent = MSG_RECAPTCHA_LOAD_ERROR;
973
+ cta?.insertAdjacentElement("beforebegin", errorNotice);
974
+ }
975
+ });
976
+ }
395
977
  cta?.addEventListener("click", () => {
396
978
  if (isUS && checkbox && !checkbox.checked) {
397
979
  errorElement?.classList.add("apm__error--visible");
398
980
  checkboxLabel?.classList.add("apm__checkbox-label--error");
399
981
  return;
400
982
  }
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();
983
+ if (this.#recaptchaTokens) {
984
+ this.onConfirm?.(this.#recaptchaTokens);
985
+ this.close();
986
+ }
409
987
  });
410
988
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
411
989
  "click",
412
990
  () => this.close()
413
991
  );
414
992
  }
415
- open() {
993
+ open(recaptchaTokensPromise) {
994
+ this.#recaptchaTokensPromise = recaptchaTokensPromise ?? getPurchaseRecaptchaTokens();
416
995
  this.#triggerElement = document.activeElement;
417
996
  document.body.appendChild(this);
418
997
  this.#keyHandler = (event) => {
@@ -439,16 +1018,101 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
439
1018
  };
440
1019
 
441
1020
  // 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';
1021
+ var apple_pay_button_template_default = `<style>
1022
+ /* Use CSS variables for layout so parent walls can easily override placement and sizing */
1023
+ teg-apple-pay-button {
1024
+ display: block;
1025
+ margin: var(--apple-pay-margin, 0);
1026
+ width: 100%;
1027
+ height: 100%;
1028
+ }
443
1029
 
444
- // src/clients/payment-checkout/config.ts
445
- var runtimeConfig = null;
446
- var configureApplePay = (config) => {
447
- runtimeConfig = {
448
- ...config
449
- };
450
- };
451
- var getApplePayConfig = () => runtimeConfig;
1030
+ teg-apple-pay-button .apple-pay-container {
1031
+ display: flex;
1032
+ flex-direction: column;
1033
+ align-items: stretch;
1034
+ width: 100%;
1035
+ height: 100%;
1036
+ }
1037
+
1038
+ teg-apple-pay-button .apple-pay-btn {
1039
+ display: flex;
1040
+ align-items: center;
1041
+ justify-content: center;
1042
+ gap: 0.375rem;
1043
+ width: 100%;
1044
+ min-width: 0;
1045
+ height: 100%;
1046
+ min-height: var(--apple-pay-height, 2.75rem);
1047
+ background-color: var(--apple-pay-background-color, #000);
1048
+ color: var(--apple-pay-foreground-color, #fff);
1049
+ border: none;
1050
+ border-radius: var(--apple-pay-border-radius, 1.375rem);
1051
+ cursor: pointer;
1052
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
1053
+ font-size: 1.0625rem;
1054
+ font-weight: 500;
1055
+ }
1056
+
1057
+ teg-apple-pay-button .apple-pay-btn svg {
1058
+ flex-shrink: 0;
1059
+ display: block;
1060
+ color: currentColor;
1061
+ }
1062
+
1063
+ teg-apple-pay-button .apple-pay-btn.wap-loading {
1064
+ position: relative;
1065
+ cursor: default;
1066
+ }
1067
+
1068
+ teg-apple-pay-button .apple-pay-btn.wap-loading::before {
1069
+ content: '';
1070
+ position: absolute;
1071
+ top: 50%;
1072
+ left: 50%;
1073
+ width: 1.25rem;
1074
+ height: 1.25rem;
1075
+ border: 2px solid var(--apple-pay-spinner-track-color, rgba(255, 255, 255, 0.25));
1076
+ border-top-color: var(--apple-pay-spinner-head-color, currentColor);
1077
+ border-radius: 50%;
1078
+ animation: wap-button-spinner 0.8s linear infinite;
1079
+ }
1080
+
1081
+ teg-apple-pay-button .apple-pay-btn__logo {
1082
+ display: inline-flex;
1083
+ align-items: center;
1084
+ }
1085
+
1086
+ teg-apple-pay-button .apple-pay-btn__loading {
1087
+ display: none;
1088
+ }
1089
+
1090
+ @keyframes wap-button-spinner {
1091
+ from {
1092
+ transform: translate(-50%, -50%) rotate(0deg);
1093
+ }
1094
+ to {
1095
+ transform: translate(-50%, -50%) rotate(360deg);
1096
+ }
1097
+ }
1098
+ </style>
1099
+
1100
+ <div class="apple-pay-container" aria-label="Express checkout with Apple Pay">
1101
+ <button
1102
+ class="apple-pay-btn"
1103
+ type="button"
1104
+ aria-label="Pay with Apple Pay"
1105
+ data-analytics="paywall:apple-pay-checkout"
1106
+ >
1107
+ <span class="apple-pay-btn__logo">
1108
+ <svg width="51" height="22" aria-label="Apple Pay" role="img">
1109
+ <use href="#apple-pay-logo" />
1110
+ </svg>
1111
+ </span>
1112
+ <span class="apple-pay-btn__loading" aria-hidden="true">Loading...</span>
1113
+ </button>
1114
+ </div>
1115
+ `;
452
1116
 
453
1117
  // src/clients/payment-checkout/detects.ts
454
1118
  var isAppleDeviceOrSafari = () => {
@@ -456,7 +1120,7 @@ var isAppleDeviceOrSafari = () => {
456
1120
  const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
457
1121
  return isIOSOrMacTouchDevice || isSafariBrowser;
458
1122
  };
459
- var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
1123
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null;
460
1124
  var isApplePayAvailable = async () => {
461
1125
  if (bypassApplePayChecks()) {
462
1126
  return true;
@@ -477,14 +1141,11 @@ var isApplePayAvailable = async () => {
477
1141
  }
478
1142
  const merchantId = getApplePayConfig()?.merchantId;
479
1143
  if (!merchantId) {
480
- console.warn(
481
- "[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
482
- );
483
1144
  return false;
484
1145
  }
485
- return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
486
- } catch (error) {
487
- console.error("Error checking Apple Pay active card status:", error);
1146
+ const hasActiveCard = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
1147
+ return hasActiveCard;
1148
+ } catch {
488
1149
  return false;
489
1150
  }
490
1151
  };
@@ -556,7 +1217,6 @@ var validateAppleMerchant = (data) => {
556
1217
  });
557
1218
  };
558
1219
  var performPurchase = (data) => {
559
- console.debug("[web-apple-pay] performPurchase request", JSON.stringify(data, null, 2));
560
1220
  return apiFetch("/v1/wallet/purchase", {
561
1221
  method: "POST",
562
1222
  body: JSON.stringify(data),
@@ -573,13 +1233,25 @@ var getExpressCheckoutSession = () => {
573
1233
  };
574
1234
 
575
1235
  // src/utils/expressCheckoutRedirect.ts
576
- var captureOriginUrl = () => window.location.href;
577
- var buildQueryString = () => {
578
- const countryCode = new URLSearchParams(window.location.search).get(
579
- "country"
580
- );
581
- return countryCode ? `&country=${encodeURIComponent(countryCode)}` : "";
1236
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1237
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1238
+ function isAllowedRedirectUrl(url) {
1239
+ if (url.startsWith("/")) return !url.startsWith("//");
1240
+ try {
1241
+ const { hostname } = new URL(url);
1242
+ return hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1243
+ } catch {
1244
+ return false;
1245
+ }
1246
+ }
1247
+ var writeCheckoutRedirect = (originUrl) => {
1248
+ if (!isAllowedRedirectUrl(originUrl)) return;
1249
+ try {
1250
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1251
+ } catch {
1252
+ }
582
1253
  };
1254
+ var captureOriginUrl = () => window.location.href;
583
1255
  var decodeSku = (sku) => {
584
1256
  try {
585
1257
  return atob(sku.substring(sku.indexOf("-") + 1));
@@ -590,75 +1262,25 @@ var decodeSku = (sku) => {
590
1262
  var redirectToCheckoutComplete = (params) => {
591
1263
  const url = new URL("/checkout/complete", window.location.origin);
592
1264
  url.searchParams.set("basketId", params.basketId);
593
- if (params.queryString) {
594
- const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
595
- new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
596
- }
597
1265
  url.searchParams.set("lmo_offer", params.skuId);
598
1266
  url.searchParams.set("status", params.status);
599
1267
  url.searchParams.set("redirectToCheckout", "true");
600
- url.searchParams.set("returnUrl", params.returnUrl);
1268
+ if (params.email) url.searchParams.set("email", params.email);
1269
+ if (params.country) url.searchParams.set("country", params.country);
601
1270
  return url.pathname + url.search;
602
1271
  };
603
1272
  var redirectToCheckoutFallback = (params) => {
604
1273
  const url = new URL(window.location.href);
605
1274
  url.pathname = "/checkout";
606
- url.searchParams.set("lmo_offer", params.skuId);
1275
+ url.searchParams.set("lmo_offer", decodeSku(params.skuId));
607
1276
  if (params.inlineError)
608
1277
  url.searchParams.set("inlineError", params.inlineError);
609
1278
  if (params.email) url.searchParams.set("email", params.email);
610
1279
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
611
- url.searchParams.set("returnUrl", params.returnUrl);
1280
+ if (params.country) url.searchParams.set("country", params.country);
612
1281
  return url.pathname + url.search;
613
1282
  };
614
1283
 
615
- // src/utils/recaptcha.ts
616
- var loadRecaptchaScript = (siteKey) => {
617
- return new Promise((resolve, reject) => {
618
- if (typeof grecaptcha !== "undefined") return resolve();
619
- const existingScript = document.querySelector(
620
- 'script[src*="recaptcha/api.js"]'
621
- );
622
- if (existingScript) {
623
- existingScript.addEventListener("load", () => resolve());
624
- existingScript.addEventListener(
625
- "error",
626
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
627
- );
628
- return;
629
- }
630
- const script = document.createElement("script");
631
- script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
632
- script.async = true;
633
- script.defer = true;
634
- script.onload = () => resolve();
635
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
636
- document.head.appendChild(script);
637
- });
638
- };
639
- var getRecaptchaToken = async (action) => {
640
- const siteKey = getApplePayConfig()?.recaptchaSiteKey;
641
- if (!siteKey) {
642
- throw new Error(
643
- "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
644
- );
645
- }
646
- await loadRecaptchaScript(siteKey);
647
- return new Promise((resolve, reject) => {
648
- grecaptcha.ready(() => {
649
- grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
650
- });
651
- });
652
- };
653
- var getPurchaseRecaptchaTokens = async () => {
654
- const [checkEligibility, newBasket, registerUser] = await Promise.all([
655
- getRecaptchaToken("checkEligibility"),
656
- getRecaptchaToken("newBasket"),
657
- getRecaptchaToken("registerUser")
658
- ]);
659
- return { checkEligibility, newBasket, registerUser };
660
- };
661
-
662
1284
  // src/utils/expressCheckoutTracking.ts
663
1285
  var EXPRESS_CHECKOUT_EVENTS = {
664
1286
  /** Wallet button displayed */
@@ -739,11 +1361,42 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
739
1361
  var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
740
1362
  var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
741
1363
  var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
742
- var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
743
- var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
744
1364
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
745
1365
 
746
1366
  // src/clients/payment-checkout/apple-session.ts
1367
+ function redirectToFallbackCheckout(config) {
1368
+ writeCheckoutRedirect(config.returnUrl);
1369
+ trackFallbackCheckout(config.skuId);
1370
+ window.location.href = redirectToCheckoutFallback({
1371
+ skuId: config.skuId,
1372
+ inlineError: MSG_PAYMENT_GENERIC_ERROR,
1373
+ country: config.countryCode
1374
+ });
1375
+ }
1376
+ async function handleMerchantValidation(event, session, config) {
1377
+ try {
1378
+ const validationHost = new URL(event.validationURL).host;
1379
+ if (!validationHost.endsWith("apple.com")) {
1380
+ session.abort();
1381
+ redirectToFallbackCheckout(config);
1382
+ return;
1383
+ }
1384
+ const recaptchaToken = await getRecaptchaToken("verifyMerchant");
1385
+ const response = await validateAppleMerchant({
1386
+ validationURL: event.validationURL,
1387
+ recaptchaToken
1388
+ });
1389
+ if (!response?.merchantSession) {
1390
+ session.abort();
1391
+ redirectToFallbackCheckout(config);
1392
+ return;
1393
+ }
1394
+ session.completeMerchantValidation(response.merchantSession);
1395
+ } catch {
1396
+ session.abort();
1397
+ redirectToFallbackCheckout(config);
1398
+ }
1399
+ }
747
1400
  function startApplePaySession(config) {
748
1401
  const paymentRequest = {
749
1402
  countryCode: config.countryCode,
@@ -758,55 +1411,23 @@ function startApplePaySession(config) {
758
1411
  // Only request email from Apple Pay sheet if user is NOT logged in
759
1412
  ...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
760
1413
  };
761
- const session = new ApplePaySession(3, paymentRequest);
762
- session.onvalidatemerchant = async (event) => {
763
- try {
764
- const validationHost = new URL(event.validationURL).host;
765
- if (!validationHost.endsWith("apple.com")) {
766
- trackFallbackCheckout(config.skuId);
767
- session.abort();
768
- window.location.href = redirectToCheckoutFallback({
769
- skuId: config.skuId,
770
- returnUrl: config.returnUrl,
771
- inlineError: MSG_PAYMENT_GENERIC_ERROR
772
- });
773
- return;
774
- }
775
- const recaptchaToken = await getRecaptchaToken("verifyMerchant");
776
- const response = await validateAppleMerchant({
777
- validationURL: event.validationURL,
778
- recaptchaToken
779
- });
780
- if (!response?.merchantSession) {
781
- trackFallbackCheckout(config.skuId);
782
- session.abort();
783
- window.location.href = redirectToCheckoutFallback({
784
- skuId: config.skuId,
785
- returnUrl: config.returnUrl,
786
- inlineError: MSG_PAYMENT_GENERIC_ERROR
787
- });
788
- return;
789
- }
790
- session.completeMerchantValidation(response.merchantSession);
791
- } catch {
792
- trackFallbackCheckout(config.skuId);
793
- session.abort();
794
- window.location.href = redirectToCheckoutFallback({
795
- skuId: config.skuId,
796
- returnUrl: config.returnUrl,
797
- inlineError: MSG_PAYMENT_GENERIC_ERROR
798
- });
799
- }
800
- };
801
- session.onpaymentauthorized = (event) => {
802
- trackWalletPaymentAuthorised(config.skuId);
803
- config.onPaymentAuthorized(event, session);
804
- };
805
- session.oncancel = () => {
806
- config.onCancel?.();
807
- };
808
- trackWalletPaymentSheetOpen(config.skuId);
809
- session.begin();
1414
+ try {
1415
+ const session = new ApplePaySession(3, paymentRequest);
1416
+ session.onvalidatemerchant = async (event) => {
1417
+ await handleMerchantValidation(event, session, config);
1418
+ };
1419
+ session.onpaymentauthorized = (event) => {
1420
+ trackWalletPaymentAuthorised(config.skuId);
1421
+ config.onPaymentAuthorized(event, session);
1422
+ };
1423
+ session.oncancel = () => {
1424
+ config.onCancel?.();
1425
+ };
1426
+ trackWalletPaymentSheetOpen(config.skuId);
1427
+ session.begin();
1428
+ } catch {
1429
+ redirectToFallbackCheckout(config);
1430
+ }
810
1431
  }
811
1432
 
812
1433
  // src/clients/payment-checkout/contact-validation.ts
@@ -926,8 +1547,6 @@ var shippingContactDetails = (shippingContact) => {
926
1547
  };
927
1548
 
928
1549
  // src/clients/payment-checkout/payment-handler.ts
929
- var MSG_ELIGIBILITY = "Something went wrong, please try again";
930
- var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
931
1550
  var failApplePaySession = (session, errors = []) => {
932
1551
  session.completePayment({
933
1552
  status: ApplePaySession.STATUS_FAILURE,
@@ -951,37 +1570,42 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
951
1570
  }
952
1571
  return void 0;
953
1572
  }
954
- function redirectFallback(skuId, returnUrl, options) {
1573
+ function redirectFallback(skuId, returnUrl, country, options) {
1574
+ writeCheckoutRedirect(returnUrl);
955
1575
  trackFallbackCheckout(skuId);
956
1576
  window.location.href = redirectToCheckoutFallback({
957
1577
  skuId,
958
- returnUrl,
1578
+ country,
959
1579
  ...options
960
1580
  });
961
1581
  }
962
1582
  var ERROR_HANDLERS = {
963
- EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
964
- emailError: MSG_EMAIL_COLLISION,
1583
+ // emailError flows as an inline field error on the email input.
1584
+ // email pre-populates the input so the user doesn't have to retype it.
1585
+ EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1586
+ emailError: walletError.message,
1587
+ email: validatedEmail
1588
+ }),
1589
+ PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1590
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1591
+ REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1592
+ ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1593
+ emailError: walletError.message,
965
1594
  email: validatedEmail
966
1595
  }),
967
- PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
968
- // Matches sma-checkout catch-all: always redirect with generic inlineError.
969
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
970
- ),
971
- ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
972
- REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
973
- VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1596
+ VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1597
+ RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
974
1598
  };
975
1599
  function handleStructuredError(ctx) {
976
1600
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
977
1601
  if (handler) {
978
1602
  handler(ctx);
979
1603
  } else {
980
- redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1604
+ redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
981
1605
  }
982
1606
  }
983
1607
  async function handlePaymentAuthorized(event, session, sessionData, config) {
984
- const { skuId, returnUrl, queryString } = config;
1608
+ const { skuId, returnUrl, country } = config;
985
1609
  let validatedEmail;
986
1610
  try {
987
1611
  validatedEmail = validateContacts(
@@ -989,7 +1613,6 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
989
1613
  sessionData,
990
1614
  config.supportedBillingCountries
991
1615
  );
992
- console.debug("[web-apple-pay] performPurchase currency", { skuId, currency: config.currency });
993
1616
  const result = await performPurchase({
994
1617
  applePayToken: event.payment.token,
995
1618
  skuId: config.skuId,
@@ -1002,28 +1625,29 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1002
1625
  });
1003
1626
  trackWalletPaymentSuccess(skuId, result.basketId);
1004
1627
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1628
+ const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
1005
1629
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1630
+ writeCheckoutRedirect(returnUrl);
1006
1631
  window.location.href = redirectToCheckoutComplete({
1007
1632
  basketId: result.basketId,
1008
1633
  status: userStatus,
1009
1634
  skuId: decodeSku(skuId),
1010
- returnUrl,
1011
- queryString
1635
+ email: successEmail,
1636
+ country
1012
1637
  });
1013
1638
  } catch (err) {
1014
1639
  if (err instanceof ContactValidationError) {
1015
1640
  failApplePaySession(session, err.errors);
1016
1641
  return;
1017
1642
  }
1018
- console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1019
1643
  const walletError = parseWalletApiError(err);
1020
1644
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1021
1645
  failApplePaySession(session);
1022
1646
  if (!walletError) {
1023
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1647
+ redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1024
1648
  return;
1025
1649
  }
1026
- handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1650
+ handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
1027
1651
  }
1028
1652
  }
1029
1653
 
@@ -1289,11 +1913,9 @@ var getSupportedBillingCountries = (offerCountry, offerVariant) => {
1289
1913
  };
1290
1914
 
1291
1915
  // src/apple-pay-button.ts
1292
- var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
1293
1916
  var TOTAL_LABEL = "The Economist";
1294
1917
  var DEFAULT_CURRENCY = "USD";
1295
1918
  var DEFAULT_TOTAL = "0.00";
1296
- var ERROR_INITIATE = "Failed to initiate Apple Pay session:";
1297
1919
  var SEL_CONTAINER = ".apple-pay-container";
1298
1920
  var SEL_BUTTON = ".apple-pay-btn";
1299
1921
  var APPLE_PAY_EXCLUDED_VARIANTS = [
@@ -1306,19 +1928,22 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1306
1928
  define(_ApplePayButton.tag, _ApplePayButton);
1307
1929
  }
1308
1930
  /**
1309
- * Creates and configures an ApplePayButton if the feature flag is present.
1931
+ * Creates and configures an ApplePayButton if the offer is eligible.
1310
1932
  */
1311
1933
  static createIfEnabled(offer, country, entryPoint) {
1312
- if (!offer) return null;
1313
- if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant))
1934
+ if (!offer) {
1314
1935
  return null;
1315
- if (!new URLSearchParams(window.location.search).has(FEATURE_APPLE_PAY_EXPRESS_CHECKOUT))
1936
+ }
1937
+ if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant)) {
1316
1938
  return null;
1939
+ }
1317
1940
  _ApplePayButton.define();
1318
1941
  const button = document.createElement(_ApplePayButton.tag);
1319
1942
  button.offer = offer;
1320
1943
  button.country = country;
1321
- button.entryPoint = entryPoint;
1944
+ if (entryPoint) {
1945
+ button.entryPoint = entryPoint;
1946
+ }
1322
1947
  return button;
1323
1948
  }
1324
1949
  /**
@@ -1340,6 +1965,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1340
1965
  #returnUrl = "";
1341
1966
  #supportedBillingCountries = [];
1342
1967
  #entryPoint = "";
1968
+ #recaptchaTokensPromise = null;
1343
1969
  // ─── Public API ─────────────────────────────────────────────────────────────
1344
1970
  get offer() {
1345
1971
  return this.#offer;
@@ -1404,20 +2030,25 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1404
2030
  this.#paymentOptions = null;
1405
2031
  this.#returnUrl = "";
1406
2032
  this.#supportedBillingCountries = [];
2033
+ this.#recaptchaTokensPromise = null;
1407
2034
  this.querySelector(SEL_CONTAINER)?.remove();
1408
2035
  }
1409
2036
  // ─── Render pipeline ────────────────────────────────────────────────────────
1410
2037
  async #run() {
1411
2038
  try {
2039
+ this.#renderTemplate();
1412
2040
  if (await this.#checkAvailability()) return;
1413
- if (await this.#checkSession()) return;
1414
2041
  if (this.#checkOffer()) return;
2042
+ if (await this.#checkSession()) return;
1415
2043
  this.#captureReturnUrl();
1416
2044
  this.#computeBillingCountries();
1417
2045
  if (await this.#fetchAndCacheOptions()) return;
1418
- this.#renderTemplate();
1419
- } catch {
2046
+ console.log("[web-apple-pay] button shown \u2014 reason: all checks passed, Apple Pay is available and offer is eligible");
2047
+ this.#finishLoading();
2048
+ } catch (error) {
2049
+ console.warn("[web-apple-pay] button hidden \u2014 reason: unexpected error in render pipeline", error);
1420
2050
  this.style.display = "none";
2051
+ this.querySelector(SEL_CONTAINER)?.remove();
1421
2052
  } finally {
1422
2053
  this.#rendering = false;
1423
2054
  }
@@ -1426,7 +2057,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1426
2057
  async #checkAvailability() {
1427
2058
  const available = await isApplePayAvailable();
1428
2059
  if (!available) {
2060
+ console.log("[web-apple-pay] button removed from DOM \u2014 reason: Apple Pay unavailable on this device/browser");
1429
2061
  this.style.display = "none";
2062
+ this.querySelector(SEL_CONTAINER)?.remove();
1430
2063
  return true;
1431
2064
  }
1432
2065
  return false;
@@ -1438,7 +2071,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1438
2071
  async #checkSession() {
1439
2072
  this.#sessionData = await this.#fetchSession();
1440
2073
  if (this.#sessionData?.loggedIn && (this.#sessionData.userType === "active-subscription" || this.#sessionData.userType === "b2b-user")) {
2074
+ console.log(`[web-apple-pay] button removed from DOM \u2014 reason: session check failed, userType="${this.#sessionData.userType}"`);
1441
2075
  this.style.display = "none";
2076
+ this.querySelector(SEL_CONTAINER)?.remove();
1442
2077
  return true;
1443
2078
  }
1444
2079
  return false;
@@ -1446,7 +2081,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1446
2081
  /** Returns `true` (hide) when no offer has been set before mount. */
1447
2082
  #checkOffer() {
1448
2083
  if (!this.#offer) {
2084
+ console.log("[web-apple-pay] button removed from DOM \u2014 reason: no offer set on element before mount");
1449
2085
  this.style.display = "none";
2086
+ this.querySelector(SEL_CONTAINER)?.remove();
1450
2087
  return true;
1451
2088
  }
1452
2089
  return false;
@@ -1472,32 +2109,43 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1472
2109
  */
1473
2110
  async #fetchAndCacheOptions() {
1474
2111
  this.#paymentOptions = await this.#fetchPaymentOptions();
1475
- if (!this.#paymentOptions?.apple_pay) {
2112
+ if (!this.#paymentOptions?.options?.apple_pay) {
2113
+ console.log("[web-apple-pay] button removed from DOM \u2014 reason: payment options BFF reports Apple Pay unavailable for this offer");
1476
2114
  this.style.display = "none";
2115
+ this.querySelector(SEL_CONTAINER)?.remove();
1477
2116
  return true;
1478
2117
  }
1479
2118
  return false;
1480
2119
  }
1481
2120
  /** Injects the button template and wires click and confirm event listeners. */
1482
2121
  #renderTemplate() {
2122
+ console.log("[web-apple-pay] button shown \u2014 reason: template injected into DOM (loading state), awaiting pipeline checks");
1483
2123
  this.style.removeProperty("display");
1484
2124
  ensureApplePayLogoSymbol();
1485
2125
  const template = document.createElement("template");
1486
2126
  template.innerHTML = apple_pay_button_template_default;
1487
2127
  this.appendChild(template.content.cloneNode(true));
1488
- this.#updateDisabledState();
1489
- trackExpressWalletImpression(this.#offer.sku_id, {
1490
- entryPoint: this.#entryPoint,
1491
- country: this.#country
1492
- });
1493
2128
  const button = this.querySelector(SEL_BUTTON);
2129
+ button.classList.add("wap-loading");
2130
+ button.setAttribute("disabled", "");
2131
+ button.setAttribute("aria-disabled", "true");
2132
+ button.setAttribute("aria-busy", "true");
1494
2133
  button.addEventListener("click", () => {
2134
+ if (button.classList.contains("wap-loading")) return;
1495
2135
  this.#openModal();
1496
2136
  });
1497
- this.addEventListener("apple-pay-confirmed", (event) => {
1498
- const { skuId, country } = event.detail;
1499
- this.#initiateApplePay(skuId, country);
1500
- });
2137
+ }
2138
+ #finishLoading() {
2139
+ const button = this.querySelector(SEL_BUTTON);
2140
+ if (button) {
2141
+ button.classList.remove("wap-loading");
2142
+ this.#updateDisabledState();
2143
+ this.#scheduleRecaptchaTokens();
2144
+ trackExpressWalletImpression(this.#offer.sku_id, {
2145
+ entryPoint: this.#entryPoint,
2146
+ country: this.#country
2147
+ });
2148
+ }
1501
2149
  }
1502
2150
  // ─── Private helpers ────────────────────────────────────────────────────────
1503
2151
  #updateDisabledState() {
@@ -1506,13 +2154,19 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1506
2154
  if (this.#offer) {
1507
2155
  button.removeAttribute("disabled");
1508
2156
  button.removeAttribute("aria-disabled");
2157
+ button.removeAttribute("aria-busy");
1509
2158
  return;
1510
2159
  }
1511
2160
  button.setAttribute("disabled", "");
1512
2161
  button.setAttribute("aria-disabled", "true");
1513
2162
  }
1514
2163
  #openModal() {
1515
- if (!this.#offer || document.querySelector(ApplePayModal.tag)) return;
2164
+ if (!this.#offer) {
2165
+ return;
2166
+ }
2167
+ if (document.querySelector(ApplePayModal.tag)) {
2168
+ return;
2169
+ }
1516
2170
  trackExpressWalletClick(this.#offer.sku_id);
1517
2171
  ApplePayModal.define();
1518
2172
  const modal = document.createElement(ApplePayModal.tag);
@@ -1521,7 +2175,29 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1521
2175
  if (this.#sessionData?.loggedIn) {
1522
2176
  modal.dataset.loggedIn = "true";
1523
2177
  }
1524
- modal.open();
2178
+ const skuId = this.#offer.sku_id;
2179
+ const country = this.#country;
2180
+ modal.onConfirm = (recaptchaTokens) => {
2181
+ this.#initiateApplePay(skuId, country, recaptchaTokens);
2182
+ this.#scheduleRecaptchaTokens();
2183
+ };
2184
+ modal.open(this.#recaptchaTokensPromise);
2185
+ }
2186
+ /**
2187
+ * Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
2188
+ * If the fetch rejects, the field is cleared so the next `modal.open()` call
2189
+ * falls back to a new attempt via the modal's own null-fallback path — preventing
2190
+ * a single transient failure from permanently disabling the CTA on all retries.
2191
+ * The identity guard prevents a late rejection from clobbering a newer attempt.
2192
+ */
2193
+ #scheduleRecaptchaTokens() {
2194
+ const attempt = getPurchaseRecaptchaTokens();
2195
+ attempt.catch(() => {
2196
+ if (this.#recaptchaTokensPromise === attempt) {
2197
+ this.#recaptchaTokensPromise = null;
2198
+ }
2199
+ });
2200
+ this.#recaptchaTokensPromise = attempt;
1525
2201
  }
1526
2202
  async #fetchSession() {
1527
2203
  try {
@@ -1548,7 +2224,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1548
2224
  return {
1549
2225
  countryCode: country,
1550
2226
  currencyCode: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1551
- supportedNetworks: paymentOptions.apple_pay.settings.supported_networks,
2227
+ supportedNetworks: paymentOptions.options.apple_pay.settings.supported_networks,
1552
2228
  totalAmount,
1553
2229
  totalLabel: TOTAL_LABEL,
1554
2230
  skuId,
@@ -1559,17 +2235,15 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1559
2235
  country,
1560
2236
  currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1561
2237
  returnUrl: this.#returnUrl,
1562
- queryString: buildQueryString(),
1563
2238
  supportedBillingCountries: this.#supportedBillingCountries,
1564
2239
  recaptchaTokens
1565
2240
  })
1566
2241
  };
1567
2242
  }
1568
- async #initiateApplePay(skuId, country) {
2243
+ #initiateApplePay(skuId, country, recaptchaTokens) {
1569
2244
  try {
1570
2245
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1571
2246
  const paymentOptions = this.#paymentOptions;
1572
- const recaptchaTokens = await getPurchaseRecaptchaTokens();
1573
2247
  startApplePaySession(
1574
2248
  this.#buildSessionParams(
1575
2249
  skuId,
@@ -1579,12 +2253,19 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1579
2253
  recaptchaTokens
1580
2254
  )
1581
2255
  );
1582
- } catch (error) {
1583
- console.error(ERROR_INITIATE, error);
2256
+ } catch {
1584
2257
  }
1585
2258
  }
1586
2259
  };
1587
2260
 
2261
+ // src/constants.ts
2262
+ var ApplePayEntryPoint = /* @__PURE__ */ ((ApplePayEntryPoint2) => {
2263
+ ApplePayEntryPoint2["PAYWALL"] = "article_paywall";
2264
+ ApplePayEntryPoint2["REGWALL"] = "article_regwall";
2265
+ ApplePayEntryPoint2["DUAL_OFFER_BANNER"] = "dual_offer_banner";
2266
+ return ApplePayEntryPoint2;
2267
+ })(ApplePayEntryPoint || {});
2268
+
1588
2269
  // src/index.ts
1589
2270
  function defineApplePayElements() {
1590
2271
  ApplePayButton.define();
@@ -1593,6 +2274,7 @@ function defineApplePayElements() {
1593
2274
  export {
1594
2275
  APPLE_PAY_EXCLUDED_VARIANTS,
1595
2276
  ApplePayButton,
2277
+ ApplePayEntryPoint,
1596
2278
  ApplePayModal,
1597
2279
  configureApplePay,
1598
2280
  defineApplePayElements,