@vio-live/web-sdk 0.23.1 → 0.26.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.
@@ -1,4 +1,4 @@
1
- import { Vio, __decorateClass, primaryImageUrl, displayPrice, getGlobalCurrency, ensureVippsButton, formatPrice, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, normalizeScope } from './chunk-BRJMT7P4.js';
1
+ import { Vio, __decorateClass, primaryImageUrl, displayPrice, toCampaignId, getGlobalCurrency, ensureVippsButton, formatPrice, buyerRefOf, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, gatewayHasBuyerRef, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, normalizeScope, campaignCandidates } from './chunk-5KWICUME.js';
2
2
  import { LitElement, css, html } from 'lit';
3
3
  import { property, state, query } from 'lit/decorators.js';
4
4
 
@@ -171,6 +171,8 @@ var SCOPE_ATTR = "vio-scope";
171
171
  var SCOPE_NAME_ATTR = "vio-scope-name";
172
172
  var SCOPE_DATA_ATTR = "data-vio-scope";
173
173
  var SCOPE_NAME_DATA_ATTR = "data-vio-scope-name";
174
+ var CAMPAIGN_ATTR = "vio-campaign";
175
+ var CAMPAIGN_DATA_ATTR = "data-vio-campaign";
174
176
  var liveAutoIds = (() => {
175
177
  const g = globalThis;
176
178
  return g.__VIO_LIVE_SLOT_IDS__ ?? (g.__VIO_LIVE_SLOT_IDS__ = /* @__PURE__ */ new Map());
@@ -192,6 +194,14 @@ var SlotRegistration = class {
192
194
  get effective() {
193
195
  return this.identity?.id ?? "";
194
196
  }
197
+ /** The element's own page campaign (`vio-campaign` / ancestor `data-vio-campaign`); undefined → `Vio.init`'s applies. */
198
+ get campaignId() {
199
+ return this.identity?.campaignId;
200
+ }
201
+ /** The element's page-campaign candidates in order (own, ancestor) — the hint for `Vio.attribution` / `campaignForSlot`. */
202
+ get campaignHint() {
203
+ return this.identity?.campaignCandidates ?? [];
204
+ }
195
205
  /** On connect: read the scope from the DOM, then register. */
196
206
  connect(attrs) {
197
207
  this.connected = true;
@@ -216,7 +226,7 @@ var SlotRegistration = class {
216
226
  this.unregister();
217
227
  if (!next) return;
218
228
  try {
219
- this.release = typeof Vio.registerSlot === "function" ? Vio.registerSlot(next.id, next.displayName) : null;
229
+ this.release = typeof Vio.registerSlot !== "function" ? null : next.campaignId !== void 0 ? Vio.registerSlot(next.id, next.displayName, next.campaignId, { kind: this.kind }) : Vio.registerSlot(next.id, next.displayName, { kind: this.kind });
220
230
  this.identity = next;
221
231
  } catch {
222
232
  this.release = null;
@@ -227,13 +237,20 @@ var SlotRegistration = class {
227
237
  resolve(attrs) {
228
238
  const explicit = normalizeSlotId(attrs.vioSlot);
229
239
  const name = typeof attrs.vioSlotName === "string" && attrs.vioSlotName.trim() ? attrs.vioSlotName.trim() : void 0;
240
+ const campaignCandidates2 = campaignCandidatesOf(this.host, attrs);
241
+ const campaignId = campaignCandidates2[0];
230
242
  if (explicit) {
231
243
  this.releaseAuto();
232
- return { id: explicit, displayName: name };
244
+ return { id: explicit, displayName: name, campaignId, campaignCandidates: campaignCandidates2 };
233
245
  }
234
246
  if (!this.connected) return null;
235
247
  const auto = this.claimAuto();
236
- return { id: auto.id, displayName: name ?? autoSlotDisplayName(this.kind, auto.number - 1, this.scopeName) };
248
+ return {
249
+ id: auto.id,
250
+ displayName: name ?? autoSlotDisplayName(this.kind, auto.number - 1, this.scopeName),
251
+ campaignId,
252
+ campaignCandidates: campaignCandidates2
253
+ };
237
254
  }
238
255
  /** Unregister whatever is registered and free the auto number. */
239
256
  clear() {
@@ -275,7 +292,12 @@ var SlotRegistration = class {
275
292
  };
276
293
  function sameIdentity(a, b) {
277
294
  if (!a || !b) return a === b;
278
- return a.id === b.id && a.displayName === b.displayName;
295
+ return a.id === b.id && a.displayName === b.displayName && a.campaignId === b.campaignId;
296
+ }
297
+ function campaignCandidatesOf(el, attrs) {
298
+ const own = attrs && attrs.vioCampaign !== void 0 && attrs.vioCampaign !== null ? attrs.vioCampaign : el.vioCampaign !== void 0 ? el.vioCampaign : el.getAttribute(CAMPAIGN_ATTR);
299
+ const ancestor = closestAcrossShadow(el, `[${CAMPAIGN_DATA_ATTR}]`)?.getAttribute(CAMPAIGN_DATA_ATTR);
300
+ return campaignCandidates([own, ancestor]);
279
301
  }
280
302
  function scopeOf(el, attrs) {
281
303
  const own = attrs && typeof attrs.vioScope === "string" ? attrs.vioScope : typeof el.vioScope === "string" ? el.vioScope ?? "" : el.getAttribute(SCOPE_ATTR) ?? "";
@@ -314,8 +336,10 @@ var VioProduct = class extends LitElement {
314
336
  this.vioSlotName = "";
315
337
  this.vioScope = "";
316
338
  this.vioScopeName = "";
339
+ this.vioCampaign = "";
317
340
  this.vioContentId = "";
318
341
  this.vioContentTitle = "";
342
+ this.componentTemplateId = "";
319
343
  this.slotRegistration = new SlotRegistration(this, "card");
320
344
  this.hasVariants = false;
321
345
  this.variants = void 0;
@@ -330,9 +354,13 @@ var VioProduct = class extends LitElement {
330
354
  get effectiveSlot() {
331
355
  return this.slotRegistration.effective;
332
356
  }
357
+ /** The element's own page campaign (`vio-campaign` / ancestor), if any; `Vio.init`'s applies otherwise. */
358
+ get effectiveCampaignId() {
359
+ return this.slotRegistration.campaignId;
360
+ }
333
361
  /** The slot campaign's sponsor as the string the attributes use ('' when unknown). */
334
362
  sponsorFromAttribution() {
335
- const id = typeof Vio.attribution === "function" ? Vio.attribution(this.effectiveSlot).sponsorId : void 0;
363
+ const id = typeof Vio.attribution === "function" ? Vio.attribution(this.effectiveSlot, { campaignId: this.slotRegistration.campaignHint }).sponsorId : void 0;
336
364
  return id !== void 0 ? String(id) : "";
337
365
  }
338
366
  slotAttributes() {
@@ -340,7 +368,8 @@ var VioProduct = class extends LitElement {
340
368
  vioSlot: this.vioSlot,
341
369
  vioSlotName: this.vioSlotName,
342
370
  vioScope: this.vioScope,
343
- vioScopeName: this.vioScopeName
371
+ vioScopeName: this.vioScopeName,
372
+ vioCampaign: this.vioCampaign
344
373
  };
345
374
  }
346
375
  /**
@@ -349,9 +378,13 @@ var VioProduct = class extends LitElement {
349
378
  * set `vio-content-id` / `vio-content-title` on this card.
350
379
  */
351
380
  attribution() {
352
- const base = typeof Vio.attribution === "function" ? Vio.attribution(this.effectiveSlot, { sponsorId: this.sponsorId || void 0 }) : { slot: this.effectiveSlot || void 0 };
381
+ const base = typeof Vio.attribution === "function" ? Vio.attribution(this.effectiveSlot, {
382
+ sponsorId: this.sponsorId || void 0,
383
+ campaignId: this.slotRegistration.campaignHint
384
+ }) : { slot: this.effectiveSlot || void 0 };
353
385
  return {
354
386
  ...base,
387
+ componentTemplateId: this.componentTemplateId || "product-card",
355
388
  ...this.vioContentId ? { contentId: this.vioContentId } : {},
356
389
  ...this.vioContentTitle ? { contentTitle: this.vioContentTitle } : {}
357
390
  };
@@ -479,7 +512,7 @@ var VioProduct = class extends LitElement {
479
512
  }
480
513
  updated(changed) {
481
514
  if (changed.has("productId") || changed.has("sponsorId")) this.updateInCart();
482
- if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName")) {
515
+ if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName") || changed.has("vioCampaign")) {
483
516
  this.slotRegistration.sync(this.slotAttributes());
484
517
  }
485
518
  }
@@ -550,11 +583,15 @@ var VioProduct = class extends LitElement {
550
583
  // The host's sponsor-id wins; without one, the slot's campaign sponsor.
551
584
  sponsorId: this.sponsorId || this.sponsorFromAttribution(),
552
585
  // The host adds to the cart: pass this through as `origin` so the
553
- // purchase is attributed to this place and campaign.
586
+ // purchase is attributed to this place, campaign, kind of element
587
+ // and page — the cart keeps it and Commerce receives it (ADR-0035).
554
588
  origin: {
555
589
  campaignId: attribution.campaignId,
556
590
  slot: attribution.slot,
557
- contentId: attribution.contentId
591
+ contentId: attribution.contentId,
592
+ componentKind: attribution.componentTemplateId,
593
+ contentUrl: attribution.contentUrl,
594
+ contentTitle: attribution.contentTitle
558
595
  }
559
596
  }
560
597
  })
@@ -635,12 +672,18 @@ __decorateClass([
635
672
  __decorateClass([
636
673
  property({ type: String, attribute: "vio-scope-name" })
637
674
  ], VioProduct.prototype, "vioScopeName", 2);
675
+ __decorateClass([
676
+ property({ type: String, attribute: "vio-campaign" })
677
+ ], VioProduct.prototype, "vioCampaign", 2);
638
678
  __decorateClass([
639
679
  property({ type: String, attribute: "vio-content-id" })
640
680
  ], VioProduct.prototype, "vioContentId", 2);
641
681
  __decorateClass([
642
682
  property({ type: String, attribute: "vio-content-title" })
643
683
  ], VioProduct.prototype, "vioContentTitle", 2);
684
+ __decorateClass([
685
+ property({ type: String, attribute: "component-template-id" })
686
+ ], VioProduct.prototype, "componentTemplateId", 2);
644
687
  __decorateClass([
645
688
  property({ type: Boolean, attribute: "has-variants" })
646
689
  ], VioProduct.prototype, "hasVariants", 2);
@@ -679,6 +722,7 @@ var VioProductCarousel = class extends LitElement {
679
722
  this.vioSlotName = "";
680
723
  this.vioScope = "";
681
724
  this.vioScopeName = "";
725
+ this.vioCampaign = "";
682
726
  this.vioContentId = "";
683
727
  this.vioContentTitle = "";
684
728
  this.slotRegistration = new SlotRegistration(this, "carousel");
@@ -697,19 +741,28 @@ var VioProductCarousel = class extends LitElement {
697
741
  get effectiveSlot() {
698
742
  return this.slotRegistration.effective;
699
743
  }
744
+ /** The element's own page campaign (`vio-campaign` / ancestor), if any; `Vio.init`'s applies otherwise. */
745
+ get effectiveCampaignId() {
746
+ return this.slotRegistration.campaignId;
747
+ }
700
748
  slotAttributes() {
701
749
  return {
702
750
  vioSlot: this.vioSlot,
703
751
  vioSlotName: this.vioSlotName,
704
752
  vioScope: this.vioScope,
705
- vioScopeName: this.vioScopeName
753
+ vioScopeName: this.vioScopeName,
754
+ vioCampaign: this.vioCampaign
706
755
  };
707
756
  }
708
757
  /** Attribution every analytics event of this carousel carries (`Vio.attribution` + the content overrides). */
709
758
  attribution() {
710
- const base = typeof Vio.attribution === "function" ? Vio.attribution(this.effectiveSlot, { sponsorId: this.sponsorId > 0 ? this.sponsorId : void 0 }) : { slot: this.effectiveSlot || void 0 };
759
+ const base = typeof Vio.attribution === "function" ? Vio.attribution(this.effectiveSlot, {
760
+ sponsorId: this.sponsorId > 0 ? this.sponsorId : void 0,
761
+ campaignId: this.slotRegistration.campaignHint
762
+ }) : { slot: this.effectiveSlot || void 0 };
711
763
  return {
712
764
  ...base,
765
+ componentTemplateId: "product-carousel",
713
766
  ...this.vioContentId ? { contentId: this.vioContentId } : {},
714
767
  ...this.vioContentTitle ? { contentTitle: this.vioContentTitle } : {}
715
768
  };
@@ -874,10 +927,10 @@ var VioProductCarousel = class extends LitElement {
874
927
  super.disconnectedCallback();
875
928
  }
876
929
  updated(changed) {
877
- if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName")) {
930
+ if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName") || changed.has("vioCampaign")) {
878
931
  this.slotRegistration.sync(this.slotAttributes());
879
932
  }
880
- const propChanged = changed.has("productRefs") || changed.has("productIds") || changed.has("commerceKey") || changed.has("sponsorId") || changed.has("vioSlot");
933
+ const propChanged = changed.has("productRefs") || changed.has("productIds") || changed.has("commerceKey") || changed.has("sponsorId") || changed.has("vioSlot") || changed.has("vioCampaign");
881
934
  if (propChanged && (this.productRefs.trim() || this.productIds.trim())) {
882
935
  void this.autoFetch();
883
936
  }
@@ -889,7 +942,7 @@ var VioProductCarousel = class extends LitElement {
889
942
  */
890
943
  effectiveSponsorId() {
891
944
  if (this.sponsorId > 0) return this.sponsorId;
892
- const fromSlot = typeof Vio.campaignForSlot === "function" ? Number(Vio.campaignForSlot(this.effectiveSlot || null)?.primarySponsor?.id) : NaN;
945
+ const fromSlot = typeof Vio.campaignForSlot === "function" ? Number(Vio.campaignForSlot(this.effectiveSlot || null, { campaignId: this.slotRegistration.campaignHint })?.primarySponsor?.id) : NaN;
893
946
  return Number.isFinite(fromSlot) && fromSlot > 0 ? fromSlot : 0;
894
947
  }
895
948
  async autoFetch() {
@@ -951,7 +1004,6 @@ var VioProductCarousel = class extends LitElement {
951
1004
  this,
952
1005
  {
953
1006
  sponsorId: this.sponsorId > 0 ? this.sponsorId : void 0,
954
- componentTemplateId: "product-carousel",
955
1007
  // The slot is the real location id; the label only stands in for it.
956
1008
  locationId: this.effectiveSlot || this.label || void 0,
957
1009
  ...this.attribution()
@@ -979,7 +1031,6 @@ var VioProductCarousel = class extends LitElement {
979
1031
  Vio.analytics.track("view_item_list", {
980
1032
  context: {
981
1033
  sponsorId: sponsorId > 0 ? sponsorId : void 0,
982
- componentTemplateId: "product-carousel",
983
1034
  locationId: this.effectiveSlot || this.label || void 0,
984
1035
  ...this.attribution()
985
1036
  },
@@ -1038,6 +1089,8 @@ var VioProductCarousel = class extends LitElement {
1038
1089
  image-url=${img}
1039
1090
  ?has-variants=${(p.variants?.length ?? 0) > 1}
1040
1091
  .vioSlot=${this.effectiveSlot}
1092
+ .componentTemplateId=${"product-carousel"}
1093
+ .vioCampaign=${this.effectiveCampaignId !== void 0 ? String(this.effectiveCampaignId) : ""}
1041
1094
  .vioContentId=${this.vioContentId}
1042
1095
  .vioContentTitle=${this.vioContentTitle}
1043
1096
  ></vio-product>
@@ -1121,6 +1174,9 @@ __decorateClass([
1121
1174
  __decorateClass([
1122
1175
  property({ type: String, attribute: "vio-scope-name" })
1123
1176
  ], VioProductCarousel.prototype, "vioScopeName", 2);
1177
+ __decorateClass([
1178
+ property({ type: String, attribute: "vio-campaign" })
1179
+ ], VioProductCarousel.prototype, "vioCampaign", 2);
1124
1180
  __decorateClass([
1125
1181
  property({ type: String, attribute: "vio-content-id" })
1126
1182
  ], VioProductCarousel.prototype, "vioContentId", 2);
@@ -1369,6 +1425,7 @@ var VioProductDetail = class extends LitElement {
1369
1425
  this.vioSlotName = "";
1370
1426
  this.vioScope = "";
1371
1427
  this.vioScopeName = "";
1428
+ this.vioCampaign = "";
1372
1429
  this.vioContentId = "";
1373
1430
  this.vioContentTitle = "";
1374
1431
  this.slotRegistration = new SlotRegistration(this, "detail");
@@ -1376,6 +1433,7 @@ var VioProductDetail = class extends LitElement {
1376
1433
  this.eventSlot = "";
1377
1434
  this.eventContentId = "";
1378
1435
  this.eventContentTitle = "";
1436
+ this.eventCampaignId = void 0;
1379
1437
  this.eventProductId = "";
1380
1438
  this.product = null;
1381
1439
  this.sellerName = null;
@@ -1410,11 +1468,16 @@ var VioProductDetail = class extends LitElement {
1410
1468
  const slot = typeof ev.detail.slot === "string" ? ev.detail.slot.trim() : "";
1411
1469
  let sponsorIdNum = parseInt(ev.detail.sponsorId, 10);
1412
1470
  if (Number.isNaN(sponsorIdNum) || sponsorIdNum <= 0) {
1413
- const fromSlot = typeof Vio.campaignForSlot === "function" ? Number(Vio.campaignForSlot(slot || this.effectiveSlot || null)?.primarySponsor?.id) : NaN;
1471
+ const fromSlot = typeof Vio.campaignForSlot === "function" ? Number(
1472
+ Vio.campaignForSlot(slot || this.effectiveSlot || null, {
1473
+ campaignId: [ev.detail.campaignId, ...this.slotRegistration.campaignHint]
1474
+ })?.primarySponsor?.id
1475
+ ) : NaN;
1414
1476
  if (!Number.isFinite(fromSlot) || fromSlot <= 0) return;
1415
1477
  sponsorIdNum = fromSlot;
1416
1478
  }
1417
1479
  this.eventSlot = slot;
1480
+ this.eventCampaignId = toCampaignId(ev.detail.campaignId);
1418
1481
  this.eventProductId = String(ev.detail.productId);
1419
1482
  this.eventContentId = ev.detail.contentId !== void 0 && ev.detail.contentId !== null ? String(ev.detail.contentId).trim() : "";
1420
1483
  this.eventContentTitle = typeof ev.detail.contentTitle === "string" ? ev.detail.contentTitle.trim() : "";
@@ -1433,25 +1496,39 @@ var VioProductDetail = class extends LitElement {
1433
1496
  get effectiveSlot() {
1434
1497
  return this.slotRegistration.effective;
1435
1498
  }
1499
+ /** The element's own page campaign (`vio-campaign` / ancestor), if any; `Vio.init`'s applies otherwise. */
1500
+ get effectiveCampaignId() {
1501
+ return this.slotRegistration.campaignId;
1502
+ }
1436
1503
  /** The slot the open product is attributed to: the opening card's, else this element's. */
1437
1504
  get activeSlot() {
1438
1505
  return this.fromCard && this.eventSlot || this.effectiveSlot;
1439
1506
  }
1507
+ /** The page-campaign candidates of the open product: the opening card's, then this element's own and its ancestor's. */
1508
+ get activeCampaignHint() {
1509
+ const own = this.slotRegistration.campaignHint;
1510
+ return this.fromCard && this.eventCampaignId !== void 0 ? [this.eventCampaignId, ...own] : own;
1511
+ }
1440
1512
  slotAttributes() {
1441
1513
  return {
1442
1514
  vioSlot: this.vioSlot,
1443
1515
  vioSlotName: this.vioSlotName,
1444
1516
  vioScope: this.vioScope,
1445
- vioScopeName: this.vioScopeName
1517
+ vioScopeName: this.vioScopeName,
1518
+ vioCampaign: this.vioCampaign
1446
1519
  };
1447
1520
  }
1448
1521
  /** Attribution every event of the open product carries (`Vio.attribution` of the opening card's slot, else this element's). */
1449
1522
  attribution() {
1450
- const base = typeof Vio.attribution === "function" ? Vio.attribution(this.activeSlot, { sponsorId: this.sponsorId > 0 ? this.sponsorId : void 0 }) : { slot: this.activeSlot || void 0 };
1523
+ const base = typeof Vio.attribution === "function" ? Vio.attribution(this.activeSlot, {
1524
+ sponsorId: this.sponsorId > 0 ? this.sponsorId : void 0,
1525
+ campaignId: this.activeCampaignHint
1526
+ }) : { slot: this.activeSlot || void 0 };
1451
1527
  const contentId = this.fromCard && this.eventContentId || this.vioContentId;
1452
1528
  const contentTitle = this.fromCard && this.eventContentTitle || this.vioContentTitle;
1453
1529
  return {
1454
1530
  ...base,
1531
+ componentTemplateId: "product-detail",
1455
1532
  ...contentId ? { contentId } : {},
1456
1533
  ...contentTitle ? { contentTitle } : {}
1457
1534
  };
@@ -1914,7 +1991,7 @@ var VioProductDetail = class extends LitElement {
1914
1991
  }
1915
1992
  updated(changed) {
1916
1993
  if (changed.has("open")) this.dialogFocus.update(this.open);
1917
- if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName")) {
1994
+ if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName") || changed.has("vioCampaign")) {
1918
1995
  this.slotRegistration.sync(this.slotAttributes());
1919
1996
  }
1920
1997
  }
@@ -1928,6 +2005,7 @@ var VioProductDetail = class extends LitElement {
1928
2005
  this.eventSlot = "";
1929
2006
  this.eventContentId = "";
1930
2007
  this.eventContentTitle = "";
2008
+ this.eventCampaignId = void 0;
1931
2009
  this.eventProductId = "";
1932
2010
  this.product = null;
1933
2011
  this.sellerName = null;
@@ -1987,7 +2065,6 @@ var VioProductDetail = class extends LitElement {
1987
2065
  Vio.analytics.track("view_item", {
1988
2066
  context: {
1989
2067
  sponsorId: this.sponsorId > 0 ? Number(this.sponsorId) : void 0,
1990
- componentTemplateId: "product-detail",
1991
2068
  ...this.attribution()
1992
2069
  },
1993
2070
  commerce: {
@@ -2028,7 +2105,7 @@ var VioProductDetail = class extends LitElement {
2028
2105
  }
2029
2106
  /** Which buy buttons to render — backend-configured per sponsor. */
2030
2107
  async loadAvailablePaymentMethods() {
2031
- const spId = this.sponsorId || (typeof Vio.campaignForSlot === "function" ? Number(Vio.campaignForSlot(this.activeSlot || null)?.primarySponsor?.id) : 0) || globalThis.__VIO_SPONSOR_ID__;
2108
+ const spId = this.sponsorId || (typeof Vio.campaignForSlot === "function" ? Number(Vio.campaignForSlot(this.activeSlot || null, { campaignId: this.activeCampaignHint })?.primarySponsor?.id) : 0) || globalThis.__VIO_SPONSOR_ID__;
2032
2109
  if (!spId) return;
2033
2110
  try {
2034
2111
  const methods = await Vio.checkout.getAvailablePaymentMethods(spId);
@@ -2310,14 +2387,14 @@ var VioProductDetail = class extends LitElement {
2310
2387
  const sponsor = Vio.bootstrapCache?.primarySponsor;
2311
2388
  const secondaries = Vio.bootstrapCache?.secondarySponsors ?? [];
2312
2389
  const sponsorName = sponsor?.id === this.sponsorId ? sponsor.name : secondaries.find((s) => s.id === this.sponsorId)?.name;
2313
- const { campaignId, slot, contentId } = this.attribution();
2390
+ const { campaignId, slot, contentId, componentTemplateId, contentUrl, contentTitle } = this.attribution();
2314
2391
  Vio.cart.addProduct({
2315
2392
  product: this.product,
2316
2393
  sponsorId: this.sponsorId,
2317
2394
  sponsorName,
2318
2395
  quantity: this.quantity,
2319
2396
  variantId: variant?.id,
2320
- origin: { campaignId, slot, contentId }
2397
+ origin: { campaignId, slot, contentId, componentKind: componentTemplateId, contentUrl, contentTitle }
2321
2398
  });
2322
2399
  return variant?.id;
2323
2400
  }
@@ -2734,6 +2811,9 @@ __decorateClass([
2734
2811
  __decorateClass([
2735
2812
  property({ type: String, attribute: "vio-scope-name" })
2736
2813
  ], VioProductDetail.prototype, "vioScopeName", 2);
2814
+ __decorateClass([
2815
+ property({ type: String, attribute: "vio-campaign" })
2816
+ ], VioProductDetail.prototype, "vioCampaign", 2);
2737
2817
  __decorateClass([
2738
2818
  property({ type: String, attribute: "vio-content-id" })
2739
2819
  ], VioProductDetail.prototype, "vioContentId", 2);
@@ -3282,11 +3362,12 @@ var VioCart = class extends LitElement {
3282
3362
  for (const sponsorId of [...this.carts.keys()]) {
3283
3363
  Vio.cart.clearSponsorCart(sponsorId);
3284
3364
  }
3365
+ const buyerRef = buyerRefOf({ result });
3285
3366
  this.dispatchEvent(
3286
3367
  new CustomEvent("vio:payment-success", {
3287
3368
  bubbles: true,
3288
3369
  composed: true,
3289
- detail: { method: "apple-pay", result }
3370
+ detail: { method: "apple-pay", result, ...buyerRef ? { buyerRef } : {} }
3290
3371
  })
3291
3372
  );
3292
3373
  });
@@ -3856,6 +3937,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
3856
3937
  this.paymentError = detail.method === "klarna" ? `Klarna-betalingen kunne ikke fullf\xF8res: ${detail.error}` : friendlyPaymentError(detail.error, detail.method);
3857
3938
  this.open = true;
3858
3939
  };
3940
+ /** Payment events still waiting for their buyer reference: settled early (without it) when the page hides or the element leaves. */
3941
+ this.pendingSuccessEmits = /* @__PURE__ */ new Set();
3859
3942
  }
3860
3943
  /** A payment is being started or confirmed right now: the overlay must not close under it. */
3861
3944
  get paymentInFlight() {
@@ -4546,13 +4629,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
4546
4629
  this.orderConfirmed = true;
4547
4630
  this.open = true;
4548
4631
  void this.fillOrderNumber(checkoutId, sponsorId);
4549
- this.dispatchEvent(
4550
- new CustomEvent("vio:payment-success", {
4551
- bubbles: true,
4552
- composed: true,
4553
- detail: { method: vioMethod, sponsorId, orderId: checkoutId }
4554
- })
4555
- );
4632
+ void this.emitPaymentSuccess({ method: vioMethod, sponsorId, orderId: checkoutId }, checkoutId, sponsorId);
4556
4633
  } else if (outcome === "duplicate") {
4557
4634
  this.paymentError = CART_PAID_MESSAGE;
4558
4635
  this.open = true;
@@ -4894,6 +4971,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
4894
4971
  this.unmountKlarna();
4895
4972
  this.unmountNexi();
4896
4973
  this.unmountAdyen();
4974
+ for (const settle of [...this.pendingSuccessEmits]) settle();
4897
4975
  super.disconnectedCallback();
4898
4976
  }
4899
4977
  updated(changed) {
@@ -5405,18 +5483,122 @@ var _VioCheckout = class _VioCheckout extends LitElement {
5405
5483
  this.confirmedMethod = method;
5406
5484
  this.orderConfirmed = true;
5407
5485
  this.open = true;
5408
- this.dispatchEvent(
5409
- new CustomEvent("vio:payment-success", {
5486
+ void this.fillOrderNumber(checkoutId, sponsorId);
5487
+ void this.emitPaymentSuccess(
5488
+ { method, sponsorId, result, checkoutId },
5489
+ checkoutId,
5490
+ sponsorId,
5491
+ () => Vio.cart.clearSponsorCart(sponsorId)
5492
+ );
5493
+ }
5494
+ static {
5495
+ /** Longest `vio:payment-success` waits for the checkout's buyer reference, and when it asks a second time. */
5496
+ this.BUYER_REF_WAIT_MS = 2500;
5497
+ }
5498
+ static {
5499
+ this.BUYER_REF_RETRY_AT_MS = 1200;
5500
+ }
5501
+ /**
5502
+ * `vio:payment-success` for the host and analytics — with the buyer
5503
+ * reference (`buyerRef`: shopcart's keyed hash of the email → analytics
5504
+ * `buyer_id`) when the checkout can say it. An emitter whose own answer
5505
+ * carried it (Apple Pay, Klarna native) sends at once; the others (a
5506
+ * redirect return, Stripe / Adyen / Nexi in the drawer, a provider's
5507
+ * receipt) ask the checkout — at once and, while it has neither order nor
5508
+ * reference, again at 1.2 s, since a webhook may write the checkout a
5509
+ * moment after the redirect — within 2.5 s, and
5510
+ * send without it when the answer is late or empty. Nothing is asked when
5511
+ * it cannot help (the gateway has no `buyer_ref` yet; a confirmation that
5512
+ * already answered). Only the event waits — the screen is already the
5513
+ * confirmation — and not past the page: when the page hides (`pagehide`,
5514
+ * `visibilitychange`) or the element leaves, the event goes out at once
5515
+ * without the reference. Exactly one event per call. `afterwards` runs
5516
+ * right after the event, so a path keeps the order it always had (the cart
5517
+ * is cleared after the event).
5518
+ */
5519
+ async emitPaymentSuccess(detail, checkoutId, sponsorId, afterwards) {
5520
+ let done = false;
5521
+ const finish = (buyerRef) => {
5522
+ if (done) return;
5523
+ done = true;
5524
+ const event = new CustomEvent("vio:payment-success", {
5410
5525
  bubbles: true,
5411
5526
  composed: true,
5412
- // `checkoutId` is Vio's own reference for the purchase — what the
5413
- // redirect path already sends as `orderId`; analytics falls back to it
5414
- // when the provider's result carries no order id.
5415
- detail: { method, sponsorId, result, checkoutId }
5416
- })
5417
- );
5418
- Vio.cart.clearSponsorCart(sponsorId);
5419
- void this.fillOrderNumber(checkoutId, sponsorId);
5527
+ detail: buyerRef ? { ...detail, buyerRef } : detail
5528
+ });
5529
+ this.dispatchEvent(event);
5530
+ if (!this.isConnected && typeof window !== "undefined") {
5531
+ window.dispatchEvent(new CustomEvent("vio:payment-success", { detail: event.detail }));
5532
+ }
5533
+ try {
5534
+ afterwards?.();
5535
+ } catch {
5536
+ }
5537
+ };
5538
+ const known = buyerRefOf(detail);
5539
+ if (known || !checkoutId || !this.shouldAskBuyerRef(detail)) {
5540
+ finish(known);
5541
+ return;
5542
+ }
5543
+ const settleEarly = () => finish();
5544
+ const onVisibility = () => {
5545
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") settleEarly();
5546
+ };
5547
+ this.pendingSuccessEmits.add(settleEarly);
5548
+ window.addEventListener("pagehide", settleEarly);
5549
+ document.addEventListener("visibilitychange", onVisibility);
5550
+ try {
5551
+ finish(await this.askBuyerRef(checkoutId, sponsorId, () => done));
5552
+ } catch {
5553
+ finish();
5554
+ } finally {
5555
+ this.pendingSuccessEmits.delete(settleEarly);
5556
+ window.removeEventListener("pagehide", settleEarly);
5557
+ document.removeEventListener("visibilitychange", onVisibility);
5558
+ }
5559
+ }
5560
+ /** Whether asking the checkout for the buyer reference can still help. */
5561
+ shouldAskBuyerRef(detail) {
5562
+ if (typeof Vio.checkout.getOrderReference !== "function") return false;
5563
+ if (!gatewayHasBuyerRef()) return false;
5564
+ if ((detail.method === "apple-pay" || detail.method === "klarna") && detail.result) return false;
5565
+ return true;
5566
+ }
5567
+ /**
5568
+ * The buyer reference of a checkout: asked at once and, when that answer
5569
+ * failed or has neither the reference nor an order yet (the webhook has not
5570
+ * written the checkout), again at 1.2 s — all within 2.5 s. An order
5571
+ * without a reference is a settled answer: the backend has none, asking
5572
+ * again cannot help. `undefined` when the time is up, when the answer
5573
+ * stopped mattering (`stopped()`) or on failure.
5574
+ */
5575
+ async askBuyerRef(checkoutId, sponsorId, stopped) {
5576
+ const started = Date.now();
5577
+ const remaining = () => _VioCheckout.BUYER_REF_WAIT_MS - (Date.now() - started);
5578
+ let deadline;
5579
+ const timeUp = new Promise((resolve) => {
5580
+ deadline = setTimeout(() => resolve(null), _VioCheckout.BUYER_REF_WAIT_MS);
5581
+ });
5582
+ try {
5583
+ for (const at of [0, _VioCheckout.BUYER_REF_RETRY_AT_MS]) {
5584
+ const wait = Math.min(at - (Date.now() - started), remaining());
5585
+ if (wait > 0) await Promise.race([new Promise((resolve) => setTimeout(resolve, wait)), timeUp]);
5586
+ if (stopped() || remaining() <= 0) return void 0;
5587
+ try {
5588
+ const reference = await Promise.race([
5589
+ Vio.checkout.getOrderReference(checkoutId, sponsorId || void 0),
5590
+ timeUp
5591
+ ]);
5592
+ if (reference === null) return void 0;
5593
+ if (reference.buyerRef) return reference.buyerRef;
5594
+ if (reference.orderNumber !== null) return void 0;
5595
+ } catch {
5596
+ }
5597
+ }
5598
+ return void 0;
5599
+ } finally {
5600
+ clearTimeout(deadline);
5601
+ }
5420
5602
  }
5421
5603
  static {
5422
5604
  /** Waits between asks for the Vio order number: the order is often born a
@@ -5567,19 +5749,14 @@ var _VioCheckout = class _VioCheckout extends LitElement {
5567
5749
  this.confirmedMethod = method;
5568
5750
  this.open = true;
5569
5751
  const checkoutId = returnCheckoutId || this.checkoutState?.checkoutId;
5570
- this.dispatchEvent(
5571
- new CustomEvent("vio:payment-success", {
5572
- bubbles: true,
5573
- composed: true,
5574
- detail: { method, sponsorId, result, checkoutId }
5575
- })
5576
- );
5577
- if (sponsorId) {
5578
- try {
5579
- Vio.cart.clearSponsorCart(sponsorId);
5580
- } catch {
5752
+ void this.emitPaymentSuccess(
5753
+ { method, sponsorId, result, checkoutId },
5754
+ checkoutId,
5755
+ sponsorId,
5756
+ () => {
5757
+ if (sponsorId) Vio.cart.clearSponsorCart(sponsorId);
5581
5758
  }
5582
- }
5759
+ );
5583
5760
  }
5584
5761
  /** Back from the payment page, asking whether it was paid — and nothing
5585
5762
  * else on screen while we do. */
@@ -7034,5 +7211,5 @@ function registerVioElements() {
7034
7211
  }
7035
7212
 
7036
7213
  export { VioCart, VioCheckout, VioProduct, VioProductCarousel, VioProductDetail, VioTokens, applyVioTheme, registerVioElements };
7037
- //# sourceMappingURL=chunk-UVDOKFGD.js.map
7038
- //# sourceMappingURL=chunk-UVDOKFGD.js.map
7214
+ //# sourceMappingURL=chunk-A3LN5JEM.js.map
7215
+ //# sourceMappingURL=chunk-A3LN5JEM.js.map