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

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,57 @@ 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 MSG_BILLING_COUNTRY_MISMATCH_REDIRECT = "This country isn\u2019t supported for this purchase. Try updating your billing address.";
1387
+ var buildCountryMismatchMessage = (countryCode) => {
1388
+ if (!countryCode) return BILLING_COUNTRY_MISMATCH_FALLBACK;
1389
+ try {
1390
+ const regionNames = new Intl.DisplayNames(["en"], { type: "region", fallback: "none" });
1391
+ const upper = countryCode.toUpperCase();
1392
+ const countryName = regionNames.of(upper);
1393
+ const isResolved = countryName && countryName !== upper && countryName !== "Unknown Region";
1394
+ return isResolved ? `${countryName} address required` : BILLING_COUNTRY_MISMATCH_FALLBACK;
1395
+ } catch {
1396
+ return BILLING_COUNTRY_MISMATCH_FALLBACK;
1397
+ }
1398
+ };
741
1399
  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
1400
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
745
1401
 
746
1402
  // src/clients/payment-checkout/apple-session.ts
1403
+ function redirectToFallbackCheckout(config) {
1404
+ writeCheckoutRedirect(config.returnUrl);
1405
+ trackFallbackCheckout(config.skuId);
1406
+ window.location.href = redirectToCheckoutFallback({
1407
+ skuId: config.skuId,
1408
+ inlineError: MSG_PAYMENT_GENERIC_ERROR,
1409
+ country: config.countryCode
1410
+ });
1411
+ }
1412
+ async function handleMerchantValidation(event, session, config) {
1413
+ try {
1414
+ const validationHost = new URL(event.validationURL).host;
1415
+ if (!validationHost.endsWith("apple.com")) {
1416
+ session.abort();
1417
+ redirectToFallbackCheckout(config);
1418
+ return;
1419
+ }
1420
+ const recaptchaToken = await getRecaptchaToken("verifyMerchant");
1421
+ const response = await validateAppleMerchant({
1422
+ validationURL: event.validationURL,
1423
+ recaptchaToken
1424
+ });
1425
+ if (!response?.merchantSession) {
1426
+ session.abort();
1427
+ redirectToFallbackCheckout(config);
1428
+ return;
1429
+ }
1430
+ session.completeMerchantValidation(response.merchantSession);
1431
+ } catch {
1432
+ session.abort();
1433
+ redirectToFallbackCheckout(config);
1434
+ }
1435
+ }
747
1436
  function startApplePaySession(config) {
748
1437
  const paymentRequest = {
749
1438
  countryCode: config.countryCode,
@@ -758,55 +1447,26 @@ function startApplePaySession(config) {
758
1447
  // Only request email from Apple Pay sheet if user is NOT logged in
759
1448
  ...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
760
1449
  };
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
1450
+ try {
1451
+ const session = new ApplePaySession(3, paymentRequest);
1452
+ session.onvalidatemerchant = async (event) => {
1453
+ await handleMerchantValidation(event, session, config);
1454
+ };
1455
+ session.onpaymentauthorized = (event) => {
1456
+ trackWalletPaymentAuthorised(config.skuId);
1457
+ config.onPaymentAuthorized(event, session).catch(() => {
1458
+ session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
1459
+ redirectToFallbackCheckout(config);
798
1460
  });
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();
1461
+ };
1462
+ session.oncancel = () => {
1463
+ config.onCancel?.();
1464
+ };
1465
+ trackWalletPaymentSheetOpen(config.skuId);
1466
+ session.begin();
1467
+ } catch {
1468
+ redirectToFallbackCheckout(config);
1469
+ }
810
1470
  }
811
1471
 
812
1472
  // src/clients/payment-checkout/contact-validation.ts
@@ -926,8 +1586,6 @@ var shippingContactDetails = (shippingContact) => {
926
1586
  };
927
1587
 
928
1588
  // 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
1589
  var failApplePaySession = (session, errors = []) => {
932
1590
  session.completePayment({
933
1591
  status: ApplePaySession.STATUS_FAILURE,
@@ -951,37 +1609,42 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
951
1609
  }
952
1610
  return void 0;
953
1611
  }
954
- function redirectFallback(skuId, returnUrl, options) {
1612
+ function redirectFallback(skuId, returnUrl, country, options) {
1613
+ writeCheckoutRedirect(returnUrl);
955
1614
  trackFallbackCheckout(skuId);
956
1615
  window.location.href = redirectToCheckoutFallback({
957
1616
  skuId,
958
- returnUrl,
1617
+ country,
959
1618
  ...options
960
1619
  });
961
1620
  }
962
1621
  var ERROR_HANDLERS = {
963
- EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
964
- emailError: MSG_EMAIL_COLLISION,
1622
+ // emailError flows as an inline field error on the email input.
1623
+ // email pre-populates the input so the user doesn't have to retype it.
1624
+ EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1625
+ emailError: walletError.message,
1626
+ email: validatedEmail
1627
+ }),
1628
+ PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1629
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1630
+ REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1631
+ ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1632
+ emailError: walletError.message,
965
1633
  email: validatedEmail
966
1634
  }),
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 })
1635
+ VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1636
+ RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
974
1637
  };
