@economist/web-apple-pay 0.1.3-beta.2 → 0.1.3-beta.21

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.
@@ -76,7 +76,509 @@ function ensureApplePayLogoSymbol() {
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: none;
113
+ border: none;
114
+ cursor: pointer;
115
+ padding: 0;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ }
120
+
121
+ .apm__close:focus {
122
+ outline: solid 0.125rem var(--focus-border-color, #121212);
123
+ border-radius: 0.25rem;
124
+ }
125
+
126
+ .apm__offer-name {
127
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
128
+ font-size: var(--mb-typestyle-title-3-size, 1.25rem);
129
+ font-weight: 500;
130
+ color: var(--mb-colour-greyscale-london-5, #0d0d0d);
131
+ line-height: var(--mb-typestyle-title-3-leading, 1.75rem);
132
+ font-style: normal;
133
+ margin: 0 1.5rem 0.5rem 0;
134
+ }
135
+
136
+ .apm__due-today {
137
+ color: #1a1a1a;
138
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
139
+ font-size: var(--mb-typestyle-label-2-size, 0.9375rem);
140
+ font-style: normal;
141
+ font-weight: 400;
142
+ line-height: var(--mb-typestyle-label-2-leading, 1.25rem);
143
+ margin: 0 0 0.75rem 0;
144
+ }
145
+
146
+ .apm__rule {
147
+ border: none;
148
+ border-top: 0.0625rem solid #d9d9d9;
149
+ margin: 0 0 1rem 0;
150
+ }
151
+
152
+ .apm__terms {
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 1rem 0;
160
+ }
161
+
162
+ .apm__terms-bullets {
163
+ list-style: disc outside;
164
+ padding-left: 1.25rem;
165
+ margin: 0 0 0.75rem 0;
166
+ }
167
+
168
+ .apm__terms-bullets li {
169
+ margin-bottom: 0.375rem;
170
+ }
171
+
172
+ .apm__terms-legal {
173
+ margin: 0;
174
+ }
175
+
176
+ .apm__passive-terms {
177
+ font-family: var(--wall-type-system-sans);
178
+ font-size: 0.9375rem;
179
+ color: #0d0d0d;
180
+ line-height: 1.4;
181
+ margin: 0 0 8px 0;
182
+ }
183
+
184
+ .apm__passive-terms a {
185
+ color: #1a1a1a;
186
+ font-weight: 700;
187
+ text-decoration: underline;
188
+ }
189
+
190
+ .apm__checkbox-label {
191
+ display: flex;
192
+ align-items: flex-start;
193
+ gap: var(--mb-spacing-sm, 0.5rem);
194
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
195
+ font-size: var(--mb-typestyle-label-2-size, 0.9375rem);
196
+ color: var(--mb-colour-greyscale-london-20, #1a1a1a);
197
+ line-height: var(--mb-typestyle-label-2-leading, 1.25rem);
198
+ cursor: pointer;
199
+ margin: 0 0 1.5rem 0;
200
+ }
201
+
202
+ .apm__checkbox-label--error {
203
+ margin-bottom: 0.25rem;
204
+ }
205
+
206
+ .apm__checkbox-wrapper {
207
+ position: relative;
208
+ flex-shrink: 0;
209
+ width: 1.25rem;
210
+ height: 1.25rem;
211
+ margin-top: 0.0625rem;
212
+ }
213
+
214
+ .apm__checkbox-input {
215
+ position: absolute;
216
+ opacity: 0;
217
+ width: 100%;
218
+ height: 100%;
219
+ cursor: pointer;
220
+ margin: 0;
221
+ z-index: 1;
222
+ }
223
+
224
+ .apm__checkbox-custom {
225
+ position: absolute;
226
+ top: 0;
227
+ left: 0;
228
+ width: 1.25rem;
229
+ height: 1.25rem;
230
+ pointer-events: none;
231
+ }
232
+
233
+ .apm__checkbox-custom svg {
234
+ display: none;
235
+ width: 1.25rem;
236
+ height: 1.25rem;
237
+ }
238
+
239
+ .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-unchecked {
240
+ display: block;
241
+ }
242
+
243
+ .apm__checkbox-input:checked ~ .apm__checkbox-custom .svg-checked {
244
+ display: block;
245
+ }
246
+
247
+ .apm__checkbox-label--error
248
+ .apm__checkbox-input:not(:checked)
249
+ ~ .apm__checkbox-custom
250
+ .svg-unchecked {
251
+ display: none;
252
+ }
253
+
254
+ .apm__checkbox-label--error
255
+ .apm__checkbox-input:not(:checked)
256
+ ~ .apm__checkbox-custom
257
+ .svg-error {
258
+ display: block;
259
+ }
260
+
261
+ .apm__checkbox-input:focus-visible ~ .apm__checkbox-custom {
262
+ outline: solid 0.125rem #c91d42;
263
+ outline-offset: 0.125rem;
264
+ border-radius: 0.25rem;
265
+ }
266
+
267
+ .apm__checkbox-label a {
268
+ color: #1a1a1a;
269
+ font-weight: 700;
270
+ text-decoration: underline;
271
+ }
272
+
273
+ .apm__error {
274
+ display: none;
275
+ align-items: center;
276
+ gap: 0.75rem;
277
+ color: var(--mb-colour-greyscale-london-5, #0d0d0d);
278
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
279
+ font-size: 1.0625rem;
280
+ font-style: normal;
281
+ font-weight: 500;
282
+ line-height: 1.5rem;
283
+ margin-bottom: 1rem;
284
+ }
285
+
286
+ .apm__error--visible {
287
+ display: flex;
288
+ }
289
+
290
+ .apm__cta {
291
+ display: flex;
292
+ width: 100%;
293
+ height: 2.75rem;
294
+ padding: 0.375rem 1rem;
295
+ justify-content: center;
296
+ align-items: center;
297
+ gap: 0.375rem;
298
+ border-radius: 0.25rem;
299
+ background: #000;
300
+ border: none;
301
+ cursor: pointer;
302
+ color: #fff;
303
+ text-align: center;
304
+ font-family: var(
305
+ --mb-typeface-sans,
306
+ var(--wall-type-system-sans, system-ui, sans-serif)
307
+ );
308
+ font-size: 1.25rem;
309
+ font-style: normal;
310
+ font-weight: 500;
311
+ line-height: normal;
312
+ }
313
+
314
+ .apm__cta:disabled {
315
+ cursor: not-allowed;
316
+ }
317
+
318
+ .apm__cta[aria-busy="true"] {
319
+ position: relative;
320
+ cursor: not-allowed;
321
+ }
322
+
323
+ .apm__cta[aria-busy="true"] > * {
324
+ visibility: hidden;
325
+ }
326
+
327
+ .apm__cta[aria-busy="true"]::after {
328
+ content: '';
329
+ position: absolute;
330
+ left: 50%;
331
+ top: 50%;
332
+ margin-left: -0.625rem;
333
+ margin-top: -0.625rem;
334
+ width: 1.25rem;
335
+ height: 1.25rem;
336
+ border: 2px solid rgba(255, 255, 255, 0.2);
337
+ border-top-color: #fff;
338
+ border-radius: 50%;
339
+ animation: apm-cta-spinner 0.8s linear infinite;
340
+ }
341
+
342
+ @keyframes apm-cta-spinner {
343
+ to {
344
+ transform: rotate(360deg);
345
+ }
346
+ }
347
+
348
+ .apm__cta svg {
349
+ flex-shrink: 0;
350
+ display: block;
351
+ }
352
+
353
+ @media (min-width: 22.5rem) and (max-width: 37.5rem) {
354
+ .apm {
355
+ width: 24.375rem;
356
+ max-width: 24.375rem;
357
+ }
358
+ }
359
+
360
+ @media (min-width: 37.5625rem) {
361
+ .apm {
362
+ border-radius: 0.5rem;
363
+ }
364
+ }
365
+
366
+ @media (max-width: 22.4375rem) {
367
+ .apm {
368
+ width: 20rem;
369
+ max-width: 20rem;
370
+ }
371
+ }
372
+
373
+ @media (max-width: 26.1875rem) {
374
+ .apm__cta {
375
+ font-size: 1.125rem;
376
+ }
377
+ }
378
+ </style>
379
+
380
+ <div class="apm-overlay" id="apple-pay-modal-overlay">
381
+ <div
382
+ class="apm"
383
+ role="dialog"
384
+ aria-modal="true"
385
+ aria-labelledby="apple-pay-modal-offer-name"
386
+ id="apple-pay-modal"
387
+ tabindex="-1"
388
+ >
389
+ <button class="apm__close" id="apple-pay-modal-close" aria-label="Close">
390
+ <svg
391
+ aria-hidden="true"
392
+ xmlns="http://www.w3.org/2000/svg"
393
+ width="24"
394
+ height="24"
395
+ viewBox="0 0 24 24"
396
+ fill="none"
397
+ >
398
+ <g clip-path="url(#clip0_15712_93054)">
399
+ <rect width="24" height="24" fill="white" fill-opacity="0.01" />
400
+ <path
401
+ fill-rule="evenodd"
402
+ clip-rule="evenodd"
403
+ d="M0 0H24V24H0V0Z"
404
+ fill="white"
405
+ fill-opacity="0.01"
406
+ />
407
+ <path
408
+ fill-rule="evenodd"
409
+ clip-rule="evenodd"
410
+ 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"
411
+ fill="#333333"
412
+ />
413
+ </g>
414
+ <defs>
415
+ <clipPath id="clip0_15712_93054">
416
+ <rect width="24" height="24" fill="white" />
417
+ </clipPath>
418
+ </defs>
419
+ </svg>
420
+ </button>
421
+ <p class="apm__offer-name" id="apple-pay-modal-offer-name"></p>
422
+ <p class="apm__due-today" id="apple-pay-modal-due-today"></p>
423
+ <hr class="apm__rule" />
424
+ <div class="apm__terms" id="apple-pay-modal-terms"></div>
425
+ <p class="apm__passive-terms" id="apple-pay-modal-passive-terms"></p>
426
+ <label
427
+ class="apm__checkbox-label"
428
+ id="apple-pay-modal-checkbox-label"
429
+ for="apple-pay-modal-checkbox"
430
+ >
431
+ <div class="apm__checkbox-wrapper">
432
+ <input
433
+ class="apm__checkbox-input"
434
+ type="checkbox"
435
+ id="apple-pay-modal-checkbox"
436
+ />
437
+ <span class="apm__checkbox-custom">
438
+ <svg
439
+ aria-hidden="true"
440
+ class="svg-unchecked"
441
+ width="24"
442
+ height="24"
443
+ viewBox="0 0 24 24"
444
+ fill="none"
445
+ xmlns="http://www.w3.org/2000/svg"
446
+ >
447
+ <path
448
+ 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"
449
+ fill="white"
450
+ stroke="#595959"
451
+ />
452
+ </svg>
453
+ <svg
454
+ aria-hidden="true"
455
+ class="svg-checked"
456
+ width="24"
457
+ height="24"
458
+ viewBox="0 0 24 24"
459
+ fill="none"
460
+ xmlns="http://www.w3.org/2000/svg"
461
+ >
462
+ <g clip-path="url(#clip0_20014_9555)">
463
+ <path
464
+ 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"
465
+ fill="#333333"
466
+ />
467
+ <path
468
+ d="M10.2857 17L6 12.6814L7.20857 11.4635L10.2857 14.5557L16.7914 8L18 9.22649L10.2857 17Z"
469
+ fill="white"
470
+ />
471
+ </g>
472
+ <defs>
473
+ <clipPath id="clip0_20014_9555">
474
+ <path
475
+ 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"
476
+ fill="white"
477
+ />
478
+ </clipPath>
479
+ </defs>
480
+ </svg>
481
+ <svg
482
+ aria-hidden="true"
483
+ class="svg-error"
484
+ width="24"
485
+ height="24"
486
+ viewBox="0 0 24 24"
487
+ fill="none"
488
+ xmlns="http://www.w3.org/2000/svg"
489
+ >
490
+ <path
491
+ 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"
492
+ fill="white"
493
+ stroke="#C91D42"
494
+ stroke-width="2"
495
+ />
496
+ </svg>
497
+ </span>
498
+ </div>
499
+ <span id="apple-pay-modal-checkbox-text"></span>
500
+ </label>
501
+ <div class="apm__error" id="apple-pay-modal-error" role="alert">
502
+ <svg
503
+ aria-hidden="true"
504
+ width="20"
505
+ height="20"
506
+ viewBox="0 0 20 20"
507
+ fill="none"
508
+ xmlns="http://www.w3.org/2000/svg"
509
+ >
510
+ <path
511
+ fill-rule="evenodd"
512
+ clip-rule="evenodd"
513
+ 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"
514
+ fill="#C91D42"
515
+ />
516
+ </svg>
517
+ Please accept the terms to continue
518
+ </div>
519
+ <button class="apm__cta" id="apple-pay-modal-cta" aria-label="Continue with Apple Pay">
520
+ <span class="apm__cta-content">
521
+ Continue with
522
+ <svg width="51" height="22" aria-hidden="true" role="img">
523
+ <use href="#apple-pay-logo" />
524
+ </svg>
525
+ </span>
526
+ </button>
527
+ </div>
528
+ </div>
529
+
530
+ `;
531
+
532
+ // src/clients/payment-checkout/config.ts
533
+ var runtimeConfig = null;
534
+ var getApplePayConfig = () => runtimeConfig;
535
+
536
+ // src/utils/recaptcha.ts
537
+ var loadRecaptchaScript = (siteKey) => {
538
+ return new Promise((resolve, reject) => {
539
+ if (typeof grecaptcha !== "undefined") return resolve();
540
+ const existingScript = document.querySelector(
541
+ 'script[src*="recaptcha/api.js"]'
542
+ );
543
+ if (existingScript) {
544
+ existingScript.addEventListener("load", () => resolve());
545
+ existingScript.addEventListener(
546
+ "error",
547
+ () => reject(new Error("Failed to load existing reCAPTCHA script"))
548
+ );
549
+ return;
550
+ }
551
+ const script = document.createElement("script");
552
+ script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
553
+ script.async = true;
554
+ script.defer = true;
555
+ script.onload = () => resolve();
556
+ script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
557
+ document.head.appendChild(script);
558
+ });
559
+ };
560
+ var getRecaptchaToken = async (action) => {
561
+ const siteKey = getApplePayConfig()?.recaptchaSiteKey;
562
+ if (!siteKey) {
563
+ throw new Error(
564
+ "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
565
+ );
566
+ }
567
+ await loadRecaptchaScript(siteKey);
568
+ return new Promise((resolve, reject) => {
569
+ grecaptcha.ready(() => {
570
+ grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
571
+ });
572
+ });
573
+ };
574
+ var getPurchaseRecaptchaTokens = async () => {
575
+ const [checkEligibility, newBasket, registerUser] = await Promise.all([
576
+ getRecaptchaToken("checkEligibility"),
577
+ getRecaptchaToken("newBasket"),
578
+ getRecaptchaToken("registerUser")
579
+ ]);
580
+ return { checkEligibility, newBasket, registerUser };
581
+ };
80
582
 
81
583
  // src/apple-pay-modal.ts
82
584
  function getRenewalDateText(unit, duration) {
@@ -128,6 +630,7 @@ function getOfferTypeText(offerType) {
128
630
  }
129
631
  var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
130
632
  var PRIVACY_LINK = '<a href="https://www.economistgroup.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>';
633
+ var MSG_RECAPTCHA_LOAD_ERROR = "Something went wrong. Please close and try again.";
131
634
  function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
132
635
  const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
133
636
  if (isNYT) {
@@ -297,6 +800,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
297
800
  #country = "";
298
801
  #triggerElement = null;
299
802
  #keyHandler = null;
803
+ #recaptchaTokens = null;
804
+ #recaptchaTokensPromise = null;
805
+ /**
806
+ * Synchronous callback invoked directly from the CTA click handler once the
807
+ * user confirms. The caller (ApplePayButton) must call startApplePaySession
808
+ * inside this callback to preserve the browser's user-gesture chain — Safari
809
+ * requires new ApplePaySession() to execute within the original click stack.
810
+ *
811
+ * reCAPTCHA tokens are guaranteed non-null here: the CTA remains disabled
812
+ * until the token pre-fetch resolves, so this callback is never reached
813
+ * without valid tokens.
814
+ */
815
+ onConfirm = null;
300
816
  set offer(value) {
301
817
  this.#offer = value;
302
818
  }
@@ -391,27 +907,57 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
391
907
  }
392
908
  });
393
909
  }
910
+ if (this.#recaptchaTokensPromise) {
911
+ if (cta) {
912
+ cta.setAttribute("disabled", "");
913
+ cta.setAttribute("aria-disabled", "true");
914
+ cta.setAttribute("aria-busy", "true");
915
+ }
916
+ this.#recaptchaTokensPromise.then((tokens) => {
917
+ this.#recaptchaTokens = tokens;
918
+ if (cta) {
919
+ cta.removeAttribute("disabled");
920
+ cta.removeAttribute("aria-disabled");
921
+ cta.removeAttribute("aria-busy");
922
+ }
923
+ }).catch(() => {
924
+ console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
925
+ if (cta) {
926
+ cta.removeAttribute("aria-busy");
927
+ }
928
+ const existingError = this.querySelector("#apple-pay-modal-error");
929
+ if (existingError) {
930
+ const icon = existingError.querySelector("svg");
931
+ existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
932
+ if (icon) existingError.prepend(icon);
933
+ existingError.classList.add("apm__error--visible");
934
+ } else {
935
+ const errorNotice = document.createElement("div");
936
+ errorNotice.setAttribute("role", "alert");
937
+ errorNotice.className = "apm__error apm__error--visible";
938
+ errorNotice.textContent = MSG_RECAPTCHA_LOAD_ERROR;
939
+ cta?.insertAdjacentElement("beforebegin", errorNotice);
940
+ }
941
+ });
942
+ }
394
943
  cta?.addEventListener("click", () => {
395
944
  if (isUS && checkbox && !checkbox.checked) {
396
945
  errorElement?.classList.add("apm__error--visible");
397
946
  checkboxLabel?.classList.add("apm__checkbox-label--error");
398
947
  return;
399
948
  }
400
- this.dispatchEvent(
401
- new CustomEvent("apple-pay-confirmed", {
402
- bubbles: true,
403
- composed: true,
404
- detail: { skuId: offer.sku_id, country }
405
- })
406
- );
407
- this.close();
949
+ if (this.#recaptchaTokens) {
950
+ this.onConfirm?.(this.#recaptchaTokens);
951
+ this.close();
952
+ }
408
953
  });
409
954
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
410
955
  "click",
411
956
  () => this.close()
412
957
  );
413
958
  }
414
- open() {
959
+ open(recaptchaTokensPromise) {
960
+ this.#recaptchaTokensPromise = recaptchaTokensPromise ?? getPurchaseRecaptchaTokens();
415
961
  this.#triggerElement = document.activeElement;
416
962
  document.body.appendChild(this);
417
963
  this.#keyHandler = (event) => {
@@ -524,10 +1070,6 @@ var apple_pay_button_template_default = `<style>
524
1070
  </div>
525
1071
  `;
526
1072
 
527
- // src/clients/payment-checkout/config.ts
528
- var runtimeConfig = null;
529
- var getApplePayConfig = () => runtimeConfig;
530
-
531
1073
  // src/clients/payment-checkout/detects.ts
532
1074
  var isAppleDeviceOrSafari = () => {
533
1075
  const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
@@ -536,27 +1078,21 @@ var isAppleDeviceOrSafari = () => {
536
1078
  };
537
1079
  var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
538
1080
  var isApplePayAvailable = async () => {
539
- console.log("[web-apple-pay] isApplePayAvailable: checking availability");
540
1081
  if (bypassApplePayChecks()) {
541
- console.log("[web-apple-pay] isApplePayAvailable: bypass active \u2192 true");
542
1082
  return true;
543
1083
  }
544
1084
  if (!isAppleDeviceOrSafari()) {
545
- console.log("[web-apple-pay] isApplePayAvailable: not Apple device or Safari \u2192 false");
546
1085
  return false;
547
1086
  }
548
1087
  const applePaySession = window.ApplePaySession;
549
1088
  if (!applePaySession) {
550
- console.log("[web-apple-pay] isApplePayAvailable: ApplePaySession not found on window \u2192 false");
551
1089
  return false;
552
1090
  }
553
1091
  try {
554
1092
  if (typeof applePaySession.canMakePayments !== "function") {
555
- console.log("[web-apple-pay] isApplePayAvailable: canMakePayments is not a function \u2192 false");
556
1093
  return false;
557
1094
  }
558
1095
  if (!applePaySession.canMakePayments()) {
559
- console.log("[web-apple-pay] isApplePayAvailable: canMakePayments() returned false \u2192 false");
560
1096
  return false;
561
1097
  }
562
1098
  const merchantId = getApplePayConfig()?.merchantId;
@@ -566,10 +1102,8 @@ var isApplePayAvailable = async () => {
566
1102
  );
567
1103
  return false;
568
1104
  }
569
- console.log("[web-apple-pay] isApplePayAvailable : calling canMakePaymentsWithActiveCard", { merchantId });
570
- const result = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
571
- console.log("[web-apple-pay] isApplePayAvailable: canMakePaymentsWithActiveCard \u2192", result);
572
- return result;
1105
+ const hasActiveCard = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
1106
+ return hasActiveCard;
573
1107
  } catch (error) {
574
1108
  console.error("Error checking Apple Pay active card status:", error);
575
1109
  return false;
@@ -617,11 +1151,18 @@ var apiFetch = async (path, options = {}) => {
617
1151
  });
618
1152
  if (!response.ok) {
619
1153
  const contentType = response.headers.get("content-type") || "";
1154
+ console.warn("[web-apple-pay] apiFetch: non-2xx response", {
1155
+ path,
1156
+ status: response.status,
1157
+ contentType
1158
+ });
620
1159
  if (contentType.includes("application/json")) {
621
1160
  const errorBody = await response.json();
1161
+ console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
622
1162
  throw new ApiError(response.status, errorBody);
623
1163
  }
624
1164
  const errorText = await response.text();
1165
+ console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
625
1166
  throw new Error(
626
1167
  `API request failed: ${response.status} ${response.statusText} - ${errorText}`
627
1168
  );
@@ -659,6 +1200,22 @@ var getExpressCheckoutSession = () => {
659
1200
  };
660
1201
 
661
1202
  // src/utils/expressCheckoutRedirect.ts
1203
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1204
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1205
+ var writeCheckoutRedirect = (originUrl) => {
1206
+ if (originUrl.startsWith("/")) {
1207
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1208
+ return;
1209
+ }
1210
+ try {
1211
+ const { hostname } = new URL(originUrl);
1212
+ const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1213
+ if (isAllowed) {
1214
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1215
+ }
1216
+ } catch {
1217
+ }
1218
+ };
662
1219
  var captureOriginUrl = () => window.location.href;
663
1220
  var buildQueryString = () => {
664
1221
  const countryCode = new URLSearchParams(window.location.search).get(
@@ -683,68 +1240,19 @@ var redirectToCheckoutComplete = (params) => {
683
1240
  url.searchParams.set("lmo_offer", params.skuId);
684
1241
  url.searchParams.set("status", params.status);
685
1242
  url.searchParams.set("redirectToCheckout", "true");
686
- url.searchParams.set("returnUrl", params.returnUrl);
687
1243
  return url.pathname + url.search;
688
1244
  };
689
1245
  var redirectToCheckoutFallback = (params) => {
690
1246
  const url = new URL(window.location.href);
691
1247
  url.pathname = "/checkout";
692
- url.searchParams.set("lmo_offer", params.skuId);
1248
+ url.searchParams.set("lmo_offer", decodeSku(params.skuId));
693
1249
  if (params.inlineError)
694
1250
  url.searchParams.set("inlineError", params.inlineError);
695
1251
  if (params.email) url.searchParams.set("email", params.email);
696
1252
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
697
- url.searchParams.set("returnUrl", params.returnUrl);
698
1253
  return url.pathname + url.search;
699
1254
  };
700
1255
 
701
- // src/utils/recaptcha.ts
702
- var loadRecaptchaScript = (siteKey) => {
703
- return new Promise((resolve, reject) => {
704
- if (typeof grecaptcha !== "undefined") return resolve();
705
- const existingScript = document.querySelector(
706
- 'script[src*="recaptcha/api.js"]'
707
- );
708
- if (existingScript) {
709
- existingScript.addEventListener("load", () => resolve());
710
- existingScript.addEventListener(
711
- "error",
712
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
713
- );
714
- return;
715
- }
716
- const script = document.createElement("script");
717
- script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
718
- script.async = true;
719
- script.defer = true;
720
- script.onload = () => resolve();
721
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
722
- document.head.appendChild(script);
723
- });
724
- };
725
- var getRecaptchaToken = async (action) => {
726
- const siteKey = getApplePayConfig()?.recaptchaSiteKey;
727
- if (!siteKey) {
728
- throw new Error(
729
- "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
730
- );
731
- }
732
- await loadRecaptchaScript(siteKey);
733
- return new Promise((resolve, reject) => {
734
- grecaptcha.ready(() => {
735
- grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
736
- });
737
- });
738
- };
739
- var getPurchaseRecaptchaTokens = async () => {
740
- const [checkEligibility, newBasket, registerUser] = await Promise.all([
741
- getRecaptchaToken("checkEligibility"),
742
- getRecaptchaToken("newBasket"),
743
- getRecaptchaToken("registerUser")
744
- ]);
745
- return { checkEligibility, newBasket, registerUser };
746
- };
747
-
748
1256
  // src/utils/expressCheckoutTracking.ts
749
1257
  var EXPRESS_CHECKOUT_EVENTS = {
750
1258
  /** Wallet button displayed */
@@ -831,72 +1339,39 @@ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
831
1339
 
832
1340
  // src/clients/payment-checkout/apple-session.ts
833
1341
  function redirectToFallbackCheckout(config) {
834
- console.warn("[web-apple-pay] redirectToFallbackCheckout", {
835
- skuId: config.skuId,
836
- returnUrl: config.returnUrl,
837
- inlineError: MSG_PAYMENT_GENERIC_ERROR
838
- });
1342
+ writeCheckoutRedirect(config.returnUrl);
839
1343
  trackFallbackCheckout(config.skuId);
840
1344
  window.location.href = redirectToCheckoutFallback({
841
1345
  skuId: config.skuId,
842
- returnUrl: config.returnUrl,
843
1346
  inlineError: MSG_PAYMENT_GENERIC_ERROR
844
1347
  });
845
1348
  }
846
1349
  async function handleMerchantValidation(event, session, config) {
847
- console.log("[web-apple-pay] handleMerchantValidation: start", {
848
- skuId: config.skuId,
849
- validationURL: event.validationURL
850
- });
851
1350
  try {
852
1351
  const validationHost = new URL(event.validationURL).host;
853
- console.log("[web-apple-pay] handleMerchantValidation: host parsed", {
854
- validationHost
855
- });
856
1352
  if (!validationHost.endsWith("apple.com")) {
857
- console.warn(
858
- "[web-apple-pay] handleMerchantValidation: invalid validation host",
859
- { validationHost }
860
- );
861
1353
  session.abort();
862
1354
  redirectToFallbackCheckout(config);
863
1355
  return;
864
1356
  }
865
- console.log("[web-apple-pay] handleMerchantValidation: requesting recaptcha token");
866
1357
  const recaptchaToken = await getRecaptchaToken("verifyMerchant");
867
- console.log("[web-apple-pay] handleMerchantValidation: recaptcha token received");
868
1358
  const response = await validateAppleMerchant({
869
1359
  validationURL: event.validationURL,
870
1360
  recaptchaToken
871
1361
  });
872
1362
  if (!response?.merchantSession) {
873
- console.warn(
874
- "[web-apple-pay] handleMerchantValidation: missing merchantSession in response"
875
- );
876
1363
  session.abort();
877
1364
  redirectToFallbackCheckout(config);
878
1365
  return;
879
1366
  }
880
- console.log("[web-apple-pay] handleMerchantValidation: completing merchant validation");
881
1367
  session.completeMerchantValidation(response.merchantSession);
882
- console.log("[web-apple-pay] handleMerchantValidation: success");
883
1368
  } catch (err) {
884
- console.error("[web-apple-pay] handleMerchantValidation: failed", {
885
- skuId: config.skuId,
886
- err
887
- });
1369
+ console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
888
1370
  session.abort();
889
1371
  redirectToFallbackCheckout(config);
890
1372
  }
891
1373
  }
892
1374
  function startApplePaySession(config) {
893
- console.log("[web-apple-pay] startApplePaySession", {
894
- skuId: config.skuId,
895
- countryCode: config.countryCode,
896
- currencyCode: config.currencyCode,
897
- totalAmount: config.totalAmount,
898
- userLoggedIn: config.userLoggedIn
899
- });
900
1375
  const paymentRequest = {
901
1376
  countryCode: config.countryCode,
902
1377
  currencyCode: config.currencyCode,
@@ -910,43 +1385,22 @@ function startApplePaySession(config) {
910
1385
  // Only request email from Apple Pay sheet if user is NOT logged in
911
1386
  ...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
912
1387
  };
913
- console.log("[web-apple-pay] startApplePaySession: payment request created", {
914
- skuId: config.skuId,
915
- requiredShippingContactFields: paymentRequest.requiredShippingContactFields
916
- });
917
1388
  try {
918
1389
  const session = new ApplePaySession(3, paymentRequest);
919
- console.log("[web-apple-pay] startApplePaySession: ApplePaySession created");
920
1390
  session.onvalidatemerchant = async (event) => {
921
- console.log("[web-apple-pay] onvalidatemerchant: invoked", {
922
- skuId: config.skuId
923
- });
924
1391
  await handleMerchantValidation(event, session, config);
925
1392
  };
926
1393
  session.onpaymentauthorized = (event) => {
927
- console.log("[web-apple-pay] onpaymentauthorized: invoked", {
928
- skuId: config.skuId
929
- });
930
1394
  trackWalletPaymentAuthorised(config.skuId);
931
1395
  config.onPaymentAuthorized(event, session);
932
1396
  };
933
1397
  session.oncancel = () => {
934
- console.log("[web-apple-pay] oncancel: invoked", {
935
- skuId: config.skuId
936
- });
937
1398
  config.onCancel?.();
938
1399
  };
939
- console.log("[web-apple-pay] startApplePaySession: opening payment sheet", {
940
- skuId: config.skuId
941
- });
942
1400
  trackWalletPaymentSheetOpen(config.skuId);
943
1401
  session.begin();
944
- console.log("[web-apple-pay] startApplePaySession: session begin called");
945
1402
  } catch (err) {
946
- console.error("[web-apple-pay] startApplePaySession: failed to start session", {
947
- skuId: config.skuId,
948
- err
949
- });
1403
+ console.error("[web-apple-pay] startApplePaySession: failed to create session", err);
950
1404
  redirectToFallbackCheckout(config);
951
1405
  }
952
1406
  }
@@ -1037,25 +1491,13 @@ var billingContactDetails = (contact, supportedBillingCountries) => {
1037
1491
  // src/clients/payment-checkout/shipping-contact-details.ts
1038
1492
  var EMAIL_FORMAT_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1039
1493
  var shippingContactDetails = (shippingContact) => {
1040
- console.log("[web-apple-pay] shippingContactDetails: start", {
1041
- hasShippingContact: Boolean(shippingContact),
1042
- hasEmailAddress: shippingContact?.emailAddress !== void 0
1043
- });
1044
1494
  const errors = [];
1045
1495
  let emailAddress = "";
1046
1496
  if (shippingContact?.emailAddress === void 0 || shippingContact?.emailAddress === "") {
1047
- console.log(
1048
- "[web-apple-pay] shippingContactDetails: email missing or empty"
1049
- );
1050
1497
  pushRequiredFieldError(errors, "shipping", "emailAddress");
1051
1498
  } else {
1052
- console.log("[web-apple-pay] shippingContactDetails: email provided");
1053
1499
  emailAddress = shippingContact.emailAddress.trim();
1054
- console.log("[web-apple-pay] shippingContactDetails: email trimmed", {
1055
- emailLength: emailAddress.length
1056
- });
1057
1500
  if (emailAddress.length > 80) {
1058
- console.log("[web-apple-pay] shippingContactDetails: email too long");
1059
1501
  pushFieldError(
1060
1502
  errors,
1061
1503
  "shipping",
@@ -1064,9 +1506,6 @@ var shippingContactDetails = (shippingContact) => {
1064
1506
  MSG_EMAIL_TOO_LONG
1065
1507
  );
1066
1508
  } else if (!EMAIL_FORMAT_REGEX.test(emailAddress)) {
1067
- console.log(
1068
- "[web-apple-pay] shippingContactDetails: email format invalid"
1069
- );
1070
1509
  pushFieldError(
1071
1510
  errors,
1072
1511
  "shipping",
@@ -1074,17 +1513,11 @@ var shippingContactDetails = (shippingContact) => {
1074
1513
  "format",
1075
1514
  MSG_EMAIL_WRONG_FORMAT
1076
1515
  );
1077
- } else {
1078
- console.log("[web-apple-pay] shippingContactDetails: email valid");
1079
1516
  }
1080
1517
  }
1081
1518
  if (errors.length > 0) {
1082
- console.log("[web-apple-pay] shippingContactDetails: throwing validation error", {
1083
- errorCount: errors.length
1084
- });
1085
1519
  throw new ContactValidationError(errors);
1086
1520
  }
1087
- console.log("[web-apple-pay] shippingContactDetails: success");
1088
1521
  return { emailAddress };
1089
1522
  };
1090
1523
 
@@ -1099,9 +1532,15 @@ var failApplePaySession = (session, errors = []) => {
1099
1532
  };
1100
1533
  function parseWalletApiError(err) {
1101
1534
  const body = err instanceof ApiError ? err.body : err;
1535
+ console.warn("[web-apple-pay] parseWalletApiError: raw error", {
1536
+ isApiError: err instanceof ApiError,
1537
+ body
1538
+ });
1102
1539
  if (body && typeof body === "object" && "errorType" in body && "message" in body) {
1540
+ console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
1103
1541
  return body;
1104
1542
  }
1543
+ console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
1105
1544
  return null;
1106
1545
  }
1107
1546
  function validateContacts(event, sessionData, supportedBillingCountries) {
@@ -1115,10 +1554,10 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
1115
1554
  return void 0;
1116
1555
  }
1117
1556
  function redirectFallback(skuId, returnUrl, options) {
1557
+ writeCheckoutRedirect(returnUrl);
1118
1558
  trackFallbackCheckout(skuId);
1119
1559
  window.location.href = redirectToCheckoutFallback({
1120
1560
  skuId,
1121
- returnUrl,
1122
1561
  ...options
1123
1562
  });
1124
1563
  }
@@ -1146,58 +1585,49 @@ function handleStructuredError(ctx) {
1146
1585
  async function handlePaymentAuthorized(event, session, sessionData, config) {
1147
1586
  const { skuId, returnUrl, queryString } = config;
1148
1587
  let validatedEmail;
1149
- console.log("[web-apple-pay] handlePaymentAuthorized: starting", { skuId, userLoggedIn: sessionData.loggedIn });
1150
1588
  try {
1151
- console.log("[web-apple-pay] handlePaymentAuthorized: step 1 \u2014 validating contacts");
1152
1589
  validatedEmail = validateContacts(
1153
1590
  event,
1154
1591
  sessionData,
1155
1592
  config.supportedBillingCountries
1156
1593
  );
1157
- console.log("[web-apple-pay] handlePaymentAuthorized: contacts valid", { validatedEmail });
1158
- console.log("[web-apple-pay] handlePaymentAuthorized: step 2 \u2014 calling performPurchase", { skuId });
1159
1594
  const result = await performPurchase({
1160
1595
  applePayToken: event.payment.token,
1161
1596
  skuId: config.skuId,
1597
+ currency: config.currency,
1162
1598
  billingContact: event.payment.billingContact,
1163
1599
  ...event.payment.shippingContact && {
1164
1600
  shippingContact: event.payment.shippingContact
1165
1601
  },
1166
1602
  recaptchaTokens: config.recaptchaTokens
1167
1603
  });
1168
- console.log("[web-apple-pay] handlePaymentAuthorized: performPurchase succeeded", { basketId: result.basketId });
1169
- console.log("[web-apple-pay] handlePaymentAuthorized: step 3 \u2014 completing payment and redirecting");
1170
1604
  trackWalletPaymentSuccess(skuId, result.basketId);
1171
1605
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1172
1606
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1607
+ writeCheckoutRedirect(returnUrl);
1173
1608
  window.location.href = redirectToCheckoutComplete({
1174
1609
  basketId: result.basketId,
1175
1610
  status: userStatus,
1176
1611
  skuId: decodeSku(skuId),
1177
- returnUrl,
1178
1612
  queryString
1179
1613
  });
1180
1614
  } catch (err) {
1181
1615
  if (err instanceof ContactValidationError) {
1182
- console.warn("[web-apple-pay] handlePaymentAuthorized: contact validation failed", err.errors);
1183
1616
  failApplePaySession(session, err.errors);
1184
1617
  return;
1185
1618
  }
1186
1619
  console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1187
1620
  const walletError = parseWalletApiError(err);
1188
- console.error("[web-apple-pay] handlePaymentAuthorized: BFF/network error", {
1189
- skuId,
1190
- errorType: walletError?.errorType ?? "UNKNOWN",
1191
- message: walletError?.message
1621
+ console.warn("[web-apple-pay] handlePaymentAuthorized: walletError result", {
1622
+ walletError,
1623
+ validatedEmail
1192
1624
  });
1193
1625
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1194
1626
  failApplePaySession(session);
1195
1627
  if (!walletError) {
1196
- console.warn("[web-apple-pay] handlePaymentAuthorized: unstructured error, using generic fallback");
1197
1628
  redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1198
1629
  return;
1199
1630
  }
1200
- console.log("[web-apple-pay] handlePaymentAuthorized: dispatching structured error handler", { errorType: walletError.errorType });
1201
1631
  handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1202
1632
  }
1203
1633
  }
@@ -1519,7 +1949,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1519
1949
  #returnUrl = "";
1520
1950
  #supportedBillingCountries = [];
1521
1951
  #entryPoint = "";
1522
- #recaptchaTokens = null;
1952
+ #recaptchaTokensPromise = null;
1523
1953
  // ─── Public API ─────────────────────────────────────────────────────────────
1524
1954
  get offer() {
1525
1955
  return this.#offer;
@@ -1584,6 +2014,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1584
2014
  this.#paymentOptions = null;
1585
2015
  this.#returnUrl = "";
1586
2016
  this.#supportedBillingCountries = [];
2017
+ this.#recaptchaTokensPromise = null;
1587
2018
  this.querySelector(SEL_CONTAINER)?.remove();
1588
2019
  }
1589
2020
  // ─── Render pipeline ────────────────────────────────────────────────────────
@@ -1597,7 +2028,8 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1597
2028
  this.#computeBillingCountries();
1598
2029
  if (await this.#fetchAndCacheOptions()) return;
1599
2030
  this.#finishLoading();
1600
- } catch {
2031
+ } catch (err) {
2032
+ console.error("[web-apple-pay] #run: unexpected error, hiding button", err);
1601
2033
  this.style.display = "none";
1602
2034
  this.querySelector(SEL_CONTAINER)?.remove();
1603
2035
  } finally {
@@ -1678,16 +2110,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1678
2110
  if (button.classList.contains("wap-loading")) return;
1679
2111
  this.#openModal();
1680
2112
  });
1681
- document.addEventListener("apple-pay-confirmed", (event) => {
1682
- const { skuId, country } = event.detail;
1683
- this.#initiateApplePay(skuId, country);
1684
- }, { once: true });
1685
2113
  }
1686
2114
  #finishLoading() {
1687
2115
  const button = this.querySelector(SEL_BUTTON);
1688
2116
  if (button) {
1689
2117
  button.classList.remove("wap-loading");
1690
2118
  this.#updateDisabledState();
2119
+ this.#scheduleRecaptchaTokens();
1691
2120
  trackExpressWalletImpression(this.#offer.sku_id, {
1692
2121
  entryPoint: this.#entryPoint,
1693
2122
  country: this.#country
@@ -1715,10 +2144,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1715
2144
  return;
1716
2145
  }
1717
2146
  trackExpressWalletClick(this.#offer.sku_id);
1718
- getPurchaseRecaptchaTokens().then((tokens) => {
1719
- this.#recaptchaTokens = tokens;
1720
- }).catch(() => {
1721
- });
1722
2147
  ApplePayModal.define();
1723
2148
  const modal = document.createElement(ApplePayModal.tag);
1724
2149
  modal.offer = this.#offer;
@@ -1726,7 +2151,30 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1726
2151
  if (this.#sessionData?.loggedIn) {
1727
2152
  modal.dataset.loggedIn = "true";
1728
2153
  }
1729
- modal.open();
2154
+ const skuId = this.#offer.sku_id;
2155
+ const country = this.#country;
2156
+ modal.onConfirm = (recaptchaTokens) => {
2157
+ this.#initiateApplePay(skuId, country, recaptchaTokens);
2158
+ this.#scheduleRecaptchaTokens();
2159
+ };
2160
+ modal.open(this.#recaptchaTokensPromise);
2161
+ }
2162
+ /**
2163
+ * Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
2164
+ * If the fetch rejects, the field is cleared so the next `modal.open()` call
2165
+ * falls back to a new attempt via the modal's own null-fallback path — preventing
2166
+ * a single transient failure from permanently disabling the CTA on all retries.
2167
+ * The identity guard prevents a late rejection from clobbering a newer attempt.
2168
+ */
2169
+ #scheduleRecaptchaTokens() {
2170
+ const attempt = getPurchaseRecaptchaTokens();
2171
+ attempt.catch((err) => {
2172
+ console.error("[web-apple-pay] reCAPTCHA token pre-fetch failed; will retry on modal open", err);
2173
+ if (this.#recaptchaTokensPromise === attempt) {
2174
+ this.#recaptchaTokensPromise = null;
2175
+ }
2176
+ });
2177
+ this.#recaptchaTokensPromise = attempt;
1730
2178
  }
1731
2179
  async #fetchSession() {
1732
2180
  try {
@@ -1762,6 +2210,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1762
2210
  onPaymentAuthorized: (event, session) => handlePaymentAuthorized(event, session, sessionData, {
1763
2211
  skuId,
1764
2212
  country,
2213
+ currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1765
2214
  returnUrl: this.#returnUrl,
1766
2215
  queryString: buildQueryString(),
1767
2216
  supportedBillingCountries: this.#supportedBillingCountries,
@@ -1769,11 +2218,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1769
2218
  })
1770
2219
  };
1771
2220
  }
1772
- async #initiateApplePay(skuId, country) {
2221
+ #initiateApplePay(skuId, country, recaptchaTokens) {
1773
2222
  try {
1774
2223
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1775
2224
  const paymentOptions = this.#paymentOptions;
1776
- const recaptchaTokens = this.#recaptchaTokens ?? await getPurchaseRecaptchaTokens();
1777
2225
  startApplePaySession(
1778
2226
  this.#buildSessionParams(
1779
2227
  skuId,