@vio-live/web-sdk 0.26.1 → 0.28.0

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.
@@ -2917,6 +2917,160 @@ function lineOrigins(line) {
2917
2917
  return copy.origins;
2918
2918
  }
2919
2919
 
2920
+ // src/core/sponsor-identity.ts
2921
+ var CHECKOUT_SPONSOR_STORAGE_KEY = "vio.checkoutSponsor.v1";
2922
+ var CHECKOUT_SPONSOR_MEMORY_MAX = 20;
2923
+ function toSponsorId(value) {
2924
+ if (value === null || value === void 0 || value === "") return void 0;
2925
+ const n = typeof value === "number" ? value : Number(String(value).trim());
2926
+ return Number.isInteger(n) && n > 0 ? n : void 0;
2927
+ }
2928
+ function urlOrNull(value) {
2929
+ if (typeof value !== "string") return null;
2930
+ const v = value.trim();
2931
+ return v ? v : null;
2932
+ }
2933
+ function pageUrlOrNull(value) {
2934
+ const v = urlOrNull(value);
2935
+ if (!v) return null;
2936
+ try {
2937
+ return new URL(v).protocol === "https:" ? v : null;
2938
+ } catch {
2939
+ return null;
2940
+ }
2941
+ }
2942
+ function nameOf(value) {
2943
+ return typeof value === "string" ? value.trim() : "";
2944
+ }
2945
+ function findSponsorIn(boot, sponsorId) {
2946
+ const id = toSponsorId(sponsorId);
2947
+ if (id === void 0 || !boot || typeof boot !== "object") return void 0;
2948
+ const match = (s) => !!s && typeof s === "object" && Number(s.id) === id;
2949
+ if (match(boot.primarySponsor)) return boot.primarySponsor;
2950
+ const secondary = Array.isArray(boot.secondarySponsors) ? boot.secondarySponsors.find(match) : void 0;
2951
+ if (secondary) return secondary;
2952
+ const campaigns = Array.isArray(boot.campaigns) ? boot.campaigns : [];
2953
+ for (const c of campaigns) {
2954
+ if (!c || typeof c !== "object") continue;
2955
+ if (match(c.primarySponsor)) return c.primarySponsor;
2956
+ const s = Array.isArray(c.secondarySponsors) ? c.secondarySponsors.find(match) : void 0;
2957
+ if (s) return s;
2958
+ }
2959
+ return void 0;
2960
+ }
2961
+ function sponsorIdentityFrom(boot, sponsorId) {
2962
+ try {
2963
+ const id = toSponsorId(sponsorId);
2964
+ if (id === void 0) return null;
2965
+ const sponsor = findSponsorIn(boot, id);
2966
+ if (!sponsor) return null;
2967
+ const name = nameOf(sponsor.name);
2968
+ const logoUrl = urlOrNull(sponsor.logoUrl);
2969
+ if (!name && !logoUrl) return null;
2970
+ const termsUrl = pageUrlOrNull(sponsor.termsUrl);
2971
+ const refundPolicyUrl = pageUrlOrNull(sponsor.refundPolicyUrl);
2972
+ return {
2973
+ id,
2974
+ name,
2975
+ logoUrl,
2976
+ avatarUrl: urlOrNull(sponsor.avatarUrl),
2977
+ ...termsUrl ? { termsUrl } : {},
2978
+ ...refundPolicyUrl ? { refundPolicyUrl } : {}
2979
+ };
2980
+ } catch {
2981
+ return null;
2982
+ }
2983
+ }
2984
+ function checkoutSponsorOf(identity) {
2985
+ if (!identity) return void 0;
2986
+ return { id: identity.id, name: identity.name, logoUrl: identity.logoUrl ?? null };
2987
+ }
2988
+ var inMemory = [];
2989
+ var storageRefusedWrite = false;
2990
+ function sessionStore() {
2991
+ try {
2992
+ const s = globalThis.sessionStorage;
2993
+ return s && typeof s.getItem === "function" && typeof s.setItem === "function" ? s : null;
2994
+ } catch {
2995
+ return null;
2996
+ }
2997
+ }
2998
+ function normalizeEntry(raw) {
2999
+ if (!raw || typeof raw !== "object") return null;
3000
+ const r = raw;
3001
+ const checkoutId = typeof r.checkoutId === "string" ? r.checkoutId.trim() : "";
3002
+ const sponsorId = toSponsorId(r.sponsorId);
3003
+ if (!checkoutId || sponsorId === void 0) return null;
3004
+ return { checkoutId, sponsorId, name: nameOf(r.name), logoUrl: urlOrNull(r.logoUrl) };
3005
+ }
3006
+ function readEntries() {
3007
+ const store = storageRefusedWrite ? null : sessionStore();
3008
+ if (store) {
3009
+ try {
3010
+ const raw = store.getItem(CHECKOUT_SPONSOR_STORAGE_KEY);
3011
+ if (!raw) return [];
3012
+ const parsed = JSON.parse(raw);
3013
+ if (Array.isArray(parsed)) {
3014
+ return parsed.map(normalizeEntry).filter((e) => e !== null);
3015
+ }
3016
+ } catch {
3017
+ }
3018
+ }
3019
+ return inMemory.slice();
3020
+ }
3021
+ function writeEntries(entries) {
3022
+ inMemory = entries.slice();
3023
+ const store = sessionStore();
3024
+ if (!store) return;
3025
+ try {
3026
+ store.setItem(CHECKOUT_SPONSOR_STORAGE_KEY, JSON.stringify(entries));
3027
+ } catch {
3028
+ storageRefusedWrite = true;
3029
+ }
3030
+ }
3031
+ function rememberCheckoutSponsor(checkoutId, sponsor) {
3032
+ try {
3033
+ const id = typeof checkoutId === "string" ? checkoutId.trim() : "";
3034
+ const sponsorId = toSponsorId(sponsor?.sponsorId);
3035
+ if (!id || sponsorId === void 0) return;
3036
+ const entries = readEntries();
3037
+ const previous = entries.find((e) => e.checkoutId === id);
3038
+ let name = nameOf(sponsor.name);
3039
+ let logoUrl = urlOrNull(sponsor.logoUrl);
3040
+ if (previous && previous.sponsorId === sponsorId && !name && !logoUrl) {
3041
+ name = previous.name;
3042
+ logoUrl = previous.logoUrl;
3043
+ }
3044
+ const next = entries.filter((e) => e.checkoutId !== id);
3045
+ next.push({ checkoutId: id, sponsorId, name, logoUrl });
3046
+ writeEntries(next.slice(-CHECKOUT_SPONSOR_MEMORY_MAX));
3047
+ } catch {
3048
+ }
3049
+ }
3050
+ function rememberedCheckoutSponsor(checkoutId) {
3051
+ try {
3052
+ const id = typeof checkoutId === "string" ? checkoutId.trim() : "";
3053
+ if (!id) return null;
3054
+ return readEntries().find((e) => e.checkoutId === id) ?? null;
3055
+ } catch {
3056
+ return null;
3057
+ }
3058
+ }
3059
+ function rememberedSponsor(sponsorId) {
3060
+ try {
3061
+ const id = toSponsorId(sponsorId);
3062
+ if (id === void 0) return null;
3063
+ const entries = readEntries();
3064
+ for (let i = entries.length - 1; i >= 0; i--) {
3065
+ const e = entries[i];
3066
+ if (e.sponsorId === id && (e.name || e.logoUrl)) return e;
3067
+ }
3068
+ return null;
3069
+ } catch {
3070
+ return null;
3071
+ }
3072
+ }
3073
+
2920
3074
  // src/core/checkout/payments/apple-pay.ts