975
1638
  function handleStructuredError(ctx) {
976
1639
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
977
1640
  if (handler) {
978
1641
  handler(ctx);
979
1642
  } else {
980
- redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1643
+ redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
981
1644
  }
982
1645
  }
983
1646
  async function handlePaymentAuthorized(event, session, sessionData, config) {
984
- const { skuId, returnUrl, queryString } = config;
1647
+ const { skuId, returnUrl, country } = config;
985
1648
  let validatedEmail;
986
1649
  try {
987
1650
  validatedEmail = validateContacts(
@@ -989,7 +1652,6 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
989
1652
  sessionData,
990
1653
  config.supportedBillingCountries
991
1654
  );
992
- console.log("[web-apple-pay] performPurchase currency", { skuId, currency: config.currency });
993
1655
  const result = await performPurchase({
994
1656
  applePayToken: event.payment.token,
995
1657
  skuId: config.skuId,
@@ -998,32 +1660,45 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
998
1660
  ...event.payment.shippingContact && {
999
1661
  shippingContact: event.payment.shippingContact
1000
1662
  },
1001
- recaptchaTokens: config.recaptchaTokens
1663
+ recaptchaToken: config.recaptchaToken
1002
1664
  });
1003
1665
  trackWalletPaymentSuccess(skuId, result.basketId);
1004
1666
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1667
+ const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
1005
1668
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1669
+ writeCheckoutRedirect(returnUrl);
1006
1670
  window.location.href = redirectToCheckoutComplete({
1007
1671
  basketId: result.basketId,
1008
1672
  status: userStatus,
1009
1673
  skuId: decodeSku(skuId),
1010
- returnUrl,
1011
- queryString
1674
+ email: successEmail,
1675
+ country
1012
1676
  });
1013
1677
  } catch (err) {
1014
1678
  if (err instanceof ContactValidationError) {
1015
- failApplePaySession(session, err.errors);
1679
+ const isBillingCountryMismatch = err.errors.some(
1680
+ (e) => e.message === MSG_UNSUPPORTED_BILLING_COUNTRY
1681
+ );
1682
+ if (isBillingCountryMismatch) {
1683
+ const countryCode = event.payment.billingContact?.countryCode;
1684
+ const userMessage = buildCountryMismatchMessage(countryCode);
1685
+ failApplePaySession(session, [
1686
+ new ApplePayError("billingContactInvalid", "countryCode", userMessage)
1687
+ ]);
1688
+ redirectFallback(skuId, returnUrl, country, { inlineError: MSG_BILLING_COUNTRY_MISMATCH_REDIRECT });
1689
+ } else {
1690
+ failApplePaySession(session, err.errors);
1691
+ }
1016
1692
  return;
1017
1693
  }
1018
- console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1019
1694
  const walletError = parseWalletApiError(err);
1020
1695
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1021
1696
  failApplePaySession(session);
1022
1697
  if (!walletError) {
1023
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1698
+ redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1024
1699
  return;
1025
1700
  }
1026
- handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1701
+ handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
1027
1702
  }
1028
1703
  }
1029
1704
 
@@ -1288,12 +1963,24 @@ var getSupportedBillingCountries = (offerCountry, offerVariant) => {
1288
1963
  return [...zoneCountries];
1289
1964
  };
