@economist/web-apple-pay 0.1.3-beta.3 → 0.1.3-beta.31

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,57 @@ 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
+ console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
960
+ if (cta) {
961
+ cta.removeAttribute("aria-busy");
962
+ }
963
+ const existingError = this.querySelector("#apple-pay-modal-error");
964
+ if (existingError) {
965
+ const icon = existingError.querySelector("svg");
966
+ existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
967
+ if (icon) existingError.prepend(icon);
968
+ existingError.classList.add("apm__error--visible");
969
+ } else {
970
+ const errorNotice = document.createElement("div");
971
+ errorNotice.setAttribute("role", "alert");
972
+ errorNotice.className = "apm__error apm__error--visible";
973
+ errorNotice.textContent = MSG_RECAPTCHA_LOAD_ERROR;
974
+ cta?.insertAdjacentElement("beforebegin", errorNotice);
975
+ }
976
+ });
977
+ }
395
978
  cta?.addEventListener("click", () => {
396
979
  if (isUS && checkbox && !checkbox.checked) {
397
980
  errorElement?.classList.add("apm__error--visible");
398
981
  checkboxLabel?.classList.add("apm__checkbox-label--error");
399
982
  return;
400
983
  }
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();
984
+ if (this.#recaptchaTokens) {
985
+ this.onConfirm?.(this.#recaptchaTokens);
986
+ this.close();
987
+ }
409
988
  });
410
989
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
411
990
  "click",
412
991
  () => this.close()
413
992
  );
414
993
  }
