@economist/web-apple-pay 1.7.2 → 1.7.4

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.
@@ -1,12 +1,27 @@
1
1
  import type { Offer } from './types/offer.types';
2
+ import { getPurchaseRecaptchaTokens } from './utils/recaptcha';
3
+ /** Resolved reCAPTCHA token set, mirroring the recaptcha utility return type. */
4
+ type RecaptchaTokens = Awaited<ReturnType<typeof getPurchaseRecaptchaTokens>>;
2
5
  export declare class ApplePayModal extends HTMLElement {
3
6
  #private;
4
7
  static readonly tag = "teg-apple-pay-modal";
5
8
  static define(): void;
9
+ /**
10
+ * Synchronous callback invoked directly from the CTA click handler once the
11
+ * user confirms. The caller (ApplePayButton) must call startApplePaySession
12
+ * inside this callback to preserve the browser's user-gesture chain — Safari
13
+ * requires new ApplePaySession() to execute within the original click stack.
14
+ *
15
+ * reCAPTCHA tokens are guaranteed non-null here: the CTA remains disabled
16
+ * until the token pre-fetch resolves, so this callback is never reached
17
+ * without valid tokens.
18
+ */
19
+ onConfirm: ((recaptchaTokens: RecaptchaTokens) => void) | null;
6
20
  set offer(value: Offer | null);
7
21
  get country(): string;
8
22
  set country(value: string);
9
23
  connectedCallback(): void;
10
- open(): void;
24
+ open(recaptchaTokensPromise?: Promise<RecaptchaTokens> | null): void;
11
25
  close(): void;
12
26
  }
27
+ export {};
@@ -76,7 +76,509 @@ function ensureApplePayLogoSymbol() {
76
76
  }
77
77
 
78
78
  // src/templates/apple-pay-modal.template.html
