@economist/web-apple-pay 0.1.3-beta.5 → 0.1.3-beta.50

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,543 @@ 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("error", () => {
579
+ reject(new Error("Failed to load existing reCAPTCHA script"));
580
+ });
581
+ return;
582
+ }
583
+ const script = document.createElement("script");
584
+ script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
585
+ script.async = true;
586
+ script.defer = true;
587
+ script.onload = () => resolve();
588
+ script.onerror = () => {
589
+ reject(new Error("Failed to load reCAPTCHA script"));
590
+ };
591
+ document.head.appendChild(script);
592
+ });
593
+ };
594
+ var getRecaptchaToken = async (action) => {
595
+ const siteKey = getApplePayConfig()?.recaptchaSiteKey;
596
+ if (!siteKey) {
597
+ throw new Error(
598
+ "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
599
+ );
600
+ }
601
+ await loadRecaptchaScript(siteKey);
602
+ return new Promise((resolve, reject) => {
603
+ grecaptcha.ready(() => {
604
+ grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
605
+ });
606
+ });
607
+ };
608
+ var getPurchaseRecaptchaToken = () => {
609
+ return getRecaptchaToken("walletPurchase");
610
+ };
80
611
 
81
612
  // src/apple-pay-modal.ts
82
613
  function getRenewalDateText(unit, duration) {
@@ -128,6 +659,7 @@ function getOfferTypeText(offerType) {
128
659
  }
129
660
  var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
130
661
  var PRIVACY_LINK = '<a href="https://www.economistgroup.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>';
662
+ var MSG_RECAPTCHA_LOAD_ERROR = "Something went wrong. Please close and try again.";
131
663
  function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
132
664
  const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
133
665
  if (isNYT) {
@@ -160,6 +692,8 @@ function renewalPeriodText(unit, duration) {
160
692
  return "";
161
693
  }
162
694
  }
695
+ var LI_SEPARATOR = "\0LI_SEP\0";
696
+ var P_SEPARATOR = "\0P_SEP\0";
163
697
  function buildTermsContext(offer, country) {
164
698
  const isUS = country === "US";
165
699
  const isCA = country === "CA";
@@ -196,19 +730,19 @@ function freeTrialTerms(offer, ctx) {
196
730
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? 1;
197
731
  const trialDateStr = `on ${getRenewalDateText(trialUnit, trialDuration)}${ctx.verificationText}`;
198
732
  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}`;
733
+ 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
734
  }
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}`;
735
+ 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
736
  }
203
737
  function nytTerms(ctx) {
204
738
  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
739
  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}`;
740
+ 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
741
  }
208
742
  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}`;
743
+ 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
744
  }
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}`;
745
+ 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
746
  }
213
747
  function minTermContractTerms(offer, ctx) {
214
748
  const duePrice = formatPrice(offer.price.purchase, offer.price.currency_code);
@@ -218,28 +752,28 @@ function minTermContractTerms(offer, ctx) {
218
752
  const contractLength = mtDuration && mtUnit ? `${mtDuration} ${mtUnit}` : "";
219
753
  const initialContractText = contractLength ? `your initial ${contractLength} contract` : "your initial contract";
220
754
  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}`;
755
+ 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
756
  }
223
757
  function paidTrialTerms(offer, ctx) {
224
758
  const trialUnit = offer.subscription_options?.trial_period?.unit ?? ctx.unit;
225
759
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? ctx.duration;
226
760
  const trialDateText = getRenewalDateText(trialUnit, trialDuration);
227
761
  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}`;
762
+ 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
763
  }
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}`;
764
+ 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
765
  }
232
766
  function autoRenewalTerms(ctx) {
233
767
  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}`;
768
+ 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
769
  }
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}`;
770
+ 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
771
  }
238
772
  function noAutoRenewalTerms(ctx) {
239
773
  if (ctx.isUS || ctx.isCA) {
240
- return `Your subscription does not renew automatically${ctx.taxFootnote}{{liSeparator}}`;
774
+ return `Your subscription does not renew automatically${ctx.taxFootnote}${LI_SEPARATOR}`;
241
775
  }
242
- return `Your subscription does not renew automatically{{liSeparator}}`;
776
+ return `Your subscription does not renew automatically${LI_SEPARATOR}`;
243
777
  }
244
778
  function generateApplePayModalTerms(offer, country) {
245
779
  const ctx = buildTermsContext(offer, country);
@@ -262,9 +796,9 @@ function generateApplePayModalTerms(offer, country) {
262
796
  return noAutoRenewalTerms(ctx);
263
797
  }
264
798
  function renderTermsHtml(termsText) {
265
- const renewalTextLists = termsText.split("{{liSeparator}}");
799
+ const renewalTextLists = termsText.split(LI_SEPARATOR);
266
800
  const lastPart = renewalTextLists.pop() || "";
267
- const renewalTextParagraphs = lastPart.split("{{pSeparator}}");
801
+ const renewalTextParagraphs = lastPart.split(P_SEPARATOR);
268
802
  const ulHtml = `<ul class="apm__terms-bullets">${renewalTextLists.map((value) => `<li>${value}</li>`).join("")}</ul>`;
269
803
  const paragraphHtml = renewalTextParagraphs.filter(Boolean).map((value) => `<p class="apm__terms-legal">${value}</p>`).join("");
270
804
  return ulHtml + paragraphHtml;
@@ -297,6 +831,16 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
297
831
  #country = "";
298
832
  #triggerElement = null;
299
833
  #keyHandler = null;
834
+ #isClosed = false;
835
+ /**
836
+ * Callback invoked synchronously from the CTA click handler.
837
+ * Receives a Promise of a reCAPTCHA token — the promise is already in-flight
838
+ * by the time this is called. The caller (ApplePayButton) must call
839
+ * new ApplePaySession() synchronously inside this callback to stay within
840
+ * Safari's user-gesture window. The token promise resolves before
841
+ * onPaymentAuthorized fires (user still needs to authenticate on the sheet).
842
+ */
843
+ onConfirm = null;
300
844
  set offer(value) {
301
845
  this.#offer = value;
302
846
  }
@@ -317,7 +861,6 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
317
861
  const offer = this.#offer;
318
862
  const country = this.#country;
319
863
  if (!offer) {
320
- console.error("ApplePayModal: Missing or invalid offer data");
321
864
  this.remove();
322
865
  return;
323
866
  }
@@ -393,19 +936,40 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
393
936
  });
