@vio-live/web-sdk 0.29.0 → 0.33.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.
@@ -207,9 +207,9 @@ function slotDeclarationKey(location2, pageCampaignId) {
207
207
  const kind = normalizeSlotKind(location2.kind);
208
208
  return location2.id + (location2.displayName ? `${location2.displayName}` : "") + (campaignId !== void 0 ? `${campaignId}` : "") + (kind !== void 0 ? `${kind}` : "");
209
209
  }
210
- function pendingSlotKeys(memory, keys, now = Date.now(), ttl = SLOT_DECLARATION_TTL_MS) {
210
+ function pendingSlotKeys(memory2, keys, now = Date.now(), ttl = SLOT_DECLARATION_TTL_MS) {
211
211
  return keys.filter((key) => {
212
- const at = memory?.[key];
212
+ const at = memory2?.[key];
213
213
  if (typeof at !== "number" || !Number.isFinite(at)) return true;
214
214
  return now - at >= ttl || now < at;
215
215
  });
@@ -218,8 +218,8 @@ function slotKeyId(key) {
218
218
  const cut4 = key.search(/[\u001f\u001e\u001d]/);
219
219
  return cut4 >= 0 ? key.slice(0, cut4) : key;
220
220
  }
221
- function rememberSlotKeys(memory, keys, at, cap = SLOT_DECLARATION_MEMORY_CAP) {
222
- const next = { ...memory ?? {} };
221
+ function rememberSlotKeys(memory2, keys, at, cap = SLOT_DECLARATION_MEMORY_CAP) {
222
+ const next = { ...memory2 ?? {} };
223
223
  const ids = new Set(keys.map(slotKeyId));
224
224
  for (const existing of Object.keys(next)) {
225
225
  if (ids.has(slotKeyId(existing)) && !keys.includes(existing)) delete next[existing];
@@ -258,10 +258,10 @@ function readSlotDeclaration(storageKey) {
258
258
  return {};
259
259
  }
260
260
  }
261
- function writeSlotDeclaration(storageKey, memory) {
261
+ function writeSlotDeclaration(storageKey, memory2) {
262
262
  try {
263
263
  if (typeof localStorage === "undefined") return;
264
- localStorage.setItem(storageKey, JSON.stringify(memory));
264
+ localStorage.setItem(storageKey, JSON.stringify(memory2));
265
265
  } catch {
266
266
  }
267
267
  }
@@ -466,6 +466,51 @@ function createVioApi(options) {
466
466
  return new VioApi(options);
467
467
  }
468
468
 
469
+ // src/core/safe-storage.ts
470
+ var memory = {
471
+ local: /* @__PURE__ */ new Map(),
472
+ session: /* @__PURE__ */ new Map()
473
+ };
474
+ function area(kind) {
475
+ try {
476
+ const s = globalThis[kind === "local" ? "localStorage" : "sessionStorage"];
477
+ return s && typeof s.getItem === "function" ? s : null;
478
+ } catch {
479
+ return null;
480
+ }
481
+ }
482
+ function storageGet(kind, key) {
483
+ const kept = memory[kind];
484
+ if (kept.has(key)) return kept.get(key) ?? null;
485
+ const s = area(kind);
486
+ if (!s) return null;
487
+ try {
488
+ return s.getItem(key);
489
+ } catch {
490
+ return null;
491
+ }
492
+ }
493
+ function storageSet(kind, key, value, opts = {}) {
494
+ try {
495
+ const s = area(kind);
496
+ if (s) {
497
+ s.setItem(key, value);
498
+ memory[kind].delete(key);
499
+ return true;
500
+ }
501
+ } catch {
502
+ }
503
+ if (opts.memory !== false) memory[kind].set(key, value);
504
+ return false;
505
+ }
506
+ function storageRemove(kind, key) {
507
+ memory[kind].delete(key);
508
+ try {
509
+ area(kind)?.removeItem(key);
510
+ } catch {
511
+ }
512
+ }
513
+
469
514
  // src/core/types.ts
470
515
  function displayPrice(product) {
471
516
  const p = product.price;
@@ -475,37 +520,24 @@ function displayPrice(product) {
475
520
  function getGlobalCountryCode() {
476
521
  const g = globalThis;
477
522
  if (g.__VIO_COUNTRY__) return g.__VIO_COUNTRY__;
478
- if (typeof localStorage !== "undefined") {
479
- try {
480
- const v = localStorage.getItem("vio.country");
481
- if (v) return v;
482
- } catch {
483
- }
484
- }
523
+ const stored = storageGet("local", "vio.country");
524
+ if (stored) return stored;
485
525
  return "NO";
486
526
  }
487
527
  function getGlobalLocale() {
488
528
  const g = globalThis;
489
529
  if (g.__VIO_LOCALE__) return g.__VIO_LOCALE__;
490
- if (typeof localStorage !== "undefined") {
491
- try {
492
- const v = localStorage.getItem("vio.locale");
493
- if (v) return v;
494
- } catch {
495
- }
496
- }
530
+ const stored = storageGet("local", "vio.locale");
531
+ if (stored) return stored;
497
532
  return "nb-NO";
498
533
  }
499
534
  function getGlobalSymbol(currency) {
500
535
  const activeCurrency = currency || getGlobalCurrency();
501
536
  const g = globalThis;
502
537
  if (!currency && g.__VIO_SYMBOL__) return g.__VIO_SYMBOL__;
503
- if (!currency && typeof localStorage !== "undefined") {
504
- try {
505
- const v = localStorage.getItem("vio.symbol");
506
- if (v) return v;
507
- } catch {
508
- }
538
+ if (!currency) {
539
+ const v = storageGet("local", "vio.symbol");
540
+ if (v) return v;
509
541
  }
510
542
  const curr = String(activeCurrency).toUpperCase();
511
543
  if (curr === "NOK" || curr === "DKK") return "Kr";
@@ -519,13 +551,8 @@ function getGlobalSymbol(currency) {
519
551
  function getGlobalCurrency() {
520
552
  const g = globalThis;
521
553
  if (g.__VIO_CURRENCY__) return g.__VIO_CURRENCY__;
522
- if (typeof localStorage !== "undefined") {
523
- try {
524
- const v = localStorage.getItem("vio.currency");
525
- if (v) return v;
526
- } catch {
527
- }
528
- }
554
+ const stored = storageGet("local", "vio.currency");
555
+ if (stored) return stored;
529
556
  return "NOK";
530
557
  }
531
558
  var validatedLocales = /* @__PURE__ */ new Map();
@@ -665,6 +692,80 @@ var GET_PRODUCT_SELLER = (
665
692
  }
666
693
  `
667
694
  );
695
+ var GET_PRODUCT_SHIPPING = (
696
+ /* GraphQL */
697
+ `
698
+ query GetProductShipping(
699
+ $productIds: [Int!]
700
+ $useCache: Boolean!
701
+ $currency: String
702
+ $shippingCountryCode: String
703
+ ) {
704
+ Channel {
705
+ GetProductsByIds(
706
+ product_ids: $productIds
707
+ useCache: $useCache
708
+ currency: $currency
709
+ shipping_country_code: $shippingCountryCode
710
+ ) {
711
+ id
712
+ product_shipping {
713
+ id
714
+ name
715
+ description
716
+ default
717
+ shipping_country {
718
+ country
719
+ price {
720
+ amount
721
+ amount_incl_taxes
722
+ currency_code
723
+ }
724
+ }
725
+ }
726
+ }
727
+ }
728
+ }
729
+ `
730
+ );
731
+ var GET_PRODUCT_SHIPPING_WITH_LOGO = (
732
+ /* GraphQL */
733
+ `
734
+ query GetProductShippingWithLogo(
735
+ $productIds: [Int!]
736
+ $useCache: Boolean!
737
+ $currency: String
738
+ $shippingCountryCode: String
739
+ ) {
740
+ Channel {
741
+ GetProductsByIds(
742
+ product_ids: $productIds
743
+ useCache: $useCache
744
+ currency: $currency
745
+ shipping_country_code: $shippingCountryCode
746
+ ) {
747
+ id
748
+ product_shipping {
749
+ id
750
+ name
751
+ description
752
+ logo_url
753
+ default
754
+ shipping_country {
755
+ country
756
+ price {
757
+ amount
758
+ amount_incl_taxes
759
+ currency_code
760
+ }
761
+ }
762
+ }
763
+ }
764
+ }
765
+ }
766
+ `
767
+ );
768
+ var gatewayHasShippingLogo;
668
769
  var ChannelProduct = class {
669
770
  constructor(client) {
670
771
  this.client = client;
@@ -704,7 +805,77 @@ var ChannelProduct = class {
704
805
  return null;
705
806
  }
706
807
  }
808
+ /**
809
+ * The product's shipping rates for the page's market (or `country`),
810
+ * cheapest first; `[]` when it has none there, `null` when they could not
811
+ * be read. Never throws. Never from the cache, for the reason given at
812
+ * getSellerCompany.
813
+ */
814
+ async getShippingRates(productId, options = {}) {
815
+ const country = options.country || getGlobalCountryCode();
816
+ const variables = {
817
+ productIds: [productId],
818
+ useCache: false,
819
+ currency: options.currency ?? getGlobalCurrency(),
820
+ shippingCountryCode: country
821
+ };
822
+ try {
823
+ let data;
824
+ if (gatewayHasShippingLogo === false) {
825
+ data = await this.client.request(GET_PRODUCT_SHIPPING, variables);
826
+ } else {
827
+ try {
828
+ data = await this.client.request(GET_PRODUCT_SHIPPING_WITH_LOGO, variables);
829
+ gatewayHasShippingLogo = true;
830
+ } catch (err) {
831
+ if (!/logo_url/.test(String(err?.message ?? ""))) throw err;
832
+ gatewayHasShippingLogo = false;
833
+ data = await this.client.request(GET_PRODUCT_SHIPPING, variables);
834
+ }
835
+ }
836
+ const product = data.Channel?.GetProductsByIds?.[0];
837
+ if (!product) return null;
838
+ return shippingRatesFor(product.product_shipping, country);
839
+ } catch {
840
+ return null;
841
+ }
842
+ }
707
843
  };
844
+ var FILLER_SHIPPING_DESCRIPTIONS = Object.freeze(["by outshifter", "default shipping method"]);
845
+ function shippingDescriptionOf(value, repeats = []) {
846
+ const text3 = typeof value === "string" ? value.trim() : "";
847
+ if (!text3) return "";
848
+ const key = text3.toLowerCase();
849
+ if (FILLER_SHIPPING_DESCRIPTIONS.includes(key)) return "";
850
+ for (const r of repeats) {
851
+ if (typeof r === "string" && r.trim() && r.trim().toLowerCase() === key) return "";
852
+ }
853
+ return text3;
854
+ }
855
+ function shippingRatesFor(productShipping, country) {
856
+ if (!Array.isArray(productShipping)) return [];
857
+ const want = String(country || "").trim().toUpperCase();
858
+ const out = [];
859
+ for (const s of productShipping) {
860
+ const countries = Array.isArray(s?.shipping_country) ? s.shipping_country : [];
861
+ const row = countries.find((c) => String(c?.country ?? "").trim().toUpperCase() === want);
862
+ const price = row?.price;
863
+ const amount = typeof price?.amount_incl_taxes === "number" ? price.amount_incl_taxes : price?.amount;
864
+ if (typeof amount !== "number" || !Number.isFinite(amount) || amount < 0) continue;
865
+ const name = String(s.name ?? "");
866
+ const description = shippingDescriptionOf(s.description, [name]);
867
+ out.push({
868
+ id: String(s.id ?? ""),
869
+ name,
870
+ price: amount,
871
+ currency: String(price?.currency_code ?? ""),
872
+ default: s.default === true,
873
+ description,
874
+ logoUrl: httpsUrlOrEmpty(s.logo_url ?? s.logoUrl)
875
+ });
876
+ }
877
+ return out.sort((a, b) => a.price - b.price);
878
+ }
708
879
  var Channel = class {
709
880
  constructor(client) {
710
881
  this.product = new ChannelProduct(client);
@@ -723,6 +894,11 @@ var CommerceClient = class {
723
894
  function createCommerceClient(options) {
724
895
  return new CommerceClient(options);
725
896
  }
897
+ function httpsUrlOrEmpty(value) {
898
+ if (typeof value !== "string") return "";
899
+ const url = value.trim();
900
+ return /^https:\/\/[^\s]+$/i.test(url) ? url : "";
901
+ }
726
902
 
727
903
  // src/core/content.ts
728
904
  var CONTENT_ID_QUERY = "vio_content";
@@ -1851,7 +2027,7 @@ query Config {
1851
2027
  }
1852
2028
  }
1853
2029
  `;
1854
- async function getCartGraphQLOptions(sponsorId) {
2030
+ async function getCartGraphQLOptions(sponsorId, ctx) {
1855
2031
  const facade = globalThis.__VIO_FACADE__;
1856
2032
  if (!Configuration.isInitialized) {
1857
2033
  await Configuration.whenReady();
@@ -1869,14 +2045,31 @@ async function getCartGraphQLOptions(sponsorId) {
1869
2045
  const anyCfg = cfg;
1870
2046
  let commerceKey = anyCfg.apiKey;
1871
2047
  let sponsorResolved = false;
1872
- if (sponsorId && typeof facade?.findSponsor === "function") {
2048
+ let channelId;
2049
+ let channelMissing = false;
2050
+ const askedChannel = ctx?.channelId === null || ctx?.channelId === void 0 ? "" : String(ctx.channelId).trim();
2051
+ if (sponsorId && typeof facade?.sponsorCommerce === "function") {
2052
+ const sponsor = facade.sponsorCommerce(sponsorId, ctx ?? {});
2053
+ if (sponsor?.commerce?.apiKey) {
2054
+ commerceKey = sponsor.commerce.apiKey;
2055
+ sponsorResolved = true;
2056
+ const ch = sponsor.commerce.channelId;
2057
+ if (ch !== null && ch !== void 0 && String(ch).trim()) channelId = String(ch).trim();
2058
+ } else if (askedChannel && facade.bootstrapCache) {
2059
+ channelMissing = true;
2060
+ }
2061
+ } else if (sponsorId && !askedChannel && typeof facade?.findSponsor === "function") {
1873
2062
  const sponsor = facade.findSponsor(sponsorId);
1874
2063
  if (sponsor?.commerce?.apiKey) {
1875
2064
  commerceKey = sponsor.commerce.apiKey;
1876
2065
  sponsorResolved = true;
1877
2066
  }
1878
2067
  }
1879
- if (sponsorId && !sponsorResolved && typeof console !== "undefined") {
2068
+ if (channelMissing && typeof console !== "undefined") {
2069
+ console.warn(
2070
+ `[Vio] channel ${askedChannel} of sponsor ${sponsorId} is no longer in the bootstrap \u2014 not calling it with another channel's key`
2071
+ );
2072
+ } else if (sponsorId && !sponsorResolved && typeof console !== "undefined") {
1880
2073
  console.warn(
1881
2074
  `[Vio] no commerce key for sponsor ${sponsorId} \u2014 falling back to the platform apiKey, which the commerce API will reject. Is the sponsor configured, and did Vio.init run before this call?`
1882
2075
  );
@@ -1885,16 +2078,17 @@ async function getCartGraphQLOptions(sponsorId) {
1885
2078
  endpoint: anyCfg.graphQLBase || "https://graph-ql-dev.vio.live",
1886
2079
  apiKey: anyCfg.apiKey,
1887
2080
  commerceKey,
1888
- sponsorResolved
2081
+ sponsorResolved,
2082
+ ...channelId ? { channelId } : {},
2083
+ ...channelMissing ? { channelMissing } : {}
1889
2084
  };
1890
2085
  }
1891
2086
  var CUSTOMER_SESSION_KEY = "vio.customer_session_id.v1";
1892
2087
  function getCustomerSessionId() {
1893
- if (typeof localStorage === "undefined") return "cs_session_default";
1894
- let id = localStorage.getItem(CUSTOMER_SESSION_KEY);
2088
+ let id = storageGet("local", CUSTOMER_SESSION_KEY);
1895
2089
  if (!id) {
1896
2090
  id = "cs_" + Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
1897
- localStorage.setItem(CUSTOMER_SESSION_KEY, id);
2091
+ storageSet("local", CUSTOMER_SESSION_KEY, id);
1898
2092
  }
1899
2093
  return id;
1900
2094
  }
@@ -2149,7 +2343,18 @@ async function getVippsStatus(variables, options) {
2149
2343
  }
2150
2344
 
2151
2345
  // src/core/cart/cart-manager.ts
2152
- var STORAGE_KEY = "vio.cart.v1";
2346
+ var CART_STORAGE_PREFIX = "vio.cart.v1";
2347
+ function cartStorageKey(scope) {
2348
+ return scope ? `${CART_STORAGE_PREFIX}:${scope}` : CART_STORAGE_PREFIX;
2349
+ }
2350
+ function cartScopeOf(environment, apiKey) {
2351
+ let h = 2166136261;
2352
+ for (let i = 0; i < apiKey.length; i++) {
2353
+ h ^= apiKey.charCodeAt(i);
2354
+ h = Math.imul(h, 16777619) >>> 0;
2355
+ }
2356
+ return `${environment || "production"}:${h.toString(16).padStart(8, "0")}`;
2357
+ }
2153
2358
  function finalTarget(sync) {
2154
2359
  return sync.steps.reduce((quantity, step) => quantity + step.delta, sync.sent);
2155
2360
  }
@@ -2177,6 +2382,17 @@ function queueStep(sync, target, origin) {
2177
2382
  else sync.steps.push({ delta: -left });
2178
2383
  }
2179
2384
  }
2385
+ function cartCampaignOf(cart) {
2386
+ const items = cart?.items ?? [];
2387
+ for (let i = items.length - 1; i >= 0; i--) {
2388
+ const origins = items[i]?.origins ?? [];
2389
+ for (let j = origins.length - 1; j >= 0; j--) {
2390
+ const id = Number(origins[j]?.campaignId);
2391
+ if (Number.isInteger(id) && id > 0) return id;
2392
+ }
2393
+ }
2394
+ return void 0;
2395
+ }
2180
2396
  function lineId() {
2181
2397
  return "li_" + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
2182
2398
  }
@@ -2187,7 +2403,7 @@ function normId(val) {
2187
2403
  return String(val).trim();
2188
2404
  }
2189
2405
  var CartManager = class extends EventTarget {
2190
- constructor() {
2406
+ constructor(scope = "") {
2191
2407
  super();
2192
2408
  /** In-flight CreateCart per sponsor — two quick adds must share ONE backend
2193
2409
  * cart (two CreateCarts split the items and one silently disappears). */
@@ -2211,6 +2427,21 @@ var CartManager = class extends EventTarget {
2211
2427
  * in parallel; each response re-applied the whole backend cart, so an
2212
2428
  * older one landing after a newer optimistic change rolled the line back.) */
2213
2429
  this.qtySyncs = /* @__PURE__ */ new Map();
2430
+ /**
2431
+ * Bumped per sponsor each time its backend cart is dropped because its
2432
+ * channel went away: the lines as they stood then (`carryOver`) go to the
2433
+ * new backend cart, so an add whose local units were already counted in
2434
+ * them must not send its own AddItem as well (see `addManual`).
2435
+ */
2436
+ this.rebuildEpoch = /* @__PURE__ */ new Map();
2437
+ /** The lines to give the next backend cart of a sponsor, after its channel went away. */
2438
+ this.carryOver = /* @__PURE__ */ new Map();
2439
+ /**
2440
+ * Whether this is the page's connection now. A manager left behind by a
2441
+ * re-init (another key) still finishes its requests and keeps its own
2442
+ * carts, but says nothing to the page: no `vio:cart-*` events.
2443
+ */
2444
+ this.active = true;
2214
2445
  /** Quantity intents for lines whose backend id hasn't arrived yet — replayed
2215
2446
  * when the AddItem response reconciles (keyed by product::variant). */
2216
2447
  this.pendingQtyIntents = /* @__PURE__ */ new Map();
@@ -2224,7 +2455,7 @@ var CartManager = class extends EventTarget {
2224
2455
  * that changed (`source: 'storage'`); nothing when nothing did.
2225
2456
  */
2226
2457
  this.onStorage = (e) => {
2227
- if (e.key !== null && e.key !== STORAGE_KEY) return;
2458
+ if (e.key !== null && e.key !== this.storageKey) return;
2228
2459
  const stored = this.loadFromStorage();
2229
2460
  const changed = [];
2230
2461
  for (const sponsorId of /* @__PURE__ */ new Set([...this.cartsBySponsor.keys(), ...stored.keys()])) {
@@ -2240,11 +2471,28 @@ var CartManager = class extends EventTarget {
2240
2471
  this.emit();
2241
2472
  for (const sponsorId of changed) this.emitCartUpdated(sponsorId, "storage");
2242
2473
  };
2474
+ this.storageKey = cartStorageKey(scope);
2243
2475
  this.cartsBySponsor = this.loadFromStorage();
2476
+ if (scope && this.cartsBySponsor.size === 0) {
2477
+ const legacy = this.loadFromStorage(CART_STORAGE_PREFIX);
2478
+ if (legacy.size > 0) {
2479
+ this.cartsBySponsor = legacy;
2480
+ this.persist();
2481
+ storageRemove("local", CART_STORAGE_PREFIX);
2482
+ }
2483
+ }
2244
2484
  if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
2245
2485
  window.addEventListener("storage", this.onStorage);
2246
2486
  }
2247
2487
  }
2488
+ /** The page's connection is (or is no longer) this one; see `active`. */
2489
+ setActive(active) {
2490
+ this.active = active;
2491
+ }
2492
+ /** Tell the listeners what this connection's carts hold now (after the page switched to it). */
2493
+ announce() {
2494
+ this.emit();
2495
+ }
2248
2496
  nextSeq(sponsorId) {
2249
2497
  const n = (this.mutationSeq.get(sponsorId) ?? 0) + 1;
2250
2498
  this.mutationSeq.set(sponsorId, n);
@@ -2338,15 +2586,118 @@ var CartManager = class extends EventTarget {
2338
2586
  emitError(message, err) {
2339
2587
  if (typeof console !== "undefined") console.warn("[CartManager]", message, err ?? "");
2340
2588
  this.dispatchEvent(new CustomEvent("error", { detail: { message } }));
2341
- if (typeof document !== "undefined") {
2589
+ if (this.active && typeof document !== "undefined") {
2342
2590
  document.dispatchEvent(
2343
2591
  new CustomEvent("vio:cart-error", { bubbles: true, detail: { message } })
2344
2592
  );
2345
2593
  }
2346
2594
  }
2347
2595
  // MARK: - Backend cart sync (Vio Commerce GraphQL)
2348
- /** Ensure the sponsor cart exists server-side; returns its backend cart id. */
2349
- async ensureCartId(sponsorId, currency, shippingCountry) {
2596
+ /**
2597
+ * The options of a sponsor's backend cart: the key of the channel it was
2598
+ * created on (`channelMissing` when the bootstrap no longer lists it). A
2599
+ * cart from before channels were remembered keeps the sponsor's first
2600
+ * block, as when it was created, and remembers that channel from now on.
2601
+ */
2602
+ async cartOptions(sponsorId) {
2603
+ const cart = this.cartsBySponsor.get(sponsorId);
2604
+ if (cart?.cartId && cart.channelId) {
2605
+ return getCartGraphQLOptions(sponsorId, { channelId: cart.channelId });
2606
+ }
2607
+ if (!cart?.cartId) return getCartGraphQLOptions(sponsorId);
2608
+ const opts = await getCartGraphQLOptions(sponsorId, { campaignId: null });
2609
+ const now = this.cartsBySponsor.get(sponsorId);
2610
+ if (now?.cartId && !now.channelId && opts.sponsorResolved && opts.channelId) {
2611
+ now.channelId = opts.channelId;
2612
+ this.persist();
2613
+ }
2614
+ return opts;
2615
+ }
2616
+ /**
2617
+ * The backend cart to call for a sponsor, with the key of its channel. When
2618
+ * the bootstrap no longer lists that channel the stale cart is dropped and a
2619
+ * new one is created on the sponsor's current channel with the same lines
2620
+ * (`dropStaleBackendCart` + `ensureCartId`) — never a call with a key of
2621
+ * another channel. `undefined` when there is no backend cart (or none could
2622
+ * be made).
2623
+ */
2624
+ async liveCart(sponsorId) {
2625
+ const cart = this.cartsBySponsor.get(sponsorId);
2626
+ const cartId = cart?.cartId;
2627
+ if (!cart || !cartId) return void 0;
2628
+ const opts = await this.cartOptions(sponsorId);
2629
+ if (!opts.channelMissing) {
2630
+ const current = this.cartsBySponsor.get(sponsorId)?.cartId;
2631
+ if (current && current !== cartId) return { cartId: current, opts: await this.cartOptions(sponsorId) };
2632
+ return { cartId, opts };
2633
+ }
2634
+ const now = this.cartsBySponsor.get(sponsorId);
2635
+ if (now?.cartId === cartId) this.dropStaleBackendCart(sponsorId);
2636
+ const rebuilt = await this.ensureCartId(sponsorId, now?.currency || void 0, now?.shippingCountry || void 0);
2637
+ if (!rebuilt) return void 0;
2638
+ return { cartId: rebuilt, opts: await this.cartOptions(sponsorId) };
2639
+ }
2640
+ /**
2641
+ * The channel of a sponsor's cart is gone from the bootstrap (the campaign
2642
+ * moved, a refresh after hours): forget the backend cart and its line ids,
2643
+ * keep the lines, and remember them as they stand now for the next backend
2644
+ * cart (`carryOver`). Queued quantity syncs of the old cart are dropped —
2645
+ * their quantities are in the lines carried.
2646
+ */
2647
+ dropStaleBackendCart(sponsorId) {
2648
+ const cart = this.cartsBySponsor.get(sponsorId);
2649
+ if (!cart?.cartId) return;
2650
+ const carried = cart.items.filter((item) => item.quantity > 0).map((item) => ({ productId: item.productId, variantId: item.variantId, origins: lineOrigins(item) }));
2651
+ delete cart.cartId;
2652
+ delete cart.channelId;
2653
+ for (const item of cart.items) delete item.cartItemId;
2654
+ for (const key of [...this.qtySyncs.keys()]) {
2655
+ if (key.startsWith(`${sponsorId}:`)) this.qtySyncs.delete(key);
2656
+ }
2657
+ this.carryOver.set(sponsorId, carried);
2658
+ this.rebuildEpoch.set(sponsorId, (this.rebuildEpoch.get(sponsorId) ?? 0) + 1);
2659
+ this.persist();
2660
+ }
2661
+ /** Give a new backend cart the lines of the one whose channel went away — each unit with its own origin. */
2662
+ async replayCarriedLines(sponsorId, cartId, carried, opts) {
2663
+ let last;
2664
+ const seq = this.nextSeq(sponsorId);
2665
+ try {
2666
+ const privacyMode = await currentPrivacyMode();
2667
+ for (const line of carried) {
2668
+ for (const origin of line.origins) {
2669
+ if (!(origin.quantity > 0)) continue;
2670
+ const { quantity, ...place } = origin;
2671
+ const wire = Object.keys(place).length ? lineOriginWire(normalizeOrigin(place), { page: currentOriginPage(), privacyMode }) : void 0;
2672
+ last = await addItem(
2673
+ cartId,
2674
+ [
2675
+ {
2676
+ product_id: Number(line.productId || 0),
2677
+ quantity,
2678
+ variant_id: line.variantId != null ? Number(line.variantId) : null,
2679
+ ...wire ? { origin: wire } : {}
2680
+ }
2681
+ ],
2682
+ opts
2683
+ );
2684
+ }
2685
+ }
2686
+ } catch (err) {
2687
+ this.emitError("moving the cart to the current channel failed \u2014 some lines may not be in it", err);
2688
+ }
2689
+ const res = last;
2690
+ if (res && Array.isArray(res.line_items)) this.applyBackendCart(sponsorId, res, seq);
2691
+ }
2692
+ /**
2693
+ * Ensure the sponsor cart exists server-side; returns its backend cart id.
2694
+ * A new backend cart is created on the channel of the campaign in context —
2695
+ * `ctx.campaignId` (the add's origin), else the campaign the cart's lines
2696
+ * came from, else `Vio.init`'s — and remembers it (`channelId`). An existing
2697
+ * one whose channel the bootstrap no longer lists is created again on the
2698
+ * current channel, with its lines (`liveCart`).
2699
+ */
2700
+ async ensureCartId(sponsorId, currency, shippingCountry, ctx = {}) {
2350
2701
  const activeCurrency = currency || getGlobalCurrency();
2351
2702
  const activeCountry = shippingCountry || getGlobalCountryCode();
2352
2703
  let cart = this.cartsBySponsor.get(sponsorId);
@@ -2356,11 +2707,15 @@ var CartManager = class extends EventTarget {
2356
2707
  } else {
2357
2708
  cart.shippingCountry = activeCountry;
2358
2709
  }
2359
- if (cart.cartId) return cart.cartId;
2710
+ if (cart.cartId) {
2711
+ if (!cart.channelId) return cart.cartId;
2712
+ return (await this.liveCart(sponsorId))?.cartId;
2713
+ }
2360
2714
  const inFlight = this.cartIdPromises.get(sponsorId);
2361
2715
  if (inFlight) return inFlight;
2362
2716
  const creation = (async () => {
2363
- const opts = await getCartGraphQLOptions(sponsorId);
2717
+ const campaignId = ctx.campaignId ?? cartCampaignOf(cart);
2718
+ const opts = await getCartGraphQLOptions(sponsorId, campaignId !== void 0 ? { campaignId } : void 0);
2364
2719
  if (!opts.sponsorResolved) {
2365
2720
  this.emitError(`no commerce key resolved for sponsor ${sponsorId} \u2014 cart not synced`);
2366
2721
  return void 0;
@@ -2375,6 +2730,8 @@ var CartManager = class extends EventTarget {
2375
2730
  );
2376
2731
  if (backendCart?.cart_id) {
2377
2732
  cart.cartId = backendCart.cart_id;
2733
+ if (opts.channelId) cart.channelId = opts.channelId;
2734
+ else delete cart.channelId;
2378
2735
  if (Array.isArray(backendCart.line_items) && backendCart.line_items.length > 0) {
2379
2736
  this.updateFromBackendCart(sponsorId, backendCart);
2380
2737
  } else {
@@ -2388,6 +2745,11 @@ var CartManager = class extends EventTarget {
2388
2745
  this.persist();
2389
2746
  this.emit();
2390
2747
  }
2748
+ const carried = this.carryOver.get(sponsorId);
2749
+ if (carried) {
2750
+ this.carryOver.delete(sponsorId);
2751
+ if (carried.length > 0) await this.replayCarriedLines(sponsorId, backendCart.cart_id, carried, opts);
2752
+ }
2391
2753
  }
2392
2754
  } catch (err) {
2393
2755
  this.emitError("createCart failed", err);
@@ -2480,7 +2842,7 @@ var CartManager = class extends EventTarget {
2480
2842
  this.emit();
2481
2843
  const syncedCartId = cart.cartId;
2482
2844
  if (syncedCartId) {
2483
- for (const id of removedBeforeSync) this.syncQuantity(sponsorId, syncedCartId, id, 0);
2845
+ for (const id of removedBeforeSync) this.syncQuantity(sponsorId, syncedCartId, id, 0, void 0, cart.channelId);
2484
2846
  }
2485
2847
  if (this.pendingQtyIntents.size > 0) {
2486
2848
  const currentCart = this.cartsBySponsor.get(sponsorId);
@@ -2572,13 +2934,17 @@ var CartManager = class extends EventTarget {
2572
2934
  this.emit();
2573
2935
  const addedItem = existing ?? cart.items[cart.items.length - 1];
2574
2936
  const addedLineId = addedItem.id;
2937
+ const epochAtAdd = this.rebuildEpoch.get(opts.sponsorId) ?? 0;
2575
2938
  const seq = this.nextSeq(opts.sponsorId);
2576
2939
  let settling;
2577
2940
  const mutation = (async () => {
2578
2941
  try {
2579
- const cartId = await this.ensureCartId(opts.sponsorId, opts.currency || void 0);
2942
+ const cartId = await this.ensureCartId(opts.sponsorId, opts.currency || void 0, void 0, {
2943
+ campaignId: opts.origin?.campaignId
2944
+ });
2945
+ if ((this.rebuildEpoch.get(opts.sponsorId) ?? 0) !== epochAtAdd) return;
2580
2946
  if (cartId) {
2581
- const gqlOpts = await getCartGraphQLOptions(opts.sponsorId);
2947
+ const gqlOpts = await this.cartOptions(opts.sponsorId);
2582
2948
  const origin = opts.origin ? lineOriginWire(normalizeOrigin(opts.origin), {
2583
2949
  page: currentOriginPage(),
2584
2950
  privacyMode: await currentPrivacyMode()
@@ -2647,7 +3013,7 @@ var CartManager = class extends EventTarget {
2647
3013
  const addedOrigin = increase ? latestOrigin(item) : void 0;
2648
3014
  if (target <= 0) {
2649
3015
  cart.items = cart.items.filter((i) => i !== item);
2650
- if (typeof document !== "undefined") {
3016
+ if (this.active && typeof document !== "undefined") {
2651
3017
  document.dispatchEvent(
2652
3018
  new CustomEvent("vio:removed-from-cart", {
2653
3019
  bubbles: true,
@@ -2674,12 +3040,17 @@ var CartManager = class extends EventTarget {
2674
3040
  if (cart.items.length === 0) this.cartsBySponsor.delete(sponsorId);
2675
3041
  this.persist();
2676
3042
  this.emit();
2677
- if (!cart.cartId) return;
3043
+ if (!cart.cartId) {
3044
+ if (this.cartIdPromises.has(sponsorId)) {
3045
+ this.pendingQtyIntents.set(this.intentKey(item.productId, item.variantId), target);
3046
+ }
3047
+ return;
3048
+ }
2678
3049
  if (!targetItemId) {
2679
3050
  this.pendingQtyIntents.set(this.intentKey(item.productId, item.variantId), target);
2680
3051
  return;
2681
3052
  }
2682
- this.syncQuantity(sponsorId, cart.cartId, targetItemId, target, addedOrigin);
3053
+ this.syncQuantity(sponsorId, cart.cartId, targetItemId, target, addedOrigin, cart.channelId);
2683
3054
  }
2684
3055
  /** Send a line's target quantity to the backend — ONE request in flight per
2685
3056
  * line. Targets that arrive while one is in flight queue behind it as
@@ -2691,7 +3062,7 @@ var CartManager = class extends EventTarget {
2691
3062
  * optimistic quantity through any backend response (see
2692
3063
  * `updateFromBackendCart`); a rejection drops what was queued and
2693
3064
  * reconciles the cart from the server. */
2694
- syncQuantity(sponsorId, backendCartId, cartItemId, target, origin) {
3065
+ syncQuantity(sponsorId, backendCartId, cartItemId, target, origin, channelId) {
2695
3066
  const key = this.qtySyncKey(sponsorId, cartItemId);
2696
3067
  const active = this.qtySyncs.get(key);
2697
3068
  if (active) {
@@ -2706,9 +3077,19 @@ var CartManager = class extends EventTarget {
2706
3077
  let nextOrigin = origin;
2707
3078
  let removalRetried = false;
2708
3079
  while (next !== void 0) {
3080
+ let gqlOpts;
3081
+ if (this.cartsBySponsor.get(sponsorId)?.cartId) {
3082
+ const live = await this.liveCart(sponsorId);
3083
+ if (!live || live.cartId !== backendCartId) {
3084
+ if (this.qtySyncs.get(key) === sync) this.qtySyncs.delete(key);
3085
+ break;
3086
+ }
3087
+ gqlOpts = live.opts;
3088
+ } else {
3089
+ gqlOpts = await getCartGraphQLOptions(sponsorId, channelId ? { channelId, retired: true } : void 0);
3090
+ }
2709
3091
  const seq = this.nextSeq(sponsorId);
2710
3092
  try {
2711
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2712
3093
  const originWire = next > 0 && nextOrigin ? lineOriginWire(nextOrigin, { page: currentOriginPage(), privacyMode: await currentPrivacyMode() }) : void 0;
2713
3094
  const resCart = next <= 0 ? await deleteItem(backendCartId, cartItemId, gqlOpts) : originWire ? await updateItem(backendCartId, cartItemId, null, next, gqlOpts, { origin: originWire }) : await updateItem(backendCartId, cartItemId, null, next, gqlOpts);
2714
3095
  const step = sync.steps.shift();
@@ -2768,10 +3149,9 @@ var CartManager = class extends EventTarget {
2768
3149
  }
2769
3150
  /** Refresh the local cart from the backend (source of truth). */
2770
3151
  async fetchCartFromBackend(sponsorId) {
2771
- const cart = this.cartsBySponsor.get(sponsorId);
2772
- if (!cart?.cartId) return null;
2773
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2774
- const resCart = await getCart(cart.cartId, gqlOpts);
3152
+ const live = await this.liveCart(sponsorId);
3153
+ if (!live) return null;
3154
+ const resCart = await getCart(live.cartId, live.opts);
2775
3155
  if (resCart) this.updateFromBackendCart(sponsorId, resCart);
2776
3156
  return resCart;
2777
3157
  }
@@ -2795,8 +3175,9 @@ var CartManager = class extends EventTarget {
2795
3175
  if (!cart?.cartId) return cart;
2796
3176
  let resCart;
2797
3177
  try {
2798
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2799
- resCart = await getCart(cart.cartId, gqlOpts);
3178
+ const live = await this.liveCart(sponsorId);
3179
+ if (!live) return this.cartsBySponsor.get(sponsorId);
3180
+ resCart = await getCart(live.cartId, live.opts);
2800
3181
  } catch (err) {
2801
3182
  if (typeof console !== "undefined") {
2802
3183
  console.warn("[CartManager] GetCart before the checkout failed \u2014 the local cart stands", err);
@@ -2815,7 +3196,7 @@ var CartManager = class extends EventTarget {
2815
3196
  }
2816
3197
  /** `vio:cart-updated` on `document` — see `CartUpdatedDetail`. */
2817
3198
  emitCartUpdated(sponsorId, source) {
2818
- if (typeof document === "undefined") return;
3199
+ if (!this.active || typeof document === "undefined") return;
2819
3200
  const cart = this.cartsBySponsor.get(sponsorId);
2820
3201
  const items = (cart?.items ?? []).map((item) => ({ ...item }));
2821
3202
  const detail = {
@@ -2831,17 +3212,15 @@ var CartManager = class extends EventTarget {
2831
3212
  }
2832
3213
  /** Line items grouped by supplier (shipping is per-supplier server-side). */
2833
3214
  async fetchLineItemsBySupplier(sponsorId) {
2834
- const cart = this.cartsBySponsor.get(sponsorId);
2835
- if (!cart?.cartId) return null;
2836
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2837
- return getLineItemsBySupplier(cart.cartId, gqlOpts);
3215
+ const live = await this.liveCart(sponsorId);
3216
+ if (!live) return null;
3217
+ return getLineItemsBySupplier(live.cartId, live.opts);
2838
3218
  }
2839
3219
  /** Set the chosen shipping option per supplier on the backend cart. */
2840
3220
  async updateShippingsBySupplier(sponsorId, data) {
2841
- const cart = this.cartsBySponsor.get(sponsorId);
2842
- if (!cart?.cartId) return null;
2843
- const gqlOpts = await getCartGraphQLOptions(sponsorId);
2844
- const resCart = await updateShippingsBySupplier(cart.cartId, data, gqlOpts);
3221
+ const live = await this.liveCart(sponsorId);
3222
+ if (!live) return null;
3223
+ const resCart = await updateShippingsBySupplier(live.cartId, data, live.opts);
2845
3224
  if (resCart && Array.isArray(resCart.line_items)) {
2846
3225
  this.updateFromBackendCart(sponsorId, resCart);
2847
3226
  }
@@ -2881,19 +3260,23 @@ var CartManager = class extends EventTarget {
2881
3260
  return formatPrice(this.totalAcrossSponsors, this.primaryCurrency);
2882
3261
  }
2883
3262
  // MARK: - Persistence
3263
+ /**
3264
+ * Keep the carts for the next page and the other tabs. Where storage
3265
+ * refuses (in-app browsers, private modes) nothing throws (safe-storage.ts):
3266
+ * the carts stay in this manager's own map, the add to cart and the
3267
+ * checkout go on, and the cart just does not survive a reload there.
3268
+ */
2884
3269
  persist() {
2885
- if (typeof localStorage === "undefined") return;
2886
3270
  try {
2887
3271
  const obj = {};
2888
3272
  for (const [k, v] of this.cartsBySponsor) obj[String(k)] = v;
2889
- localStorage.setItem(STORAGE_KEY, JSON.stringify(obj));
3273
+ storageSet("local", this.storageKey, JSON.stringify(obj), { memory: false });
2890
3274
  } catch {
2891
3275
  }
2892
3276
  }
2893
- loadFromStorage() {
2894
- if (typeof localStorage === "undefined") return /* @__PURE__ */ new Map();
3277
+ loadFromStorage(key = this.storageKey) {
2895
3278
  try {
2896
- const raw = localStorage.getItem(STORAGE_KEY);
3279
+ const raw = storageGet("local", key);
2897
3280
  if (!raw) return /* @__PURE__ */ new Map();
2898
3281
  const obj = JSON.parse(raw);
2899
3282
  const map = /* @__PURE__ */ new Map();
@@ -3044,6 +3427,56 @@ function findSponsorIn(boot, sponsorId) {
3044
3427
  }
3045
3428
  return void 0;
3046
3429
  }
3430
+ function channelIdOf(value) {
3431
+ if (value === null || value === void 0) return void 0;
3432
+ const s = String(value).trim();
3433
+ return s ? s : void 0;
3434
+ }
3435
+ function sells(s) {
3436
+ const commerce = s?.commerce;
3437
+ if (!commerce || typeof commerce !== "object") return false;
3438
+ const key = commerce.apiKey;
3439
+ return typeof key === "string" && key.trim() !== "";
3440
+ }
3441
+ function sellingBlocksIn(boot, sponsorId) {
3442
+ const id = toSponsorId(sponsorId);
3443
+ const out = [];
3444
+ if (id === void 0 || !boot || typeof boot !== "object") return out;
3445
+ const take = (s, campaignId) => {
3446
+ if (!s || typeof s !== "object" || Number(s.id) !== id || !sells(s)) return;
3447
+ out.push({ campaignId: toCampaignId(campaignId), sponsor: s });
3448
+ };
3449
+ try {
3450
+ const defaultId = boot.campaign?.id;
3451
+ take(boot.primarySponsor, defaultId);
3452
+ if (Array.isArray(boot.secondarySponsors)) for (const s of boot.secondarySponsors) take(s, defaultId);
3453
+ const campaigns = Array.isArray(boot.campaigns) ? boot.campaigns : [];
3454
+ for (const c of campaigns) {
3455
+ if (!c || typeof c !== "object") continue;
3456
+ take(c.primarySponsor, c.id);
3457
+ if (Array.isArray(c.secondarySponsors)) for (const s of c.secondarySponsors) take(s, c.id);
3458
+ }
3459
+ } catch {
3460
+ }
3461
+ return out;
3462
+ }
3463
+ function sponsorCommerceIn(boot, sponsorId, ctx = {}) {
3464
+ try {
3465
+ const blocks = sellingBlocksIn(boot, sponsorId);
3466
+ if (blocks.length === 0) return void 0;
3467
+ const channel = channelIdOf(ctx?.channelId);
3468
+ if (channel !== void 0) {
3469
+ return blocks.find((b) => channelIdOf(b.sponsor.commerce.channelId) === channel)?.sponsor;
3470
+ }
3471
+ for (const campaignId of campaignCandidates(ctx?.campaignId)) {
3472
+ const found = blocks.find((b) => b.campaignId === campaignId);
3473
+ if (found) return found.sponsor;
3474
+ }
3475
+ return blocks[0].sponsor;
3476
+ } catch {
3477
+ return void 0;
3478
+ }
3479
+ }
3047
3480
  function sponsorIdentityFrom(boot, sponsorId) {
3048
3481
  try {
3049
3482
  const id = toSponsorId(sponsorId);
@@ -3055,13 +3488,15 @@ function sponsorIdentityFrom(boot, sponsorId) {
3055
3488
  if (!name && !logoUrl) return null;
3056
3489
  const termsUrl = pageUrlOrNull(sponsor.termsUrl);
3057
3490
  const refundPolicyUrl = pageUrlOrNull(sponsor.refundPolicyUrl);
3491
+ const highlights = highlightsOf(sponsor.highlights);
3058
3492
  return {
3059
3493
  id,
3060
3494
  name,
3061
3495
  logoUrl,
3062
3496
  avatarUrl: urlOrNull(sponsor.avatarUrl),
3063
3497
  ...termsUrl ? { termsUrl } : {},
3064
- ...refundPolicyUrl ? { refundPolicyUrl } : {}
3498
+ ...refundPolicyUrl ? { refundPolicyUrl } : {},
3499
+ ...highlights.length ? { highlights } : {}
3065
3500
  };
3066
3501
  } catch {
3067
3502
  return null;
@@ -3087,7 +3522,8 @@ function normalizeEntry(raw) {
3087
3522
  const checkoutId = typeof r.checkoutId === "string" ? r.checkoutId.trim() : "";
3088
3523
  const sponsorId = toSponsorId(r.sponsorId);
3089
3524
  if (!checkoutId || sponsorId === void 0) return null;
3090
- return { checkoutId, sponsorId, name: nameOf(r.name), logoUrl: urlOrNull(r.logoUrl) };
3525
+ const channelId = channelIdOf(r.channelId);
3526
+ return { checkoutId, sponsorId, name: nameOf(r.name), logoUrl: urlOrNull(r.logoUrl), ...channelId ? { channelId } : {} };
3091
3527
  }
3092
3528
  function readEntries() {
3093
3529
  const store = storageRefusedWrite ? null : sessionStore();
@@ -3127,8 +3563,9 @@ function rememberCheckoutSponsor(checkoutId, sponsor) {
3127
3563
  name = previous.name;
3128
3564
  logoUrl = previous.logoUrl;
3129
3565
  }
3566
+ const channelId = channelIdOf(sponsor.channelId) ?? (previous && previous.sponsorId === sponsorId ? previous.channelId : void 0);
3130
3567
  const next = entries.filter((e) => e.checkoutId !== id);
3131
- next.push({ checkoutId: id, sponsorId, name, logoUrl });
3568
+ next.push({ checkoutId: id, sponsorId, name, logoUrl, ...channelId ? { channelId } : {} });
3132
3569
  writeEntries(next.slice(-CHECKOUT_SPONSOR_MEMORY_MAX));
3133
3570
  } catch {
3134
3571
  }
@@ -3156,6 +3593,20 @@ function rememberedSponsor(sponsorId) {
3156
3593
  return null;
3157
3594
  }
3158
3595
  }
3596
+ function highlightsOf(value) {
3597
+ if (!Array.isArray(value)) return [];
3598
+ const out = [];
3599
+ for (const item of value) {
3600
+ if (!item || typeof item !== "object") continue;
3601
+ const { icon, title, text: text3 } = item;
3602
+ const t = typeof title === "string" ? title.trim() : "";
3603
+ if (!t) continue;
3604
+ const body = typeof text3 === "string" ? text3.trim() : "";
3605
+ out.push({ icon: typeof icon === "string" ? icon.trim() : "", title: t, ...body ? { text: body } : {} });
3606
+ if (out.length === 3) break;
3607
+ }
3608
+ return out;
3609
+ }
3159
3610
 
3160
3611
  // src/core/checkout/payments/apple-pay.ts
3161
3612
  var STRIPE_JS_URL = "https://js.stripe.com/v3";
@@ -3353,7 +3804,7 @@ async function prepareApplePay(config) {
3353
3804
  }
3354
3805
 
3355
3806
  // src/core/version.ts
3356
- var SDK_VERSION = "0.29.0";
3807
+ var SDK_VERSION = "0.33.0";
3357
3808
 
3358
3809
  // src/core/checkout/payments/stripe-embedded.ts
3359
3810
  var CREATE_PAYMENT_INTENT_STRIPE_MUTATION = `
@@ -3519,11 +3970,8 @@ async function mountKlarnaExpressButton(opts) {
3519
3970
  const returnUrl = opts.returnUrl ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : "");
3520
3971
  let settled = false;
3521
3972
  button.on("click", () => {
3522
- if (opts.pendingKey && opts.pendingContext && typeof sessionStorage !== "undefined") {
3523
- try {
3524
- sessionStorage.setItem(opts.pendingKey, JSON.stringify(opts.pendingContext));
3525
- } catch {
3526
- }
3973
+ if (opts.pendingKey && opts.pendingContext) {
3974
+ storageSet("session", opts.pendingKey, JSON.stringify(opts.pendingContext));
3527
3975
  }
3528
3976
  const promise = sdk.Payment.initiate(() => ({
3529
3977
  currency: opts.paymentRequest.currency,
@@ -4601,6 +5049,74 @@ function adyenReturnOutcome(payment) {
4601
5049
  }
4602
5050
  }
4603
5051
 
5052
+ // src/core/checkout/phone.ts
5053
+ var CALLING_CODES = {
5054
+ NO: "47",
5055
+ SE: "46",
5056
+ DK: "45",
5057
+ FI: "358",
5058
+ IS: "354",
5059
+ DE: "49",
5060
+ GB: "44",
5061
+ UK: "44",
5062
+ IE: "353",
5063
+ NL: "31",
5064
+ BE: "32",
5065
+ FR: "33",
5066
+ ES: "34",
5067
+ PT: "351",
5068
+ IT: "39",
5069
+ AT: "43",
5070
+ CH: "41",
5071
+ PL: "48",
5072
+ EE: "372",
5073
+ LV: "371",
5074
+ LT: "370",
5075
+ CZ: "420",
5076
+ SK: "421",
5077
+ HU: "36",
5078
+ US: "1",
5079
+ CA: "1"
5080
+ };
5081
+ var KNOWN_CODES = [...new Set(Object.values(CALLING_CODES))].sort((a, b) => b.length - a.length);
5082
+ var NATIONAL_LENGTH = {
5083
+ "47": [8, 8],
5084
+ "45": [8, 8],
5085
+ "46": [7, 9],
5086
+ "358": [6, 10],
5087
+ "49": [10, 11],
5088
+ "44": [10, 10],
5089
+ "1": [10, 10]
5090
+ };
5091
+ function callingCodeFor(country) {
5092
+ return CALLING_CODES[String(country ?? "").trim().toUpperCase()] ?? null;
5093
+ }
5094
+ function parsePhone(raw, country) {
5095
+ const typed = String(raw ?? "").trim().replace(/[\s\-().]/g, "");
5096
+ if (!typed) return null;
5097
+ let code = null;
5098
+ let national;
5099
+ if (/^(\+|00)/.test(typed)) {
5100
+ const digits = typed.replace(/^(\+|00)/, "");
5101
+ if (!/^\d+$/.test(digits)) return null;
5102
+ code = KNOWN_CODES.find((c) => digits.startsWith(c)) ?? null;
5103
+ if (!code) return null;
5104
+ national = digits.slice(code.length);
5105
+ } else {
5106
+ if (!/^\d+$/.test(typed)) return null;
5107
+ code = callingCodeFor(country);
5108
+ if (!code) return null;
5109
+ national = typed;
5110
+ }
5111
+ if (national.startsWith("0") && code !== "47" && code !== "45") national = national.slice(1);
5112
+ const [min, max] = NATIONAL_LENGTH[code] ?? [6, 12];
5113
+ if (national.length < min || national.length > max) return null;
5114
+ return { phone: national, phone_code: code };
5115
+ }
5116
+ function isValidPhone(raw, country) {
5117
+ return parsePhone(raw, country) !== null;
5118
+ }
5119
+
4604
5120
  // src/core/checkout/checkout-manager.ts
4605
5121
  function describeError(err) {
4606
5122
  if (err instanceof Error) return err.message;
@@ -4618,6 +5134,7 @@ function describeError(err) {
4618
5134
  }
4619
5135
  return String(err);
4620
5136
  }
5137
+ var KLARNA_CONFIRMED_KEY = "vio.klarna.confirmed.v1";
4621
5138
  var KLARNA_PENDING_KEY = "vio.klarna.pending.v1";
4622
5139
  var KLARNA_SHIPPING_OPTIONS = [
4623
5140
  {
@@ -4781,15 +5298,20 @@ function buildCheckoutAddress(address) {
4781
5298
  const zip = address.postalCode || address.postal_code || address.zip || null;
4782
5299
  const country_code = address.countryCode || address.country_code || address.country || getGlobalCountryCode();
4783
5300
  if (!firstName && !lastName && !address1 && !city && !zip) return null;
5301
+ const phone = parsePhone(address.phone, country_code);
4784
5302
  return {
4785
5303
  first_name: firstName,
4786
5304
  last_name: lastName,
4787
5305
  address1,
4788
5306
  city,
4789
5307
  zip,
4790
- country_code
5308
+ country_code,
5309
+ ...phone ? { phone: phone.phone, phone_code: phone.phone_code } : {}
4791
5310
  };
4792
5311
  }
5312
+ function isCompleteCheckoutAddress(a) {
5313
+ return !!a && ["first_name", "last_name", "address1", "city", "zip"].every((k) => String(a[k] ?? "").trim().length > 0);
5314
+ }
4793
5315
  function adyenReturnUrl(params) {
4794
5316
  let page = null;
4795
5317
  if (typeof window !== "undefined" && window.location?.href) {
@@ -4851,8 +5373,20 @@ function resolveHttpsReturnUrl(overrideUrl, params = null) {
4851
5373
  }
4852
5374
  return url;
4853
5375
  }
4854
- async function executeKlarnaGraphQL(query, variables, sponsorId) {
4855
- return executeCartGraphQL(query, variables, await getCartGraphQLOptions(sponsorId));
5376
+ async function executeKlarnaGraphQL(query, variables, options) {
5377
+ return executeCartGraphQL(query, variables, options);
5378
+ }
5379
+ function usesStripe(methods) {
5380
+ if (!Array.isArray(methods)) return false;
5381
+ return methods.some((m) => {
5382
+ if (String(m?.name ?? "").toLowerCase() !== "stripe") return false;
5383
+ const config = Array.isArray(m?.config) ? m.config : [];
5384
+ const key = config.find((c) => c?.name === "publishableKey")?.value;
5385
+ return typeof key === "string" && key.startsWith("pk_");
5386
+ });
5387
+ }
5388
+ function cartTotal(items) {
5389
+ return items.reduce((sum, i) => sum + (Number(i.unitPrice) || 0) * (Number(i.quantity) || 0), 0);
4856
5390
  }
4857
5391
  var CheckoutManager = class _CheckoutManager extends EventTarget {
4858
5392
  constructor(cartManager, deps = {}) {
@@ -4907,7 +5441,9 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4907
5441
  /** In-flight `getOrderReference` per checkout id (see there). */
4908
5442
  this.referenceInFlight = /* @__PURE__ */ new Map();
4909
5443
  this.kustomController = null;
4910
- preloadStripeJs();
5444
+ if (Configuration.isInitialized && Configuration.get()?.stripePublishableKey) {
5445
+ preloadStripeJs();
5446
+ }
4911
5447
  if (typeof cartManager.addEventListener === "function") {
4912
5448
  cartManager.addEventListener("change", () => this.onCartChange());
4913
5449
  }
@@ -4921,8 +5457,44 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4921
5457
  return this.currentStateValue;
4922
5458
  }
4923
5459
  set state(next) {
5460
+ if (next?.checkoutId && next.sponsorId && !next.channelId) {
5461
+ const remembered = rememberedCheckoutSponsor(next.checkoutId);
5462
+ const channelId = (remembered?.sponsorId === next.sponsorId ? remembered.channelId : void 0) ?? this.cartManager.getCart(next.sponsorId)?.channelId;
5463
+ if (channelId) next = { ...next, channelId };
5464
+ }
4924
5465
  this.currentStateValue = next;
4925
- if (next?.checkoutId && next.sponsorId) this.rememberSponsorOfCheckout(next.checkoutId, next.sponsorId);
5466
+ if (next?.checkoutId && next.sponsorId) {
5467
+ this.rememberSponsorOfCheckout(next.checkoutId, next.sponsorId, next.channelId);
5468
+ }
5469
+ }
5470
+ /**
5471
+ * The options of a call for a sponsor's cart or checkout: the key of the
5472
+ * channel the checkout lives on — what this tab remembered for the
5473
+ * checkout (a return from a redirect, after the cart was cleared), the open
5474
+ * checkout's, else the cart's — even if a bootstrap refresh no longer lists
5475
+ * it (an open checkout keeps paying with its own key). Without any, the
5476
+ * sponsor's block as `getCartGraphQLOptions` resolves it.
5477
+ */
5478
+ async gqlOptions(sponsorId, checkoutId) {
5479
+ const channelId = this.channelOf(sponsorId, checkoutId);
5480
+ return getCartGraphQLOptions(sponsorId, channelId ? { channelId, retired: true } : void 0);
5481
+ }
5482
+ /** See `gqlOptions`. */
5483
+ channelOf(sponsorId, checkoutId) {
5484
+ try {
5485
+ if (!sponsorId) return void 0;
5486
+ const state = this.state?.sponsorId === sponsorId ? this.state : null;
5487
+ const id = checkoutId ?? state?.checkoutId;
5488
+ if (id) {
5489
+ const remembered = rememberedCheckoutSponsor(id);
5490
+ if (remembered?.channelId && remembered.sponsorId === sponsorId) return remembered.channelId;
5491
+ if (state?.checkoutId === id && state.channelId) return state.channelId;
5492
+ }
5493
+ const cart = this.cartManager.getCart(sponsorId);
5494
+ return cart?.cartId ? cart.channelId : void 0;
5495
+ } catch {
5496
+ return void 0;
5497
+ }
4926
5498
  }
4927
5499
  /** The open checkout's lines as a signature, to tell a real change from an echo. */
4928
5500
  cartSignature(sponsorId) {
@@ -4944,6 +5516,9 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4944
5516
  if (!cart || cart.items.length === 0) {
4945
5517
  throw new Error(`[VioCheckout] sponsor ${sponsorId} has no cart items`);
4946
5518
  }
5519
+ if (!(cartTotal(cart.items) > 0)) {
5520
+ throw new Error(`[VioCheckout] sponsor ${sponsorId} has a cart that totals 0`);
5521
+ }
4947
5522
  this.state = {
4948
5523
  sponsorId,
4949
5524
  session: ++this.sessionSeq,
@@ -4994,7 +5569,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4994
5569
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${sponsorId}`);
4995
5570
  }
4996
5571
  }
4997
- const opts = await getCartGraphQLOptions(sponsorId);
5572
+ const opts = await this.gqlOptions(sponsorId);
4998
5573
  const attribution = await this.attributionFor(sponsorId);
4999
5574
  const checkout = attribution ? await createCheckout(cartId, opts, { attribution }) : await createCheckout(cartId, opts);
5000
5575
  if (checkout?.id) {
@@ -5059,10 +5634,25 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5059
5634
  } catch {
5060
5635
  }
5061
5636
  }
5637
+ /**
5638
+ * Nothing is created or paid for a cart that is empty or totals 0 — as
5639
+ * the SERVER has it (the callers read it from the server first). Throws,
5640
+ * so a payment button ends in its error path, never at a provider asking
5641
+ * for 0 kr.
5642
+ */
5643
+ assertPayableCart(sponsorId) {
5644
+ const cart = this.cartManager.getCart(sponsorId);
5645
+ if (!cart || !Array.isArray(cart.items) || cart.items.length === 0) {
5646
+ throw new Error(`[CheckoutManager] the cart of sponsor ${sponsorId} is empty \u2014 nothing to pay`);
5647
+ }
5648
+ if (!(cartTotal(cart.items) > 0)) {
5649
+ throw new Error(`[CheckoutManager] the cart of sponsor ${sponsorId} totals 0 \u2014 nothing to pay`);
5650
+ }
5651
+ }
5062
5652
  /** Update the backend checkout (terms are re-asserted unless overridden; the attribution rides along unless the caller gives its own). */
5063
5653
  async updateCheckout(variables, sponsorId) {
5064
5654
  const spId = sponsorId ?? this.state?.sponsorId;
5065
- const opts = await getCartGraphQLOptions(spId);
5655
+ const opts = await this.gqlOptions(spId);
5066
5656
  const attribution = "attribution" in variables ? void 0 : await this.attributionFor(spId);
5067
5657
  const fullVars = {
5068
5658
  buyerAcceptsPurchaseConditions: true,
@@ -5078,7 +5668,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5078
5668
  return updated;
5079
5669
  }
5080
5670
  async getCheckout(checkoutId, sponsorId) {
5081
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
5671
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5082
5672
  return getCheckout(checkoutId, opts);
5083
5673
  }
5084
5674
  /**
@@ -5101,8 +5691,9 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5101
5691
  if (!request) {
5102
5692
  const asked = (async () => {
5103
5693
  try {
5104
- const opts = await getCartGraphQLOptions(
5105
- sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId
5694
+ const opts = await this.gqlOptions(
5695
+ sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId,
5696
+ checkoutId
5106
5697
  );
5107
5698
  return await getCheckoutReference(checkoutId, opts);
5108
5699
  } catch {
@@ -5155,7 +5746,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5155
5746
  * sponsor's name and logo as known now — so a confirmation after a redirect
5156
5747
  * or a reload can name the brand. Written again only when something changed.
5157
5748
  */
5158
- rememberSponsorOfCheckout(checkoutId, sponsorId) {
5749
+ rememberSponsorOfCheckout(checkoutId, sponsorId, channelId) {
5159
5750
  try {
5160
5751
  let identity = null;
5161
5752
  try {
@@ -5165,10 +5756,10 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5165
5756
  }
5166
5757
  const name = identity?.name || this.cartManager.getCart(sponsorId)?.sponsorName || "";
5167
5758
  const logoUrl = identity?.logoUrl ?? null;
5168
- const key = JSON.stringify([checkoutId, sponsorId, name, logoUrl]);
5759
+ const key = JSON.stringify([checkoutId, sponsorId, name, logoUrl, channelId ?? null]);
5169
5760
  if (key === this.lastRememberedSponsor) return;
5170
5761
  this.lastRememberedSponsor = key;
5171
- rememberCheckoutSponsor(checkoutId, { sponsorId, name, logoUrl });
5762
+ rememberCheckoutSponsor(checkoutId, { sponsorId, name, logoUrl, channelId });
5172
5763
  } catch {
5173
5764
  }
5174
5765
  }
@@ -5186,9 +5777,10 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5186
5777
  if (inFlight) return inFlight;
5187
5778
  const promise = (async () => {
5188
5779
  try {
5189
- const opts = await getCartGraphQLOptions(spId);
5780
+ const opts = await this.gqlOptions(spId);
5190
5781
  const res = await getAvailablePaymentMethods(opts);
5191
5782
  if (res) this.paymentMethodsCache.set(spId, res);
5783
+ if (usesStripe(res)) preloadStripeJs();
5192
5784
  return res;
5193
5785
  } finally {
5194
5786
  this.paymentMethodsPromises.delete(spId);
@@ -5222,7 +5814,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5222
5814
  cartId = await this.cartManager.ensureCartId(spId);
5223
5815
  }
5224
5816
  if (!cartId) return [];
5225
- const opts = await getCartGraphQLOptions(spId);
5817
+ const opts = await this.gqlOptions(spId);
5226
5818
  const supplierGroups = await getLineItemsBySupplier(cartId, opts);
5227
5819
  this.shippingsResolved = Array.isArray(supplierGroups);
5228
5820
  const firstGroup = Array.isArray(supplierGroups) ? supplierGroups[0] : null;
@@ -5276,7 +5868,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5276
5868
  if (!spId) return null;
5277
5869
  const cart = this.cartManager.getCart(spId);
5278
5870
  if (!cart?.cartId) return null;
5279
- const opts = await getCartGraphQLOptions(spId);
5871
+ const opts = await this.gqlOptions(spId);
5280
5872
  const updated = await updateShippingsBySupplier(cart.cartId, data, opts);
5281
5873
  if (updated) {
5282
5874
  this.cartManager.updateFromBackendCart(spId, updated);
@@ -5288,11 +5880,21 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5288
5880
  * backend cart + checkout exist, push email/address onto the checkout, and
5289
5881
  * return everything the payment mutation needs.
5290
5882
  */
5291
- async prepareRedirectPayment(sponsorId, formData, extraUpdateVars = {}, leaving = false) {
5883
+ async prepareRedirectPayment(sponsorId, formData, extraUpdateVars = {}, leaving = false, vippsWithAddress = false) {
5292
5884
  const spId = sponsorId ?? this.state?.sponsorId;
5293
5885
  if (!spId) throw new Error("[CheckoutManager] no sponsor for payment");
5294
5886
  await this.cartManager.waitForCartMutations(spId);
5295
5887
  await this.cartManager.refreshFromServer(spId);
5888
+ this.assertPayableCart(spId);
5889
+ const isVipps = extraUpdateVars?.paymentMethod === "Vipps";
5890
+ const addr = typeof formData === "object" && formData !== null ? formData : this.state?.address;
5891
+ const built = buildCheckoutAddress(addr);
5892
+ if (isVipps && vippsWithAddress && !(isCompleteCheckoutAddress(built) && built?.phone)) {
5893
+ throw new Error(
5894
+ "[CheckoutManager] the standard Vipps payment needs the complete delivery address (name, street, postcode, city and a mobile number) \u2014 no payment started"
5895
+ );
5896
+ }
5897
+ const addressObj = !isVipps ? built : vippsWithAddress ? built : null;
5296
5898
  const cart = this.cartManager.getCart(spId);
5297
5899
  let cartId = cart?.cartId;
5298
5900
  if (!cartId) {
@@ -5301,7 +5903,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5301
5903
  if (!cartId) {
5302
5904
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5303
5905
  }
5304
- const opts = await getCartGraphQLOptions(spId);
5906
+ const opts = await this.gqlOptions(spId);
5305
5907
  const attribution = await this.attributionFor(spId);
5306
5908
  let checkoutId = this.state?.checkoutId;
5307
5909
  if (!checkoutId) {
@@ -5315,9 +5917,6 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5315
5917
  if (!checkoutId) {
5316
5918
  throw new Error("[CheckoutManager] no backend checkout \u2014 cannot start the payment");
5317
5919
  }
5318
- const isVipps = extraUpdateVars?.paymentMethod === "Vipps";
5319
- const addr = typeof formData === "object" && formData !== null ? formData : this.state?.address;
5320
- const addressObj = !isVipps ? buildCheckoutAddress(addr) : null;
5321
5920
  const emailVal = (typeof formData === "string" ? formData : formData?.email) || this.state?.address?.email || "";
5322
5921
  const hasEmail = !!emailVal && emailVal.includes("@");
5323
5922
  if (!hasEmail && !isVipps) {
@@ -5412,6 +6011,11 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5412
6011
  * so the caller can open the ordinary checkout instead. Without the flag
5413
6012
  * the backend decides: Express while the cart has no delivery yet, a
5414
6013
  * plain payment once the shopper chose one in our form.
6014
+ *
6015
+ * `express: false` is the checkout's standard path (2026-10-28): the
6016
+ * form's address — name, street, mobile — goes on the checkout before the
6017
+ * payment is created, and the delivery already chosen on the cart is what
6018
+ * Vipps charges for.
5415
6019
  */
5416
6020
  async startVippsPayment(sponsorId, formData, options = {}) {
5417
6021
  const waitFor = sponsorId ?? this.state?.sponsorId;
@@ -5420,7 +6024,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5420
6024
  sponsorId,
5421
6025
  formData,
5422
6026
  { paymentMethod: "Vipps" },
5423
- true
6027
+ true,
6028
+ options.express === false
5424
6029
  );
5425
6030
  const returnUrl = resolveHttpsReturnUrl(null, {
5426
6031
  vio_payment: "success",
@@ -5457,7 +6062,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5457
6062
  /** Vipps' own payment state for the checkout — see getVippsStatus in
5458
6063
  * cart-queries.ts for why this exists instead of trusting GetCheckout. */
5459
6064
  async getVippsStatus(checkoutId, sponsorId) {
5460
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6065
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5461
6066
  return getVippsStatus({ checkoutId }, opts);
5462
6067
  }
5463
6068
  /**
@@ -5474,7 +6079,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5474
6079
  if (!cartId) {
5475
6080
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5476
6081
  }
5477
- const opts = await getCartGraphQLOptions(spId);
6082
+ const opts = await this.gqlOptions(spId);
5478
6083
  let checkoutId = this.state?.checkoutId;
5479
6084
  if (!checkoutId) {
5480
6085
  const checkoutRes = await this.createCheckout(spId, "kustom");
@@ -5520,7 +6125,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5520
6125
  const spId = sponsorId ?? this.state?.sponsorId;
5521
6126
  const checkoutId = this.state?.checkoutId;
5522
6127
  if (!spId || !checkoutId) return null;
5523
- const opts = await getCartGraphQLOptions(spId);
6128
+ const opts = await this.gqlOptions(spId);
5524
6129
  suspendKustom();
5525
6130
  try {
5526
6131
  return await syncPaymentKustom(checkoutId, opts);
@@ -5530,7 +6135,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5530
6135
  }
5531
6136
  /** Re-read a Kustom order — used on the confirmation return trip. */
5532
6137
  async getKustomOrder(orderId, sponsorId) {
5533
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6138
+ const opts = await this.gqlOptions(sponsorId ?? this.state?.sponsorId);
5534
6139
  return getKustomOrder(orderId, opts);
5535
6140
  }
5536
6141
  /** Render a Kustom html_snippet (e.g. the confirmation receipt) into a container. */
@@ -5550,7 +6155,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5550
6155
  if (!cartId) {
5551
6156
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5552
6157
  }
5553
- const opts = await getCartGraphQLOptions(spId);
6158
+ const opts = await this.gqlOptions(spId);
5554
6159
  let checkoutId = this.state?.checkoutId;
5555
6160
  if (!checkoutId) {
5556
6161
  const checkoutRes = await this.createCheckout(spId, "qliro");
@@ -5620,7 +6225,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5620
6225
  }
5621
6226
  /** Re-read the Qliro order owned by a checkout — the confirmation trip. */
5622
6227
  async getQliroOrder(checkoutId, sponsorId) {
5623
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6228
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5624
6229
  return getQliroOrder(checkoutId, opts);
5625
6230
  }
5626
6231
  /**
@@ -5638,7 +6243,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5638
6243
  if (!cartId) {
5639
6244
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5640
6245
  }
5641
- const opts = await getCartGraphQLOptions(spId);
6246
+ const opts = await this.gqlOptions(spId);
5642
6247
  let checkoutId = this.state?.checkoutId;
5643
6248
  if (!checkoutId) {
5644
6249
  const checkoutRes = await this.createCheckout(spId, "walley");
@@ -5675,7 +6280,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5675
6280
  }
5676
6281
  /** Re-read the Walley session owned by a checkout — the confirmation trip. */
5677
6282
  async getWalleyOrder(checkoutId, sponsorId) {
5678
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6283
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5679
6284
  return getWalleyOrder(checkoutId, opts);
5680
6285
  }
5681
6286
  /**
@@ -5701,7 +6306,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5701
6306
  if (!cartId) {
5702
6307
  throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
5703
6308
  }
5704
- const opts = await getCartGraphQLOptions(spId);
6309
+ const opts = await this.gqlOptions(spId);
5705
6310
  const stored = resume ? readNexiPending() : null;
5706
6311
  const pending2 = stored && stored.paymentId === resume?.paymentId ? stored : null;
5707
6312
  let checkoutId = this.state?.checkoutId;
@@ -5935,7 +6540,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
5935
6540
  }
5936
6541
  /** Re-read the Nexi payment owned by a checkout. */
5937
6542
  async getNexiOrder(checkoutId, sponsorId) {
5938
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6543
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
5939
6544
  return getNexiOrder(checkoutId, opts);
5940
6545
  }
5941
6546
  /**
@@ -6013,7 +6618,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6013
6618
  * finalised on the server.
6014
6619
  */
6015
6620
  async getAdyenPayment(checkoutId, proof = {}, sponsorId) {
6016
- const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
6621
+ const opts = await this.gqlOptions(sponsorId ?? this.sponsorIdOfCheckout(checkoutId) ?? this.state?.sponsorId, checkoutId);
6017
6622
  return getAdyenPayment({ checkoutId, ...proof }, opts);
6018
6623
  }
6019
6624
  /* eslint-enable @typescript-eslint/no-explicit-any */
@@ -6292,7 +6897,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6292
6897
  console.warn("[VioCheckout] updateCheckout before Apple Pay confirm failed:", err);
6293
6898
  }
6294
6899
  }
6295
- const gqlOpts = await getCartGraphQLOptions(spId);
6900
+ const gqlOpts = await this.gqlOptions(spId);
6296
6901
  const createRes = await createPaymentApplePay({ checkoutId }, gqlOpts);
6297
6902
  const gatewayId = String(createRes?.gateway_merchant_id ?? "");
6298
6903
  if (gatewayId.startsWith("pk_")) this.applePayPublishableKey = gatewayId;
@@ -6544,7 +7149,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6544
7149
  const json = await executeKlarnaGraphQL(
6545
7150
  CREATE_PAYMENT_KLARNA_NATIVE_MUTATION,
6546
7151
  variables,
6547
- sponsorId
7152
+ await this.gqlOptions(sponsorId, checkoutId)
6548
7153
  );
6549
7154
  const nativeRes = json?.data?.Payment?.CreatePaymentKlarnaNative;
6550
7155
  if (!nativeRes || !nativeRes.client_token) {
@@ -6694,7 +7299,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6694
7299
  const json = await executeKlarnaGraphQL(
6695
7300
  CREATE_PAYMENT_KLARNA_NATIVE_MUTATION,
6696
7301
  variables,
6697
- sponsorId
7302
+ await this.gqlOptions(sponsorId, checkoutId)
6698
7303
  );
6699
7304
  const nativeRes = json?.data?.Payment?.CreatePaymentKlarnaNative;
6700
7305
  if (!nativeRes || !nativeRes.client_token) {
@@ -6757,14 +7362,13 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6757
7362
  * the SDK's resumed `complete` event — finish creating the order + confirm.
6758
7363
  */
6759
7364
  async resumeKlarnaReturn() {
6760
- if (typeof sessionStorage === "undefined") return;
6761
- const raw = sessionStorage.getItem(KLARNA_PENDING_KEY);
7365
+ const raw = storageGet("session", KLARNA_PENDING_KEY);
6762
7366
  if (!raw) return;
6763
7367
  let pending2;
6764
7368
  try {
6765
7369
  pending2 = JSON.parse(raw);
6766
7370
  } catch {
6767
- sessionStorage.removeItem(KLARNA_PENDING_KEY);
7371
+ storageRemove("session", KLARNA_PENDING_KEY);
6768
7372
  return;
6769
7373
  }
6770
7374
  const fromUrl = readKlarnaTokenFromUrl();
@@ -6822,7 +7426,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6822
7426
  async completeKlarnaOrder(authorizationToken, ctx, result) {
6823
7427
  if (this.klarnaOrderInFlight) return;
6824
7428
  this.klarnaOrderInFlight = true;
6825
- if (typeof sessionStorage !== "undefined" && sessionStorage.getItem("vio.klarna.confirmed.v1") === authorizationToken) {
7429
+ if (storageGet("session", KLARNA_CONFIRMED_KEY) === authorizationToken) {
6826
7430
  this.klarnaOrderInFlight = false;
6827
7431
  return;
6828
7432
  }
@@ -6837,12 +7441,10 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6837
7441
  shippingAddress: buildKlarnaAddress(address),
6838
7442
  customer: buildKlarnaCustomer(address)
6839
7443
  };
7444
+ const klarnaSponsorId = ctx?.sponsorId ?? this.state?.sponsorId;
7445
+ const klarnaOptions = await this.gqlOptions(klarnaSponsorId, ctx?.checkoutId ?? this.state?.checkoutId);
6840
7446
  const json = await withBuyerRef(
6841
- (withRef) => executeKlarnaGraphQL(
6842
- confirmPaymentKlarnaNativeMutation(withRef),
6843
- variables,
6844
- ctx?.sponsorId ?? this.state?.sponsorId
6845
- )
7447
+ (withRef) => executeKlarnaGraphQL(confirmPaymentKlarnaNativeMutation(withRef), variables, klarnaOptions)
6846
7448
  );
6847
7449
  const confirmRes = json?.data?.Payment?.ConfirmPaymentKlarnaNative;
6848
7450
  if (!confirmRes) {
@@ -6850,10 +7452,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
6850
7452
  }
6851
7453
  const orderData = confirmRes.order || confirmRes;
6852
7454
  const buyerRef = normalizeBuyerRef(confirmRes[BUYER_REF_FIELD]);
6853
- if (typeof sessionStorage !== "undefined") {
6854
- sessionStorage.setItem("vio.klarna.confirmed.v1", authorizationToken);
6855
- sessionStorage.removeItem(KLARNA_PENDING_KEY);
6856
- }
7455
+ storageSet("session", KLARNA_CONFIRMED_KEY, authorizationToken);
7456
+ storageRemove("session", KLARNA_PENDING_KEY);
6857
7457
  const state = this.state ?? { sponsorId: ctx.sponsorId, subtotal: ctx.orderAmount, currency: ctx.currency };
6858
7458
  this.dispatchEvent(
6859
7459
  new CustomEvent("payment-complete", {
@@ -7095,6 +7695,54 @@ function safeCall(fn) {
7095
7695
  }
7096
7696
  }
7097
7697
 
7698
+ // src/core/analytics/internal-traffic.ts
7699
+ var INTERNAL_KEY = "vio.internal.v1";
7700
+ var INTERNAL_PARAM = "vio_internal";
7701
+ function defaultInternalEnvironment() {
7702
+ return {
7703
+ location: typeof location !== "undefined" ? location : null,
7704
+ storage: localStorageOrNull()
7705
+ };
7706
+ }
7707
+ function resolveInternal(env = defaultInternalEnvironment()) {
7708
+ const param = internalParam(env.location?.search);
7709
+ if (param !== void 0) {
7710
+ writeInternal(param, env.storage);
7711
+ return param;
7712
+ }
7713
+ return readInternal(env.storage);
7714
+ }
7715
+ function readInternal(storage2 = defaultInternalEnvironment().storage) {
7716
+ try {
7717
+ return storage2?.getItem(INTERNAL_KEY) === "1";
7718
+ } catch {
7719
+ return false;
7720
+ }
7721
+ }
7722
+ function writeInternal(internal, storage2 = defaultInternalEnvironment().storage) {
7723
+ try {
7724
+ if (internal) storage2?.setItem(INTERNAL_KEY, "1");
7725
+ else storage2?.removeItem?.(INTERNAL_KEY);
7726
+ } catch {
7727
+ }
7728
+ }
7729
+ function internalParam(search) {
7730
+ try {
7731
+ const value = new URLSearchParams(typeof search === "string" ? search : "").get(INTERNAL_PARAM)?.trim();
7732
+ if (value === "1") return true;
7733
+ if (value === "0") return false;
7734
+ } catch {
7735
+ }
7736
+ return void 0;
7737
+ }
7738
+ function localStorageOrNull() {
7739
+ try {
7740
+ return typeof window !== "undefined" && window.localStorage ? window.localStorage : null;
7741
+ } catch {
7742
+ return null;
7743
+ }
7744
+ }
7745
+
7098
7746
  // src/core/cart/slices.ts
7099
7747
  function cartSlicesFrom(lines, resolveCampaign, fallbackCurrency) {
7100
7748
  if (!Array.isArray(lines) || lines.length === 0) return [];
@@ -7171,6 +7819,7 @@ var FLUSH_AT_COUNT = 20;
7171
7819
  var MAX_QUEUE = 500;
7172
7820
  var RETRY_BACKOFF_MS = [2e3, 4e3, 8e3];
7173
7821
  var ANON_KEY = "vio.anon.v1";
7822
+ var MAX_ITEMS = 100;
7174
7823
  var SESSION_KEY = "vio.session.v1";
7175
7824
  var IMPRESSION_THRESHOLD = 0.5;
7176
7825
  var IMPRESSION_DWELL_MS = 1e3;
@@ -7178,7 +7827,14 @@ var AnalyticsManager = class extends EventTarget {
7178
7827
  constructor() {
7179
7828
  super(...arguments);
7180
7829
  this.started = false;
7181
- this.options = { host: "custom", collector: true, dataLayer: false, debug: false, autoTrack: true };
7830
+ this.options = {
7831
+ host: "custom",
7832
+ collector: true,
7833
+ dataLayer: false,
7834
+ debug: false,
7835
+ autoTrack: true,
7836
+ privateContent: false
7837
+ };
7182
7838
  this.queue = [];
7183
7839
  this.flushTimer = null;
7184
7840
  this.retryAttempt = 0;
@@ -7199,6 +7855,14 @@ var AnalyticsManager = class extends EventTarget {
7199
7855
  this.client = null;
7200
7856
  /** The landing it was read from — persisted later when consent arrives after the first event. */
7201
7857
  this.landing = null;
7858
+ /**
7859
+ * Internal traffic (ADR-0041): `client.internal: true` on every event while
7860
+ * set. Read once (`?vio_internal=1|0`, else `localStorage`) at `Vio.init()`
7861
+ * or on the first event; `setInternal()` changes it. `null` = not read yet.
7862
+ */
7863
+ this.internal = null;
7864
+ /** The console line for testers goes out once per page load. */
7865
+ this.internalAnnounced = false;
7202
7866
  /** Checkouts (`c:<id>`) and orders (`o:<number>`) this page load reported a `purchase` for: one each. */
7203
7867
  this.purchased = /* @__PURE__ */ new Set();
7204
7868
  /** Checkouts this page load reported an `add_payment_info` for: one each. */
@@ -7248,7 +7912,8 @@ var AnalyticsManager = class extends EventTarget {
7248
7912
  collector: options.collector ?? this.options.collector,
7249
7913
  dataLayer: options.dataLayer ?? this.options.dataLayer,
7250
7914
  debug: options.debug ?? this.options.debug,
7251
- autoTrack: options.autoTrack ?? this.options.autoTrack
7915
+ autoTrack: options.autoTrack ?? this.options.autoTrack,
7916
+ privateContent: options.privateContent ?? this.options.privateContent
7252
7917
  };
7253
7918
  if (options.sessionId) this.externalSessionId = options.sessionId;
7254
7919
  if (this.started || !hasDom()) return;
@@ -7291,6 +7956,43 @@ var AnalyticsManager = class extends EventTarget {
7291
7956
  identify(externalUserId) {
7292
7957
  this.externalUserId = externalUserId ?? void 0;
7293
7958
  }
7959
+ /**
7960
+ * Mark (or unmark) this browser as Vio's own traffic (ADR-0041): while
7961
+ * marked, every event carries `client.internal: true` and the collector's
7962
+ * reads leave it out. Remembered in `localStorage` (`vio.internal.v1`) —
7963
+ * the same mark `?vio_internal=1` / `?vio_internal=0` sets on any page.
7964
+ */
7965
+ setInternal(internal) {
7966
+ this.internal = internal === true;
7967
+ writeInternal(this.internal);
7968
+ this.announceInternal();
7969
+ }
7970
+ /** Whether this browser is marked as internal traffic (reads the mark the first time). */
7971
+ get isInternal() {
7972
+ return this.syncInternal();
7973
+ }
7974
+ /**
7975
+ * Read the mark: the page's `?vio_internal=1|0` (remembered), else what the
7976
+ * browser remembers. Once per page load — `Vio.init()` calls it so a page
7977
+ * with the parameter marks the browser even if it never starts analytics;
7978
+ * `force` reads again.
7979
+ */
7980
+ syncInternal(force = false) {
7981
+ if (this.internal === null || force) {
7982
+ this.internal = hasDom() ? resolveInternal() : readInternal(null);
7983
+ this.announceInternal();
7984
+ }
7985
+ return this.internal;
7986
+ }
7987
+ /** One console line when marked, so a tester can see the mark took. */
7988
+ announceInternal() {
7989
+ if (!this.internal || this.internalAnnounced) return;
7990
+ this.internalAnnounced = true;
7991
+ try {
7992
+ console.info("[Vio.analytics] this browser is marked as internal traffic (vio_internal=1): its events are not counted. Open any page with ?vio_internal=0 to undo.");
7993
+ } catch {
7994
+ }
7995
+ }
7294
7996
  /** Queue one event. Works pre-init and pre-start (SSR: no-op). */
7295
7997
  track(name, opts = {}) {
7296
7998
  if (!hasDom()) return;
@@ -7335,7 +8037,7 @@ var AnalyticsManager = class extends EventTarget {
7335
8037
  this.ensureSession();
7336
8038
  if (this.impressedComponents.has(key)) return;
7337
8039
  this.impressedComponents.add(key);
7338
- this.track(name, { context });
8040
+ this.track(name, { context, commerce: impressionCommerce(opts.commerce) });
7339
8041
  opts.onImpression?.();
7340
8042
  observer.disconnect();
7341
8043
  }, IMPRESSION_DWELL_MS);
@@ -7416,26 +8118,28 @@ var AnalyticsManager = class extends EventTarget {
7416
8118
  // one is usually the tracking tail, not the page. The other host-given
7417
8119
  // fields (broadcastId, componentTemplateId, errorCode, variant, …) are
7418
8120
  // the host's responsibility: the SDK does not touch them.
7419
- content_url: capContentUrl(ctx.contentUrl ?? (typeof location !== "undefined" ? location.href : void 0)),
8121
+ // Con `privateContent` ninguno de los tres sale del dispositivo.
8122
+ content_url: this.options.privateContent ? void 0 : capContentUrl(ctx.contentUrl ?? (typeof location !== "undefined" ? location.href : void 0)),
7420
8123
  // The host's own content id — its join key back onto its reporting.
7421
8124
  // Explicit on the event when it fits, else what the SDK resolved for
7422
8125
  // the page (already capped per source), else nothing.
7423
- content_id: explicitContentId ?? fitsId(content.id),
8126
+ content_id: this.options.privateContent ? void 0 : explicitContentId ?? fitsId(content.id),
7424
8127
  // Deletion-proof reporting: sources have no trash bin — the title
7425
8128
  // snapshotted at event time keeps reports legible if content dies.
7426
8129
  error_code: ctx.errorCode,
7427
- content_title: capTitle(
8130
+ content_title: this.options.privateContent ? void 0 : capTitle(
7428
8131
  ctx.contentTitle ?? content.title ?? (typeof document !== "undefined" && document.title ? document.title : void 0)
7429
8132
  ),
7430
8133
  variant: ctx.variant
7431
8134
  });
7432
8135
  const com = opts.commerce;
7433
8136
  const commerce = com ? compact({
7434
- items: com.items?.map(
8137
+ // The collector rejects an event with more lines than its cap, whole.
8138
+ items: com.items?.slice(0, MAX_ITEMS).map(
7435
8139
  (i) => compact({
7436
8140
  product_id: String(i.productId),
7437
- name: i.name,
7438
- brand: i.brand,
8141
+ name: this.options.privateContent ? void 0 : i.name,
8142
+ brand: this.options.privateContent ? void 0 : i.brand,
7439
8143
  variant_id: i.variantId !== void 0 ? String(i.variantId) : void 0,
7440
8144
  price: i.price,
7441
8145
  quantity: i.quantity
@@ -7452,7 +8156,8 @@ var AnalyticsManager = class extends EventTarget {
7452
8156
  // ids does not carry a stable identifier of a person either.
7453
8157
  buyer_id: this.canPersist ? normalizeBuyerRef(com.buyerId) : void 0
7454
8158
  }) : void 0;
7455
- const client = this.clientBlock();
8159
+ const base = this.clientBlock();
8160
+ const client = this.syncInternal() ? { ...base ?? {}, internal: true } : base;
7456
8161
  return {
7457
8162
  event_id: uuid(),
7458
8163
  name,
@@ -7981,6 +8686,15 @@ function safeStorageSet(key, value) {
7981
8686
  } catch {
7982
8687
  }
7983
8688
  }
8689
+ function impressionCommerce(read) {
8690
+ if (!read) return void 0;
8691
+ try {
8692
+ const commerce = read();
8693
+ return commerce?.items?.length ? commerce : void 0;
8694
+ } catch {
8695
+ return void 0;
8696
+ }
8697
+ }
7984
8698
  function compact(obj) {
7985
8699
  const out = {};
7986
8700
  for (const [k, v] of Object.entries(obj)) {
@@ -8254,6 +8968,8 @@ function cut3(value, max) {
8254
8968
 
8255
8969
  // src/core/client.ts
8256
8970
  var SLOT_DECLARATION_DEBOUNCE_MS = 1e3;
8971
+ var BOOTSTRAP_MAX_AGE_MS = 5 * 6e4;
8972
+ var BOOTSTRAP_REFRESH_BACKOFF_MS = 6e4;
8257
8973
  var PRIVACY_MODE_TIMEOUT_MS = 1500;
8258
8974
  var PRIVACY_UNKNOWN_TTL_MS = 6e4;
8259
8975
  function privacyModeOf(value) {
@@ -8269,9 +8985,23 @@ function privacyModeOfSurface(res) {
8269
8985
  var VioFacade = class {
8270
8986
  constructor() {
8271
8987
  this.apiInstance = null;
8988
+ /** Commerce clients by sponsor AND key: one sponsor may sell on several channels (one per campaign). */
8272
8989
  this.commerceClients = /* @__PURE__ */ new Map();
8273
8990
  this.bootstrapPromise = null;
8274
8991
  this.cachedBootstrap = null;
8992
+ /** When the cached bootstrap arrived (ms epoch). */
8993
+ this.bootstrapAt = 0;
8994
+ /** A refresh of the bootstrap in flight (`refreshStaleBootstrap`). */
8995
+ this.bootstrapRefresh = null;
8996
+ this.bootstrapRefreshFailedAt = 0;
8997
+ /** The `visibilitychange` listener is installed once per page. */
8998
+ this.visibilityHooked = false;
8999
+ /**
9000
+ * Every selling block this page has seen, by `sponsorId:channelId` — so a
9001
+ * checkout opened on a channel keeps its key after a bootstrap refresh no
9002
+ * longer lists that channel (`sponsorCommerce(…, { retired: true })`).
9003
+ */
9004
+ this.seenChannels = /* @__PURE__ */ new Map();
8275
9005
  /** Last bootstrap failure — short backoff so cart mutations don't hammer a
8276
9006
  * dead endpoint on every call. */
8277
9007
  this.bootstrapFailedAt = 0;
@@ -8295,7 +9025,17 @@ var VioFacade = class {
8295
9025
  this.warnedDeclarationCap = false;
8296
9026
  // Client-side managers — created lazily on first access. They don't
8297
9027
  // need Vio.init() to work (state is purely local).
8298
- this.cartManager = null;
9028
+ /**
9029
+ * One cart manager per connection (environment + key), each with its own
9030
+ * storage entry; `Vio.cart` is a stable front for the current one, so a
9031
+ * re-init with another key switches carts without the page re-subscribing.
9032
+ * A manager left behind keeps its carts (they come back if the page switches
9033
+ * back) and finishes its requests into ITS carts, never the new one's.
9034
+ */
9035
+ this.cartManagers = /* @__PURE__ */ new Map();
9036
+ this.cartScope = "";
9037
+ this.cartFront = null;
9038
+ this.cartEvents = new EventTarget();
8299
9039
  this.checkoutManager = null;
8300
9040
  this.analyticsManager = null;
8301
9041
  /** What the surface answered of its privacy mode (`GET /v2/mobile/surface`) and when; `unknown` expires after a minute. */
@@ -8305,15 +9045,28 @@ var VioFacade = class {
8305
9045
  }
8306
9046
  init(config) {
8307
9047
  Configuration.init(config);
9048
+ try {
9049
+ const resolved = Configuration.get();
9050
+ this.switchCartScope(cartScopeOf(resolved.environment, resolved.apiKey));
9051
+ } catch {
9052
+ }
8308
9053
  this.apiInstance = null;
8309
9054
  this.commerceClients.clear();
8310
9055
  this.bootstrapPromise = null;
8311
9056
  this.cachedBootstrap = null;
9057
+ this.bootstrapAt = 0;
9058
+ this.bootstrapRefresh = null;
9059
+ this.bootstrapRefreshFailedAt = 0;
9060
+ this.seenChannels.clear();
8312
9061
  this.bootstrapFailedAt = 0;
8313
9062
  this.generation++;
8314
9063
  this.surfaceMode = null;
8315
9064
  this.surfaceRequest = null;
8316
9065
  this.preloadPrivacyMode();
9066
+ try {
9067
+ this.analytics.syncInternal(true);
9068
+ } catch {
9069
+ }
8317
9070
  this.checkoutManager?.clearPaymentMethodsCache();
8318
9071
  if (this.slotDeclareTimer) {
8319
9072
  clearTimeout(this.slotDeclareTimer);
@@ -8352,8 +9105,66 @@ var VioFacade = class {
8352
9105
  // MARK: - Client-side managers (no init required)
8353
9106
  /** Multi-sponsor cart manager. Reactive — listen via addEventListener('change', ...). */
8354
9107
  get cart() {
8355
- if (!this.cartManager) this.cartManager = new CartManager();
8356
- return this.cartManager;
9108
+ if (!this.cartFront) this.cartFront = this.makeCartFront();
9109
+ return this.cartFront;
9110
+ }
9111
+ /** The cart manager of the page's connection now (created on first use). */
9112
+ currentCartManager() {
9113
+ const scope = this.cartScope;
9114
+ let manager = this.cartManagers.get(scope);
9115
+ if (!manager) {
9116
+ const created = new CartManager(scope);
9117
+ for (const type of ["change", "error"]) {
9118
+ created.addEventListener(type, (e) => {
9119
+ if (this.cartManagers.get(this.cartScope) !== created) return;
9120
+ this.cartEvents.dispatchEvent(new CustomEvent(type, { detail: e.detail }));
9121
+ });
9122
+ }
9123
+ this.cartManagers.set(scope, created);
9124
+ manager = created;
9125
+ }
9126
+ return manager;
9127
+ }
9128
+ /**
9129
+ * `Vio.cart`: every property and method is the current connection's
9130
+ * manager's; the listeners live on the front, so they survive a switch.
9131
+ */
9132
+ makeCartFront() {
9133
+ const events = this.cartEvents;
9134
+ const listenerMethods = {
9135
+ addEventListener: events.addEventListener.bind(events),
9136
+ removeEventListener: events.removeEventListener.bind(events),
9137
+ dispatchEvent: events.dispatchEvent.bind(events)
9138
+ };
9139
+ return new Proxy(Object.create(CartManager.prototype), {
9140
+ get: (_target, prop) => {
9141
+ if (typeof prop === "string" && prop in listenerMethods) return listenerMethods[prop];
9142
+ const manager = this.currentCartManager();
9143
+ const value = Reflect.get(manager, prop, manager);
9144
+ if (typeof value !== "function" || Object.prototype.hasOwnProperty.call(manager, prop)) return value;
9145
+ return value.bind(manager);
9146
+ },
9147
+ set: (_target, prop, value) => Reflect.set(this.currentCartManager(), prop, value),
9148
+ has: (_target, prop) => Reflect.has(this.currentCartManager(), prop),
9149
+ // A property defined on Vio.cart (a test's spy, a host's patch) lands on
9150
+ // the current manager, where its own methods find it.
9151
+ defineProperty: (_target, prop, descriptor) => Reflect.defineProperty(this.currentCartManager(), prop, descriptor),
9152
+ deleteProperty: (_target, prop) => Reflect.deleteProperty(this.currentCartManager(), prop),
9153
+ getOwnPropertyDescriptor: (_target, prop) => {
9154
+ const own = Reflect.getOwnPropertyDescriptor(this.currentCartManager(), prop);
9155
+ return own ? { ...own, configurable: true } : void 0;
9156
+ }
9157
+ });
9158
+ }
9159
+ /** After `init`: the carts are the new connection's. Says `change` when that switched them. */
9160
+ switchCartScope(scope) {
9161
+ if (scope === this.cartScope) return;
9162
+ this.cartManagers.get(this.cartScope)?.setActive(false);
9163
+ this.cartScope = scope;
9164
+ if (!this.cartFront) return;
9165
+ const manager = this.currentCartManager();
9166
+ manager.setActive(true);
9167
+ manager.announce();
8357
9168
  }
8358
9169
  /** Checkout flow manager. Subscribes to cart state via the CartManager; asks here for the order's attribution. */
8359
9170
  get checkout() {
@@ -8401,7 +9212,7 @@ var VioFacade = class {
8401
9212
  const promise = this.api.bootstrap(Configuration.get().platform).then(
8402
9213
  (res) => {
8403
9214
  if (generation !== this.generation) return this.bootstrap();
8404
- this.cachedBootstrap = res;
9215
+ this.adoptBootstrap(res);
8405
9216
  this.bootstrapFailedAt = 0;
8406
9217
  if (this.slotsDirty) this.scheduleSlotDeclaration();
8407
9218
  this.preloadPrivacyMode();
@@ -8421,6 +9232,85 @@ var VioFacade = class {
8421
9232
  get bootstrapCache() {
8422
9233
  return this.cachedBootstrap;
8423
9234
  }
9235
+ /** A bootstrap becomes the page's: when it arrived, the channels it lists, and the tab-visible refresh. */
9236
+ adoptBootstrap(res) {
9237
+ this.cachedBootstrap = res;
9238
+ this.bootstrapAt = Date.now();
9239
+ try {
9240
+ const ids = /* @__PURE__ */ new Set();
9241
+ const add = (s) => {
9242
+ const id = toSponsorId(s?.id);
9243
+ if (id !== void 0) ids.add(id);
9244
+ };
9245
+ add(res?.primarySponsor);
9246
+ if (Array.isArray(res?.secondarySponsors)) res.secondarySponsors.forEach(add);
9247
+ if (Array.isArray(res?.campaigns)) {
9248
+ for (const c of res.campaigns) {
9249
+ if (!c || typeof c !== "object") continue;
9250
+ add(c.primarySponsor);
9251
+ if (Array.isArray(c.secondarySponsors)) c.secondarySponsors.forEach(add);
9252
+ }
9253
+ }
9254
+ for (const id of ids) {
9255
+ for (const { sponsor } of sellingBlocksIn(res, id)) {
9256
+ const channel = channelIdOf(sponsor.commerce.channelId);
9257
+ if (channel !== void 0) this.seenChannels.set(`${id}:${channel}`, sponsor);
9258
+ }
9259
+ }
9260
+ } catch {
9261
+ }
9262
+ this.hookVisibility();
9263
+ }
9264
+ /**
9265
+ * The bootstrap is cached for the page's life, but a tab can stay open for
9266
+ * days: when it becomes visible again and the cached one is older than
9267
+ * five minutes, it is fetched again (new campaigns, channels). The old one
9268
+ * stays until the new one arrives — and stays if the refresh fails. Carts
9269
+ * keep their lines; a cart whose channel the new bootstrap no longer lists
9270
+ * is moved to the current channel the next time it is used (CartManager),
9271
+ * and an open checkout keeps paying with the key of its own channel.
9272
+ */
9273
+ hookVisibility() {
9274
+ if (this.visibilityHooked) return;
9275
+ if (typeof document === "undefined" || typeof document.addEventListener !== "function") return;
9276
+ this.visibilityHooked = true;
9277
+ document.addEventListener("visibilitychange", () => {
9278
+ try {
9279
+ if (document.visibilityState === "visible") void this.refreshStaleBootstrap();
9280
+ } catch {
9281
+ }
9282
+ });
9283
+ }
9284
+ /** Fetch the bootstrap again when the cached one is older than `maxAgeMs`; never throws, never clears the cache. */
9285
+ refreshStaleBootstrap(maxAgeMs = BOOTSTRAP_MAX_AGE_MS) {
9286
+ try {
9287
+ if (this.bootstrapRefresh) return this.bootstrapRefresh;
9288
+ if (!this.cachedBootstrap || this.bootstrapPromise || !Configuration.isInitialized) return Promise.resolve();
9289
+ const now = Date.now();
9290
+ if (now - this.bootstrapAt < maxAgeMs) return Promise.resolve();
9291
+ if (this.bootstrapRefreshFailedAt && now - this.bootstrapRefreshFailedAt < BOOTSTRAP_REFRESH_BACKOFF_MS) {
9292
+ return Promise.resolve();
9293
+ }
9294
+ const generation = this.generation;
9295
+ const refresh = this.api.bootstrap(Configuration.get().platform).then(
9296
+ (res) => {
9297
+ if (generation !== this.generation || !res || typeof res !== "object") return;
9298
+ this.adoptBootstrap(res);
9299
+ this.bootstrapRefreshFailedAt = 0;
9300
+ if (this.slotsDirty) this.scheduleSlotDeclaration();
9301
+ },
9302
+ () => {
9303
+ if (generation === this.generation) this.bootstrapRefreshFailedAt = Date.now();
9304
+ }
9305
+ ).finally(() => {
9306
+ if (this.bootstrapRefresh === refresh) this.bootstrapRefresh = null;
9307
+ });
9308
+ this.bootstrapRefresh = refresh;
9309
+ return refresh;
9310
+ } catch {
9311
+ return Promise.resolve();
9312
+ }
9313
+ }
8424
9314
  /**
8425
9315
  * Who a sponsor is to a shopper — `{ id, name, logoUrl, avatarUrl }` — by
8426
9316
  * the ONE rule every element uses (`sponsorIdentityFrom`): the live
@@ -8455,22 +9345,60 @@ var VioFacade = class {
8455
9345
  }
8456
9346
  }
8457
9347
  /**
8458
- * Get a CommerceClient scoped to a specific sponsor (via bootstrap).
8459
- * Throws if bootstrap hasn't been called or the sponsor is unknown.
9348
+ * The block a sponsor sells with — its `commerce.apiKey` and `channelId` —
9349
+ * by the rule one article = one campaign = one channel = one sponsor (see
9350
+ * `sponsorCommerceIn`): `ctx.channelId` → that channel's block only (a cart
9351
+ * created on a channel is only called with its key); else the campaign in
9352
+ * context — `ctx.campaignId` candidates (the click's, the element's
9353
+ * `vio-campaign`, an ancestor's `data-vio-campaign`), then
9354
+ * `Vio.init({ campaignId })` — when the sponsor takes part in it; else the
9355
+ * sponsor's first block that sells (the behaviour before). `campaignId:
9356
+ * null` means no campaign at all, not even `Vio.init`'s. `retired: true`
9357
+ * also answers a channel the current bootstrap no longer lists with the
9358
+ * block this page saw for it (an open checkout after a bootstrap refresh).
9359
+ * Blocks with `commerce: null` are skipped. `null` when none; never throws.
9360
+ */
9361
+ sponsorCommerce(sponsorId, ctx = {}) {
9362
+ try {
9363
+ const id = toSponsorId(sponsorId);
9364
+ if (id === void 0) return null;
9365
+ const channel = channelIdOf(ctx?.channelId);
9366
+ if (channel !== void 0) {
9367
+ const listed = sponsorCommerceIn(this.cachedBootstrap, id, { channelId: channel });
9368
+ if (listed) return listed;
9369
+ return ctx.retired ? this.seenChannels.get(`${id}:${channel}`) ?? null : null;
9370
+ }
9371
+ const campaignId = ctx?.campaignId === null ? [] : this.pageCampaignCandidates(ctx?.campaignId);
9372
+ return sponsorCommerceIn(this.cachedBootstrap, id, { campaignId }) ?? null;
9373
+ } catch {
9374
+ return null;
9375
+ }
9376
+ }
9377
+ /**
9378
+ * Get a CommerceClient scoped to a sponsor (via bootstrap) — on the
9379
+ * channel of the campaign in context (`ctx.campaignId`, the element's
9380
+ * candidates first; see `sponsorCommerce`) or of `ctx.channelId`. Without
9381
+ * `ctx`, the page's campaign (`Vio.init`) when the sponsor takes part in
9382
+ * it, else the sponsor's first selling block, as before. Clients are kept
9383
+ * per sponsor and key. Throws if bootstrap hasn't been called or nothing of
9384
+ * the sponsor sells there.
8460
9385
  */
8461
- commerceFor(sponsorId) {
9386
+ commerceFor(sponsorId, ctx) {
8462
9387
  sponsorId = Number(sponsorId);
8463
- const existing = this.commerceClients.get(sponsorId);
8464
- if (existing) return existing;
8465
- const sponsor = this.findSponsor(sponsorId);
9388
+ const sponsor = this.sponsorCommerce(sponsorId, ctx ?? {});
8466
9389
  if (!sponsor) {
9390
+ const channel = channelIdOf(ctx?.channelId);
8467
9391
  throw new Error(
8468
- `[Vio] Sponsor ${sponsorId} not found in bootstrap. Call Vio.bootstrap() first or pass commerce-key directly.`
9392
+ channel !== void 0 ? `[Vio] Channel ${channel} of sponsor ${sponsorId} not found in bootstrap.` : `[Vio] Sponsor ${sponsorId} not found in bootstrap. Call Vio.bootstrap() first or pass commerce-key directly.`
8469
9393
  );
8470
9394
  }
9395
+ const apiKey = sponsor.commerce.apiKey;
9396
+ const cacheKey = `${sponsorId}:${apiKey}`;
9397
+ const existing = this.commerceClients.get(cacheKey);
9398
+ if (existing) return existing;
8471
9399
  const cfg = Configuration.get();
8472
- const client = createCommerceClient({ endpoint: cfg.graphQLBase, apiKey: sponsor.commerce.apiKey });
8473
- this.commerceClients.set(sponsorId, client);
9400
+ const client = createCommerceClient({ endpoint: cfg.graphQLBase, apiKey });
9401
+ this.commerceClients.set(cacheKey, client);
8474
9402
  return client;
8475
9403
  }
8476
9404
  /** Get a CommerceClient using an explicit commerce apiKey (skip bootstrap). */
@@ -8505,9 +9433,9 @@ var VioFacade = class {
8505
9433
  for (const l of all) {
8506
9434
  if (l.campaignId !== void 0 && l.campaignId === pageCampaignId) delete l.campaignId;
8507
9435
  }
8508
- const memory = readSlotDeclaration(storageKey);
9436
+ const memory2 = readSlotDeclaration(storageKey);
8509
9437
  const keyOf = (l) => slotDeclarationKey(l, pageCampaignId);
8510
- const pending2 = new Set(pendingSlotKeys(memory, all.map(keyOf), now));
9438
+ const pending2 = new Set(pendingSlotKeys(memory2, all.map(keyOf), now));
8511
9439
  let locations = opts.force ? all : all.filter((l) => pending2.has(keyOf(l)));
8512
9440
  if (locations.length === 0) return null;
8513
9441
  if (locations.length > MAX_LOCATIONS_PER_DECLARATION) {
@@ -8540,11 +9468,11 @@ var VioFacade = class {
8540
9468
  if (result?.mode === "merge") {
8541
9469
  const unhandled = this.campaignNotTakenUp(result, locations, pageCampaignId);
8542
9470
  const settled = keys.filter((k) => !unhandled.has(k));
8543
- let next = rememberSlotKeys(memory, settled, now);
9471
+ let next = rememberSlotKeys(memory2, settled, now);
8544
9472
  if (unhandled.size > 0) next = rememberSlotKeys(next, [...unhandled], failedAt(now));
8545
9473
  writeSlotDeclaration(storageKey, next);
8546
9474
  } else {
8547
- writeSlotDeclaration(storageKey, rememberSlotKeys(memory, keys, failedAt(now)));
9475
+ writeSlotDeclaration(storageKey, rememberSlotKeys(memory2, keys, failedAt(now)));
8548
9476
  warn(
8549
9477
  "slots declared but the server ignored mode=merge (old server); not caching, will retry next load"
8550
9478
  );
@@ -8809,9 +9737,10 @@ var VioFacade = class {
8809
9737
  * The attribution of the order a sponsor's cart is about to become
8810
9738
  * (ADR-0035 phase B) — what `CreateCheckout` / `UpdateCheckout` send as
8811
9739
  * `attribution` and shopcart writes on the order: the surface (its SDK
8812
- * key), the page's campaign (`pageCampaign()`; without one, the campaign
8813
- * that explains most units of the cart under the same rule `purchase` is
8814
- * split by — `cartSlices`) and, on a surface KNOWN to be `standard`
9740
+ * key), the campaign of the order — always one the cart's sponsor takes part
9741
+ * in (`orderCampaign`: the cart lines' own campaign, else the page's;
9742
+ * without one, the campaign that explains most units of the cart under the
9743
+ * same rule `purchase` is split by — `cartSlices`) and, on a surface KNOWN to be `standard`
8815
9744
  * (`privacyModeNow`), the page (`content()`'s url as origin + path, and
8816
9745
  * title) and where the session came in: the landing the session REMEMBERS
8817
9746
  * (`storedLanding`, else the one analytics resolved on this page) — never
@@ -8823,10 +9752,13 @@ var VioFacade = class {
8823
9752
  try {
8824
9753
  const config = Configuration.isInitialized ? Configuration.get() : void 0;
8825
9754
  const content = this.content();
9755
+ const sponsor = toSponsorId(sponsorId);
9756
+ const belongs = (campaignId) => campaignId === void 0 || campaignTakesSponsor(this.cachedBootstrap, campaignId, sponsor);
8826
9757
  return checkoutAttributionOf({
8827
9758
  surfaceKey: config?.apiKey,
8828
- pageCampaignId: toNumber(this.pageCampaign()?.id),
8829
- slices: this.cartSlices(sponsorId),
9759
+ pageCampaignId: this.orderCampaign(sponsor),
9760
+ // The fallback campaign of the slices, too, only when it is the sponsor's.
9761
+ slices: this.cartSlices(sponsorId).filter((slice) => belongs(slice.campaignId)),
8830
9762
  content: { url: content.url, title: content.title },
8831
9763
  landing: this.rememberedLanding(),
8832
9764
  privacyMode: this.privacyModeNow()
@@ -8835,6 +9767,29 @@ var VioFacade = class {
8835
9767
  return void 0;
8836
9768
  }
8837
9769
  }
9770
+ /**
9771
+ * The campaign an order of `sponsorId` is credited to — one article = one
9772
+ * campaign = one sponsor, so it is always one the sponsor takes part in:
9773
+ * the campaign the cart's own lines were added from (their origins; the one
9774
+ * that explains most units), else the page's — the elements' campaigns on
9775
+ * the page (`vio-campaign` / `data-vio-campaign`, in document order), then
9776
+ * `Vio.init({ campaignId })` — the first one active with that sponsor.
9777
+ * `undefined` when neither says one (the slices then decide). Without a
9778
+ * sponsor, the page's campaign as before.
9779
+ */
9780
+ orderCampaign(sponsorId) {
9781
+ const boot = this.cachedBootstrap;
9782
+ if (sponsorId !== void 0) {
9783
+ const lines = [...this.cart.getCart(sponsorId)?.items ?? []];
9784
+ const fromLines = dominantCampaignOf(
9785
+ cartSlicesFrom(lines, () => void 0).filter(
9786
+ (slice) => slice.campaignId !== void 0 && campaignTakesSponsor(boot, slice.campaignId, sponsorId)
9787
+ )
9788
+ );
9789
+ if (fromLines !== void 0) return fromLines;
9790
+ }
9791
+ return toNumber(activeCampaignEntry(boot, this.pageCampaignCandidates(documentCampaignHints()), sponsorId)?.id);
9792
+ }
8838
9793
  /** The landing the session remembers: what the first page stored, else what analytics resolved on this page load (consent pending); null otherwise. */
8839
9794
  rememberedLanding() {
8840
9795
  try {
@@ -8936,6 +9891,35 @@ var VioFacade = class {
8936
9891
  return findSponsorIn(this.cachedBootstrap, sponsorId);
8937
9892
  }
8938
9893
  };
9894
+ function campaignTakesSponsor(boot, campaignId, sponsorId) {
9895
+ if (sponsorId === void 0 || !boot) return true;
9896
+ try {
9897
+ const listed = (Array.isArray(boot.campaigns) ? boot.campaigns : []).filter(
9898
+ (c) => !!c && typeof c === "object" && toNumber(c.id) === campaignId
9899
+ );
9900
+ if (listed.length > 0) return listed.some((c) => campaignSponsorIds(c).includes(sponsorId));
9901
+ if (toNumber(boot.campaign?.id) === campaignId) {
9902
+ const entry = defaultCampaignEntry(boot);
9903
+ return !!entry && campaignSponsorIds(entry).includes(sponsorId);
9904
+ }
9905
+ return true;
9906
+ } catch {
9907
+ return true;
9908
+ }
9909
+ }
9910
+ function documentCampaignHints() {
9911
+ try {
9912
+ if (typeof document === "undefined" || typeof document.querySelectorAll !== "function") return [];
9913
+ const out = [];
9914
+ document.querySelectorAll("[vio-campaign], [data-vio-campaign]").forEach((el) => {
9915
+ const value = el.getAttribute("vio-campaign") || el.getAttribute("data-vio-campaign");
9916
+ if (value) out.push(value);
9917
+ });
9918
+ return out;
9919
+ } catch {
9920
+ return [];
9921
+ }
9922
+ }
8939
9923
  function warn(...args) {
8940
9924
  if (typeof console !== "undefined") console.warn("[Vio]", ...args);
8941
9925
  }
@@ -8994,6 +9978,6 @@ function vippsButtonAttributes(verb = "buy", opts = {}) {
8994
9978
  };
8995
9979
  }
8996
9980
 
8997
- 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 };
8998
- //# sourceMappingURL=chunk-AG4G2S2M.js.map
8999
- //# sourceMappingURL=chunk-AG4G2S2M.js.map
9981
+ 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, INTERNAL_KEY, INTERNAL_PARAM, 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, getGlobalCountryCode, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, isUnknownFeatureError, isUnknownTokenError, isValidPhone, kustomReturnOutcome, landingUrl, lineOriginWire, lineOrigins, nexiReturnPaymentId, normalizeBuyerRef, normalizeOrigin, normalizeScope, normalizeSlotId, normalizeSlotKind, originAndPath, primaryImageUrl, purchaseFromPaymentSuccess, purchaseIdsOf, readInternal, readNexiPending, rememberedCheckoutSponsor, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveInternal, resolveLanding, resolveSlotCampaign, sameOrigin, shippingDescriptionOf, showApplePaySheet, sponsorCampaignEntry, sponsorIdentityFrom, storageGet, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature, writeInternal };
9982
+ //# sourceMappingURL=chunk-ZORYG4W3.js.map
9983
+ //# sourceMappingURL=chunk-ZORYG4W3.js.map