@economist/web-apple-pay 1.7.6 → 1.7.7

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.
@@ -71,7 +71,7 @@ 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
 
@@ -109,16 +109,32 @@ var apple_pay_modal_template_default = `<style>
109
109
  position: absolute;
110
110
  top: 1.5rem;
111
111
  right: 1.3125rem;
112
- background: none;
112
+ background: transparent;
113
113
  border: none;
114
114
  cursor: pointer;
115
115
  padding: 0;
116
+ width: 1.5rem;
117
+ height: 1.5rem;
116
118
  display: flex;
117
119
  align-items: center;
118
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);
119
134
  }
120
135
 
121
136
  .apm__close:focus {
137
+ background-color: var(--mb-colour-base-chicago-90, #e6e6e6);
122
138
  outline: solid 0.125rem var(--focus-border-color, #121212);
123
139
  border-radius: 0.25rem;
124
140
  }
@@ -301,10 +317,6 @@ var apple_pay_modal_template_default = `<style>
301
317
  cursor: pointer;
302
318
  color: #fff;
303
319
  text-align: center;
304
- font-family: var(
305
- --mb-typeface-sans,
306
- var(--wall-type-system-sans, system-ui, sans-serif)
307
- );
308
320
  font-size: 1.25rem;
309
321
  font-style: normal;
310
322
  font-weight: 500;
@@ -421,7 +433,7 @@ var apple_pay_modal_template_default = `<style>
421
433
  fill-rule="evenodd"
422
434
  clip-rule="evenodd"
423
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"
424
- fill="#333333"
436
+ fill="currentColor"
425
437
  />
426
438
  </g>
427
439
  <defs>
@@ -679,6 +691,8 @@ function renewalPeriodText(unit, duration) {
679
691
  return "";
680
692
  }
681
693
  }
694
+ var LI_SEPARATOR = "\0LI_SEP\0";
695
+ var P_SEPARATOR = "\0P_SEP\0";
682
696
  function buildTermsContext(offer, country) {
683
697
  const isUS = country === "US";
684
698
  const isCA = country === "CA";
@@ -715,19 +729,19 @@ function freeTrialTerms(offer, ctx) {
715
729
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? 1;
716
730
  const trialDateStr = `on ${getRenewalDateText(trialUnit, trialDuration)}${ctx.verificationText}`;
717
731
  if (ctx.isUS) {
718
- 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}`;
732
+ 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}`;
719
733
  }
720
- 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}`;
734
+ 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}`;
721
735
  }
722
736
  function nytTerms(ctx) {
723
737
  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.";
724
738
  if (ctx.isUS) {
725
- 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}`;
739
+ 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}`;
726
740
  }
727
741
  if (ctx.isCA) {
728
- 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}`;
742
+ 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}`;
729
743
  }
730
- 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}`;
744
+ 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}`;
731
745
  }
732
746
  function minTermContractTerms(offer, ctx) {
733
747
  const duePrice = formatPrice(offer.price.purchase, offer.price.currency_code);
@@ -737,28 +751,28 @@ function minTermContractTerms(offer, ctx) {
737
751
  const contractLength = mtDuration && mtUnit ? `${mtDuration} ${mtUnit}` : "";
738
752
  const initialContractText = contractLength ? `your initial ${contractLength} contract` : "your initial contract";
739
753
  const mtRenewalDateText = mtUnit && mtDuration ? getRenewalDateText(mtUnit, Number(mtDuration)) : ctx.renewalDateText;
740
- 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}`;
754
+ 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}`;
741
755
  }
742
756
  function paidTrialTerms(offer, ctx) {
743
757
  const trialUnit = offer.subscription_options?.trial_period?.unit ?? ctx.unit;
744
758
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? ctx.duration;
745
759
  const trialDateText = getRenewalDateText(trialUnit, trialDuration);
746
760
  if (ctx.isUS) {
747
- 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}`;
761
+ 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}`;
748
762
  }
749
- 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}`;
763
+ 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}`;
750
764
  }
751
765
  function autoRenewalTerms(ctx) {
752
766
  if (ctx.isUS) {
753
- 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}`;
767
+ 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}`;
754
768
  }
755
- 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}`;
769
+ 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}`;
756
770
  }
757
771
  function noAutoRenewalTerms(ctx) {
758
772
  if (ctx.isUS || ctx.isCA) {
759
- return `Your subscription does not renew automatically${ctx.taxFootnote}{{liSeparator}}`;
773
+ return `Your subscription does not renew automatically${ctx.taxFootnote}${LI_SEPARATOR}`;
760
774
  }
761
- return `Your subscription does not renew automatically{{liSeparator}}`;
775
+ return `Your subscription does not renew automatically${LI_SEPARATOR}`;
762
776
  }
763
777
  function generateApplePayModalTerms(offer, country) {
764
778
  const ctx = buildTermsContext(offer, country);
@@ -781,9 +795,9 @@ function generateApplePayModalTerms(offer, country) {
781
795
  return noAutoRenewalTerms(ctx);
782
796
  }
783
797
  function renderTermsHtml(termsText) {
784
- const renewalTextLists = termsText.split("{{liSeparator}}");
798
+ const renewalTextLists = termsText.split(LI_SEPARATOR);
785
799
  const lastPart = renewalTextLists.pop() || "";
786
- const renewalTextParagraphs = lastPart.split("{{pSeparator}}");
800
+ const renewalTextParagraphs = lastPart.split(P_SEPARATOR);
787
801
  const ulHtml = `<ul class="apm__terms-bullets">${renewalTextLists.map((value) => `<li>${value}</li>`).join("")}</ul>`;
788
802
  const paragraphHtml = renewalTextParagraphs.filter(Boolean).map((value) => `<p class="apm__terms-legal">${value}</p>`).join("");
789
803
  return ulHtml + paragraphHtml;
@@ -1026,8 +1040,8 @@ var apple_pay_button_template_default = `<style>
1026
1040
  min-width: 0;
