@vio-live/web-sdk 0.28.0 → 0.32.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.
- package/CHANGELOG.md +243 -2
- package/README.md +175 -13
- package/dist/{chunk-Q2NALS7P.js → chunk-C5Q6W4B2.js} +1453 -199
- package/dist/chunk-C5Q6W4B2.js.map +1 -0
- package/dist/{chunk-AFJ5M53F.js → chunk-IR4XYTZL.js} +707 -92
- package/dist/chunk-IR4XYTZL.js.map +1 -0
- package/dist/chunk-QGJUBVH3.js +3 -0
- package/dist/{chunk-67QESEV2.js.map → chunk-QGJUBVH3.js.map} +1 -1
- package/dist/core/index.d.ts +226 -6
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/{tokens-y0KzN7bP.d.ts → tokens-D1DCVzOf.d.ts} +331 -13
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-67QESEV2.js +0 -3
- package/dist/chunk-AFJ5M53F.js.map +0 -1
- package/dist/chunk-Q2NALS7P.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,9 +14,250 @@ How to keep this file: see [CONTRIBUTING → Changelog](CONTRIBUTING.md#changelo
|
|
|
14
14
|
The details of each feature live in the [README](README.md); this file says *what*
|
|
15
15
|
and *when*, and links there.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## 0.32.0 — 2026-10-28
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
> First version published to npm since 0.29.0: **0.30.0 and 0.31.0 were never published on their own** — their
|
|
20
|
+
> changes (below) ship in this version too. A host on 0.29.0 should read the three sections.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- **Options for the product detail** (README → *Options of the detail and the product cards*):
|
|
24
|
+
`gallery="carousel"` (every photo in one swipeable row with dots, no thumbnails),
|
|
25
|
+
`description-mode` (`preview` · `folded` · `open`), `highlights`, `delivery-time`,
|
|
26
|
+
`carrier-logos`; theme keys `detailGallery`, `detailCarouselPeek`, `detailDescriptionMode`,
|
|
27
|
+
`detailDescriptionPreviewLines`, `detailHighlightsDisplay`, `detailShippingReturnsDisplay`.
|
|
28
|
+
- **The brand's promises** under the buy zone (icon, title, line of text — up to three), from the
|
|
29
|
+
sponsor block's `highlights` (dashboard Settings → Brand) unless the host passes its own.
|
|
30
|
+
- **«Frakt & retur»** fold after «Beskrivelse»: one row per carrier (its logo from Commerce's
|
|
31
|
+
`logo_url`, or `carrier-logos`, or a line icon by the kind of delivery), how it delivers, the
|
|
32
|
+
price; the delivery time the brand wrote on the rate («Leveringstid 1-5 dager»); the right of
|
|
33
|
+
withdrawal and the brand's pages.
|
|
34
|
+
- **Product cards:** `<vio-product-carousel layout="list">` (one under the other),
|
|
35
|
+
`show-description` / `descriptions` (the catalogue's text or the editor's own per product),
|
|
36
|
+
`button-style` (`icon` · `bar` · `hidden`) and `button-label` (the Vev card's «Kjøp» bar); card
|
|
37
|
+
look through theme keys `card*` and `--vio-card-*`.
|
|
38
|
+
- `commerce.channel.product.getShippingRates` returns each rate's `description` and `logoUrl`.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- **The detail's order is the same on every screen**: sizes first in the buy zone, «Frakt fra …»
|
|
42
|
+
under them, stock on the name's line, «Beskrivelse» at the very bottom. The buy zone no longer
|
|
43
|
+
pins itself on desktop (`detailBuyPosition: 'sticky'` brings that back).
|
|
44
|
+
- **«Beskrivelse» shows a preview** (the first three lines and «Les mer») instead of being folded
|
|
45
|
+
shut. `description-mode="folded"` keeps the old behaviour.
|
|
46
|
+
- **The sheets close with a pull down from anywhere** while their content is at the top, not only
|
|
47
|
+
from the grab bar or the top bar (detail, cart, checkout).
|
|
48
|
+
|
|
49
|
+
### ⚠️ Upgrade notes
|
|
50
|
+
- Rates are asked with `logo_url`; a gateway without the field is asked again without it once
|
|
51
|
+
(and not again on that page), so the rates never go missing over a logo.
|
|
52
|
+
- A host that styled `details.description-block` for «Beskrivelse» now gets
|
|
53
|
+
`.description-preview` (`part="description"` unchanged).
|
|
54
|
+
|
|
55
|
+
## 0.31.0 — 2026-10-28
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
- **Delivery and returns under the price in the product detail.** It reads «Frakt fra X · 14
|
|
59
|
+
dagers angrerett», where X is the product's cheapest shipping rate for the page's market (the
|
|
60
|
+
same `product_shipping` rates the backend cart offers). A cheapest rate of 0 reads «Gratis frakt».
|
|
61
|
+
With no known rate, only «14 dagers angrerett» is shown (`part="delivery-note"`). The rates come
|
|
62
|
+
from a separate query (`commerce.channel.product.getShippingRates`), so a failure there only
|
|
63
|
+
loses this line.
|
|
64
|
+
- **The cart drawer shows shipping and the total including it.** It uses the rate chosen on the
|
|
65
|
+
server cart, or the cheapest rate offered. Without a known rate it reads «Frakt: Beregnes i
|
|
66
|
+
kassen» and shows no total (`part="cart-shipping"`, `part="cart-total"`).
|
|
67
|
+
- **«Lagt i handlekurven» after «Legg i handlekurv».** The detail closes and a small toast offers
|
|
68
|
+
**«Til kassen»** and **«Fortsett å handle»** (`part="added-toast"`). «Til kassen» dispatches
|
|
69
|
+
`vio:checkout-open` for that brand, the same event the cart's «Til kassen» sends. It does nothing
|
|
70
|
+
when the cart was emptied in the meantime. The toast closes by itself after 8 s.
|
|
71
|
+
- **A line under «Kjøp nå med Vipps» in the product detail:** «Vipps fyller inn navn og adresse –
|
|
72
|
+
du godkjenner og betaler i appen» (`part="vipps-note"`). It is shown only when that button goes
|
|
73
|
+
straight to the app: Express is on for the brand and the brand's cart is empty. In other cases
|
|
74
|
+
the button opens the checkout first.
|
|
75
|
+
### Changed
|
|
76
|
+
- **The product detail no longer picks a size for the shopper.** Only an option with a single
|
|
77
|
+
value is preselected. The chosen value is shown beside the option's name («Størrelse: M») and
|
|
78
|
+
on its pill. Every buy button (Vipps, «Legg i handlekurv», «Kjøp nå», Stripe, Klarna, Apple Pay)
|
|
79
|
+
checks for a missing choice first: it shows «Velg størrelse» (or «Velg farge», etc.) and adds or
|
|
80
|
+
pays nothing. «Få igjen — N stk» now counts only the variant actually chosen. Before a size is
|
|
81
|
+
chosen it says «På lager» if any size is in stock.
|
|
82
|
+
- **Two Vipps paths.** The product detail's «Kjøp nå med Vipps» stays **Express**. The
|
|
83
|
+
**cart's** Vipps button now opens the checkout with Vipps preselected. It no longer starts
|
|
84
|
+
Express or hands over to Vipps. **The checkout's Vipps is always the standard payment**, even
|
|
85
|
+
when the brand has Express on:
|
|
86
|
+
- the address form, the delivery and its price are required before paying, as for Stripe;
|
|
87
|
+
- the summary counts our shipping rate, so «Velges i Vipps» is gone;
|
|
88
|
+
- the payment is `startVippsPayment(sponsorId, form, { express: false })`, which now puts the
|
|
89
|
+
form's complete address on the checkout's shipping and billing address.
|
|
90
|
+
|
|
91
|
+
`COLLECTS_OWN_ADDRESS` no longer lists Vipps. See
|
|
92
|
+
[README → Two Vipps paths](README.md#the-hand-over-to-vipps-028).
|
|
93
|
+
- **«Mobilnummer» in the checkout's address form.** It is required only for the standard Vipps
|
|
94
|
+
payment, and optional for every other method; it never blocks their buttons or widgets.
|
|
95
|
+
- It accepts any market's number. A `+`/`00` prefix sets the calling code; without one, the
|
|
96
|
+
shipping country does (NO 47, SE 46, DK 45, FI 358, DE 49, GB 44…), falling back to the
|
|
97
|
+
page's country. A trunk 0 is dropped.
|
|
98
|
+
- A number that cannot be read shows «Skriv et gyldig mobilnummer…».
|
|
99
|
+
- It is sent as `phone` (national digits) + `phone_code` (digits, no plus) on the shipping and
|
|
100
|
+
billing address of every method that takes our form. `CheckoutAddress.phone` is new.
|
|
101
|
+
- **The checkout's Vipps refuses to start without the delivery saved.** If the chosen rate cannot
|
|
102
|
+
be saved on the cart, the Vipps or Stripe-link payment does not start and the checkout says
|
|
103
|
+
«Kunne ikke lagre fraktmetoden. Prøv igjen.». This covers a failed save and a rate without a
|
|
104
|
+
supplier. `startVippsPayment(…, { express: false })` also throws before creating anything when
|
|
105
|
+
the address is incomplete or has no usable mobile.
|
|
106
|
+
|
|
107
|
+
### Fixed
|
|
108
|
+
- **«Velg størrelse» no mueve las tallas.** El aviso sale en la misma línea que el nombre de la opción, así que tocar comprar sin talla ya no desplaza los botones bajo el dedo.
|
|
109
|
+
- **No checkout and no payment for an empty cart or a total of 0 kr.** `Vio.checkout.open()`
|
|
110
|
+
throws for a cart that totals 0, as it already did for an empty one. Vipps (`startVippsPayment`,
|
|
111
|
+
`startVippsExpress`), the Stripe link, native Stripe and Adyen read the cart from the server and
|
|
112
|
+
refuse to create a checkout or a payment when it is empty or 0 kr. The buttons check the same
|
|
113
|
+
thing first: the cart's «Til kassen» is disabled, the checkout's pay buttons are disabled and
|
|
114
|
+
say «Handlekurven er tom.», and the detail shows no Vipps or Apple Pay button for a product at 0 kr.
|
|
115
|
+
- **Desktop: the product detail's pinned buy bar no longer covers the size picker.** The option
|
|
116
|
+
pickers now sit at the top of the buy zone (`part="buy-options"`), so they move with the
|
|
117
|
+
buttons whether the zone is static or sticky.
|
|
118
|
+
- **No buttons for payment methods the brand does not have while its methods load.** The
|
|
119
|
+
product detail and the cart drawer show a neutral placeholder (`part="buy-pending"` /
|
|
120
|
+
`.methods-pending`) until `getAvailablePaymentMethods` answers. They used to show every method,
|
|
121
|
+
so a Vipps-only brand flashed Stripe and Klarna. «Legg i handlekurv» and «Til kassen» are there
|
|
122
|
+
from the start. A failed lookup still shows every button, as before.
|
|
123
|
+
- **Storage never blocks add to cart or checkout (in-app browsers, private modes).** Every read
|
|
124
|
+
and write of `localStorage`/`sessionStorage` goes through one guard, `src/core/safe-storage.ts`.
|
|
125
|
+
A storage property that throws on access, a `null` storage or a refused write now falls back to
|
|
126
|
+
the page's memory. This covers the cart, the customer session id, the market's
|
|
127
|
+
currency/country/locale and the Klarna return. Before, `typeof localStorage` outside a `try`
|
|
128
|
+
could throw while the SDK started. Without storage, every browser also sent the same customer
|
|
129
|
+
session id, `cs_session_default`. It is now one id per page.
|
|
130
|
+
|
|
131
|
+
### ⚠️ Upgrade notes
|
|
132
|
+
- The detail's buy buttons stay enabled before a size is chosen. Tapping one shows the prompt
|
|
133
|
+
instead of buying. A host that clicks `.add-to-cart` from a script needs a value in every option
|
|
134
|
+
first.
|
|
135
|
+
- A host's `vio:checkout-open` handler that calls `Vio.checkout.open()` must still catch the throw
|
|
136
|
+
(it already threw for an empty cart). It now also throws for a cart of 0 kr.
|
|
137
|
+
- Host CSS that placed `::part(option-group)` relative to `::part(buy)` should be checked: the
|
|
138
|
+
option groups are now inside `buy`.
|
|
139
|
+
- A host that already answers `vio:added-to-cart` with its own UI (for example, opening its cart
|
|
140
|
+
drawer) can turn the new toast off with `<vio-product-detail no-added-toast>`. The toast's
|
|
141
|
+
«Til kassen» relies on the host's `vio:checkout-open` handler, like the cart's does.
|
|
142
|
+
- **Backend (shopcart):** the checkout's Vipps payment is now `express: false` with the address
|
|
143
|
+
already on the checkout. shopcart must not request the Vipps profile on these standard
|
|
144
|
+
payments, and must carry the address `phone`/`phone_code` through to the order. Until that is
|
|
145
|
+
deployed, check a QA order to confirm the phone arrives.
|
|
146
|
+
- Hosts that pre-fill the checkout form through `Vio.checkout.setAddress()` should include
|
|
147
|
+
`phone`, or the shopper has to type it when paying with Vipps. Other methods do not need it.
|
|
148
|
+
- **The cart's Vipps button now opens the checkout through `vio:checkout-open`**, with
|
|
149
|
+
`paymentMethod: 'vipps'`. A host without a handler for that event loses direct Vipps from the
|
|
150
|
+
cart. The `vio:payment-redirect-*` events with `source: 'cart'` are gone; those with
|
|
151
|
+
`'detail'` and `'checkout'` remain.
|
|
152
|
+
- **`startVippsPayment(sponsorId, form, { express: false })` writes the full address**, including
|
|
153
|
+
the mobile, to the checkout's shipping and billing address before it creates the payment.
|
|
154
|
+
Without the flag, or with `express: true`, nothing changes.
|
|
155
|
+
- `Vio.checkout.open()` throws on a cart that totals 0, as well as on an empty one.
|
|
156
|
+
- The «Lagt i handlekurven» toast is on by default. Use `<vio-product-detail no-added-toast>` to
|
|
157
|
+
turn it off.
|
|
158
|
+
|
|
159
|
+
## 0.30.0 — 2026-10-27
|
|
160
|
+
|
|
161
|
+
### Added
|
|
162
|
+
- **Internal traffic** (ADR-0041): opening any page with **`?vio_internal=1`** marks the browser
|
|
163
|
+
(`localStorage`, `vio.internal.v1`) and `?vio_internal=0` clears it; read once at `Vio.init()`.
|
|
164
|
+
While marked, every analytics event carries **`client.internal: true`** and the collector's
|
|
165
|
+
reads leave it out. **`Vio.analytics.setInternal(boolean)`** / `Vio.analytics.isInternal` for
|
|
166
|
+
hosts; one `console.info` when marked. See
|
|
167
|
+
[README → Internal traffic](README.md#internal-traffic--mark-your-browser-as-a-tester-unreleased).
|
|
168
|
+
|
|
169
|
+
### Changed
|
|
170
|
+
- **Stripe.js (~300 KB) is downloaded only for a brand that uses Stripe.** It used to load on
|
|
171
|
+
every page as soon as the SDK started, to have Apple Pay ready. Now it loads when the brand's
|
|
172
|
+
payment methods come back with a Stripe key (or when the host passes `stripePublishableKey`
|
|
173
|
+
to `Vio.init`). A brand that only takes Vipps never downloads it. Apple Pay still has it before
|
|
174
|
+
the tap: its button waits for the same key.
|
|
175
|
+
- **`component_impression` of `<vio-product-carousel>` carries `commerce.items`** — the products
|
|
176
|
+
shown, like its `view_item_list` — so the collector can count per-product impressions
|
|
177
|
+
(ADR-0041). The impression is observed once the products have loaded (the 1 s dwell starts
|
|
178
|
+
then); a failed fetch still counts it, without items. `Vio.analytics.observeImpression()` takes
|
|
179
|
+
a `commerce` getter, read when the impression fires. See
|
|
180
|
+
[README → Impressions carry the products shown](README.md#impressions-carry-the-products-shown-unreleased).
|
|
181
|
+
- Events never carry more than 100 `commerce.items` (the collector rejects the whole event above
|
|
182
|
+
its cap).
|
|
183
|
+
|
|
184
|
+
### ⚠️ Upgrade notes
|
|
185
|
+
- `client.internal` needs the collector that knows the field; an older one strips it (the event is
|
|
186
|
+
kept, just not marked). Nothing to do on the host.
|
|
187
|
+
|
|
188
|
+
## 0.29.0 — 2026-10-24
|
|
189
|
+
|
|
190
|
+
### Added
|
|
191
|
+
- **`vio:cart-updated`** on `document` — `{ sponsorId, cartId, items, itemCount, subtotal, currency, source }`,
|
|
192
|
+
`source: 'server'` when the cart was just read from the server (right before a checkout is
|
|
193
|
+
created or a payment link minted, and when the drawer or the checkout opens), `'storage'` when
|
|
194
|
+
another tab of the site changed it. For hosts that draw their own cart or badge.
|
|
195
|
+
`Vio.cart.refreshFromServer(sponsorId)` asks on demand. See
|
|
196
|
+
[README → What the shopper pays is what the page shows](README.md#what-the-shopper-pays-is-what-the-page-shows--the-cart-the-server-the-other-tabs).
|
|
197
|
+
- **`add_payment_info`** (analytics, GA4 name) the moment a backend checkout exists for a payment
|
|
198
|
+
— Vipps from the detail, the cart or the checkout, a Stripe payment link or Element, Klarna,
|
|
199
|
+
Apple Pay, Nexi, Kustom, Qliro, Walley, Adyen — with `commerce.checkout_id`, `payment_method`,
|
|
200
|
+
the cart's lines and the same context as `begin_checkout` (per campaign like it). Once per
|
|
201
|
+
checkout per page load, none when the checkout could not be created, and by beacon BEFORE the
|
|
202
|
+
page leaves for Vipps or a Stripe link. It is how a paid order whose `purchase` never came back
|
|
203
|
+
keeps its session, page and slot (ADR-0039; order 4128, 2026-10-23). The checkout manager
|
|
204
|
+
announces it on `window` as **`vio:checkout-created`** `{ sponsorId, checkoutId, paymentMethod,
|
|
205
|
+
campaignId?, redirect }`. See [README → `add_payment_info`](README.md#add_payment_info--the-checkout-linked-to-this-session-0290).
|
|
206
|
+
|
|
207
|
+
### Changed
|
|
208
|
+
- **«Kjøp nå med Vipps» on the product detail goes straight to Vipps only when the cart is
|
|
209
|
+
empty.** With lines already in the cart, the product is added and the checkout opens first
|
|
210
|
+
(`vio:checkout-open`, Vipps preselected, no `vio:payment-redirect-*` events), every line and
|
|
211
|
+
the total on screen. Before, the tap paid the WHOLE cart with only the Vipps page saying so
|
|
212
|
+
(Mote & Livsstil, 2026-10-23: a jumper left in the cart 13 minutes earlier was paid along
|
|
213
|
+
with the top bought «nå»). The cart's own Vipps button is unchanged: it pays the lines it shows.
|
|
214
|
+
- **Apple Pay on the product detail shows what it charges.** The sheet's amount is fixed before
|
|
215
|
+
the tap and was the product's price, while the charge was the sponsor's whole cart. The sheet
|
|
216
|
+
now opens only on an empty cart, prepared for the product × the chosen quantity (it is prepared
|
|
217
|
+
again when the quantity or variant changes); with lines already in the cart — or while the
|
|
218
|
+
sheet for a new quantity is still being prepared — the product is added and the checkout opens
|
|
219
|
+
(`vio:checkout-open` with `paymentMethod: 'apple-pay'`), where Apple Pay charges the total shown.
|
|
220
|
+
|
|
221
|
+
### Fixed
|
|
222
|
+
- **The lines the shopper pays for are the lines the page shows.** The checkout, and the
|
|
223
|
+
Vipps / Stripe page, are built on the backend cart — every line in it — while the drawer
|
|
224
|
+
and the checkout summary drew this tab's memory. Now the SDK reads the server's cart right
|
|
225
|
+
before a checkout is created or a payment link minted, and when `<vio-cart>` / `<vio-checkout>`
|
|
226
|
+
open, and draws that; an open checkout recomputes its lines and total when the cart changes
|
|
227
|
+
under it.
|
|
228
|
+
- **Other tabs of the site are heard.** The carts are shared through `localStorage` but each
|
|
229
|
+
tab read them once, at load: a payment in one tab left the others showing — and adding to —
|
|
230
|
+
a cart that was already paid; a line added in one tab was missing from the others until a
|
|
231
|
+
reload. Now a tab adopts what another tab wrote (`storage` event), except for a sponsor it is
|
|
232
|
+
mid-add for.
|
|
233
|
+
- **Vipps Express: the checkout no longer adds our shipping to the total.** The delivery is
|
|
234
|
+
chosen and paid inside Vipps, but with Vipps selected the checkout still added the seller's
|
|
235
|
+
first rate (Careless QA, 2026-10-24: «Frakt – Default Shipping» 10 kr, 1 308 kr on screen and
|
|
236
|
+
on the button, 1 298 kr in Vipps). With Vipps as the method and Express on for the seller,
|
|
237
|
+
the summary says «Frakt: Velges i Vipps» and the total and «Betal … med Vipps» are the items.
|
|
238
|
+
Vipps without Express, and every other method, still add the rate as before.
|
|
239
|
+
- **The `purchase` of a paid return is sent, not left in the queue** (2026-10-24). Order 4128
|
|
240
|
+
(Careless, prod, 2026-10-23 22:40, after two abandoned trips to Vipps): the shopper
|
|
241
|
+
paid, came back, saw the confirmation, and the brand's panel never counted the sale — Commerce
|
|
242
|
+
had its `order_paid`, the collector no `purchase`. The return was read as paid; the one way the
|
|
243
|
+
code loses that purchase is the queue: it was built, with the order number, into a queue sent
|
|
244
|
+
every 5 s, and leaving the page right away (back to the Vipps app, the home screen) beaconed
|
|
245
|
+
only a purchase still waiting for its number — a phone that discards a hidden tab never runs
|
|
246
|
+
the next tick. Now the purchase is sent as soon as it is built, and when the page hides
|
|
247
|
+
everything still queued leaves by beacon.
|
|
248
|
+
|
|
249
|
+
### ⚠️ Upgrade notes
|
|
250
|
+
- **The collector must accept `add_payment_info`** before it is counted: one that does not know the
|
|
251
|
+
name rejects that event alone (`202` with `rejected`), the rest of the batch is kept. Nothing to
|
|
252
|
+
do in the host.
|
|
253
|
+
- A host that handles `vio:checkout-open` only from the cart: the product detail now emits it
|
|
254
|
+
too when «Kjøp nå med Vipps» or Apple Pay is tapped on a non-empty cart (it already did when
|
|
255
|
+
Express was off for the seller), now also with `paymentMethod: 'apple-pay'`. Check that your
|
|
256
|
+
handler opens `<vio-checkout>` for the detail's event.
|
|
257
|
+
- A host with its own cart drawer or badge: listen to `vio:cart-updated` (or `Vio.cart`'s
|
|
258
|
+
`change`) — the lines can now change without a tap in that tab.
|
|
259
|
+
- One more `GetCart` per checkout creation / payment link and per opening of the drawer or the
|
|
260
|
+
checkout.
|
|
20
261
|
|
|
21
262
|
## 0.28.0 — 2026-10-22
|
|
22
263
|
|
package/README.md
CHANGED
|
@@ -487,11 +487,81 @@ could never be netted.
|
|
|
487
487
|
arrives after the event left with the checkout id adds no second event. `ts`
|
|
488
488
|
and `event_id` are fixed at the payment, whenever the event leaves; a purchase
|
|
489
489
|
split per campaign waits once and every slice carries the same number.
|
|
490
|
+
- **Sent at once.** A `purchase` does not wait for the queue's 5 s tick: it is
|
|
491
|
+
sent as soon as it is built, and when the page hides (`visibilitychange`, the
|
|
492
|
+
last thing a phone is sure to say before it discards a tab) whatever is still
|
|
493
|
+
queued leaves by beacon (0.29.0; order 4128 had been recorded and never sent).
|
|
490
494
|
- **A host with its own sink** (Vev) reads the same ids from the detail with
|
|
491
495
|
`purchaseIdsOf(detail)` → `{ orderId, checkoutId, orderIdKind }` (or
|
|
492
496
|
`purchaseFromPaymentSuccess(detail)`, exported from `/core`), and waits for the
|
|
493
497
|
number the same way through `Vio.checkout.getOrderReference(checkoutId)`.
|
|
494
498
|
|
|
499
|
+
### `add_payment_info` — the checkout, linked to this session (0.29.0)
|
|
500
|
+
|
|
501
|
+
The moment a backend checkout exists for a payment — Vipps (from the product
|
|
502
|
+
detail, the cart or the checkout), a Stripe payment link or Element, Klarna, Apple
|
|
503
|
+
Pay, Nexi, Kustom, Qliro, Walley, Adyen — the SDK emits **`add_payment_info`**
|
|
504
|
+
(the GA4 name) with **`commerce.checkout_id`**, **`payment_method`**, the cart's
|
|
505
|
+
lines (`items`, `value`, `currency`) and the same context as `begin_checkout`
|
|
506
|
+
(sponsor, campaign, slot, content), split per campaign like it. Once per checkout
|
|
507
|
+
per page load; nothing when the checkout could not be created. When the page is
|
|
508
|
+
about to leave for the provider (Vipps, a Stripe payment link) it goes out by
|
|
509
|
+
beacon first.
|
|
510
|
+
|
|
511
|
+
Why (ADR-0039): Commerce's `order_paid` is what says a sale happened, and the
|
|
512
|
+
browser's `purchase` can be missing (a closed tab, a phone that discards the page
|
|
513
|
+
on the way back). The collector attributes an `order_paid` without a `purchase` to
|
|
514
|
+
the session, page and slot of the `add_payment_info` with the same `checkout_id`.
|
|
515
|
+
|
|
516
|
+
The checkout manager announces each checkout on `window` as
|
|
517
|
+
**`vio:checkout-created`** — `{ sponsorId, checkoutId, paymentMethod, campaignId?,
|
|
518
|
+
redirect }` — which is what analytics listens to; a host with its own sink can
|
|
519
|
+
listen to it too. With `dataLayer: true` it is pushed as GA4's `add_payment_info`
|
|
520
|
+
with `payment_type`.
|
|
521
|
+
|
|
522
|
+
### Impressions carry the products shown (unreleased)
|
|
523
|
+
|
|
524
|
+
`<vio-product-carousel>`'s **`component_impression`** (≥50% visible for ≥1 s, once
|
|
525
|
+
per session and carousel) carries the products it shows in **`commerce.items`**
|
|
526
|
+
(`product_id`, `name`, `price`) and `commerce.currency` — the same lines as its
|
|
527
|
+
`view_item_list`. The collector counts the per-product "saw" step of the funnel
|
|
528
|
+
from them (ADR-0041). While the products load nothing is observed: the 1 s dwell
|
|
529
|
+
starts once they are on screen. A carousel whose fetch failed (slotted fallback)
|
|
530
|
+
still counts its impression, without items. A host calling
|
|
531
|
+
`Vio.analytics.observeImpression(el, context, { commerce: () => ({ items, currency }) })`
|
|
532
|
+
does the same for its own elements: the getter is read when the impression fires.
|
|
533
|
+
|
|
534
|
+
### Internal traffic — mark your browser as a tester (unreleased)
|
|
535
|
+
|
|
536
|
+
Our own visits (testers, the team, agents) must not count in the dashboard. Mark
|
|
537
|
+
**each browser you test with** once:
|
|
538
|
+
|
|
539
|
+
1. Open any page of the site that runs the SDK with **`?vio_internal=1`** added to
|
|
540
|
+
the URL — e.g. `https://example.com/article?vio_internal=1` (or
|
|
541
|
+
`…?utm_source=x&vio_internal=1` when the URL already has a query).
|
|
542
|
+
2. Open the browser's console: the SDK says once
|
|
543
|
+
`[Vio.analytics] this browser is marked as internal traffic …`.
|
|
544
|
+
3. That's it. The mark is remembered in this browser (`localStorage`,
|
|
545
|
+
`vio.internal.v1`, per site) for every page and every later visit, and every
|
|
546
|
+
analytics event it sends carries **`client.internal: true`**; the collector's
|
|
547
|
+
reads leave those events out.
|
|
548
|
+
|
|
549
|
+
To **undo** it, open any page with **`?vio_internal=0`**. Things to know:
|
|
550
|
+
|
|
551
|
+
- The mark is per browser and per site: Safari and Chrome on the same phone, a
|
|
552
|
+
private window, or another site are marked separately. Clearing the site's data
|
|
553
|
+
clears the mark.
|
|
554
|
+
- In-app browsers (Instagram, Facebook) and private windows may not keep
|
|
555
|
+
`localStorage`: there, only the page opened with `?vio_internal=1` is marked.
|
|
556
|
+
Prefer a regular browser for testing.
|
|
557
|
+
- The URL is read once per page load (at `Vio.init()`), not on client-side
|
|
558
|
+
navigation.
|
|
559
|
+
- The mark is the tester's own choice, not a tracking id: it is written even
|
|
560
|
+
before consent (`requireConsent`).
|
|
561
|
+
- A host can mark from code: **`Vio.analytics.setInternal(true | false)`**
|
|
562
|
+
(`Vio.analytics.isInternal` reads it). `resolveInternal()` / `INTERNAL_KEY` are
|
|
563
|
+
exported from `/core`.
|
|
564
|
+
|
|
495
565
|
## Sponsor identity — name and logo
|
|
496
566
|
|
|
497
567
|
The elements say **who sells**: the sponsor's (brand's) display name and logo.
|
|
@@ -623,7 +693,7 @@ or on the element (`vio-product-detail { --vio-detail-logo-height: 48px; }`).
|
|
|
623
693
|
| `detailOptionHeight` / `detailOptionGap` | `--vio-detail-option-height` / `--vio-detail-option-gap` | `36px` / `6px` | Size / option buttons (height is also their min width) |
|
|
624
694
|
| `detailOptionRadius` / `detailOptionWrap` | `--vio-detail-option-radius` / `--vio-detail-option-wrap` | `0` / `wrap` | `nowrap` = one line that scrolls sideways |
|
|
625
695
|
| `detailButtonHeight` / `detailButtonRadius` | `--vio-detail-button-height` / `--vio-detail-button-radius` | `52px` / `0` | Quantity stepper and «Legg i handlekurv» |
|
|
626
|
-
| `detailBuyPosition` / `detailBuyShadow` | `--vio-detail-buy-position` / `--vio-detail-buy-shadow` |
|
|
696
|
+
| `detailBuyPosition` / `detailBuyShadow` | `--vio-detail-buy-position` / `--vio-detail-buy-shadow` | `static` | `sticky` pins the buy zone (Vipps, quantity, add to cart) to the bottom of the panel while scrolling |
|
|
627
697
|
| `cartWidth` / `checkoutWidth` | `--vio-cart-width` / `--vio-checkout-width` | `440px` | Drawer widths (desktop) |
|
|
628
698
|
| `confirmLogoHeight` / `confirmTitleSize` / `confirmCheckBg` | `--vio-confirm-logo-height` / `--vio-confirm-title-size` / `--vio-confirm-check-bg` | `40px` / `24px` / green | The confirmation |
|
|
629
699
|
| `redirectBg` / `redirectSpinner` | `--vio-redirect-bg` / `--vio-redirect-spinner` | translucent white / Vipps orange | The «Åpner Vipps …» overlay |
|
|
@@ -639,10 +709,14 @@ Also settable as CSS only: `--vio-detail-logo-max-width` (`220px`), `--vio-detai
|
|
|
639
709
|
**`::part` names**, for what the properties do not cover:
|
|
640
710
|
|
|
641
711
|
- `<vio-product-detail>`: `topbar`, `sponsor`, `close`, `gallery`, `image`, `thumbs`, `thumb`
|
|
642
|
-
(+ `thumb-active`), `thumbs-arrow`, `info`, `brand`, `title`, `price`, `
|
|
643
|
-
`
|
|
644
|
-
`
|
|
645
|
-
`
|
|
712
|
+
(+ `thumb-active`), `thumbs-arrow`, `info`, `brand`, `title`, `price`, `delivery-note`
|
|
713
|
+
(`delivery-shipping`, `delivery-withdrawal`), `stock`, `buy`, `buy-options`, `option-group`,
|
|
714
|
+
`option-label`, `option-chosen`, `option-prompt`, `options`, `option` (+ `option-selected`),
|
|
715
|
+
`buy-pending`, `buy-vipps`, `vipps-note`, `quantity-row`, `add-to-cart`, `seller-note`,
|
|
716
|
+
`seller-terms`, `seller-refund`, `description`, `highlights`, `highlight`, `shipping-returns`,
|
|
717
|
+
`slide`, `image-dots`, `carousel-arrow`, `added-toast` (`added-toast-checkout`,
|
|
718
|
+
`added-toast-continue`).
|
|
719
|
+
- `<vio-cart>`: `cart-shipping`, `cart-total`.
|
|
646
720
|
- `<vio-checkout>`: `sponsor`, `confirmation`, `confirm-title`, `confirm-sponsor`, `confirm-details`.
|
|
647
721
|
|
|
648
722
|
```css
|
|
@@ -650,14 +724,51 @@ vio-product-detail::part(add-to-cart) { letter-spacing: 0; text-transform: none;
|
|
|
650
724
|
vio-product-detail::part(option-selected) { background: #2b2b2b; }
|
|
651
725
|
```
|
|
652
726
|
|
|
653
|
-
**The detail's order:** the selling brand top left; the
|
|
654
|
-
name
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
the
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
727
|
+
**The detail's order:** the selling brand top left; the images (thumbnails, or a carousel);
|
|
728
|
+
name and stock on one line, the price; the buy zone — the sizes (none preselected; every buy
|
|
729
|
+
button asks «Velg størrelse» first), «Frakt fra X · 14 dagers angrerett», **Vipps first** (when
|
|
730
|
+
the channel has it), quantity + «Legg i handlekurv», the other ways to pay, and under them «Du
|
|
731
|
+
handler direkte hos {brand}.» (the product's brand, never the article's) with links to the
|
|
732
|
+
brand's «Kjøpsvilkår» and «Angrerett og retur» when it gave them (the sponsor block's
|
|
733
|
+
`termsUrl` / `refundPolicyUrl`, https only, new tab); the brand's promises; «Beskrivelse»; and
|
|
734
|
+
«Frakt & retur». The same order on every screen.
|
|
735
|
+
|
|
736
|
+
### Options of the detail and the product cards (0.32)
|
|
737
|
+
|
|
738
|
+
Every one is off or as before unless the host sets it: per element (attribute), or for the
|
|
739
|
+
whole page through `applyVioTheme` (theme key → custom property). The element wins.
|
|
740
|
+
|
|
741
|
+
**`<vio-product-detail>`**
|
|
742
|
+
|
|
743
|
+
| Attribute | Theme key | Values | What |
|
|
744
|
+
|---|---|---|---|
|
|
745
|
+
| `gallery` | `detailGallery` | `thumbs` (default) · `carousel` | `carousel`: every photo in one swipeable row with a dot per photo, no thumbnails — less height. `detailCarouselPeek` (`--vio-detail-carousel-peek`) shows the edge of the next one |
|
|
746
|
+
| `description-mode` | `detailDescriptionMode` | `preview` (default) · `folded` · `open` | «Beskrivelse»: the first lines fading out + «Les mer» (`detailDescriptionPreviewLines`, `3`); only the heading until a tap; or all of it |
|
|
747
|
+
| `highlights` | — | `[{ icon, title, text }]` (JSON) | The brand's promises under the buy zone. By default the brand's own (dashboard Settings → Brand, sponsor block `highlights`); this list replaces them on the page. Icons: `plane heart shirt truck return leaf star shield mailbox home pin clock`, or an image URL. `detailHighlightsDisplay: 'none'` hides the strip |
|
|
748
|
+
| `delivery-time` | — | text, e.g. `2-5 virkedager` | «Leveringstid …» in «Frakt & retur» for a brand whose rates do not say it (each rate's own text in Commerce — «Leveringstid 1-5 dager» — wins) |
|
|
749
|
+
| `carrier-logos` | — | `{ "Bring": "https://…" }` (JSON) | Carrier logos by the name the rate starts with («Bring - Pakke i postkasse»), for rates without their own logo in Commerce (`logo_url`, which wins); otherwise a line icon by the kind of delivery |
|
|
750
|
+
| — | `detailShippingReturnsDisplay` | `block` (default) · `none` | «Frakt & retur»: one row per carrier (logo or icon, carrier, how it delivers, price), the delivery time, the right of withdrawal and the brand's pages |
|
|
751
|
+
|
|
752
|
+
The sheet closes with a pull down from its top bar, or from anywhere while its content is at the top.
|
|
753
|
+
|
|
754
|
+
**`<vio-product-carousel>`** (and its cards, `<vio-product>`)
|
|
755
|
+
|
|
756
|
+
| Attribute | Theme key | Values | What |
|
|
757
|
+
|---|---|---|---|
|
|
758
|
+
| `layout` | `carouselLayout` | `carousel` (default) · `list` | `list`: one card under the other, as wide as the column (`--vio-carousel-list-max-width`, `480px`) |
|
|
759
|
+
| `show-description` | — | boolean | Each card shows its product's description (`cardDescriptionLines`, `4`) |
|
|
760
|
+
| `descriptions` | — | `{ "<productId>": "text" }` (JSON) | The editor's own text for a product, over the catalogue's — shown even without `show-description`; `""` hides that one |
|
|
761
|
+
| `button-style` | — | `icon` (default) · `bar` · `hidden` | The buy button: the round bag on the image, a full-width «Kjøp» bar under the price (as the Vev card), or none. Both add to the cart, or open the product when it has sizes |
|
|
762
|
+
| `button-label` | — | text, default `Kjøp` | The bar's text («Kjøp · 2» once two are in the cart) |
|
|
763
|
+
|
|
764
|
+
Card look, by theme key (custom property): `cardFont`, `cardBorder`, `cardImageAspect`,
|
|
765
|
+
`cardImageFit`, `cardImageBg`, `cardImagePadding`, `cardMetaPadding`, `cardPriceFont`,
|
|
766
|
+
`cardAccent`, `cardBarRadius`, `cardDescriptionLines`, `cardRetailerOrder` (`-1` lifts the seller
|
|
767
|
+
line above the name). CSS only: `--vio-card-border-hover-color`, `--vio-card-add-size`,
|
|
768
|
+
`--vio-card-add-icon-size`, `--vio-card-brand-{size,tracking,weight,color,gap}`,
|
|
769
|
+
`--vio-card-name-{font,size,weight,gap}`, `--vio-card-price-{size,weight,gap}`,
|
|
770
|
+
`--vio-card-retailer-{display,size,tracking,weight,color,margin}`,
|
|
771
|
+
`--vio-card-description-{size,color,gap}`, `--vio-card-accent-text`.
|
|
661
772
|
|
|
662
773
|
### The hand-over to Vipps (0.28)
|
|
663
774
|
|
|
@@ -674,6 +785,57 @@ tap, so a second payment cannot start. Events on `document`:
|
|
|
674
785
|
`source` is `'detail'`, `'cart'` or `'checkout'`. The overlay is `.vio-redirect-overlay`
|
|
675
786
|
in the light DOM.
|
|
676
787
|
|
|
788
|
+
**The detail's Vipps button pays the whole cart.** «Kjøp nå med Vipps» on the product
|
|
789
|
+
detail adds the product to the sponsor's cart and the checkout is built on that cart —
|
|
790
|
+
every line in it. It goes straight to Vipps only when the cart was **empty**; with lines
|
|
791
|
+
already in it, the product is added and the checkout opens first (`vio:checkout-open`
|
|
792
|
+
with `paymentMethod: 'vipps'`, no hand-over events), every line and the total on screen,
|
|
793
|
+
so what the Vipps page lists is what the shopper saw.
|
|
794
|
+
|
|
795
|
+
**Two Vipps paths (2026-10-28).** The detail's «Kjøp nå med Vipps» is **Express**: the app
|
|
796
|
+
fills in name and address. The **cart's** Vipps button opens the checkout with Vipps
|
|
797
|
+
preselected, and the checkout's Vipps is always the **standard** payment, even when the
|
|
798
|
+
brand has Express on. Our form collects name, e-mail, **Mobilnummer**, address and the
|
|
799
|
+
delivery with its price before paying. The mobile is required for Vipps only and accepts any
|
|
800
|
+
market's number: the `+` prefix or the shipping country sets the calling code. The delivery is
|
|
801
|
+
saved on the cart first; if that fails, no payment starts. The payment goes out with
|
|
802
|
+
`express: false`, and the address (with `phone` + `phone_code`) is on the checkout's shipping
|
|
803
|
+
and billing address. **Apple Pay on the detail follows the same
|
|
804
|
+
rule**: its sheet's amount is fixed before the tap, so it opens only on an empty cart, for the
|
|
805
|
+
product × the chosen quantity; otherwise the checkout opens (`paymentMethod: 'apple-pay'`).
|
|
806
|
+
|
|
807
|
+
### What the shopper pays is what the page shows — the cart, the server, the other tabs
|
|
808
|
+
|
|
809
|
+
The cart lives in this tab's memory and in `localStorage` (`vio.cart.v1`, one entry per
|
|
810
|
+
sponsor with the backend cart id), and the checkout — and the Vipps / Stripe page — is
|
|
811
|
+
built on the **backend** cart, every line of it. Three things keep the two the same:
|
|
812
|
+
|
|
813
|
+
- **Before a checkout is created or a payment link minted**, and **when `<vio-cart>` or
|
|
814
|
+
`<vio-checkout>` opens**, the SDK asks the server for the cart (`GetCart`) and draws that
|
|
815
|
+
answer: a line another tab added, a cart another tab emptied. `Vio.cart.refreshFromServer(sponsorId)`
|
|
816
|
+
does the same on demand and resolves with the sponsor's cart. A `GetCart` that fails leaves
|
|
817
|
+
the cart as it was (and warns) — the checkout then goes on with the lines the last mutation
|
|
818
|
+
answered, as before.
|
|
819
|
+
- **Other tabs of the site are heard.** A payment in one tab (its cart cleared) empties the
|
|
820
|
+
drawer of the others and the next add there opens a new backend cart; a line added in one
|
|
821
|
+
tab is in the others without a reload. Through the browser's `storage` event — a tab that
|
|
822
|
+
is mid-add for a sponsor keeps its own lines until its answer lands.
|
|
823
|
+
- **An open checkout follows its cart**: its lines and its total are recomputed when the
|
|
824
|
+
cart changes under it (the widgets that captured an amount at mount ask to be refreshed,
|
|
825
|
+
as they already did for a changed cart).
|
|
826
|
+
|
|
827
|
+
`vio:cart-updated` on `document` says when either of the first two happened:
|
|
828
|
+
|
|
829
|
+
```ts
|
|
830
|
+
document.addEventListener('vio:cart-updated', (e) => {
|
|
831
|
+
const { sponsorId, cartId, items, itemCount, subtotal, currency, source } = e.detail
|
|
832
|
+
// source: 'server' (GetCart, right before a checkout / on opening) | 'storage' (another tab)
|
|
833
|
+
})
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
`items` are copies of the lines (`CartLineItem[]`). A host that draws its own cart or badge
|
|
837
|
+
redraws it from this event; `<vio-cart>` and the badge wired to `Vio.cart`'s `change` need nothing.
|
|
838
|
+
|
|
677
839
|
## Headless / SSR
|
|
678
840
|
|
|
679
841
|
The root and `/core` entries export the framework-agnostic core with **no DOM side-effects** — SSR-safe and **tree-shakeable** (importing one helper pulls only that helper, ~0.5 kB):
|