394
937
  }
395
938
  cta?.addEventListener("click", () => {
939
+ if (cta.disabled) return;
396
940
  if (isUS && checkbox && !checkbox.checked) {
397
941
  errorElement?.classList.add("apm__error--visible");
398
942
  checkboxLabel?.classList.add("apm__checkbox-label--error");
399
943
  return;
400
944
  }
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();
945
+ errorElement?.classList.remove("apm__error--visible");
946
+ this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
947
+ cta.disabled = true;
948
+ cta.setAttribute("aria-busy", "true");
949
+ const recaptchaTokenPromise = getPurchaseRecaptchaToken();
950
+ if (!this.#isClosed && this.onConfirm) {
951
+ this.onConfirm(recaptchaTokenPromise);
952
+ this.close();
953
+ }
954
+ recaptchaTokenPromise.catch(() => {
955
+ if (this.#isClosed) return;
956
+ cta.disabled = false;
957
+ cta.removeAttribute("aria-busy");
958
+ const existingError = this.querySelector("#apple-pay-modal-error");
959
+ if (existingError) {
960
+ const icon = existingError.querySelector("svg");
961
+ existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
962
+ if (icon) existingError.prepend(icon);
963
+ existingError.setAttribute("role", "alert");
964
+ existingError.classList.add("apm__error--visible");
965
+ } else {
966
+ const errorNotice = document.createElement("div");
967
+ errorNotice.setAttribute("role", "alert");
968
+ errorNotice.className = "apm__error apm__error--visible";
969
+ errorNotice.textContent = MSG_RECAPTCHA_LOAD_ERROR;
970
+ cta?.insertAdjacentElement("beforebegin", errorNotice);
971
+ }
972
+ });
409
973
  });
410
974
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
411
975
  "click",
@@ -413,6 +977,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
413
977
  );
414
978
  }
415
979
  open() {
980
+ this.#isClosed = false;
416
981
  this.#triggerElement = document.activeElement;
417
982
  document.body.appendChild(this);
418
983
  this.#keyHandler = (event) => {
@@ -427,6 +992,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
427
992
  this.querySelector("#apple-pay-modal")?.focus();
428
993
  }