79
- var apple_pay_modal_template_default = '<style>\n .apm-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 97;\n background: rgba(13, 13, 13, 0.7);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--wall-gutter, 2rem);\n box-sizing: border-box;\n }\n\n .apm {\n position: relative;\n background: #fff;\n border-radius: 0.5rem 0.5rem 0 0;\n border: 0.0625rem solid #d9d9d9;\n padding: var(--mb-spacing-lg, 1.5rem);\n width: 25rem;\n max-width: 25rem;\n box-sizing: border-box;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n box-shadow: 0 0.25rem 1rem 0 rgba(13, 13, 13, 0.15);\n }\n\n .apm__close {\n position: absolute;\n top: 1.5rem;\n right: 1.3125rem;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .apm__close:focus {\n outline: solid 0.125rem var(--focus-border-color, #121212);\n border-radius: 0.25rem;\n }\n\n .apm__offer-name {\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-title-3-size, 1.25rem);\n font-weight: 500;\n color: var(--mb-colour-greyscale-london-5, #0d0d0d);\n line-height: var(--mb-typestyle-title-3-leading, 1.75rem);\n font-style: normal;\n margin: 0 1.5rem 0.5rem 0;\n }\n\n .apm__due-today {\n color: #1a1a1a;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n font-style: normal;\n font-weight: 400;\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n margin: 0 0 0.75rem 0;\n }\n\n .apm__rule {\n border: none;\n border-top: 0.0625rem solid #d9d9d9;\n margin: 0 0 1rem 0;\n }\n\n .apm__terms {\n color: #1a1a1a;\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n font-style: normal;\n font-weight: 400;\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n margin: 0 0 1rem 0;\n }\n\n .apm__terms-bullets {\n list-style: disc outside;\n padding-left: 1.25rem;\n margin: 0 0 0.75rem 0;\n }\n\n .apm__terms-bullets li {\n margin-bottom: 0.375rem;\n }\n\n .apm__terms-legal {\n margin: 0;\n }\n\n .apm__passive-terms {\n font-family: var(--wall-type-system-sans);\n font-size: 0.9375rem;\n color: #0d0d0d;\n line-height: 1.4;\n margin: 0 0 8px 0;\n }\n\n .apm__passive-terms a {\n color: #1a1a1a;\n font-weight: 700;\n text-decoration: underline;\n }\n\n .apm__checkbox-label {\n display: flex;\n align-items: flex-start;\n gap: var(--mb-spacing-sm, 0.5rem);\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: var(--mb-typestyle-label-2-size, 0.9375rem);\n color: var(--mb-colour-greyscale-london-20, #1a1a1a);\n line-height: var(--mb-typestyle-label-2-leading, 1.25rem);\n cursor: pointer;\n margin: 0 0 1.5rem 0;\n }\n\n .apm__checkbox-label--error {\n margin-bottom: 0.25rem;\n }\n\n .apm__checkbox-wrapper {\n position: relative;\n flex-shrink: 0;\n width: 1.25rem;\n height: 1.25rem;\n margin-top: 0.0625rem;\n }\n\n .apm__checkbox-input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n cursor: pointer;\n margin: 0;\n z-index: 1;\n }\n\n .apm__checkbox-custom {\n position: absolute;\n top: 0;\n left: 0;\n width: 1.25rem;\n height: 1.25rem;\n pointer-events: none;\n }\n\n .apm__checkbox-custom svg {\n display: none;\n width: 1.25rem;\n height: 1.25rem;\n }\n\n .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-unchecked {\n display: block;\n }\n\n .apm__checkbox-input:checked ~ .apm__checkbox-custom .svg-checked {\n display: block;\n }\n\n .apm__checkbox-label--error\n .apm__checkbox-input:not(:checked)\n ~ .apm__checkbox-custom\n .svg-unchecked {\n display: none;\n }\n\n .apm__checkbox-label--error\n .apm__checkbox-input:not(:checked)\n ~ .apm__checkbox-custom\n .svg-error {\n display: block;\n }\n\n .apm__checkbox-input:focus-visible ~ .apm__checkbox-custom {\n outline: solid 0.125rem #c91d42;\n outline-offset: 0.125rem;\n border-radius: 0.25rem;\n }\n\n .apm__checkbox-label a {\n color: #1a1a1a;\n font-weight: 700;\n text-decoration: underline;\n }\n\n .apm__error {\n display: none;\n align-items: center;\n gap: 0.75rem;\n color: var(--mb-colour-greyscale-london-5, #0d0d0d);\n font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));\n font-size: 1.0625rem;\n font-style: normal;\n font-weight: 500;\n line-height: 1.5rem;\n margin-bottom: 1rem;\n }\n\n .apm__error--visible {\n display: flex;\n }\n\n .apm__cta {\n display: flex;\n width: 100%;\n height: 2.75rem;\n padding: 0.375rem 1rem;\n justify-content: center;\n align-items: center;\n gap: 0.375rem;\n border-radius: 0.25rem;\n background: #000;\n border: none;\n cursor: pointer;\n color: #fff;\n text-align: center;\n font-family: var(\n --mb-typeface-sans,\n var(--wall-type-system-sans, system-ui, sans-serif)\n );\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 500;\n line-height: normal;\n }\n\n .apm__cta svg {\n flex-shrink: 0;\n display: block;\n }\n\n @media (min-width: 22.5rem) and (max-width: 37.5rem) {\n .apm {\n width: 24.375rem;\n max-width: 24.375rem;\n }\n }\n\n @media (min-width: 37.5625rem) {\n .apm {\n border-radius: 0.5rem;\n }\n }\n\n @media (max-width: 22.4375rem) {\n .apm {\n width: 20rem;\n max-width: 20rem;\n }\n }\n\n @media (max-width: 26.1875rem) {\n .apm__cta {\n font-size: 1.125rem;\n }\n }\n</style>\n\n<div class="apm-overlay" id="apple-pay-modal-overlay">\n <div\n class="apm"\n role="dialog"\n aria-modal="true"\n aria-labelledby="apple-pay-modal-offer-name"\n id="apple-pay-modal"\n tabindex="-1"\n >\n <button class="apm__close" id="apple-pay-modal-close" aria-label="Close">\n <svg\n aria-hidden="true"\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n >\n <g clip-path="url(#clip0_15712_93054)">\n <rect width="24" height="24" fill="white" fill-opacity="0.01" />\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M0 0H24V24H0V0Z"\n fill="white"\n fill-opacity="0.01"\n />\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M18.75 6.60964L17.3904 5.25L12 10.6404L6.60964 5.25L5.25 6.60964L10.6404 12L5.25 17.3904L6.60964 18.75L12 13.3596L17.3904 18.75L18.75 17.3904L13.3596 12L18.75 6.60964Z"\n fill="#333333"\n />\n </g>\n <defs>\n <clipPath id="clip0_15712_93054">\n <rect width="24" height="24" fill="white" />\n </clipPath>\n </defs>\n </svg>\n </button>\n <p class="apm__offer-name" id="apple-pay-modal-offer-name"></p>\n <p class="apm__due-today" id="apple-pay-modal-due-today"></p>\n <hr class="apm__rule" />\n <div class="apm__terms" id="apple-pay-modal-terms"></div>\n <p class="apm__passive-terms" id="apple-pay-modal-passive-terms"></p>\n <label\n class="apm__checkbox-label"\n id="apple-pay-modal-checkbox-label"\n for="apple-pay-modal-checkbox"\n >\n <div class="apm__checkbox-wrapper">\n <input\n class="apm__checkbox-input"\n type="checkbox"\n id="apple-pay-modal-checkbox"\n />\n <span class="apm__checkbox-custom">\n <svg\n aria-hidden="true"\n class="svg-unchecked"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M6 2.5H18C19.933 2.5 21.5 4.067 21.5 6V18C21.5 19.933 19.933 21.5 18 21.5H6C4.067 21.5 2.5 19.933 2.5 18V6C2.5 4.067 4.067 2.5 6 2.5Z"\n fill="white"\n stroke="#595959"\n />\n </svg>\n <svg\n aria-hidden="true"\n class="svg-checked"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <g clip-path="url(#clip0_20014_9555)">\n <path\n d="M2 6C2 3.79086 3.79086 2 6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6Z"\n fill="#333333"\n />\n <path\n d="M10.2857 17L6 12.6814L7.20857 11.4635L10.2857 14.5557L16.7914 8L18 9.22649L10.2857 17Z"\n fill="white"\n />\n </g>\n <defs>\n <clipPath id="clip0_20014_9555">\n <path\n d="M2 6C2 3.79086 3.79086 2 6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6Z"\n fill="white"\n />\n </clipPath>\n </defs>\n </svg>\n <svg\n aria-hidden="true"\n class="svg-error"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M6 2.5H18C19.933 2.5 21.5 4.067 21.5 6V18C21.5 19.933 19.933 21.5 18 21.5H6C4.067 21.5 2.5 19.933 2.5 18V6C2.5 4.067 4.067 2.5 6 2.5Z"\n fill="white"\n stroke="#C91D42"\n stroke-width="2"\n />\n </svg>\n </span>\n </div>\n <span id="apple-pay-modal-checkbox-text"></span>\n </label>\n <div class="apm__error" id="apple-pay-modal-error" role="alert">\n <svg\n aria-hidden="true"\n width="20"\n height="20"\n viewBox="0 0 20 20"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n fill-rule="evenodd"\n clip-rule="evenodd"\n d="M10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2ZM0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10ZM11 13C11 13.5523 10.5523 14 10 14C9.44772 14 9 13.5523 9 13C9 12.4477 9.44772 12 10 12C10.5523 12 11 12.4477 11 13ZM10 6C10.5523 6 11 6.44772 11 7V10C11 10.5523 10.5523 11 10 11C9.44772 11 9 10.5523 9 10V7C9 6.44772 9.44772 6 10 6Z"\n fill="#C91D42"\n />\n </svg>\n Please accept the terms to continue\n </div>\n <button class="apm__cta" id="apple-pay-modal-cta">\n Continue with\n <svg width="51" height="22" aria-label="Apple Pay" role="img">\n <use href="#apple-pay-logo" />\n </svg>\n </button>\n </div>\n</div>\n\n';
79
+ var apple_pay_modal_template_default = `<style>
80
+ .apm-overlay {
81
+ position: fixed;
82
+ top: 0;
83
+ left: 0;
84
+ width: 100%;
85
+ height: 100%;
86
+ z-index: 97;
87
+ background: rgba(13, 13, 13, 0.7);
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ padding: var(--wall-gutter, 2rem);
92
+ box-sizing: border-box;
93
+ }
94
+
95
+ .apm {
96
+ position: relative;
97
+ background: #fff;
98
+ border-radius: 0.5rem 0.5rem 0 0;
99
+ border: 0.0625rem solid #d9d9d9;
100
+ padding: var(--mb-spacing-lg, 1.5rem);
101
+ width: 25rem;
102
+ max-width: 25rem;
103
+ box-sizing: border-box;
104
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
105
+ box-shadow: 0 0.25rem 1rem 0 rgba(13, 13, 13, 0.15);
106
+ }
107
+
108
+ .apm__close {
109
+ position: absolute;
110
+ top: 1.5rem;
111
+ right: 1.3125rem;
112
+ background: none;
113
+ border: none;
114
+ cursor: pointer;
115
+ padding: 0;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ }
120
+
121
+ .apm__close:focus {
122
+ outline: solid 0.125rem var(--focus-border-color, #121212);
123
+ border-radius: 0.25rem;
124
+ }
125
+
126
+ .apm__offer-name {
127
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
128
+ font-size: var(--mb-typestyle-title-3-size, 1.25rem);
129
+ font-weight: 500;
130
+ color: var(--mb-colour-greyscale-london-5, #0d0d0d);
131
+ line-height: var(--mb-typestyle-title-3-leading, 1.75rem);
132
+ font-style: normal;
133
+ margin: 0 1.5rem 0.5rem 0;
134
+ }
135
+
136
+ .apm__due-today {
137
+ color: #1a1a1a;
138
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
139
+ font-size: var(--mb-typestyle-label-2-size, 0.9375rem);
140
+ font-style: normal;
141
+ font-weight: 400;
142
+ line-height: var(--mb-typestyle-label-2-leading, 1.25rem);
143
+ margin: 0 0 0.75rem 0;
144
+ }
145
+
146
+ .apm__rule {
147
+ border: none;
148
+ border-top: 0.0625rem solid #d9d9d9;
149
+ margin: 0 0 1rem 0;
150
+ }
151
+
152
+ .apm__terms {
153
+ color: #1a1a1a;
154
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
155
+ font-size: var(--mb-typestyle-label-2-size, 0.9375rem);
156
+ font-style: normal;
157
+ font-weight: 400;
158
+ line-height: var(--mb-typestyle-label-2-leading, 1.25rem);
159
+ margin: 0 0 1rem 0;
160
+ }
161
+
162
+ .apm__terms-bullets {
163
+ list-style: disc outside;
164
+ padding-left: 1.25rem;
165
+ margin: 0 0 0.75rem 0;
166
+ }
167
+
168
+ .apm__terms-bullets li {
169
+ margin-bottom: 0.375rem;
170
+ }
171
+
172
+ .apm__terms-legal {
173
+ margin: 0;
174
+ }
175
+
176
+ .apm__passive-terms {
177
+ font-family: var(--wall-type-system-sans);
178
+ font-size: 0.9375rem;
179
+ color: #0d0d0d;
180
+ line-height: 1.4;
181
+ margin: 0 0 8px 0;
182
+ }
183
+
184
+ .apm__passive-terms a {
185
+ color: #1a1a1a;
186
+ font-weight: 700;
187
+ text-decoration: underline;
188
+ }
189
+
190
+ .apm__checkbox-label {
191
+ display: flex;
192
+ align-items: flex-start;
193
+ gap: var(--mb-spacing-sm, 0.5rem);
194
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
195
+ font-size: var(--mb-typestyle-label-2-size, 0.9375rem);
196
+ color: var(--mb-colour-greyscale-london-20, #1a1a1a);
197
+ line-height: var(--mb-typestyle-label-2-leading, 1.25rem);
198
+ cursor: pointer;
199
+ margin: 0 0 1.5rem 0;
200
+ }
201
+
202
+ .apm__checkbox-label--error {
203
+ margin-bottom: 0.25rem;
204
+ }
205
+
206
+ .apm__checkbox-wrapper {
207
+ position: relative;
208
+ flex-shrink: 0;
209
+ width: 1.25rem;
210
+ height: 1.25rem;
211
+ margin-top: 0.0625rem;
212
+ }
213
+
214
+ .apm__checkbox-input {
215
+ position: absolute;
216
+ opacity: 0;
217
+ width: 100%;
218
+ height: 100%;
219
+ cursor: pointer;
220
+ margin: 0;
221
+ z-index: 1;
222
+ }
223
+
224
+ .apm__checkbox-custom {
225
+ position: absolute;
226
+ top: 0;
227
+ left: 0;
228
+ width: 1.25rem;
229
+ height: 1.25rem;
230
+ pointer-events: none;
231
+ }
232
+
233
+ .apm__checkbox-custom svg {
234
+ display: none;
235
+ width: 1.25rem;
236
+ height: 1.25rem;
237
+ }
238
+
239
+ .apm__checkbox-input:not(:checked) ~ .apm__checkbox-custom .svg-unchecked {
240
+ display: block;
241
+ }
242
+
243
+ .apm__checkbox-input:checked ~ .apm__checkbox-custom .svg-checked {
244
+ display: block;
245
+ }
246
+
247
+ .apm__checkbox-label--error
248
+ .apm__checkbox-input:not(:checked)
249
+ ~ .apm__checkbox-custom
250
+ .svg-unchecked {
251
+ display: none;
252
+ }
253
+
254
+ .apm__checkbox-label--error
255
+ .apm__checkbox-input:not(:checked)
256
+ ~ .apm__checkbox-custom
257
+ .svg-error {
258
+ display: block;
259
+ }
260
+
261
+ .apm__checkbox-input:focus-visible ~ .apm__checkbox-custom {
262
+ outline: solid 0.125rem #c91d42;
263
+ outline-offset: 0.125rem;
264
+ border-radius: 0.25rem;
265
+ }
266
+
267
+ .apm__checkbox-label a {
268
+ color: #1a1a1a;
269
+ font-weight: 700;
270
+ text-decoration: underline;
271
+ }
272
+
273
+ .apm__error {
274
+ display: none;
275
+ align-items: center;
276
+ gap: 0.75rem;
277
+ color: var(--mb-colour-greyscale-london-5, #0d0d0d);
278
+ font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
279
+ font-size: 1.0625rem;
280
+ font-style: normal;
281
+ font-weight: 500;
282
+ line-height: 1.5rem;
283
+ margin-bottom: 1rem;
284
+ }
285
+
286
+ .apm__error--visible {
287
+ display: flex;
288
+ }
289
+
290
+ .apm__cta {
291
+ display: flex;
292
+ width: 100%;
293
+ height: 2.75rem;
294
+ padding: 0.375rem 1rem;
295
+ justify-content: center;
296
+ align-items: center;
297
+ gap: 0.375rem;
298
+ border-radius: 0.25rem;
299
+ background: #000;
300
+ border: none;
301
+ cursor: pointer;
302
+ color: #fff;
303
+ text-align: center;
304
+ font-family: var(
305
+ --mb-typeface-sans,
306
+ var(--wall-type-system-sans, system-ui, sans-serif)
307
+ );
308
+ font-size: 1.25rem;
309
+ font-style: normal;
310
+ font-weight: 500;
311
+ line-height: normal;
312
+ }
313
+
314
+ .apm__cta:disabled {
315
+ cursor: not-allowed;
316
+ }
317
+
318
+ .apm__cta[aria-busy="true"] {
319
+ position: relative;
320
+ cursor: not-allowed;
321
+ }
322
+
323
+ .apm__cta[aria-busy="true"] > * {
324
+ visibility: hidden;
325
+ }
326
+
327
+ .apm__cta[aria-busy="true"]::after {
328
+ content: '';
329
+ position: absolute;
330
+ left: 50%;
331
+ top: 50%;
332
+ margin-left: -0.625rem;
333
+ margin-top: -0.625rem;
334
+ width: 1.25rem;
335
+ height: 1.25rem;
336
+ border: 2px solid rgba(255, 255, 255, 0.2);
337
+ border-top-color: #fff;
338
+ border-radius: 50%;
339
+ animation: apm-cta-spinner 0.8s linear infinite;
340
+ }
341
+
342
+ @keyframes apm-cta-spinner {
343
+ to {
344
+ transform: rotate(360deg);
345
+ }
346
+ }
347
+
348
+ .apm__cta svg {
349
+ flex-shrink: 0;
350
+ display: block;
351
+ }
352
+
353
+ @media (min-width: 22.5rem) and (max-width: 37.5rem) {
354
+ .apm {
355
+ width: 24.375rem;
356
+ max-width: 24.375rem;
357
+ }
358
+ }
359
+
360
+ @media (min-width: 37.5625rem) {
361
+ .apm {
362
+ border-radius: 0.5rem;
363
+ }
364
+ }
365
+
366
+ @media (max-width: 22.4375rem) {
367
+ .apm {
368
+ width: 20rem;
369
+ max-width: 20rem;
370
+ }
371
+ }
372
+
373
+ @media (max-width: 26.1875rem) {
374
+ .apm__cta {
375
+ font-size: 1.125rem;
376
+ }
377
+ }
378
+ </style>
379
+
380
+ <div class="apm-overlay" id="apple-pay-modal-overlay">
381
+ <div
382
+ class="apm"
383
+ role="dialog"
384
+ aria-modal="true"
385
+ aria-labelledby="apple-pay-modal-offer-name"
386
+ id="apple-pay-modal"
387
+ tabindex="-1"
388
+ >
389
+ <button class="apm__close" id="apple-pay-modal-close" aria-label="Close">
390
+ <svg
391
+ aria-hidden="true"
392
+ xmlns="http://www.w3.org/2000/svg"
393
+ width="24"
394
+ height="24"
395
+ viewBox="0 0 24 24"
396
+ fill="none"
397
+ >
398
+ <g clip-path="url(#clip0_15712_93054)">
399
+ <rect width="24" height="24" fill="white" fill-opacity="0.01" />
400
+ <path
401
+ fill-rule="evenodd"
402
+ clip-rule="evenodd"
403
+ d="M0 0H24V24H0V0Z"
404
+ fill="white"
405
+ fill-opacity="0.01"
406
+ />
407
+ <path
408
+ fill-rule="evenodd"
409
+ clip-rule="evenodd"
410
+ d="M18.75 6.60964L17.3904 5.25L12 10.6404L6.60964 5.25L5.25 6.60964L10.6404 12L5.25 17.3904L6.60964 18.75L12 13.3596L17.3904 18.75L18.75 17.3904L13.3596 12L18.75 6.60964Z"
411
+ fill="#333333"
412
+ />
413
+ </g>
414
+ <defs>
415
+ <clipPath id="clip0_15712_93054">
416
+ <rect width="24" height="24" fill="white" />
417
+ </clipPath>
418
+ </defs>
419
+ </svg>
420
+ </button>
421
+ <p class="apm__offer-name" id="apple-pay-modal-offer-name"></p>
422
+ <p class="apm__due-today" id="apple-pay-modal-due-today"></p>
423
+ <hr class="apm__rule" />
424
+ <div class="apm__terms" id="apple-pay-modal-terms"></div>
425
+ <p class="apm__passive-terms" id="apple-pay-modal-passive-terms"></p>
426
+ <label
427
+ class="apm__checkbox-label"
428
+ id="apple-pay-modal-checkbox-label"
429
+ for="apple-pay-modal-checkbox"
430
+ >
431
+ <div class="apm__checkbox-wrapper">
432
+ <input
433
+ class="apm__checkbox-input"
434
+ type="checkbox"
435
+ id="apple-pay-modal-checkbox"
436
+ />
437
+ <span class="apm__checkbox-custom">
438
+ <svg
439
+ aria-hidden="true"
440
+ class="svg-unchecked"
441
+ width="24"
442
+ height="24"
443
+ viewBox="0 0 24 24"
444
+ fill="none"
445
+ xmlns="http://www.w3.org/2000/svg"
446
+ >
447
+ <path
448
+ d="M6 2.5H18C19.933 2.5 21.5 4.067 21.5 6V18C21.5 19.933 19.933 21.5 18 21.5H6C4.067 21.5 2.5 19.933 2.5 18V6C2.5 4.067 4.067 2.5 6 2.5Z"
449
+ fill="white"
450
+ stroke="#595959"
451
+ />
452
+ </svg>
453
+ <svg
454
+ aria-hidden="true"
455
+ class="svg-checked"
456
+ width="24"
457
+ height="24"
458
+ viewBox="0 0 24 24"
459
+ fill="none"
460
+ xmlns="http://www.w3.org/2000/svg"
461
+ >
462
+ <g clip-path="url(#clip0_20014_9555)">
463
+ <path
464
+ d="M2 6C2 3.79086 3.79086 2 6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6Z"
465
+ fill="#333333"
466
+ />
467
+ <path
468
+ d="M10.2857 17L6 12.6814L7.20857 11.4635L10.2857 14.5557L16.7914 8L18 9.22649L10.2857 17Z"
469
+ fill="white"
470
+ />
471
+ </g>
472
+ <defs>
473
+ <clipPath id="clip0_20014_9555">
474
+ <path
475
+ d="M2 6C2 3.79086 3.79086 2 6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6Z"
476
+ fill="white"
477
+ />
478
+ </clipPath>
479
+ </defs>
480
+ </svg>
481
+ <svg
482
+ aria-hidden="true"
483
+ class="svg-error"
484
+ width="24"
485
+ height="24"
486
+ viewBox="0 0 24 24"
487
+ fill="none"
488
+ xmlns="http://www.w3.org/2000/svg"
489
+ >
490
+ <path
491
+ d="M6 2.5H18C19.933 2.5 21.5 4.067 21.5 6V18C21.5 19.933 19.933 21.5 18 21.5H6C4.067 21.5 2.5 19.933 2.5 18V6C2.5 4.067 4.067 2.5 6 2.5Z"
492
+ fill="white"
493
+ stroke="#C91D42"
494
+ stroke-width="2"
495
+ />
496
+ </svg>
497
+ </span>
498
+ </div>
499
+ <span id="apple-pay-modal-checkbox-text"></span>
500
+ </label>
501
+ <div class="apm__error" id="apple-pay-modal-error" role="alert">
502
+ <svg
503
+ aria-hidden="true"
504
+ width="20"
505
+ height="20"
506
+ viewBox="0 0 20 20"
507
+ fill="none"
508
+ xmlns="http://www.w3.org/2000/svg"
509
+ >
510
+ <path
511
+ fill-rule="evenodd"
512
+ clip-rule="evenodd"
513
+ d="M10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2ZM0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10ZM11 13C11 13.5523 10.5523 14 10 14C9.44772 14 9 13.5523 9 13C9 12.4477 9.44772 12 10 12C10.5523 12 11 12.4477 11 13ZM10 6C10.5523 6 11 6.44772 11 7V10C11 10.5523 10.5523 11 10 11C9.44772 11 9 10.5523 9 10V7C9 6.44772 9.44772 6 10 6Z"
514
+ fill="#C91D42"
515
+ />
516
+ </svg>
517
+ Please accept the terms to continue
518
+ </div>
519
+ <button class="apm__cta" id="apple-pay-modal-cta" aria-label="Continue with Apple Pay">
520
+ <span class="apm__cta-content">
521
+ Continue with
522
+ <svg width="51" height="22" aria-hidden="true" role="img">
523
+ <use href="#apple-pay-logo" />
524
+ </svg>
525
+ </span>
526
+ </button>
527
+ </div>
528
+ </div>
529
+
530
+ `;
531
+
532
+ // src/clients/payment-checkout/config.ts
533
+ var runtimeConfig = null;
534
+ var getApplePayConfig = () => runtimeConfig;
535
+
536
+ // src/utils/recaptcha.ts
537
+ var loadRecaptchaScript = (siteKey) => {
538
+ return new Promise((resolve, reject) => {
539
+ if (typeof grecaptcha !== "undefined") return resolve();
540
+ const existingScript = document.querySelector(
541
+ 'script[src*="recaptcha/api.js"]'
542
+ );
543
+ if (existingScript) {
544
+ existingScript.addEventListener("load", () => resolve());
545
+ existingScript.addEventListener(
546
+ "error",
547
+ () => reject(new Error("Failed to load existing reCAPTCHA script"))
548
+ );
549
+ return;
550
+ }
551
+ const script = document.createElement("script");
552
+ script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
553
+ script.async = true;
554
+ script.defer = true;
555
+ script.onload = () => resolve();
556
+ script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
557
+ document.head.appendChild(script);
558
+ });
559
+ };
560
+ var getRecaptchaToken = async (action) => {
561
+ const siteKey = getApplePayConfig()?.recaptchaSiteKey;
562
+ if (!siteKey) {
563
+ throw new Error(
564
+ "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
565
+ );
566
+ }
567
+ await loadRecaptchaScript(siteKey);
568
+ return new Promise((resolve, reject) => {
569
+ grecaptcha.ready(() => {
570
+ grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
571
+ });
572
+ });
573
+ };
574
+ var getPurchaseRecaptchaTokens = async () => {
575
+ const [checkEligibility, newBasket, registerUser] = await Promise.all([
576
+ getRecaptchaToken("checkEligibility"),
577
+ getRecaptchaToken("newBasket"),
578
+ getRecaptchaToken("registerUser")
579
+ ]);
580
+ return { checkEligibility, newBasket, registerUser };
581
+ };
80
582
 