415
- open() {
994
+ open(recaptchaTokensPromise) {
995
+ this.#recaptchaTokensPromise = recaptchaTokensPromise ?? getPurchaseRecaptchaTokens();
416
996
  this.#triggerElement = document.activeElement;
417
997
  document.body.appendChild(this);
418
998
  this.#keyHandler = (event) => {
@@ -439,16 +1019,101 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
439
1019
  };
440
1020
 
441
1021
  // 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';
1022
+ var apple_pay_button_template_default = `<style>
1023
+ /* Use CSS variables for layout so parent walls can easily override placement and sizing */
1024
+ teg-apple-pay-button {
1025
+ display: block;
1026
+ margin: var(--apple-pay-margin, 0);
1027
+ width: 100%;
1028
+ height: 100%;
1029
+ }
443
1030
 
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;
1031
+ teg-apple-pay-button .apple-pay-container {
1032
+ display: flex;
1033
+ flex-direction: column;
1034
+ align-items: stretch;
1035
+ width: 100%;
1036
+ height: 100%;
1037
+ }
1038
+
1039
+ teg-apple-pay-button .apple-pay-btn {
1040
+ display: flex;
1041
+ align-items: center;
1042
+ justify-content: center;
1043
+ gap: 0.375rem;
1044
+ width: 100%;
1045
+ min-width: 0;
1046
+ height: 100%;
1047
+ min-height: var(--apple-pay-height, 2.75rem);
1048
+ background-color: var(--apple-pay-background-color, #000);
1049
+ color: var(--apple-pay-foreground-color, #fff);
1050
+ border: none;
1051
+ border-radius: var(--apple-pay-border-radius, 1.375rem);
1052
+ cursor: pointer;
1053
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
1054
+ font-size: 1.0625rem;
1055
+ font-weight: 500;
1056
+ }
1057
+
1058
+ teg-apple-pay-button .apple-pay-btn svg {
1059
+ flex-shrink: 0;
1060
+ display: block;
1061
+ color: currentColor;
1062
+ }
1063
+
1064
+ teg-apple-pay-button .apple-pay-btn.wap-loading {
1065
+ position: relative;
1066
+ cursor: default;
1067
+ }
1068
+
1069
+ teg-apple-pay-button .apple-pay-btn.wap-loading::before {
1070
+ content: '';
1071
+ position: absolute;
1072
+ top: 50%;
1073
+ left: 50%;
1074
+ width: 1.25rem;
1075
+ height: 1.25rem;
1076
+ border: 2px solid var(--apple-pay-spinner-track-color, rgba(255, 255, 255, 0.25));
1077
+ border-top-color: var(--apple-pay-spinner-head-color, currentColor);
1078
+ border-radius: 50%;
1079
+ animation: wap-button-spinner 0.8s linear infinite;
1080
+ }
1081
+
1082
+ teg-apple-pay-button .apple-pay-btn__logo {
1083
+ display: inline-flex;
1084
+ align-items: center;
1085
+ }
1086
+
1087
+ teg-apple-pay-button .apple-pay-btn__loading {
1088
+ display: none;
1089
+ }
1090
+
1091
+ @keyframes wap-button-spinner {
1092
+ from {
1093
+ transform: translate(-50%, -50%) rotate(0deg);
1094
+ }
1095
+ to {
1096
+ transform: translate(-50%, -50%) rotate(360deg);
1097
+ }
1098
+ }
1099
+ </style>
1100
+
1101
+ <div class="apple-pay-container" aria-label="Express checkout with Apple Pay">
1102
+ <button
1103
+ class="apple-pay-btn"
1104
+ type="button"
1105
+ aria-label="Pay with Apple Pay"
1106
+ data-analytics="paywall:apple-pay-checkout"
1107
+ >
1108
+ <span class="apple-pay-btn__logo">
1109
+ <svg width="51" height="22" aria-label="Apple Pay" role="img">
1110
+ <use href="#apple-pay-logo" />
1111
+ </svg>
1112
+ </span>
1113
+ <span class="apple-pay-btn__loading" aria-hidden="true">Loading...</span>
1114
+ </button>
1115
+ </div>
1116
+ `;
452
1117
 
453
1118
  // src/clients/payment-checkout/detects.ts
454
1119
  var isAppleDeviceOrSafari = () => {
@@ -482,7 +1147,8 @@ var isApplePayAvailable = async () => {
482
1147
  );
483
1148
  return false;
484
1149
  }
485
- return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
1150
+ const hasActiveCard = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
1151
+ return hasActiveCard;
486
1152
  } catch (error) {
487
1153
  console.error("Error checking Apple Pay active card status:", error);
488
1154
  return false;
@@ -572,13 +1238,25 @@ var getExpressCheckoutSession = () => {
572
1238
  };
573
1239
 
574
1240
  // src/utils/expressCheckoutRedirect.ts
575
- var captureOriginUrl = () => window.location.href;
576
- var buildQueryString = () => {
577
- const countryCode = new URLSearchParams(window.location.search).get(
578
- "country"
579
- );
580
- return countryCode ? `&country=${encodeURIComponent(countryCode)}` : "";
1241
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1242
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1243
+ function isAllowedRedirectUrl(url) {
1244
+ if (url.startsWith("/")) return !url.startsWith("//");
1245
+ try {
1246
+ const { hostname } = new URL(url);
1247
+ return hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1248
+ } catch {
1249
+ return false;
1250
+ }
1251
+ }
1252
+ var writeCheckoutRedirect = (originUrl) => {
1253
+ if (!isAllowedRedirectUrl(originUrl)) return;
1254
+ try {
1255
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1256
+ } catch {
1257
+ }
581
1258
  };
1259
+ var captureOriginUrl = () => window.location.href;
582
1260
  var decodeSku = (sku) => {
583
1261
  try {
584
1262
  return atob(sku.substring(sku.indexOf("-") + 1));
@@ -589,75 +1267,25 @@ var decodeSku = (sku) => {
589
1267
  var redirectToCheckoutComplete = (params) => {
590
1268
  const url = new URL("/checkout/complete", window.location.origin);
591
1269
  url.searchParams.set("basketId", params.basketId);
592
- if (params.queryString) {
593
- const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
594
- new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
595
- }
596
1270
  url.searchParams.set("lmo_offer", params.skuId);
597
1271
  url.searchParams.set("status", params.status);
598
1272
  url.searchParams.set("redirectToCheckout", "true");
599
- url.searchParams.set("returnUrl", params.returnUrl);
1273
+ if (params.email) url.searchParams.set("email", params.email);
1274
+ if (params.country) url.searchParams.set("country", params.country);
600
1275
  return url.pathname + url.search;
601
1276
  };
602
1277
  var redirectToCheckoutFallback = (params) => {
603
1278
  const url = new URL(window.location.href);
604
1279
  url.pathname = "/checkout";
605
- url.searchParams.set("lmo_offer", params.skuId);
1280
+ url.searchParams.set("lmo_offer", decodeSku(params.skuId));
606
1281
  if (params.inlineError)
607
1282
  url.searchParams.set("inlineError", params.inlineError);
608
1283
  if (params.email) url.searchParams.set("email", params.email);
609
1284
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
610
- url.searchParams.set("returnUrl", params.returnUrl);
1285
+ if (params.country) url.searchParams.set("country", params.country);
611
1286
  return url.pathname + url.search;
612
1287
  };
613
1288
 
614
- // src/utils/recaptcha.ts
615
- var loadRecaptchaScript = (siteKey) => {
616
- return new Promise((resolve, reject) => {
617
- if (typeof grecaptcha !== "undefined") return resolve();
618
- const existingScript = document.querySelector(
619
- 'script[src*="recaptcha/api.js"]'
620
- );
621
- if (existingScript) {
622
- existingScript.addEventListener("load", () => resolve());
623
- existingScript.addEventListener(
624
- "error",
625
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
626
- );
627
- return;
628
- }
629
- const script = document.createElement("script");
630
- script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
631
- script.async = true;
632
- script.defer = true;
633
- script.onload = () => resolve();
634
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
635
- document.head.appendChild(script);
636
- });
637
- };
638
- var getRecaptchaToken = async (action) => {
639
- const siteKey = getApplePayConfig()?.recaptchaSiteKey;
640
- if (!siteKey) {
641
- throw new Error(
642
- "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
643
- );
644
- }
645
- await loadRecaptchaScript(siteKey);
646
- return new Promise((resolve, reject) => {
647
- grecaptcha.ready(() => {
648
- grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
649
- });
650
- });
651
- };
652
- var getPurchaseRecaptchaTokens = async () => {
653
- const [checkEligibility, newBasket, registerUser] = await Promise.all([
654
- getRecaptchaToken("checkEligibility"),
655
- getRecaptchaToken("newBasket"),
656
- getRecaptchaToken("registerUser")
657
- ]);
658
- return { checkEligibility, newBasket, registerUser };
659
- };
660
-
661
1289
  // src/utils/expressCheckoutTracking.ts
662
1290
  var EXPRESS_CHECKOUT_EVENTS = {
663
1291
  /** Wallet button displayed */
@@ -738,11 +1366,43 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
738
1366
  var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
739
1367
  var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
740
1368
  var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
741
- var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
742
- var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
743
1369
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
744
1370
 
745
1371
  // src/clients/payment-checkout/apple-session.ts
1372
+ function redirectToFallbackCheckout(config) {
1373
+ writeCheckoutRedirect(config.returnUrl);
1374
+ trackFallbackCheckout(config.skuId);
1375
+ window.location.href = redirectToCheckoutFallback({
1376
+ skuId: config.skuId,
1377
+ inlineError: MSG_PAYMENT_GENERIC_ERROR,
1378
+ country: config.countryCode
1379
+ });
1380
+ }
1381
+ async function handleMerchantValidation(event, session, config) {
1382
+ try {
1383
+ const validationHost = new URL(event.validationURL).host;
1384
+ if (!validationHost.endsWith("apple.com")) {
1385
+ session.abort();
1386
+ redirectToFallbackCheckout(config);
1387
+ return;
1388
+ }
1389
+ const recaptchaToken = await getRecaptchaToken("verifyMerchant");
1390
+ const response = await validateAppleMerchant({
1391
+ validationURL: event.validationURL,
1392
+ recaptchaToken
1393
+ });
1394
+ if (!response?.merchantSession) {
1395
+ session.abort();
1396
+ redirectToFallbackCheckout(config);
1397
+ return;
1398
+ }
1399
+ session.completeMerchantValidation(response.merchantSession);
1400
+ } catch (err) {
1401
+ console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
1402
+ session.abort();
1403
+ redirectToFallbackCheckout(config);
1404
+ }
1405
+ }
746
1406
  function startApplePaySession(config) {
747
1407
  const paymentRequest = {
748
1408
  countryCode: config.countryCode,
@@ -757,55 +1417,24 @@ function startApplePaySession(config) {
757
1417
  // Only request email from Apple Pay sheet if user is NOT logged in
758
1418
  ...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
759
1419
  };
760
- const session = new ApplePaySession(3, paymentRequest);
761
- session.onvalidatemerchant = async (event) => {
762
- try {
763
- const validationHost = new URL(event.validationURL).host;
764
- if (!validationHost.endsWith("apple.com")) {
765
- trackFallbackCheckout(config.skuId);
766
- session.abort();
767
- window.location.href = redirectToCheckoutFallback({
768
- skuId: config.skuId,
769
- returnUrl: config.returnUrl,
770
- inlineError: MSG_PAYMENT_GENERIC_ERROR
771
- });
772
- return;
773
- }
774
- const recaptchaToken = await getRecaptchaToken("verifyMerchant");
775
- const response = await validateAppleMerchant({
776
- validationURL: event.validationURL,
777
- recaptchaToken
778
- });
779
- if (!response?.merchantSession) {
780
- trackFallbackCheckout(config.skuId);
781
- session.abort();
782
- window.location.href = redirectToCheckoutFallback({
783
- skuId: config.skuId,
784
- returnUrl: config.returnUrl,
785
- inlineError: MSG_PAYMENT_GENERIC_ERROR
786
- });
787
- return;
788
- }
789
- session.completeMerchantValidation(response.merchantSession);
790
- } catch {
791
- trackFallbackCheckout(config.skuId);
792
- session.abort();
793
- window.location.href = redirectToCheckoutFallback({
794
- skuId: config.skuId,
795
- returnUrl: config.returnUrl,
796
- inlineError: MSG_PAYMENT_GENERIC_ERROR
797
- });
798
- }
799
- };
800
- session.onpaymentauthorized = (event) => {
801
- trackWalletPaymentAuthorised(config.skuId);
802
- config.onPaymentAuthorized(event, session);
803
- };
804
- session.oncancel = () => {
805
- config.onCancel?.();
806
- };
807
- trackWalletPaymentSheetOpen(config.skuId);
808
- session.begin();
1420
+ try {
1421
+ const session = new ApplePaySession(3, paymentRequest);
1422
+ session.onvalidatemerchant = async (event) => {
1423
+ await handleMerchantValidation(event, session, config);
1424
+ };
1425
+ session.onpaymentauthorized = (event) => {
1426
+ trackWalletPaymentAuthorised(config.skuId);
1427
+ config.onPaymentAuthorized(event, session);
1428
+ };
1429
+ session.oncancel = () => {
1430
+ config.onCancel?.();
1431
+ };
1432
+ trackWalletPaymentSheetOpen(config.skuId);
1433
+ session.begin();
1434
+ } catch (err) {
1435
+ console.error("[web-apple-pay] startApplePaySession: failed to create session", err);
1436
+ redirectToFallbackCheckout(config);
1437
+ }
809
1438
  }
810
1439
 
811
1440
  // src/clients/payment-checkout/contact-validation.ts
@@ -925,8 +1554,6 @@ var shippingContactDetails = (shippingContact) => {
925
1554
  };
926
1555
 
927
1556
  // src/clients/payment-checkout/payment-handler.ts
928
- var MSG_ELIGIBILITY = "Something went wrong, please try again";
929
- var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
930
1557
  var failApplePaySession = (session, errors = []) => {
931
1558
  session.completePayment({
932
1559
  status: ApplePaySession.STATUS_FAILURE,
@@ -950,37 +1577,42 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
950
1577
  }
951
1578
  return void 0;
952
1579
  }
953
- function redirectFallback(skuId, returnUrl, options) {
1580
+ function redirectFallback(skuId, returnUrl, country, options) {
1581
+ writeCheckoutRedirect(returnUrl);
954
1582
  trackFallbackCheckout(skuId);
955
1583
  window.location.href = redirectToCheckoutFallback({
956
1584
  skuId,
957
- returnUrl,
1585
+ country,
958
1586
  ...options
959
1587
  });
960
1588
  }
961
1589
  var ERROR_HANDLERS = {
962
- EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
963
- emailError: MSG_EMAIL_COLLISION,
1590
+ // emailError flows as an inline field error on the email input.
1591
+ // email pre-populates the input so the user doesn't have to retype it.
1592
+ EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1593
+ emailError: walletError.message,
1594
+ email: validatedEmail
1595
+ }),
1596
+ PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1597
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1598
+ REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1599
+ ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1600
+ emailError: walletError.message,
964
1601
  email: validatedEmail
965
1602
  }),
966
- PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
967
- // Matches sma-checkout catch-all: always redirect with generic inlineError.
968
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
969
- ),
970
- ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
971
- REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
972
- VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1603
+ VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1604
+ RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
973
1605
  };
974
1606
  function handleStructuredError(ctx) {
975
1607
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
976
1608
  if (handler) {
977
1609
  handler(ctx);
978
1610
  } else {
979
- redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1611
+ redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
980
1612
  }
981
1613
  }
982
1614
  async function handlePaymentAuthorized(event, session, sessionData, config) {
983
- const { skuId, returnUrl, queryString } = config;
1615
+ const { skuId, returnUrl, country } = config;
984
1616
  let validatedEmail;
985
1617
  try {
986
1618
  validatedEmail = validateContacts(
@@ -1000,13 +1632,15 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1000
1632
  });
1001
1633
  trackWalletPaymentSuccess(skuId, result.basketId);
1002
1634
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1635
+ const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
1003
1636
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1637
+ writeCheckoutRedirect(returnUrl);
1004
1638
  window.location.href = redirectToCheckoutComplete({
1005
1639
  basketId: result.basketId,
1006
1640
  status: userStatus,
1007
1641
  skuId: decodeSku(skuId),
1008
- returnUrl,
1009
- queryString
1642
+ email: successEmail,
1643
+ country
1010
1644
  });
1011
1645
  } catch (err) {
1012
1646
  if (err instanceof ContactValidationError) {
@@ -1018,10 +1652,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1018
1652
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1019
1653
  failApplePaySession(session);
1020
1654
  if (!walletError) {
1021
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1655
+ redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1022
1656
  return;
1023
1657
  }
1024
- handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1658
+ handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
1025
1659
  }
1026
1660
  }
1027
1661
 
@@ -1307,16 +1941,22 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1307
1941
  * Creates and configures an ApplePayButton if the feature flag is present.
1308
1942
  */
1309
1943
  static createIfEnabled(offer, country, entryPoint) {
1310
- if (!offer) return null;
1311
- if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant))
1944
+ if (!offer) {
1312
1945
  return null;
1313
- if (!new URLSearchParams(window.location.search).has(FEATURE_APPLE_PAY_EXPRESS_CHECKOUT))
1946
+ }
1947
+ if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant)) {
1948
+ return null;
1949
+ }
1950
+ if (!new URLSearchParams(window.location.search).has(FEATURE_APPLE_PAY_EXPRESS_CHECKOUT)) {
1314
1951
  return null;
1952
+ }
1315
1953
  _ApplePayButton.define();
1316
1954
  const button = document.createElement(_ApplePayButton.tag);
1317
1955
  button.offer = offer;
1318
1956
  button.country = country;
1319
- button.entryPoint = entryPoint;
1957
+ if (entryPoint) {
1958
+ button.entryPoint = entryPoint;
1959
+ }
1320
1960
  return button;
1321
1961
  }
