@economist/web-apple-pay 0.1.3-beta.28 → 0.1.3-beta.29

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
@@ -684,6 +684,8 @@ function renewalPeriodText(unit, duration) {
684
684
  return "";
685
685
  }
686
686
  }
687
+ var LI_SEPARATOR = "LI_SEPARATOR";
688
+ var P_SEPARATOR = "P_SEPARATOR";
687
689
  function buildTermsContext(offer, country) {
688
690
  const isUS = country === "US";
689
691
  const isCA = country === "CA";
@@ -720,19 +722,19 @@ function freeTrialTerms(offer, ctx) {
720
722
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? 1;
721
723
  const trialDateStr = `on ${getRenewalDateText(trialUnit, trialDuration)}${ctx.verificationText}`;
722
724
  if (ctx.isUS) {
723
- 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}`;
725
+ 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}`;
724
726
  }
725
- 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}`;
727
+ 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}`;
726
728
  }
727
729
  function nytTerms(ctx) {
728
730
  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.";
729
731
  if (ctx.isUS) {
730
- 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}`;
732
+ 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}`;
731
733
  }
732
734
  if (ctx.isCA) {
733
- 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}`;
735
+ 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}`;
734
736
  }
735
- 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}`;
737
+ 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}`;
736
738
  }
737
739
  function minTermContractTerms(offer, ctx) {
738
740
  const duePrice = formatPrice(offer.price.purchase, offer.price.currency_code);
@@ -742,28 +744,28 @@ function minTermContractTerms(offer, ctx) {
742
744
  const contractLength = mtDuration && mtUnit ? `${mtDuration} ${mtUnit}` : "";
743
745
  const initialContractText = contractLength ? `your initial ${contractLength} contract` : "your initial contract";
744
746
  const mtRenewalDateText = mtUnit && mtDuration ? getRenewalDateText(mtUnit, Number(mtDuration)) : ctx.renewalDateText;
745
- 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}`;
747
+ 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}`;
746
748
  }
747
749
  function paidTrialTerms(offer, ctx) {
748
750
  const trialUnit = offer.subscription_options?.trial_period?.unit ?? ctx.unit;
749
751
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? ctx.duration;
750
752
  const trialDateText = getRenewalDateText(trialUnit, trialDuration);
751
753
  if (ctx.isUS) {
752
- 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}`;
754
+ 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}`;
753
755
  }
754
- 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}`;
756
+ 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}`;
755
757
  }
756
758
  function autoRenewalTerms(ctx) {
757
759
  if (ctx.isUS) {
758
- 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}`;
760
+ 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}`;
759
761
  }
760
- 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}`;
762
+ 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}`;
761
763
  }
762
764
  function noAutoRenewalTerms(ctx) {
763
765
  if (ctx.isUS || ctx.isCA) {
764
- return `Your subscription does not renew automatically${ctx.taxFootnote}{{liSeparator}}`;
766
+ return `Your subscription does not renew automatically${ctx.taxFootnote}${LI_SEPARATOR}`;
765
767
  }
766
- return `Your subscription does not renew automatically{{liSeparator}}`;
768
+ return `Your subscription does not renew automatically${LI_SEPARATOR}`;
767
769
  }
768
770
  function generateApplePayModalTerms(offer, country) {
769
771
  const ctx = buildTermsContext(offer, country);
@@ -786,9 +788,9 @@ function generateApplePayModalTerms(offer, country) {
786
788
  return noAutoRenewalTerms(ctx);
787
789
  }
788
790
  function renderTermsHtml(termsText) {
789
- const renewalTextLists = termsText.split("{{liSeparator}}");
791
+ const renewalTextLists = termsText.split(LI_SEPARATOR);
790
792
  const lastPart = renewalTextLists.pop() || "";
791
- const renewalTextParagraphs = lastPart.split("{{pSeparator}}");
793
+ const renewalTextParagraphs = lastPart.split(P_SEPARATOR);
792
794
  const ulHtml = `<ul class="apm__terms-bullets">${renewalTextLists.map((value) => `<li>${value}</li>`).join("")}</ul>`;
793
795
  const paragraphHtml = renewalTextParagraphs.filter(Boolean).map((value) => `<p class="apm__terms-legal">${value}</p>`).join("");
794
796
  return ulHtml + paragraphHtml;
@@ -1207,7 +1209,6 @@ var validateAppleMerchant = (data) => {
1207
1209
  });
1208
1210
  };
