@economist/web-apple-pay 0.1.3-beta.20 → 0.1.3-beta.22

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
@@ -516,11 +516,13 @@ var apple_pay_modal_template_default = `<style>
516
516
  </svg>
517
517
  Please accept the terms to continue
518
518
  </div>
519
- <button class="apm__cta" id="apple-pay-modal-cta">
520
- Continue with
521
- <svg width="51" height="22" aria-label="Apple Pay" role="img">
522
- <use href="#apple-pay-logo" />
523
- </svg>
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>
524
526
  </button>
525
527
  </div>
526
528
  </div>
@@ -633,6 +635,7 @@ function getOfferTypeText(offerType) {
633
635
  }
634
636
  var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
635
637
  var PRIVACY_LINK = '<a href="https://www.economistgroup.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>';
638
+ var MSG_RECAPTCHA_LOAD_ERROR = "Something went wrong. Please close and try again.";
636
639
  function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
637
640
  const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
638
641
  if (isNYT) {
@@ -924,6 +927,22 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
924
927
  }
925
928
  }).catch(() => {
926
929
  console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
930
+ if (cta) {
931
+ cta.removeAttribute("aria-busy");
932
+ }
933
+ const existingError = this.querySelector("#apple-pay-modal-error");
934
+ if (existingError) {
935
+ const icon = existingError.querySelector("svg");
936
+ existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
937
+ if (icon) existingError.prepend(icon);
938
+ existingError.classList.add("apm__error--visible");
939
+ } else {
940
+ const errorNotice = document.createElement("div");
941
+ errorNotice.setAttribute("role", "alert");
942
+ errorNotice.className = "apm__error apm__error--visible";
943
+ errorNotice.textContent = MSG_RECAPTCHA_LOAD_ERROR;
944
+ cta?.insertAdjacentElement("beforebegin", errorNotice);
945
+ }
927
946
  });
928
947
  }
929
948
  cta?.addEventListener("click", () => {
@@ -934,8 +953,8 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
934
953
  }
935
954
  if (this.#recaptchaTokens) {
936
955
  this.onConfirm?.(this.#recaptchaTokens);
956
+ this.close();
937
957
  }
938
- this.close();
939
958
  });
940
959
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
941
960
  "click",
@@ -1137,11 +1156,18 @@ var apiFetch = async (path, options = {}) => {
1137
1156
  });
1138
1157
  if (!response.ok) {
1139
1158
  const contentType = response.headers.get("content-type") || "";
1159
+ console.warn("[web-apple-pay] apiFetch: non-2xx response", {
1160
+ path,
1161
+ status: response.status,
1162
+ contentType
1163
+ });
1140
1164
  if (contentType.includes("application/json")) {
1141
1165
  const errorBody = await response.json();
1166
+ console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
1142
1167
  throw new ApiError(response.status, errorBody);
1143
1168
  }
1144
1169
  const errorText = await response.text();
1170
+ console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
1145
1171
  throw new Error(
1146
1172
  `API request failed: ${response.status} ${response.statusText} - ${errorText}`
1147
1173
  );
@@ -1179,6 +1205,22 @@ var getExpressCheckoutSession = () => {
1179
1205
  };
1180
1206
 
1181
1207
  // src/utils/expressCheckoutRedirect.ts
1208
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1209
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1210
+ var writeCheckoutRedirect = (originUrl) => {
1211
+ if (originUrl.startsWith("/")) {
1212
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1213
+ return;
1214
+ }
1215
+ try {
1216
+ const { hostname } = new URL(originUrl);
1217
+ const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1218
+ if (isAllowed) {
1219
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1220
+ }
1221
+ } catch {
1222
+ }
1223
+ };
1182
1224
  var captureOriginUrl = () => window.location.href;
1183
1225
  var buildQueryString = () => {
1184
1226
  const countryCode = new URLSearchParams(window.location.search).get(
@@ -1188,11 +1230,8 @@ var buildQueryString = () => {
1188
1230
  };
1189
1231
  var decodeSku = (sku) => {
1190
1232
  try {
1191
- const decoded = atob(sku.substring(sku.indexOf("-") + 1));
1192
- console.log("[web-apple-pay] decodeSku", { input: sku, decoded });
1193
- return decoded;
1194
- } catch (err) {
1195
- console.warn("[web-apple-pay] decodeSku: failed to decode, returning raw", { input: sku, err });
1233
+ return atob(sku.substring(sku.indexOf("-") + 1));
1234
+ } catch {
1196
1235
  return sku;
1197
1236
  }
1198
1237
  };
@@ -1206,24 +1245,16 @@ var redirectToCheckoutComplete = (params) => {
1206
1245
  url.searchParams.set("lmo_offer", params.skuId);
1207
1246
  url.searchParams.set("status", params.status);
1208
1247
  url.searchParams.set("redirectToCheckout", "true");
1209
- url.searchParams.set("returnUrl", params.returnUrl);
1210
1248
  return url.pathname + url.search;
1211
1249
  };
1212
1250
  var redirectToCheckoutFallback = (params) => {
1213
- const decodedOffer = decodeSku(params.skuId);
1214
- console.log("[web-apple-pay] redirectToCheckoutFallback", {
1215
- rawSkuId: params.skuId,
1216
- decodedOffer
1217
- });
1218
1251
  const url = new URL(window.location.href);
1219
1252
  url.pathname = "/checkout";
1220
- url.searchParams.set("lmo_offer", decodedOffer);
1253
+ url.searchParams.set("lmo_offer", decodeSku(params.skuId));
1221
1254
  if (params.inlineError)
1222
1255
  url.searchParams.set("inlineError", params.inlineError);
1223
1256
  if (params.email) url.searchParams.set("email", params.email);
1224
1257
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
1225
- url.searchParams.set("returnUrl", params.returnUrl);
1226
- console.log("[web-apple-pay] fallback redirect URL", url.pathname + url.search);
1227
1258
  return url.pathname + url.search;
1228
1259
  };
1229
1260
 
@@ -1313,10 +1344,10 @@ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
1313
1344
 
1314
1345
  // src/clients/payment-checkout/apple-session.ts
1315
1346
  function redirectToFallbackCheckout(config) {
1347
+ writeCheckoutRedirect(config.returnUrl);
1316
1348
  trackFallbackCheckout(config.skuId);
1317
1349
  window.location.href = redirectToCheckoutFallback({
1318
1350
  skuId: config.skuId,
1319
- returnUrl: config.returnUrl,
1320
1351
  inlineError: MSG_PAYMENT_GENERIC_ERROR
1321
1352
  });
1322
1353
  }
@@ -1506,9 +1537,15 @@ var failApplePaySession = (session, errors = []) => {
1506
1537
  };
1507
1538
  function parseWalletApiError(err) {
1508
1539
  const body = err instanceof ApiError ? err.body : err;
1540
+ console.warn("[web-apple-pay] parseWalletApiError: raw error", {
1541
+ isApiError: err instanceof ApiError,
1542
+ body
1543
+ });
1509
1544
  if (body && typeof body === "object" && "errorType" in body && "message" in body) {
1545
+ console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
1510
1546
  return body;
1511
1547
  }
1548
+ console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
1512
1549
  return null;
1513
1550
  }
1514
1551
  function validateContacts(event, sessionData, supportedBillingCountries) {
@@ -1522,11 +1559,10 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
1522
1559
  return void 0;
1523
1560
  }
1524
1561
  function redirectFallback(skuId, returnUrl, options) {
1525
- console.log("[web-apple-pay] redirectFallback called", { skuId, returnUrl, options });
1562
+ writeCheckoutRedirect(returnUrl);
1526
1563
  trackFallbackCheckout(skuId);
1527
1564
  window.location.href = redirectToCheckoutFallback({
1528
1565
  skuId,
1529
- returnUrl,
1530
1566
  ...options
1531
1567
  });
1532
1568
  }
@@ -1541,7 +1577,12 @@ var ERROR_HANDLERS = {
1541
1577
  ),
1542
1578
  ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
1543
1579
  REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
1544
- VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1580
+ ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
1581
+ emailError: MSG_REGISTRATION,
1582
+ email: validatedEmail
1583
+ }),
1584
+ VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED }),
1585
+ RECAPTCHA_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1545
1586
  };
1546
1587
  function handleStructuredError(ctx) {
1547
1588
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
@@ -1573,11 +1614,11 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1573
1614
  trackWalletPaymentSuccess(skuId, result.basketId);
1574
1615
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1575
1616
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1617
+ writeCheckoutRedirect(returnUrl);
1576
1618
  window.location.href = redirectToCheckoutComplete({
1577
1619
  basketId: result.basketId,
1578
1620
  status: userStatus,
1579
1621
  skuId: decodeSku(skuId),
1580
- returnUrl,
1581
1622
  queryString
1582
1623
  });
1583
1624
  } catch (err) {
@@ -1587,6 +1628,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1587
1628
  }
1588
1629
  console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1589
1630
  const walletError = parseWalletApiError(err);
1631
+ console.warn("[web-apple-pay] handlePaymentAuthorized: walletError result", {
1632
+ walletError,
1633
+ validatedEmail
1634
+ });
1590
1635
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1591
1636
  failApplePaySession(session);
1592
1637
  if (!walletError) {
@@ -2081,7 +2126,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2081
2126
  if (button) {
2082
2127
  button.classList.remove("wap-loading");
2083
2128
  this.#updateDisabledState();
2084
- this.#recaptchaTokensPromise = getPurchaseRecaptchaTokens();
2129
+ this.#scheduleRecaptchaTokens();
2085
2130
  trackExpressWalletImpression(this.#offer.sku_id, {
2086
2131
  entryPoint: this.#entryPoint,
2087
2132
  country: this.#country
@@ -2120,10 +2165,27 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2120
2165
  const country = this.#country;
2121
2166
  modal.onConfirm = (recaptchaTokens) => {
2122
2167
  this.#initiateApplePay(skuId, country, recaptchaTokens);
2123
- this.#recaptchaTokensPromise = getPurchaseRecaptchaTokens();
2168
+ this.#scheduleRecaptchaTokens();
2124
2169
  };
2125
2170
  modal.open(this.#recaptchaTokensPromise);
2126
2171
  }
2172
+ /**
2173
+ * Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
2174
+ * If the fetch rejects, the field is cleared so the next `modal.open()` call
2175
+ * falls back to a new attempt via the modal's own null-fallback path — preventing
2176
+ * a single transient failure from permanently disabling the CTA on all retries.
2177
+ * The identity guard prevents a late rejection from clobbering a newer attempt.
2178
+ */
2179
+ #scheduleRecaptchaTokens() {
2180
+ const attempt = getPurchaseRecaptchaTokens();
2181
+ attempt.catch((err) => {
2182
+ console.error("[web-apple-pay] reCAPTCHA token pre-fetch failed; will retry on modal open", err);
2183
+ if (this.#recaptchaTokensPromise === attempt) {
2184
+ this.#recaptchaTokensPromise = null;
2185
+ }
2186
+ });
2187
+ this.#recaptchaTokensPromise = attempt;
2188
+ }
2127
2189
  async #fetchSession() {
2128
2190
  try {
2129
2191
  return await getExpressCheckoutSession();
@@ -516,11 +516,13 @@ var apple_pay_modal_template_default = `<style>
516
516
  </svg>