81
583
  // src/apple-pay-modal.ts
82
584
  function getRenewalDateText(unit, duration) {
@@ -128,6 +630,7 @@ function getOfferTypeText(offerType) {
128
630
  }
129
631
  var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
130
632
  var PRIVACY_LINK = '<a href="https://www.economistgroup.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>';
633
+ var MSG_RECAPTCHA_LOAD_ERROR = "Something went wrong. Please close and try again.";
131
634
  function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
132
635
  const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
133
636
  if (isNYT) {
@@ -297,6 +800,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
297
800
  #country = "";
298
801
  #triggerElement = null;
299
802
  #keyHandler = null;
803
+ #recaptchaTokens = null;
804
+ #recaptchaTokensPromise = null;
805
+ /**
806
+ * Synchronous callback invoked directly from the CTA click handler once the
807
+ * user confirms. The caller (ApplePayButton) must call startApplePaySession
808
+ * inside this callback to preserve the browser's user-gesture chain — Safari
809
+ * requires new ApplePaySession() to execute within the original click stack.
810
+ *
811
+ * reCAPTCHA tokens are guaranteed non-null here: the CTA remains disabled
812
+ * until the token pre-fetch resolves, so this callback is never reached
813
+ * without valid tokens.
814
+ */
815
+ onConfirm = null;
300
816
  set offer(value) {
301
817
  this.#offer = value;
302
818
  }
@@ -391,27 +907,57 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
391
907
  }
392
908
  });