429
994
  close() {
995
+ this.#isClosed = true;
430
996
  if (this.#keyHandler) {
431
997
  document.removeEventListener("keydown", this.#keyHandler, {
432
998
  capture: true
@@ -439,16 +1005,136 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
439
1005
  };
440
1006
 
441
1007
  // 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';
1008
+ var apple_pay_button_template_default = `<style>
1009
+ /*
1010
+ * Entry CTA: \u201CSubscribe with\u201D + Apple Pay wordmark (Figma 1118:14088).
1011
+ * \u201CSubscribe with\u201D matches Continue to checkout typography (EconomistSans Regular / 17 / 24 / 400).
1012
+ */
1013
+ teg-apple-pay-button {
1014
+ display: block;
1015
+ margin: var(--apple-pay-margin, 0);
1016
+ width: 100%;
1017
+ height: 100%;
1018
+ }
443
1019
 
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;
1020
+ teg-apple-pay-button .apple-pay-container {
1021
+ display: flex;
1022
+ flex-direction: column;
1023
+ align-items: stretch;
1024
+ width: 100%;
1025
+ height: 100%;
1026
+ }
1027
+
1028
+ teg-apple-pay-button .apple-pay-btn {
1029
+ display: flex;
1030
+ align-items: center;
1031
+ justify-content: center;
1032
+ width: 100%;
1033
+ min-width: 0;
1034
+ height: 100%;
1035
+ min-height: var(--apple-pay-height, 2.5rem);
1036
+ padding: 0;
1037
+ box-sizing: border-box;
1038
+ background-color: var(--apple-pay-background-color, #1a1a1a);
1039
+ color: var(--apple-pay-foreground-color, #fff);
1040
+ border: none;
1041
+ border-radius: var(--apple-pay-border-radius, 0.25rem);
1042
+ cursor: pointer;
1043
+ /* Same as wall Continue to checkout / Marber button large */
1044
+ font-family: var(
1045
+ --mb-typeface-sans,
1046
+ var(--wall-type-system-sans, 'EconomistSans', system-ui, sans-serif)
1047
+ );
1048
+ font-size: var(--mb-button-font-size, 1.0625rem);
1049
+ font-weight: var(--mb-font-weight-regular, 400);
1050
+ line-height: 1;
1051
+ }
1052
+
1053
+ teg-apple-pay-button .apple-pay-btn__label {
1054
+ display: inline-flex;
1055
+ align-items: center;
1056
+ /* No flex gap \u2014 spacing after \u201Cwith\u201D uses the same word-space as \u201CSubscribe with\u201D */
1057
+ gap: 0;
1058
+ white-space: nowrap;
1059
+ }
1060
+
1061
+ teg-apple-pay-button .apple-pay-btn__caption {
1062
+ /* One typographic word space after \u201Cwith\u201D, matching Subscribe\u2194with */
1063
+ padding-right: 0.25em;
1064
+ }
1065
+
1066
+ teg-apple-pay-button .apple-pay-btn__logo {
1067
+ flex-shrink: 0;
1068
+ display: block;
1069
+ /* Match caption font-size so mark aligns with text metrics */
1070
+ height: 1.05em;
1071
+ width: auto;
1072
+ aspect-ratio: 51 / 22;
1073
+ color: currentColor;
1074
+ /* Optical nudge \u2014 SVG viewBox sits slightly high vs EconomistSans caps */
1075
+ transform: translateY(0.06em);
1076
+ }
1077
+
1078
+ teg-apple-pay-button .apple-pay-btn.wap-loading {
1079
+ position: relative;
1080
+ cursor: default;
1081
+ }
1082
+
1083
+ teg-apple-pay-button .apple-pay-btn.wap-loading .apple-pay-btn__label {
1084
+ visibility: hidden;
1085
+ }
1086
+
1087
+ teg-apple-pay-button .apple-pay-btn.wap-loading::before {
1088
+ content: '';
1089
+ position: absolute;
1090
+ top: 50%;
1091
+ left: 50%;
1092
+ width: 1.25rem;
1093
+ height: 1.25rem;
1094
+ border: 2px solid var(--apple-pay-spinner-track-color, rgba(255, 255, 255, 0.25));
1095
+ border-top-color: var(--apple-pay-spinner-head-color, currentColor);
1096
+ border-radius: 50%;
1097
+ animation: wap-button-spinner 0.8s linear infinite;
1098
+ }
1099
+
1100
+ teg-apple-pay-button .apple-pay-btn__loading {
1101
+ display: none;
1102
+ }
1103
+
1104
+ @keyframes wap-button-spinner {
1105
+ from {
1106
+ transform: translate(-50%, -50%) rotate(0deg);
1107
+ }
1108
+ to {
1109
+ transform: translate(-50%, -50%) rotate(360deg);
1110
+ }
1111
+ }
1112
+ </style>
1113
+
1114
+ <div class="apple-pay-container" aria-label="Subscribe with Apple Pay">
1115
+ <button
1116
+ class="apple-pay-btn"
1117
+ type="button"
1118
+ aria-label="Subscribe with Apple Pay"
1119
+ data-analytics="paywall:apple-pay-checkout"
1120
+ >
1121
+ <span class="apple-pay-btn__label"
1122
+ ><span class="apple-pay-btn__caption">Subscribe with</span
1123
+ ><svg
1124
+ class="apple-pay-btn__logo"
1125
+ width="42"
1126
+ height="18"
1127
+ viewBox="0 0 51 22"
1128
+ aria-hidden="true"
1129
+ role="img"
1130
+ >
1131
+ <use href="#apple-pay-logo" />
1132
+ </svg
1133
+ ></span>
1134
+ <span class="apple-pay-btn__loading" aria-hidden="true">Loading...</span>
1135
+ </button>
1136
+ </div>
1137
+ `;
452
1138
 
453
1139
  // src/clients/payment-checkout/detects.ts
454
1140
  var isAppleDeviceOrSafari = () => {
@@ -456,7 +1142,7 @@ var isAppleDeviceOrSafari = () => {
456
1142
  const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
457
1143
  return isIOSOrMacTouchDevice || isSafariBrowser;
458
1144
  };
459
- var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
1145
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null;
460
1146
  var isApplePayAvailable = async () => {
461
1147
  if (bypassApplePayChecks()) {
462
1148
  return true;
@@ -477,14 +1163,11 @@ var isApplePayAvailable = async () => {
477
1163
  }
478
1164
  const merchantId = getApplePayConfig()?.merchantId;
479
1165
  if (!merchantId) {
480
- console.warn(
481
- "[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
482
- );
483
1166
  return false;
484
1167
  }
485
- return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
486
- } catch (error) {
487
- console.error("Error checking Apple Pay active card status:", error);
1168
+ const hasActiveCard = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
1169
+ return hasActiveCard;
1170
+ } catch {
488
1171
  return false;
489
1172
  }
490
1173
  };
@@ -556,7 +1239,6 @@ var validateAppleMerchant = (data) => {
556
1239
  });
557
1240
  };
558
1241
  var performPurchase = (data) => {
559
- console.log("[web-apple-pay] performPurchase request", JSON.stringify(data, null, 2));
560
1242
  return apiFetch("/v1/wallet/purchase", {
561
1243
  method: "POST",
562
1244
  body: JSON.stringify(data),
@@ -573,13 +1255,25 @@ var getExpressCheckoutSession = () => {
573
1255
  };
574
1256
 
575
1257
  // src/utils/expressCheckoutRedirect.ts
576
- var captureOriginUrl = () => window.location.href;
577
- var buildQueryString = () => {
578
- const countryCode = new URLSearchParams(window.location.search).get(
579
- "country"
580
- );
581
- return countryCode ? `&country=${encodeURIComponent(countryCode)}` : "";
1258
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1259
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1260
+ function isAllowedRedirectUrl(url) {
1261
+ if (url.startsWith("/")) return !url.startsWith("//");
1262
+ try {
1263
+ const { hostname } = new URL(url);
1264
+ return hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1265
+ } catch {
1266
+ return false;
1267
+ }
1268
+ }
1269
+ var writeCheckoutRedirect = (originUrl) => {
1270
+ if (!isAllowedRedirectUrl(originUrl)) return;
1271
+ try {
1272
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1273
+ } catch {
1274
+ }
582
1275
  };
1276
+ var captureOriginUrl = () => window.location.href;
583
1277
  var decodeSku = (sku) => {
584
1278
  try {
585
1279
  return atob(sku.substring(sku.indexOf("-") + 1));
@@ -590,75 +1284,25 @@ var decodeSku = (sku) => {
590
1284
  var redirectToCheckoutComplete = (params) => {
591
1285
  const url = new URL("/checkout/complete", window.location.origin);
592
1286
  url.searchParams.set("basketId", params.basketId);
593
- if (params.queryString) {
594
- const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
595
- new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
596
- }
597
1287
  url.searchParams.set("lmo_offer", params.skuId);
598
1288
  url.searchParams.set("status", params.status);
599
1289
  url.searchParams.set("redirectToCheckout", "true");
600
- url.searchParams.set("returnUrl", params.returnUrl);
1290
+ if (params.email) url.searchParams.set("email", params.email);
1291
+ if (params.country) url.searchParams.set("country", params.country);
601
1292
  return url.pathname + url.search;
602
1293
  };
603
1294
  var redirectToCheckoutFallback = (params) => {
604
1295
  const url = new URL(window.location.href);
605
1296
  url.pathname = "/checkout";
606
- url.searchParams.set("lmo_offer", params.skuId);
1297
+ url.searchParams.set("lmo_offer", decodeSku(params.skuId));
607
1298
  if (params.inlineError)
608
1299
  url.searchParams.set("inlineError", params.inlineError);
609
1300
  if (params.email) url.searchParams.set("email", params.email);
610
1301
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
611
- url.searchParams.set("returnUrl", params.returnUrl);
1302
+ if (params.country) url.searchParams.set("country", params.country);
612
1303
  return url.pathname + url.search;
613
1304
  };
614
1305
 
615
- // src/utils/recaptcha.ts
616
- var loadRecaptchaScript = (siteKey) => {
617
- return new Promise((resolve, reject) => {
618
- if (typeof grecaptcha !== "undefined") return resolve();
619
- const existingScript = document.querySelector(
620
- 'script[src*="recaptcha/api.js"]'
621
- );
622
- if (existingScript) {
623
- existingScript.addEventListener("load", () => resolve());
624
- existingScript.addEventListener(
625
- "error",
626
- () => reject(new Error("Failed to load existing reCAPTCHA script"))
627
- );
628
- return;
629
- }
630
- const script = document.createElement("script");
631
- script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
632
- script.async = true;
633
- script.defer = true;
634
- script.onload = () => resolve();
635
- script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
636
- document.head.appendChild(script);
637
- });
638
- };
639
- var getRecaptchaToken = async (action) => {
640
- const siteKey = getApplePayConfig()?.recaptchaSiteKey;
641
- if (!siteKey) {
642
- throw new Error(
643
- "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
644
- );
645
- }
646
- await loadRecaptchaScript(siteKey);
647
- return new Promise((resolve, reject) => {
648
- grecaptcha.ready(() => {
649
- grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
650
- });
651
- });
652
- };
653
- var getPurchaseRecaptchaTokens = async () => {
654
- const [checkEligibility, newBasket, registerUser] = await Promise.all([
655
- getRecaptchaToken("checkEligibility"),
656
- getRecaptchaToken("newBasket"),
657
- getRecaptchaToken("registerUser")
658
- ]);
659
- return { checkEligibility, newBasket, registerUser };
660
- };
661
-
662
1306
  // src/utils/expressCheckoutTracking.ts
663
1307
  var EXPRESS_CHECKOUT_EVENTS = {
664
1308
  /** Wallet button displayed */
@@ -738,12 +1382,54 @@ var MSG_MISSING_REQUIRED_FIELD = "Missing required field";
738
1382
  var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a valid email address.";
739
1383
  var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
740
1384
  var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
1385
+ var BILLING_COUNTRY_MISMATCH_FALLBACK = "Country not supported for this purchase";
1386
+ var buildCountryMismatchMessage = (countryCode) => {
1387
+ if (!countryCode) return BILLING_COUNTRY_MISMATCH_FALLBACK;
1388
+ try {
1389
+ const regionNames = new Intl.DisplayNames(["en"], { type: "region" });
1390
+ const countryName = regionNames.of(countryCode.toUpperCase());
1391
+ return countryName ? `${countryName} address required` : BILLING_COUNTRY_MISMATCH_FALLBACK;
1392
+ } catch {
1393
+ return BILLING_COUNTRY_MISMATCH_FALLBACK;
1394
+ }
1395
+ };
741
1396
  var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
742
- var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
743
- var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
744
1397
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
745
1398
 
746
1399
  // src/clients/payment-checkout/apple-session.ts
1400
+ function redirectToFallbackCheckout(config) {
1401
+ writeCheckoutRedirect(config.returnUrl);
1402
+ trackFallbackCheckout(config.skuId);
1403
+ window.location.href = redirectToCheckoutFallback({
1404
+ skuId: config.skuId,
1405
+ inlineError: MSG_PAYMENT_GENERIC_ERROR,
1406
+ country: config.countryCode
1407
+ });
1408
+ }
1409
+ async function handleMerchantValidation(event, session, config) {
1410
+ try {
1411
+ const validationHost = new URL(event.validationURL).host;
1412
+ if (!validationHost.endsWith("apple.com")) {
1413
+ session.abort();
1414
+ redirectToFallbackCheckout(config);
1415
+ return;
1416
+ }
1417
+ const recaptchaToken = await getRecaptchaToken("verifyMerchant");
1418
+ const response = await validateAppleMerchant({
1419
+ validationURL: event.validationURL,
1420
+ recaptchaToken
1421
+ });
1422
+ if (!response?.merchantSession) {
1423
+ session.abort();
1424
+ redirectToFallbackCheckout(config);
1425
+ return;
1426
+ }
1427
+ session.completeMerchantValidation(response.merchantSession);
1428
+ } catch {
1429
+ session.abort();
1430
+ redirectToFallbackCheckout(config);
1431
+ }
1432
+ }
747
1433
  function startApplePaySession(config) {
748
1434
  const paymentRequest = {
749
1435
  countryCode: config.countryCode,
@@ -758,55 +1444,26 @@ function startApplePaySession(config) {
758
1444
  // Only request email from Apple Pay sheet if user is NOT logged in
759
1445
  ...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
760
1446
  };
761
- const session = new ApplePaySession(3, paymentRequest);
762
- session.onvalidatemerchant = async (event) => {
763
- try {
764
- const validationHost = new URL(event.validationURL).host;
765
- if (!validationHost.endsWith("apple.com")) {
766
- trackFallbackCheckout(config.skuId);
767
- session.abort();
768
- window.location.href = redirectToCheckoutFallback({
769
- skuId: config.skuId,
770
- returnUrl: config.returnUrl,
771
- inlineError: MSG_PAYMENT_GENERIC_ERROR
772
- });
773
- return;
774
- }
775
- const recaptchaToken = await getRecaptchaToken("verifyMerchant");
776
- const response = await validateAppleMerchant({
777
- validationURL: event.validationURL,
778
- recaptchaToken
779
- });
780
- if (!response?.merchantSession) {
781
- trackFallbackCheckout(config.skuId);
782
- session.abort();
783
- window.location.href = redirectToCheckoutFallback({
784
- skuId: config.skuId,
785
- returnUrl: config.returnUrl,
786
- inlineError: MSG_PAYMENT_GENERIC_ERROR
787
- });
788
- return;
789
- }
790
- session.completeMerchantValidation(response.merchantSession);
791
- } catch {
792
- trackFallbackCheckout(config.skuId);
793
- session.abort();
794
- window.location.href = redirectToCheckoutFallback({
795
- skuId: config.skuId,
796
- returnUrl: config.returnUrl,
797
- inlineError: MSG_PAYMENT_GENERIC_ERROR
1447
+ try {
1448
+ const session = new ApplePaySession(3, paymentRequest);
1449
+ session.onvalidatemerchant = async (event) => {
1450
+ await handleMerchantValidation(event, session, config);
1451
+ };
1452
+ session.onpaymentauthorized = (event) => {
1453
+ trackWalletPaymentAuthorised(config.skuId);
1454
+ config.onPaymentAuthorized(event, session).catch(() => {
1455
+ session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
1456
+ redirectToFallbackCheckout(config);
798
1457
  });
799
- }
800
- };
801
- session.onpaymentauthorized = (event) => {
802
- trackWalletPaymentAuthorised(config.skuId);
803
- config.onPaymentAuthorized(event, session);
804
- };
805
- session.oncancel = () => {
806
- config.onCancel?.();
807
- };
808
- trackWalletPaymentSheetOpen(config.skuId);
809
- session.begin();
1458
+ };
1459
+ session.oncancel = () => {
1460
+ config.onCancel?.();
1461
+ };
1462
+ trackWalletPaymentSheetOpen(config.skuId);
1463
+ session.begin();
1464
+ } catch {
1465
+ redirectToFallbackCheckout(config);
1466
+ }
810
1467
  }
811
1468
 
812
1469
  // src/clients/payment-checkout/contact-validation.ts
@@ -926,8 +1583,6 @@ var shippingContactDetails = (shippingContact) => {
926
1583
  };
927
1584
 
928
1585
  // src/clients/payment-checkout/payment-handler.ts
929
- var MSG_ELIGIBILITY = "Something went wrong, please try again";
930
- var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
931
1586
  var failApplePaySession = (session, errors = []) => {
932
1587
  session.completePayment({
933
1588
  status: ApplePaySession.STATUS_FAILURE,
@@ -951,37 +1606,42 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
951
1606
  }
952
1607
  return void 0;
953
1608
  }
954
- function redirectFallback(skuId, returnUrl, options) {
1609
+ function redirectFallback(skuId, returnUrl, country, options) {
1610
+ writeCheckoutRedirect(returnUrl);
955
1611
  trackFallbackCheckout(skuId);
956
1612
  window.location.href = redirectToCheckoutFallback({
957
1613
  skuId,
958
- returnUrl,
1614
+ country,
959
1615
  ...options
960
1616
  });
961
1617
  }
962
1618
  var ERROR_HANDLERS = {
963
- EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
964
- emailError: MSG_EMAIL_COLLISION,
1619
+ // emailError flows as an inline field error on the email input.
1620
+ // email pre-populates the input so the user doesn't have to retype it.
1621
+ EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1622
+ emailError: walletError.message,
1623
+ email: validatedEmail
1624
+ }),
1625
+ PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1626
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1627
+ REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1628
+ ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1629
+ emailError: walletError.message,
965
1630
  email: validatedEmail
966
1631
  }),
967
- PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
968
- // Matches sma-checkout catch-all: always redirect with generic inlineError.
969
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
970
- ),
971
- ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
972
- REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
973
- VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1632
+ VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1633
+ RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
974
1634
  };
975
1635
  function handleStructuredError(ctx) {
976
1636
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
977
1637
  if (handler) {
978
1638
  handler(ctx);
979
1639
  } else {
980
- redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1640
+ redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
981
1641
  }
982
1642
  }
983
1643
  async function handlePaymentAuthorized(event, session, sessionData, config) {
984
- const { skuId, returnUrl, queryString } = config;
1644
+ const { skuId, returnUrl, country } = config;
985
1645
  let validatedEmail;
986
1646
  try {
987
1647
  validatedEmail = validateContacts(
@@ -989,7 +1649,6 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
989
1649
  sessionData,
990
1650
  config.supportedBillingCountries
991
1651
  );
992
- console.log("[web-apple-pay] performPurchase currency", { skuId, currency: config.currency });
993
1652
  const result = await performPurchase({
994
1653
  applePayToken: event.payment.token,
995
1654
  skuId: config.skuId,
@@ -998,32 +1657,45 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
998
1657
  ...event.payment.shippingContact && {
999
1658
  shippingContact: event.payment.shippingContact
1000
1659
  },
1001
- recaptchaTokens: config.recaptchaTokens
1660
+ recaptchaToken: config.recaptchaToken
1002
1661
  });
1003
1662
  trackWalletPaymentSuccess(skuId, result.basketId);
1004
1663
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1664
+ const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
1005
1665
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1666
+ writeCheckoutRedirect(returnUrl);
1006
1667
  window.location.href = redirectToCheckoutComplete({
1007
1668
  basketId: result.basketId,
1008
1669
  status: userStatus,
1009
1670
  skuId: decodeSku(skuId),
1010
- returnUrl,
1011
- queryString
1671
+ email: successEmail,
1672
+ country
1012
1673
  });
1013
1674
  } catch (err) {
1014
1675
  if (err instanceof ContactValidationError) {
1015
- failApplePaySession(session, err.errors);
1676
+ const isBillingCountryMismatch = err.errors.some(
1677
+ (e) => e.message === MSG_UNSUPPORTED_BILLING_COUNTRY
1678
+ );
1679
+ if (isBillingCountryMismatch) {
1680
+ const countryCode = event.payment.billingContact?.countryCode;
1681
+ const userMessage = buildCountryMismatchMessage(countryCode);
1682
+ failApplePaySession(session, [
1683
+ new ApplePayError("billingContactInvalid", "countryCode", userMessage)
1684
+ ]);
1685
+ redirectFallback(skuId, returnUrl, country, { inlineError: userMessage });
1686
+ } else {
1687
+ failApplePaySession(session, err.errors);
1688
+ }
1016
1689
  return;
1017
1690
  }
1018
- console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1019
1691
  const walletError = parseWalletApiError(err);
1020
1692
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1021
1693
  failApplePaySession(session);
1022
1694
  if (!walletError) {
1023
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1695
+ redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1024
1696
  return;
1025
1697
  }
1026
- handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1698
+ handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
1027
1699
  }
1028
1700
  }
1029
1701
 
@@ -1288,12 +1960,24 @@ var getSupportedBillingCountries = (offerCountry, offerVariant) => {
1288
1960
  return [...zoneCountries];
1289
1961
  };
1290
1962
 
1963
+ // src/constants.ts
1964
+ var ApplePayEntryPoint = /* @__PURE__ */ ((ApplePayEntryPoint3) => {
1965
+ ApplePayEntryPoint3["PAYWALL"] = "article_paywall";
1966
+ ApplePayEntryPoint3["REGWALL"] = "article_regwall";
1967
+ ApplePayEntryPoint3["DUAL_OFFER_BANNER"] = "dual_offer_banner";
1968
+ return ApplePayEntryPoint3;
1969
+ })(ApplePayEntryPoint || {});
1970
+ var APPLE_PAY_EXPRESS_EXPERIMENT_KEY = "apple-pay-express-feature-flag-placeholder";
1971
+ var APPLE_PAY_EXPRESS_VARIANTS = {
1972
+ CONTROL: "control",
1973
+ TREATMENT: "treatment"
1974
+ };
1975
+
1291
1976
  // src/apple-pay-button.ts
1292
1977
  var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
1293
1978
  var TOTAL_LABEL = "The Economist";
1294
1979
  var DEFAULT_CURRENCY = "USD";
1295
1980
  var DEFAULT_TOTAL = "0.00";
1296
- var ERROR_INITIATE = "Failed to initiate Apple Pay session:";
1297
1981
  var SEL_CONTAINER = ".apple-pay-container";
1298
1982
  var SEL_BUTTON = ".apple-pay-btn";
1299
1983
  var APPLE_PAY_EXCLUDED_VARIANTS = [
@@ -1306,19 +1990,48 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1306
1990
  define(_ApplePayButton.tag, _ApplePayButton);
1307
1991
  }
1308
1992
  /**
1309
- * Creates and configures an ApplePayButton if the feature flag is present.
1993
+ * Creates and configures an ApplePayButton when the Amplitude experiment is enabled.
1994
+ *
1995
+ * Reads the experiment variant from `window.experimentIntegration.getVariant` using
1996
+ * {@link APPLE_PAY_EXPRESS_EXPERIMENT_KEY}. The host page is responsible
1997
+ * for setting up `window.experimentIntegration` before calling this method.
1998
+ *
1999
+ * - `treatment` → button created and returned
2000
+ * - `control` / missing / anything else → returns null (button suppressed)
2001
+ *
2002
+ * **Transition period:** the legacy `FEATURE_APPLE_PAY_EXPRESS_CHECKOUT` URL param
2003
+ * gate is also evaluated as a fallback until all host apps have integrated
2004
+ * `window.experimentIntegration`. Once all consumers are migrated, the URL param
2005
+ * fallback will be removed.
2006
+ *
2007
+ * @param offer - The offer to display in the Apple Pay flow.
2008
+ * @param country - The user's country code.
2009
+ * @param entryPoint - Where the button is being rendered (e.g. paywall, banner).
1310
2010
  */
1311
2011
  static createIfEnabled(offer, country, entryPoint) {
1312
- if (!offer) return null;
1313
- if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant))
2012
+ if (!offer) {
1314
2013
  return null;
1315
- if (!new URLSearchParams(window.location.search).has(FEATURE_APPLE_PAY_EXPRESS_CHECKOUT))
2014
+ }
2015
+ if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant)) {
2016
+ return null;
2017
+ }
2018
+ const experimentValue = window.experimentIntegration?.getVariant?.(
2019
+ APPLE_PAY_EXPRESS_EXPERIMENT_KEY
2020
+ );
2021
+ const isTreatment = experimentValue === APPLE_PAY_EXPRESS_VARIANTS.TREATMENT;
2022
+ const hasUrlParamFlag = new URLSearchParams(window.location.search).has(
2023
+ FEATURE_APPLE_PAY_EXPRESS_CHECKOUT
2024
+ );
2025
+ if (!isTreatment && !hasUrlParamFlag) {
1316
2026
  return null;
2027
+ }
1317
2028
  _ApplePayButton.define();
1318
2029
  const button = document.createElement(_ApplePayButton.tag);
1319
2030
  button.offer = offer;
1320
2031
  button.country = country;
1321
- button.entryPoint = entryPoint;
2032
+ if (entryPoint) {
2033
+ button.entryPoint = entryPoint;
2034
+ }
1322
2035
  return button;
1323
2036
  }
1324
2037
  /**
@@ -1409,15 +2122,17 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1409
2122
  // ─── Render pipeline ────────────────────────────────────────────────────────
1410
2123
  async #run() {
1411
2124
  try {
2125
+ this.#renderTemplate();
1412
2126
  if (await this.#checkAvailability()) return;
1413
- if (await this.#checkSession()) return;
1414
2127
  if (this.#checkOffer()) return;
2128
+ if (await this.#checkSession()) return;
1415
2129
  this.#captureReturnUrl();
1416
2130
  this.#computeBillingCountries();
1417
2131
  if (await this.#fetchAndCacheOptions()) return;
1418
- this.#renderTemplate();
2132
+ this.#finishLoading();
1419
2133
  } catch {
1420
2134
  this.style.display = "none";
2135
+ this.querySelector(SEL_CONTAINER)?.remove();
1421
2136
  } finally {
1422
2137
  this.#rendering = false;
1423
2138
  }
@@ -1427,6 +2142,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1427
2142
  const available = await isApplePayAvailable();
1428
2143
  if (!available) {
1429
2144
  this.style.display = "none";
2145
+ this.querySelector(SEL_CONTAINER)?.remove();
1430
2146
  return true;
1431
2147
  }
1432
2148
  return false;
@@ -1439,6 +2155,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1439
2155
  this.#sessionData = await this.#fetchSession();
1440
2156
  if (this.#sessionData?.loggedIn && (this.#sessionData.userType === "active-subscription" || this.#sessionData.userType === "b2b-user")) {
1441
2157
  this.style.display = "none";
2158
+ this.querySelector(SEL_CONTAINER)?.remove();
1442
2159
  return true;
1443
2160
  }
1444
2161
  return false;
@@ -1447,6 +2164,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1447
2164
  #checkOffer() {
1448
2165
  if (!this.#offer) {
1449
2166
  this.style.display = "none";
2167
+ this.querySelector(SEL_CONTAINER)?.remove();
1450
2168
  return true;
1451
2169
  }
1452
2170
  return false;
@@ -1472,8 +2190,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1472
2190
  */
1473
2191
  async #fetchAndCacheOptions() {
1474
2192
  this.#paymentOptions = await this.#fetchPaymentOptions();
1475
- if (!this.#paymentOptions?.apple_pay) {
2193
+ if (!this.#paymentOptions?.options?.apple_pay) {
1476
2194
  this.style.display = "none";
2195
+ this.querySelector(SEL_CONTAINER)?.remove();
1477
2196
  return true;
1478
2197
  }
1479
2198
  return false;
@@ -1485,19 +2204,26 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1485
2204
  const template = document.createElement("template");
1486
2205
  template.innerHTML = apple_pay_button_template_default;
1487
2206
  this.appendChild(template.content.cloneNode(true));
1488
- this.#updateDisabledState();
1489
- trackExpressWalletImpression(this.#offer.sku_id, {
1490
- entryPoint: this.#entryPoint,
1491
- country: this.#country
1492
- });
1493
2207
  const button = this.querySelector(SEL_BUTTON);
2208
+ button.classList.add("wap-loading");
2209
+ button.setAttribute("disabled", "");
2210
+ button.setAttribute("aria-disabled", "true");
2211
+ button.setAttribute("aria-busy", "true");
1494
2212
  button.addEventListener("click", () => {
2213
+ if (button.classList.contains("wap-loading")) return;
1495
2214
  this.#openModal();
1496
2215
  });
1497
- this.addEventListener("apple-pay-confirmed", (event) => {
1498
- const { skuId, country } = event.detail;
1499
- this.#initiateApplePay(skuId, country);
1500
- });
2216
+ }
2217
+ #finishLoading() {
2218
+ const button = this.querySelector(SEL_BUTTON);
2219
+ if (button) {
2220
+ button.classList.remove("wap-loading");
2221
+ this.#updateDisabledState();
2222
+ trackExpressWalletImpression(this.#offer.sku_id, {
2223
+ entryPoint: this.#entryPoint,
2224
+ country: this.#country
2225
+ });
2226
+ }
1501
2227
  }
1502
2228
  // ─── Private helpers ────────────────────────────────────────────────────────
1503
2229
  #updateDisabledState() {
@@ -1506,13 +2232,19 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1506
2232
  if (this.#offer) {
1507
2233
  button.removeAttribute("disabled");
1508
2234
  button.removeAttribute("aria-disabled");
2235
+ button.removeAttribute("aria-busy");
1509
2236
  return;
1510
2237
  }
1511
2238
  button.setAttribute("disabled", "");
1512
2239
  button.setAttribute("aria-disabled", "true");
1513
2240
  }
1514
2241
  #openModal() {
1515
- if (!this.#offer || document.querySelector(ApplePayModal.tag)) return;
2242
+ if (!this.#offer) {
2243
+ return;
2244
+ }
2245
+ if (document.querySelector(ApplePayModal.tag)) {
2246
+ return;
2247
+ }
1516
2248
  trackExpressWalletClick(this.#offer.sku_id);
1517
2249
  ApplePayModal.define();
1518
2250
  const modal = document.createElement(ApplePayModal.tag);
@@ -1521,6 +2253,11 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1521
2253
  if (this.#sessionData?.loggedIn) {
1522
2254
  modal.dataset.loggedIn = "true";
1523
2255
  }
2256
+ const skuId = this.#offer.sku_id;
2257
+ const country = this.#country;
2258
+ modal.onConfirm = (recaptchaTokenPromise) => {
2259
+ this.#initiateApplePay(skuId, country, recaptchaTokenPromise);
2260
+ };
1524
2261
  modal.open();
1525
2262
  }
1526
2263
  async #fetchSession() {
@@ -1542,45 +2279,53 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1542
2279
  return null;
1543
2280
  }
1544
2281
  }
