PageSourceSearch

https://unherd.com/wp-content/plugins/coeditor-connector/public/js/coeditor-connector-script.js?1790237849

js unherd.com collected 2026-09-24 08:28:58 UTC 25,956 bytes, 861 lines download raw bytes

1// Use the localized config from PHP
2const config = {
3  stripeKey: coeditorConnectorConfig.stripeKey,
4  checkoutUrl: coeditorConnectorConfig.checkoutUrl,
5  baseUrl: coeditorConnectorConfig.baseUrl,
6};
7
8function configureCoeditorPiano() {
9  const piano = window.coeditorConnectorConfig;
10  if (!piano || !piano.pianoAppId) {
11    return;
12  }
13
14  tp.push(["setAid", piano.pianoAppId]);
15  tp.push(["setUseTinypassAccounts", false]);
16  tp.push(["setUsePianoIdUserProvider", true]);
17  tp.push(["setRequestUserAuthForLinkedTerm", false]);
18
19  if (piano.endpoint) {
20    tp.push(["setEndpoint", piano.endpoint]);
21  } else if (piano.sandbox) {
22    tp.push(["setEndpoint", "https://sandbox.tinypass.com/api/v3"]);
23  } else {
24    tp.push(["setEndpoint", "https://buy.tinypass.com/api/v3"]);
25  }
26
27  if (piano.pianoIdUrl) {
28    tp.push(["setPianoIdUrl", piano.pianoIdUrl]);
29  }
30  if (piano.composerHost) {
31    tp.push(["setComposerHost", piano.composerHost]);
32  }
33  if (piano.staticDomain) {
34    tp.push(["setStaticDomain", piano.staticDomain]);
35  }
36
37  tp.push([
38    "addHandler",
39    "loginSuccess",
40    function () {
41      setTimeout(function () {
42        location.reload();
43      }, 2000);
44    },
45  ]);
46}
47
48// ------------------------------
49// Long tracking params helper
50// ------------------------------
51// Stripe drops query params where the VALUE is >= 50 chars on redirect.
52// We persist those params and re-apply them on the thank-you page.
53const COEDITOR_TRACKING_PARAMS_KEY = "coeditor_tracking_params_v1";
54const COEDITOR_REFER_URL_KEY = "refer_url";
55const COEDITOR_TRACKING_MIN_LEN = 50;
56const COEDITOR_SUBS_ATTRIBUTION_COOKIE = "subs_attribution";
57const COEDITOR_DEFAULT_SUBS_ATTRIBUTION_KEYS = [
58  "unherd_subs_attribution",
59  "spec_subs_attribution",
60  "subs_attribution",
61];
62const COEDITOR_SUBS_ATTRIBUTION_COOKIE_MAX = 3500;
63
64let COEDITOR_TRACKING_PARAMS = {};
65
66function isStripeDroppedParamValue(value) {
67  if (!value) return false;
68  const s = String(value);
69  const decodedLen = s.length;
70  const encodedLen = encodeURIComponent(s).length;
71  return Math.max(decodedLen, encodedLen) >= COEDITOR_TRACKING_MIN_LEN;
72}
73
74function getTrackingParamsFromUrl(href = window.location.href) {
75  try {
76    const url = new URL(href);
77    const out = {};
78    for (const [k, v] of url.searchParams.entries()) {
79      if (isStripeDroppedParamValue(v)) out[k] = v;
80    }
81    return out;
82  } catch {
83    return {};
84  }
85}
86
87function getStoredReferUrl() {
88  try {
89    const stored = localStorage.getItem(COEDITOR_REFER_URL_KEY);
90    return stored && stored.trim() ? stored.trim() : null;
91  } catch {
92    return null;
93  }
94}
95
96function getCheckoutReferUrl() {
97  const storedRefer = getStoredReferUrl();
98  if (storedRefer) {
99    return storedRefer;
100  }
101
102  const queryRefer = decodeReferUrlParam(
103    new URLSearchParams(window.location.search).get("refer_url"),
104  );
105  return queryRefer || "";
106}
107
108function decodeReferUrlParam(raw) {
109  if (!raw) return "";
110  try {
111    return decodeURIComponent(raw);
112  } catch {
113    return raw;
114  }
115}
116
117let pendingReferUrlFromSession = null;
118let referRedirectStarted = false;
119
120function getPendingReferUrl() {
121  return getStoredReferUrl() || pendingReferUrlFromSession || "";
122}
123
124function isCrossSellOfferVisible() {
125  const wrapper = document.getElementById("cross_sell_wrapper");
126  return !!(wrapper && wrapper.querySelector(".do_cross_sell"));
127}
128
129function redirectToPendingReferUrl() {
130  const target = getPendingReferUrl();
131  if (!target) {
132    return false;
133  }
134
135  try {
136    localStorage.removeItem(COEDITOR_REFER_URL_KEY);
137  } catch {}
138
139  referRedirectStarted = true;
140  window.location.replace(target);
141  return true;
142}
143
144function maybeRedirectAfterCheckout(session) {
145  if (session && session.redirect_url) {
146    pendingReferUrlFromSession = session.redirect_url;
147  }
148
149  if (!getPendingReferUrl()) {
150    return false;
151  }
152
153  if (isCrossSellOfferVisible()) {
154    return false;
155  }
156
157  return redirectToPendingReferUrl();
158}
159
160window.coeditorReferRedirect = {
161  maybeRedirectAfterCheckout,
162  redirectToPendingReferUrl,
163  isCrossSellOfferVisible,
164};
165
166// IMPORTANT: on /checkout/ your long param often only exists inside refer_url (encoded).
167function getTrackingParamsFromReferUrlParam() {
168  try {
169    const storedRefer = getStoredReferUrl();
170    if (storedRefer) {
171      return getTrackingParamsFromUrl(storedRefer);
172    }
173
174    const url = new URL(window.location.href);
175    const referRaw = url.searchParams.get("refer_url");
176    if (!referRaw) return {};
177
178    return getTrackingParamsFromUrl(decodeReferUrlParam(referRaw));
179  } catch {
180    return {};
181  }
182}
183
184function getConfiguredSubsAttributionKeys() {
185  const configured = window.coeditorConnectorConfig?.subsAttributionKeys;
186  if (Array.isArray(configured) && configured.length) {
187    return configured.filter((key) => typeof key === "string" && key);
188  }
189  return COEDITOR_DEFAULT_SUBS_ATTRIBUTION_KEYS.slice();
190}
191
192function getSubsAttributionStorageKeys() {
193  const keys = getConfiguredSubsAttributionKeys();
194  try {
195    for (let i = 0; i < localStorage.length; i++) {
196      const key = localStorage.key(i);
197      if (key && /subs_attribution$/i.test(key) && !keys.includes(key)) {
198        keys.push(key);
199      }
200    }
201  } catch {}
202  return keys;
203}
204
205function getSubsAttribution() {
206  const merged = {};
207  for (const key of getSubsAttributionStorageKeys()) {
208    try {
209      const raw = localStorage.getItem(key);
210      if (!raw) continue;
211      const parsed = JSON.parse(raw);
212      if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
213        Object.assign(merged, parsed);
214      }
215    } catch {}
216  }
217  return Object.keys(merged).length ? merged : null;
218}
219
220function persistSubsAttributionCookie(attribution) {
221  if (!attribution) return;
222
223  try {
224    const encoded = encodeURIComponent(JSON.stringify(attribution));
225    if (encoded.length > COEDITOR_SUBS_ATTRIBUTION_COOKIE_MAX) {
226      return;
227    }
228    document.cookie =
229      COEDITOR_SUBS_ATTRIBUTION_COOKIE +
230      "=" +
231      encoded +
232      "; path=/; SameSite=Lax";
233  } catch {}
234}
235
236function getTrackingParamsFromStorage() {
237  const out = {};
238  try {
239    const raw = localStorage.getItem(COEDITOR_TRACKING_PARAMS_KEY);
240    if (raw) Object.assign(out, JSON.parse(raw));
241  } catch {}
242  return out;
243}
244
245function storeTrackingParams(params) {
246  if (!params || typeof params !== "object") return;
247  if (Object.keys(params).length === 0) return;
248
249  try {
250    const existing = getTrackingParamsFromStorage();
251    const merged = { ...existing, ...params };
252    localStorage.setItem(COEDITOR_TRACKING_PARAMS_KEY, JSON.stringify(merged));
253  } catch {}
254}
255
256function clearStoredTrackingParams() {
257  try {
258    localStorage.removeItem(COEDITOR_TRACKING_PARAMS_KEY);
259  } catch {}
260}
261
262// Capture once on page load:
263// - from current URL 
264// - AND from decoded refer_url (checkout page usually has it here)
265function captureTrackingParamsOnce() {
266  const fromUrl = getTrackingParamsFromUrl();
267  const fromRefer = getTrackingParamsFromReferUrlParam();
268  const merged = { ...fromRefer, ...fromUrl }; // current URL wins
269
270  if (Object.keys(merged).length) {
271    COEDITOR_TRACKING_PARAMS = { ...COEDITOR_TRACKING_PARAMS, ...merged };
272    storeTrackingParams(merged);
273    return COEDITOR_TRACKING_PARAMS;
274  }
275
276  COEDITOR_TRACKING_PARAMS = getTrackingParamsFromStorage();
277  return COEDITOR_TRACKING_PARAMS;
278}
279
280// Read-only
281function getTrackingParams() {
282  const fromUrl = getTrackingParamsFromUrl();
283  const fromStore = getTrackingParamsFromStorage();
284  return { ...fromStore, ...COEDITOR_TRACKING_PARAMS, ...fromUrl };
285}
286
287function ensureTrackingParamsInUrl(params) {
288  if (!params || typeof params !== "object") return;
289
290  const url = new URL(window.location.href);
291  let changed = false;
292
293  for (const [k, v] of Object.entries(params)) {
294    if (!v) continue;
295
296    const current = url.searchParams.get(k);
297
298    // Add if missing, or restore if Stripe truncated/blanked it
299    if (!current || current.length < String(v).length) {
300      url.searchParams.set(k, v);
301      changed = true;
302    }
303  }
304
305  if (changed) window.history.replaceState({}, "", url.toString());
306}
307
308// Keep your existing nonce behaviour, but also restore tracking params on thank-you URL
309function ensureNonceAndTrackingParamsInUrl() {
310  const url = new URL(window.location.href);
311  const csid = url.searchParams.get("checkout_session_id");
312  if (!csid) return;
313
314  let changed = false;
315
316  if (url.searchParams.get("__nonce") !== "true") {
317    url.searchParams.set("__nonce", "true");
318    changed = true;
319  }
320
321  const tracking = getTrackingParams();
322  for (const [k, v] of Object.entries(tracking)) {
323    if (!v) continue;
324    if (url.searchParams.get(k) !== v) {
325      url.searchParams.set(k, v);
326      changed = true;
327    }
328  }
329
330  if (changed) window.history.replaceState({}, "", url.toString());
331}
332
333
334async function handleCheckoutSession() {
335  const urlParams = new URLSearchParams(window.location.search);
336  const csid = urlParams.get("checkout_session_id");
337  if (!csid) return;
338
339  try {
340    const trackingParams = getTrackingParams();
341    const fastStatus = Boolean(getPendingReferUrl() && !isCrossSellOfferVisible());
342
343    const cleanBaseUrl = stripQueryParams(config.baseUrl);
344    const response = await fetch(`${cleanBaseUrl}/wp-json/coeditor-connector/v1/stripe-status`, {
345      headers: {
346        Accept: "application/json",
347        "Content-Type": "application/json",
348      },
349      method: "POST",
350      body: JSON.stringify({
351        session_id: csid,
352        stripeAccountId: urlParams.get("sid"),
353        tracking_params: trackingParams,
354        fast_status: fastStatus,
355        subs_attribution: getSubsAttribution(),
356      }),
357    });
358
359    const session = await response.json();
360
361    if (session.status === "open") {
362      const fallbackUrl = config.checkoutUrl || config.baseUrl + "/offer/";
363      window.location.replace(fallbackUrl);
364      return; // stop further processing
365    }
366
367    if (session.status === "complete") {
368      const expires = new Date(Date.now() + 3600 * 1000);
369      const cookieDomain = window.location.hostname;
370
371      if (session.token?.access_token) {
372        document.cookie =
373          "__utp=" +
374          session.token.access_token +
375          "; expires=" +
376          expires.toUTCString() +
377          "; path=/";
378      }
379
380      if (session.piano_user_id) {
381        document.cookie =
382          "tpuser=" +
383          session.piano_user_id +
384          ";domain=." +
385          cookieDomain +
386          ";path=/";
387      }
388
389      document.body.classList.add("coeditor-logged-in");
390
391      if (session.customer_email) {
392        localStorage.setItem(
393          "coeditor_customer_email",
394          session.customer_email,
395        );
396      }
397
398      try {
399        localStorage.setItem(
400          "coeditor_stripe_checkout",
401          JSON.stringify({
402            session_id: session.session_id || csid,
403            subscription_id: session.subscription_id || null,
404            currency: session.currency || null,
405            amount_total: session.amount_total ?? null,
406            payment_status: session.payment_status || null,
407            customer_email: session.customer_email || null,
408            customer_name: session.customer_name || null,
409            saved_at: Date.now(),
410            product_name: session.product_name || null,
411            product_description: session.product_description || null,
412            price_id: session.price_id || null,
413            product_id: session.product_id || null,
414            unit_amount: session.unit_amount || null,
415            quantity: session.quantity || 1,
416            billing_interval: session.billing_interval || null,
417            billing_interval_count:
418              session.billing_interval_count || null,
419            billing_cycle: session.billing_cycle || null,
420          }),
421        );
422      } catch (error) {
423        console.warn("Could not persist Stripe checkout info:", error);
424      }
425
426      const prefix = "offerFields_";
427      for (let i = localStorage.length - 1; i >= 0; i--) {
428        const key = localStorage.key(i);
429
430        if (key && key.startsWith(prefix)) {
431          localStorage.removeItem(key);
432        }
433      }
434
435      loginUser();
436
437      const tracking = getTrackingParams();
438      const urlNow = new URL(window.location.href);
439      const allPresent = Object.entries(tracking).every(
440        ([key, value]) => urlNow.searchParams.get(key) === value,
441      );
442
443      if (allPresent) {
444        clearStoredTrackingParams();
445        COEDITOR_TRACKING_PARAMS = {};
446      }
447
448      const redirectDelayStartedAt = performance.now();
449
450      console.log("[Checkout] Waiting before redirect");
451
452      await waitBeforeCheckoutRedirect();
453
454      console.log(
455        `[Checkout] Redirecting after ${Math.round(
456          performance.now() - redirectDelayStartedAt,
457        )} ms`,
458      );
459
460      if (maybeRedirectAfterCheckout(session)) {
461        return;
462      }
463    }
464
465        } catch (error) {
466          console.error("Error fetching session status:", error);
467        }
468      }
469
470  const CHECKOUT_REDIRECT_DELAY_MS = 1500;
471
472  function waitBeforeCheckoutRedirect() {
473    return new Promise((resolve) => {
474      window.setTimeout(resolve, CHECKOUT_REDIRECT_DELAY_MS);
475    });
476  }
477
478// This is for elements not yet available when the script runs
479function watchForElements(selector, callback) {
480  const observer = new MutationObserver((mutations, obs) => {
481    const elements = document.querySelectorAll(selector);
482    if (elements.length > 0) {
483      callback(elements);
484      obs.disconnect();
485    }
486  });
487  observer.observe(document.body, { childList: true, subtree: true });
488}
489
490function stripQueryParams(urlString) {
491  const url = new URL(urlString);
492  return url.origin + url.pathname;
493}
494
495function loginUser() {
496  var user = tp.pianoId.getUser();
497  var name = user.email.slice(0, 2);
498  if (user.firstName && user.lastName) {
499    name = user.firstName.slice(0, 1) + user.lastName.slice(0, 1);
500  } else if (user.firstName) {
501    name = user.firstName.slice(0, 2);
502  } else if (user.lastName) {
503    name = user.lastName.slice(0, 2);
504  }
505
506  watchForElements(".header-nav__button--account", (elements) => {
507    elements[0].style.display = "block";
508  });
509
510  watchForElements(".header-nav__button--login", (elements) => {
511    elements[0].style.display = "none";
512  });
513
514//   if (document.querySelectorAll("#auth-link-1").length > 0) {
515//     const button1 = document.getElementById("auth-link-1");
516//     const button2 = document.getElementById("auth-link-2");
517
518//     button1.innerHTML = "My Account";
519//     button1.href = "/my-account";
520//     button2.innerHTML = "Logout";
521
522//     button2.addEventListener("click", function () {
523//       button2.innerText = "Please wait";
524
525// 	// Legacy logout flow,
526//     //   tp.user.logout(function () {
527//     //     setTimeout(function () {
528//     //       tp.pianoId.logout();
529//     //       window.location.href = window.location.origin;
530//     //     }, 2000);
531//     //   });
532
533// 		tp.pianoId.logout();
534// 		window.location.href = window.location.origin;
535//     });
536//   }
537
538  if (document.querySelectorAll("#briefingAccount").length > 0) {
539    var accountContainer = document.getElementById("briefingAccount");
540    if (tp.pianoId.isUserValid()) {
541      accountContainer.innerHTML = '<button id="logoutBtn">Logout</button>';
542      document.getElementById("logoutBtn").addEventListener("click", function () {
543        tp.pianoId.logout();
544        window.location.reload();
545      });
546    } else {
547      accountContainer.innerHTML = '<button id="loginBtn">Login</button>';
548      document.getElementById("loginBtn").addEventListener("click", function () {
549        tp.pianoId.show({ screen: "login" });
550      });
551    }
552  }
553
554  if (document.querySelectorAll(".single-post").length > 0) {
555    document.querySelector(".single-post").classList.add("logged-in");
556  }
557
558  if (document.querySelectorAll(".hamburger").length > 0) {
559    document.querySelector(".hamburger").innerHTML =
560      '<div class="user-name">' + name + "</div>";
561  }
562
563  [].forEach.call(document.querySelectorAll(".not-logged"), function (el) {
564    el.style.display = "none";
565  });
566
567  [].forEach.call(document.querySelectorAll(".logged"), function (el) {
568    el.style.display = "block";
569  });
570
571  if (document.querySelectorAll(".piano-login-link").length > 0) {
572    document.querySelectorAll(".piano-login-link").forEach(function (el) {
573      el.classList.add("loggedin");
574      el.innerHTML = '<a href="/my-subscription">My Account</a>';
575    });
576  }
577}
578
579document.addEventListener("DOMContentLoaded", async function () {
580  // NEW: capture long params from URL and/or refer_url early
581  captureTrackingParamsOnce();
582
583  // NEW: on thank-you pages, restore nonce + tracking params to the URL immediately
584  ensureNonceAndTrackingParamsInUrl();
585
586  window.addEventListener("message", (event) => {
587    if (event.data?.type === "checkoutData") {
588      localStorage.setItem("offerFields_" + event.data.fieldId, event.data.value);
589    }
590  });
591
592  // Initialize the tp object
593  tp = window["tp"] || [];
594  configureCoeditorPiano();
595
596  function onLinkedTermSelected(sessionId, linkedTerm) {
597    const subsAttribution = getSubsAttribution();
598    persistSubsAttributionCookie(subsAttribution);
599
600    // Keep the Spectator cookie name so older Connector PHP still receives it.
601    try {
602      const specSubsAttribution = localStorage.getItem("spec_subs_attribution");
603      if (specSubsAttribution) {
604        document.cookie =
605          "spec_subs_attribution=" +
606          encodeURIComponent(specSubsAttribution) +
607          "; path=/";
608      }
609    } catch {}
610
611    var data;
612    if (linkedTerm.linkedTermData !== undefined) {
613      data = linkedTerm.linkedTermData;
614      var payload = data;
615      var externalTermId = data.externalTermId;
616      var externalProductId = data.externalProductIds;
617    } else if (linkedTerm.linkTermSignedData !== undefined) {
618      data = linkedTerm.linkTermSignedData;
619      var payload = JSON.parse(data.payload);
620      var externalTermId = payload.externalTermId;
621      var externalProductId = payload.externalProductIds;
622    } else {
623      console.log("No link term data found");
624    }
625
626    const url = new URL(config.checkoutUrl.replace(/\/+$/, ""));
627
628	console.log(payload.customData);
629	
630    url.searchParams.set("session_id", sessionId);
631    url.searchParams.set("term_id", externalTermId);
632    url.searchParams.set("coupon", payload.customData.coupon ?? "");
633    url.searchParams.set("offer_id", payload.customData.offer_id ?? "");
634    url.searchParams.set("price_id", externalTermId);
635    url.searchParams.set("product_id", externalProductId);
636    url.searchParams.set("coupon_id", payload.customData.coupon_id ?? "");
637    url.searchParams.set("description", payload.customData.product_description ?? "");
638    const storedRefer = getStoredReferUrl();
639    if (storedRefer) {
640      url.searchParams.set("refer_url", encodeURIComponent(storedRefer));
641    } else {
642      url.searchParams.set("refer_url", encodeURIComponent(window.location.href));
643    }
644    url.searchParams.set("allow_shipping", payload.customData.allow_shipping ?? "false");
645    url.searchParams.set("free_gift", payload.customData.free_gift ?? "");
646    url.searchParams.set("transaction_type", payload.customData.transaction_type ?? "");
647    url.searchParams.set("request_phone_number", payload.customData.request_phone_number ?? "");
648    url.searchParams.set("p_mode", payload.customData.p_mode ?? "");
649
650
651    // NEW (minimal): capture before leaving this page
652    captureTrackingParamsOnce();
653
654    window.location.href = url.toString();
655  }
656
657  tp.push(["setLinkedTermSelected", onLinkedTermSelected]);
658
659  tp.push([
660    "init",
661    function () {
662      tp.pianoId.init({ confirmation: "none" });
663
664      if (tp.pianoId.isUserValid()) {
665        document.body.classList.add("coeditor-logged-in");
666      }
667
668      tp.experience.init();
669    },
670  ]);
671
672  await handleCheckoutSession();
673
674  const urlParams = new URLSearchParams(window.location.search);
675  const nonce = urlParams.get("__nonce");
676  const checkoutSessionId = urlParams.get("checkout_session_id");
677
678  if (checkoutSessionId !== null && (nonce === null || nonce === "false")) {
679    urlParams.set("__nonce", "true");
680    const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
681    window.history.replaceState({}, "", newUrl);
682  }
683
684  if (checkoutSessionId !== null && !referRedirectStarted) {
685    loginUser();
686  }
687
688  initialize();
689
690  async function initialize() {
691    const urlParams = new URLSearchParams(window.location.search);
692
693    if (urlParams.get("session_id") === null) {
694      console.log("No session_id provided");
695      return;
696    } else {
697      let stripePublicKey = null;
698      let stripeAccountId = null;
699
700      const getCookie = (name) => {
701        const value = `; ${document.cookie}`;
702        const parts = value.split(`; ${name}=`);
703        if (parts.length === 2) return parts.pop().split(";").shift();
704      };
705
706      const decodeJWT = (token) => {
707        try {
708          const [header, payload, signature] = token.split(".");
709          const decodedPayload = JSON.parse(
710            atob(payload.replace(/-/g, "+").replace(/_/g, "/")),
711          );
712          return decodedPayload;
713        } catch (error) {
714          console.error("Failed to decode JWT:", error);
715          return null;
716        }
717      };
718
719      const fetchClientSecret = async () => {
720        const utpCookie = getCookie("__utp");
721        const offer = localStorage.getItem("templateId");
722
723        const product = {
724          price_id: urlParams.get("price_id"),
725          term_id: urlParams.get("term_id"),
726          product_id: urlParams.get("product_id"),
727          session_id: urlParams.get("session_id"),
728          allow_billing_address: urlParams.get("allow_shipping"),
729          coupon: urlParams.get("coupon"),
730          coupon_id: urlParams.get("coupon_id"),
731          description: urlParams.get("description"),
732          refer_url: getCheckoutReferUrl(),
733          refer_url_from_storage: Boolean(getStoredReferUrl()),
734          free_gift: urlParams.get("free_gift"),
735          transaction_type: urlParams.get("transaction_type"),
736          request_phone_number: urlParams.get("request_phone_number"),
737          p_mode: urlParams.get("p_mode"),
738          offer_fields: {},
739        };
740
741		console.log(product);
742        const prefix = "offerFields_";
743        for (let i = 0; i < localStorage.length; i++) {
744          const key = localStorage.key(i);
745          if (key.startsWith(prefix)) {
746            const fieldId = key.replace(prefix, "");
747            product.offer_fields[fieldId] = localStorage.getItem(key);
748          }
749        }
750
751        // Optional: include tracking params on create-session too
752        product.tracking_params = getTrackingParams();
753
754        const subsAttribution = getSubsAttribution();
755        if (subsAttribution) {
756          product.subs_attribution = subsAttribution;
757          persistSubsAttributionCookie(subsAttribution);
758        }
759
760        const fbcCookie = getCookie("_fbc");
761        const fpcCookie = getCookie("_fpc");
762        if (fbcCookie || fpcCookie) {
763          product.tracking_cookies = {};
764          if (fbcCookie) product.tracking_cookies._fbc = fbcCookie;
765          if (fpcCookie) product.tracking_cookies._fpc = fpcCookie;
766        }
767
768        if (utpCookie) {
769          const decodedPayload = decodeJWT(utpCookie);
770          if (decodedPayload?.email) {
771            product.customer_email = decodedPayload.email;
772          }
773        }
774
775        const cleanBaseUrl = stripQueryParams(config.baseUrl);
776
777        const response = await fetch(
778          `${cleanBaseUrl}/wp-json/coeditor-connector/v1/stripe-create-session`,
779          {
780            method: "POST",
781            headers: { "Content-Type": "application/json" },
782            body: JSON.stringify(product),
783          },
784        );
785
786        const result = await response.json();
787
788        stripePublicKey = result.public_key;
789        stripeAccountId = result.account_id;
790
791        return [
792          {
793            clientSecret: result.clientSecret,
794            publicKey: result.public_key,
795            accountId: result.account_id,
796          },
797        ];
798      };
799
800      const [{ clientSecret, publicKey, accountId }] =
801        await fetchClientSecret();
802
803      const stripe = accountId
804        ? Stripe(stripePublicKey || config.stripeKey, { stripeAccount: accountId })
805        : Stripe(stripePublicKey || config.stripeKey);
806
807      const checkout = await stripe.initEmbeddedCheckout({
808        clientSecret,
809      });
810
811      checkout.mount("#checkout");
812    }
813  }
814});
815
816(function ($) {
817  "use strict";
818
819  $(document).ready(function () {
820    $(document).on("click", ".do_cross_sell", function (e) {
821      e.preventDefault();
822
823      let data;
824
825      const urlParams = new URLSearchParams(window.location.search);
826
827      const email = this.getAttribute("data-email");
828      if (!email) return alert("Missing email.");
829
830      const productId = this.getAttribute("data-product");
831      const publication = this.getAttribute("data-publication");
832
833      jQuery.ajax({
834        type: "POST",
835        url: coeditorConnectorConfig.ajax_url,
836        data: {
837          action: "cross_sell_user",
838          session: urlParams.get("checkout_session_id"),
839          stripe_account: urlParams.get("sid"),
840          productId: productId,
841          publication: publication,
842          nonce: coeditorConnectorConfig.nonce,
843        },
844        success: function (response) {
845          document.getElementById("cross_sell_wrapper").innerHTML = "";
846          data = typeof response === "string" ? JSON.parse(response) : response;
847          if (data.success) {
848            document.getElementById("cross_sell_wrapper").innerHTML =
849              data.rendered_template;
850            if (window.coeditorReferRedirect) {
851              window.coeditorReferRedirect.redirectToPendingReferUrl();
852            }
853          }
854        },
855        error: function (err) {
856          console.error("AJAX error:", err);
857        },
858      });
859    });
860  });
861})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.