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

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.
@@ -43,11 +43,18 @@ var apiFetch = async (path, options = {}) => {
43
43
  });
44
44
  if (!response.ok) {
45
45
  const contentType = response.headers.get("content-type") || "";
46
+ console.warn("[web-apple-pay] apiFetch: non-2xx response", {
47
+ path,
48
+ status: response.status,
49
+ contentType
50
+ });
46
51
  if (contentType.includes("application/json")) {
47
52
  const errorBody = await response.json();
53
+ console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
48
54
  throw new ApiError(response.status, errorBody);
49
55
  }
50
56
  const errorText = await response.text();
57
+ console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
51
58
  throw new Error(
52
59
  `API request failed: ${response.status} ${response.statusText} - ${errorText}`
53
60
  );
@@ -239,13 +246,26 @@ var shippingContactDetails = (shippingContact) => {
239
246
  };
240
247
 
241
248
  // src/utils/expressCheckoutRedirect.ts
249
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
250
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
251
+ var writeCheckoutRedirect = (originUrl) => {
252
+ if (originUrl.startsWith("/")) {
253
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
254
+ return;
255
+ }
256
+ try {
257
+ const { hostname } = new URL(originUrl);
258
+ const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
259
+ if (isAllowed) {
260
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
261
+ }
262
+ } catch {
263
+ }
264
+ };
242
265
  var decodeSku = (sku) => {
243
266
  try {
244
- const decoded = atob(sku.substring(sku.indexOf("-") + 1));
245
- console.log("[web-apple-pay] decodeSku", { input: sku, decoded });
246
- return decoded;
247
- } catch (err) {
248
- console.warn("[web-apple-pay] decodeSku: failed to decode, returning raw", { input: sku, err });
267
+ return atob(sku.substring(sku.indexOf("-") + 1));
268
+ } catch {
249
269
  return sku;
250
270
  }
251
271
  };
@@ -259,24 +279,16 @@ var redirectToCheckoutComplete = (params) => {
259
279
  url.searchParams.set("lmo_offer", params.skuId);
260
280
  url.searchParams.set("status", params.status);
261
281
  url.searchParams.set("redirectToCheckout", "true");
262
- url.searchParams.set("returnUrl", params.returnUrl);
263
282
  return url.pathname + url.search;
264
283
  };
265
284
  var redirectToCheckoutFallback = (params) => {
266
- const decodedOffer = decodeSku(params.skuId);
267
- console.log("[web-apple-pay] redirectToCheckoutFallback", {
268
- rawSkuId: params.skuId,
269
- decodedOffer
270
- });
271
285
  const url = new URL(window.location.href);
272
286
  url.pathname = "/checkout";
273
- url.searchParams.set("lmo_offer", decodedOffer);
287
+ url.searchParams.set("lmo_offer", decodeSku(params.skuId));
274
288
  if (params.inlineError)
275
289
  url.searchParams.set("inlineError", params.inlineError);
276
290
  if (params.email) url.searchParams.set("email", params.email);
277
291
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
278
- url.searchParams.set("returnUrl", params.returnUrl);
279
- console.log("[web-apple-pay] fallback redirect URL", url.pathname + url.search);
280
292
  return url.pathname + url.search;
281
293
  };
282
294
 
@@ -291,9 +303,15 @@ var failApplePaySession = (session, errors = []) => {
291
303
  };
292
304
  function parseWalletApiError(err) {
293
305
  const body = err instanceof ApiError ? err.body : err;
306
+ console.warn("[web-apple-pay] parseWalletApiError: raw error", {
307
+ isApiError: err instanceof ApiError,
308
+ body
309
+ });
294
310
  if (body && typeof body === "object" && "errorType" in body && "message" in body) {
311
+ console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
295
312
  return body;
296
313
  }
314
+ console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
297
315
  return null;
298
316
  }
299
317
  function validateContacts(event, sessionData, supportedBillingCountries) {
@@ -307,11 +325,10 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
307
325
  return void 0;
308
326
  }
309
327
  function redirectFallback(skuId, returnUrl, options) {
310
- console.log("[web-apple-pay] redirectFallback called", { skuId, returnUrl, options });
328
+ writeCheckoutRedirect(returnUrl);
311
329
  trackFallbackCheckout(skuId);
312
330
  window.location.href = redirectToCheckoutFallback({
313
331
  skuId,
314
- returnUrl,
315
332
  ...options
316
333
  });
317
334
  }
@@ -358,11 +375,11 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
358
375
  trackWalletPaymentSuccess(skuId, result.basketId);
359
376
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
360
377
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
378
+ writeCheckoutRedirect(returnUrl);
361
379
  window.location.href = redirectToCheckoutComplete({
362
380
  basketId: result.basketId,
363
381
  status: userStatus,
364
382
  skuId: decodeSku(skuId),
365
- returnUrl,
366
383
  queryString
367
384
  });
368
385
  } catch (err) {
@@ -372,6 +389,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
372
389
  }