1027
1041
  height: 100%;
1028
1042
  min-height: var(--apple-pay-height, 2.75rem);
1029
- background-color: #000;
1030
- color: #fff;
1043
+ background-color: var(--apple-pay-background-color, #000);
1044
+ color: var(--apple-pay-foreground-color, #fff);
1031
1045
  border: none;
1032
1046
  border-radius: var(--apple-pay-border-radius, 1.375rem);
1033
1047
  cursor: pointer;
@@ -1039,6 +1053,7 @@ var apple_pay_button_template_default = `<style>
1039
1053
  teg-apple-pay-button .apple-pay-btn svg {
1040
1054
  flex-shrink: 0;
1041
1055
  display: block;
1056
+ color: currentColor;
1042
1057
  }
1043
1058
 
1044
1059
  teg-apple-pay-button .apple-pay-btn.wap-loading {
@@ -1053,8 +1068,8 @@ var apple_pay_button_template_default = `<style>
1053
1068
  left: 50%;
1054
1069
  width: 1.25rem;
1055
1070
  height: 1.25rem;
1056
- border: 2px solid rgba(255, 255, 255, 0.25);
1057
- border-top-color: #fff;
1071
+ border: 2px solid var(--apple-pay-spinner-track-color, rgba(255, 255, 255, 0.25));
1072
+ border-top-color: var(--apple-pay-spinner-head-color, currentColor);
1058
1073
  border-radius: 50%;
1059
1074
  animation: wap-button-spinner 0.8s linear infinite;
1060
1075
  }
@@ -1218,13 +1233,25 @@ var getExpressCheckoutSession = () => {
1218
1233
  };
1219
1234
 
1220
1235
  // src/utils/expressCheckoutRedirect.ts
1221
- var captureOriginUrl = () => window.location.href;
1222
- var buildQueryString = () => {
1223
- const countryCode = new URLSearchParams(window.location.search).get(
1224
- "country"
1225
- );
1226
- return countryCode ? `&country=${encodeURIComponent(countryCode)}` : "";
1236
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1237
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1238
+ function isAllowedRedirectUrl(url) {
1239
+ if (url.startsWith("/")) return !url.startsWith("//");
1240
+ try {
1241
+ const { hostname } = new URL(url);
1242
+ return hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1243
+ } catch {
1244
+ return false;
1245
+ }
1246
+ }
1247
+ var writeCheckoutRedirect = (originUrl) => {
1248
+ if (!isAllowedRedirectUrl(originUrl)) return;
1249
+ try {
1250
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1251
+ } catch {
1252
+ }
1227
1253
  };
1254
+ var captureOriginUrl = () => window.location.href;
1228
1255
  var decodeSku = (sku) => {
1229
1256
  try {
1230
1257
  return atob(sku.substring(sku.indexOf("-") + 1));
@@ -1235,14 +1262,11 @@ var decodeSku = (sku) => {
1235
1262
  var redirectToCheckoutComplete = (params) => {
1236
1263
  const url = new URL("/checkout/complete", window.location.origin);
1237
1264
  url.searchParams.set("basketId", params.basketId);
1238
- if (params.queryString) {
1239
- const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
1240
- new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
1241
- }
1242
1265
  url.searchParams.set("lmo_offer", params.skuId);
1243
1266
  url.searchParams.set("status", params.status);
1244
1267
  url.searchParams.set("redirectToCheckout", "true");
1245
- url.searchParams.set("returnUrl", params.returnUrl);
1268
+ if (params.email) url.searchParams.set("email", params.email);
1269
+ if (params.country) url.searchParams.set("country", params.country);
1246
1270
  return url.pathname + url.search;
1247
1271
  };
1248
1272
  var redirectToCheckoutFallback = (params) => {
@@ -1253,7 +1277,7 @@ var redirectToCheckoutFallback = (params) => {
1253
1277
  url.searchParams.set("inlineError", params.inlineError);
1254
1278
  if (params.email) url.searchParams.set("email", params.email);
1255
1279
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
1256
- url.searchParams.set("returnUrl", params.returnUrl);
1280
+ if (params.country) url.searchParams.set("country", params.country);
1257
1281
  return url.pathname + url.search;
1258
1282
  };
1259
1283
 
@@ -1337,17 +1361,16 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
1337
1361
  var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
1338
1362
  var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
1339
1363
  var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
1340
- var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
1341
- var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
1342
1364
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
1343
1365
 
1344
1366
  // src/clients/payment-checkout/apple-session.ts
1345
1367
  function redirectToFallbackCheckout(config) {
1368
+ writeCheckoutRedirect(config.returnUrl);
1346
1369
  trackFallbackCheckout(config.skuId);
1347
1370
  window.location.href = redirectToCheckoutFallback({
1348
1371
  skuId: config.skuId,
1349
- returnUrl: config.returnUrl,
1350
- inlineError: MSG_PAYMENT_GENERIC_ERROR
1372
+ inlineError: MSG_PAYMENT_GENERIC_ERROR,
1373
+ country: config.countryCode
1351
1374
  });
1352
1375
  }
1353
1376
  async function handleMerchantValidation(event, session, config) {
@@ -1526,8 +1549,6 @@ var shippingContactDetails = (shippingContact) => {
1526
1549
  };
1527
1550
 
1528
1551
  // src/clients/payment-checkout/payment-handler.ts
1529
- var MSG_ELIGIBILITY = "Something went wrong, please try again";
1530
- var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
1531
1552
  var failApplePaySession = (session, errors = []) => {
1532
1553
  session.completePayment({
1533
1554
  status: ApplePaySession.STATUS_FAILURE,
@@ -1551,37 +1572,42 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
1551
1572
  }
1552
1573
  return void 0;
1553
1574
  }
1554
- function redirectFallback(skuId, returnUrl, options) {
1575
+ function redirectFallback(skuId, returnUrl, country, options) {
1576
+ writeCheckoutRedirect(returnUrl);
1555
1577
  trackFallbackCheckout(skuId);
1556
1578
  window.location.href = redirectToCheckoutFallback({
1557
1579
  skuId,
1558
- returnUrl,
1580
+ country,
1559
1581
  ...options
1560
1582
  });
1561
1583
  }
1562
1584
  var ERROR_HANDLERS = {
1563
- EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
1564
- emailError: MSG_EMAIL_COLLISION,
1585
+ // emailError flows as an inline field error on the email input.
1586
+ // email pre-populates the input so the user doesn't have to retype it.
1587
+ EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1588
+ emailError: walletError.message,
1589
+ email: validatedEmail
1590
+ }),
1591
+ PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1592
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1593
+ REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1594
+ ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1595
+ emailError: walletError.message,
1565
1596
  email: validatedEmail
1566
1597
  }),
1567
- PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
1568
- // Matches sma-checkout catch-all: always redirect with generic inlineError.
1569
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
1570
- ),
1571
- ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
1572
- REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
1573
- VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1598
+ VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1599
+ RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
1574
1600
  };
1575
1601
  function handleStructuredError(ctx) {
1576
1602
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
1577
1603
  if (handler) {
1578
1604
  handler(ctx);
1579
1605
  } else {
1580
- redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1606
+ redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1581
1607
  }
1582
1608
  }
1583
1609
  async function handlePaymentAuthorized(event, session, sessionData, config) {
1584
- const { skuId, returnUrl, queryString } = config;
1610
+ const { skuId, returnUrl, country } = config;
1585
1611
  let validatedEmail;
1586
1612
  try {
1587
1613
  validatedEmail = validateContacts(
@@ -1601,13 +1627,15 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1601
1627
  });
1602
1628
  trackWalletPaymentSuccess(skuId, result.basketId);
1603
1629
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1630
+ const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
1604
1631
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1632
+ writeCheckoutRedirect(returnUrl);
1605
1633
  window.location.href = redirectToCheckoutComplete({
1606
1634
  basketId: result.basketId,
1607
1635
  status: userStatus,
1608
1636
  skuId: decodeSku(skuId),
1609
- returnUrl,
1610
- queryString
1637
+ email: successEmail,
1638
+ country
1611
1639
  });
1612
1640
  } catch (err) {
1613
1641
  if (err instanceof ContactValidationError) {
@@ -1619,10 +1647,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1619
1647
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1620
1648
  failApplePaySession(session);
1621
1649
  if (!walletError) {
1622
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1650
+ redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1623
1651
  return;
1624
1652
  }
1625
- handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1653
+ handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
1626
1654
  }
1627
1655
  }
1628
1656
 
@@ -2208,7 +2236,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2208
2236
  country,
2209
2237
  currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
2210
2238
  returnUrl: this.#returnUrl,
2211
- queryString: buildQueryString(),
2212
2239
  supportedBillingCountries: this.#supportedBillingCountries,
2213
2240
  recaptchaTokens
2214
2241
  })
@@ -62,6 +62,24 @@ var validateAppleMerchant = (data) => {
62
62
  };
63
63
 
64
64
  // src/utils/expressCheckoutRedirect.ts
65
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
66
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
67
+ function isAllowedRedirectUrl(url) {
68
+ if (url.startsWith("/")) return !url.startsWith("//");
69
+ try {
70
+ const { hostname } = new URL(url);
71
+ return hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ var writeCheckoutRedirect = (originUrl) => {
77
+ if (!isAllowedRedirectUrl(originUrl)) return;
78
+ try {
79
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
80
+ } catch {
81
+ }
82
+ };
65
83
  var decodeSku = (sku) => {
66
84
  try {
67
85
  return atob(sku.substring(sku.indexOf("-") + 1));
@@ -77,7 +95,7 @@ var redirectToCheckoutFallback = (params) => {
77
95
  url.searchParams.set("inlineError", params.inlineError);
78
96
  if (params.email) url.searchParams.set("email", params.email);
79
97
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
80
- url.searchParams.set("returnUrl", params.returnUrl);
98
+ if (params.country) url.searchParams.set("country", params.country);
81
99
  return url.pathname + url.search;
82
100
  };
83
101
 
@@ -166,11 +184,12 @@ var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment
166
184
 
167
185
  // src/clients/payment-checkout/apple-session.ts
168
186
  function redirectToFallbackCheckout(config) {
187
+ writeCheckoutRedirect(config.returnUrl);
169
188
  trackFallbackCheckout(config.skuId);
170
189
  window.location.href = redirectToCheckoutFallback({
171
190
  skuId: config.skuId,
172
- returnUrl: config.returnUrl,
173
- inlineError: MSG_PAYMENT_GENERIC_ERROR
191
+ inlineError: MSG_PAYMENT_GENERIC_ERROR,
192
+ country: config.countryCode
174
193
  });
175
194
  }
176
195
  async function handleMerchantValidation(event, session, config) {
@@ -1,9 +1,18 @@
1
1
  /**
2
- * Canonical user-facing error messages for the Apple Pay Express Checkout flow.
2
+ * Canonical user-facing strings for the Apple Pay Express Checkout flow.
3
3
  *
4
- * All copy here is verified against sma-checkout (src/actions/constants/index.ts
5
- * and handlers/create-on-payment-authorized-handler/) to ensure parity with the
6
- * reference implementation. Update both repos if copy changes.
4
+ * These constants cover two categories:
5
+ * 1. Local contact/field validation — fired before any BFF call, no API
6
+ * message is available.
7
+ * 2. Pre-purchase fallbacks — merchant validation failures, unstructured
8
+ * network errors, and generic fallback handling when a BFF WalletApiError
9
+ * is absent or present but not mapped to dedicated frontend copy.
10
+ *
11
+ * Structured BFF errors (EMAIL_COLLISION, PAYMENT_DECLINED, RECAPTCHA_FAILED,
12
+ * etc.) generally are NOT represented here — their user-facing copy comes
13
+ * directly from walletError.message in the API response, so copy changes on
14
+ * the BFF side take effect without a frontend deploy. Unhandled structured
15
+ * BFF errors may still fall back to MSG_PAYMENT_GENERIC_ERROR.
7
16
  */
8
17
  /** Shown when a required Apple Pay contact field is absent. */
9
18
  export declare const MSG_MISSING_REQUIRED_FIELD = "Missing required field";
@@ -19,15 +28,5 @@ export declare const MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not
19
28
  * Matches sma-checkout GENERIC_ERROR_MSG pattern.
20
29
  */
21
30
  export declare const MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
22
- /**
23
- * Shown when a reCAPTCHA security check fails.
24
- * Matches sma-checkout reference: handlers/create-on-payment-authorized-handler/subscribe.
25
- */
26
- export declare const MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
27
- /**
28
- * Shown when the BFF returns an EMAIL_COLLISION error (account already exists).
29
- * Matches sma-checkout L103-104.
30
- */
31
- export declare const MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
32
31
  /** Internal ContactValidationError message — not shown to the user directly. */
33
32
  export declare const MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
@@ -4,16 +4,12 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
4
4
  var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
5
5
  var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
6
6
  var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
7
- var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
8
- var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
9
7
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
10
8
  export {
11
9
  MSG_CONTACT_VALIDATION_INTERNAL,
12
- MSG_EMAIL_COLLISION,
13
10
  MSG_EMAIL_TOO_LONG,
14
11
  MSG_EMAIL_WRONG_FORMAT,
15
12
  MSG_MISSING_REQUIRED_FIELD,
16
13
  MSG_PAYMENT_GENERIC_ERROR,
17
- MSG_RECAPTCHA_VALIDATION_FAILED,
18
14
  MSG_UNSUPPORTED_BILLING_COUNTRY
19
15
  };
@@ -19,14 +19,6 @@ export type PaymentHandlerConfig = {
19
19
  country: string;
20
20
  currency: string;
21
21
  returnUrl: string;
22
- /**
23
- * Optional `&country=` query param captured from the page URL at the moment
24
- * the payment sheet was authorised (via `buildQueryString()`).
25
- * Appended verbatim to the checkout-complete and fallback redirect URLs so
26
- * the country context is preserved across the redirect.
27
- * e.g. `'&country=US'`, `'&country=GB%2FIE'`, or `''` when absent.
28
- */
29
- queryString?: string;
30
22
  supportedBillingCountries: string[];
31
23
  recaptchaTokens: {
32
24
  checkEligibility: string;
@@ -118,8 +118,6 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
118
118
  var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
119
119
  var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
120
120
  var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
121
- var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
122
- var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
123
121
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
124
122
 
125
123
  // src/clients/payment-checkout/contact-validation.ts
@@ -239,6 +237,24 @@ var shippingContactDetails = (shippingContact) => {
239
237
  };
240
238
 
241
239
  // src/utils/expressCheckoutRedirect.ts
240
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
241
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
242
+ function isAllowedRedirectUrl(url) {
243
+ if (url.startsWith("/")) return !url.startsWith("//");
244
+ try {
245
+ const { hostname } = new URL(url);
246
+ return hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
247
+ } catch {
248
+ return false;
249
+ }
250
+ }
251
+ var writeCheckoutRedirect = (originUrl) => {
252
+ if (!isAllowedRedirectUrl(originUrl)) return;
253
+ try {
254
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
255
+ } catch {
256
+ }
257
+ };
242
258
  var decodeSku = (sku) => {
243
259
  try {
244
260
  return atob(sku.substring(sku.indexOf("-") + 1));
@@ -249,14 +265,11 @@ var decodeSku = (sku) => {
249
265
  var redirectToCheckoutComplete = (params) => {
250
266
  const url = new URL("/checkout/complete", window.location.origin);
251
267
  url.searchParams.set("basketId", params.basketId);
252
- if (params.queryString) {
253
- const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
254
- new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
255
- }
256
268
  url.searchParams.set("lmo_offer", params.skuId);
257
269
  url.searchParams.set("status", params.status);
258
270
  url.searchParams.set("redirectToCheckout", "true");
259
- url.searchParams.set("returnUrl", params.returnUrl);
271
+ if (params.email) url.searchParams.set("email", params.email);
272
+ if (params.country) url.searchParams.set("country", params.country);
260
273
  return url.pathname + url.search;
261
274
  };
262
275
  var redirectToCheckoutFallback = (params) => {
@@ -267,13 +280,11 @@ var redirectToCheckoutFallback = (params) => {
267
280
  url.searchParams.set("inlineError", params.inlineError);
268
281
  if (params.email) url.searchParams.set("email", params.email);
269
282
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
270
- url.searchParams.set("returnUrl", params.returnUrl);
283
+ if (params.country) url.searchParams.set("country", params.country);
271
284
  return url.pathname + url.search;
272
285
  };
273
286
 
274
287
  // src/clients/payment-checkout/payment-handler.ts
275
- var MSG_ELIGIBILITY = "Something went wrong, please try again";
276
- var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
277
288
  var failApplePaySession = (session, errors = []) => {
278
289
  session.completePayment({
279
290
  status: ApplePaySession.STATUS_FAILURE,
@@ -297,37 +308,42 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
297
308
  }
298
309
  return void 0;
299
310
  }
300
- function redirectFallback(skuId, returnUrl, options) {
311
+ function redirectFallback(skuId, returnUrl, country, options) {
312
+ writeCheckoutRedirect(returnUrl);
301
313
  trackFallbackCheckout(skuId);
302
314
  window.location.href = redirectToCheckoutFallback({
303
315
  skuId,
304
- returnUrl,
316
+ country,
305
317
  ...options
306
318
  });
307
319
  }
308
320
  var ERROR_HANDLERS = {
309
- EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
310
- emailError: MSG_EMAIL_COLLISION,
321
+ // emailError flows as an inline field error on the email input.
322
+ // email pre-populates the input so the user doesn't have to retype it.
323
+ EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
324
+ emailError: walletError.message,
325
+ email: validatedEmail
326
+ }),
327
+ PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
328
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
329
+ REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
330
+ ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
331
+ emailError: walletError.message,
311
332
  email: validatedEmail
312
333
  }),
313
- PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
314
- // Matches sma-checkout catch-all: always redirect with generic inlineError.
315
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
316
- ),
317
- ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
318
- REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
319
- VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
334
+ VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
335
+ RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
320
336
  };
321
337
  function handleStructuredError(ctx) {
322
338
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
323
339
  if (handler) {
324
340
  handler(ctx);
325
341
  } else {
326
- redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
342
+ redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
327
343
  }
328
344
  }
329
345
  async function handlePaymentAuthorized(event, session, sessionData, config) {
330
- const { skuId, returnUrl, queryString } = config;
346
+ const { skuId, returnUrl, country } = config;
331
347
  let validatedEmail;
332
348
  try {
333
349
  validatedEmail = validateContacts(
@@ -347,13 +363,15 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
347
363
  });
348
364
  trackWalletPaymentSuccess(skuId, result.basketId);
349
365
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
366
+ const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
350
367
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
368
+ writeCheckoutRedirect(returnUrl);
351
369
  window.location.href = redirectToCheckoutComplete({
352
370
  basketId: result.basketId,
353
371
  status: userStatus,
354
372
  skuId: decodeSku(skuId),
355
- returnUrl,
356
- queryString
373
+ email: successEmail,
374
+ country
357
375
  });
358
376
  } catch (err) {
359
377
  if (err instanceof ContactValidationError) {
@@ -365,10 +383,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
365
383
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
366
384
  failApplePaySession(session);
367
385
  if (!walletError) {
368
- redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
386
+ redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
369
387
  return;
370
388
  }
371
- handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
389
+ handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
372
390
  }
373
391
  }
374
392
  export {
@@ -104,7 +104,7 @@ export type PurchaseResponse = {
104
104
  * Structured error body returned by all /v1/wallet/* endpoints on 4xx/5xx.
105
105
  */
106
106
  export type WalletApiError = {
107
- errorType: 'SERVICE_UNAVAILABLE' | 'PAYMENT_DECLINED' | 'VALIDATION_ERROR' | 'BAD_REQUEST' | 'EMAIL_COLLISION' | 'ELIGIBILITY_REJECTED' | 'REGISTRATION_FAILED';
107
+ errorType: 'NOT_FOUND' | 'SERVICE_UNAVAILABLE' | 'INTERNAL_SERVER_ERROR' | 'NOT_IMPLEMENTED' | 'EMAIL_COLLISION' | 'ACCOUNT_CREATION_FAILED' | 'PAYMENT_DECLINED' | 'RECAPTCHA_FAILED' | 'ELIGIBILITY_REJECTED' | 'INVALID_REQUEST' | 'VALIDATION_ERROR' | 'UNAUTHORIZED' | 'BAD_REQUEST' | 'REGISTRATION_FAILED';
108
108
  message: string;
109
109
  };
110
110
  /**