1209
1211
  var performPurchase = (data) => {
1210
- console.log("[web-apple-pay] /v1/wallet/purchase payload", JSON.stringify(data, null, 2));
1211
1212
  return apiFetch("/v1/wallet/purchase", {
1212
1213
  method: "POST",
1213
1214
  body: JSON.stringify(data),
@@ -1224,22 +1225,6 @@ var getExpressCheckoutSession = () => {
1224
1225
  };
1225
1226
 
1226
1227
  // src/utils/expressCheckoutRedirect.ts
1227
- var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1228
- var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1229
- var writeCheckoutRedirect = (originUrl) => {
1230
- if (originUrl.startsWith("/")) {
1231
- localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1232
- return;
1233
- }
1234
- try {
1235
- const { hostname } = new URL(originUrl);
1236
- const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1237
- if (isAllowed) {
1238
- localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1239
- }
1240
- } catch {
1241
- }
1242
- };
1243
1228
  var captureOriginUrl = () => window.location.href;
1244
1229
  var buildQueryString = () => {
1245
1230
  const countryCode = new URLSearchParams(window.location.search).get(
@@ -1264,8 +1249,7 @@ var redirectToCheckoutComplete = (params) => {
1264
1249
  url.searchParams.set("lmo_offer", params.skuId);
1265
1250
  url.searchParams.set("status", params.status);
1266
1251
  url.searchParams.set("redirectToCheckout", "true");
1267
- if (params.email) url.searchParams.set("email", params.email);
1268
- if (params.country) url.searchParams.set("country", params.country);
1252
+ url.searchParams.set("returnUrl", params.returnUrl);
1269
1253
  return url.pathname + url.search;
1270
1254
  };
1271
1255
  var redirectToCheckoutFallback = (params) => {
@@ -1276,7 +1260,7 @@ var redirectToCheckoutFallback = (params) => {
1276
1260
  url.searchParams.set("inlineError", params.inlineError);
1277
1261
  if (params.email) url.searchParams.set("email", params.email);
1278
1262
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
1279
- if (params.country) url.searchParams.set("country", params.country);
1263
+ url.searchParams.set("returnUrl", params.returnUrl);
1280
1264
  return url.pathname + url.search;
1281
1265
  };
1282
1266
 
@@ -1360,16 +1344,17 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
1360
1344
  var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
1361
1345
  var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
1362
1346
  var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
1347
+ var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
1348
+ var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
1363
1349
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
1364
1350
 
1365
1351
  // src/clients/payment-checkout/apple-session.ts
1366
1352
  function redirectToFallbackCheckout(config) {
1367
- writeCheckoutRedirect(config.returnUrl);
1368
1353
  trackFallbackCheckout(config.skuId);
1369
1354
  window.location.href = redirectToCheckoutFallback({
1370
1355
  skuId: config.skuId,
1371
- inlineError: MSG_PAYMENT_GENERIC_ERROR,
1372
- country: config.countryCode
1356
+ returnUrl: config.returnUrl,
1357
+ inlineError: MSG_PAYMENT_GENERIC_ERROR
1373
1358
  });
1374
1359
  }
1375
1360
  async function handleMerchantValidation(event, session, config) {
@@ -1548,6 +1533,8 @@ var shippingContactDetails = (shippingContact) => {
1548
1533
  };
1549
1534
 
1550
1535
  // src/clients/payment-checkout/payment-handler.ts
1536
+ var MSG_ELIGIBILITY = "Something went wrong, please try again";
1537
+ var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
1551
1538
  var failApplePaySession = (session, errors = []) => {
1552
1539
  session.completePayment({
1553
1540
  status: ApplePaySession.STATUS_FAILURE,
@@ -1556,15 +1543,9 @@ var failApplePaySession = (session, errors = []) => {
1556
1543
  };
1557
1544
  function parseWalletApiError(err) {
1558
1545
  const body = err instanceof ApiError ? err.body : err;
1559
- console.warn("[web-apple-pay] parseWalletApiError: raw error", {
1560
- isApiError: err instanceof ApiError,
1561
- body
1562
- });
1563
1546
  if (body && typeof body === "object" && "errorType" in body && "message" in body) {
1564
- console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
1565
1547
  return body;
1566
1548
  }
1567
- console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
1568
1549
  return null;
1569
1550
  }
1570
1551
  function validateContacts(event, sessionData, supportedBillingCountries) {
@@ -1577,42 +1558,37 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
1577
1558
  }
1578
1559
  return void 0;
1579
1560
  }
1580
- function redirectFallback(skuId, returnUrl, country, options) {
1581
- writeCheckoutRedirect(returnUrl);
1561
+ function redirectFallback(skuId, returnUrl, options) {
1582
1562
  trackFallbackCheckout(skuId);
1583
1563
  window.location.href = redirectToCheckoutFallback({
1584
1564
  skuId,
1585
- country,
1565
+ returnUrl,
1586
1566
  ...options
1587
1567
  });
1588
1568
  }
1589
1569
  var ERROR_HANDLERS = {
1590
- // emailError flows as an inline field error on the email input.
1591
- // email pre-populates the input so the user doesn't have to retype it.
1592
- EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1593
- emailError: walletError.message,
1594
- email: validatedEmail
1595
- }),
1596
- PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1597
- ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1598
- REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1599
- ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
1600
- emailError: walletError.message,
1570
+ EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
1571
+ emailError: MSG_EMAIL_COLLISION,
1601
1572
  email: validatedEmail
1602
1573
  }),
1603
- VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
1604
- RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
1574
+ PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
1575
+ // Matches sma-checkout catch-all: always redirect with generic inlineError.
1576
+ redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
1577
+ ),
1578
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
1579
+ REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
1580
+ VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1605
1581
  };
1606
1582
  function handleStructuredError(ctx) {
1607
1583
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
1608
1584
  if (handler) {
1609
1585
  handler(ctx);
1610
1586
  } else {
1611
- redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1587
+ redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1612
1588
  }
1613
1589
  }
1614
1590
  async function handlePaymentAuthorized(event, session, sessionData, config) {
1615
- const { skuId, returnUrl, queryString, country } = config;
1591
+ const { skuId, returnUrl, queryString } = config;
1616
1592
  let validatedEmail;
1617
1593
  try {
1618
1594
  validatedEmail = validateContacts(
@@ -1620,32 +1596,25 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1620
1596
  sessionData,
1621
1597
  config.supportedBillingCountries
1622
1598
  );
1623
- const resolvedShippingContact = sessionData.loggedIn ? {
1624
- ...event.payment.shippingContact ?? {},
1625
- emailAddress: sessionData.emailAddress
1626
- } : event.payment.shippingContact;
1627
1599
  const result = await performPurchase({
1628
1600
  applePayToken: event.payment.token,
1629
1601
  skuId: config.skuId,
1630
1602
  currency: config.currency,
1631
1603
  billingContact: event.payment.billingContact,
1632
- ...resolvedShippingContact && {
1633
- shippingContact: resolvedShippingContact
1604
+ ...event.payment.shippingContact && {
1605
+ shippingContact: event.payment.shippingContact
1634
1606
  },
1635
1607
  recaptchaTokens: config.recaptchaTokens
1636
1608
  });
1637
1609
  trackWalletPaymentSuccess(skuId, result.basketId);
1638
1610
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1639
- const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
1640
1611
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1641
- writeCheckoutRedirect(returnUrl);
1642
1612
  window.location.href = redirectToCheckoutComplete({
1643
1613
  basketId: result.basketId,
1644
1614
  status: userStatus,
1645
1615
  skuId: decodeSku(skuId),
1646
- queryString,
1647
- email: successEmail,
1648
- country
1616
+ returnUrl,
1617
+ queryString
1649
1618
  });
1650
1619
  } catch (err) {
1651
1620
  if (err instanceof ContactValidationError) {
@@ -1657,10 +1626,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1657
1626
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1658
1627
  failApplePaySession(session);
1659
1628
  if (!walletError) {
1660
- redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1629
+ redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1661
1630
  return;
1662
1631
  }
1663
- handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
1632
+ handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1664
1633
  }
1665
1634
  }
1666
1635
 
@@ -679,6 +679,8 @@ function renewalPeriodText(unit, duration) {
679
679
  return "";
680
680
  }
681
681
  }
682
+ var LI_SEPARATOR = "LI_SEPARATOR";
683
+ var P_SEPARATOR = "P_SEPARATOR";
682
684
  function buildTermsContext(offer, country) {
683
685
  const isUS = country === "US";
684
686
  const isCA = country === "CA";
@@ -715,19 +717,19 @@ function freeTrialTerms(offer, ctx) {
715
717
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? 1;
716
718
  const trialDateStr = `on ${getRenewalDateText(trialUnit, trialDuration)}${ctx.verificationText}`;
717
719
  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}`;
720
+ 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
721
  }
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}`;
722
+ 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
723
  }
722
724
  function nytTerms(ctx) {
723
725
  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
726
  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}`;
727
+ 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
728
  }
727
729
  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}`;
730
+ 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
731
  }
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}`;
732
+ 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
733
  }
732
734
  function minTermContractTerms(offer, ctx) {
733
735
  const duePrice = formatPrice(offer.price.purchase, offer.price.currency_code);
@@ -737,28 +739,28 @@ function minTermContractTerms(offer, ctx) {
737
739
  const contractLength = mtDuration && mtUnit ? `${mtDuration} ${mtUnit}` : "";
738
740
  const initialContractText = contractLength ? `your initial ${contractLength} contract` : "your initial contract";
739
741
  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}`;
742
+ 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
743
  }
742
744
  function paidTrialTerms(offer, ctx) {
743
745
  const trialUnit = offer.subscription_options?.trial_period?.unit ?? ctx.unit;
744
746
  const trialDuration = offer.subscription_options?.trial_period?.duration ?? ctx.duration;
745
747
  const trialDateText = getRenewalDateText(trialUnit, trialDuration);
746
748
  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}`;
749
+ 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
750
  }
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}`;
751
+ 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
752
  }
751
753
  function autoRenewalTerms(ctx) {
752
754
  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}`;
755
+ 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
756
  }
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}`;
757
+ 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
758
  }
757
759
  function noAutoRenewalTerms(ctx) {
758
760
  if (ctx.isUS || ctx.isCA) {
759
- return `Your subscription does not renew automatically${ctx.taxFootnote}{{liSeparator}}`;
761
+ return `Your subscription does not renew automatically${ctx.taxFootnote}${LI_SEPARATOR}`;
760
762
  }
761
- return `Your subscription does not renew automatically{{liSeparator}}`;
763
+ return `Your subscription does not renew automatically${LI_SEPARATOR}`;
762
764
  }
763
765
  function generateApplePayModalTerms(offer, country) {
764
766
  const ctx = buildTermsContext(offer, country);
@@ -781,9 +783,9 @@ function generateApplePayModalTerms(offer, country) {
781
783
  return noAutoRenewalTerms(ctx);
782
784
  }
783
785
  function renderTermsHtml(termsText) {
784
- const renewalTextLists = termsText.split("{{liSeparator}}");
786
+ const renewalTextLists = termsText.split(LI_SEPARATOR);
785
787
  const lastPart = renewalTextLists.pop() || "";
786
- const renewalTextParagraphs = lastPart.split("{{pSeparator}}");
788
+ const renewalTextParagraphs = lastPart.split(P_SEPARATOR);
787
789
  const ulHtml = `<ul class="apm__terms-bullets">${renewalTextLists.map((value) => `<li>${value}</li>`).join("")}</ul>`;
788
790
  const paragraphHtml = renewalTextParagraphs.filter(Boolean).map((value) => `<p class="apm__terms-legal">${value}</p>`).join("");
789
791
  return ulHtml + paragraphHtml;
@@ -1202,7 +1204,6 @@ var validateAppleMerchant = (data) => {
1202
1204
  });
1203
1205
  };
1204
1206
  var performPurchase = (data) => {
1205
- console.log("[web-apple-pay] /v1/wallet/purchase payload", JSON.stringify(data, null, 2));
1206
1207
  return apiFetch("/v1/wallet/purchase", {
1207
1208
  method: "POST",
1208
1209
  body: JSON.stringify(data),
@@ -1219,22 +1220,6 @@ var getExpressCheckoutSession = () => {
1219
1220
  };
1220
1221
 
1221
1222
  // src/utils/expressCheckoutRedirect.ts
1222
- var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
1223
- var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
1224
- var writeCheckoutRedirect = (originUrl) => {
1225
- if (originUrl.startsWith("/")) {
1226
- localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1227
- return;
1228
- }
1229
- try {
1230
- const { hostname } = new URL(originUrl);
1231
- const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
1232
- if (isAllowed) {
1233
- localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
1234
- }
1235
- } catch {
1236
- }
1237
- };
1238
1223
  var captureOriginUrl = () => window.location.href;
1239
1224
  var buildQueryString = () => {
1240
1225
  const countryCode = new URLSearchParams(window.location.search).get(
@@ -1259,8 +1244,7 @@ var redirectToCheckoutComplete = (params) => {
1259
1244
  url.searchParams.set("lmo_offer", params.skuId);
1260
1245
  url.searchParams.set("status", params.status);
1261
1246
  url.searchParams.set("redirectToCheckout", "true");
1262
- if (params.email) url.searchParams.set("email", params.email);
1263
- if (params.country) url.searchParams.set("country", params.country);
1247
+ url.searchParams.set("returnUrl", params.returnUrl);
1264
1248
  return url.pathname + url.search;
1265
1249
  };
1266
1250
  var redirectToCheckoutFallback = (params) => {
@@ -1271,7 +1255,7 @@ var redirectToCheckoutFallback = (params) => {
1271
1255
  url.searchParams.set("inlineError", params.inlineError);
1272
1256
  if (params.email) url.searchParams.set("email", params.email);
1273
1257
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
1274
- if (params.country) url.searchParams.set("country", params.country);
1258
+ url.searchParams.set("returnUrl", params.returnUrl);
1275
1259
  return url.pathname + url.search;
1276
1260
  };
1277
1261
 
@@ -1355,16 +1339,17 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
1355
1339
  var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
1356
1340
  var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
1357
1341
  var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
1342
+ var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
1343
+ var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
1358
1344
  var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
1359
1345
 
1360
1346
  // src/clients/payment-checkout/apple-session.ts
1361
1347
  function redirectToFallbackCheckout(config) {
1362
- writeCheckoutRedirect(config.returnUrl);
1363
1348
  trackFallbackCheckout(config.skuId);
1364
1349
  window.location.href = redirectToCheckoutFallback({
1365
1350
  skuId: config.skuId,
1366
- inlineError: MSG_PAYMENT_GENERIC_ERROR,
1367
- country: config.countryCode
1351
+ returnUrl: config.returnUrl,
1352
+ inlineError: MSG_PAYMENT_GENERIC_ERROR
1368
1353
  });
1369
1354
  }
1370
1355
  async function handleMerchantValidation(event, session, config) {
@@ -1543,6 +1528,8 @@ var shippingContactDetails = (shippingContact) => {
1543
1528
  };
1544
1529
 
1545
1530
  // src/clients/payment-checkout/payment-handler.ts
1531
+ var MSG_ELIGIBILITY = "Something went wrong, please try again";
1532
+ var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
1546
1533
  var failApplePaySession = (session, errors = []) => {
1547
1534
  session.completePayment({
1548
1535
  status: ApplePaySession.STATUS_FAILURE,
@@ -1551,15 +1538,9 @@ var failApplePaySession = (session, errors = []) => {
1551
1538
  };
1552
1539
  function parseWalletApiError(err) {
1553
1540
  const body = err instanceof ApiError ? err.body : err;
1554
- console.warn("[web-apple-pay] parseWalletApiError: raw error", {
1555
- isApiError: err instanceof ApiError,
1556
- body
1557
- });
1558
1541
  if (body && typeof body === "object" && "errorType" in body && "message" in body) {
1559
- console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
1560
1542
  return body;
1561
1543
  }
1562
- console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
1563
1544
  return null;
1564
1545
  }
1565
1546
  function validateContacts(event, sessionData, supportedBillingCountries) {
@@ -1572,42 +1553,37 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
1572
1553
  }
1573
1554
  return void 0;
1574
1555
  }
1575
- function redirectFallback(skuId, returnUrl, country, options) {
1576
- writeCheckoutRedirect(returnUrl);
1556
+ function redirectFallback(skuId, returnUrl, options) {
1577
1557
  trackFallbackCheckout(skuId);
1578
1558
  window.location.href = redirectToCheckoutFallback({
1579
1559
  skuId,
1580
- country,
1560
+ returnUrl,
1581
1561
  ...options
1582
1562
  });
1583
1563
  }
1584
1564
  var ERROR_HANDLERS = {
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
+ EMAIL_COLLISION: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
1566
+ emailError: MSG_EMAIL_COLLISION,
1596
1567
  email: validatedEmail
1597
1568
  }),
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 })
1569
+ PAYMENT_DECLINED: ({ skuId, returnUrl }) => (
1570
+ // Matches sma-checkout catch-all: always redirect with generic inlineError.
1571
+ redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
1572
+ ),
1573
+ ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
1574
+ REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
1575
+ VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
1600
1576
  };
1601
1577
  function handleStructuredError(ctx) {
1602
1578
  const handler = ERROR_HANDLERS[ctx.walletError.errorType];
1603
1579
  if (handler) {
1604
1580
  handler(ctx);
1605
1581
  } else {
1606
- redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1582
+ redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1607
1583
  }
1608
1584
  }
1609
1585
  async function handlePaymentAuthorized(event, session, sessionData, config) {
1610
- const { skuId, returnUrl, queryString, country } = config;
1586
+ const { skuId, returnUrl, queryString } = config;
1611
1587
  let validatedEmail;
1612
1588
  try {
1613
1589
  validatedEmail = validateContacts(
@@ -1615,32 +1591,25 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1615
1591
  sessionData,
1616
1592
  config.supportedBillingCountries
1617
1593
  );
1618
- const resolvedShippingContact = sessionData.loggedIn ? {
1619
- ...event.payment.shippingContact ?? {},
1620
- emailAddress: sessionData.emailAddress
1621
- } : event.payment.shippingContact;
1622
1594
  const result = await performPurchase({
1623
1595
  applePayToken: event.payment.token,
1624
1596
  skuId: config.skuId,
1625
1597
  currency: config.currency,
1626
1598
  billingContact: event.payment.billingContact,
1627
- ...resolvedShippingContact && {
1628
- shippingContact: resolvedShippingContact
1599
+ ...event.payment.shippingContact && {
1600
+ shippingContact: event.payment.shippingContact
1629
1601
  },
1630
1602
  recaptchaTokens: config.recaptchaTokens
1631
1603
  });
1632
1604
  trackWalletPaymentSuccess(skuId, result.basketId);
1633
1605
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1634
- const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
1635
1606
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1636
- writeCheckoutRedirect(returnUrl);
1637
1607
  window.location.href = redirectToCheckoutComplete({
1638
1608
  basketId: result.basketId,
1639
1609
  status: userStatus,
1640
1610
  skuId: decodeSku(skuId),
1641
- queryString,
1642
- email: successEmail,
1643
- country
1611
+ returnUrl,
1612
+ queryString
1644
1613
  });
1645
1614
  } catch (err) {
1646
1615
  if (err instanceof ContactValidationError) {
@@ -1652,10 +1621,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1652
1621
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1653
1622
  failApplePaySession(session);
1654
1623
  if (!walletError) {
1655
- redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1624
+ redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
1656
1625
  return;
1657
1626
  }
1658
- handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
1627
+ handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
1659
1628
  }
1660
1629
  }
1661
1630