373
390
  console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
374
391
  const walletError = parseWalletApiError(err);
392
+ console.warn("[web-apple-pay] handlePaymentAuthorized: walletError result", {
393
+ walletError,
394
+ validatedEmail
395
+ });
375
396
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
376
397
  failApplePaySession(session);
377
398
  if (!walletError) {
@@ -43,11 +43,18 @@ var apiFetch = async (path, options = {}) => {
43
43
  });
44
44
  if (!response.ok) {
45
45
  const contentType = response.headers.get("content-type") || "";
46
+ console.warn("[web-apple-pay] apiFetch: non-2xx response", {
47
+ path,
48
+ status: response.status,
49
+ contentType
50
+ });
46
51
  if (contentType.includes("application/json")) {
47
52
  const errorBody = await response.json();
53
+ console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
48
54
  throw new ApiError(response.status, errorBody);
49
55
  }
50
56
  const errorText = await response.text();
57
+ console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
51
58
  throw new Error(
52
59
  `API request failed: ${response.status} ${response.statusText} - ${errorText}`
53
60
  );
package/dist/src/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
  }
@@ -1573,11 +1609,11 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1573
1609
  trackWalletPaymentSuccess(skuId, result.basketId);
1574
1610
  session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
1575
1611
  const userStatus = sessionData.loggedIn ? "lex" : "nex";
1612
+ writeCheckoutRedirect(returnUrl);
1576
1613
  window.location.href = redirectToCheckoutComplete({
1577
1614
  basketId: result.basketId,
1578
1615
  status: userStatus,
1579
1616
  skuId: decodeSku(skuId),
1580
- returnUrl,
1581
1617
  queryString
1582
1618
  });
1583
1619
  } catch (err) {
@@ -1587,6 +1623,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
1587
1623
  }
1588
1624
  console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
1589
1625
  const walletError = parseWalletApiError(err);
1626
+ console.warn("[web-apple-pay] handlePaymentAuthorized: walletError result", {
1627
+ walletError,
1628
+ validatedEmail
1629
+ });
1590
1630
  trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
1591
1631
  failApplePaySession(session);