1545
- #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokens) {
2282
+ #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokenPromise) {
1546
2283
  const offer = this.#offer;
1547
2284
  const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
1548
2285
  return {
1549
2286
  countryCode: country,
1550
2287
  currencyCode: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1551
- supportedNetworks: paymentOptions.apple_pay.settings.supported_networks,
2288
+ supportedNetworks: paymentOptions.options.apple_pay.settings.supported_networks,
1552
2289
  totalAmount,
1553
2290
  totalLabel: TOTAL_LABEL,
1554
2291
  skuId,
1555
2292
  returnUrl: this.#returnUrl,
1556
2293
  userLoggedIn: sessionData.loggedIn,
1557
- onPaymentAuthorized: (event, session) => handlePaymentAuthorized(event, session, sessionData, {
1558
- skuId,
1559
- country,
1560
- currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1561
- returnUrl: this.#returnUrl,
1562
- queryString: buildQueryString(),
1563
- supportedBillingCountries: this.#supportedBillingCountries,
1564
- recaptchaTokens
1565
- })
2294
+ onPaymentAuthorized: async (event, session) => {
2295
+ const recaptchaToken = await recaptchaTokenPromise;
2296
+ return handlePaymentAuthorized(event, session, sessionData, {
2297
+ skuId,
2298
+ country,
2299
+ currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
2300
+ returnUrl: this.#returnUrl,
2301
+ supportedBillingCountries: this.#supportedBillingCountries,
2302
+ recaptchaToken
2303
+ });
2304
+ }
1566
2305
  };