517
517
  Please accept the terms to continue
518
518
  </div>
519
- <button class="apm__cta" id="apple-pay-modal-cta">
520
- Continue with
521
- <svg width="51" height="22" aria-label="Apple Pay" role="img">
522
- <use href="#apple-pay-logo" />
523
- </svg>
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>
524
526
  </button>
525
527
  </div>
526
528
  </div>
@@ -628,6 +630,7 @@ function getOfferTypeText(offerType) {
628
630
  }
629
631
  var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
630
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.";
631
634
  function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
632
635
  const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
633
636
  if (isNYT) {
@@ -919,6 +922,22 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
919
922
  }
920
923
  }).catch(() => {
921
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
+ }
922
941
  });
923
942
  }
924
943
  cta?.addEventListener("click", () => {
@@ -929,8 +948,8 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
929
948
  }
930
949
  if (this.#recaptchaTokens) {
931
950
  this.onConfirm?.(this.#recaptchaTokens);
951
+ this.close();
932
952
  }
933
- this.close();
934
953
  });
935
954
  this.querySelector("#apple-pay-modal-close")?.addEventListener(
936
955
  "click",
@@ -1132,11 +1151,18 @@ var apiFetch = async (path, options = {}) => {
1132
1151
  });
1133
1152
  if (!response.ok) {
1134
1153
  const contentType = response.headers.get("content-type") || "";
1154
+ console.warn("[web-apple-pay] apiFetch: non-2xx response", {
1155
+ path,
1156
+ status: response.status,
1157
+ contentType
1158
+ });
1135
1159
  if (contentType.includes("application/json")) {
1136
1160
  const errorBody = await response.json();
1161
+ console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
1137
1162
  throw new ApiError(response.status, errorBody);
1138
1163
  }
1139
1164
  const errorText = await response.text();
1165
+ console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
1140
1166
  throw new Error(
1141
1167
  `API request failed: ${response.status} ${response.statusText} - ${errorText}`
1142
1168
  );
@@ -1174,6 +1200,22 @@ var getExpressCheckoutSession = () => {
1174
1200
  };
1175
1201
 
1176
1202
  // src/utils/expressCheckoutRedirect.ts
1203
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1204
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1205
+ var writeCheckoutRedirect = (originUrl) => {
1206
+ if (originUrl.startsWith("/")) {
1207
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1208
+ return;
1209
+ }
1210
+ try {
1211
+ const { hostname } = new URL(originUrl);
1212
+ const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1213
+ if (isAllowed) {
1214
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1215
+ }
1216
+ } catch {
1217
+ }
1218
+ };
1177
1219
  var captureOriginUrl = () => window.location.href;
1178
1220
  var buildQueryString = () => {
1179
1221
  const countryCode = new URLSearchParams(window.location.search).get(
@@ -1183,11 +1225,8 @@ var buildQueryString = () => {
1183
1225
  };
1184
1226
  var decodeSku = (sku) => {
1185
1227
  try {
1186
- const decoded = atob(sku.substring(sku.indexOf("-") + 1));
1187
- console.log("[web-apple-pay] decodeSku", { input: sku, decoded });
1188
- return decoded;
1189
- } catch (err) {
1190
- console.warn("[web-apple-pay] decodeSku: failed to decode, returning raw", { input: sku, err });
1228
+ return atob(sku.substring(sku.indexOf("-") + 1));
1229
+ } catch {
1191
1230
  return sku;
1192
1231
  }
1193
1232
  };
@@ -1201,24 +1240,16 @@ var redirectToCheckoutComplete = (params) => {
1201
1240
  url.searchParams.set("lmo_offer", params.skuId);
1202
1241
  url.searchParams.set("status", params.status);
1203
1242
  url.searchParams.set("redirectToCheckout", "true");
1204
- url.searchParams.set("returnUrl", params.returnUrl);
1205
1243
  return url.pathname + url.search;
1206
1244
  };
1207
1245
  var redirectToCheckoutFallback = (params) => {
1208
- const decodedOffer = decodeSku(params.skuId);
1209
- console.log("[web-apple-pay] redirectToCheckoutFallback", {
1210
- rawSkuId: params.skuId,
1211
- decodedOffer
1212
- });
1213
1246
  const url = new URL(window.location.href);
1214
1247
  url.pathname = "/checkout";
1215
- url.searchParams.set("lmo_offer", decodedOffer);
1248
+ url.searchParams.set("lmo_offer", decodeSku(params.skuId));
1216
1249
  if (params.inlineError)
1217
1250
  url.searchParams.set("inlineError", params.inlineError);
1218
1251
  if (params.email) url.searchParams.set("email", params.email);
1219
1252
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
1220
- url.searchParams.set("returnUrl", params.returnUrl);
1221
- console.log("[web-apple-pay] fallback redirect URL", url.pathname + url.search);
1222
1253
  return url.pathname + url.search;
1223
1254
  };
1224
1255
 
@@ -1308,10 +1339,10 @@ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
1308
1339
 
1309
1340
  // src/clients/payment-checkout/apple-session.ts
1310
1341
  function redirectToFallbackCheckout(config) {
1342
+ writeCheckoutRedirect(config.returnUrl);
1311
1343
  trackFallbackCheckout(config.skuId);
1312
1344
  window.location.href = redirectToCheckoutFallback({
1313
1345
  skuId: config.skuId,
1314
- returnUrl: config.returnUrl,
1315
1346
  inlineError: MSG_PAYMENT_GENERIC_ERROR
1316
1347
  });
1317
1348
  }
@@ -1501,9 +1532,15 @@ var failApplePaySession = (session, errors = []) => {
1501
1532
  };
1502
1533
  function parseWalletApiError(err) {
1503
1534
  const body = err instanceof ApiError ? err.body : err;
1535
+ console.warn("[web-apple-pay] parseWalletApiError: raw error", {
1536
+ isApiError: err instanceof ApiError,
1537
+ body
1538
+ });
1504
1539
  if (body && typeof body === "object" && "errorType" in body && "message" in body) {
1540
+ console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
1505
1541
  return body;
1506
1542
  }
1543
+ console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
1507
1544
  return null;
1508
1545
  }
1509
1546
  function validateContacts(event, sessionData, supportedBillingCountries) {
@@ -1517,11 +1554,10 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
1517
1554
  return void 0;
1518
1555
  }
1519
1556
  function redirectFallback(skuId, returnUrl, options) {
1520
- console.log("[web-apple-pay] redirectFallback called", { skuId, returnUrl, options });
1557
+ writeCheckoutRedirect(returnUrl);
1521
1558
  trackFallbackCheckout(skuId);
1522
1559
  window.location.href = redirectToCheckoutFallback({
1523
1560
  skuId,
1524
- returnUrl,
1525
1561
  ...options
1526
1562
  });
1527
1563
  }
@@ -1536,7 +1572,12 @@ var ERROR_HANDLERS = {
1536
1572
  ),
1537
1573
  ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
1538
1574
  REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
1539
- VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1575
+ ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
1576
+ emailError: MSG_REGISTRATION,
1577
+ email: validatedEmail
1578
+ }),
1579
+ VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED }),
1580
+ RECAPTCHA_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1540
1581
  };