1322
1962
  /**
@@ -1338,6 +1978,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1338
1978
  #returnUrl = "";
1339
1979
  #supportedBillingCountries = [];
1340
1980
  #entryPoint = "";
1981
+ #recaptchaTokensPromise = null;
1341
1982
  // ─── Public API ─────────────────────────────────────────────────────────────
1342
1983
  get offer() {
1343
1984
  return this.#offer;
@@ -1402,20 +2043,24 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1402
2043
  this.#paymentOptions = null;
1403
2044
  this.#returnUrl = "";
1404
2045
  this.#supportedBillingCountries = [];
2046
+ this.#recaptchaTokensPromise = null;
1405
2047
  this.querySelector(SEL_CONTAINER)?.remove();
1406
2048
  }
1407
2049
  // ─── Render pipeline ────────────────────────────────────────────────────────
1408
2050
  async #run() {
1409
2051
  try {
2052
+ this.#renderTemplate();
1410
2053
  if (await this.#checkAvailability()) return;
1411
- if (await this.#checkSession()) return;
1412
2054
  if (this.#checkOffer()) return;
2055
+ if (await this.#checkSession()) return;
1413
2056
  this.#captureReturnUrl();
1414
2057
  this.#computeBillingCountries();
1415
2058
  if (await this.#fetchAndCacheOptions()) return;
1416
- this.#renderTemplate();
1417
- } catch {
2059
+ this.#finishLoading();
2060
+ } catch (err) {
2061
+ console.error("[web-apple-pay] #run: unexpected error, hiding button", err);
1418
2062
  this.style.display = "none";
2063
+ this.querySelector(SEL_CONTAINER)?.remove();
1419
2064
  } finally {
1420
2065
  this.#rendering = false;
1421
2066
  }
@@ -1425,6 +2070,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1425
2070
  const available = await isApplePayAvailable();
1426
2071
  if (!available) {
1427
2072
  this.style.display = "none";
2073
+ this.querySelector(SEL_CONTAINER)?.remove();
1428
2074
  return true;
1429
2075
  }
1430
2076
  return false;
@@ -1437,6 +2083,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1437
2083
  this.#sessionData = await this.#fetchSession();
1438
2084
  if (this.#sessionData?.loggedIn && (this.#sessionData.userType === "active-subscription" || this.#sessionData.userType === "b2b-user")) {
1439
2085
  this.style.display = "none";
2086
+ this.querySelector(SEL_CONTAINER)?.remove();
1440
2087
  return true;
1441
2088
  }
1442
2089
  return false;
@@ -1445,6 +2092,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1445
2092
  #checkOffer() {
1446
2093
  if (!this.#offer) {
1447
2094
  this.style.display = "none";
2095
+ this.querySelector(SEL_CONTAINER)?.remove();
1448
2096
  return true;
1449
2097
  }
1450
2098
  return false;
@@ -1470,8 +2118,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1470
2118
  */