393
909
  }
910
+ if (this.#recaptchaTokensPromise) {
911
+ if (cta) {
912
+ cta.setAttribute("disabled", "");
913
+ cta.setAttribute("aria-disabled", "true");
914
+ cta.setAttribute("aria-busy", "true");
915
+ }
916
+ this.#recaptchaTokensPromise.then((tokens) => {
917
+ this.#recaptchaTokens = tokens;
918
+ if (cta) {
919
+ cta.removeAttribute("disabled");
920
+ cta.removeAttribute("aria-disabled");
921
+ cta.removeAttribute("aria-busy");
922
+ }
923
+ }).catch(() => {
924
+ console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
925
+ if (cta) {
926
+ cta.removeAttribute("aria-busy");
927
+ }
928
+ const existingError = this.querySelector("#apple-pay-modal-error");
929
+ if (existingError) {
930
+ const icon = existingError.querySelector("svg");
931
+ existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
932
+ if (icon) existingError.prepend(icon);
933
+ existingError.classList.add("apm__error--visible");
934
+ } else {
935
+ const errorNotice = document.createElement("div");
936
+ errorNotice.setAttribute("role", "alert");
937
+ errorNotice.className = "apm__error apm__error--visible";
938
+ errorNotice.textContent = MSG_RECAPTCHA_LOAD_ERROR;
939
+ cta?.insertAdjacentElement("beforebegin", errorNotice);
940
+ }
941
+ });
942
+ }
394
943
  cta?.addEventListener("click", () => {
395
944
  if (isUS && checkbox && !checkbox.checked) {
396
945
  errorElement?.classList.add("apm__error--visible");
397
946
  checkboxLabel?.classList.add("apm__checkbox-label--error");
398
947
  return;
399
948
  }
400
- this.dispatchEvent(
401
- new CustomEvent("apple-pay-confirmed", {
402
- bubbles: true,
403
- composed: true,
404
- detail: { skuId: offer.sku_id, country }
405
- })
406
- );
407
- this.close();
949
+ if (this.#recaptchaTokens) {
950
+ this.onConfirm?.(this.#recaptchaTokens);
951
+ this.close();
952
+ }
408
953
  });
409
954
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
410
955
  "click",
411
956
  () => this.close()
412
957
  );
413
958
  }
414
- open() {
959
+ open(recaptchaTokensPromise) {
960
+ this.#recaptchaTokensPromise = recaptchaTokensPromise ?? getPurchaseRecaptchaTokens();
415
961
  this.#triggerElement = document.activeElement;
416
962
  document.body.appendChild(this);
417
963
  this.#keyHandler = (event) => {