1541
1582
  function handleStructuredError(ctx) {
1542
1583
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
@@ -1568,11 +1609,11 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1568
1609
  trackWalletPaymentSuccess(skuId, result.basketId);
1569
1610
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1570
1611
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1612
+ writeCheckoutRedirect(returnUrl);
1571
1613
  window.location.href = redirectToCheckoutComplete({
1572
1614
  basketId: result.basketId,
1573
1615
  status: userStatus,
1574
1616
  skuId: decodeSku(skuId),
1575
- returnUrl,
1576
1617
  queryString
1577
1618
  });
1578
1619
  } catch (err) {
@@ -1582,6 +1623,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1582
1623
  }
1583
1624
  console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1584
1625
  const walletError = parseWalletApiError(err);
1626
+ console.warn("[web-apple-pay] handlePaymentAuthorized: walletError result", {
1627
+ walletError,
1628
+ validatedEmail
1629
+ });
1585
1630
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1586
1631
  failApplePaySession(session);
1587
1632
  if (!walletError) {
@@ -2076,7 +2121,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2076
2121
  if (button) {
2077
2122
  button.classList.remove("wap-loading");
2078
2123
  this.#updateDisabledState();
2079
- this.#recaptchaTokensPromise = getPurchaseRecaptchaTokens();
2124
+ this.#scheduleRecaptchaTokens();
2080
2125
  trackExpressWalletImpression(this.#offer.sku_id, {
2081
2126
  entryPoint: this.#entryPoint,
2082
2127
  country: this.#country
@@ -2115,10 +2160,27 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2115
2160
  const country = this.#country;
2116
2161
  modal.onConfirm = (recaptchaTokens) => {
2117
2162
  this.#initiateApplePay(skuId, country, recaptchaTokens);
2118
- this.#recaptchaTokensPromise = getPurchaseRecaptchaTokens();
2163
+ this.#scheduleRecaptchaTokens();
2119
2164
  };
2120
2165
  modal.open(this.#recaptchaTokensPromise);
2121
2166
  }
2167
+ /**
2168
+ * Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
2169
+ * If the fetch rejects, the field is cleared so the next `modal.open()` call
2170
+ * falls back to a new attempt via the modal's own null-fallback path — preventing
2171
+ * a single transient failure from permanently disabling the CTA on all retries.
2172
+ * The identity guard prevents a late rejection from clobbering a newer attempt.
2173
+ */
2174
+ #scheduleRecaptchaTokens() {
2175
+ const attempt = getPurchaseRecaptchaTokens();
2176
+ attempt.catch((err) => {
2177
+ console.error("[web-apple-pay] reCAPTCHA token pre-fetch failed; will retry on modal open", err);
2178
+ if (this.#recaptchaTokensPromise === attempt) {
2179
+ this.#recaptchaTokensPromise = null;
2180
+ }
2181
+ });
2182
+ this.#recaptchaTokensPromise = attempt;
2183
+ }
2122
2184
  async #fetchSession() {
2123
2185
  try {
2124
2186
  return await getExpressCheckoutSession();