@vio-live/web-sdk 0.24.0 → 0.26.1

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.
@@ -47,6 +47,14 @@ interface VioConfig {
47
47
  campaignId?: number;
48
48
  /** Log the SDK's slot/attribution decisions at `console.debug` (manifest assignments per slot). */
49
49
  debug?: boolean;
50
+ /**
51
+ * The surface's privacy mode, when the host knows it (ADR-0035): `minimal`
52
+ * keeps the entry context (referrer, utm, landing page) off the order
53
+ * attribution. Without it the SDK asks the surface itself
54
+ * (`GET /v2/mobile/surface`), once per page load; a host-set `minimal`
55
+ * always wins.
56
+ */
57
+ privacyMode?: 'standard' | 'minimal';
50
58
  /** Override REST API base. Defaults are env-aware. */
51
59
  apiBase?: string;
52
60
  /** Override GraphQL base. Defaults are env-aware. */
@@ -84,6 +92,7 @@ interface ResolvedConfig {
84
92
  contentTitle?: string;
85
93
  campaignId?: number;
86
94
  debug: boolean;
95
+ privacyMode?: 'standard' | 'minimal';
87
96
  apiBase: string;
88
97
  graphQLBase: string;
89
98
  eventsBase: string;
@@ -234,7 +243,15 @@ interface BootstrapResponse {
234
243
  webSocketBase?: string;
235
244
  commerceGraphQL?: string;
236
245
  };
237
- campaign?: BootstrapCampaign;
246
+ /**
247
+ * `null` when the surface has no active campaign: a web, custom-web or Vev
248
+ * page then keeps selling with the sponsors of the surface's channels
249
+ * (`noActiveCampaign: true`, `campaigns: []`), and nothing it sells carries
250
+ * a campaign. Native apps get a 404 instead.
251
+ */
252
+ campaign?: BootstrapCampaign | null;
253
+ /** True when `campaign` is null on purpose: no campaign is active on the surface. */
254
+ noActiveCampaign?: boolean;
238
255
  primarySponsor?: Sponsor;
239
256
  secondarySponsors?: Sponsor[];
240
257
  features?: {
@@ -246,6 +263,12 @@ interface BootstrapResponse {
246
263
  countryCode: string;
247
264
  currencyCode: string;
248
265
  };
266
+ /**
267
+ * The surface of the key, when the bootstrap says it (the campaigns
268
+ * service answers it on `/v2/mobile/surface` today; here when it adds it).
269
+ * Its `privacyMode` decides what the order attribution may carry.
270
+ */
271
+ surface?: SurfaceResponse['surface'];
249
272
  }
250
273
  /**
251
274
  * A slot this page declares to the campaigns service (ADR-0027 / ADR-0034):
@@ -257,6 +280,8 @@ interface ManifestLocation {
257
280
  displayName?: string;
258
281
  /** The campaign this location belongs to when it differs from the page's (`ManifestRequest.campaignId`). */
259
282
  campaignId?: number;
283
+ /** The kind of element the slot holds — `card` | `carousel` | `detail` for the SDK's, a host's own word otherwise. For the dashboard. */
284
+ kind?: string;
260
285
  }
261
286
  /**
262
287
  * Where something happened, as `Vio.attribution(slot)` answers it — the ONE
@@ -271,6 +296,29 @@ interface Attribution {
271
296
  sponsorId?: number;
272
297
  contentId?: string;
273
298
  contentTitle?: string;
299
+ /** The page's URL within the 2 048 cap (`content_url`) — what Commerce keeps on the order and its lines. */
300
+ contentUrl?: string;
301
+ }
302
+ /**
303
+ * How much context analytics and Commerce may keep of a visit to this
304
+ * surface. `minimal` drops what says WHICH screen someone was on and WHY —
305
+ * the page (`content_url`, `content_title`), the slot (`location_id`) and
306
+ * the entry (referrer, utm, landing page) — the collector drops it from the
307
+ * events on its side (vio-analytics `privacy.ts`); the SDK keeps it off the
308
+ * order and off the cart lines it sends to Commerce. What measures the
309
+ * funnel and bills the sale stays: the surface, the campaign, the kind of
310
+ * component, the products and the amounts. A mode the SDK could not learn
311
+ * counts as `minimal`.
312
+ */
313
+ type SurfacePrivacyMode = 'standard' | 'minimal';
314
+ /** Response of `GET /v2/mobile/surface`: what the key may know of its own surface. */
315
+ interface SurfaceResponse {
316
+ surface?: {
317
+ id?: number;
318
+ name?: string;
319
+ status?: string;
320
+ privacyMode?: SurfacePrivacyMode | string | null;
321
+ } | null;
274
322
  }
275
323
  /** Body of `POST /v2/mobile/components/manifest`. */