1471
2119
  async #fetchAndCacheOptions() {
1472
2120
  this.#paymentOptions = await this.#fetchPaymentOptions();
1473
- if (!this.#paymentOptions?.apple_pay) {
2121
+ if (!this.#paymentOptions?.options?.apple_pay) {
1474
2122
  this.style.display = "none";
2123
+ this.querySelector(SEL_CONTAINER)?.remove();
1475
2124
  return true;
1476
2125
  }
1477
2126
  return false;
@@ -1483,19 +2132,27 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1483
2132
  const template = document.createElement("template");
1484
2133
  template.innerHTML = apple_pay_button_template_default;
1485
2134
  this.appendChild(template.content.cloneNode(true));
1486
- this.#updateDisabledState();
1487
- trackExpressWalletImpression(this.#offer.sku_id, {
1488
- entryPoint: this.#entryPoint,
1489
- country: this.#country
1490
- });
1491
2135
  const button = this.querySelector(SEL_BUTTON);
2136
+ button.classList.add("wap-loading");
2137
+ button.setAttribute("disabled", "");
2138
+ button.setAttribute("aria-disabled", "true");
2139
+ button.setAttribute("aria-busy", "true");
1492
2140
  button.addEventListener("click", () => {
2141
+ if (button.classList.contains("wap-loading")) return;
1493
2142
  this.#openModal();
1494
2143
  });