2921
3075
  var STRIPE_JS_URL = "https://js.stripe.com/v3";
2922
3076
  var stripeJsPromise = null;
@@ -3113,7 +3267,7 @@ async function prepareApplePay(config) {
3113
3267
  }
3114
3268
 
3115
3269
  // src/core/version.ts
3116
- var SDK_VERSION = "0.25.0";
3270
+ var SDK_VERSION = "0.28.0";
3117
3271
 
3118
3272
  // src/core/checkout/payments/stripe-embedded.ts
3119
3273
  var CREATE_PAYMENT_INTENT_STRIPE_MUTATION = `
@@ -4614,7 +4768,9 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4614
4768
  super();
4615
4769
  this.cartManager = cartManager;
4616
4770
  this.deps = deps;
4617
- this.state = null;
4771
+ this.currentStateValue = null;
4772
+ /** The last checkout + identity written to the memory — the state is set on every keystroke of the form. */
4773
+ this.lastRememberedSponsor = "";
4618
4774
  /** Counts `open()` calls — see CheckoutState.session. */
4619
4775
  this.sessionSeq = 0;
4620
4776
  this.applePayAvailableCache = null;
@@ -4658,6 +4814,18 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4658
4814
  this.kustomController = null;
4659
4815
  preloadStripeJs();
4660
4816
  }
4817
+ /**
4818
+ * Every write of the state goes through here: a state that names its
4819
+ * backend checkout is remembered with its sponsor (see
4820
+ * `rememberSponsorOfCheckout`), whatever path created the checkout.
4821
+ */
4822
+ get state() {
4823
+ return this.currentStateValue;
4824
+ }
4825
+ set state(next) {
4826
+ this.currentStateValue = next;
4827
+ if (next?.checkoutId && next.sponsorId) this.rememberSponsorOfCheckout(next.checkoutId, next.sponsorId);
4828
+ }
4661
4829
  open(sponsorId) {
4662
4830
  const cart = this.cartManager.getCart(sponsorId);
4663
4831
  if (!cart || cart.items.length === 0) {
@@ -4776,21 +4944,81 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4776
4944
  * `buyer_id`. Never throws; `null` for whatever the API does not say.
4777
4945
  */
4778
4946
  async getOrderReference(checkoutId, sponsorId) {
4779
- if (!checkoutId) return { orderNumber: null, buyerRef: null };
4780
- const inFlight = this.referenceInFlight.get(checkoutId);
4781
- if (inFlight) return inFlight;
4782
- const request = (async () => {
4947
+ if (!checkoutId) return this.withSponsor({ orderNumber: null, buyerRef: null }, checkoutId, sponsorId);
4948
+ let request = this.referenceInFlight.get(checkoutId);
4949
+ if (!request) {
4950
+ const asked = (async () => {
4951
+ try {
4952
+ const opts = await getCartGraphQLOptions(
4953
+ sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId
4954
+ );
4955
+ return await getCheckoutReference(checkoutId, opts);
4956
+ } catch {
4957
+ return { orderNumber: null, buyerRef: null };
4958
+ } finally {
4959
+ this.referenceInFlight.delete(checkoutId);
4960
+ }
4961
+ })();
4962
+ this.referenceInFlight.set(checkoutId, asked);
4963
+ request = asked;
4964
+ }
4965
+ return this.withSponsor(await request, checkoutId, sponsorId);
4966
+ }
4967
+ /**
4968
+ * The sponsor of a checkout as `getOrderReference` carries it
4969
+ * (`{ id, name, logoUrl }`): the given sponsor, else the one this tab
4970
+ * remembered for the checkout, else the open checkout's when it IS that
4971
+ * checkout — named by the `Vio.sponsorIdentity` rule. `undefined` when
4972
+ * nothing names it; never throws.
4973
+ */
4974
+ checkoutSponsor(checkoutId, sponsorId) {
4975
+ try {
4976
+ const id = sponsorId ?? this.sponsorIdOfCheckout(checkoutId ?? void 0);
4977
+ const numeric = id === void 0 || id === null || id === "" ? void 0 : Number(id);
4978
+ if (typeof this.deps.sponsorIdentity === "function") {
4979
+ return checkoutSponsorOf(this.deps.sponsorIdentity(numeric, { checkoutId: checkoutId ?? void 0 }));
4980
+ }
4981
+ const memo = rememberedCheckoutSponsor(checkoutId ?? void 0);
4982
+ if (memo && (numeric === void 0 || memo.sponsorId === numeric) && (memo.name || memo.logoUrl)) {
4983
+ return { id: memo.sponsorId, name: memo.name, logoUrl: memo.logoUrl };
4984
+ }
4985
+ return void 0;
4986
+ } catch {
4987
+ return void 0;
4988
+ }
4989
+ }
4990
+ /** The sponsor id of a checkout: this tab's memory of it, else the open checkout's when it is that one. */
4991
+ sponsorIdOfCheckout(checkoutId) {
4992
+ if (!checkoutId) return void 0;
4993
+ const memo = rememberedCheckoutSponsor(checkoutId);
4994
+ if (memo) return memo.sponsorId;
4995
+ return this.state?.checkoutId === checkoutId ? this.state.sponsorId : void 0;
4996
+ }
4997
+ withSponsor(reference, checkoutId, sponsorId) {
4998
+ const sponsor = this.checkoutSponsor(checkoutId || void 0, sponsorId);
4999
+ return sponsor ? { ...reference, sponsor } : reference;
5000
+ }
5001
+ /**
5002
+ * Remember whose checkout this is (`rememberCheckoutSponsor`), with the
5003
+ * sponsor's name and logo as known now — so a confirmation after a redirect
5004
+ * or a reload can name the brand. Written again only when something changed.
5005
+ */
5006
+ rememberSponsorOfCheckout(checkoutId, sponsorId) {
5007
+ try {
5008
+ let identity = null;
4783
5009
  try {
4784
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
4785
- return await getCheckoutReference(checkoutId, opts);
5010
+ identity = this.deps.sponsorIdentity?.(sponsorId, { checkoutId }) ?? null;
4786
5011
  } catch {
4787
- return { orderNumber: null, buyerRef: null };
4788
- } finally {
4789
- this.referenceInFlight.delete(checkoutId);
4790
- }
4791
- })();
4792
- this.referenceInFlight.set(checkoutId, request);
4793
- return request;
5012
+ identity = null;
5013
+ }
5014
+ const name = identity?.name || this.cartManager.getCart(sponsorId)?.sponsorName || "";
5015
+ const logoUrl = identity?.logoUrl ?? null;
5016
+ const key = JSON.stringify([checkoutId, sponsorId, name, logoUrl]);
5017
+ if (key === this.lastRememberedSponsor) return;
5018
+ this.lastRememberedSponsor = key;
5019
+ rememberCheckoutSponsor(checkoutId, { sponsorId, name, logoUrl });
5020
+ } catch {
5021
+ }
4794
5022
  }
4795
5023
  /**
4796
5024
  * Payment methods enabled for the sponsor (backend-configured). Memoised per
@@ -5940,7 +6168,12 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5940
6168
  );
5941
6169
  }
5942
6170
  const buyerRef = normalizeBuyerRef(confirmRes?.[BUYER_REF_FIELD]);
5943
- return { orderId: confirmRes?.order_id, status: confirmRes?.status, ...buyerRef ? { buyerRef } : {} };
6171
+ return {
6172
+ orderId: confirmRes?.order_id,
6173
+ status: confirmRes?.status,
6174
+ checkoutId,
6175
+ ...buyerRef ? { buyerRef } : {}
6176
+ };
5944
6177
  };
5945
6178
  }
5946
6179
  static {
@@ -6769,6 +7002,8 @@ function hasExplicitCampaign(lines) {
6769
7002
  }
6770
7003
 
6771
7004
  // src/core/analytics/analytics-manager.ts
7005
+ var ORDER_NUMBER_BACKOFF_MS = [500, 1e3, 2e3, 3e3, 3e3];
7006
+ var ORDER_NUMBER_MAX_WAIT_MS = 1e4;
6772
7007
  var SESSION_TTL_MS = 30 * 60 * 1e3;
6773
7008
  var FLUSH_INTERVAL_MS = 5e3;
6774
7009
  var FLUSH_AT_COUNT = 20;
@@ -6803,6 +7038,12 @@ var AnalyticsManager = class extends EventTarget {
6803
7038
  this.client = null;
6804
7039
  /** The landing it was read from — persisted later when consent arrives after the first event. */
6805
7040
  this.landing = null;
7041
+ /** Checkouts (`c:<id>`) and orders (`o:<number>`) this page load reported a `purchase` for: one each. */
7042
+ this.purchased = /* @__PURE__ */ new Set();
7043
+ /** Purchases still waiting for Vio's order number: each sends at once, with what it has, when called. */
7044
+ this.pendingPurchases = /* @__PURE__ */ new Set();
7045
+ /** Between `pagehide` and `pageshow` (bfcache): nothing waits, it all goes out now. */
7046
+ this.pageHiding = false;
6806
7047
  this.teardown = [];
6807
7048
  }
6808
7049
  /** The landing this manager resolved on this page load (the first event's), or null before it; read only — for the order's entry context when nothing is stored yet. */
@@ -6852,15 +7093,32 @@ var AnalyticsManager = class extends EventTarget {
6852
7093
  this.ensureSession();
6853
7094
  this.flushTimer = setInterval(() => void this.flush(), FLUSH_INTERVAL_MS);
6854
7095
  const onPageHide = () => {
7096
+ this.pageHiding = true;
7097
+ this.settlePendingPurchases();
6855
7098
  this.trackSessionEnd();
6856
7099
  this.flushWithBeacon();
6857
7100
  };
7101
+ const onPageShow = () => {
7102
+ this.pageHiding = false;
7103
+ };
6858
7104
  window.addEventListener("pagehide", onPageHide);
7105
+ window.addEventListener("pageshow", onPageShow);
6859
7106
  this.teardown.push(() => window.removeEventListener("pagehide", onPageHide));
7107
+ this.teardown.push(() => window.removeEventListener("pageshow", onPageShow));
7108
+ if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
7109
+ const onVisibility = () => {
7110
+ if (document.visibilityState !== "hidden" || this.pendingPurchases.size === 0) return;
7111
+ this.settlePendingPurchases();
7112
+ this.flushWithBeacon();
7113
+ };
7114
+ document.addEventListener("visibilitychange", onVisibility);
7115
+ this.teardown.push(() => document.removeEventListener("visibilitychange", onVisibility));
7116
+ }
6860
7117
  if (this.options.autoTrack) this.installAutoListeners();
6861
7118
  }
6862
- /** Stop timers + listeners (tests / SPA unmount). Queue survives. */
7119
+ /** Stop timers + listeners (tests / SPA unmount). Queue survives — a purchase still waiting for its order number joins it now. */
6863
7120
  stop() {
7121
+ this.settlePendingPurchases();
6864
7122
  if (this.flushTimer) clearInterval(this.flushTimer);
6865
7123
  this.flushTimer = null;
6866
7124
  for (const fn of this.teardown.splice(0)) fn();
@@ -6873,7 +7131,10 @@ var AnalyticsManager = class extends EventTarget {
6873
7131
  /** Queue one event. Works pre-init and pre-start (SSR: no-op). */
6874
7132
  track(name, opts = {}) {
6875
7133
  if (!hasDom()) return;
6876
- const event = this.buildEvent(name, opts);
7134
+ this.record(this.buildEvent(name, opts));
7135
+ }
7136
+ /** The sinks and the queue for one built event — `track`'s second half. */
7137
+ record(event) {
6877
7138
  if (!event) return;
6878
7139
  if (this.options.debug) {
6879
7140
  console.debug("[Vio.analytics]", event.name, event);
@@ -7020,6 +7281,8 @@ var AnalyticsManager = class extends EventTarget {
7020
7281
  value: com.value,
7021
7282
  currency: com.currency,
7022
7283
  order_id: com.orderId,
7284
+ // A uuid always fits; anything over the collector's 128 is left out, not cut.
7285
+ checkout_id: fitsId(com.checkoutId),
7023
7286
  payment_method: com.paymentMethod,
7024
7287
  // Only a reference survives — never an email, whatever a host passes —
7025
7288
  // and only with consent to persist: a visit measured with ephemeral
@@ -7259,32 +7522,135 @@ var AnalyticsManager = class extends EventTarget {
7259
7522
  });
7260
7523
  on("vio:payment-success", (ev) => {
7261
7524
  const d = ev.detail ?? {};
7525
+ const ids = purchaseIdsOf(d);
7526
+ if (!this.claimPurchase(ids)) return;
7262
7527
  const sponsorId = d.sponsorId !== void 0 ? Number(d.sponsorId) : void 0;
7263
7528
  const base = { sponsorId, campaignId: campaignOf(d), slot: slotOf(d), ...contentOf(d) };
7264
7529
  const { slices, explicit } = this.cartSlices(sponsorId);
7265
- if (slices.length > 1) {
7266
- for (const slice of slices) {
7267
- this.track("purchase", {
7268
- context: this.sliceContext(slice, base),
7269
- commerce: { ...sliceCommerce(slice), ...purchaseFromPaymentSuccess(d), paymentMethod: d.method },
7270
- props: { slice: slice.slice, slices: slice.slices }
7271
- });
7272
- }
7273
- return;
7274
- }
7275
- const snap = this.cartCommerce(sponsorId);
7276
- this.track("purchase", {
7277
- context: this.singleSliceContext(slices[0], explicit, base),
7278
- commerce: {
7530
+ const parts = slices.length > 1 ? (
7531
+ // Several campaigns paid in one go: one purchase per campaign — its
7532
+ // items and value, the SAME order_id — so each campaign's revenue is
7533
+ // its own and the order still adds up. `props.slice/slices` lets the
7534
+ // collector count the order once.
7535
+ slices.map((slice) => ({
7536
+ context: this.sliceContext(slice, base),
7537
+ commerce: { ...sliceCommerce(slice), paymentMethod: d.method },
7538
+ props: { slice: slice.slice, slices: slice.slices }
7539
+ }))
7540
+ ) : [
7541
+ {
7542
+ context: this.singleSliceContext(slices[0], explicit, base),
7279
7543
  // value / currency / items: the cart snapshot, on every path.
7280
- ...snap ?? {},
7281
- // order_id: Vio's id for the purchase (never the provider's).
7282
- ...purchaseFromPaymentSuccess(d),
7283
- paymentMethod: d.method
7544
+ commerce: { ...this.cartCommerce(sponsorId) ?? {}, paymentMethod: d.method }
7284
7545
  }
7285
- });
7546
+ ];
7547
+ this.reportPurchase(parts, ids, buyerRefOf(d), sponsorId);
7286
7548
  });
7287
7549
  }
7550
+ /**
7551
+ * Claims the purchase of a payment for this page load: false when its
7552
+ * checkout (or its order) already has one, reported or waiting.
7553
+ */
7554
+ claimPurchase(ids) {
7555
+ const keys = [];
7556
+ if (ids.checkoutId) keys.push(`c:${ids.checkoutId}`);
7557
+ if (ids.orderIdKind === "order" && ids.orderId) keys.push(`o:${ids.orderId}`);
7558
+ if (keys.some((key) => this.purchased.has(key))) return false;
7559
+ for (const key of keys) this.purchased.add(key);
7560
+ return true;
7561
+ }
7562
+ /**
7563
+ * Sends the `purchase` event(s) of one payment — ONCE, with Vio's order
7564
+ * number as `order_id` (`props.order_id_kind: 'order'`) and the checkout id
7565
+ * as `checkout_id`. When the payment event did not bring the number (a
7566
+ * redirect return, an order a webhook creates) the checkout is asked for it
7567
+ * — the receipt's own path, `Vio.checkout.getOrderReference` — at once and
7568
+ * after 0.5 / 1 / 2 / 3 / 3 s, for at most 10 s; nothing else waits for it.
7569
+ * Unanswered, or when the page hides or `stop()` comes first, the events go
7570
+ * out with what they have: the checkout id as `order_id`
7571
+ * (`order_id_kind: 'checkout'`), as before — and the late number adds no
7572
+ * second event. The events are BUILT at the payment — its moment (`ts`),
7573
+ * `event_id`, session, page and content — and only the order number (and a
7574
+ * buyer reference the payment did not bring) is filled in when it comes.
7575
+ */
7576
+ reportPurchase(parts, ids, buyerId, sponsorId) {
7577
+ const events = parts.map(
7578
+ (part) => this.buildEvent("purchase", {
7579
+ ...part,
7580
+ commerce: {
7581
+ ...part.commerce,
7582
+ ...ids.orderId !== void 0 ? { orderId: ids.orderId } : {},
7583
+ ...ids.checkoutId !== void 0 ? { checkoutId: ids.checkoutId } : {},
7584
+ ...buyerId !== void 0 ? { buyerId } : {}
7585
+ },
7586
+ ...ids.orderIdKind ? { props: { ...part.props, order_id_kind: ids.orderIdKind } } : {}
7587
+ })
7588
+ );
7589
+ const send = (found = {}) => {
7590
+ if (found.orderNumber) this.purchased.add(`o:${found.orderNumber}`);
7591
+ const lateBuyer = this.canPersist ? normalizeBuyerRef(found.buyerRef) : void 0;
7592
+ for (const event of events) {
7593
+ if (!event) continue;
7594
+ if (found.orderNumber) {
7595
+ event.commerce = { ...event.commerce, order_id: found.orderNumber };
7596
+ event.props = { ...event.props, order_id_kind: "order" };
7597
+ }
7598
+ if (lateBuyer && event.commerce && event.commerce.buyer_id === void 0) {
7599
+ event.commerce = { ...event.commerce, buyer_id: lateBuyer };
7600
+ }
7601
+ this.record(event);
7602
+ }
7603
+ };
7604
+ const ask = ids.orderIdKind === "checkout" ? this.orderReferenceAsker(ids.checkoutId, sponsorId) : null;
7605
+ if (!ask) {
7606
+ send();
7607
+ return;
7608
+ }
7609
+ if (this.isPageHidden()) {
7610
+ send();
7611
+ this.flushWithBeacon();
7612
+ return;
7613
+ }
7614
+ let done = false;
7615
+ const settle = (found) => {
7616
+ if (done) return;
7617
+ done = true;
7618
+ this.pendingPurchases.delete(settleNow);
7619
+ send(found);
7620
+ };
7621
+ const settleNow = () => settle();
7622
+ this.pendingPurchases.add(settleNow);
7623
+ void waitForOrderNumber(ask, () => done).then(settle, () => settle());
7624
+ }
7625
+ /** Sends every purchase still waiting for its order number, at once, with what it has. */
7626
+ settlePendingPurchases() {
7627
+ for (const settle of [...this.pendingPurchases]) settle();
7628
+ }
7629
+ /** Whether the page is hidden or going away (`pagehide` until `pageshow`). */
7630
+ isPageHidden() {
7631
+ if (this.pageHiding) return true;
7632
+ try {
7633
+ return typeof document !== "undefined" && document.visibilityState === "hidden";
7634
+ } catch {
7635
+ return false;
7636
+ }
7637
+ }
7638
+ /**
7639
+ * How a purchase asks a checkout for Vio's order number: through the SDK's
7640
+ * checkout manager (`getOrderReference`, which never throws and shares its
7641
+ * in-flight ask with the receipt's). `null` when there is nothing to ask —
7642
+ * no checkout id, or no checkout manager on this page.
7643
+ */
7644
+ orderReferenceAsker(checkoutId, sponsorId) {
7645
+ if (!checkoutId) return null;
7646
+ try {
7647
+ const checkout = vioFacade()?.checkout;
7648
+ if (!checkout || typeof checkout.getOrderReference !== "function") return null;
7649
+ return () => checkout.getOrderReference(checkoutId, sponsorId || void 0);
7650
+ } catch {
7651
+ return null;
7652
+ }
7653
+ }
7288
7654
  /** The lines of the cart (current, else the pre-clear snapshot), optionally one sponsor's. */
7289
7655
  cartLines(sponsorId) {
7290
7656
  let items = [];
@@ -7518,15 +7884,63 @@ function buyerRefOf(d) {
7518
7884
  }
7519
7885
  return void 0;
7520
7886
  }
7521
- function purchaseFromPaymentSuccess(d) {
7887
+ function purchaseIdsOf(d) {
7888
+ if (!d) return {};
7522
7889
  const r = d.result ?? {};
7523
- const orderId = firstId(d.orderId, r.confirmation?.orderId, d.checkoutId);
7890
+ const legacy = firstId(d.orderId);
7891
+ const legacyIsNumber = legacy !== void 0 && isOrderNumber(legacy);
7892
+ const orderNumber = firstId(d.orderNumber, r.confirmation?.orderId, legacyIsNumber ? legacy : void 0);
7893
+ const checkoutId = firstId(legacyIsNumber ? void 0 : legacy, d.checkoutId, r.confirmation?.checkoutId);
7894
+ if (orderNumber !== void 0) {
7895
+ return { orderId: orderNumber, ...checkoutId !== void 0 ? { checkoutId } : {}, orderIdKind: "order" };
7896
+ }
7897
+ if (checkoutId !== void 0) return { orderId: checkoutId, checkoutId, orderIdKind: "checkout" };
7898
+ return {};
7899
+ }
7900
+ function purchaseFromPaymentSuccess(d) {
7901
+ const { orderId, checkoutId } = purchaseIdsOf(d);
7524
7902
  const buyerId = buyerRefOf(d);
7525
7903
  return {
7526
7904
  ...orderId !== void 0 ? { orderId } : {},
7905
+ ...checkoutId !== void 0 ? { checkoutId } : {},
7527
7906
  ...buyerId !== void 0 ? { buyerId } : {}
7528
7907
  };
7529
7908
  }
7909
+ function isOrderNumber(value) {
7910
+ return /^[1-9][0-9]*$/.test(value);
7911
+ }
7912
+ async function waitForOrderNumber(ask, stopped) {
7913
+ let deadline;
7914
+ const timeUp = new Promise((resolve) => {
7915
+ deadline = setTimeout(() => resolve(null), ORDER_NUMBER_MAX_WAIT_MS);
7916
+ });
7917
+ let buyerRef;
7918
+ const found = (orderNumber) => ({
7919
+ ...orderNumber ? { orderNumber } : {},
7920
+ ...buyerRef ? { buyerRef } : {}
7921
+ });
7922
+ try {
7923
+ for (let attempt = 0; attempt <= ORDER_NUMBER_BACKOFF_MS.length; attempt++) {
7924
+ if (attempt > 0) {
7925
+ const wait = ORDER_NUMBER_BACKOFF_MS[attempt - 1];
7926
+ const slept = await Promise.race([new Promise((resolve) => setTimeout(() => resolve(true), wait)), timeUp]);
7927
+ if (slept === null) break;
7928
+ }
7929
+ if (stopped()) break;
7930
+ try {
7931
+ const reference = await Promise.race([ask(), timeUp]);
7932
+ if (reference === null) break;
7933
+ buyerRef ??= normalizeBuyerRef(reference?.buyerRef);
7934
+ const orderNumber = firstId(reference?.orderNumber);
7935
+ if (orderNumber !== void 0 && isOrderNumber(orderNumber)) return found(orderNumber);
7936
+ } catch {
7937
+ }
7938
+ }
7939
+ return found();
7940
+ } finally {
7941
+ clearTimeout(deadline);
7942
+ }
7943
+ }
7530
7944
  function firstId(...candidates) {
7531
7945
  for (const c of candidates) {
7532
7946
  if (c === void 0 || c === null) continue;
@@ -7744,7 +8158,8 @@ var VioFacade = class {
7744
8158
  get checkout() {
7745
8159
  if (!this.checkoutManager) {
7746
8160
  this.checkoutManager = new CheckoutManager(this.cart, {
7747
- orderAttribution: (sponsorId) => this.orderAttribution(sponsorId)
8161
+ orderAttribution: (sponsorId) => this.orderAttribution(sponsorId),
8162
+ sponsorIdentity: (sponsorId, opts) => this.sponsorIdentity(sponsorId, opts)
7748
8163
  });
7749
8164
  }
7750
8165
  return this.checkoutManager;
@@ -7805,6 +8220,39 @@ var VioFacade = class {
7805
8220
  get bootstrapCache() {
7806
8221
  return this.cachedBootstrap;
7807
8222
  }
8223
+ /**
8224
+ * Who a sponsor is to a shopper — `{ id, name, logoUrl, avatarUrl }` — by
8225
+ * the ONE rule every element uses (`sponsorIdentityFrom`): the live
8226
+ * bootstrap's sponsor blocks (the config's primary and secondaries, then
8227
+ * every `campaigns[]` primary and secondaries, first match); when the
8228
+ * bootstrap does not know the id (not loaded yet, a redirect return), what
8229
+ * this tab remembered of the checkout (`opts.checkoutId`, else the latest
8230
+ * checkout of that sponsor), then the cart's `sponsorName`. With only
8231
+ * `opts.checkoutId`, the sponsor is the one remembered for that checkout.
8232
+ * In a checkout pass the CHECKOUT's sponsor — never the page's
8233
+ * attribution. URLs as given; an empty logo is `null`. `null` when nothing
8234
+ * names it. Pure over local state, never throws.
8235
+ */
8236
+ sponsorIdentity(sponsorId, opts = {}) {
8237
+ try {
8238
+ let memo;
8239
+ const memoOfCheckout = () => memo === void 0 ? memo = rememberedCheckoutSponsor(opts?.checkoutId ?? void 0) : memo;
8240
+ const id = toSponsorId(sponsorId) ?? memoOfCheckout()?.sponsorId;
8241
+ if (id === void 0) return null;
8242
+ const fromBootstrap = sponsorIdentityFrom(this.cachedBootstrap, id);
8243
+ if (fromBootstrap) return fromBootstrap;
8244
+ const ofCheckout = memoOfCheckout();
8245
+ const remembered = (ofCheckout && ofCheckout.sponsorId === id && (ofCheckout.name || ofCheckout.logoUrl) ? ofCheckout : null) ?? rememberedSponsor(id);
8246
+ if (remembered) {
8247
+ return { id, name: remembered.name, logoUrl: remembered.logoUrl, avatarUrl: null };
8248
+ }
8249
+ const cartName = this.cart.getCart(id)?.sponsorName;
8250
+ const name = typeof cartName === "string" ? cartName.trim() : "";
8251
+ return name ? { id, name, logoUrl: null, avatarUrl: null } : null;
8252
+ } catch {
8253
+ return null;
8254
+ }
8255
+ }
7808
8256
  /**
7809
8257
  * Get a CommerceClient scoped to a specific sponsor (via bootstrap).
7810
8258
  * Throws if bootstrap hasn't been called or the sponsor is unknown.
@@ -8284,20 +8732,7 @@ var VioFacade = class {
8284
8732
  }, SLOT_DECLARATION_DEBOUNCE_MS);
8285
8733
  }
8286
8734
  findSponsor(sponsorId) {
8287
- const boot = this.cachedBootstrap;
8288
- if (!boot) return void 0;
8289
- const id = Number(sponsorId);
8290
- if (Number(boot.primarySponsor?.id) === id) return boot.primarySponsor;
8291
- const secondary = boot.secondarySponsors?.find((s) => Number(s.id) === id);
8292
- if (secondary) return secondary;
8293
- const campaigns = Array.isArray(boot.campaigns) ? boot.campaigns : [];
8294
- for (const c of campaigns) {
8295
- if (!c || typeof c !== "object") continue;
8296
- if (Number(c.primarySponsor?.id) === id) return c.primarySponsor ?? void 0;
8297
- const s = Array.isArray(c.secondarySponsors) ? c.secondarySponsors.find((x) => Number(x?.id) === id) : void 0;
8298
- if (s) return s;
8299
- }
8300
- return void 0;
8735
+ return findSponsorIn(this.cachedBootstrap, sponsorId);
8301
8736
  }
8302
8737
  };
8303
8738
  function warn(...args) {
@@ -8358,6 +8793,6 @@ function vippsButtonAttributes(verb = "buy", opts = {}) {
8358
8793
  };
8359
8794
  }
8360
8795
 
8361
- export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, BUYER_REF_MAX_LENGTH, CHECKOUT_ATTRIBUTION_CAPS, CLIENT_LANDING_URL_MAX_LENGTH, CONTENT_URL_MAX_LENGTH, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, GRAPHQL_INT_MAX, KUSTOM_RETURN_QUERY_KEYS, LINE_ORIGIN_CAPS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_ITEM_WITH_ORIGIN_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, activeCampaignEntry, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, buyerRefOf, campaignCandidates, capContentUrl, cartSlicesFrom, checkApplePayAvailability, checkoutAttributionOf, componentKindOf, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, dominantCampaignOf, ensureVippsButton, entryContextOf, executeCartGraphQL, formatPrice, gatewayHas, gatewayHasBuyerRef, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCheckoutReference, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, isUnknownFeatureError, isUnknownTokenError, kustomReturnOutcome, landingUrl, lineOriginWire, lineOrigins, nexiReturnPaymentId, normalizeBuyerRef, normalizeOrigin, normalizeScope, normalizeSlotId, normalizeSlotKind, originAndPath, primaryImageUrl, purchaseFromPaymentSuccess, readNexiPending, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveLanding, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature };
8362
- //# sourceMappingURL=chunk-NXIYIY2D.js.map
8363
- //# sourceMappingURL=chunk-NXIYIY2D.js.map
8796
+ export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, BUYER_REF_MAX_LENGTH, CHECKOUT_ATTRIBUTION_CAPS, CHECKOUT_SPONSOR_STORAGE_KEY, CLIENT_LANDING_URL_MAX_LENGTH, CONTENT_URL_MAX_LENGTH, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, GRAPHQL_INT_MAX, KUSTOM_RETURN_QUERY_KEYS, LINE_ORIGIN_CAPS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_ITEM_WITH_ORIGIN_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, activeCampaignEntry, addItem, adyenReturnOutcome, adyenReturnParams, autoSlotDisplayName, autoSlotId, buyerRefOf, campaignCandidates, capContentUrl, cartSlicesFrom, checkApplePayAvailability, checkoutAttributionOf, checkoutSponsorOf, componentKindOf, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, dominantCampaignOf, ensureVippsButton, entryContextOf, executeCartGraphQL, formatPrice, gatewayHas, gatewayHasBuyerRef, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCheckoutReference, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, isUnknownFeatureError, isUnknownTokenError, kustomReturnOutcome, landingUrl, lineOriginWire, lineOrigins, nexiReturnPaymentId, normalizeBuyerRef, normalizeOrigin, normalizeScope, normalizeSlotId, normalizeSlotKind, originAndPath, primaryImageUrl, purchaseFromPaymentSuccess, purchaseIdsOf, readNexiPending, rememberedCheckoutSponsor, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveLanding, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, sponsorIdentityFrom, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature };
8797
+ //# sourceMappingURL=chunk-AFJ5M53F.js.map
8798
+ //# sourceMappingURL=chunk-AFJ5M53F.js.map