1592
1632
  if (!walletError) {
@@ -2081,7 +2121,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2081
2121
  if (button) {
2082
2122
  button.classList.remove("wap-loading");
2083
2123
  this.#updateDisabledState();
2084
- this.#recaptchaTokensPromise = getPurchaseRecaptchaTokens();
2124
+ this.#scheduleRecaptchaTokens();
2085
2125
  trackExpressWalletImpression(this.#offer.sku_id, {
2086
2126
  entryPoint: this.#entryPoint,
2087
2127
  country: this.#country
@@ -2120,10 +2160,27 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
2120
2160
  const country = this.#country;
2121
2161
  modal.onConfirm = (recaptchaTokens) => {
2122
2162
  this.#initiateApplePay(skuId, country, recaptchaTokens);
2123
- this.#recaptchaTokensPromise = getPurchaseRecaptchaTokens();
2163
+ this.#scheduleRecaptchaTokens();
2124
2164
  };
2125
2165
  modal.open(this.#recaptchaTokensPromise);
2126
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
+ }
2127
2184
  async #fetchSession() {
2128
2185
  try {
2129
2186
  return await getExpressCheckoutSession();
@@ -32,6 +32,24 @@ var createFetchMock = (sessionResponse, sessionStatus = 200) => {
32
32
  headers: { "content-type": "application/json" }
33
33
  });
34
34
  }
35
+ if (requestUrl.includes("/v1/wallet/payment-options/sku/")) {
36
+ return new Response(
37
+ JSON.stringify({
38
+ options: {
39
+ apple_pay: {
40
+ provider: "apple-pay",
41
+ settings: {
42
+ supported_networks: ["visa", "masterCard", "amex"]
43
+ }
44
+ }
45
+ }
46
+ }),
47
+ {
48
+ status: 200,
49
+ headers: { "content-type": "application/json" }
50
+ }
51
+ );
52
+ }
35
53
  return new Response("Not Found", {
36
54
  status: 404,
37
55
  statusText: "Not Found"
@@ -1,11 +1,24 @@
1
1
  /**
2
2
  * Centralized redirect URL builders for the Express Checkout payment handler.
3
3
  *
4
- * All redirects include a `returnUrl` parameter so downstream pages
5
- * (/checkout/complete, onboarding) can return the user to the original article.
4
+ * Before redirecting, the origin article URL is persisted to localStorage under
5
+ * the `checkout-redirect` key so that sma-checkout / the onboarding app can
6
+ * return the user to the article after checkout and onboarding complete.
6
7
  *
7
8
  * Matches sma-checkout: src/lib/navigate-to-checkout/index.tsx
8
9
  */
10
+ /**
11
+ * Validates that a URL is safe to use as a post-checkout redirect destination,
12
+ * then writes it to localStorage under `checkout-redirect`.
13
+ *
14
+ * Allowed values:
15
+ * - Relative paths (start with `/`)
16
+ * - Absolute URLs on `economist.com` or any subdomain of it
17
+ *
18
+ * Any other value (e.g. an external domain) is silently rejected to prevent
19
+ * open-redirect vulnerabilities.
20
+ */
21
+ export declare const writeCheckoutRedirect: (originUrl: string) => void;
9
22
  /**
10
23
  * Captures the current page URL (the article the user is reading).
11
24
  * Must be called before any redirect occurs.
@@ -29,26 +42,34 @@ export declare const buildQueryString: () => string;
29
42
  export declare const decodeSku: (sku: string) => string;
30
43
  /**
31
44
  * Builds the success redirect URL.
32
- * /checkout/complete?basketId={id}&lmo_offer={sku}&status={userStatus}&redirectToCheckout=true&returnUrl=...
45
+ * /checkout/complete?basketId={id}&lmo_offer={sku}&status={userStatus}&redirectToCheckout=true
46
+ *
47
+ * The origin article URL is written to localStorage by the caller via
48
+ * writeCheckoutRedirect() before this redirect fires.
33
49
  */
34
50
  export declare const redirectToCheckoutComplete: (params: {
35
51
  basketId: string;
36
52
  status: "nex" | "lex";
37
53
  skuId: string;
38
- returnUrl: string;
39
54
  queryString?: string;
40
55
  }) => string;
41
56
  /**
42
57
  * Builds the fallback redirect URL for error scenarios.
43
- * /checkout?lmo_offer={sku}&inlineError=...&returnUrl=...
58
+ * /checkout?lmo_offer={offerPath}&inlineError=...
59
+ *
60
+ * The skuId is decoded via decodeSku() before being set as lmo_offer, producing
61
+ * a Limio offer path (e.g. /offers2/UK-UK.DIGITAL.1YAR.X.DISC.30.NYT) as
62
+ * checkout expects. If decoding fails (malformed SKU), the raw skuId is used.
44
63
  *
45
64
  * Builds on top of the current page URL (window.location.href) so that any
46
65
  * existing query params (UTM tags, campaign flags) are preserved — matching
47
66
  * sma-checkout: src/lib/navigate-to-checkout/index.tsx
67
+ *
68
+ * The origin article URL is written to localStorage by the caller via
69
+ * writeCheckoutRedirect() before this redirect fires.
48
70
  */
49
71
  export declare const redirectToCheckoutFallback: (params: {
50
72
  skuId: string;
51
- returnUrl: string;
52
73
  inlineError?: string;
53
74
  emailError?: string;
54
75
  email?: string;
@@ -1,4 +1,20 @@
1
1
  // src/utils/expressCheckoutRedirect.ts
2
+ var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
3
+ var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
4
+ var writeCheckoutRedirect = (originUrl) => {
5
+ if (originUrl.startsWith("/")) {
6
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
7
+ return;
8
+ }
9
+ try {
10
+ const { hostname } = new URL(originUrl);
11
+ const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
12
+ if (isAllowed) {
13
+ localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
14
+ }
15
+ } catch {
16
+ }
17
+ };
2
18
  var captureOriginUrl = () => window.location.href;
3
19
  var buildQueryString = () => {
4
20
  const countryCode = new URLSearchParams(window.location.search).get(
@@ -8,11 +24,8 @@ var buildQueryString = () => {
8
24
  };
9
25
  var decodeSku = (sku) => {
10
26
  try {
11
- const decoded = atob(sku.substring(sku.indexOf("-") + 1));
12
- console.log("[web-apple-pay] decodeSku", { input: sku, decoded });
13
- return decoded;
14
- } catch (err) {
15
- console.warn("[web-apple-pay] decodeSku: failed to decode, returning raw", { input: sku, err });
27
+ return atob(sku.substring(sku.indexOf("-") + 1));
28
+ } catch {
16
29
  return sku;
17
30
  }
18
31
  };
@@ -26,24 +39,16 @@ var redirectToCheckoutComplete = (params) => {
26
39
  url.searchParams.set("lmo_offer", params.skuId);
27
40
  url.searchParams.set("status", params.status);
28
41
  url.searchParams.set("redirectToCheckout", "true");
29
- url.searchParams.set("returnUrl", params.returnUrl);
30
42
  return url.pathname + url.search;
31
43
  };
32
44
  var redirectToCheckoutFallback = (params) => {
33
- const decodedOffer = decodeSku(params.skuId);
34
- console.log("[web-apple-pay] redirectToCheckoutFallback", {
35
- rawSkuId: params.skuId,
36
- decodedOffer
37
- });
38
45
  const url = new URL(window.location.href);
39
46
  url.pathname = "/checkout";
40
- url.searchParams.set("lmo_offer", decodedOffer);
47
+ url.searchParams.set("lmo_offer", decodeSku(params.skuId));
41
48
  if (params.inlineError)
42
49
  url.searchParams.set("inlineError", params.inlineError);
43
50
  if (params.email) url.searchParams.set("email", params.email);
44
51
  if (params.emailError) url.searchParams.set("emailError", params.emailError);
45
- url.searchParams.set("returnUrl", params.returnUrl);
46
- console.log("[web-apple-pay] fallback redirect URL", url.pathname + url.search);
47
52
  return url.pathname + url.search;
48
53
  };
49
54
  export {
@@ -51,5 +56,6 @@ export {
51
56
  captureOriginUrl,
52
57
  decodeSku,
53
58
  redirectToCheckoutComplete,
54
- redirectToCheckoutFallback
59
+ redirectToCheckoutFallback,
60
+ writeCheckoutRedirect
55
61
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@economist/web-apple-pay",
3
3
  "description": "Web component package for Apple Pay checkout UI",
4
- "version": "0.1.3-beta.20",
4
+ "version": "0.1.3-beta.21",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",