1495
- this.addEventListener("apple-pay-confirmed", (event) => {
1496
- const { skuId, country } = event.detail;
1497
- this.#initiateApplePay(skuId, country);
1498
- });
2144
+ }
2145
+ #finishLoading() {
2146
+ const button = this.querySelector(SEL_BUTTON);
2147
+ if (button) {
2148
+ button.classList.remove("wap-loading");
2149
+ this.#updateDisabledState();
2150
+ this.#scheduleRecaptchaTokens();
2151
+ trackExpressWalletImpression(this.#offer.sku_id, {
2152
+ entryPoint: this.#entryPoint,
2153
+ country: this.#country
2154
+ });
2155
+ }
1499
2156
  }
1500
2157
  // ─── Private helpers ────────────────────────────────────────────────────────
1501
2158
  #updateDisabledState() {
@@ -1504,13 +2161,19 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1504
2161
  if (this.#offer) {
1505
2162
  button.removeAttribute("disabled");
1506
2163
  button.removeAttribute("aria-disabled");
2164
+ button.removeAttribute("aria-busy");
1507
2165
  return;
1508
2166
  }
1509
2167
  button.setAttribute("disabled", "");
1510
2168
  button.setAttribute("aria-disabled", "true");
1511
2169
  }
1512
2170
  #openModal() {