1290
1965
 
1966
+ // src/constants.ts
1967
+ var ApplePayEntryPoint = /* @__PURE__ */ ((ApplePayEntryPoint3) => {
1968
+ ApplePayEntryPoint3["PAYWALL"] = "article_paywall";
1969
+ ApplePayEntryPoint3["REGWALL"] = "article_regwall";
1970
+ ApplePayEntryPoint3["DUAL_OFFER_BANNER"] = "dual_offer_banner";
1971
+ return ApplePayEntryPoint3;
1972
+ })(ApplePayEntryPoint || {});
1973
+ var APPLE_PAY_EXPRESS_EXPERIMENT_KEY = "apple-pay-express-feature-flag-placeholder";
1974
+ var APPLE_PAY_EXPRESS_VARIANTS = {
1975
+ CONTROL: "control",
1976
+ TREATMENT: "treatment"
1977
+ };
1978
+
1291
1979
  // src/apple-pay-button.ts
1292
1980
  var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
1293
1981
  var TOTAL_LABEL = "The Economist";
1294
1982
  var DEFAULT_CURRENCY = "USD";
1295
1983
  var DEFAULT_TOTAL = "0.00";
1296
- var ERROR_INITIATE = "Failed to initiate Apple Pay session:";
1297
1984
  var SEL_CONTAINER = ".apple-pay-container";
1298
1985
  var SEL_BUTTON = ".apple-pay-btn";
1299
1986
  var APPLE_PAY_EXCLUDED_VARIANTS = [
@@ -1306,19 +1993,48 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1306
1993
  define(_ApplePayButton.tag, _ApplePayButton);
1307
1994
  }
1308
1995
  /**
1309
- * Creates and configures an ApplePayButton if the feature flag is present.
1996
+ * Creates and configures an ApplePayButton when the Amplitude experiment is enabled.
1997
+ *
1998
+ * Reads the experiment variant from `window.experimentIntegration.getVariant` using
1999
+ * {@link APPLE_PAY_EXPRESS_EXPERIMENT_KEY}. The host page is responsible
2000
+ * for setting up `window.experimentIntegration` before calling this method.
2001
+ *
2002
+ * - `treatment` → button created and returned
2003
+ * - `control` / missing / anything else → returns null (button suppressed)
2004
+ *
2005
+ * **Transition period:** the legacy `FEATURE_APPLE_PAY_EXPRESS_CHECKOUT` URL param
2006
+ * gate is also evaluated as a fallback until all host apps have integrated
2007
+ * `window.experimentIntegration`. Once all consumers are migrated, the URL param
2008
+ * fallback will be removed.
2009
+ *
2010
+ * @param offer - The offer to display in the Apple Pay flow.
2011
+ * @param country - The user's country code.
2012
+ * @param entryPoint - Where the button is being rendered (e.g. paywall, banner).
1310
2013
  */
1311
2014
  static createIfEnabled(offer, country, entryPoint) {
1312
- if (!offer) return null;
1313
- if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant))
2015
+ if (!offer) {
1314
2016
  return null;
1315
- if (!new URLSearchParams(window.location.search).has(FEATURE_APPLE_PAY_EXPRESS_CHECKOUT))
2017
+ }
2018
+ if (APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant)) {
2019
+ return null;
2020
+ }
2021
+ const experimentValue = window.experimentIntegration?.getVariant?.(
2022
+ APPLE_PAY_EXPRESS_EXPERIMENT_KEY
2023
+ );
2024
+ const isTreatment = experimentValue === APPLE_PAY_EXPRESS_VARIANTS.TREATMENT;
2025
+ const hasUrlParamFlag = new URLSearchParams(window.location.search).has(
2026
+ FEATURE_APPLE_PAY_EXPRESS_CHECKOUT
2027
+ );
2028
+ if (!isTreatment && !hasUrlParamFlag) {
1316
2029
  return null;
2030
+ }
1317
2031
  _ApplePayButton.define();
1318
2032
  const button = document.createElement(_ApplePayButton.tag);
1319
2033
  button.offer = offer;
1320
2034
  button.country = country;
