@vio-live/web-sdk 0.24.0 → 0.26.0

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