276
324
  interface ManifestRequest {
@@ -382,6 +430,12 @@ declare class VioApi {
382
430
  * sends `mode: 'merge'`; see `Vio.declareSlots()` for the dedupe on top.
383
431
  */
384
432
  declareManifest(body: ManifestRequest): Promise<ManifestResult>;
433
+ /**
434
+ * The surface of the key (`GET /v2/mobile/surface`): its id, name, status
435
+ * and `privacyMode` — what decides whether the order attribution may carry
436
+ * the entry context (ADR-0035). A 404 means a service that predates it.
437
+ */
438
+ surface(): Promise<SurfaceResponse>;
385
439
  /**
386
440
  * Get placement components for a campaign. Used by the carousel auto-fetch
387
441
  * mode (`<vio-product-carousel campaign-id="36" location="news-favorites">`).
@@ -469,15 +523,29 @@ declare function createCommerceClient(options: CommerceClientOptions): CommerceC
469
523
  * mental model stays consistent across platforms.
470
524
  */
471
525
  /**
472
- * Where a unit in the cart came from (ADR-0027 / ADR-0034): the campaign
473
- * the place showed when it was added, the place (`slot`) and the content
474
- * (article). What `Vio.attribution(slot)` answers, minus the sponsor and the
475
- * title. The purchase is split per campaign from these.
526
+ * Where a unit in the cart came from (ADR-0027 / ADR-0034 / ADR-0035): the
527
+ * campaign the place showed when it was added, the place (`slot`), the kind
528
+ * of element it was added from, and the content (article: its id, URL and
529
+ * title). What `Vio.attribution(slot)` answers, minus the sponsor, plus the
530
+ * element's kind. The purchase is split per campaign from these, and since
531
+ * 0.26 Commerce receives them with every add (`origin` on the line, ADR-0035
532
+ * phase B) so the order knows where each unit was sold from.
476
533
  */
477
534
  interface CartOrigin {
478
535
  campaignId?: number;
479
536
  slot?: string;
480
537
  contentId?: string;
538
+ /**
539
+ * The kind of component the units were added from, as Commerce and the
540
+ * collector name it: `card` | `carousel` | `detail` for the SDK's elements
541
+ * (`componentKindOf()` — from the element's `componentTemplateId`, else
542
+ * the kind word in the slot id).
543
+ */
544
+ componentKind?: string;
545
+ /** The page the units were added from (`content_url`, ≤ 2 048), kept so a later +1 from the cart credits the same page. */
546
+ contentUrl?: string;
547
+ /** Its title (`content_title`, ≤ 255). */
548
+ contentTitle?: string;
481
549
  }
482
550
  /** One origin of a line and how many of its units came from it. */
483
551
  interface CartLineOrigin extends CartOrigin {
@@ -538,9 +606,10 @@ interface SponsorCartState {
538
606
  * CustomEvent that UI components subscribe to (Web Components react via
539
607
  * `requestUpdate()`).
540
608
  *
541
- * Server-side cart sync (Vio Commerce GraphQL CreateCart / AddToCart
542
- * mutations) is deferred to checkout time via the CheckoutManager —
543
- * this manager is purely client-side state for now.
609
+ * The backend cart (Vio Commerce GraphQL CreateCart / AddItem / UpdateItem /
610
+ * DeleteItem) is kept in sync optimistically from here; since 0.26 every add
611
+ * and every increase tells it where the units come from (`origin`, ADR-0035
612
+ * phase B) so the order is born attributed — see `origin-wire.ts`.
544
613
  */
545
614
 
546
615
  interface CartChangeDetail {
@@ -689,12 +758,15 @@ declare class CartManager extends EventTarget {
689
758
  origin?: CartOrigin;
690
759
  }): void;
691
760
  /** Send a line's target quantity to the backend — ONE request in flight per
692
- * line. A target that arrives while one is in flight replaces any earlier
693
- * waiting one and goes out when the current request settles, so the server
694
- * ends on the last intent and never sees two updates racing each other.
695
- * While the sync is active the line keeps its optimistic quantity through
696
- * any backend response (see `updateFromBackendCart`); a rejection drops
697
- * what was queued and reconciles the cart from the server. */
761
+ * line. Targets that arrive while one is in flight queue behind it as
762
+ * steps (`queueStep`): reduced like the line's origins, sent in order once
763
+ * the current request settles — one request per queued increase from a
764
+ * different origin, one for a net decrease — so the server ends on the
765
+ * last intent, credits every increase to its own origin, and never sees two
766
+ * updates racing each other. While the sync is active the line keeps its
767
+ * optimistic quantity through any backend response (see
768
+ * `updateFromBackendCart`); a rejection drops what was queued and
769
+ * reconciles the cart from the server. */
698
770
  private syncQuantity;
699
771
  removeItem(lineItemId: string, sponsorId: number): void;
700
772
  clearSponsorCart(sponsorId: number): void;
@@ -719,9 +791,15 @@ declare class CartManager extends EventTarget {
719
791
  private loadFromStorage;
720
792
  private emit;
721
793
  }
722
- /** Only the three attribution fields, trimmed; `{}` when nothing is known. */
794
+ /**
795
+ * Only the attribution fields, trimmed; `{}` when nothing is known. The kind
796
+ * of element is kept as Commerce and the collector name it (`card`,
797
+ * `carousel`, `detail` — `componentKindOf`), from `componentKind` or from
798
+ * the `componentTemplateId` an element's attribution carries (`product-card`);
799
+ * the page as origin + path (`contentUrl`) and its title, within Commerce's caps.
800
+ */
723
801
  declare function normalizeOrigin(origin: CartOrigin | null | undefined): CartOrigin;
724
- /** Two origins are the same place when campaign, slot and content all match. */
802
+ /** Two origins are the same place when campaign, slot, content and kind of element all match (the page's URL and title are attributes of the place, not its identity). */
725
803
  declare function sameOrigin(a: CartOrigin, b: CartOrigin): boolean;
726
804
  /**
727
805
  * The origins of a line for reporting: what it carries, plus an empty origin
@@ -731,610 +809,1405 @@ declare function sameOrigin(a: CartOrigin, b: CartOrigin): boolean;
731
809
  declare function lineOrigins(line: Pick<CartLineItem, 'origins' | 'quantity'>): CartLineOrigin[];
732
810
 
733
811
  /**
734
- * Vipps MobilePay Payment Adapter — GraphQL Integration & Types.
735
- *
736
- * Vipps payment integration uses 2 GraphQL operations:
812
+ * What the commerce gateway may not know yet.
737
813
  *
738
- * 1. `CreatePaymentVipps` mutation:
739
- * Initiates a Vipps payment session for a checkout and returns `payment_url`.
814
+ * The SDK ships ahead of the gateway more than once a quarter: a selection
815
+ * (`buyer_ref`, 2026-10-19), an argument (`origin` on the cart lines,
816
+ * `attribution` on the checkout — ADR-0035 phase B). A gateway that does not
817
+ * have the field refuses the WHOLE operation at validation — before anything
818
+ * runs, with no `data` — so each such operation is sent again without the
819
+ * field, once, and the answer remembered for the page load. An operation that
820
+ * DID run and failed is never repeated: for a mutation that would be a second
821
+ * charge, a second line in the cart.
740
822
  *
741
- * 2. `GetVippsStatus` query:
742
- * Queries Vipps payment state directly for a checkout (`state`).
823
+ * One mechanism for all of them, so the next field costs a token here and a
824
+ * call to `withGatewayFeature`, not a new copy of the retry.
743
825
  */
744
-
826
+ type GatewayFeature = 'buyer_ref' | 'line_origin' | 'checkout_attribution';
827
+ /** Whether this page load still expects the gateway to know `feature` (false once it refused it). */
828
+ declare function gatewayHas(feature: GatewayFeature): boolean;
829
+ /** Forget what this page load learned (tests). One feature, or all of them. */
830
+ declare function resetGatewayFeatures(feature?: GatewayFeature): void;
745
831
  /**
746
- * GraphQL mutation to create a Vipps payment session.
832
+ * True when `err` is the gateway refusing something it does not know, named
833
+ * `token` (`Cannot query field "buyer_ref" on type …`, `Unknown argument
834
+ * "origin" on field …`, `Unknown type "CartLineOriginInput"`, `Field "origin" is
835
+ * not defined by type "LineItemInput"`): a validation error, with no `data`
836
+ * and no `path` because nothing ran — safe to send again without it, a
837
+ * mutation included. Anything that ran (data present, or an error with a
838
+ * path) or any other message is not.
747
839
  */
748
- declare const CREATE_PAYMENT_VIPPS_MUTATION = "\nmutation CreatePaymentVipps($checkoutId: String!, $email: String, $returnUrl: String!, $express: Boolean) {\n Payment {\n CreatePaymentVipps(checkout_id: $checkoutId, email: $email, return_url: $returnUrl, express: $express) {\n payment_url\n reference\n express\n reason\n }\n }\n}\n";
840
+ declare function isUnknownTokenError(err: unknown, token: string): boolean;
841
+ /** `isUnknownTokenError` for any of the names a feature goes by. */
842
+ declare function isUnknownFeatureError(err: unknown, feature: GatewayFeature): boolean;
749
843
  /**
750
- * GraphQL query to fetch current Vipps payment status.
844
+ * Runs `request(true)` (the operation with the feature); when the gateway
845
+ * does not know it, `request(false)` (without it) — and from then on, for
846
+ * this page load, straight to the latter. Said once in the console: a
847
+ * gateway behind the SDK is worth knowing about, and so is a renamed type.
751
848
  */
752
- declare const GET_VIPPS_STATUS_QUERY = "\nquery GetVippsStatus($checkoutId: String!) {\n Payment {\n GetVippsStatus(checkout_id: $checkoutId) {\n state\n duplicate_of\n }\n }\n}\n";
753
- interface CreatePaymentVippsVariables extends Record<string, unknown> {
754
- checkoutId: string;
755
- /** Optional since Express: Vipps hands the email back with the payment. */
756
- email?: string;
757
- returnUrl: string;
849
+ declare function withGatewayFeature<T>(feature: GatewayFeature, request: (supported: boolean) => Promise<T>): Promise<T>;
850
+
851
+ /**
852
+ * A line origin as Commerce receives it (ADR-0035 phase B): `origin` on each
853
+ * line of `AddItem` and, when the quantity goes UP, on `UpdateItem` — the
854
+ * origin of the units that update adds. shopcart keeps every origin of a
855
+ * line with its units (`cart_item.origins`) and writes the order from them:
856
+ * `order_item.campaign_id` / `location_id` / `component_kind`, and the
857
+ * order's page when the checkout does not name one.
858
+ *
859
+ * snake_case on the wire, the caps of shopcart's `LineOriginDto` enforced
860
+ * here, once. Pure: `lineOriginWire()` takes the origin and the page.
861
+ */
862
+
863
+ /** The largest `Int` GraphQL coerces; a bigger id would fail the whole mutation. */
864
+ declare const GRAPHQL_INT_MAX = 2147483647;
865
+ /** `LineOriginDto` (shopcart `cart/dtos/send/line-origin.dto.ts`). */
866
+ interface LineOriginWire {
867
+ campaign_id?: number;
868
+ location_id?: string;
869
+ component_kind?: string;
870
+ content_url?: string;
871
+ content_title?: string;
872
+ }
873
+ /** The caps of `LineOriginDto` — a longer value is refused whole by the API, so it is cut here. */
874
+ declare const LINE_ORIGIN_CAPS: Readonly<{
875
+ locationId: 100;
876
+ componentKind: 32;
877
+ contentUrl: 2048;
878
+ contentTitle: 255;
879
+ }>;
880
+ /** The page the units are added from, for an origin that does not carry its own. */
881
+ interface OriginPage {
882
+ contentUrl?: string;
883
+ contentTitle?: string;
884
+ }
885
+ interface LineOriginWireOptions {
886
+ /** The current page, for an origin that does not carry its own. */
887
+ page?: OriginPage;
758
888
  /**
759
- * `true` asks for Express — address and delivery chosen inside the Vipps
760
- * app. When the cart cannot have it, no payment is created and the answer
761
- * says `express: false` with the reason; the caller then opens the
762
- * ordinary checkout.
889
+ * The surface's privacy mode. Only on a `standard` surface do the slot
890
+ * (`location_id`) and the page (`content_url`, `content_title`) travel —
891
+ * which screen a unit was sold from is what `minimal` keeps private, the
892
+ * same promise the collector keeps for the events. `campaign_id` and
893
+ * `component_kind` always do. Default `unknown`: fail closed.
763
894
  */
764
- express?: boolean;
765
- }
766
- interface CreatePaymentVippsResponse {
767
- /** Absent when Express was asked for and refused. */
768
- payment_url?: string;
769
- /** The reference the seller reads in its Vipps portal. */
770
- reference?: string;
771
- express?: boolean;
772
- reason?: string;
773
- }
774
- interface GetVippsStatusVariables extends Record<string, unknown> {
775
- checkoutId: string;
776
- }
777
- interface VippsStatusResult {
778
- state: string;
779
- /** Set when this checkout was closed as a duplicate: the checkout that paid the cart. */
780
- duplicate_of?: string | null;
895
+ privacyMode?: SurfacePrivacyMode | 'unknown';
781
896
  }
782
- type VippsPaymentState = 'CREATED' | 'INITIATED' | 'AUTHORIZED' | 'TERMINATED' | 'EXPIRED' | string;
783
897
  /**
784
- * Executes the `CreatePaymentVipps` GraphQL mutation.
898
+ * The origin for the wire, or `undefined` when the SDK knows no PLACE for the
899
+ * units — no campaign, no slot, no kind of element (a host adding with no
900
+ * attribution): then nothing is sent and the line inherits the order's
901
+ * campaign on the server. The page is the origin's own when it has one — the
902
+ * page the units were added from — else the current page's, as origin + path;
903
+ * `campaign_id` only as a positive `Int` (the DTO's `@IsInt() @Min(1)`, the
904
+ * gateway's 32-bit coercion).
785
905
  */
786
- declare function createPaymentVipps(variables: CreatePaymentVippsVariables | Record<string, unknown>, options?: CartQueryOptions): Promise<CreatePaymentVippsResponse | null>;
906
+ declare function lineOriginWire(origin: CartOrigin | null | undefined, options?: LineOriginWireOptions): LineOriginWire | undefined;
907
+
787
908
  /**
788
- * Executes the `GetVippsStatus` GraphQL query.
909
+ * The `client` block — the standard context of the visit (EVENTS_CONTRACT
910
+ * "client", 2026-10-19): language, where the session landed from (the
911
+ * referrer and `utm_*` of the LANDING page, remembered for the session),
912
+ * screen size and time zone. What the collector derives from the request
913
+ * itself — device type, OS, browser, country — is NOT sent: no User-Agent
914
+ * parsing here, no duplicate of what the server knows better.
915
+ *
916
+ * Pure like `content.ts`: everything takes the globals it reads (defaults to
917
+ * the real ones), never throws, and answers `{}` where there is no browser
918
+ * (SSR, old engines). Capped to the collector's limits in ONE place: an
919
+ * over-long value is cut — none of these is an identity, so a cut referrer
920
+ * is still the right dimension.
921
+ *
922
+ * Landing memory: the first page of a session writes its referrer and utm
923
+ * parameters to `sessionStorage`; later pages read them back, so an event on
924
+ * page three still says the visit came from the newsletter — their own
925
+ * `document.referrer` (an internal page) is never a source. The referrer is
926
+ * kept as origin + path (no query, no fragment: a referrer's query is its
927
+ * tracking tail, not where the visit came from) and dropped altogether when
928
+ * it is this same host (a self-referral says nothing about the entry). A URL
929
+ * with utm parameters is a new landing (another campaign click) that replaces
930
+ * the memory — unless it was reached from this same host, i.e. an internal
931
+ * link that propagates utm, which keeps the landing that was. Without
932
+ * storage, or without consent to persist, each page stands alone.
789
933
  */
790
- declare function getVippsStatus(variables: GetVippsStatusVariables | Record<string, unknown>, options?: CartQueryOptions): Promise<VippsStatusResult | null>;
791
-
792
- declare const CREATE_CART_MUTATION = "\nmutation CreateCart($customerSessionId: String!, $currency: String!, $shippingCountry: String) {\n Cart {\n CreateCart(customer_session_id: $customerSessionId, currency: $currency, shipping_country: $shippingCountry) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
793
- declare const ADD_ITEM_MUTATION = "\nmutation AddItem($cartId: String!, $lineItems: [LineItemInput!]!) {\n Cart {\n AddItem(cart_id: $cartId, line_items: $lineItems) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
794
- declare const DELETE_ITEM_MUTATION = "\nmutation DeleteItem($cartId: String!, $cartItemId: String!) {\n Cart {\n DeleteItem(cart_id: $cartId, cart_item_id: $cartItemId) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
795
- declare const UPDATE_ITEM_MUTATION = "\nmutation UpdateItem($cartId: String!, $cartItemId: String!, $shippingId: String, $qty: Int) {\n Cart {\n UpdateItem(cart_id: $cartId, cart_item_id: $cartItemId, shipping_id: $shippingId, qty: $qty) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
796
- declare const GET_CART_QUERY = "\nquery GetCart($cartId: String!) {\n Cart {\n GetCart(cart_id: $cartId) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
797
- declare const GET_LINE_ITEMS_BY_SUPPLIER_QUERY = "\nquery GetLineItemsBySupplier($cartId: String!) {\n Cart {\n GetLineItemsBySupplier(cart_id: $cartId) {\n supplier {\n id\n name\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n }\n }\n}\n";
798
- declare const UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION = "\nmutation UpdateShippingsBySupplier($cartId: String!, $data: [DataUpdateShippingsBySupplier!]!) {\n Cart {\n UpdateShippingsBySupplier(cart_id: $cartId, data: $data) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
799
- declare const CREATE_CHECKOUT_MUTATION = "\nmutation CreateCheckout($cartId: String!) {\n Checkout {\n CreateCheckout(cart_id: $cartId) {\n buyer_accepts_purchase_conditions\n buyer_accepts_terms_conditions\n created_at\n updated_at\n id\n deleted_at\n success_url\n cancel_url\n payment_method\n email\n status\n checkout_url\n origin_payment_id\n billing_address {\n id\n first_name\n last_name\n phone_code\n phone\n company\n address1\n address2\n city\n province\n province_code\n country\n country_code\n zip\n }\n shipping_address {\n id\n first_name\n last_name\n phone_code\n phone\n company\n address1\n address2\n city\n province\n province_code\n country\n country_code\n zip\n }\n available_payment_methods {\n name\n }\n discount_code\n cart {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n totals {\n currency_code\n subtotal\n shipping\n total\n taxes\n tax_rate\n discounts\n }\n }\n }\n}\n";
800
- declare const UPDATE_CHECKOUT_MUTATION = "\nmutation UpdateCheckout($checkoutId: String!, $status: String, $successUrl: String, $email: String, $cancelUrl: String, $paymentMethod: String, $shippingAddress: AddressArgs, $billingAddress: AddressArgs, $buyerAcceptsPurchaseConditions: Boolean, $buyerAcceptsTermsConditions: Boolean) {\n Checkout {\n UpdateCheckout(checkout_id: $checkoutId, status: $status, success_url: $successUrl, email: $email, cancel_url: $cancelUrl, payment_method: $paymentMethod, shipping_address: $shippingAddress, billing_address: $billingAddress, buyer_accepts_purchase_conditions: $buyerAcceptsPurchaseConditions, buyer_accepts_terms_conditions: $buyerAcceptsTermsConditions) {\n buyer_accepts_purchase_conditions\n buyer_accepts_terms_conditions\n created_at\n updated_at\n id\n deleted_at\n success_url\n cancel_url\n payment_method\n email\n status\n checkout_url\n origin_payment_id\n billing_address {\n id\n first_name\n last_name\n phone_code\n phone\n company\n address1\n address2\n city\n province\n province_code\n country\n country_code\n zip\n }\n shipping_address {\n id\n first_name\n last_name\n phone_code\n phone\n company\n address1\n address2\n city\n province\n province_code\n country\n country_code\n zip\n }\n available_payment_methods {\n name\n }\n discount_code\n cart {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n totals {\n currency_code\n subtotal\n shipping\n total\n taxes\n tax_rate\n discounts\n }\n }\n }\n}\n";
801
- declare const GET_CHECKOUT_QUERY = "\nquery GetCheckout($checkoutId: String!) {\n Checkout {\n GetCheckout(checkout_id: $checkoutId) {\n buyer_accepts_purchase_conditions\n buyer_accepts_terms_conditions\n created_at\n updated_at\n id\n deleted_at\n success_url\n cancel_url\n payment_method\n email\n status\n checkout_url\n origin_payment_id\n duplicate_of\n billing_address {\n id\n first_name\n last_name\n phone_code\n phone\n company\n address1\n address2\n city\n province\n province_code\n country\n country_code\n zip\n }\n shipping_address {\n id\n first_name\n last_name\n phone_code\n phone\n company\n address1\n address2\n city\n province\n province_code\n country\n country_code\n zip\n }\n available_payment_methods {\n name\n }\n discount_code\n cart {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n totals {\n currency_code\n subtotal\n shipping\n total\n taxes\n tax_rate\n discounts\n }\n }\n }\n}\n";
802
- declare const GET_AVAILABLE_PAYMENT_METHODS_QUERY = "\nquery Config {\n Payment {\n GetAvailablePaymentMethods {\n config {\n name\n type\n value\n }\n name\n }\n }\n}\n";
803
- /** Transport options for a cart/checkout GraphQL call. */
804
- interface CartQueryOptions {
805
- endpoint?: string;
806
- apiKey?: string;
807
- /** Sponsor commerce key — goes in the Authorization header. */
808
- commerceKey?: string;
809
- /** True when the commerce key came from a resolved sponsor (bootstrap).
810
- * False = we fell back to the host apiKey — writes like CreateCart should
811
- * NOT proceed with the wrong key (the cart would be orphaned). */
812
- sponsorResolved?: boolean;
813
- }
934
+ interface ClientContext {
935
+ language?: string;
936
+ referrer?: string;
937
+ utm_source?: string;
938
+ utm_medium?: string;
939
+ utm_campaign?: string;
940
+ utm_term?: string;
941
+ utm_content?: string;
942
+ screen_w?: number;
943
+ screen_h?: number;
944
+ tz?: string;
945
+ }
946
+ declare const UTM_KEYS: readonly ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
947
+ type UtmKey = (typeof UTM_KEYS)[number];
948
+ /** Commerce's cap for the landing page on the order's entry context (`entry.landing_url`, ADR-0035). */
949
+ declare const CLIENT_LANDING_URL_MAX_LENGTH = 2048;
814
950
  /**
815
- * Resolve endpoint + keys for cart GraphQL calls. Awaits Vio.bootstrap() once
816
- * if it hasn't run yet (the sponsor commerce keys come from bootstrap), then
817
- * uses the sponsor's commerce apiKey (via the Vio facade on globalThis,
818
- * avoiding an import cycle); falls back to the host apiKey.
951
+ * What a session remembers of the page it landed on. `url` is the landing
952
+ * page itself — origin + path, like the referrer: no query (the utm are kept
953
+ * apart, the rest is a tracking tail), no fragment — for the order's entry
954
+ * context; the events do not carry it (the collector derives the landing).
819
955
  */
820
- declare function getCartGraphQLOptions(sponsorId?: number): Promise<CartQueryOptions>;
821
- /** Stable per-browser customer session id (backend CreateCart requires one). */
822
- declare function getCustomerSessionId(): string;
823
- interface GraphQLEnvelope {
824
- data?: Record<string, Record<string, unknown>>;
825
- errors?: Array<{
826
- message?: string;
827
- }>;
956
+ interface Landing {
957
+ referrer?: string;
958
+ utm?: Partial<Record<UtmKey, string>>;
959
+ url?: string;
960
+ }
961
+ interface StorageLike {
962
+ getItem(key: string): string | null;
963
+ setItem(key: string, value: string): void;
964
+ removeItem?(key: string): void;
965
+ }
966
+ /** The globals the block is read from — injectable for tests; defaults to the real ones. */
967
+ interface ClientEnvironment {
968
+ navigator?: {
969
+ language?: unknown;
970
+ } | null;
971
+ document?: {
972
+ referrer?: unknown;
973
+ } | null;
974
+ location?: {
975
+ search?: unknown;
976
+ host?: unknown;
977
+ href?: unknown;
978
+ } | null;
979
+ screen?: {
980
+ width?: unknown;
981
+ height?: unknown;
982
+ } | null;
983
+ /** The session memory for the landing; `null` = none (SSR, private mode, blocked). */
984
+ storage?: StorageLike | null;
985
+ /** The time zone; defaults to `Intl.DateTimeFormat().resolvedOptions().timeZone`. */
986
+ timeZone?: () => unknown;
828
987
  }
829
- /** Execute a GraphQL query/mutation against the configured commerce endpoint.
830
- * Callers that skip `getCartGraphQLOptions()` (or pass a bare sponsorId) get
831
- * it resolved here instead of silently sending an unauthenticated request.
832
- * Rejects after `CART_GRAPHQL_TIMEOUT_MS` (and aborts the fetch). */
833
- declare function executeCartGraphQL(query: string, variables: Record<string, unknown>, options?: CartQueryOptions | number | undefined): Promise<GraphQLEnvelope>;
834
- declare function createCart(customerSessionId: string, currency: string, shippingCountry?: string | null, options?: CartQueryOptions): Promise<any>;
835
- declare function addItem(cartId: string, lineItems: unknown[], options?: CartQueryOptions): Promise<any>;
836
- declare function deleteItem(cartId: string, cartItemId: string, options?: CartQueryOptions): Promise<any>;
837
- declare function updateItem(cartId: string, cartItemId: string, shippingId?: string | null, qty?: number | null, options?: CartQueryOptions): Promise<any>;
838
- declare function getCart(cartId: string, options?: CartQueryOptions): Promise<any>;
839
- declare function getLineItemsBySupplier(cartId: string, options?: CartQueryOptions): Promise<any>;
840
- declare function updateShippingsBySupplier(cartId: string, data: unknown[], options?: CartQueryOptions): Promise<any>;
841
- declare function createCheckout(cartId: string, options?: CartQueryOptions): Promise<any>;
842
- declare function updateCheckout(variables: Record<string, unknown>, options?: CartQueryOptions): Promise<any>;
843
- declare function getCheckout(checkoutId: string, options?: CartQueryOptions): Promise<any>;
844
- declare function getAvailablePaymentMethods(options?: CartQueryOptions): Promise<any>;
845
- declare const CREATE_PAYMENT_STRIPE_MUTATION = "\nmutation CreatePaymentStripe($checkoutId: String!, $successUrl: String!, $paymentMethod: String!, $email: String!) {\n Payment {\n CreatePaymentStripe(checkout_id: $checkoutId, success_url: $successUrl, payment_method: $paymentMethod, email: $email) {\n order_id\n checkout_url\n }\n }\n}\n";
846
- declare function createPaymentStripe(variables: Record<string, unknown>, options?: CartQueryOptions): Promise<any>;
988
+ /**
989
+ * The landing of this session: what is remembered — unless this page is a
990
+ * landing itself (utm parameters in its URL, reached from outside this host)
991
+ * or nothing is remembered yet, when this page's referrer and utm are taken
992
+ * (and, with `persist`, kept).
993
+ */
994
+ declare function resolveLanding(env?: ClientEnvironment, persist?: boolean): Landing;
995
+ /** The landing page as the landing keeps it: origin + path, no query, no fragment; capped. */
996
+ declare function landingUrl(raw: unknown): string | undefined;
997
+ /**
998
+ * The landing this session REMEMBERS — what was written by the first page —
999
+ * or null when nothing is stored (no storage, no consent, analytics never
1000
+ * started). Read only: for the order's entry context, which must not take the
1001
+ * page the shopper pays on for the page they came in through.
1002
+ */
1003
+ declare function storedLanding(storage?: StorageLike | null | undefined): Landing | null;
1004
+ /** The block for the wire — read once per page load by the manager and reused on every event. */
1005
+ declare function resolveClientContext(env?: ClientEnvironment, landing?: Landing): ClientContext;
847
1006
 
848
1007
  /**
849
- * Stripe on our own page — the Payment Element, no redirect.
1008
+ * Slots — the pure half of ADR-0027 / ADR-0034 on the web.
850
1009
  *
851
- * The older web flow minted a Stripe Payment Link and sent the shopper to a
852
- * page hosted by Stripe. This one keeps them here (Angelo, 2026-09-24):
1010
+ * A slot is a NAME for a place on a page. The page declares its slots to the
1011
+ * campaigns service (`Vio.declareSlots()` / `Vio.registerSlot()` in client.ts),
1012
+ * the dashboard assigns a campaign to each one, optionally with a date window,
1013
+ * and `GET /v2/mobile/config` answers with `campaigns[].slots[]` — only the
1014
+ * assignments whose window is open NOW. Whatever no slot names shows the
1015
+ * surface's default campaign (`campaign`).
853
1016
  *
854
- * 1. Vio Commerce creates a PaymentIntent for the checkout
855
- * (`Payment { CreatePaymentIntentStripe }`) and answers with its client
856
- * secret AND the publishable key of the account that minted it — the
857
- * seller's when they brought both keys, ours otherwise. The two are
858
- * halves of one account: the browser confirms with the key what the
859
- * server minted with the secret one.
860
- * 2. Stripe's Payment Element is mounted in a LIGHT-DOM container, the way
861
- * the other embedded widgets are. Card fields are Stripe's own iframes,
862
- * so card data never touches this SDK or our servers.
863
- * 3. `confirm()` pays. A 3-D Secure challenge opens in Stripe's own modal
864
- * over the page: `redirect: 'if_required'` keeps the shopper here for
865
- * cards, and still redirects for a method that has no other way.
866
- * 4. The Commerce order is NOT born here. Stripe's `payment_intent.succeeded`
867
- * webhook creates it, verified server-side; the checkout is polled for
868
- * its status afterwards, exactly as the redirect return does.
1017
+ * Everything here is side-effect free and never throws on a malformed
1018
+ * bootstrap: a broken `campaigns[]` must never blank a product block or break
1019
+ * `track()`.
869
1020
  */
870
1021
 
871
- /** What the backend hands over to pay a checkout on our page. */
872
- interface StripeIntent {
873
- /** `pi_..._secret_...`: what the browser confirms. */
874
- client_secret: string;
875
- /** The key of the SAME account that minted the secret. */
876
- publishable_key: string;
877
- customer?: string;
878
- /**
879
- * Stripe Connect (ADR-0022): the seller's connected account, where the
880
- * intent lives. The browser must name it or Stripe answers "no such
881
- * payment_intent". Absent for every seller not on Connect.
882
- */
883
- stripe_account?: string;
884
- }
885
- /** What `confirm()` answers. The browser's word is never the order. */
886
- type StripeConfirmOutcome = {
887
- status: 'succeeded';
888
- paymentIntentId?: string;
889
- }
890
- /** Authorised but not settled yet, or Stripe redirected and will return. */
891
- | {
892
- status: 'pending';
893
- paymentIntentId?: string;
894
- } | {
895
- status: 'failed';
896
- message?: string;
897
- code?: string;
1022
+ /** What `Vio.declareSlots()` accepts per slot. */
1023
+ type SlotInput = string | {
1024
+ id: string;
1025
+ displayName?: string;
1026
+ campaignId?: number;
1027
+ kind?: string;
898
1028
  };
899
- interface StripeElementHandle {
900
- confirm(): Promise<StripeConfirmOutcome>;
901
- unmount(): void;
1029
+ /** The third argument of `Vio.registerSlot(id, displayName, …)`: the element's own campaign and what kind of element it is. */
1030
+ interface RegisterSlotOptions {
1031
+ campaignId?: number | string | null;
1032
+ /** `card` | `carousel` | `detail` for the SDK's elements; a host may name its own kind. */
1033
+ kind?: string;
902
1034
  }
903
-
904
1035
  /**
905
- * Apple Pay on the Web — via Stripe Payment Request API.
906
- *
907
- * Architecture decision (matches iOS Vio SDK pattern):
908
- * - One Stripe **publishable key** for the Vio platform (global, set
909
- * via `Vio.init({ stripePublishableKey })`).
910
- * - One **Stripe Connect account id** per sponsor — sourced from
911
- * `Vio.bootstrap()` response. Without it, the payment lands on the
912
- * platform account instead of the merchant.
913
- *
914
- * Apple Pay on the Web prerequisites (Stripe-managed for us):
915
- * - Stripe Connect account is enabled for Apple Pay
916
- * - Domain registration for Apple Pay (Stripe handles via Dashboard)
917
- * - Safari + macOS/iOS device with Apple Pay configured
1036
+ * The kind of component some units were sold from, as Commerce keeps it on
1037
+ * the order line (`order_item.component_kind`, ≤ 32) — the SAME rule the
1038
+ * collector applies for `breakdown?by=component_kind` (vio-analytics
1039
+ * `componentKind`), so the order and the events agree: what the element says
1040
+ * it is (`component_template_id`: `product-card`, `product-carousel`,
1041
+ * `product-detail`, a Vev block's `vev-card`) lower-cased and without its
1042
+ * `product-` / `vev-` prefix, else the kind word in the slot id the page
1043
+ * declared (`card-<key>`, `<scope>-carousel-2`, `detail-1`); `undefined` when
1044
+ * neither says.
1045
+ */
1046
+ declare function componentKindOf(templateId: unknown, slot?: unknown): string | undefined;
1047
+ /** The kind as the manifest carries it: trimmed, lower-case, cut at 32 chars; `undefined` for nothing. */
1048
+ declare function normalizeSlotKind(value: unknown): string | undefined;
1049
+ type BootLike = Pick<BootstrapResponse, 'campaign' | 'campaigns' | 'primarySponsor' | 'secondarySponsors'>;
1050
+ /**
1051
+ * The campaign a slot shows right now. Among the `status === 'active'`
1052
+ * entries of `campaigns[]`, the one with an assignment named `slot` for this
1053
+ * platform (`platform` null on the assignment, or equal to `platform`) whose
1054
+ * window contains `now` (`startDate <= now < endDate`, null = open). When
1055
+ * several qualify, the surface's default campaign wins, else the first the
1056
+ * backend lists.
918
1057
  *
919
- * In Chrome / non-Apple browsers `canMakePayment()` returns null, so the
920
- * UI hides the Apple Pay button automatically.
1058
+ * No assignment → the PAGE's campaign (`pageCampaignId`: the element's own
1059
+ * `vio-campaign`, an ancestor's `data-vio-campaign`, or `Vio.init({ campaignId })`)
1060
+ * when `campaigns[]` lists it as active — an editorial page resolves to its
1061
+ * campaign before (and regardless of) the manifest round-trip. Else the
1062
+ * default campaign: the `campaigns[]` entry whose id is `campaign.id`, or a
1063
+ * minimal entry built from `campaign` (+ the top-level sponsors) when the
1064
+ * list does not carry it. No bootstrap → null.
921
1065
  */
922
- interface ApplePayConfig {
923
- /** Stripe platform publishable key (pk_live_xxx or pk_test_xxx). */
924
- publishableKey: string;
925
- /** Sponsor's Stripe Connect account (acct_xxx). Optional but recommended. */
926
- connectedAccount?: string;
927
- /** Amount in the smallest unit of the currency (e.g. øre for NOK, cents for USD). */
928
- amount: number;
929
- /** ISO currency code, lowercased internally for Stripe. */
930
- currency: string;
931
- /** Two-letter country code, e.g. "NO". */
932
- country: string;
933
- /** Label shown in the Apple Pay sheet (merchant or order name). */
934
- label: string;
935
- /** Whether to ask the user for payer name. */
936
- requestPayerName?: boolean;
937
- /** Whether to ask the user for payer email. */
938
- requestPayerEmail?: boolean;
939
- /** Whether to ask for shipping address. */
940
- requestShipping?: boolean;
941
- /**
942
- * Shipping options shown natively in the Apple Pay sheet. `amount` is in the
943
- * smallest unit (øre). When set, `requestShipping` is forced on and the sheet
944
- * total = `amount` (items) + the selected option; switching options updates
945
- * the total live (Apple Pay native shipping).
946
- */
947
- shippingOptions?: Array<{
948
- id: string;
949
- label: string;
950
- detail?: string;
951
- amount: number;
952
- }>;
953
- /**
954
- * Charge hook, run INSIDE the sheet's authorize event with the tokenized
955
- * payment. Must perform the real (backend) charge and throw on failure —
956
- * only then does the sheet close with success; a throw closes it with
957
- * failure. Its resolved value is surfaced as `ApplePayResult.confirmation`.
958
- */
959
- onAuthorize?: (result: ApplePayResult) => Promise<unknown>;
960
- }
961
- interface ApplePayResult {
962
- /** Stripe PaymentMethod id (pm_xxx) — pass to backend to confirm the PaymentIntent. */
963
- paymentMethodId: string;
964
- payerName?: string;
965
- payerEmail?: string;
966
- payerPhone?: string;
967
- shippingAddress?: {
968
- line1?: string;
969
- line2?: string;
970
- city?: string;
971
- postal_code?: string;
972
- state?: string;
973
- country?: string;
974
- };
975
- /** Whatever `onAuthorize` resolved with (e.g. backend order_id/status). */
976
- confirmation?: unknown;
977
- }
1066
+ declare function resolveSlotCampaign(boot: BootLike | null | undefined, slot: string | null | undefined, now?: number, platform?: string | null, pageCampaignId?: CampaignHint, sponsorId?: number | null): BootstrapCampaignEntry | null;
978
1067
  /**
979
- * Check whether Apple Pay is available right now (Safari + supported device
980
- * + Stripe configured + domain registered).
981
- *
982
- * Returns false on any failure path — never throws.
1068
+ * The sponsor rule: the active campaign of `campaigns[]` the sponsor takes
1069
+ * part in, as its primary or one of its secondaries — when it is in several,
1070
+ * the surface's default campaign if it is one of them, else the first one
1071
+ * the backend lists. Null when none (or on a malformed list).
983
1072
  */
984
- declare function checkApplePayAvailability(config: ApplePayConfig): Promise<boolean>;
1073
+ declare function sponsorCampaignEntry(boot: BootLike | null | undefined, sponsorId: number | null | undefined): BootstrapCampaignEntry | null;
985
1074
  /**
986
- * Show the Apple Pay sheet. Resolves with the payment method id (`pm_xxx`)
987
- * after the user authorises, or rejects on cancel / error.
988
- *
989
- * The backend must then confirm a Stripe PaymentIntent with this
990
- * payment method id (server-side step, not handled here).
1075
+ * A page campaign as the callers give it: one id, or the candidates in
1076
+ * precedence order (the element's `vio-campaign`, an ancestor's
1077
+ * `data-vio-campaign`, `Vio.init`'s). An unusable level (invalid, paused,
1078
+ * unknown) falls through to the next one; `0`, `null`, `''` mean unset.
991
1079
  */
992
- declare function showApplePaySheet(config: ApplePayConfig): Promise<ApplePayResult | null>;
993
-
994
- /** One line in the express button's purchase data. Amounts in MINOR units. */
995
- interface KlarnaLineItem {
996
- name: string;
997
- quantity: number;
998
- /** Line total (unitPrice × quantity), minor units (e.g. øre for NOK). */
999
- totalAmount: number;
1000
- /** Per-unit price, minor units. Optional. */
1001
- unitPrice?: number;
1002
- /** Merchant SKU / product reference. Optional. */
1003
- reference?: string;
1004
- /** Absolute image URL. Optional. */
1005
- imageUrl?: string;
1006
- }
1007
- /** Payment request handed to the KEC button. Amounts in MINOR units. */
1008
- interface KlarnaPaymentRequestData {
1009
- /** ISO currency code (e.g. "NOK"). */
1010
- currency: string;
1011
- /** Order total in minor units. */
1012
- amount: number;
1013
- /** Line items — shown in the Klarna sheet. */
1014
- supplementaryPurchaseData?: {
1015
- lineItems: KlarnaLineItem[];
1016
- };
1017
- }
1018
- interface KlarnaAuthorizeResult {
1019
- /** Authorization token — backend exchanges it for an order. */
1020
- authorizationToken: string;
1021
- /** Klarna may require a finalize step (multistep flows). */
1022
- finalizeRequired?: boolean;
1023
- /** Raw payload from the SDK, for debugging / forward-compat. */
1024
- raw?: unknown;
1025
- }
1026
- interface KlarnaButtonHandle {
1027
- unmount(): void;
1028
- }
1029
-
1030
- interface KlarnaPaymentsCategory {
1031
- identifier: string;
1032
- name: string;
1033
- asset_urls?: {
1034
- descriptive?: string;
1035
- standard?: string;
1036
- };
1037
- }
1038
- interface KpLoadResult {
1039
- show_form?: boolean;
1040
- error?: unknown;
1041
- }
1042
- interface KpAuthorizeResult {
1043
- approved?: boolean;
1044
- show_form?: boolean;
1045
- authorization_token?: string;
1046
- finalize_required?: boolean;
1047
- error?: unknown;
1048
- }
1049
- interface KlarnaPaymentsApi {
1050
- init(opts: {
1051
- client_token: string;
1052
- }): void;
1053
- load(opts: {
1054
- container: string | HTMLElement;
1055
- payment_method_category?: string;
1056
- }, data: Record<string, unknown>, cb?: (res: KpLoadResult) => void): void;
1057
- authorize(opts: {
1058
- payment_method_category?: string;
1059
- auto_finalize?: boolean;
1060
- }, data: Record<string, unknown>, cb?: (res: KpAuthorizeResult) => void): void;
1061
- /** Debug/lifecycle events — e.g. 'fullscreenOverlayShown'/'fullscreenOverlayHidden'. */
1062
- on?(event: string, cb: () => void): void;
1063
- }
1064
- interface KlarnaGlobal {
1065
- Payments: KlarnaPaymentsApi;
1066
- }
1067
- declare global {
1068
- interface Window {
1069
- Klarna?: KlarnaGlobal;
1070
- klarnaAsyncCallback?: () => void;
1071
- }
1072
- }
1080
+ type CampaignHint = number | string | null | undefined | ReadonlyArray<number | string | null | undefined>;
1081
+ /**
1082
+ * A campaign id as the SDK accepts it: a positive safe integer, given as a
1083
+ * number or as a string of decimal digits (an attribute). Anything else —
1084
+ * `true`, `'0x10'`, `'1e3'`, an array, a float — is "absent".
1085
+ */
1086
+ declare function toCampaignId(value: unknown): number | undefined;
1087
+ /** The usable ids of a hint, in order, without duplicates. */
1088
+ declare function campaignCandidates(hint: CampaignHint): number[];
1089
+ /**
1090
+ * The first candidate of `hint` that `campaigns[]` lists as ACTIVE (and, when
1091
+ * `sponsorId` is given, that the sponsor takes part in — primary or
1092
+ * secondary); null when none — a paused, ended or unknown page campaign is
1093
+ * ignored and the resolution moves on.
1094
+ */
1095
+ declare function activeCampaignEntry(boot: BootLike | null | undefined, hint: CampaignHint, sponsorId?: number): BootstrapCampaignEntry | null;
1096
+ /** The kinds of Vio element that register themselves. */
1097
+ type SlotKind = 'card' | 'carousel' | 'detail';
1098
+ /**
1099
+ * The id an element gets when the host set no `vio-slot`:
1100
+ * `${scope ? scope + '-' : ''}${kind}-${ordinal + 1}` (`carousel-1`, `card-3`,
1101
+ * `article-123-carousel-1`), `ordinal` being the element's index among the
1102
+ * same-kind Vio elements WITH THE SAME SCOPE, in document order at connect
1103
+ * time. The scope is inherited from the DOM (`vio-scope` on the element, else
1104
+ * the nearest `data-vio-scope` ancestor): a Vev page or a Replit article
1105
+ * (`<body data-vio-scope="article-123">`) gets per-page slots with one line
1106
+ * and no per-element naming; a template that wants slots shared across
1107
+ * articles simply sets no scope. On purpose there is NO page path in the id:
1108
+ * the article goes to analytics as `content_id`, not into the slot. Stable
1109
+ * across reloads while the layout is; a layout change leaves a stale entry,
1110
+ * which ADR-0034 accepts.
1111
+ */
1112
+ declare function autoSlotId(kind: SlotKind, ordinal: number, scope?: string | null): string;
1113
+ /** `"Carousel 1"`, `"Card 3"` — with ` · ${scopeName}` when the scope has a human label. */
1114
+ declare function autoSlotDisplayName(kind: SlotKind, ordinal: number, scopeName?: string | null): string;
1115
+ /** A scope value fit for an id: trimmed, inner whitespace → `-`. */
1116
+ declare function normalizeScope(scope: string | null | undefined): string;
1073
1117
 
1074
1118
  /**
1075
- * Kustom (former Klarna Checkout) — embedded KCO v3 checkout.
1119
+ * Vio.analytics — the web SDK's half of the Vio Analytics pipeline.
1076
1120
  *
1077
- * Kustom kept the Klarna Checkout dialect on its own hosts, so the flow is
1078
- * the classic KCO embed:
1121
+ * Events flow to the Vio collector (`POST <eventsBase>/v1/events`, repo
1122
+ * vio-live/vio-analytics) which owns raw storage (ClickHouse) and vendor
1123
+ * fan-out (Mixpanel today). THE SDK NEVER TALKS TO VENDORS — swapping or
1124
+ * dropping a vendor is a server-side change, zero client releases.
1125
+ * Wire contract: vio-analytics `docs/EVENTS_CONTRACT.md` (v1, snake_case).
1079
1126
  *
1080
- * 1. Vio Commerce creates the order (`Payment { CreatePaymentKustom }`,
1081
- * authed with the sponsor's commerce API key; the seller's own
1082
- * kco_*_api_* key lives server-side, per seller, never here). One order
1083
- * per checkout: asking again while it is open UPDATES it.
1084
- * 2. The response's `html_snippet` is injected into a LIGHT-DOM container
1085
- * — it renders Kustom's iframe, which collects address, shipping AND
1086
- * payment by itself (the widget does everything; no Vio address form).
1087
- * 3. While it is open, the widget talks through `window._klarnaCheckout`:
1088
- * we suspend it around a cart change (`Payment { SyncPaymentKustom }`),
1089
- * and listen to its totals so our summary never disagrees with it.
1090
- * 4. On completion Kustom redirects to the confirmation URL
1091
- * (`?order_id=…&payment_processor=KUSTOM&checkout_id=…`), where the SDK
1092
- * re-reads the order (`Payment { GetKustomOrder }`) — the backend
1093
- * completes it right there — and renders Kustom's own receipt.
1127
+ * Design rules:
1128
+ * - SIDE-EFFECT-FREE MODULE. Nothing runs at import time (tsup/tree-shaking
1129
+ * contract, see lessons/web-sdk-tsup-singleton-and-build). Listeners are
1130
+ * installed by an explicit `Vio.analytics.start()`.
1131
+ * - `track()` works BEFORE `Vio.init()` — events queue and flush once the
1132
+ * SDK is configured (transport resolves at flush time, not at track time).
1133
+ * - SSR-safe: without `window` every call is a silent no-op.
1134
+ * - Retries are safe: `event_id` is stable per event, the collector
1135
+ * dedupes. Queue is bounded (drop-oldest) so a dead collector can never
1136
+ * grow memory unbounded.
1137
+ * - Anti-double-count: the SDK only reports what the server can't see.
1138
+ * Votes/participations are server truth; `ad_activation`/`cart_intent`
1139
+ * are mirrored server-side, never from here.
1094
1140
  *
1095
- * The Commerce order is created server-side, from what Kustom says it
1096
- * charged (Order Management), whether the shopper comes back or not (the
1097
- * push) — the browser never confirms money.
1141
+ * Three sinks, matching the Vev precedent (vio-vev analytics.ts, which
1142
+ * becomes an adapter over this in F3):
1143
+ * 1. collector (default ON once started)
1144
+ * 2. window.dataLayer — GA4/GTM (opt-in `start({ dataLayer: true })`,
1145
+ * the publisher-facing feature; NOT part of the Vio pipeline)
1146
+ * 3. `vio:analytics` DOM event — always when started (inspector/bridges)
1098
1147
  */
1099
1148
 
1100
- /** A Kustom order as shopcart normalizes it (never Kustom's raw order). */
1101
- interface KustomOrder {
1102
- order_id: string;
1103
- /** `checkout_incomplete` | `checkout_complete`, lower-cased by the backend. */
1104
- status: string;
1105
- html_snippet: string;
1106
- purchase_country?: string;
1107
- purchase_currency?: string;
1108
- checkout_id?: string;
1109
- environment?: string;
1110
- /** Goods + the shipping shown or charged, decimal. */
1111
- total_price?: number;
1112
- shipping_name?: string;
1113
- shipping_price?: number;
1114
- /** Whether the Vio Commerce order exists (after completion). */
1115
- order_created?: boolean;
1116
- /** Kustom's short shopper-facing reference (after completion). */
1117
- reference?: string;
1118
- }
1119
- interface KustomShippingChange {
1120
- id?: string;
1121
- name?: string;
1122
- description?: string;
1123
- /** Minor units, as the widget reports them. */
1124
- price?: number;
1125
- tax_amount?: number;
1126
- tax_rate?: number;
1127
- }
1128
- interface KustomListenerHandlers {
1129
- /** The iframe rendered. */
1130
- onLoad?: (data: any) => void;
1131
- /** The order total the widget shows, in MINOR units. */
1132
- onOrderTotalChange?: (orderTotalMinor: number) => void;
1133
- /** The shopper picked a rate inside the widget. */
1134
- onShippingOptionChange?: (option: KustomShippingChange) => void;
1135
- /** The shopper could not pay (declined credit…): show another way. */
1136
- onCannotComplete?: () => void;
1137
- onNetworkError?: () => void;
1138
- /** The shopper is about to leave for the confirmation page. */
1139
- onRedirectInitiated?: () => void;
1140
- }
1141
-
1142
- /**
1143
- * Qliro Checkout — embedded checkout via Vio Commerce.
1144
- *
1145
- * Same embed philosophy as Kustom (the widget collects address, shipping
1146
- * AND payment), different provider API underneath — shopcart normalizes
1147
- * Qliro's Merchant API (OrderId / CustomerCheckoutStatus / OrderHtmlSnippet)
1148
- * to the { order_id, status, html_snippet } shape both flows share:
1149
- *
1150
- * 1. `Payment { CreatePaymentQliro }` creates the order and returns the
1151
- * snippet (sponsor-key authed; the seller's MerchantApiKey/Secret live
1152
- * server-side, per seller, never here).
1153
- * 2. The snippet is injected (script-executing embed, shared with Kustom).
1154
- * 3. On completion Qliro redirects to the confirmation URL
1155
- * (`?checkout_id=…&payment_processor=QLIRO`); the SDK re-reads the
1156
- * order via `Payment { GetQliroOrder(checkout_id) }` — Qliro
1157
- * correlates by the OWNING CHECKOUT, not the order id — and renders
1158
- * the snippet again, which shows Qliro's own receipt.
1159
- *
1160
- * The Commerce order is created server-side by Qliro's push webhook
1161
- * (shopcart /checkout/payment/qliro/ok) — the browser never confirms money.
1162
- */
1163
-
1164
- /** Normalized Qliro order (shopcart maps the PascalCase Merchant API). */
1165
- interface QliroOrder {
1166
- order_id: string;
1167
- /** Qliro CustomerCheckoutStatus: InProcess | Completed | OnHold | Refused. */
1168
- status: string;
1169
- html_snippet: string;
1170
- purchase_country?: string;
1171
- purchase_currency?: string;
1172
- total_price?: number;
1173
- }
1174
-
1175
- /**
1176
- * Qliro's browser-side protocol (`q1`).
1177
- *
1178
- * The embedded widget publishes a `q1` object on the window and calls a
1179
- * globally-defined `q1Ready` once it has rendered. Everything the customer
1180
- * does inside the iframe — picking a shipping method, changing their address,
1181
- * having a payment declined — is only observable through these listeners.
1182
- * Without them the widget is a black box: the customer can change the
1183
- * shipping and our own summary keeps showing the old total.
1184
- *
1185
- * Two rules from Qliro's documentation shape this module:
1186
- *
1187
- * 1. **Lock before touching the backend.** Otherwise the customer can
1188
- * complete the purchase while the order is still being synchronized.
1189
- * 2. **Compare before unlocking.** Otherwise a second change interrupts the
1190
- * synchronization of the first.
1191
- *
1192
- * We compare on `MerchantUpdateVersion`, the token Qliro echoes back through
1193
- * `onOrderUpdated`, rather than on totals. Totals cannot be the test here: in
1194
- * the modes where Qliro owns the shipping amount our total and Qliro's
1195
- * legitimately differ, and the customer would sit locked out of a checkout
1196
- * that is in fact up to date.
1197
- */
1198
-
1199
- interface QliroSyncResult {
1200
- order_id: string;
1201
- /** Token to wait for in `onOrderUpdated` before unlocking. */
1202
- update_version: string;
1203
- total_price: number;
1149
+ declare const ANALYTICS_EVENT_NAMES: readonly ["view_item_list", "select_item", "view_item", "add_to_cart", "remove_from_cart", "view_cart", "begin_checkout", "purchase", "component_impression", "component_click", "ad_impression", "ad_click", "poll_impression", "contest_impression", "session_start", "session_end", "checkout_error", "sdk_error"];
1150
+ type AnalyticsEventName = (typeof ANALYTICS_EVENT_NAMES)[number];
1151
+ /** Camel-case for DX; mapped to the snake_case wire format at build time. */
1152
+ interface AnalyticsContext {
1153
+ campaignId?: number;
1154
+ broadcastId?: string;
1155
+ campaignComponentId?: number;
1156
+ appPlacementId?: number;
1157
+ locationId?: string;
1158
+ componentTemplateId?: string;
1159
+ sponsorId?: number;
1160
+ activationId?: number;
1161
+ contentUrl?: string;
1162
+ /**
1163
+ * The host's own id for the piece of content the component sits in — an
1164
+ * article id in a CMS, a page id in a site builder. Carried verbatim so the
1165
+ * host can join Vio's funnel back onto its own per-article reporting
1166
+ * (revenue stops being an estimate). Left alone by the SDK.
1167
+ */
1168
+ contentId?: string | number;
1169
+ /** Human-readable content snapshot; auto-filled from document.title. */
1170
+ contentTitle?: string;
1171
+ /** Código corto del fallo (`sdk_error` / `checkout_error`). */
1172
+ errorCode?: string;
1173
+ variant?: string;
1174
+ /**
1175
+ * The slot (ADR-0027) the component sits in — its `vio-slot`. Attribution
1176
+ * follows it: the event's `campaign_id` is the campaign assigned to the slot
1177
+ * right now, before the sponsor rule. On the wire it fills `location_id`
1178
+ * when no explicit `locationId` is given (a slot IS a location id to the
1179
+ * campaigns service).
1180
+ */
1181
+ slot?: string;
1204
1182
  }
1205
-
1206
- /**
1207
- * Walley (former Collector) — embedded checkout via Vio Commerce.
1208
- *
1209
- * Third embedded provider, same category as Kustom/Qliro: the widget
1210
- * collects address, shipping AND payment by itself.
1211
- *
1212
- * 1. `Payment { CreatePaymentWalley }` initializes the session (sponsor-key
1213
- * authed; the seller's own Walley credentials live server-side, per
1214
- * seller, never here) and returns a SYNTHESIZED html_snippet — Walley's
1215
- * real embed is a `<script data-token>` loader, so shopcart wraps it
1216
- * in the same shape Kustom/Qliro use, and this module reuses their
1217
- * exact script-executing injection.
1218
- * 2. Walley's own script renders the iframe. On completion it dispatches
1219
- * the DOM event `walleyCheckoutPurchaseCompleted` — the PRIMARY success
1220
- * signal, no page reload needed. `redirectPageUri` is registered too
1221
- * (`?checkout_id=…&payment_processor=WALLEY`, same contract as
1222
- * Kustom/Qliro) as a safety net in case the event is missed (tab
1223
- * backgrounded, page reload).
1224
- * 3. Either path re-reads the session (`Payment { GetWalleyOrder }`) BY
1225
- * OWNING CHECKOUT — same as Qliro — and re-renders Walley's own
1226
- * receipt.
1227
- *
1228
- * The Commerce order itself is created server-side by Walley's notification
1229
- * webhook (shopcart /checkout/payment/walley/ok) — the browser never
1230
- * confirms money.
1231
- */
1232
-
1233
- /** Normalized Walley session (shopcart maps privateId/publicToken/status). */
1234
- interface WalleyOrder {
1235
- order_id: string;
1236
- /** Walley status: Initialized | CustomerIdentified | CommittedToPurchase | PurchaseCompleted | Aborted. */
1237
- status: string;
1238
- html_snippet: string;
1239
- public_token?: string;
1240
- purchase_country?: string;
1241
- purchase_currency?: string;
1242
- total_price?: number;
1183
+ interface AnalyticsItemInput {
1184
+ productId: number | string;
1185
+ name?: string;
1186
+ brand?: string;
1187
+ variantId?: number | string;
1188
+ price?: number;
1189
+ quantity?: number;
1243
1190
  }
1244
-
1245
- /**
1246
- * Nexi Checkout (formerly Nets Easy) — embedded checkout via Vio Commerce.
1247
- *
1248
- * Fourth embedded provider, same category as Kustom/Qliro/Walley: the widget
1249
- * collects email, phone, addresses AND payment. Two things set it apart and
1250
- * shape this module:
1251
- *
1252
- * 1. There is no html_snippet. `Payment { CreatePaymentNexi }` creates the
1253
- * payment server-side (the seller's SECRET key never leaves shopcart)
1254
- * and returns the paymentId plus the PUBLIC checkout key; the browser
1255
- * loads Nexi's `checkout.js` and mounts `new Dibs.Checkout({…})` in a
1256
- * LIGHT-DOM container — Nexi refuses a container inside a shadow root
1257
- * or another iframe.
1258
- * 2. Nexi has no shipping picker. The payment is created with
1259
- * `merchantHandlesShippingCost`, so Nexi holds its pay button until we
1260
- * price the address the shopper typed: on `address-changed` the widget
1261
- * is frozen, `Payment { UpdateNexiShipping }` re-prices for that country
1262
- * and pushes the SHIPPING line, and the widget is thawed.
1263
- *
1264
- * Completion arrives as the `payment-completed` event — Nexi neither
1265
- * redirects nor renders a receipt of its own, so the component shows Vio's
1266
- * confirmation. Some methods inside Nexi (Vipps, Swish, MobilePay) leave the
1267
- * page and come back to the SAME url with `?paymentId=…` appended; the
1268
- * pending session is remembered in sessionStorage so the widget can be
1269
- * re-mounted on that paymentId and finish.
1270
- *
1271
- * The Commerce order itself is created server-side by Nexi's webhook
1272
- * (shopcart /checkout/payment/nexi/ok) — the browser never confirms money.
1273
- */
1274
-
1275
- /** Normalized Nexi payment (shopcart maps the Payment API). */
1276
- interface NexiOrder {
1277
- /** Nexi paymentId (32 hex). */
1278
- order_id: string;
1279
- /** Created | Reserved | Charged | Cancelled | Terminated | Unknown. */
1280
- status: string;
1281
- /** Public checkout key for Dibs.Checkout. */
1282
- checkout_key: string;
1283
- /** checkout.js of the seller's environment (test or live). */
1284
- checkout_js_url: string;
1285
- purchase_country?: string;
1286
- purchase_currency?: string;
1287
- total_price?: number;
1191
+ interface AnalyticsCommerce {
1192
+ items?: AnalyticsItemInput[];
1193
+ value?: number;
1194
+ currency?: string;
1195
+ orderId?: string;
1196
+ paymentMethod?: string;
1288
1197
  /**
1289
- * Shipping to show next to the widget: the purchase country's rates while
1290
- * no address is known (`reason: AWAITING_ADDRESS`, nothing charged), or what
1291
- * the payment carries once one was priced (a resumed widget does not
1292
- * announce its address again).
1198
+ * The buyer reference (`buyer_id` on the wire): shopcart's `buyer_ref`, a
1199
+ * keyed hash of the buyer's email — one person, one ref across orders and
1200
+ * devices, nothing reversible. Never an email (see core/buyer-ref.ts).
1293
1201
  */
1294
- shipping?: NexiShippingUpdate | null;
1295
- /** How it was paid ("Visa", "Vipps"…) — once Nexi has the details. */
1296
- payment_method?: string | null;
1297
- /** Last four digits of the card. */
1298
- card_last4?: string | null;
1299
- /** Where the receipt goes. */
1300
- email?: string | null;
1202
+ buyerId?: string;
1301
1203
  }
1302
- interface NexiShippingUpdate {
1303
- ok: boolean;
1204
+ interface TrackOptions {
1205
+ context?: AnalyticsContext;
1206
+ commerce?: AnalyticsCommerce;
1207
+ props?: Record<string, unknown>;
1208
+ }
1209
+ interface AnalyticsStartOptions {
1210
+ /** Where the SDK is embedded: 'vev' | 'replit' | 'custom' | … Default 'custom'. */
1211
+ host?: string;
1304
1212
  /**
1305
- * When !ok: NO_SHIPPING — Vio does not ship to that country; or
1306
- * AWAITING_ADDRESS — nothing charged yet, `options` are the purchase
1307
- * country's rates and `shipping_id` the suggested one.
1213
+ * Arrancar en modo cookieless hasta que el host llame `setConsent(true)`.
1214
+ * Default false: el SDK asume que el CMP del publisher ya decidió si nos
1215
+ * carga. Los publishers EU que necesitan medir antes del consentimiento
1216
+ * (o no pueden persistir sin él) ponen true.
1308
1217
  */
1309
- reason?: string;
1310
- order_id: string;
1311
- total_price?: number;
1312
- shipping_name?: string;
1313
- shipping_price?: number;
1314
- /** The rate on the payment now. */
1315
- shipping_id?: string;
1218
+ requireConsent?: boolean;
1316
1219
  /**
1317
- * Every rate the shopper may pick for this address, best first. Nexi has
1318
- * no shipping picker of its own, so the checkout shows these.
1220
+ * Install the automatic funnel listeners (vio:added-to-cart,
1221
+ * vio:checkout-open, vio:open-cart, vio:payment-success). Default true.
1222
+ * Hosts with their OWN instrumentation choke point (e.g. the Vev module,
1223
+ * which forwards every event through trackEvent()) set false so the same
1224
+ * user action is never tracked twice — they keep the session/batching/
1225
+ * transport engine and call track() themselves.
1319
1226
  */
1320
- options?: NexiShippingOption[];
1321
- /** The address it was priced for (alpha-2 country). */
1322
- country?: string;
1323
- postal_code?: string;
1324
- /** market — the checkout's country, priced at creation; consumer — the shopper's address. */
1325
- address_source?: 'market' | 'consumer' | null;
1326
- /** Whether this call changed the amount on the payment. */
1327
- changed?: boolean | null;
1227
+ autoTrack?: boolean;
1328
1228
  /**
1329
- * Client-side only: set when the amount changed as the shopper pressed pay,
1330
- * so the payment was stopped and the widget reloaded.
1229
+ * Adopt the host's session id instead of minting one. A host that already
1230
+ * tracks its readers (its own impressions, dwell, clicks) needs Vio's
1231
+ * purchases to land in the SAME session, or the two datasets cannot be
1232
+ * joined. Pass the id the host already stores; omit it and the SDK keeps
1233
+ * its own rotating session.
1331
1234
  */
1332
- repriced_at_pay?: boolean;
1235
+ sessionId?: string;
1236
+ /** Send to the Vio collector. Default true. */
1237
+ collector?: boolean;
1238
+ /** Also push GA4 e-commerce events to window.dataLayer (publisher feature). */
1239
+ dataLayer?: boolean;
1240
+ /** console.debug every event. */
1241
+ debug?: boolean;
1333
1242
  }
1334
- interface NexiShippingOption {
1335
- id: string;
1336
- name: string;
1337
- /** Incl. VAT, decimal. */
1243
+ declare class AnalyticsManager extends EventTarget {
1244
+ private started;
1245
+ private options;
1246
+ private queue;
1247
+ private flushTimer;
1248
+ private retryAttempt;
1249
+ private retryNotBefore;
1250
+ private externalUserId;
1251
+ private sessionId;
1252
+ private requireConsent;
1253
+ private consentGranted;
1254
+ private anonIdCache;
1255
+ /** Set via start({ sessionId }) — the host owns the session, we follow. */
1256
+ private externalSessionId;
1257
+ private sessionStartedAt;
1258
+ /** Impression once-per-(session, component) guard; reset on session rotation. */
1259
+ private impressedComponents;
1260
+ /** Last NON-EMPTY cart snapshot — `purchase` uses it because several flows
1261
+ * (Apple Pay) clear the cart BEFORE `vio:payment-success` fires. */
1262
+ private lastCartSnapshot;
1263
+ /** The `client` block (language, landing referrer / utm, screen, tz): read once per page load, reused on every event. */
1264
+ private client;
1265
+ /** The landing it was read from — persisted later when consent arrives after the first event. */
1266
+ private landing;
1267
+ /** The landing this manager resolved on this page load (the first event's), or null before it; read only — for the order's entry context when nothing is stored yet. */
1268
+ get currentLanding(): Landing | null;
1269
+ private teardown;
1270
+ /**
1271
+ * Install listeners + begin flushing. Explicit on purpose (no module
1272
+ * side-effects). Safe to call twice (second call only updates options).
1273
+ */
1274
+ /**
1275
+ * Consentimiento (ePrivacy/GDPR). Por defecto el SDK asume que el host ya
1276
+ * lo gestionó — su CMP decide si nos carga. Con `requireConsent: true`
1277
+ * arranca en modo **cookieless**: mide la visita con ids efímeros en
1278
+ * memoria y NO escribe nada en localStorage hasta que el host llame
1279
+ * `setConsent(true)`. Retirarlo borra lo persistido.
1280
+ */
1281
+ setConsent(granted: boolean): void;
1282
+ /** ¿Podemos escribir en el dispositivo? */
1283
+ private get canPersist();
1284
+ start(options?: AnalyticsStartOptions): void;
1285
+ /** Stop timers + listeners (tests / SPA unmount). Queue survives. */
1286
+ stop(): void;
1287
+ /** Attach the partner's user id (opaque) to all subsequent events. */
1288
+ identify(externalUserId: string | null): void;
1289
+ /** Queue one event. Works pre-init and pre-start (SSR: no-op). */
1290
+ track(name: AnalyticsEventName, opts?: TrackOptions): void;
1291
+ /**
1292
+ * Impression helper: observes an element and tracks ONE
1293
+ * `component_impression` per (session, campaignComponentId) when the
1294
+ * contract's rule is met (≥50% visible, ≥1s dwell). Returns an
1295
+ * unobserve function.
1296
+ */
1297
+ observeImpression(element: Element, context?: AnalyticsContext, opts?: {
1298
+ name?: Extract<AnalyticsEventName, 'component_impression' | 'ad_impression'>;
1299
+ /** Dedupe key override. Defaults to the strongest id available:
1300
+ * campaignComponentId → locationId → componentTemplateId. */
1301
+ key?: string;
1302
+ /** Called the one time the impression is tracked (e.g. to fire a
1303
+ * companion view_item_list). */
1304
+ onImpression?: () => void;
1305
+ }): () => void;
1306
+ /** Force a flush (mostly for tests). */
1307
+ flush(): Promise<void>;
1308
+ private buildEvent;
1309
+ /**
1310
+ * The `client` block: read once — the landing is remembered for the session
1311
+ * only when we may persist — then the same (frozen) object on every event.
1312
+ * `null` when there is nothing to say (no browser).
1313
+ */
1314
+ private clientBlock;
1315
+ /** Guarda la sesión solo si podemos escribir en el dispositivo. */
1316
+ private persistSession;
1317
+ private anonId;
1318
+ /** Rolling session: renewed by activity, rotated after 30 min idle. */
1319
+ private ensureSession;
1320
+ private trackSessionEnd;
1321
+ private flushWithBeacon;
1322
+ /**
1323
+ * Auto-instrumentation over the SDK's own DOM events — the host app
1324
+ * writes ZERO tracking code for the commerce funnel.
1325
+ */
1326
+ private installAutoListeners;
1327
+ /** The lines of the cart (current, else the pre-clear snapshot), optionally one sponsor's. */
1328
+ private cartLines;
1329
+ /**
1330
+ * The cart split per campaign — the shared rule of cart/slices.ts over the
1331
+ * current cart (else the pre-clear snapshot), plus whether any unit named
1332
+ * its campaign itself (a cart without origins is reported as before).
1333
+ */
1334
+ private cartSlices;
1335
+ /**
1336
+ * The context of one of several per-campaign events: the slice's campaign,
1337
+ * slot and content — only its own; when the slice spans several slots or
1338
+ * articles the event names none rather than the one the click came from.
1339
+ */
1340
+ private sliceContext;
1341
+ /**
1342
+ * The context of a single-campaign checkout/purchase: when the cart's origins
1343
+ * say which campaign they came from, the event says it too (plus the slot /
1344
+ * content when unique) — a cart without origins is exactly as before.
1345
+ */
1346
+ private singleSliceContext;
1347
+ /** Current cart → commerce payload; falls back to the pre-clear snapshot. */
1348
+ private cartCommerce;
1349
+ private findLine;
1350
+ }
1351
+ /**
1352
+ * The `campaign_id` of an event. In order: the context's explicit id; the
1353
+ * campaign assigned to the context's `slot` right now (ADR-0027 — only an
1354
+ * open assignment for the init platform counts, see `findSlotAssignment`);
1355
+ * the PAGE's campaign (`opts.pageCampaignId`, else `Vio.init({ campaignId })`)
1356
+ * when `campaigns[]` lists it as active (ADR-0034, amendment 3);
1357
+ * the active campaign of `campaigns[]` the event's sponsor takes part in, as
1358
+ * its primary or one of its secondaries — when it is in several, the
1359
+ * surface's default campaign if it is one of them, else the first one the
1360
+ * backend lists; the default campaign (`campaign.id`). `/v2/mobile/config`
1361
+ * has no top-level `campaignId` — reading one is why every event used to
1362
+ * leave with `campaign_id: 0` and the campaigns screen stayed empty.
1363
+ *
1364
+ * A malformed `campaigns[]` (not an array, a null entry) never breaks
1365
+ * `track()`: it falls back to `campaign.id`.
1366
+ */
1367
+ declare function resolveCampaignId(ctx: Pick<AnalyticsContext, 'campaignId' | 'sponsorId' | 'slot'>, boot: Pick<BootstrapResponse, 'campaign' | 'campaigns'> | null | undefined, opts?: {
1368
+ pageCampaignId?: CampaignHint;
1369
+ }): number | undefined;
1370
+ /**
1371
+ * `detail` of `vio:payment-success` as the checkout emits it. Four paths,
1372
+ * three shapes: a redirect return (`orderId` = Vio's checkout id), the
1373
+ * confirmation drawer and a provider's own receipt (`result` from the
1374
+ * provider or the backend, plus Vio's `checkoutId`), and Apple Pay express
1375
+ * from the cart (`result` with the backend's `confirmation`, no sponsor).
1376
+ */
1377
+ interface PaymentSuccessDetail {
1378
+ method?: string;
1379
+ sponsorId?: number | string;
1380
+ /** The slot the purchase started from, when the emitter knows it. */
1381
+ slot?: string;
1382
+ /** The campaign the emitter attributed the purchase to (its `Vio.attribution`). */
1383
+ campaignId?: number;
1384
+ /** Per-element content override (`vio-content-id` / `vio-content-title`), when the emitter carries one. */
1385
+ contentId?: string | number;
1386
+ contentTitle?: string;
1387
+ /** Redirect return: Vio's checkout id (the name predates `checkoutId`). */
1388
+ orderId?: string | number;
1389
+ /** Vio's checkout id — from the checkout state or the return URL. */
1390
+ checkoutId?: string | number;
1391
+ /**
1392
+ * The buyer reference the emitter learned with the payment — shopcart's
1393
+ * `buyer_ref`, a keyed hash of the email (`buyerRef` from the SDK's own
1394
+ * checkout; `buyer_ref` when a host passes the backend's answer through).
1395
+ * Becomes `commerce.buyer_id`; never an email.
1396
+ */
1397
+ buyerRef?: string | null;
1398
+ buyer_ref?: string | null;
1399
+ result?: PaymentSuccessResult | null;
1400
+ }
1401
+ /**
1402
+ * What the checkout attaches as `result`. Only `confirmation` carries a Vio
1403
+ * id: the order `ConfirmPaymentApplePay` created. `order.orderId` / `orderId`
1404
+ * are the PROVIDER's references (Qliro's, Walley's — the checkout itself
1405
+ * shows them as `providerRef`), and `chargedTotal` includes shipping on some
1406
+ * methods only. Analytics reads none of those.
1407
+ */
1408
+ interface PaymentSuccessResult {
1409
+ confirmation?: {
1410
+ orderId?: string | number | null;
1411
+ status?: string;
1412
+ buyerRef?: string | null;
1413
+ buyer_ref?: string | null;
1414
+ } | null;
1415
+ /** The checkout as a host's backend answered it, when the host passes it through. */
1416
+ checkout?: {
1417
+ buyerRef?: string | null;
1418
+ buyer_ref?: string | null;
1419
+ } | null;
1420
+ buyerRef?: string | null;
1421
+ buyer_ref?: string | null;
1422
+ [key: string]: unknown;
1423
+ }
1424
+ /**
1425
+ * The buyer reference of a `vio:payment-success` / `vio:checkout-open`
1426
+ * detail, wherever its emitter put it: top-level (`buyerRef`, or `buyer_ref`
1427
+ * as the backend spells it), on the result, on the result's `confirmation`
1428
+ * (Apple Pay) or `checkout`. Only a reference survives — never an email
1429
+ * (`normalizeBuyerRef`).
1430
+ */
1431
+ declare function buyerRefOf(d: {
1432
+ buyerRef?: unknown;
1433
+ buyer_ref?: unknown;
1434
+ result?: PaymentSuccessResult | null;
1435
+ } | null | undefined): string | undefined;
1436
+ /**
1437
+ * The `order_id` and `buyer_id` of a `purchase`. The id is ALWAYS a Vio id, never a provider's
1438
+ * reference. Normalized across every emitter: `orderId` (redirect return) →
1439
+ * `result.confirmation.orderId` (Apple Pay) → `checkoutId`. `value` and
1440
+ * `currency` are deliberately NOT read from the result: on every path they
1441
+ * come from the cart snapshot (sum of the items, no shipping), so revenue
1442
+ * stays comparable across payment methods — the charged total will arrive
1443
+ * through the server-side `order_paid` event. With nothing to say it
1444
+ * returns `{}`, so the cart snapshot keeps its keys.
1445
+ */
1446
+ declare function purchaseFromPaymentSuccess(d: PaymentSuccessDetail): Pick<AnalyticsCommerce, 'orderId' | 'buyerId'>;
1447
+
1448
+ /**
1449
+ * The cart split per campaign (ADR-0034) — ONE rule, shared by the SDK's own
1450
+ * `begin_checkout` / `purchase` events, by `Vio.cartSlices()` and so by any
1451
+ * host with its own analytics sink (Vev).
1452
+ *
1453
+ * Every unit of every line goes to the campaign of its origin — the one the
1454
+ * place showed when it was added — and units without one to the campaign the
1455
+ * caller's rule resolves for the line's sponsor (`resolveCampaign`). One slice
1456
+ * per campaign: its items (quantities from the origins, merged per product +
1457
+ * variant), its value with two decimals, and `slot` / `contentId` /
1458
+ * `sponsorId` only when they are the same for the whole slice. A cart with
1459
+ * one campaign is one slice. Pure; never throws on odd lines.
1460
+ */
1461
+
1462
+ /** An item of a slice — the analytics item shape (ids as given, normalized on the wire). */
1463
+ type VioItem = AnalyticsItemInput;
1464
+ /** One campaign's share of the cart. */
1465
+ interface CartSlice {
1466
+ campaignId?: number;
1467
+ sponsorId?: number;
1468
+ slot?: string;
1469
+ contentId?: string;
1470
+ items: VioItem[];
1471
+ value: number;
1472
+ currency?: string;
1473
+ /** 1-based position of this slice and how many there are. */
1474
+ slice: number;
1475
+ slices: number;
1476
+ }
1477
+ /** The campaign of a unit without an explicit one: the caller's rule (slot assignment → sponsor → default). */
1478
+ type ResolveCampaign = (slot: string | undefined, sponsorId: number) => number | undefined;
1479
+ declare function cartSlicesFrom(lines: ReadonlyArray<CartLineItem>, resolveCampaign: ResolveCampaign, fallbackCurrency?: string): CartSlice[];
1480
+
1481
+ /**
1482
+ * The attribution of a checkout (ADR-0035 phase B): what the order is born
1483
+ * knowing about the surface it was sold from, the campaign of the page, the
1484
+ * page itself and where the shopper came in from. Sent as `attribution` on
1485
+ * `CreateCheckout` and on the `UpdateCheckout` right before paying; shopcart
1486
+ * keeps it on the checkout (`checkout.attribution`, merged key by key) and
1487
+ * writes `order.surface_id` / `campaign_id` / `content_url` / `content_title`
1488
+ * / `entry_source` from it when the order is created. The surface itself is
1489
+ * checked there against the cart's channel: a page cannot credit a sale to
1490
+ * somebody else's surface.
1491
+ *
1492
+ * Pure: `checkoutAttributionOf()` takes what the facade gathered. snake_case
1493
+ * on the wire, the caps of shopcart's `CheckoutAttributionDto` enforced here.
1494
+ */
1495
+
1496
+ /** `EntryContextDto`: what the page knew when the shopper came in (the SDK's landing memory). */
1497
+ interface EntryContextWire {
1498
+ referrer?: string;
1499
+ utm_source?: string;
1500
+ utm_medium?: string;
1501
+ utm_campaign?: string;
1502
+ landing_url?: string;
1503
+ }
1504
+ /** `CheckoutAttributionDto` (shopcart `checkout/dtos/checkout-attribution.dto.ts`). */
1505
+ interface CheckoutAttributionWire {
1506
+ surface_key?: string;
1507
+ campaign_id?: number;
1508
+ content_url?: string;
1509
+ content_title?: string;
1510
+ entry?: EntryContextWire;
1511
+ }
1512
+ /** The caps of `CheckoutAttributionDto` / `EntryContextDto`. */
1513
+ declare const CHECKOUT_ATTRIBUTION_CAPS: Readonly<{
1514
+ surfaceKey: 255;
1515
+ contentUrl: 2048;
1516
+ contentTitle: 255;
1517
+ referrer: 2048;
1518
+ utm: 128;
1519
+ landingUrl: 2048;
1520
+ }>;
1521
+ interface CheckoutAttributionInput {
1522
+ /** The surface's SDK key (`Vio.init({ apiKey })`) — shopcart resolves the surface from it. */
1523
+ surfaceKey?: string;
1524
+ /** The campaign the PAGE belongs to, when the bootstrap lists it as active (`Vio.pageCampaign()`). */
1525
+ pageCampaignId?: number;
1526
+ /** The cart split per campaign (`Vio.cartSlices(sponsorId)`), for the campaign that explains most units when the page has none. */
1527
+ slices?: ReadonlyArray<CartSlice>;
1528
+ /** The page at pay time: `Vio.content()`'s url and title. */
1529
+ content?: {
1530
+ url?: string;
1531
+ title?: string;
1532
+ };
1533
+ /** Where the session came in (`resolveLanding()`): referrer, utm, landing page. */
1534
+ landing?: Landing | null;
1535
+ /**
1536
+ * The surface's privacy mode. Only on a `standard` surface do the page
1537
+ * (`content_url`, `content_title`) and the entry context travel: which
1538
+ * screen the sale was made on and why the shopper was there is what
1539
+ * `minimal` keeps private — the same promise the collector keeps for the
1540
+ * events. `unknown` (it could not be learnt) counts as `minimal`: what can
1541
+ * reveal it is only sent when the surface is known to allow it. The surface
1542
+ * and the campaign always travel.
1543
+ */
1544
+ privacyMode?: SurfacePrivacyMode | 'unknown';
1545
+ }
1546
+ /**
1547
+ * The attribution for the wire, or `undefined` when there is nothing to say.
1548
+ * The campaign is the page's; without one, the campaign that explains most
1549
+ * units of the cart under the same rule `purchase` is split by (ties: the
1550
+ * higher value, then the first). The page (origin + path) and the entry go
1551
+ * only on a `standard` surface.
1552
+ */
1553
+ declare function checkoutAttributionOf(input: CheckoutAttributionInput): CheckoutAttributionWire | undefined;
1554
+ /** The landing as `entry`, capped; `undefined` when it says nothing. */
1555
+ declare function entryContextOf(landing: Landing | null | undefined): EntryContextWire | undefined;
1556
+ /**
1557
+ * The campaign that explains most units of the cart — over the slices
1558
+ * `purchase` is split by, so the order names the campaign the biggest
1559
+ * purchase event names. Ties go to the higher value, then to the first
1560
+ * slice. Units nobody attributes (a slice without campaign) never win.
1561
+ */
1562
+ declare function dominantCampaignOf(slices: ReadonlyArray<CartSlice> | null | undefined): number | undefined;
1563
+
1564
+ /**
1565
+ * Vipps MobilePay Payment Adapter — GraphQL Integration & Types.
1566
+ *
1567
+ * Vipps payment integration uses 2 GraphQL operations:
1568
+ *
1569
+ * 1. `CreatePaymentVipps` mutation:
1570
+ * Initiates a Vipps payment session for a checkout and returns `payment_url`.
1571
+ *
1572
+ * 2. `GetVippsStatus` query:
1573
+ * Queries Vipps payment state directly for a checkout (`state`).
1574
+ */
1575
+
1576
+ /**
1577
+ * GraphQL mutation to create a Vipps payment session.
1578
+ */
1579
+ declare const CREATE_PAYMENT_VIPPS_MUTATION = "\nmutation CreatePaymentVipps($checkoutId: String!, $email: String, $returnUrl: String!, $express: Boolean) {\n Payment {\n CreatePaymentVipps(checkout_id: $checkoutId, email: $email, return_url: $returnUrl, express: $express) {\n payment_url\n reference\n express\n reason\n }\n }\n}\n";
1580
+ /**
1581
+ * GraphQL query to fetch current Vipps payment status.
1582
+ */
1583
+ declare const GET_VIPPS_STATUS_QUERY = "\nquery GetVippsStatus($checkoutId: String!) {\n Payment {\n GetVippsStatus(checkout_id: $checkoutId) {\n state\n duplicate_of\n }\n }\n}\n";
1584
+ interface CreatePaymentVippsVariables extends Record<string, unknown> {
1585
+ checkoutId: string;
1586
+ /** Optional since Express: Vipps hands the email back with the payment. */
1587
+ email?: string;
1588
+ returnUrl: string;
1589
+ /**
1590
+ * `true` asks for Express — address and delivery chosen inside the Vipps
1591
+ * app. When the cart cannot have it, no payment is created and the answer
1592
+ * says `express: false` with the reason; the caller then opens the
1593
+ * ordinary checkout.
1594
+ */
1595
+ express?: boolean;
1596
+ }
1597
+ interface CreatePaymentVippsResponse {
1598
+ /** Absent when Express was asked for and refused. */
1599
+ payment_url?: string;
1600
+ /** The reference the seller reads in its Vipps portal. */
1601
+ reference?: string;
1602
+ express?: boolean;
1603
+ reason?: string;
1604
+ }
1605
+ interface GetVippsStatusVariables extends Record<string, unknown> {
1606
+ checkoutId: string;
1607
+ }
1608
+ interface VippsStatusResult {
1609
+ state: string;
1610
+ /** Set when this checkout was closed as a duplicate: the checkout that paid the cart. */
1611
+ duplicate_of?: string | null;
1612
+ }
1613
+ type VippsPaymentState = 'CREATED' | 'INITIATED' | 'AUTHORIZED' | 'TERMINATED' | 'EXPIRED' | string;
1614
+ /**
1615
+ * Executes the `CreatePaymentVipps` GraphQL mutation.
1616
+ */
1617
+ declare function createPaymentVipps(variables: CreatePaymentVippsVariables | Record<string, unknown>, options?: CartQueryOptions): Promise<CreatePaymentVippsResponse | null>;
1618
+ /**
1619
+ * Executes the `GetVippsStatus` GraphQL query.
1620
+ */
1621
+ declare function getVippsStatus(variables: GetVippsStatusVariables | Record<string, unknown>, options?: CartQueryOptions): Promise<VippsStatusResult | null>;
1622
+
1623
+ declare const CREATE_CART_MUTATION = "\nmutation CreateCart($customerSessionId: String!, $currency: String!, $shippingCountry: String) {\n Cart {\n CreateCart(customer_session_id: $customerSessionId, currency: $currency, shipping_country: $shippingCountry) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
1624
+ declare const ADD_ITEM_MUTATION = "\nmutation AddItem($cartId: String!, $lineItems: [LineItemInput!]!) {\n Cart {\n AddItem(cart_id: $cartId, line_items: $lineItems) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
1625
+ declare const DELETE_ITEM_MUTATION = "\nmutation DeleteItem($cartId: String!, $cartItemId: String!) {\n Cart {\n DeleteItem(cart_id: $cartId, cart_item_id: $cartItemId) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
1626
+ declare const UPDATE_ITEM_MUTATION: string;
1627
+ /** `UpdateItem` with the `origin` of the added units (`CartLineOriginInput`, snake_case fields — see `lineOriginWire`). */
1628
+ declare const UPDATE_ITEM_WITH_ORIGIN_MUTATION: string;
1629
+ declare const GET_CART_QUERY = "\nquery GetCart($cartId: String!) {\n Cart {\n GetCart(cart_id: $cartId) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
1630
+ declare const GET_LINE_ITEMS_BY_SUPPLIER_QUERY = "\nquery GetLineItemsBySupplier($cartId: String!) {\n Cart {\n GetLineItemsBySupplier(cart_id: $cartId) {\n supplier {\n id\n name\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n }\n }\n}\n";
1631
+ declare const UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION = "\nmutation UpdateShippingsBySupplier($cartId: String!, $data: [DataUpdateShippingsBySupplier!]!) {\n Cart {\n UpdateShippingsBySupplier(cart_id: $cartId, data: $data) {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n }\n}\n";
1632
+ declare const CREATE_CHECKOUT_MUTATION: string;
1633
+ /** `CreateCheckout` with the checkout's `attribution` (`CheckoutAttributionInput`, snake_case fields — see `checkoutAttributionOf`). */
1634
+ declare const CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION: string;
1635
+ declare const UPDATE_CHECKOUT_MUTATION: string;
1636
+ /** `UpdateCheckout` with the checkout's `attribution` (`CheckoutAttributionInput`). */
1637
+ declare const UPDATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION: string;
1638
+ declare const GET_CHECKOUT_QUERY = "\nquery GetCheckout($checkoutId: String!) {\n Checkout {\n GetCheckout(checkout_id: $checkoutId) {\n buyer_accepts_purchase_conditions\n buyer_accepts_terms_conditions\n created_at\n updated_at\n id\n deleted_at\n success_url\n cancel_url\n payment_method\n email\n status\n checkout_url\n origin_payment_id\n duplicate_of\n billing_address {\n id\n first_name\n last_name\n phone_code\n phone\n company\n address1\n address2\n city\n province\n province_code\n country\n country_code\n zip\n }\n shipping_address {\n id\n first_name\n last_name\n phone_code\n phone\n company\n address1\n address2\n city\n province\n province_code\n country\n country_code\n zip\n }\n available_payment_methods {\n name\n }\n discount_code\n cart {\n cart_id\n customer_session_id\n shipping_country\n line_items {\n id\n supplier\n image {\n id\n url\n width\n height\n }\n sku\n barcode\n brand\n product_id\n title\n variant_id\n variant_title\n variant {\n option\n value\n }\n quantity\n price {\n amount\n currency_code\n discount\n compare_at\n compare_at_incl_taxes\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n shipping {\n id\n name\n description\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n available_shippings {\n id\n name\n description\n country_code\n price {\n amount\n currency_code\n amount_incl_taxes\n tax_amount\n tax_rate\n }\n }\n }\n subtotal\n shipping\n currency\n available_shipping_countries\n }\n totals {\n currency_code\n subtotal\n shipping\n total\n taxes\n tax_rate\n discounts\n }\n }\n }\n}\n";
1639
+ declare const GET_AVAILABLE_PAYMENT_METHODS_QUERY = "\nquery Config {\n Payment {\n GetAvailablePaymentMethods {\n config {\n name\n type\n value\n }\n name\n }\n }\n}\n";
1640
+
1641
+ /** Transport options for a cart/checkout GraphQL call. */
1642
+ interface CartQueryOptions {
1643
+ endpoint?: string;
1644
+ apiKey?: string;
1645
+ /** Sponsor commerce key — goes in the Authorization header. */
1646
+ commerceKey?: string;
1647
+ /** True when the commerce key came from a resolved sponsor (bootstrap).
1648
+ * False = we fell back to the host apiKey — writes like CreateCart should
1649
+ * NOT proceed with the wrong key (the cart would be orphaned). */
1650
+ sponsorResolved?: boolean;
1651
+ }
1652
+ /**
1653
+ * Resolve endpoint + keys for cart GraphQL calls. Awaits Vio.bootstrap() once
1654
+ * if it hasn't run yet (the sponsor commerce keys come from bootstrap), then
1655
+ * uses the sponsor's commerce apiKey (via the Vio facade on globalThis,
1656
+ * avoiding an import cycle); falls back to the host apiKey.
1657
+ */
1658
+ declare function getCartGraphQLOptions(sponsorId?: number): Promise<CartQueryOptions>;
1659
+ /** Stable per-browser customer session id (backend CreateCart requires one). */
1660
+ declare function getCustomerSessionId(): string;
1661
+ interface GraphQLEnvelope {
1662
+ data?: Record<string, Record<string, unknown>> | null;
1663
+ errors?: Array<{
1664
+ message?: string;
1665
+ path?: unknown;
1666
+ }>;
1667
+ }
1668
+ /** Execute a GraphQL query/mutation against the configured commerce endpoint.
1669
+ * Callers that skip `getCartGraphQLOptions()` (or pass a bare sponsorId) get
1670
+ * it resolved here instead of silently sending an unauthenticated request.
1671
+ * Rejects after `CART_GRAPHQL_TIMEOUT_MS` (and aborts the fetch). */
1672
+ declare function executeCartGraphQL(query: string, variables: Record<string, unknown>, options?: CartQueryOptions | number | undefined): Promise<GraphQLEnvelope>;
1673
+ declare function createCart(customerSessionId: string, currency: string, shippingCountry?: string | null, options?: CartQueryOptions): Promise<any>;
1674
+ /**
1675
+ * `AddItem`. A line may carry `origin` (`LineOriginWire`: where its units
1676
+ * come from — ADR-0035 phase B). A gateway that predates the field refuses
1677
+ * the whole mutation at validation (`LineItemInput` has no `origin`), so the
1678
+ * lines are sent again without it, once per page load — see gateway-features.
1679
+ */
1680
+ declare function addItem(cartId: string, lineItems: unknown[], options?: CartQueryOptions): Promise<any>;
1681
+ declare function deleteItem(cartId: string, cartItemId: string, options?: CartQueryOptions): Promise<any>;
1682
+ /**
1683
+ * `UpdateItem`. `extra.origin` is where the units a quantity INCREASE adds
1684
+ * come from (ADR-0035 phase B); the server credits the new units to it. Sent
1685
+ * only with it; a gateway that predates the argument gets the legacy
1686
+ * mutation instead, once per page load — see gateway-features.
1687
+ */
1688
+ declare function updateItem(cartId: string, cartItemId: string, shippingId?: string | null, qty?: number | null, options?: CartQueryOptions, extra?: {
1689
+ origin?: LineOriginWire;
1690
+ }): Promise<any>;
1691
+ declare function getCart(cartId: string, options?: CartQueryOptions): Promise<any>;
1692
+ declare function getLineItemsBySupplier(cartId: string, options?: CartQueryOptions): Promise<any>;
1693
+ declare function updateShippingsBySupplier(cartId: string, data: unknown[], options?: CartQueryOptions): Promise<any>;
1694
+ /**
1695
+ * `CreateCheckout`. With `extra.attribution` (`CheckoutAttributionWire`:
1696
+ * the surface, the page's campaign, the page and the entry — ADR-0035 phase
1697
+ * B) the checkout is born attributed; a gateway that predates the argument
1698
+ * gets the legacy mutation, once per page load — see gateway-features.
1699
+ */
1700
+ declare function createCheckout(cartId: string, options?: CartQueryOptions, extra?: {
1701
+ attribution?: CheckoutAttributionWire;
1702
+ }): Promise<any>;
1703
+ /**
1704
+ * `UpdateCheckout`. `variables.attribution` (`CheckoutAttributionWire`) is
1705
+ * merged key by key over what the checkout has; a gateway that predates the
1706
+ * argument gets the legacy mutation without it, once per page load.
1707
+ */
1708
+ declare function updateCheckout(variables: Record<string, unknown>, options?: CartQueryOptions): Promise<any>;
1709
+ declare function getCheckout(checkoutId: string, options?: CartQueryOptions): Promise<any>;
1710
+ /** What a checkout says of itself after paying: its Vio order number, once the order exists, and its buyer reference. */
1711
+ interface CheckoutReference {
1712
+ orderNumber: number | null;
1713
+ buyerRef: string | null;
1714
+ }
1715
+ /** The order number (null while the order does not exist yet) and the buyer reference (null when the API does not say it). */
1716
+ declare function getCheckoutReference(checkoutId: string, options?: CartQueryOptions): Promise<CheckoutReference>;
1717
+ declare function getAvailablePaymentMethods(options?: CartQueryOptions): Promise<any>;
1718
+ declare const CREATE_PAYMENT_STRIPE_MUTATION = "\nmutation CreatePaymentStripe($checkoutId: String!, $successUrl: String!, $paymentMethod: String!, $email: String!) {\n Payment {\n CreatePaymentStripe(checkout_id: $checkoutId, success_url: $successUrl, payment_method: $paymentMethod, email: $email) {\n order_id\n checkout_url\n }\n }\n}\n";
1719
+ declare function createPaymentStripe(variables: Record<string, unknown>, options?: CartQueryOptions): Promise<any>;
1720
+
1721
+ /**
1722
+ * Stripe on our own page — the Payment Element, no redirect.
1723
+ *
1724
+ * The older web flow minted a Stripe Payment Link and sent the shopper to a
1725
+ * page hosted by Stripe. This one keeps them here (Angelo, 2026-09-24):
1726
+ *
1727
+ * 1. Vio Commerce creates a PaymentIntent for the checkout
1728
+ * (`Payment { CreatePaymentIntentStripe }`) and answers with its client
1729
+ * secret AND the publishable key of the account that minted it — the
1730
+ * seller's when they brought both keys, ours otherwise. The two are
1731
+ * halves of one account: the browser confirms with the key what the
1732
+ * server minted with the secret one.
1733
+ * 2. Stripe's Payment Element is mounted in a LIGHT-DOM container, the way
1734
+ * the other embedded widgets are. Card fields are Stripe's own iframes,
1735
+ * so card data never touches this SDK or our servers.
1736
+ * 3. `confirm()` pays. A 3-D Secure challenge opens in Stripe's own modal
1737
+ * over the page: `redirect: 'if_required'` keeps the shopper here for
1738
+ * cards, and still redirects for a method that has no other way.
1739
+ * 4. The Commerce order is NOT born here. Stripe's `payment_intent.succeeded`
1740
+ * webhook creates it, verified server-side; the checkout is polled for
1741
+ * its status afterwards, exactly as the redirect return does.
1742
+ */
1743
+
1744
+ /** What the backend hands over to pay a checkout on our page. */
1745
+ interface StripeIntent {
1746
+ /** `pi_..._secret_...`: what the browser confirms. */
1747
+ client_secret: string;
1748
+ /** The key of the SAME account that minted the secret. */
1749
+ publishable_key: string;
1750
+ customer?: string;
1751
+ /**
1752
+ * Stripe Connect (ADR-0022): the seller's connected account, where the
1753
+ * intent lives. The browser must name it or Stripe answers "no such
1754
+ * payment_intent". Absent for every seller not on Connect.
1755
+ */
1756
+ stripe_account?: string;
1757
+ }
1758
+ /** What `confirm()` answers. The browser's word is never the order. */
1759
+ type StripeConfirmOutcome = {
1760
+ status: 'succeeded';
1761
+ paymentIntentId?: string;
1762
+ }
1763
+ /** Authorised but not settled yet, or Stripe redirected and will return. */
1764
+ | {
1765
+ status: 'pending';
1766
+ paymentIntentId?: string;
1767
+ } | {
1768
+ status: 'failed';
1769
+ message?: string;
1770
+ code?: string;
1771
+ };
1772
+ interface StripeElementHandle {
1773
+ confirm(): Promise<StripeConfirmOutcome>;
1774
+ unmount(): void;
1775
+ }
1776
+
1777
+ /**
1778
+ * Apple Pay on the Web — via Stripe Payment Request API.
1779
+ *
1780
+ * Architecture decision (matches iOS Vio SDK pattern):
1781
+ * - One Stripe **publishable key** for the Vio platform (global, set
1782
+ * via `Vio.init({ stripePublishableKey })`).
1783
+ * - One **Stripe Connect account id** per sponsor — sourced from
1784
+ * `Vio.bootstrap()` response. Without it, the payment lands on the
1785
+ * platform account instead of the merchant.
1786
+ *
1787
+ * Apple Pay on the Web prerequisites (Stripe-managed for us):
1788
+ * - Stripe Connect account is enabled for Apple Pay
1789
+ * - Domain registration for Apple Pay (Stripe handles via Dashboard)
1790
+ * - Safari + macOS/iOS device with Apple Pay configured
1791
+ *
1792
+ * In Chrome / non-Apple browsers `canMakePayment()` returns null, so the
1793
+ * UI hides the Apple Pay button automatically.
1794
+ */
1795
+ interface ApplePayConfig {
1796
+ /** Stripe platform publishable key (pk_live_xxx or pk_test_xxx). */
1797
+ publishableKey: string;
1798
+ /** Sponsor's Stripe Connect account (acct_xxx). Optional but recommended. */
1799
+ connectedAccount?: string;
1800
+ /** Amount in the smallest unit of the currency (e.g. øre for NOK, cents for USD). */
1801
+ amount: number;
1802
+ /** ISO currency code, lowercased internally for Stripe. */
1803
+ currency: string;
1804
+ /** Two-letter country code, e.g. "NO". */
1805
+ country: string;
1806
+ /** Label shown in the Apple Pay sheet (merchant or order name). */
1807
+ label: string;
1808
+ /** Whether to ask the user for payer name. */
1809
+ requestPayerName?: boolean;
1810
+ /** Whether to ask the user for payer email. */
1811
+ requestPayerEmail?: boolean;
1812
+ /** Whether to ask for shipping address. */
1813
+ requestShipping?: boolean;
1814
+ /**
1815
+ * Shipping options shown natively in the Apple Pay sheet. `amount` is in the
1816
+ * smallest unit (øre). When set, `requestShipping` is forced on and the sheet
1817
+ * total = `amount` (items) + the selected option; switching options updates
1818
+ * the total live (Apple Pay native shipping).
1819
+ */
1820
+ shippingOptions?: Array<{
1821
+ id: string;
1822
+ label: string;
1823
+ detail?: string;
1824
+ amount: number;
1825
+ }>;
1826
+ /**
1827
+ * Charge hook, run INSIDE the sheet's authorize event with the tokenized
1828
+ * payment. Must perform the real (backend) charge and throw on failure —
1829
+ * only then does the sheet close with success; a throw closes it with
1830
+ * failure. Its resolved value is surfaced as `ApplePayResult.confirmation`.
1831
+ */
1832
+ onAuthorize?: (result: ApplePayResult) => Promise<unknown>;
1833
+ }
1834
+ interface ApplePayResult {
1835
+ /** Stripe PaymentMethod id (pm_xxx) — pass to backend to confirm the PaymentIntent. */
1836
+ paymentMethodId: string;
1837
+ payerName?: string;
1838
+ payerEmail?: string;
1839
+ payerPhone?: string;
1840
+ shippingAddress?: {
1841
+ line1?: string;
1842
+ line2?: string;
1843
+ city?: string;
1844
+ postal_code?: string;
1845
+ state?: string;
1846
+ country?: string;
1847
+ };
1848
+ /** Whatever `onAuthorize` resolved with (e.g. backend order_id/status). */
1849
+ confirmation?: unknown;
1850
+ }
1851
+ /**
1852
+ * Check whether Apple Pay is available right now (Safari + supported device
1853
+ * + Stripe configured + domain registered).
1854
+ *
1855
+ * Returns false on any failure path — never throws.
1856
+ */
1857
+ declare function checkApplePayAvailability(config: ApplePayConfig): Promise<boolean>;
1858
+ /**
1859
+ * Show the Apple Pay sheet. Resolves with the payment method id (`pm_xxx`)
1860
+ * after the user authorises, or rejects on cancel / error.
1861
+ *
1862
+ * The backend must then confirm a Stripe PaymentIntent with this
1863
+ * payment method id (server-side step, not handled here).
1864
+ */
1865
+ declare function showApplePaySheet(config: ApplePayConfig): Promise<ApplePayResult | null>;
1866
+
1867
+ /** One line in the express button's purchase data. Amounts in MINOR units. */
1868
+ interface KlarnaLineItem {
1869
+ name: string;
1870
+ quantity: number;
1871
+ /** Line total (unitPrice × quantity), minor units (e.g. øre for NOK). */
1872
+ totalAmount: number;
1873
+ /** Per-unit price, minor units. Optional. */
1874
+ unitPrice?: number;
1875
+ /** Merchant SKU / product reference. Optional. */
1876
+ reference?: string;
1877
+ /** Absolute image URL. Optional. */
1878
+ imageUrl?: string;
1879
+ }
1880
+ /** Payment request handed to the KEC button. Amounts in MINOR units. */
1881
+ interface KlarnaPaymentRequestData {
1882
+ /** ISO currency code (e.g. "NOK"). */
1883
+ currency: string;
1884
+ /** Order total in minor units. */
1885
+ amount: number;
1886
+ /** Line items — shown in the Klarna sheet. */
1887
+ supplementaryPurchaseData?: {
1888
+ lineItems: KlarnaLineItem[];
1889
+ };
1890
+ }
1891
+ interface KlarnaAuthorizeResult {
1892
+ /** Authorization token — backend exchanges it for an order. */
1893
+ authorizationToken: string;
1894
+ /** Klarna may require a finalize step (multistep flows). */
1895
+ finalizeRequired?: boolean;
1896
+ /** Raw payload from the SDK, for debugging / forward-compat. */
1897
+ raw?: unknown;
1898
+ }
1899
+ interface KlarnaButtonHandle {
1900
+ unmount(): void;
1901
+ }
1902
+
1903
+ interface KlarnaPaymentsCategory {
1904
+ identifier: string;
1905
+ name: string;
1906
+ asset_urls?: {
1907
+ descriptive?: string;
1908
+ standard?: string;
1909
+ };
1910
+ }
1911
+ interface KpLoadResult {
1912
+ show_form?: boolean;
1913
+ error?: unknown;
1914
+ }
1915
+ interface KpAuthorizeResult {
1916
+ approved?: boolean;
1917
+ show_form?: boolean;
1918
+ authorization_token?: string;
1919
+ finalize_required?: boolean;
1920
+ error?: unknown;
1921
+ }
1922
+ interface KlarnaPaymentsApi {
1923
+ init(opts: {
1924
+ client_token: string;
1925
+ }): void;
1926
+ load(opts: {
1927
+ container: string | HTMLElement;
1928
+ payment_method_category?: string;
1929
+ }, data: Record<string, unknown>, cb?: (res: KpLoadResult) => void): void;
1930
+ authorize(opts: {
1931
+ payment_method_category?: string;
1932
+ auto_finalize?: boolean;
1933
+ }, data: Record<string, unknown>, cb?: (res: KpAuthorizeResult) => void): void;
1934
+ /** Debug/lifecycle events — e.g. 'fullscreenOverlayShown'/'fullscreenOverlayHidden'. */
1935
+ on?(event: string, cb: () => void): void;
1936
+ }
1937
+ interface KlarnaGlobal {
1938
+ Payments: KlarnaPaymentsApi;
1939
+ }
1940
+ declare global {
1941
+ interface Window {
1942
+ Klarna?: KlarnaGlobal;
1943
+ klarnaAsyncCallback?: () => void;
1944
+ }
1945
+ }
1946
+
1947
+ /**
1948
+ * Kustom (former Klarna Checkout) — embedded KCO v3 checkout.
1949
+ *
1950
+ * Kustom kept the Klarna Checkout dialect on its own hosts, so the flow is
1951
+ * the classic KCO embed:
1952
+ *
1953
+ * 1. Vio Commerce creates the order (`Payment { CreatePaymentKustom }`,
1954
+ * authed with the sponsor's commerce API key; the seller's own
1955
+ * kco_*_api_* key lives server-side, per seller, never here). One order
1956
+ * per checkout: asking again while it is open UPDATES it.
1957
+ * 2. The response's `html_snippet` is injected into a LIGHT-DOM container
1958
+ * — it renders Kustom's iframe, which collects address, shipping AND
1959
+ * payment by itself (the widget does everything; no Vio address form).
1960
+ * 3. While it is open, the widget talks through `window._klarnaCheckout`:
1961
+ * we suspend it around a cart change (`Payment { SyncPaymentKustom }`),
1962
+ * and listen to its totals so our summary never disagrees with it.
1963
+ * 4. On completion Kustom redirects to the confirmation URL
1964
+ * (`?order_id=…&payment_processor=KUSTOM&checkout_id=…`), where the SDK
1965
+ * re-reads the order (`Payment { GetKustomOrder }`) — the backend
1966
+ * completes it right there — and renders Kustom's own receipt.
1967
+ *
1968
+ * The Commerce order is created server-side, from what Kustom says it
1969
+ * charged (Order Management), whether the shopper comes back or not (the
1970
+ * push) — the browser never confirms money.
1971
+ */
1972
+
1973
+ /** A Kustom order as shopcart normalizes it (never Kustom's raw order). */
1974
+ interface KustomOrder {
1975
+ order_id: string;
1976
+ /** `checkout_incomplete` | `checkout_complete`, lower-cased by the backend. */
1977
+ status: string;
1978
+ html_snippet: string;
1979
+ purchase_country?: string;
1980
+ purchase_currency?: string;
1981
+ checkout_id?: string;
1982
+ environment?: string;
1983
+ /** Goods + the shipping shown or charged, decimal. */
1984
+ total_price?: number;
1985
+ shipping_name?: string;
1986
+ shipping_price?: number;
1987
+ /** Whether the Vio Commerce order exists (after completion). */
1988
+ order_created?: boolean;
1989
+ /** Kustom's short shopper-facing reference (after completion). */
1990
+ reference?: string;
1991
+ }
1992
+ interface KustomShippingChange {
1993
+ id?: string;
1994
+ name?: string;
1995
+ description?: string;
1996
+ /** Minor units, as the widget reports them. */
1997
+ price?: number;
1998
+ tax_amount?: number;
1999
+ tax_rate?: number;
2000
+ }
2001
+ interface KustomListenerHandlers {
2002
+ /** The iframe rendered. */
2003
+ onLoad?: (data: any) => void;
2004
+ /** The order total the widget shows, in MINOR units. */
2005
+ onOrderTotalChange?: (orderTotalMinor: number) => void;
2006
+ /** The shopper picked a rate inside the widget. */
2007
+ onShippingOptionChange?: (option: KustomShippingChange) => void;
2008
+ /** The shopper could not pay (declined credit…): show another way. */
2009
+ onCannotComplete?: () => void;
2010
+ onNetworkError?: () => void;
2011
+ /** The shopper is about to leave for the confirmation page. */
2012
+ onRedirectInitiated?: () => void;
2013
+ }
2014
+
2015
+ /**
2016
+ * Qliro Checkout — embedded checkout via Vio Commerce.
2017
+ *
2018
+ * Same embed philosophy as Kustom (the widget collects address, shipping
2019
+ * AND payment), different provider API underneath — shopcart normalizes
2020
+ * Qliro's Merchant API (OrderId / CustomerCheckoutStatus / OrderHtmlSnippet)
2021
+ * to the { order_id, status, html_snippet } shape both flows share:
2022
+ *
2023
+ * 1. `Payment { CreatePaymentQliro }` creates the order and returns the
2024
+ * snippet (sponsor-key authed; the seller's MerchantApiKey/Secret live
2025
+ * server-side, per seller, never here).
2026
+ * 2. The snippet is injected (script-executing embed, shared with Kustom).
2027
+ * 3. On completion Qliro redirects to the confirmation URL
2028
+ * (`?checkout_id=…&payment_processor=QLIRO`); the SDK re-reads the
2029
+ * order via `Payment { GetQliroOrder(checkout_id) }` — Qliro
2030
+ * correlates by the OWNING CHECKOUT, not the order id — and renders
2031
+ * the snippet again, which shows Qliro's own receipt.
2032
+ *
2033
+ * The Commerce order is created server-side by Qliro's push webhook
2034
+ * (shopcart /checkout/payment/qliro/ok) — the browser never confirms money.
2035
+ */
2036
+
2037
+ /** Normalized Qliro order (shopcart maps the PascalCase Merchant API). */
2038
+ interface QliroOrder {
2039
+ order_id: string;
2040
+ /** Qliro CustomerCheckoutStatus: InProcess | Completed | OnHold | Refused. */
2041
+ status: string;
2042
+ html_snippet: string;
2043
+ purchase_country?: string;
2044
+ purchase_currency?: string;
2045
+ total_price?: number;
2046
+ }
2047
+
2048
+ /**
2049
+ * Qliro's browser-side protocol (`q1`).
2050
+ *
2051
+ * The embedded widget publishes a `q1` object on the window and calls a
2052
+ * globally-defined `q1Ready` once it has rendered. Everything the customer
2053
+ * does inside the iframe — picking a shipping method, changing their address,
2054
+ * having a payment declined — is only observable through these listeners.
2055
+ * Without them the widget is a black box: the customer can change the
2056
+ * shipping and our own summary keeps showing the old total.
2057
+ *
2058
+ * Two rules from Qliro's documentation shape this module:
2059
+ *
2060
+ * 1. **Lock before touching the backend.** Otherwise the customer can
2061
+ * complete the purchase while the order is still being synchronized.
2062
+ * 2. **Compare before unlocking.** Otherwise a second change interrupts the
2063
+ * synchronization of the first.
2064
+ *
2065
+ * We compare on `MerchantUpdateVersion`, the token Qliro echoes back through
2066
+ * `onOrderUpdated`, rather than on totals. Totals cannot be the test here: in
2067
+ * the modes where Qliro owns the shipping amount our total and Qliro's
2068
+ * legitimately differ, and the customer would sit locked out of a checkout
2069
+ * that is in fact up to date.
2070
+ */
2071
+
2072
+ interface QliroSyncResult {
2073
+ order_id: string;
2074
+ /** Token to wait for in `onOrderUpdated` before unlocking. */
2075
+ update_version: string;
2076
+ total_price: number;
2077
+ }
2078
+
2079
+ /**
2080
+ * Walley (former Collector) — embedded checkout via Vio Commerce.
2081
+ *
2082
+ * Third embedded provider, same category as Kustom/Qliro: the widget
2083
+ * collects address, shipping AND payment by itself.
2084
+ *
2085
+ * 1. `Payment { CreatePaymentWalley }` initializes the session (sponsor-key
2086
+ * authed; the seller's own Walley credentials live server-side, per
2087
+ * seller, never here) and returns a SYNTHESIZED html_snippet — Walley's
2088
+ * real embed is a `<script data-token>` loader, so shopcart wraps it
2089
+ * in the same shape Kustom/Qliro use, and this module reuses their
2090
+ * exact script-executing injection.
2091
+ * 2. Walley's own script renders the iframe. On completion it dispatches
2092
+ * the DOM event `walleyCheckoutPurchaseCompleted` — the PRIMARY success
2093
+ * signal, no page reload needed. `redirectPageUri` is registered too
2094
+ * (`?checkout_id=…&payment_processor=WALLEY`, same contract as
2095
+ * Kustom/Qliro) as a safety net in case the event is missed (tab
2096
+ * backgrounded, page reload).
2097
+ * 3. Either path re-reads the session (`Payment { GetWalleyOrder }`) BY
2098
+ * OWNING CHECKOUT — same as Qliro — and re-renders Walley's own
2099
+ * receipt.
2100
+ *
2101
+ * The Commerce order itself is created server-side by Walley's notification
2102
+ * webhook (shopcart /checkout/payment/walley/ok) — the browser never
2103
+ * confirms money.
2104
+ */
2105
+
2106
+ /** Normalized Walley session (shopcart maps privateId/publicToken/status). */
2107
+ interface WalleyOrder {
2108
+ order_id: string;
2109
+ /** Walley status: Initialized | CustomerIdentified | CommittedToPurchase | PurchaseCompleted | Aborted. */
2110
+ status: string;
2111
+ html_snippet: string;
2112
+ public_token?: string;
2113
+ purchase_country?: string;
2114
+ purchase_currency?: string;
2115
+ total_price?: number;
2116
+ }
2117
+
2118
+ /**
2119
+ * Nexi Checkout (formerly Nets Easy) — embedded checkout via Vio Commerce.
2120
+ *
2121
+ * Fourth embedded provider, same category as Kustom/Qliro/Walley: the widget
2122
+ * collects email, phone, addresses AND payment. Two things set it apart and
2123
+ * shape this module:
2124
+ *
2125
+ * 1. There is no html_snippet. `Payment { CreatePaymentNexi }` creates the
2126
+ * payment server-side (the seller's SECRET key never leaves shopcart)
2127
+ * and returns the paymentId plus the PUBLIC checkout key; the browser
2128
+ * loads Nexi's `checkout.js` and mounts `new Dibs.Checkout({…})` in a
2129
+ * LIGHT-DOM container — Nexi refuses a container inside a shadow root
2130
+ * or another iframe.
2131
+ * 2. Nexi has no shipping picker. The payment is created with
2132
+ * `merchantHandlesShippingCost`, so Nexi holds its pay button until we
2133
+ * price the address the shopper typed: on `address-changed` the widget
2134
+ * is frozen, `Payment { UpdateNexiShipping }` re-prices for that country
2135
+ * and pushes the SHIPPING line, and the widget is thawed.
2136
+ *
2137
+ * Completion arrives as the `payment-completed` event — Nexi neither
2138
+ * redirects nor renders a receipt of its own, so the component shows Vio's
2139
+ * confirmation. Some methods inside Nexi (Vipps, Swish, MobilePay) leave the
2140
+ * page and come back to the SAME url with `?paymentId=…` appended; the
2141
+ * pending session is remembered in sessionStorage so the widget can be
2142
+ * re-mounted on that paymentId and finish.
2143
+ *
2144
+ * The Commerce order itself is created server-side by Nexi's webhook
2145
+ * (shopcart /checkout/payment/nexi/ok) — the browser never confirms money.
2146
+ */
2147
+
2148
+ /** Normalized Nexi payment (shopcart maps the Payment API). */
2149
+ interface NexiOrder {
2150
+ /** Nexi paymentId (32 hex). */
2151
+ order_id: string;
2152
+ /** Created | Reserved | Charged | Cancelled | Terminated | Unknown. */
2153
+ status: string;
2154
+ /** Public checkout key for Dibs.Checkout. */
2155
+ checkout_key: string;
2156
+ /** checkout.js of the seller's environment (test or live). */
2157
+ checkout_js_url: string;
2158
+ purchase_country?: string;
2159
+ purchase_currency?: string;
2160
+ total_price?: number;
2161
+ /**
2162
+ * Shipping to show next to the widget: the purchase country's rates while
2163
+ * no address is known (`reason: AWAITING_ADDRESS`, nothing charged), or what
2164
+ * the payment carries once one was priced (a resumed widget does not
2165
+ * announce its address again).
2166
+ */
2167
+ shipping?: NexiShippingUpdate | null;
2168
+ /** How it was paid ("Visa", "Vipps"…) — once Nexi has the details. */
2169
+ payment_method?: string | null;
2170
+ /** Last four digits of the card. */
2171
+ card_last4?: string | null;
2172
+ /** Where the receipt goes. */
2173
+ email?: string | null;
2174
+ }
2175
+ interface NexiShippingUpdate {
2176
+ ok: boolean;
2177
+ /**
2178
+ * When !ok: NO_SHIPPING — Vio does not ship to that country; or
2179
+ * AWAITING_ADDRESS — nothing charged yet, `options` are the purchase
2180
+ * country's rates and `shipping_id` the suggested one.
2181
+ */
2182
+ reason?: string;
2183
+ order_id: string;
2184
+ total_price?: number;
2185
+ shipping_name?: string;
2186
+ shipping_price?: number;
2187
+ /** The rate on the payment now. */
2188
+ shipping_id?: string;
2189
+ /**
2190
+ * Every rate the shopper may pick for this address, best first. Nexi has
2191
+ * no shipping picker of its own, so the checkout shows these.
2192
+ */
2193
+ options?: NexiShippingOption[];
2194
+ /** The address it was priced for (alpha-2 country). */
2195
+ country?: string;
2196
+ postal_code?: string;
2197
+ /** market — the checkout's country, priced at creation; consumer — the shopper's address. */
2198
+ address_source?: 'market' | 'consumer' | null;
2199
+ /** Whether this call changed the amount on the payment. */
2200
+ changed?: boolean | null;
2201
+ /**
2202
+ * Client-side only: set when the amount changed as the shopper pressed pay,
2203
+ * so the payment was stopped and the widget reloaded.
2204
+ */
2205
+ repriced_at_pay?: boolean;
2206
+ }
2207
+ interface NexiShippingOption {
2208
+ id: string;
2209
+ name: string;
2210
+ /** Incl. VAT, decimal. */
1338
2211
  price: number;
1339
2212
  }
1340
2213
 
@@ -1521,8 +2394,20 @@ interface KlarnaPaymentsHandle {
1521
2394
  /** Tear down the mounted widget. */
1522
2395
  unmount(): void;
1523
2396
  }
2397
+ /**
2398
+ * What the facade lends the manager. `orderAttribution` answers the
2399
+ * attribution of the order about to be created for a sponsor's cart
2400
+ * (`Vio.orderAttribution`, ADR-0035 phase B) — the surface, the page's
2401
+ * campaign, the page and the entry; sent as `attribution` on `CreateCheckout`
2402
+ * and on the `UpdateCheckout` before paying. Optional: without it (a manager
2403
+ * built apart from the facade) nothing is sent and nothing changes.
2404
+ */
2405
+ interface CheckoutManagerDeps {
2406
+ orderAttribution?: (sponsorId: number) => Promise<CheckoutAttributionWire | undefined> | CheckoutAttributionWire | undefined;
2407
+ }
1524
2408
  declare class CheckoutManager extends EventTarget {
1525
2409
  private readonly cartManager;
2410
+ private readonly deps;
1526
2411
  private state;
1527
2412
  /** Counts `open()` calls — see CheckoutState.session. */
1528
2413
  private sessionSeq;
@@ -1560,12 +2445,18 @@ declare class CheckoutManager extends EventTarget {
1560
2445
  /** Per-sponsor payment methods cache + in-flight dedupe. */
1561
2446
  private paymentMethodsCache;
1562
2447
  private paymentMethodsPromises;
1563
- constructor(cartManager: CartManager);
2448
+ constructor(cartManager: CartManager, deps?: CheckoutManagerDeps);
1564
2449
  open(sponsorId: number): CheckoutState;
1565
2450
  close(): void;
1566
- /** Create the backend checkout for the sponsor's cart (auto-accepts terms). */
2451
+ /**
2452
+ * The attribution of the order this sponsor's cart is about to become
2453
+ * (ADR-0035 phase B), through the facade; `undefined` when there is none
2454
+ * to give or it fails — attribution never costs a checkout.
2455
+ */
2456
+ private attributionFor;
2457
+ /** Create the backend checkout for the sponsor's cart (auto-accepts terms); born attributed when the facade knows the attribution. */
1567
2458
  createCheckout(sponsorId: number): Promise<any>;
1568
- /** Update the backend checkout (terms are re-asserted unless overridden). */
2459
+ /** Update the backend checkout (terms are re-asserted unless overridden; the attribution rides along unless the caller gives its own). */
1569
2460
  updateCheckout(variables: Record<string, unknown>, sponsorId?: number): Promise<any>;
1570
2461
  getCheckout(checkoutId: string, sponsorId?: number): Promise<any>;
1571
2462
  /**
@@ -1574,6 +2465,15 @@ declare class CheckoutManager extends EventTarget {
1574
2465
  * receipt, and a receipt does not fail for it.
1575
2466
  */
1576
2467
  getOrderNumber(checkoutId: string, sponsorId?: number): Promise<number | null>;
2468
+ /**
2469
+ * What a paid checkout says of itself: the order number (as above) and the
2470
+ * buyer reference — shopcart's keyed hash of the buyer's email, what the
2471
+ * `vio:payment-success` detail carries as `buyerRef` and analytics sends as
2472
+ * `buyer_id`. Never throws; `null` for whatever the API does not say.
2473
+ */
2474
+ getOrderReference(checkoutId: string, sponsorId?: number): Promise<CheckoutReference>;
2475
+ /** In-flight `getOrderReference` per checkout id (see there). */
2476
+ private referenceInFlight;
1577
2477
  /**
1578
2478
  * Payment methods enabled for the sponsor (backend-configured). Memoised per
1579
2479
  * sponsor with in-flight dedupe — several UI components ask on every open.
@@ -1805,523 +2705,150 @@ declare class CheckoutManager extends EventTarget {
1805
2705
  * and the caller then reads the checkout to see it land.
1806
2706
  */
1807
2707
  mountStripeCheckout(container: HTMLElement, sponsorId: number | undefined, formData: unknown, callbacks?: {
1808
- onReady?: () => void;
1809
- onLoadError?: (message: string) => void;
1810
- }): Promise<{
1811
- handle: StripeElementHandle;
1812
- intent: StripeIntent;
1813
- checkoutId: string;
1814
- }>;
1815
- /** Unmount the Payment Element. Safe when nothing is mounted. */
1816
- destroyStripe(): void;
1817
- /**
1818
- * The Stripe publishable key for a sponsor, taken from its own commerce
1819
- * channel (`GetAvailablePaymentMethods` → Stripe → config.publishableKey).
1820
- *
1821
- * The channel is the right source: the key belongs to the brand's Stripe
1822
- * account, so two brands on one page each get theirs, and a host never has
1823
- * to configure it. `Vio.init({ stripePublishableKey })` still wins when set,
1824
- * which keeps existing integrations working and allows an override.
1825
- */
1826
- private resolveStripePublishableKey;
1827
- /**
1828
- * Returns true if Apple Pay can be shown on this device + Stripe config.
1829
- * Result is memoised — call `clearApplePayCache()` if config changes.
1830
- */
1831
- isApplePayAvailable(country?: string): Promise<boolean>;
1832
- clearApplePayCache(): void;
1833
- /**
1834
- * Prepare Apple Pay for the product-page express button (no active checkout
1835
- * needed). Pre-creates the Stripe PaymentRequest + resolves canMakePayment so
1836
- * the returned `show()` can fire synchronously inside the tap (Apple blocks
1837
- * the sheet if `show()` is reached after an await). `show()` resolves the
1838
- * sheet, then dispatches `payment-complete` (→ confirmation) or
1839
- * `payment-error`. `available` is true only when Apple Pay will actually work.
1840
- */
1841
- prepareApplePayFor(amount: number, currency: string, country?: string): Promise<{
1842
- available: boolean;
1843
- show: () => Promise<void>;
1844
- }>;
1845
- /**
1846
- * Prepare an Apple Pay express charge for an arbitrary amount (e.g. the whole
1847
- * cart) with NO active checkout. Unlike `prepareApplePayFor`, the returned
1848
- * `show()` RESOLVES with the result (or `null` on cancel/failure) and does
1849
- * NOT dispatch the global `payment-complete` — so no checkout overlay appears.
1850
- * The caller (the cart drawer) renders its own confirmation. `show()` must be
1851
- * called synchronously inside the tap (Apple's user-gesture rule).
1852
- */
1853
- prepareApplePayExpress(amount: number, currency: string, country?: string, sponsorId?: number): Promise<{
1854
- available: boolean;
1855
- show: () => Promise<ApplePayResult | null>;
1856
- }>;
1857
- /** The charge closure run inside the Apple Pay sheet's authorize event —
1858
- * shared by the checkout button and the cart's express button. Fail-closed:
1859
- * any failure throws and the sheet closes with failure, nothing confirmed. */
1860
- private buildApplePayAuthorize;
1861
- /** Backend statuses from ConfirmPaymentApplePay that mean the charge did
1862
- * NOT go through — anything here fails the sheet (fail-closed). */
1863
- private static readonly APPLE_PAY_FAILED_STATUSES;
1864
- /**
1865
- * Launches the Apple Pay sheet and charges server-side INSIDE the sheet's
1866
- * authorize event (round-5 wiring, contract from the commerce backend):
1867
- * 1. ensure a backend checkout exists (create if missing)
1868
- * 2. UpdateCheckout(paymentMethod: "Apple Pay" + payer email)
1869
- * 3. CreatePaymentApplePay → gateway hints (pk_/acct_ stored by prefix)
1870
- * 4. ConfirmPaymentApplePay(token, email, shipping) → the REAL charge;
1871
- * a failed/absent confirmation throws → the sheet closes with failure
1872
- * Resolves null if the user closes the sheet. On success, dispatches
1873
- * `payment-complete` with the backend order id in `result.confirmation`.
1874
- */
1875
- startApplePay(options?: {
1876
- country?: string;
1877
- label?: string;
1878
- sponsorId?: number;
1879
- }): Promise<ApplePayResult | null>;
1880
- /**
1881
- * True if the Klarna Express button can be offered (clientId configured +
1882
- * SDK loads). Memoised per active checkout. Never throws.
1883
- */
1884
- isKlarnaExpressAvailable(): Promise<boolean>;
1885
- clearKlarnaCache(): void;
1886
- /**
1887
- * Build the Klarna payment request from the active cart — amounts in the
1888
- * currency's minor unit (øre/cents), one line item per cart line.
1889
- */
1890
- buildKlarnaPaymentRequest(): KlarnaPaymentRequestData;
1891
- /**
1892
- * Mount the Klarna Express button into `container`. On a successful
1893
- * authorization, fires `payment-complete` with the authorization token —
1894
- * the host (or vio-backend) then creates the Klarna order server-side.
1895
- *
1896
- * Returns the button handle so the UI can unmount on teardown.
1897
- */
1898
- mountKlarnaExpress(container: HTMLElement): Promise<KlarnaButtonHandle>;
1899
- /**
1900
- * Klarna Payments — classic widget flow (server client_token). Creates a
1901
- * session on vio-backend, then mounts Klarna's inline widget into
1902
- * `container`. Returns a handle to switch category and to authorize → which
1903
- * on approval creates the order via the same backend /orders route as KEC.
1904
- *
1905
- * Unlike `mountKlarnaExpress`, this needs no public `klarnaClientId` and no
1906
- * origin handshake — auth is the server-side API key.
1907
- */
1908
- mountKlarnaPayments(container: HTMLElement, opts?: {
1909
- withShipping?: boolean;
1910
- shippingId?: string;
1911
- category?: string;
1912
- }): Promise<KlarnaPaymentsHandle>;
1913
- /** True if the Klarna Payments widget lib can load (UI gate). */
1914
- klarnaPaymentsAvailable(): Promise<boolean>;
1915
- /** Cart snapshot + chosen shipping line/amount, for express purchases. */
1916
- private buildKlarnaInstantContext;
1917
- /**
1918
- * Express instant purchase — go straight to Klarna's own modal, no checkout
1919
- * overlay. Creates a session (with shipping options), loads the widget into a
1920
- * hidden container, then authorizes (which opens Klarna's confirm modal). On
1921
- * approval creates the order → `payment-complete` (host shows confirmation);
1922
- * on failure → `payment-error`. Call `open(sponsorId)` first.
1923
- */
1924
- startKlarnaInstant(): Promise<void>;
1925
- /**
1926
- * REDIRECT path: call once on app load. If a Klarna purchase was launched
1927
- * (pending context persisted) and we're back with a token — from the URL or
1928
- * the SDK's resumed `complete` event — finish creating the order + confirm.
1929
- */
1930
- resumeKlarnaReturn(): Promise<void>;
1931
- /** Snapshot of the active cart for redirect recovery (minor units). */
1932
- private buildKlarnaPendingContext;
1933
- /**
1934
- * POST the token + order lines to vio-backend (it holds the secret API key
1935
- * and creates the real Klarna order). On success → `payment-complete`; on
1936
- * failure → `payment-error`. Idempotent: clears the pending key so the
1937
- * popup and redirect paths can't double-fire.
1938
- */
1939
- private completeKlarnaOrder;
1940
- get currentState(): CheckoutState | null;
1941
- get items(): CartLineItem[];
2708
+ onReady?: () => void;
2709
+ onLoadError?: (message: string) => void;
2710
+ }): Promise<{
2711
+ handle: StripeElementHandle;
2712
+ intent: StripeIntent;
2713
+ checkoutId: string;
2714
+ }>;
2715
+ /** Unmount the Payment Element. Safe when nothing is mounted. */
2716
+ destroyStripe(): void;
1942
2717
  /**
1943
- * Resolve the Stripe Connect account for the active sponsor — for now
1944
- * pulled from bootstrap cache if present. Returns undefined if not set
1945
- * (Stripe will route to the platform account in that case).
2718
+ * The Stripe publishable key for a sponsor, taken from its own commerce
2719
+ * channel (`GetAvailablePaymentMethods` → Stripe → config.publishableKey).
2720
+ *
2721
+ * The channel is the right source: the key belongs to the brand's Stripe
2722
+ * account, so two brands on one page each get theirs, and a host never has
2723
+ * to configure it. `Vio.init({ stripePublishableKey })` still wins when set,
2724
+ * which keeps existing integrations working and allows an override.
1946
2725
  */
1947
- private findStripeConnectAccount;
1948
- /** The sponsor's connected account, read fresh from its channel config. */
1949
- private resolveStripeConnectAccount;
1950
- private emit;
1951
- }
1952
-
1953
- /**
1954
- * Slots — the pure half of ADR-0027 / ADR-0034 on the web.
1955
- *
1956
- * A slot is a NAME for a place on a page. The page declares its slots to the
1957
- * campaigns service (`Vio.declareSlots()` / `Vio.registerSlot()` in client.ts),
1958
- * the dashboard assigns a campaign to each one, optionally with a date window,
1959
- * and `GET /v2/mobile/config` answers with `campaigns[].slots[]` — only the
1960
- * assignments whose window is open NOW. Whatever no slot names shows the
1961
- * surface's default campaign (`campaign`).
1962
- *
1963
- * Everything here is side-effect free and never throws on a malformed
1964
- * bootstrap: a broken `campaigns[]` must never blank a product block or break
1965
- * `track()`.
1966
- */
1967
-
1968
- /** What `Vio.declareSlots()` accepts per slot. */
1969
- type SlotInput = string | {
1970
- id: string;
1971
- displayName?: string;
1972
- campaignId?: number;
1973
- };
1974
- type BootLike = Pick<BootstrapResponse, 'campaign' | 'campaigns' | 'primarySponsor' | 'secondarySponsors'>;
1975
- /**
1976
- * The campaign a slot shows right now. Among the `status === 'active'`
1977
- * entries of `campaigns[]`, the one with an assignment named `slot` for this
1978
- * platform (`platform` null on the assignment, or equal to `platform`) whose
1979
- * window contains `now` (`startDate <= now < endDate`, null = open). When
1980
- * several qualify, the surface's default campaign wins, else the first the
1981
- * backend lists.
1982
- *
1983
- * No assignment → the PAGE's campaign (`pageCampaignId`: the element's own
1984
- * `vio-campaign`, an ancestor's `data-vio-campaign`, or `Vio.init({ campaignId })`)
1985
- * when `campaigns[]` lists it as active — an editorial page resolves to its
1986
- * campaign before (and regardless of) the manifest round-trip. Else the
1987
- * default campaign: the `campaigns[]` entry whose id is `campaign.id`, or a
1988
- * minimal entry built from `campaign` (+ the top-level sponsors) when the
1989
- * list does not carry it. No bootstrap → null.
1990
- */
1991
- declare function resolveSlotCampaign(boot: BootLike | null | undefined, slot: string | null | undefined, now?: number, platform?: string | null, pageCampaignId?: CampaignHint, sponsorId?: number | null): BootstrapCampaignEntry | null;
1992
- /**
1993
- * The sponsor rule: the active campaign of `campaigns[]` the sponsor takes
1994
- * part in, as its primary or one of its secondaries — when it is in several,
1995
- * the surface's default campaign if it is one of them, else the first one
1996
- * the backend lists. Null when none (or on a malformed list).
1997
- */
1998
- declare function sponsorCampaignEntry(boot: BootLike | null | undefined, sponsorId: number | null | undefined): BootstrapCampaignEntry | null;
1999
- /**
2000
- * A page campaign as the callers give it: one id, or the candidates in
2001
- * precedence order (the element's `vio-campaign`, an ancestor's
2002
- * `data-vio-campaign`, `Vio.init`'s). An unusable level (invalid, paused,
2003
- * unknown) falls through to the next one; `0`, `null`, `''` mean unset.
2004
- */
2005
- type CampaignHint = number | string | null | undefined | ReadonlyArray<number | string | null | undefined>;
2006
- /**
2007
- * A campaign id as the SDK accepts it: a positive safe integer, given as a
2008
- * number or as a string of decimal digits (an attribute). Anything else —
2009
- * `true`, `'0x10'`, `'1e3'`, an array, a float — is "absent".
2010
- */
2011
- declare function toCampaignId(value: unknown): number | undefined;
2012
- /** The usable ids of a hint, in order, without duplicates. */
2013
- declare function campaignCandidates(hint: CampaignHint): number[];
2014
- /**
2015
- * The first candidate of `hint` that `campaigns[]` lists as ACTIVE (and, when
2016
- * `sponsorId` is given, that the sponsor takes part in — primary or
2017
- * secondary); null when none — a paused, ended or unknown page campaign is
2018
- * ignored and the resolution moves on.
2019
- */
2020
- declare function activeCampaignEntry(boot: BootLike | null | undefined, hint: CampaignHint, sponsorId?: number): BootstrapCampaignEntry | null;
2021
- /** The kinds of Vio element that register themselves. */
2022
- type SlotKind = 'card' | 'carousel' | 'detail';
2023
- /**
2024
- * The id an element gets when the host set no `vio-slot`:
2025
- * `${scope ? scope + '-' : ''}${kind}-${ordinal + 1}` (`carousel-1`, `card-3`,
2026
- * `article-123-carousel-1`), `ordinal` being the element's index among the
2027
- * same-kind Vio elements WITH THE SAME SCOPE, in document order at connect
2028
- * time. The scope is inherited from the DOM (`vio-scope` on the element, else
2029
- * the nearest `data-vio-scope` ancestor): a Vev page or a Replit article
2030
- * (`<body data-vio-scope="article-123">`) gets per-page slots with one line
2031
- * and no per-element naming; a template that wants slots shared across
2032
- * articles simply sets no scope. On purpose there is NO page path in the id:
2033
- * the article goes to analytics as `content_id`, not into the slot. Stable
2034
- * across reloads while the layout is; a layout change leaves a stale entry,
2035
- * which ADR-0034 accepts.
2036
- */
2037
- declare function autoSlotId(kind: SlotKind, ordinal: number, scope?: string | null): string;
2038
- /** `"Carousel 1"`, `"Card 3"` — with ` · ${scopeName}` when the scope has a human label. */
2039
- declare function autoSlotDisplayName(kind: SlotKind, ordinal: number, scopeName?: string | null): string;
2040
- /** A scope value fit for an id: trimmed, inner whitespace → `-`. */
2041
- declare function normalizeScope(scope: string | null | undefined): string;
2042
-
2043
- /**
2044
- * Vio.analytics — the web SDK's half of the Vio Analytics pipeline.
2045
- *
2046
- * Events flow to the Vio collector (`POST <eventsBase>/v1/events`, repo
2047
- * vio-live/vio-analytics) which owns raw storage (ClickHouse) and vendor
2048
- * fan-out (Mixpanel today). THE SDK NEVER TALKS TO VENDORS — swapping or
2049
- * dropping a vendor is a server-side change, zero client releases.
2050
- * Wire contract: vio-analytics `docs/EVENTS_CONTRACT.md` (v1, snake_case).
2051
- *
2052
- * Design rules:
2053
- * - SIDE-EFFECT-FREE MODULE. Nothing runs at import time (tsup/tree-shaking
2054
- * contract, see lessons/web-sdk-tsup-singleton-and-build). Listeners are
2055
- * installed by an explicit `Vio.analytics.start()`.
2056
- * - `track()` works BEFORE `Vio.init()` — events queue and flush once the
2057
- * SDK is configured (transport resolves at flush time, not at track time).
2058
- * - SSR-safe: without `window` every call is a silent no-op.
2059
- * - Retries are safe: `event_id` is stable per event, the collector
2060
- * dedupes. Queue is bounded (drop-oldest) so a dead collector can never
2061
- * grow memory unbounded.
2062
- * - Anti-double-count: the SDK only reports what the server can't see.
2063
- * Votes/participations are server truth; `ad_activation`/`cart_intent`
2064
- * are mirrored server-side, never from here.
2065
- *
2066
- * Three sinks, matching the Vev precedent (vio-vev analytics.ts, which
2067
- * becomes an adapter over this in F3):
2068
- * 1. collector (default ON once started)
2069
- * 2. window.dataLayer — GA4/GTM (opt-in `start({ dataLayer: true })`,
2070
- * the publisher-facing feature; NOT part of the Vio pipeline)
2071
- * 3. `vio:analytics` DOM event — always when started (inspector/bridges)
2072
- */
2073
-
2074
- declare const ANALYTICS_EVENT_NAMES: readonly ["view_item_list", "select_item", "view_item", "add_to_cart", "remove_from_cart", "view_cart", "begin_checkout", "purchase", "component_impression", "component_click", "ad_impression", "ad_click", "poll_impression", "contest_impression", "session_start", "session_end", "checkout_error", "sdk_error"];
2075
- type AnalyticsEventName = (typeof ANALYTICS_EVENT_NAMES)[number];
2076
- /** Camel-case for DX; mapped to the snake_case wire format at build time. */
2077
- interface AnalyticsContext {
2078
- campaignId?: number;
2079
- broadcastId?: string;
2080
- campaignComponentId?: number;
2081
- appPlacementId?: number;
2082
- locationId?: string;
2083
- componentTemplateId?: string;
2084
- sponsorId?: number;
2085
- activationId?: number;
2086
- contentUrl?: string;
2726
+ private resolveStripePublishableKey;
2087
2727
  /**
2088
- * The host's own id for the piece of content the component sits in — an
2089
- * article id in a CMS, a page id in a site builder. Carried verbatim so the
2090
- * host can join Vio's funnel back onto its own per-article reporting
2091
- * (revenue stops being an estimate). Left alone by the SDK.
2728
+ * Returns true if Apple Pay can be shown on this device + Stripe config.
2729
+ * Result is memoised — call `clearApplePayCache()` if config changes.
2092
2730
  */
2093
- contentId?: string | number;
2094
- /** Human-readable content snapshot; auto-filled from document.title. */
2095
- contentTitle?: string;
2096
- /** Código corto del fallo (`sdk_error` / `checkout_error`). */
2097
- errorCode?: string;
2098
- variant?: string;
2731
+ isApplePayAvailable(country?: string): Promise<boolean>;
2732
+ clearApplePayCache(): void;
2099
2733
  /**
2100
- * The slot (ADR-0027) the component sits in — its `vio-slot`. Attribution
2101
- * follows it: the event's `campaign_id` is the campaign assigned to the slot
2102
- * right now, before the sponsor rule. On the wire it fills `location_id`
2103
- * when no explicit `locationId` is given (a slot IS a location id to the
2104
- * campaigns service).
2734
+ * Prepare Apple Pay for the product-page express button (no active checkout
2735
+ * needed). Pre-creates the Stripe PaymentRequest + resolves canMakePayment so
2736
+ * the returned `show()` can fire synchronously inside the tap (Apple blocks
2737
+ * the sheet if `show()` is reached after an await). `show()` resolves the
2738
+ * sheet, then dispatches `payment-complete` (→ confirmation) or
2739
+ * `payment-error`. `available` is true only when Apple Pay will actually work.
2105
2740
  */
2106
- slot?: string;
2107
- }
2108
- interface AnalyticsItemInput {
2109
- productId: number | string;
2110
- name?: string;
2111
- brand?: string;
2112
- variantId?: number | string;
2113
- price?: number;
2114
- quantity?: number;
2115
- }
2116
- interface AnalyticsCommerce {
2117
- items?: AnalyticsItemInput[];
2118
- value?: number;
2119
- currency?: string;
2120
- orderId?: string;
2121
- paymentMethod?: string;
2122
- }
2123
- interface TrackOptions {
2124
- context?: AnalyticsContext;
2125
- commerce?: AnalyticsCommerce;
2126
- props?: Record<string, unknown>;
2127
- }
2128
- interface AnalyticsStartOptions {
2129
- /** Where the SDK is embedded: 'vev' | 'replit' | 'custom' | … Default 'custom'. */
2130
- host?: string;
2741
+ prepareApplePayFor(amount: number, currency: string, country?: string): Promise<{
2742
+ available: boolean;
2743
+ show: () => Promise<void>;
2744
+ }>;
2131
2745
  /**
2132
- * Arrancar en modo cookieless hasta que el host llame `setConsent(true)`.
2133
- * Default false: el SDK asume que el CMP del publisher ya decidió si nos
2134
- * carga. Los publishers EU que necesitan medir antes del consentimiento
2135
- * (o no pueden persistir sin él) ponen true.
2746
+ * Prepare an Apple Pay express charge for an arbitrary amount (e.g. the whole
2747
+ * cart) with NO active checkout. Unlike `prepareApplePayFor`, the returned
2748
+ * `show()` RESOLVES with the result (or `null` on cancel/failure) and does
2749
+ * NOT dispatch the global `payment-complete` — so no checkout overlay appears.
2750
+ * The caller (the cart drawer) renders its own confirmation. `show()` must be
2751
+ * called synchronously inside the tap (Apple's user-gesture rule).
2136
2752
  */
2137
- requireConsent?: boolean;
2753
+ prepareApplePayExpress(amount: number, currency: string, country?: string, sponsorId?: number): Promise<{
2754
+ available: boolean;
2755
+ show: () => Promise<ApplePayResult | null>;
2756
+ }>;
2757
+ /** The charge closure run inside the Apple Pay sheet's authorize event —
2758
+ * shared by the checkout button and the cart's express button. Fail-closed:
2759
+ * any failure throws and the sheet closes with failure, nothing confirmed. */
2760
+ private buildApplePayAuthorize;
2761
+ /** Backend statuses from ConfirmPaymentApplePay that mean the charge did
2762
+ * NOT go through — anything here fails the sheet (fail-closed). */
2763
+ private static readonly APPLE_PAY_FAILED_STATUSES;
2138
2764
  /**
2139
- * Install the automatic funnel listeners (vio:added-to-cart,
2140
- * vio:checkout-open, vio:open-cart, vio:payment-success). Default true.
2141
- * Hosts with their OWN instrumentation choke point (e.g. the Vev module,
2142
- * which forwards every event through trackEvent()) set false so the same
2143
- * user action is never tracked twice — they keep the session/batching/
2144
- * transport engine and call track() themselves.
2765
+ * Launches the Apple Pay sheet and charges server-side INSIDE the sheet's
2766
+ * authorize event (round-5 wiring, contract from the commerce backend):
2767
+ * 1. ensure a backend checkout exists (create if missing)
2768
+ * 2. UpdateCheckout(paymentMethod: "Apple Pay" + payer email)
2769
+ * 3. CreatePaymentApplePay → gateway hints (pk_/acct_ stored by prefix)
2770
+ * 4. ConfirmPaymentApplePay(token, email, shipping) → the REAL charge;
2771
+ * a failed/absent confirmation throws → the sheet closes with failure
2772
+ * Resolves null if the user closes the sheet. On success, dispatches
2773
+ * `payment-complete` with the backend order id in `result.confirmation`.
2145
2774
  */
2146
- autoTrack?: boolean;
2775
+ startApplePay(options?: {
2776
+ country?: string;
2777
+ label?: string;
2778
+ sponsorId?: number;
2779
+ }): Promise<ApplePayResult | null>;
2147
2780
  /**
2148
- * Adopt the host's session id instead of minting one. A host that already
2149
- * tracks its readers (its own impressions, dwell, clicks) needs Vio's
2150
- * purchases to land in the SAME session, or the two datasets cannot be
2151
- * joined. Pass the id the host already stores; omit it and the SDK keeps
2152
- * its own rotating session.
2781
+ * True if the Klarna Express button can be offered (clientId configured +
2782
+ * SDK loads). Memoised per active checkout. Never throws.
2153
2783
  */
2154
- sessionId?: string;
2155
- /** Send to the Vio collector. Default true. */
2156
- collector?: boolean;
2157
- /** Also push GA4 e-commerce events to window.dataLayer (publisher feature). */
2158
- dataLayer?: boolean;
2159
- /** console.debug every event. */
2160
- debug?: boolean;
2161
- }
2162
- declare class AnalyticsManager extends EventTarget {
2163
- private started;
2164
- private options;
2165
- private queue;
2166
- private flushTimer;
2167
- private retryAttempt;
2168
- private retryNotBefore;
2169
- private externalUserId;
2170
- private sessionId;
2171
- private requireConsent;
2172
- private consentGranted;
2173
- private anonIdCache;
2174
- /** Set via start({ sessionId }) — the host owns the session, we follow. */
2175
- private externalSessionId;
2176
- private sessionStartedAt;
2177
- /** Impression once-per-(session, component) guard; reset on session rotation. */
2178
- private impressedComponents;
2179
- /** Last NON-EMPTY cart snapshot — `purchase` uses it because several flows
2180
- * (Apple Pay) clear the cart BEFORE `vio:payment-success` fires. */
2181
- private lastCartSnapshot;
2182
- private teardown;
2784
+ isKlarnaExpressAvailable(): Promise<boolean>;
2785
+ clearKlarnaCache(): void;
2183
2786
  /**
2184
- * Install listeners + begin flushing. Explicit on purpose (no module
2185
- * side-effects). Safe to call twice (second call only updates options).
2787
+ * Build the Klarna payment request from the active cart — amounts in the
2788
+ * currency's minor unit (øre/cents), one line item per cart line.
2186
2789
  */
2790
+ buildKlarnaPaymentRequest(): KlarnaPaymentRequestData;
2187
2791
  /**
2188
- * Consentimiento (ePrivacy/GDPR). Por defecto el SDK asume que el host ya
2189
- * lo gestionó — su CMP decide si nos carga. Con `requireConsent: true`
2190
- * arranca en modo **cookieless**: mide la visita con ids efímeros en
2191
- * memoria y NO escribe nada en localStorage hasta que el host llame
2192
- * `setConsent(true)`. Retirarlo borra lo persistido.
2792
+ * Mount the Klarna Express button into `container`. On a successful
2793
+ * authorization, fires `payment-complete` with the authorization token —
2794
+ * the host (or vio-backend) then creates the Klarna order server-side.
2795
+ *
2796
+ * Returns the button handle so the UI can unmount on teardown.
2193
2797
  */
2194
- setConsent(granted: boolean): void;
2195
- /** ¿Podemos escribir en el dispositivo? */
2196
- private get canPersist();
2197
- start(options?: AnalyticsStartOptions): void;
2198
- /** Stop timers + listeners (tests / SPA unmount). Queue survives. */
2199
- stop(): void;
2200
- /** Attach the partner's user id (opaque) to all subsequent events. */
2201
- identify(externalUserId: string | null): void;
2202
- /** Queue one event. Works pre-init and pre-start (SSR: no-op). */
2203
- track(name: AnalyticsEventName, opts?: TrackOptions): void;
2798
+ mountKlarnaExpress(container: HTMLElement): Promise<KlarnaButtonHandle>;
2204
2799
  /**
2205
- * Impression helper: observes an element and tracks ONE
2206
- * `component_impression` per (session, campaignComponentId) when the
2207
- * contract's rule is met (≥50% visible, ≥1s dwell). Returns an
2208
- * unobserve function.
2800
+ * Klarna Payments — classic widget flow (server client_token). Creates a
2801
+ * session on vio-backend, then mounts Klarna's inline widget into
2802
+ * `container`. Returns a handle to switch category and to authorize → which
2803
+ * on approval creates the order via the same backend /orders route as KEC.
2804
+ *
2805
+ * Unlike `mountKlarnaExpress`, this needs no public `klarnaClientId` and no
2806
+ * origin handshake — auth is the server-side API key.
2209
2807
  */
2210
- observeImpression(element: Element, context?: AnalyticsContext, opts?: {
2211
- name?: Extract<AnalyticsEventName, 'component_impression' | 'ad_impression'>;
2212
- /** Dedupe key override. Defaults to the strongest id available:
2213
- * campaignComponentId → locationId → componentTemplateId. */
2214
- key?: string;
2215
- /** Called the one time the impression is tracked (e.g. to fire a
2216
- * companion view_item_list). */
2217
- onImpression?: () => void;
2218
- }): () => void;
2219
- /** Force a flush (mostly for tests). */
2220
- flush(): Promise<void>;
2221
- private buildEvent;
2222
- /** Guarda la sesión solo si podemos escribir en el dispositivo. */
2223
- private persistSession;
2224
- private anonId;
2225
- /** Rolling session: renewed by activity, rotated after 30 min idle. */
2226
- private ensureSession;
2227
- private trackSessionEnd;
2228
- private flushWithBeacon;
2808
+ mountKlarnaPayments(container: HTMLElement, opts?: {
2809
+ withShipping?: boolean;
2810
+ shippingId?: string;
2811
+ category?: string;
2812
+ }): Promise<KlarnaPaymentsHandle>;
2813
+ /** True if the Klarna Payments widget lib can load (UI gate). */
2814
+ klarnaPaymentsAvailable(): Promise<boolean>;
2815
+ /** Cart snapshot + chosen shipping line/amount, for express purchases. */
2816
+ private buildKlarnaInstantContext;
2229
2817
  /**
2230
- * Auto-instrumentation over the SDK's own DOM events — the host app
2231
- * writes ZERO tracking code for the commerce funnel.
2818
+ * Express instant purchase — go straight to Klarna's own modal, no checkout
2819
+ * overlay. Creates a session (with shipping options), loads the widget into a
2820
+ * hidden container, then authorizes (which opens Klarna's confirm modal). On
2821
+ * approval creates the order → `payment-complete` (host shows confirmation);
2822
+ * on failure → `payment-error`. Call `open(sponsorId)` first.
2232
2823
  */
2233
- private installAutoListeners;
2234
- /** The lines of the cart (current, else the pre-clear snapshot), optionally one sponsor's. */
2235
- private cartLines;
2824
+ startKlarnaInstant(): Promise<void>;
2236
2825
  /**
2237
- * The cart split per campaign — the shared rule of cart/slices.ts over the
2238
- * current cart (else the pre-clear snapshot), plus whether any unit named
2239
- * its campaign itself (a cart without origins is reported as before).
2826
+ * REDIRECT path: call once on app load. If a Klarna purchase was launched
2827
+ * (pending context persisted) and we're back with a token — from the URL or
2828
+ * the SDK's resumed `complete` event — finish creating the order + confirm.
2240
2829
  */
2241
- private cartSlices;
2830
+ resumeKlarnaReturn(): Promise<void>;
2831
+ /** Snapshot of the active cart for redirect recovery (minor units). */
2832
+ private buildKlarnaPendingContext;
2242
2833
  /**
2243
- * The context of one of several per-campaign events: the slice's campaign,
2244
- * slot and content — only its own; when the slice spans several slots or
2245
- * articles the event names none rather than the one the click came from.
2834
+ * POST the token + order lines to vio-backend (it holds the secret API key
2835
+ * and creates the real Klarna order). On success → `payment-complete`; on
2836
+ * failure → `payment-error`. Idempotent: clears the pending key so the
2837
+ * popup and redirect paths can't double-fire.
2246
2838
  */
2247
- private sliceContext;
2839
+ private completeKlarnaOrder;
2840
+ get currentState(): CheckoutState | null;
2841
+ get items(): CartLineItem[];
2248
2842
  /**
2249
- * The context of a single-campaign checkout/purchase: when the cart's origins
2250
- * say which campaign they came from, the event says it too (plus the slot /
2251
- * content when unique) — a cart without origins is exactly as before.
2843
+ * Resolve the Stripe Connect account for the active sponsor — for now
2844
+ * pulled from bootstrap cache if present. Returns undefined if not set
2845
+ * (Stripe will route to the platform account in that case).
2252
2846
  */
2253
- private singleSliceContext;
2254
- /** Current cart → commerce payload; falls back to the pre-clear snapshot. */
2255
- private cartCommerce;
2256
- private findLine;
2257
- }
2258
- /**
2259
- * The `campaign_id` of an event. In order: the context's explicit id; the
2260
- * campaign assigned to the context's `slot` right now (ADR-0027 — only an
2261
- * open assignment for the init platform counts, see `findSlotAssignment`);
2262
- * the PAGE's campaign (`opts.pageCampaignId`, else `Vio.init({ campaignId })`)
2263
- * when `campaigns[]` lists it as active (ADR-0034, amendment 3);
2264
- * the active campaign of `campaigns[]` the event's sponsor takes part in, as
2265
- * its primary or one of its secondaries — when it is in several, the
2266
- * surface's default campaign if it is one of them, else the first one the
2267
- * backend lists; the default campaign (`campaign.id`). `/v2/mobile/config`
2268
- * has no top-level `campaignId` — reading one is why every event used to
2269
- * leave with `campaign_id: 0` and the campaigns screen stayed empty.
2270
- *
2271
- * A malformed `campaigns[]` (not an array, a null entry) never breaks
2272
- * `track()`: it falls back to `campaign.id`.
2273
- */
2274
- declare function resolveCampaignId(ctx: Pick<AnalyticsContext, 'campaignId' | 'sponsorId' | 'slot'>, boot: Pick<BootstrapResponse, 'campaign' | 'campaigns'> | null | undefined, opts?: {
2275
- pageCampaignId?: CampaignHint;
2276
- }): number | undefined;
2277
- /**
2278
- * `detail` of `vio:payment-success` as the checkout emits it. Four paths,
2279
- * three shapes: a redirect return (`orderId` = Vio's checkout id), the
2280
- * confirmation drawer and a provider's own receipt (`result` from the
2281
- * provider or the backend, plus Vio's `checkoutId`), and Apple Pay express
2282
- * from the cart (`result` with the backend's `confirmation`, no sponsor).
2283
- */
2284
- interface PaymentSuccessDetail {
2285
- method?: string;
2286
- sponsorId?: number | string;
2287
- /** The slot the purchase started from, when the emitter knows it. */
2288
- slot?: string;
2289
- /** The campaign the emitter attributed the purchase to (its `Vio.attribution`). */
2290
- campaignId?: number;
2291
- /** Per-element content override (`vio-content-id` / `vio-content-title`), when the emitter carries one. */
2292
- contentId?: string | number;
2293
- contentTitle?: string;
2294
- /** Redirect return: Vio's checkout id (the name predates `checkoutId`). */
2295
- orderId?: string | number;
2296
- /** Vio's checkout id — from the checkout state or the return URL. */
2297
- checkoutId?: string | number;
2298
- result?: PaymentSuccessResult | null;
2299
- }
2300
- /**
2301
- * What the checkout attaches as `result`. Only `confirmation` carries a Vio
2302
- * id: the order `ConfirmPaymentApplePay` created. `order.orderId` / `orderId`
2303
- * are the PROVIDER's references (Qliro's, Walley's — the checkout itself
2304
- * shows them as `providerRef`), and `chargedTotal` includes shipping on some
2305
- * methods only. Analytics reads none of those.
2306
- */
2307
- interface PaymentSuccessResult {
2308
- confirmation?: {
2309
- orderId?: string | number | null;
2310
- status?: string;
2311
- } | null;
2312
- [key: string]: unknown;
2847
+ private findStripeConnectAccount;
2848
+ /** The sponsor's connected account, read fresh from its channel config. */
2849
+ private resolveStripeConnectAccount;
2850
+ private emit;
2313
2851
  }
2314
- /**
2315
- * The `order_id` of a `purchase`: ALWAYS a Vio id, never a provider's
2316
- * reference. Normalized across every emitter: `orderId` (redirect return) →
2317
- * `result.confirmation.orderId` (Apple Pay) → `checkoutId`. `value` and
2318
- * `currency` are deliberately NOT read from the result: on every path they
2319
- * come from the cart snapshot (sum of the items, no shipping), so revenue
2320
- * stays comparable across payment methods — the charged total will arrive
2321
- * through the server-side `order_paid` event. With nothing to say it
2322
- * returns `{}`, so the cart snapshot keeps its keys.
2323
- */
2324
- declare function purchaseFromPaymentSuccess(d: PaymentSuccessDetail): Pick<AnalyticsCommerce, 'orderId'>;
2325
2852
 
2326
2853
  /**
2327
2854
  * Content (article) attribution — the other half of "where did this happen".
@@ -2340,11 +2867,32 @@ declare function purchaseFromPaymentSuccess(d: PaymentSuccessDetail): Pick<Analy
2340
2867
  interface ContentRef {
2341
2868
  id?: string;
2342
2869
  title?: string;
2870
+ /** The page's URL as origin + path (no query, no fragment), capped — what the order and the cart's origins carry as `content_url`. */
2871
+ url?: string;
2343
2872
  }
2344
2873
  interface ContentInit {
2345
2874
  contentId?: string | number | null;
2346
2875
  contentTitle?: string | null;
2347
2876
  }
2877
+ /** The collector (and Commerce's `order.content_url`) cap `content_url` at 2 048 chars. */
2878
+ declare const CONTENT_URL_MAX_LENGTH = 2048;
2879
+ /**
2880
+ * A page as the ORDER and the cart's origins name it (`content_url` to
2881
+ * Commerce): origin + path — no query string, no fragment — within the cap.
2882
+ * The query is where a page carries e-mails, tokens and tracking tails
2883
+ * (`?email=…&utm_…#frag`); none of that belongs on an order row. A site whose
2884
+ * article lives in the query or the hash identifies it through `contentId`
2885
+ * (`Vio.init({ contentId })`, `?vio_content=`), not through the URL. Also
2886
+ * the shape of the landing's referrer and page (client-context.ts).
2887
+ */
2888
+ declare function originAndPath(raw: unknown, max?: number): string | undefined;
2889
+ /**
2890
+ * A page URL within the cap for the analytics EVENTS — as is, else without
2891
+ * its query string (a long one is usually the tracking tail, not the page),
2892
+ * else cut. The collector keeps the query there (`page_views` strip it
2893
+ * themselves); the order does not (`originAndPath`).
2894
+ */
2895
+ declare function capContentUrl(value: unknown): string | undefined;
2348
2896
  /**
2349
2897
  * Id: `Vio.init({ contentId })` → `?vio_content=` → `<meta name="vio:content-id">`
2350
2898
  * → `<link rel="canonical">` href or `<meta property="og:url">` (without query
@@ -2358,39 +2906,7 @@ declare function resolveContent(init?: ContentInit | null, doc?: Document | null
2358
2906
  search?: string;
2359
2907
  } | null | undefined): ContentRef;
2360
2908
 
2361
- /**
2362
- * The cart split per campaign (ADR-0034) — ONE rule, shared by the SDK's own
2363
- * `begin_checkout` / `purchase` events, by `Vio.cartSlices()` and so by any
2364
- * host with its own analytics sink (Vev).
2365
- *
2366
- * Every unit of every line goes to the campaign of its origin — the one the
2367
- * place showed when it was added — and units without one to the campaign the
2368
- * caller's rule resolves for the line's sponsor (`resolveCampaign`). One slice
2369
- * per campaign: its items (quantities from the origins, merged per product +
2370
- * variant), its value with two decimals, and `slot` / `contentId` /
2371
- * `sponsorId` only when they are the same for the whole slice. A cart with
2372
- * one campaign is one slice. Pure; never throws on odd lines.
2373
- */
2374
-
2375
- /** An item of a slice — the analytics item shape (ids as given, normalized on the wire). */
2376
- type VioItem = AnalyticsItemInput;
2377
- /** One campaign's share of the cart. */
2378
- interface CartSlice {
2379
- campaignId?: number;
2380
- sponsorId?: number;
2381
- slot?: string;
2382
- contentId?: string;
2383
- items: VioItem[];
2384
- value: number;
2385
- currency?: string;
2386
- /** 1-based position of this slice and how many there are. */
2387
- slice: number;
2388
- slices: number;
2389
- }
2390
- /** The campaign of a unit without an explicit one: the caller's rule (slot assignment → sponsor → default). */
2391
- type ResolveCampaign = (slot: string | undefined, sponsorId: number) => number | undefined;
2392
- declare function cartSlicesFrom(lines: ReadonlyArray<CartLineItem>, resolveCampaign: ResolveCampaign, fallbackCurrency?: string): CartSlice[];
2393
-
2909
+ type PrivacyAnswer = SurfacePrivacyMode | 'unknown';
2394
2910
  declare class VioFacade {
2395
2911
  private apiInstance;
2396
2912
  private commerceClients;
@@ -2403,7 +2919,7 @@ declare class VioFacade {
2403
2919
  * (another key or environment) is still in flight after a re-init; its answer
2404
2920
  * belongs to that configuration and must not land in this one. */
2405
2921
  private generation;
2406
- /** Slot ids present on the page (`Vio.registerSlot`): how many elements hold each, its display name and its own campaign. */
2922
+ /** Slot ids present on the page (`Vio.registerSlot`): how many elements hold each, its display name, its own campaign and its kind. */
2407
2923
  private slotRegistry;
2408
2924
  /** Slots already warned about (`dashboard`: the dashboard's assignment stands; `replaced`: this page moved it). */
2409
2925
  private warnedDashboardSlots;
@@ -2420,6 +2936,10 @@ declare class VioFacade {
2420
2936
  private cartManager;
2421
2937
  private checkoutManager;
2422
2938
  private analyticsManager;
2939
+ /** What the surface answered of its privacy mode (`GET /v2/mobile/surface`) and when; `unknown` expires after a minute. */
2940
+ private surfaceMode;
2941
+ /** The request in flight for it, if any. */
2942
+ private surfaceRequest;
2423
2943
  init(config: VioConfig): void;
2424
2944
  /**
2425
2945
  * The content (article, page) this SDK instance is rendered in — what every
@@ -2435,7 +2955,7 @@ declare class VioFacade {
2435
2955
  get config(): ResolvedConfig;
2436
2956
  /** Multi-sponsor cart manager. Reactive — listen via addEventListener('change', ...). */
2437
2957
  get cart(): CartManager;
2438
- /** Checkout flow manager. Subscribes to cart state via the CartManager. */
2958
+ /** Checkout flow manager. Subscribes to cart state via the CartManager; asks here for the order's attribution. */
2439
2959
  get checkout(): CheckoutManager;
2440
2960
  /**
2441
2961
  * Analytics — track() queues even pre-init; nothing is sent (and no
@@ -2479,12 +2999,19 @@ declare class VioFacade {
2479
2999
  * the optional display name, and the element's own campaign when it has
2480
3000
  * one: `vio-campaign` or an ancestor's `data-vio-campaign`). The registry is
2481
3001
  * declared ONCE, debounced, after the first successful bootstrap — and again
2482
- * whenever a new id, display name or campaign appears. A `displayName` /
3002
+ * whenever a new id, display name, campaign or kind appears. A `displayName` /
2483
3003
  * `campaignId` given later for an id already registered replaces the one
2484
- * kept; `undefined` leaves it alone. The returned function unregisters;
3004
+ * kept; `undefined` leaves it alone. The third argument is the campaign id
3005
+ * (as before) or `{ campaignId?, kind? }`, and a fourth `{ kind? }` may
3006
+ * follow a positional campaign — the elements call
3007
+ * `registerSlot(id, name, campaignId, { kind })` so a facade from an older
3008
+ * bundle, which reads only three arguments, still gets the campaign. `kind`
3009
+ * is what kind of element holds the slot (`card` | `carousel` | `detail`);
3010
+ * the FIRST kind an id gets stays (the cards a carousel renders share its
3011
+ * id: the slot is the carousel's). The returned function unregisters;
2485
3012
  * removal never re-declares.
2486
3013
  */
2487
- registerSlot(name: string, displayName?: string, campaignId?: number | string | null): () => void;
3014
+ registerSlot(name: string, displayName?: string, campaignIdOrOptions?: number | string | null | RegisterSlotOptions, options?: RegisterSlotOptions): () => void;
2488
3015
  /**
2489
3016
  * The campaign a slot shows now, from the last bootstrap: its assigned and
2490
3017
  * open campaign for the init platform, else the surface's default. With no
@@ -2523,6 +3050,58 @@ declare class VioFacade {
2523
3050
  sponsorId?: number | string;
2524
3051
  campaignId?: CampaignHint;
2525
3052
  }): Attribution;
3053
+ /**
3054
+ * What the surface answered and is still believed: `standard` / `minimal`
3055
+ * for the configuration; `unknown` for a minute after a timeout or a
3056
+ * failure (then it is asked again). `null` = nothing believed.
3057
+ */
3058
+ private believedSurfaceMode;
3059
+ /**
3060
+ * Ask the surface its privacy mode (`GET /v2/mobile/surface`), once —
3061
+ * started at `init()` and again by `bootstrap()`, never by the payment
3062
+ * path, which only reads what is known (`privacyModeNow`). Resolves what
3063
+ * was learnt within 1.5 s: the mode; `standard` for a service that predates
3064
+ * the endpoint (404) or a surface that names none; `unknown` for a word the
3065
+ * SDK does not know, a timeout or any other failure — believed for a
3066
+ * minute, then asked again. An answer that arrives after the cap still
3067
+ * lands for the next step. Never throws.
3068
+ */
3069
+ preloadPrivacyMode(): Promise<PrivacyAnswer>;
3070
+ /**
3071
+ * How much of the visit the order attribution may carry (ADR-0035): the
3072
+ * surface's privacy mode, as known RIGHT NOW, without waiting —
3073
+ * `Vio.init({ privacyMode: 'minimal' })` when the host says so, else what
3074
+ * the bootstrap says of the surface, else what the surface answered to the
3075
+ * ask `init()` started (`preloadPrivacyMode`), else the host's `standard`
3076
+ * once the surface could not tell. `unknown` while nothing is known: then
3077
+ * the page, the slot and the entry stay off what goes to Commerce, because
3078
+ * what can reveal which screen someone was on, and why, is only sent when
3079
+ * the surface is known to allow it. The payment path reads this one.
3080
+ */
3081
+ privacyModeNow(): PrivacyAnswer;
3082
+ /**
3083
+ * `privacyModeNow`, but waiting for the ask in flight when there is one
3084
+ * (at most its 1.5 s cap) — for the cart's lines, which are not the
3085
+ * payment path. Never throws.
3086
+ */
3087
+ privacyMode(): Promise<PrivacyAnswer>;
3088
+ /**
3089
+ * The attribution of the order a sponsor's cart is about to become
3090
+ * (ADR-0035 phase B) — what `CreateCheckout` / `UpdateCheckout` send as
3091
+ * `attribution` and shopcart writes on the order: the surface (its SDK
3092
+ * key), the page's campaign (`pageCampaign()`; without one, the campaign
3093
+ * that explains most units of the cart under the same rule `purchase` is
3094
+ * split by — `cartSlices`) and, on a surface KNOWN to be `standard`
3095
+ * (`privacyModeNow`), the page (`content()`'s url as origin + path, and
3096
+ * title) and where the session came in: the landing the session REMEMBERS
3097
+ * (`storedLanding`, else the one analytics resolved on this page) — never
3098
+ * the page the shopper pays on taken for a landing. Synchronous: nothing
3099
+ * on the way to a payment waits for it. snake_case, capped to Commerce's
3100
+ * limits. `undefined` when there is nothing to say; never throws.
3101
+ */
3102
+ orderAttribution(sponsorId?: number): CheckoutAttributionWire | undefined;
3103
+ /** The landing the session remembers: what the first page stored, else what analytics resolved on this page load (consent pending); null otherwise. */
3104
+ private rememberedLanding;
2526
3105
  /**
2527
3106
  * The cart split per campaign — the SAME rule `begin_checkout` / `purchase`
2528
3107
  * use, for hosts with their own analytics sink (Vev). Every unit of every
@@ -2556,6 +3135,20 @@ declare class VioFacade {
2556
3135
  }
2557
3136
  declare const Vio: VioFacade;
2558
3137
 
3138
+ /**
3139
+ * The buyer reference — shopcart's `buyer_ref`: the keyed hash Commerce
3140
+ * computes from the buyer's email, so one person is one ref across orders,
3141
+ * devices and platforms, and nothing in it can be turned back into an email
3142
+ * without Commerce's secret. The SDK forwards it as analytics `buyer_id`
3143
+ * (EVENTS_CONTRACT `commerce.buyer_id`, ≤ 128 chars in the collector's
3144
+ * schema) and never puts anything else in its place: an email, a name, an
3145
+ * over-long value are dropped — not hashed client-side, not cut (a cut hash
3146
+ * is another identity).
3147
+ */
3148
+ declare const BUYER_REF_MAX_LENGTH = 128;
3149
+ /** The reference when `value` is one; `undefined` for anything else (never an email). */
3150
+ declare function normalizeBuyerRef(value: unknown): string | undefined;
3151
+
2559
3152
  /**
2560
3153
  * SDK version reported in analytics events (`sdk_version`) and available
2561
3154
  * to consumers. Kept as a constant (not a package.json import) so the
@@ -2563,7 +3156,7 @@ declare const Vio: VioFacade;
2563
3156
  *
2564
3157
  * RELEASE CHECKLIST: bump together with package.json (see CONTRIBUTING).
2565
3158
  */
2566
- declare const SDK_VERSION = "0.24.0";
3159
+ declare const SDK_VERSION = "0.25.0";
2567
3160
 
2568
3161
  /**
2569
3162
  * Vipps MobilePay's own button, the way the design guidelines ask for it.
@@ -2592,4 +3185,4 @@ declare function vippsButtonAttributes(verb?: 'buy' | 'pay' | 'express' | 'conti
2592
3185
  language?: string;
2593
3186
  }): Record<string, string>;
2594
3187
 
2595
- export { ADD_ITEM_MUTATION, type AddManualOptions, type AddProductOptions, type AnalyticsCommerce, type AnalyticsContext, type AnalyticsEventName, type AnalyticsItemInput, AnalyticsManager, type AnalyticsStartOptions, type ApplePayConfig, type ApplePayResult, type Attribution, type BootstrapCampaign, type BootstrapCampaignEntry, type BootstrapCampaignStatus, type BootstrapResponse, type BootstrapSlot, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, type CampaignHint, type CartChangeDetail, type CartLineItem, type CartLineOrigin, CartManager, type CartOrigin, type CartQueryOptions, type CartSlice, type CheckoutAddress, type CheckoutChangeDetail, CheckoutManager, type CheckoutState, CommerceClient, Configuration, type ContentRef, type CreatePaymentVippsResponse, type CreatePaymentVippsVariables, DELETE_ITEM_MUTATION, type Environment, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, type GetVippsStatusVariables, type ImageSize, type ManifestLocation, type ManifestRequest, type ManifestResult, type PaymentCompleteDetail, type PaymentMethod, type PaymentSelectDetail, type PaymentSuccessDetail, type PaymentSuccessResult, type PlacementComponent, type Product, type ProductCategory, type ProductImage, type ProductOption, type ProductPrice, type ProductVariant, type ResolvedConfig, SDK_VERSION, type SlotInput, type SlotKind, type Sponsor, type SponsorCartState, type SponsorCommerceBlock, type TrackOptions, UPDATE_CHECKOUT_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, type VioConfig, type VioItem, type VioPlatform, type VippsPaymentState, type VippsStatusResult, activeCampaignEntry, addItem, autoSlotDisplayName, autoSlotId, campaignCandidates, cartSlicesFrom, checkApplePayAvailability, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, ensureVippsButton, executeCartGraphQL, formatPrice, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, lineOrigins, normalizeOrigin, normalizeScope, primaryImageUrl, purchaseFromPaymentSuccess, resolveCampaignId, resolveContent, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes };
3188
+ export { ADD_ITEM_MUTATION, type AddManualOptions, type AddProductOptions, type AnalyticsCommerce, type AnalyticsContext, type AnalyticsEventName, type AnalyticsItemInput, AnalyticsManager, type AnalyticsStartOptions, type ApplePayConfig, type ApplePayResult, type Attribution, BUYER_REF_MAX_LENGTH, type BootstrapCampaign, type BootstrapCampaignEntry, type BootstrapCampaignStatus, type BootstrapResponse, type BootstrapSlot, CHECKOUT_ATTRIBUTION_CAPS, CLIENT_LANDING_URL_MAX_LENGTH, CONTENT_URL_MAX_LENGTH, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_CHECKOUT_WITH_ATTRIBUTION_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, type CampaignHint, type CartChangeDetail, type CartLineItem, type CartLineOrigin, CartManager, type CartOrigin, type CartQueryOptions, type CartSlice, type CheckoutAddress, type CheckoutAttributionInput, type CheckoutAttributionWire, type CheckoutChangeDetail, CheckoutManager, type CheckoutManagerDeps, type CheckoutReference, type CheckoutState, type ClientContext, CommerceClient, Configuration, type ContentRef, type CreatePaymentVippsResponse, type CreatePaymentVippsVariables, DELETE_ITEM_MUTATION, type EntryContextWire, type Environment, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, GRAPHQL_INT_MAX, type GatewayFeature, type GetVippsStatusVariables, type ImageSize, LINE_ORIGIN_CAPS, type Landing, type LineOriginWire, type LineOriginWireOptions, type ManifestLocation, type ManifestRequest, type ManifestResult, type OriginPage, type PaymentCompleteDetail, type PaymentMethod, type PaymentSelectDetail, type PaymentSuccessDetail, type PaymentSuccessResult, type PlacementComponent, type Product, type ProductCategory, type ProductImage, type ProductOption, type ProductPrice, type ProductVariant, type RegisterSlotOptions, type ResolvedConfig, SDK_VERSION, type SlotInput, type SlotKind, type Sponsor, type SponsorCartState, type SponsorCommerceBlock, type SurfacePrivacyMode, type SurfaceResponse, type TrackOptions, 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, type VioConfig, type VioItem, type VioPlatform, type VippsPaymentState, type VippsStatusResult, activeCampaignEntry, addItem, autoSlotDisplayName, autoSlotId, buyerRefOf, campaignCandidates, capContentUrl, cartSlicesFrom, checkApplePayAvailability, checkoutAttributionOf, componentKindOf, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, dominantCampaignOf, ensureVippsButton, entryContextOf, executeCartGraphQL, formatPrice, gatewayHas, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCheckoutReference, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, isUnknownFeatureError, isUnknownTokenError, landingUrl, lineOriginWire, lineOrigins, normalizeBuyerRef, normalizeOrigin, normalizeScope, normalizeSlotKind, originAndPath, primaryImageUrl, purchaseFromPaymentSuccess, resetGatewayFeatures, resolveCampaignId, resolveClientContext, resolveContent, resolveLanding, resolveSlotCampaign, sameOrigin, showApplePaySheet, sponsorCampaignEntry, storedLanding, toCampaignId, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes, withGatewayFeature };