1321
- button.entryPoint = entryPoint;
2035
+ if (entryPoint) {
2036
+ button.entryPoint = entryPoint;
2037
+ }
1322
2038
  return button;
1323
2039
  }
1324
2040
  /**
@@ -1409,15 +2125,17 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1409
2125
  // ─── Render pipeline ────────────────────────────────────────────────────────
1410
2126
  async #run() {
1411
2127
  try {
2128
+ this.#renderTemplate();
1412
2129
  if (await this.#checkAvailability()) return;
1413
- if (await this.#checkSession()) return;
1414
2130
  if (this.#checkOffer()) return;
2131
+ if (await this.#checkSession()) return;
1415
2132
  this.#captureReturnUrl();
1416
2133
  this.#computeBillingCountries();
1417
2134
  if (await this.#fetchAndCacheOptions()) return;
1418
- this.#renderTemplate();
2135
+ this.#finishLoading();
1419
2136
  } catch {
1420
2137
  this.style.display = "none";
2138
+ this.querySelector(SEL_CONTAINER)?.remove();
1421
2139
  } finally {
1422
2140
  this.#rendering = false;
1423
2141
  }
@@ -1427,6 +2145,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1427
2145
  const available = await isApplePayAvailable();
1428
2146
  if (!available) {
1429
2147
  this.style.display = "none";
2148
+ this.querySelector(SEL_CONTAINER)?.remove();
1430
2149
  return true;
1431
2150
  }
1432
2151
  return false;
@@ -1439,6 +2158,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1439
2158
  this.#sessionData = await this.#fetchSession();
1440
2159
  if (this.#sessionData?.loggedIn && (this.#sessionData.userType === "active-subscription" || this.#sessionData.userType === "b2b-user")) {
1441
2160
  this.style.display = "none";
2161
+ this.querySelector(SEL_CONTAINER)?.remove();
1442
2162
  return true;
1443
2163
  }
1444
2164
  return false;
@@ -1447,6 +2167,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1447
2167
  #checkOffer() {
1448
2168
  if (!this.#offer) {
1449
2169
  this.style.display = "none";
2170
+ this.querySelector(SEL_CONTAINER)?.remove();
1450
2171
  return true;
1451
2172
  }
1452
2173
  return false;
@@ -1472,8 +2193,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1472
2193
  */
1473
2194
  async #fetchAndCacheOptions() {
1474
2195
  this.#paymentOptions = await this.#fetchPaymentOptions();
1475
- if (!this.#paymentOptions?.apple_pay) {
2196
+ if (!this.#paymentOptions?.options?.apple_pay) {
1476
2197
  this.style.display = "none";
2198
+ this.querySelector(SEL_CONTAINER)?.remove();
1477
2199
  return true;
1478
2200
  }
1479
2201
  return false;
@@ -1485,19 +2207,26 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1485
2207
  const template = document.createElement("template");
1486
2208
  template.innerHTML = apple_pay_button_template_default;
1487
2209
  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
2210
  const button = this.querySelector(SEL_BUTTON);
2211
+ button.classList.add("wap-loading");
2212
+ button.setAttribute("disabled", "");
2213
+ button.setAttribute("aria-disabled", "true");
2214
+ button.setAttribute("aria-busy", "true");
1494
2215
  button.addEventListener("click", () => {
2216
+ if (button.classList.contains("wap-loading")) return;
1495
2217
  this.#openModal();
1496
2218
  });
1497
- this.addEventListener("apple-pay-confirmed", (event) => {
1498
- const { skuId, country } = event.detail;
1499
- this.#initiateApplePay(skuId, country);
1500
- });
2219
+ }
2220
+ #finishLoading() {
2221
+ const button = this.querySelector(SEL_BUTTON);
2222
+ if (button) {
2223
+ button.classList.remove("wap-loading");
2224
+ this.#updateDisabledState();
2225
+ trackExpressWalletImpression(this.#offer.sku_id, {
2226
+ entryPoint: this.#entryPoint,
2227
+ country: this.#country
2228
+ });
2229
+ }
1501
2230
  }
1502
2231
  // ─── Private helpers ────────────────────────────────────────────────────────
1503
2232
  #updateDisabledState() {