1513
- if (!this.#offer || document.querySelector(ApplePayModal.tag)) return;
2171
+ if (!this.#offer) {
2172
+ return;
2173
+ }
2174
+ if (document.querySelector(ApplePayModal.tag)) {
2175
+ return;
2176
+ }
1514
2177
  trackExpressWalletClick(this.#offer.sku_id);
1515
2178
  ApplePayModal.define();
1516
2179
  const modal = document.createElement(ApplePayModal.tag);
@@ -1519,7 +2182,30 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1519
2182
  if (this.#sessionData?.loggedIn) {
1520
2183
  modal.dataset.loggedIn = "true";
1521
2184
  }
1522
- modal.open();
2185
+ const skuId = this.#offer.sku_id;
2186
+ const country = this.#country;
2187
+ modal.onConfirm = (recaptchaTokens) => {
2188
+ this.#initiateApplePay(skuId, country, recaptchaTokens);
2189
+ this.#scheduleRecaptchaTokens();
2190
+ };
2191
+ modal.open(this.#recaptchaTokensPromise);
2192
+ }
2193
+ /**
2194
+ * Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
2195
+ * If the fetch rejects, the field is cleared so the next `modal.open()` call
2196
+ * falls back to a new attempt via the modal's own null-fallback path — preventing
2197
+ * a single transient failure from permanently disabling the CTA on all retries.
2198
+ * The identity guard prevents a late rejection from clobbering a newer attempt.
2199
+ */
2200
+ #scheduleRecaptchaTokens() {
2201
+ const attempt = getPurchaseRecaptchaTokens();
2202
+ attempt.catch((err) => {
2203
+ console.error("[web-apple-pay] reCAPTCHA token pre-fetch failed; will retry on modal open", err);
2204
+ if (this.#recaptchaTokensPromise === attempt) {
2205
+ this.#recaptchaTokensPromise = null;
2206
+ }
2207
+ });
2208
+ this.#recaptchaTokensPromise = attempt;
1523
2209
  }
1524
2210
  async #fetchSession() {
1525
2211
  try {
@@ -1546,7 +2232,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1546
2232
  return {
1547
2233
  countryCode: country,
1548
2234
  currencyCode: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1549
- supportedNetworks: paymentOptions.apple_pay.settings.supported_networks,
2235
+ supportedNetworks: paymentOptions.options.apple_pay.settings.supported_networks,
1550
2236
  totalAmount,
1551
2237
  totalLabel: TOTAL_LABEL,
1552
2238
  skuId,
@@ -1557,17 +2243,15 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1557
2243
  country,
1558
2244
  currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1559
2245
  returnUrl: this.#returnUrl,
1560
- queryString: buildQueryString(),
1561
2246
  supportedBillingCountries: this.#supportedBillingCountries,
1562
2247
  recaptchaTokens
1563
2248
  })
1564
2249
  };