1567
2306
  }
1568
- async #initiateApplePay(skuId, country) {
2307
+ #initiateApplePay(skuId, country, recaptchaTokenPromise) {
1569
2308
  try {
1570
2309
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1571
2310
  const paymentOptions = this.#paymentOptions;
1572
- const recaptchaTokens = await getPurchaseRecaptchaTokens();
2311
+ if (!paymentOptions) {
2312
+ return;
2313
+ }
1573
2314
  startApplePaySession(
1574
2315
  this.#buildSessionParams(
1575
2316
  skuId,
1576
2317
  country,
1577
2318
  sessionData,
1578
2319
  paymentOptions,
1579
- recaptchaTokens
2320
+ recaptchaTokenPromise
1580
2321
  )
1581
2322
  );
1582
- } catch (error) {
1583
- console.error(ERROR_INITIATE, error);
2323
+ } catch {
2324
+ redirectToFallbackCheckout({
2325
+ returnUrl: this.#returnUrl,
2326
+ skuId,
2327
+ countryCode: country
2328
+ });
1584
2329
  }
1585
2330
  }
1586
2331
  };
@@ -1593,6 +2338,7 @@ function defineApplePayElements() {
1593
2338
  export {
1594
2339
  APPLE_PAY_EXCLUDED_VARIANTS,
1595
2340
  ApplePayButton,
2341
+ ApplePayEntryPoint,
1596
2342
  ApplePayModal,
1597
2343
  configureApplePay,
1598
2344
  defineApplePayElements,