@@ -1506,13 +2235,19 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1506
2235
  if (this.#offer) {
1507
2236
  button.removeAttribute("disabled");
1508
2237
  button.removeAttribute("aria-disabled");
2238
+ button.removeAttribute("aria-busy");
1509
2239
  return;
1510
2240
  }
1511
2241
  button.setAttribute("disabled", "");
1512
2242
  button.setAttribute("aria-disabled", "true");
1513
2243
  }
1514
2244
  #openModal() {
1515
- if (!this.#offer || document.querySelector(ApplePayModal.tag)) return;
2245
+ if (!this.#offer) {
2246
+ return;
2247
+ }
2248
+ if (document.querySelector(ApplePayModal.tag)) {
2249
+ return;
2250
+ }
1516
2251
  trackExpressWalletClick(this.#offer.sku_id);
1517
2252
  ApplePayModal.define();
1518
2253
  const modal = document.createElement(ApplePayModal.tag);
@@ -1521,6 +2256,11 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1521
2256
  if (this.#sessionData?.loggedIn) {
1522
2257
  modal.dataset.loggedIn = "true";
1523
2258
  }
2259
+ const skuId = this.#offer.sku_id;
2260
+ const country = this.#country;
2261
+ modal.onConfirm = (recaptchaTokenPromise) => {
2262
+ this.#initiateApplePay(skuId, country, recaptchaTokenPromise);
2263
+ };
1524
2264
  modal.open();
1525
2265
  }
1526
2266
  async #fetchSession() {
@@ -1542,45 +2282,53 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
1542
2282
  return null;
1543
2283
  }
1544
2284
  }
1545
- #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokens) {
2285
+ #buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokenPromise) {
1546
2286
  const offer = this.#offer;
1547
2287
  const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
1548
2288
  return {
1549
2289
  countryCode: country,
1550
2290
  currencyCode: offer.price?.currency_code ?? DEFAULT_CURRENCY,
1551
- supportedNetworks: paymentOptions.apple_pay.settings.supported_networks,
2291
+ supportedNetworks: paymentOptions.options.apple_pay.settings.supported_networks,
1552
2292
  totalAmount,
1553
2293
  totalLabel: TOTAL_LABEL,
1554
2294
  skuId,
1555
2295
  returnUrl: this.#returnUrl,
1556
2296
  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
- })
2297
+ onPaymentAuthorized: async (event, session) => {
2298
+ const recaptchaToken = await recaptchaTokenPromise;
2299
+ return handlePaymentAuthorized(event, session, sessionData, {
2300
+ skuId,
2301
+ country,
2302
+ currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
2303
+ returnUrl: this.#returnUrl,
2304
+ supportedBillingCountries: this.#supportedBillingCountries,
2305
+ recaptchaToken
2306
+ });
2307
+ }
1566
2308
  };
1567
2309
  }
1568
- async #initiateApplePay(skuId, country) {
2310
+ #initiateApplePay(skuId, country, recaptchaTokenPromise) {
1569
2311
  try {
1570
2312
  const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
1571
2313
  const paymentOptions = this.#paymentOptions;
1572
- const recaptchaTokens = await getPurchaseRecaptchaTokens();
2314
+ if (!paymentOptions) {
2315
+ return;
2316
+ }
1573
2317
  startApplePaySession(
1574
2318
  this.#buildSessionParams(
1575
2319
  skuId,
1576
2320
  country,
1577
2321
  sessionData,
1578
2322
  paymentOptions,
1579
- recaptchaTokens
2323
+ recaptchaTokenPromise
1580
2324
  )
1581
2325
  );
1582
- } catch (error) {
1583
- console.error(ERROR_INITIATE, error);
2326
+ } catch {
2327
+ redirectToFallbackCheckout({
2328
+ returnUrl: this.#returnUrl,
2329
+ skuId,
2330
+ countryCode: country
2331
+ });
1584
2332
  }
1585
2333
  }
1586
2334
  };
@@ -1593,6 +2341,7 @@ function defineApplePayElements() {
1593
2341
  export {
1594
2342
  APPLE_PAY_EXCLUDED_VARIANTS,
1595
2343
  ApplePayButton,
2344
+ ApplePayEntryPoint,
1596
2345
  ApplePayModal,
1597
2346
  configureApplePay,
1598
2347
  defineApplePayElements,