1565
2250
  }
1566
- async #initiateApplePay(skuId, country) {
2251
+ #initiateApplePay(skuId, country, recaptchaTokens) {
1567
2252
  try {
1568
2253
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1569
2254
  const paymentOptions = this.#paymentOptions;
1570
- const recaptchaTokens = await getPurchaseRecaptchaTokens();
1571
2255
  startApplePaySession(
1572
2256
  this.#buildSessionParams(
1573
2257
  skuId,
@@ -1583,6 +2267,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1583
2267
  }
1584
2268
  };
1585
2269
 
2270
+ // src/constants.ts
2271
+ var ApplePayEntryPoint = /* @__PURE__ */ ((ApplePayEntryPoint2) => {
2272
+ ApplePayEntryPoint2["PAYWALL"] = "article_paywall";
2273
+ ApplePayEntryPoint2["REGWALL"] = "article_regwall";
2274
+ return ApplePayEntryPoint2;
2275
+ })(ApplePayEntryPoint || {});
2276
+
1586
2277
  // src/index.ts
1587
2278
  function defineApplePayElements() {
1588
2279
  ApplePayButton.define();
@@ -1591,6 +2282,7 @@ function defineApplePayElements() {
1591
2282
  export {
1592
2283
  APPLE_PAY_EXCLUDED_VARIANTS,
1593
2284
  ApplePayButton,
2285
+ ApplePayEntryPoint,
1594
2286
  ApplePayModal,
1595
2287
  configureApplePay,
1596
2288
  defineApplePayElements,