@vio-live/web-sdk 0.27.0 → 0.29.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 +359 -0
- package/README.md +185 -0
- package/dist/{chunk-HJRIPCWB.js → chunk-AG4G2S2M.js} +442 -37
- package/dist/chunk-AG4G2S2M.js.map +1 -0
- package/dist/{chunk-GLAKUYQA.js → chunk-L6YYYKQK.js} +905 -188
- package/dist/chunk-L6YYYKQK.js.map +1 -0
- package/dist/core/index.d.ts +187 -20
- package/dist/core/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/{tokens-CihuCU2l.d.ts → tokens-Dok4d21V.d.ts} +131 -9
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +3 -2
- package/dist/chunk-GLAKUYQA.js.map +0 -1
- package/dist/chunk-HJRIPCWB.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Changelog — `@vio-live/web-sdk`
|
|
2
|
+
|
|
3
|
+
What changed in each version, and **what a host has to know before upgrading**.
|
|
4
|
+
Every version has up to four parts:
|
|
5
|
+
|
|
6
|
+
- **Added** — new things you can use.
|
|
7
|
+
- **Changed** — the same feature behaves differently (screens, events, data on the wire).
|
|
8
|
+
- **Fixed** — bugs that are gone.
|
|
9
|
+
- **⚠️ Upgrade notes** — what a host (Vev, Replit, a custom page) must do, check or
|
|
10
|
+
watch for. Read these before bumping, even for a patch.
|
|
11
|
+
|
|
12
|
+
Newest first. `Unreleased` is merged on `main` but **not published to npm** yet.
|
|
13
|
+
How to keep this file: see [CONTRIBUTING → Changelog](CONTRIBUTING.md#changelog).
|
|
14
|
+
The details of each feature live in the [README](README.md); this file says *what*
|
|
15
|
+
and *when*, and links there.
|
|
16
|
+
|
|
17
|
+
## 0.29.0 — 2026-10-24
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **`vio:cart-updated`** on `document` — `{ sponsorId, cartId, items, itemCount, subtotal, currency, source }`,
|
|
21
|
+
`source: 'server'` when the cart was just read from the server (right before a checkout is
|
|
22
|
+
created or a payment link minted, and when the drawer or the checkout opens), `'storage'` when
|
|
23
|
+
another tab of the site changed it. For hosts that draw their own cart or badge.
|
|
24
|
+
`Vio.cart.refreshFromServer(sponsorId)` asks on demand. See
|
|
25
|
+
[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).
|
|
26
|
+
- **`add_payment_info`** (analytics, GA4 name) the moment a backend checkout exists for a payment
|
|
27
|
+
— Vipps from the detail, the cart or the checkout, a Stripe payment link or Element, Klarna,
|
|
28
|
+
Apple Pay, Nexi, Kustom, Qliro, Walley, Adyen — with `commerce.checkout_id`, `payment_method`,
|
|
29
|
+
the cart's lines and the same context as `begin_checkout` (per campaign like it). Once per
|
|
30
|
+
checkout per page load, none when the checkout could not be created, and by beacon BEFORE the
|
|
31
|
+
page leaves for Vipps or a Stripe link. It is how a paid order whose `purchase` never came back
|
|
32
|
+
keeps its session, page and slot (ADR-0039; order 4128, 2026-10-23). The checkout manager
|
|
33
|
+
announces it on `window` as **`vio:checkout-created`** `{ sponsorId, checkoutId, paymentMethod,
|
|
34
|
+
campaignId?, redirect }`. See [README → `add_payment_info`](README.md#add_payment_info--the-checkout-linked-to-this-session-0290).
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- **«Kjøp nå med Vipps» on the product detail goes straight to Vipps only when the cart is
|
|
38
|
+
empty.** With lines already in the cart, the product is added and the checkout opens first
|
|
39
|
+
(`vio:checkout-open`, Vipps preselected, no `vio:payment-redirect-*` events), every line and
|
|
40
|
+
the total on screen. Before, the tap paid the WHOLE cart with only the Vipps page saying so
|
|
41
|
+
(Mote & Livsstil, 2026-10-23: a jumper left in the cart 13 minutes earlier was paid along
|
|
42
|
+
with the top bought «nå»). The cart's own Vipps button is unchanged: it pays the lines it shows.
|
|
43
|
+
- **Apple Pay on the product detail shows what it charges.** The sheet's amount is fixed before
|
|
44
|
+
the tap and was the product's price, while the charge was the sponsor's whole cart. The sheet
|
|
45
|
+
now opens only on an empty cart, prepared for the product × the chosen quantity (it is prepared
|
|
46
|
+
again when the quantity or variant changes); with lines already in the cart — or while the
|
|
47
|
+
sheet for a new quantity is still being prepared — the product is added and the checkout opens
|
|
48
|
+
(`vio:checkout-open` with `paymentMethod: 'apple-pay'`), where Apple Pay charges the total shown.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- **The lines the shopper pays for are the lines the page shows.** The checkout, and the
|
|
52
|
+
Vipps / Stripe page, are built on the backend cart — every line in it — while the drawer
|
|
53
|
+
and the checkout summary drew this tab's memory. Now the SDK reads the server's cart right
|
|
54
|
+
before a checkout is created or a payment link minted, and when `<vio-cart>` / `<vio-checkout>`
|
|
55
|
+
open, and draws that; an open checkout recomputes its lines and total when the cart changes
|
|
56
|
+
under it.
|
|
57
|
+
- **Other tabs of the site are heard.** The carts are shared through `localStorage` but each
|
|
58
|
+
tab read them once, at load: a payment in one tab left the others showing — and adding to —
|
|
59
|
+
a cart that was already paid; a line added in one tab was missing from the others until a
|
|
60
|
+
reload. Now a tab adopts what another tab wrote (`storage` event), except for a sponsor it is
|
|
61
|
+
mid-add for.
|
|
62
|
+
- **Vipps Express: the checkout no longer adds our shipping to the total.** The delivery is
|
|
63
|
+
chosen and paid inside Vipps, but with Vipps selected the checkout still added the seller's
|
|
64
|
+
first rate (Careless QA, 2026-10-24: «Frakt – Default Shipping» 10 kr, 1 308 kr on screen and
|
|
65
|
+
on the button, 1 298 kr in Vipps). With Vipps as the method and Express on for the seller,
|
|
66
|
+
the summary says «Frakt: Velges i Vipps» and the total and «Betal … med Vipps» are the items.
|
|
67
|
+
Vipps without Express, and every other method, still add the rate as before.
|
|
68
|
+
- **The `purchase` of a paid return is sent, not left in the queue** (2026-10-24). Order 4128
|
|
69
|
+
(Careless, prod, 2026-10-23 22:40, after two abandoned trips to Vipps): the shopper
|
|
70
|
+
paid, came back, saw the confirmation, and the brand's panel never counted the sale — Commerce
|
|
71
|
+
had its `order_paid`, the collector no `purchase`. The return was read as paid; the one way the
|
|
72
|
+
code loses that purchase is the queue: it was built, with the order number, into a queue sent
|
|
73
|
+
every 5 s, and leaving the page right away (back to the Vipps app, the home screen) beaconed
|
|
74
|
+
only a purchase still waiting for its number — a phone that discards a hidden tab never runs
|
|
75
|
+
the next tick. Now the purchase is sent as soon as it is built, and when the page hides
|
|
76
|
+
everything still queued leaves by beacon.
|
|
77
|
+
|
|
78
|
+
### ⚠️ Upgrade notes
|
|
79
|
+
- **The collector must accept `add_payment_info`** before it is counted: one that does not know the
|
|
80
|
+
name rejects that event alone (`202` with `rejected`), the rest of the batch is kept. Nothing to
|
|
81
|
+
do in the host.
|
|
82
|
+
- A host that handles `vio:checkout-open` only from the cart: the product detail now emits it
|
|
83
|
+
too when «Kjøp nå med Vipps» or Apple Pay is tapped on a non-empty cart (it already did when
|
|
84
|
+
Express was off for the seller), now also with `paymentMethod: 'apple-pay'`. Check that your
|
|
85
|
+
handler opens `<vio-checkout>` for the detail's event.
|
|
86
|
+
- A host with its own cart drawer or badge: listen to `vio:cart-updated` (or `Vio.cart`'s
|
|
87
|
+
`change`) — the lines can now change without a tap in that tab.
|
|
88
|
+
- One more `GetCart` per checkout creation / payment link and per opening of the drawer or the
|
|
89
|
+
checkout.
|
|
90
|
+
|
|
91
|
+
## 0.28.0 — 2026-10-22
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
- **The detail's layout is public**: 31 new `applyVioTheme` keys, each a `--vio-*` property
|
|
95
|
+
(logo height and zoom, image fit / aspect / max height / zoom / side margin, thumbnail size and gap, option
|
|
96
|
+
height and gap, button height and radius, a sticky buy zone, cart / checkout widths,
|
|
97
|
+
confirmation logo and title, the redirect overlay), plus `::part` names on every piece of
|
|
98
|
+
the detail and the confirmation. The same keys serve our demo, Replit and Vev's Config block.
|
|
99
|
+
See [README → Layout](README.md#layout-of-the-detail-cart-checkout-and-confirmation-028).
|
|
100
|
+
- **The hand-over to Vipps**: a full-screen «Åpner Vipps …» with a soft fade from the tap to
|
|
101
|
+
the Vipps page (detail, cart, checkout), which also stops a second tap; events
|
|
102
|
+
`vio:payment-redirect-start` (cancelable — draw your own loader), `vio:payment-redirect-error`
|
|
103
|
+
and `vio:payment-redirect-cancel`. See [README → The hand-over to Vipps](README.md#the-hand-over-to-vipps-028).
|
|
104
|
+
- `commerce.checkout_id` on every `purchase` the SDK can tie to a checkout, and
|
|
105
|
+
`props.order_id_kind` (`'order'` | `'checkout'`).
|
|
106
|
+
- `vio:payment-success` may carry `orderNumber` when the checkout already knows it.
|
|
107
|
+
- `purchaseIdsOf(detail)` → `{ orderId, checkoutId, orderIdKind }` (from `/core`) for
|
|
108
|
+
hosts with their own analytics sink.
|
|
109
|
+
|
|
110
|
+
### Changed
|
|
111
|
+
- **Product detail** (asked by Mote & Livsstil): the selling brand's logo (its name without
|
|
112
|
+
one; a square logo with the name beside it) sits **top left beside ✕**, no longer above the
|
|
113
|
+
image; every main image shows in **one 4:5 box it fills** (`cover`) — no white bars, no jump
|
|
114
|
+
between photos of different shapes; thumbnails are **one row** (swipe, arrows with a mouse,
|
|
115
|
+
the selected one kept in view) instead of wrapping; sizes are **compact** (36px); **Vipps
|
|
116
|
+
comes first**, above quantity and «Legg i handlekurv»; the **description folds** («Beskrivelse»
|
|
117
|
+
opens on a tap); under the buy buttons, **«Du handler direkte hos {brand}.»**, with links to the
|
|
118
|
+
brand's «Kjøpsvilkår» and «Angrerett og retur» when its sponsor block carries `termsUrl` /
|
|
119
|
+
`refundPolicyUrl` (https only; `Vio.sponsorIdentity()` returns them too). On desktop the
|
|
120
|
+
description comes before the buy zone, which stays pinned at the bottom of the panel; phones
|
|
121
|
+
keep buy zone, then description.
|
|
122
|
+
- The confirmation's brand logo is 40px tall (was max 32px).
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
- **Phones: the detail, the cart and the checkout close by pulling them down**, from the grab bar
|
|
126
|
+
or the top bar (logo / title and ✕): the sheet follows the finger, and closes when let go far
|
|
127
|
+
enough or with a flick, or springs back. Before, the grab bar only answered a tap — pulling it
|
|
128
|
+
scrolled the sheet. The checkout never closes this way mid-payment.
|
|
129
|
+
- **Phones: the page behind stays put while a sheet is open.** A swipe past the end of the
|
|
130
|
+
detail, cart or checkout used to scroll the article underneath. The page is locked while any
|
|
131
|
+
sheet is open and comes back to the same place when the last one closes. Desktop drawers are
|
|
132
|
+
unchanged.
|
|
133
|
+
- **The `purchase` event's `commerce.order_id` is Vio's order number** (`4415`), not the
|
|
134
|
+
checkout's uuid. Before, every purchase paid through a redirect (Vipps, Klarna, hosted
|
|
135
|
+
Stripe) or whose order a webhook creates carried the uuid, so Commerce could never net
|
|
136
|
+
a cancellation or refund (`order_cancelled` / `order_refunded`, ADR-0036) against it.
|
|
137
|
+
([#88](https://github.com/vio-live/vio-web-sdk/pull/88))
|
|
138
|
+
|
|
139
|
+
### ⚠️ Upgrade notes
|
|
140
|
+
- **The product detail looks different** after upgrading (see *Changed*). A host that styled
|
|
141
|
+
`.sponsor-header` / `.topbar-brand` from outside: they are gone — use `::part(sponsor)`.
|
|
142
|
+
Hosts that want the old look back: `detailImageAspect: '1 / 1'`, `detailImageFit: 'contain'`,
|
|
143
|
+
`detailOptionHeight: '42px'`, `detailBuyPosition: 'static'`. The `4 / 5` box crops photos of
|
|
144
|
+
other shapes: pick the proportion that fits the catalog, or `contain`.
|
|
145
|
+
- A host with its own «loading Vipps» screen: call `preventDefault()` on
|
|
146
|
+
`vio:payment-redirect-start`, or the SDK's overlay shows on top of yours.
|
|
147
|
+
- **The `purchase` may leave up to 10 s after `vio:payment-success`**: the SDK asks the
|
|
148
|
+
checkout for the order number at once and then after 0.5, 1, 2, 3 and 3 s. The confirmation screen, the
|
|
149
|
+
cart and the payment never wait — only the analytics event. If the page hides first,
|
|
150
|
+
the event leaves at once with the checkout id (`order_id_kind: 'checkout'`).
|
|
151
|
+
- **Hosts with their own sink (Vev):** read the ids with `purchaseIdsOf(detail)` and wait
|
|
152
|
+
for the number the same way (`Vio.checkout.getOrderReference(checkoutId)`), or your
|
|
153
|
+
purchases keep the uuid.
|
|
154
|
+
- **Dashboards / queries** that joined purchases by checkout uuid: use `checkout_id`.
|
|
155
|
+
- **Hosts that lock the page themselves when a Vio sheet opens** (phones): the SDK does it now —
|
|
156
|
+
`position: fixed` on `<body>` at the current offset, its own inline styles restored and the
|
|
157
|
+
scroll put back on close. Drop yours, or make sure it does not undo the body's styles while a
|
|
158
|
+
sheet is still open.
|
|
159
|
+
- One `purchase` per checkout per page load: a second `vio:payment-success` for the same
|
|
160
|
+
checkout adds nothing.
|
|
161
|
+
- **«Kjøpsvilkår · Angrerett og retur» need the backend**: the links show only when the
|
|
162
|
+
sponsor block carries `termsUrl` / `refundPolicyUrl`, which the campaigns service sends
|
|
163
|
+
once the brand fills them in (Settings → Brand → Shopper policies). Until then the
|
|
164
|
+
sentence «Du handler direkte hos {brand}.» shows alone. Nothing to do on the host.
|
|
165
|
+
|
|
166
|
+
## 0.27.0 — 2026-10-21
|
|
167
|
+
|
|
168
|
+
### Added
|
|
169
|
+
- **The brand that sells is named** — name and logo — in the product detail (top),
|
|
170
|
+
the checkout header («Du handler hos …») and the confirmation («Du har handlet hos
|
|
171
|
+
…»), also after a Vipps return and a reload. ([#91](https://github.com/vio-live/vio-web-sdk/pull/91))
|
|
172
|
+
- `Vio.sponsorIdentity(sponsorId, { checkoutId? })` → `{ id, name, logoUrl, avatarUrl } | null`.
|
|
173
|
+
- `vio:checkout-open` and `vio:payment-success` carry `sponsor: { id, name, logoUrl }`.
|
|
174
|
+
- `Vio.checkout.getOrderReference(checkoutId, sponsorId?)` also answers `sponsor`.
|
|
175
|
+
- `sponsorIdentityFrom(bootstrap, sponsorId)` exported from `/core`.
|
|
176
|
+
|
|
177
|
+
### Changed
|
|
178
|
+
- The detail's top line names the seller only once (no repeated seller name).
|
|
179
|
+
- The checkout remembers, per tab, which sponsor each checkout belongs to
|
|
180
|
+
(`sessionStorage` `vio.checkoutSponsor.v1`, last 20 checkouts).
|
|
181
|
+
|
|
182
|
+
### ⚠️ Upgrade notes
|
|
183
|
+
- The sponsor in the checkout and its confirmation is **the checkout's** (the cart's
|
|
184
|
+
brand), never the article's: an article of one brand can sell another brand's product.
|
|
185
|
+
- A host confirmation page that must survive a reload keeps `checkoutId` and `sponsorId`
|
|
186
|
+
in its own URL and asks `getOrderReference` again: the SDK cleans its return
|
|
187
|
+
parameters after using them. Recipe in [README → Sponsor identity](README.md#sponsor-identity--name-and-logo).
|
|
188
|
+
- New marks are styled with `--vio-*` tokens and exposed as `part="sponsor"`.
|
|
189
|
+
|
|
190
|
+
## 0.26.1 — 2026-10-21
|
|
191
|
+
|
|
192
|
+
### Fixed
|
|
193
|
+
- A surface with **no active campaign** still names its channel sponsor in the
|
|
194
|
+
attribution (before, the sponsor came out empty). ([#89](https://github.com/vio-live/vio-web-sdk/pull/89))
|
|
195
|
+
|
|
196
|
+
## 0.26.0 — 2026-10-20
|
|
197
|
+
|
|
198
|
+
### Added
|
|
199
|
+
- **The order is born attributed** (ADR-0035 B): every `AddItem` / quantity increase
|
|
200
|
+
carries the line's `origin` (`campaign_id`, slot, `component_kind`, `content_url`,
|
|
201
|
+
`content_title`), and `CreateCheckout` / the last `UpdateCheckout` carry the checkout's
|
|
202
|
+
`attribution`. See [README → Attribution on the order](README.md#attribution-on-the-order--what-commerce-receives-026).
|
|
203
|
+
|
|
204
|
+
### Changed
|
|
205
|
+
- `content_url` is sent **without query string or fragment**.
|
|
206
|
+
- Privacy mode `minimal` sends neither the page nor the slot.
|
|
207
|
+
- The surface's privacy mode is preloaded, so the first events already respect it.
|
|
208
|
+
- `vio:payment-success` never waits longer than the page stays open; one reference
|
|
209
|
+
request per checkout.
|
|
210
|
+
- The landing `referrer` has no query and is dropped when it is a self-referral;
|
|
211
|
+
`buyer_id` only with consent.
|
|
212
|
+
|
|
213
|
+
### ⚠️ Upgrade notes
|
|
214
|
+
- **Against a gateway without the new fields** (`origin` on `LineItemInput` / `UpdateItem`,
|
|
215
|
+
`attribution` on the checkout) the SDK resends the operation without them once per page
|
|
216
|
+
load and says so in the console: the cart works as in 0.25, **but the order is not
|
|
217
|
+
attributed**. Deploy the backend for the attribution to reach Commerce.
|
|
218
|
+
- Hosts that add to the cart with their own button should pass
|
|
219
|
+
`origin: Vio.attribution(slot)` (or `e.detail.origin`) to `Vio.cart.addProduct`.
|
|
220
|
+
|
|
221
|
+
## 0.25.0 — 2026-10-19
|
|
222
|
+
|
|
223
|
+
### Added
|
|
224
|
+
- The checkout asks the gateway for **`buyer_ref`** (a keyed hash of the buyer's email)
|
|
225
|
+
and puts it on `vio:payment-success` → analytics `buyer_id`.
|
|
226
|
+
- Every event carries a **`client`** block: language, the landing's referrer and utm
|
|
227
|
+
parameters, screen size and time zone (see [README → The visit and the buyer](README.md#the-visit-and-the-buyer--client-and-buyer_id-025)).
|
|
228
|
+
- Slot manifests carry `kind`; every element carries `component_template_id`.
|
|
229
|
+
|
|
230
|
+
### Changed
|
|
231
|
+
- A slot assigned `page` counts as the page's campaign when its sponsor is known.
|
|
232
|
+
|
|
233
|
+
### ⚠️ Upgrade notes
|
|
234
|
+
- Never store an email from the events: `buyerRef` is the reference to keep.
|
|
235
|
+
- With `requireConsent`, nothing is written (landing memory, ids) until `setConsent(true)`,
|
|
236
|
+
and `buyer_id` is not sent.
|
|
237
|
+
|
|
238
|
+
## 0.24.0 — 2026-10-16
|
|
239
|
+
|
|
240
|
+
### Added
|
|
241
|
+
- **The page says which campaign it belongs to** (ADR-0034, amendment 3):
|
|
242
|
+
`Vio.init({ campaignId })`, `<body data-vio-campaign="…">`, or `vio-campaign` on one
|
|
243
|
+
element. See [README → Page campaign](README.md#page-campaign--an-editorial-page-that-belongs-to-one-campaign).
|
|
244
|
+
|
|
245
|
+
### Fixed
|
|
246
|
+
- The cart's reconcile skips the mutation that is answering; card context per product;
|
|
247
|
+
title and URL capped to the collector's limits.
|
|
248
|
+
|
|
249
|
+
### ⚠️ Upgrade notes
|
|
250
|
+
- The attributes are read **when an element mounts**: in an SPA, update `data-vio-campaign`
|
|
251
|
+
(or call `Vio.init` again) before mounting the next article's blocks.
|
|
252
|
+
- A paused, unknown or non-numeric campaign falls through to the next level.
|
|
253
|
+
|
|
254
|
+
## 0.23.1 — 2026-10-16
|
|
255
|
+
|
|
256
|
+
### Added
|
|
257
|
+
- **Automatic slot identity**: with `data-vio-scope` (and `data-vio-scope-name`) on an
|
|
258
|
+
ancestor, every element registers itself (`article-123-carousel-1`); `Vio.attribution()`.
|
|
259
|
+
- The cart remembers the origin of each unit; a purchase is split per campaign.
|
|
260
|
+
|
|
261
|
+
### Fixed
|
|
262
|
+
- Collector limits respected; a one-campaign purchase is one slice; stable origins.
|
|
263
|
+
|
|
264
|
+
## 0.23.0 — 2026-10-16
|
|
265
|
+
|
|
266
|
+
### Added
|
|
267
|
+
- **Slots**: the page declares its slots and each one resolves its campaign from the
|
|
268
|
+
dashboard (ADR-0027 / 0034). See [README → Slots](README.md#slots--where-things-happen-on-a-page).
|
|
269
|
+
|
|
270
|
+
### Fixed
|
|
271
|
+
- The checkout waits for pending cart mutations before creating a checkout or a payment link.
|
|
272
|
+
- GraphQL cart calls time out after 15 s.
|
|
273
|
+
|
|
274
|
+
### ⚠️ Upgrade notes
|
|
275
|
+
- `Vio.init({ platform: 'web' | 'custom-web' | 'vev' })` — default `'web'`. Only that
|
|
276
|
+
platform's assignments come back.
|
|
277
|
+
|
|
278
|
+
## 0.22.0 — 2026-10-16
|
|
279
|
+
Version bump only; same code as 0.21.1.
|
|
280
|
+
|
|
281
|
+
## 0.21.1 — 2026-10-16
|
|
282
|
+
|
|
283
|
+
### Fixed
|
|
284
|
+
- Quantity changes are serialized and coalesced per cart line: quick taps on + / − end on
|
|
285
|
+
the quantity the shopper sees, with one request in flight at a time.
|
|
286
|
+
|
|
287
|
+
## 0.21.0 — 2026-10-15
|
|
288
|
+
|
|
289
|
+
### Fixed
|
|
290
|
+
- Analytics: `order_id` only with Vio ids, the cart's `value`, and `checkout_id` on
|
|
291
|
+
payment returns.
|
|
292
|
+
|
|
293
|
+
## 0.20.5 — 2026-10-15
|
|
294
|
+
|
|
295
|
+
### Fixed
|
|
296
|
+
- Analytics: the real `campaign_id`, a consistent `order_id` on `purchase`, and
|
|
297
|
+
`X-Api-Key` on the events request.
|
|
298
|
+
|
|
299
|
+
## 0.20.4 — 2026-10-14
|
|
300
|
+
|
|
301
|
+
### Fixed
|
|
302
|
+
- A bootstrap request started before a re-init no longer lands in the new configuration.
|
|
303
|
+
|
|
304
|
+
## 0.20.3 — 2026-10-12
|
|
305
|
+
|
|
306
|
+
### Changed
|
|
307
|
+
- A cart already paid in another tab reads «already paid», not «failed».
|
|
308
|
+
|
|
309
|
+
## 0.20.1 / 0.20.2 — 2026-10-12
|
|
310
|
+
|
|
311
|
+
### Fixed
|
|
312
|
+
- The dialogs' focus trap keeps keyboard focus inside in Safari, and pulls it back when the
|
|
313
|
+
browser walks out of the dialog.
|
|
314
|
+
|
|
315
|
+
## 0.20.0 — 2026-10-09
|
|
316
|
+
|
|
317
|
+
### Added
|
|
318
|
+
- Keyboard and focus in the detail, cart and checkout: focus moves in on open, Tab stays
|
|
319
|
+
inside, Esc closes, focus returns on close.
|
|
320
|
+
|
|
321
|
+
### Changed
|
|
322
|
+
- The detail and the cart read the corner radius from the theme (`--vio-radius-*`).
|
|
323
|
+
- When a payment cannot start, the shopper is told what to do (empty cart, cart changed in
|
|
324
|
+
another tab, already paid) instead of a raw error.
|
|
325
|
+
|
|
326
|
+
## 0.19.0 — 2026-10-08
|
|
327
|
+
|
|
328
|
+
### Changed
|
|
329
|
+
- The product detail's header shows the seller's **Company name** (Vio Commerce →
|
|
330
|
+
Settings → Company), the brand when there is none. *(Superseded in 0.27.0 by the
|
|
331
|
+
sponsor's name and logo.)*
|
|
332
|
+
|
|
333
|
+
### ⚠️ Upgrade notes
|
|
334
|
+
- The name is read with its own query outside the product cache (`useCache: false`): a
|
|
335
|
+
narrow query written to the gateway's product cache would hand the next product query a
|
|
336
|
+
product without price or images.
|
|
337
|
+
|
|
338
|
+
## 0.18.0 — 2026-10-08
|
|
339
|
+
|
|
340
|
+
### Added
|
|
341
|
+
- Vipps' **official button** in the detail, the cart and the checkout; **Vipps Express**
|
|
342
|
+
from the product and the cart (the app collects address and delivery).
|
|
343
|
+
- Stripe Connect in the embedded payment and Apple Pay (ADR-0022).
|
|
344
|
+
|
|
345
|
+
### Changed
|
|
346
|
+
- Back from a payment page the drawer shows **only** «Bekrefter betalingen…» until it
|
|
347
|
+
knows the outcome — never the checkout behind it.
|
|
348
|
+
- The receipt's «Ordrenummer» is **Vio's order number** (`#4497`), never the checkout's uuid.
|
|
349
|
+
|
|
350
|
+
### ⚠️ Upgrade notes
|
|
351
|
+
- Payment returns come back to **the same URL** with `vio_payment`, `vio_method`,
|
|
352
|
+
`vio_sponsor`, `checkout_id`: keep `<vio-checkout>` mounted on every route and do not
|
|
353
|
+
strip the query string on load.
|
|
354
|
+
- The Vipps button loads `cdn.vippsmobilepay.com`: allow it in a CSP.
|
|
355
|
+
|
|
356
|
+
## 0.17.0 and earlier
|
|
357
|
+
Not recorded here — see the git history and the [releases](https://github.com/vio-live/vio-web-sdk/commits/main).
|
|
358
|
+
Highlights: Stripe on our own page (0.16), payment method first (0.15), Kustom (0.14),
|
|
359
|
+
Adyen (0.13), Nexi (0.12), analytics with consent (0.6 → 0.11).
|
package/README.md
CHANGED
|
@@ -453,6 +453,72 @@ confirmation screen never waits. A host with its own checkout passes `buyerRef`
|
|
|
453
453
|
(`requireConsent` until `setConsent(true)`) `buyer_id` is not sent at all: a visit
|
|
454
454
|
measured with ephemeral ids carries no stable identifier of a person.
|
|
455
455
|
|
|
456
|
+
### The purchase — `order_id` is Vio's order number, `checkout_id` beside it
|
|
457
|
+
|
|
458
|
+
A `purchase`'s **`commerce.order_id` is Vio's order number** — `4415`, the number
|
|
459
|
+
on the receipt, in the dashboard and in Commerce. Commerce's cancellations and
|
|
460
|
+
refunds (`order_cancelled` / `order_refunded`, ADR-0036) name the purchase they
|
|
461
|
+
compensate by that number: a purchase that carried the checkout's uuid instead
|
|
462
|
+
could never be netted.
|
|
463
|
+
|
|
464
|
+
- **Where the number comes from.** The `vio:payment-success` detail's
|
|
465
|
+
`orderNumber` (the SDK's checkout adds it when its ask for the buyer reference
|
|
466
|
+
already found the order), Apple Pay's backend confirmation
|
|
467
|
+
(`result.confirmation.orderId`), or an `orderId` that is a whole number (a host
|
|
468
|
+
passing the number; a checkout id is a uuid, never one). A redirect return
|
|
469
|
+
(Vipps, Klarna, hosted Stripe…) or an order a webhook creates usually fires the
|
|
470
|
+
event before the order exists: the SDK then **asks the checkout** —
|
|
471
|
+
`Vio.checkout.getOrderReference(checkoutId)`, the receipt's own path — at once
|
|
472
|
+
and after 0.5 / 1 / 2 / 3 / 3 s, for at most 10 s. Only the event waits: the
|
|
473
|
+
confirmation screen, the cart and the payment flow never do. Apple Pay and the
|
|
474
|
+
embedded Stripe Element (the drawer waits for the webhook's order before it
|
|
475
|
+
confirms) have the number at once.
|
|
476
|
+
- **`commerce.checkout_id`** — Vio's checkout id (uuid) — is always there when the
|
|
477
|
+
SDK knows the checkout (every SDK path, Apple Pay from the cart included): the
|
|
478
|
+
second key the collector can match a purchase by. It is new and additive; a
|
|
479
|
+
collector that predates it drops the key (its schema strips unknown fields) and
|
|
480
|
+
keeps the event.
|
|
481
|
+
- **`props.order_id_kind`** says what `order_id` is: `'order'` (the number) or
|
|
482
|
+
`'checkout'` — the number did not come within the 10 s, the page was hidden or
|
|
483
|
+
closed while waiting (the event leaves at once, by beacon), or the page has no
|
|
484
|
+
checkout manager to ask; then `order_id` is the checkout id, as before.
|
|
485
|
+
- **Once per checkout.** One `purchase` per checkout (or order) per page load: a
|
|
486
|
+
second `vio:payment-success` for the same one adds nothing, and a number that
|
|
487
|
+
arrives after the event left with the checkout id adds no second event. `ts`
|
|
488
|
+
and `event_id` are fixed at the payment, whenever the event leaves; a purchase
|
|
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).
|
|
494
|
+
- **A host with its own sink** (Vev) reads the same ids from the detail with
|
|
495
|
+
`purchaseIdsOf(detail)` → `{ orderId, checkoutId, orderIdKind }` (or
|
|
496
|
+
`purchaseFromPaymentSuccess(detail)`, exported from `/core`), and waits for the
|
|
497
|
+
number the same way through `Vio.checkout.getOrderReference(checkoutId)`.
|
|
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
|
+
|
|
456
522
|
## Sponsor identity — name and logo
|
|
457
523
|
|
|
458
524
|
The elements say **who sells**: the sponsor's (brand's) display name and logo.
|
|
@@ -558,6 +624,125 @@ useful if you show multiple differently-branded sponsors on the same page:
|
|
|
558
624
|
applyVioTheme({ colorAccent: '#ff6600' }, document.getElementById('sponsor-2'))
|
|
559
625
|
```
|
|
560
626
|
|
|
627
|
+
### Layout of the detail, cart, checkout and confirmation (0.28)
|
|
628
|
+
|
|
629
|
+
Beyond colors and fonts, the layout a host needs to fit its design is public too —
|
|
630
|
+
**one key in `applyVioTheme`, one `--vio-*` property behind it**. Our demo, Replit and
|
|
631
|
+
Vev's Config block set the same keys, so a page looks the same in the editor, the
|
|
632
|
+
preview and published. Set them with `applyVioTheme` (preferred) or as CSS on `:root`
|
|
633
|
+
or on the element (`vio-product-detail { --vio-detail-logo-height: 48px; }`).
|
|
634
|
+
|
|
635
|
+
| `applyVioTheme` key | Property | Default | What it does |
|
|
636
|
+
|---|---|---|---|
|
|
637
|
+
| `detailWidth` | `--vio-detail-width` | `440px` | Detail panel width (desktop) |
|
|
638
|
+
| `detailPaddingX` | `--vio-detail-padding-x` | `24px` | Side padding of the detail |
|
|
639
|
+
| `detailLogoHeight` | `--vio-detail-logo-height` | `44px` | Selling brand's logo, top left beside ✕ (width keeps its proportions; the name when there is no logo; a square logo — a mark — gets the name beside it) |
|
|
640
|
+
| `detailLogoZoom` | `--vio-detail-logo-zoom` | `1` | Zoom inside the logo's box: `1.8` crops the white around a mark in a square file |
|
|
641
|
+
| `detailTitleSize` / `detailPriceSize` | `--vio-detail-title-size` / `--vio-detail-price-size` | `26px` / `24px` | Name and price |
|
|
642
|
+
| `detailImageFit` | `--vio-detail-image-fit` | `cover` | `cover` = fills the box, crops the overflow; `contain` = whole image, bars where shapes differ |
|
|
643
|
+
| `detailImageAspect` | `--vio-detail-image-aspect` | `4 / 5` | One proportion for every image, so the panel never jumps: `1 / 1`, `3 / 4`… or `auto` (each image its own) |
|
|
644
|
+
| `detailImageMaxHeight` | `--vio-detail-image-max-height` | `85vh` | Cap on the main image's height |
|
|
645
|
+
| `detailImageZoom` | `--vio-detail-image-zoom` | `1` | Zoom; e.g. `1.15` crops a white margin baked into the file |
|
|
646
|
+
| `detailImageInset` | `--vio-detail-image-inset` | the padding | Side margin of the image; `0` = edge to edge |
|
|
647
|
+
| `detailImageBg` | `--vio-detail-image-bg` | muted surface | Behind the image |
|
|
648
|
+
| `detailThumbSize` / `detailThumbGap` | `--vio-detail-thumb-size` / `--vio-detail-thumb-gap` | `64px` / `8px` | Thumbnails — always one row: swipe on touch, arrows with a mouse, the selected one kept in view |
|
|
649
|
+
| `detailThumbRadius` / `detailThumbActiveColor` | `--vio-detail-thumb-radius` / `--vio-detail-thumb-active-color` | `0` / text color | Thumbnail corners, selected border |
|
|
650
|
+
| `detailOptionHeight` / `detailOptionGap` | `--vio-detail-option-height` / `--vio-detail-option-gap` | `36px` / `6px` | Size / option buttons (height is also their min width) |
|
|
651
|
+
| `detailOptionRadius` / `detailOptionWrap` | `--vio-detail-option-radius` / `--vio-detail-option-wrap` | `0` / `wrap` | `nowrap` = one line that scrolls sideways |
|
|
652
|
+
| `detailButtonHeight` / `detailButtonRadius` | `--vio-detail-button-height` / `--vio-detail-button-radius` | `52px` / `0` | Quantity stepper and «Legg i handlekurv» |
|
|
653
|
+
| `detailBuyPosition` / `detailBuyShadow` | `--vio-detail-buy-position` / `--vio-detail-buy-shadow` | phones `static`, desktop `sticky` | Whether the buy zone (Vipps, quantity, add to cart) stays at the bottom of the panel while scrolling |
|
|
654
|
+
| `cartWidth` / `checkoutWidth` | `--vio-cart-width` / `--vio-checkout-width` | `440px` | Drawer widths (desktop) |
|
|
655
|
+
| `confirmLogoHeight` / `confirmTitleSize` / `confirmCheckBg` | `--vio-confirm-logo-height` / `--vio-confirm-title-size` / `--vio-confirm-check-bg` | `40px` / `24px` / green | The confirmation |
|
|
656
|
+
| `redirectBg` / `redirectSpinner` | `--vio-redirect-bg` / `--vio-redirect-spinner` | translucent white / Vipps orange | The «Åpner Vipps …» overlay |
|
|
657
|
+
|
|
658
|
+
Also settable as CSS only: `--vio-detail-logo-max-width` (`220px`), `--vio-detail-image-position`,
|
|
659
|
+
`--vio-detail-image-radius`, `--vio-detail-thumb-fit` (`cover`), `--vio-detail-thumb-border-width`,
|
|
660
|
+
`--vio-detail-option-padding-x` (`12px`), `--vio-detail-option-font-size`, `--vio-detail-buy-gap`,
|
|
661
|
+
`--vio-detail-buy-padding`, `--vio-detail-buy-bg`, `--vio-detail-info-gap`, `--vio-detail-gallery-gap`,
|
|
662
|
+
`--vio-detail-title-weight`, `--vio-detail-price-weight`, `--vio-confirm-logo-max-width`,
|
|
663
|
+
`--vio-confirm-title-weight`, `--vio-confirm-text-size`, `--vio-redirect-color`, `--vio-redirect-font`,
|
|
664
|
+
`--vio-detail-seller-note-size` (`12px`), `--vio-detail-seller-note-align` (`center`).
|
|
665
|
+
|
|
666
|
+
**`::part` names**, for what the properties do not cover:
|
|
667
|
+
|
|
668
|
+
- `<vio-product-detail>`: `topbar`, `sponsor`, `close`, `gallery`, `image`, `thumbs`, `thumb`
|
|
669
|
+
(+ `thumb-active`), `thumbs-arrow`, `info`, `brand`, `title`, `price`, `stock`,
|
|
670
|
+
`option-group`, `option-label`, `options`, `option` (+ `option-selected`), `buy`,
|
|
671
|
+
`buy-vipps`, `quantity-row`, `add-to-cart`, `seller-note`, `seller-terms`, `seller-refund`,
|
|
672
|
+
`description`.
|
|
673
|
+
- `<vio-checkout>`: `sponsor`, `confirmation`, `confirm-title`, `confirm-sponsor`, `confirm-details`.
|
|
674
|
+
|
|
675
|
+
```css
|
|
676
|
+
vio-product-detail::part(add-to-cart) { letter-spacing: 0; text-transform: none; }
|
|
677
|
+
vio-product-detail::part(option-selected) { background: #2b2b2b; }
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**The detail's order:** the selling brand top left; the main image; the thumbnails;
|
|
681
|
+
name, price, stock, sizes; then, **on phones**, the buy zone — **Vipps first** (when the
|
|
682
|
+
channel has it), quantity + «Legg i handlekurv», then the other ways to pay, and under them
|
|
683
|
+
«Du handler direkte hos {brand}.» (the product's brand, never the article's) with links to
|
|
684
|
+
the brand's «Kjøpsvilkår» and «Angrerett og retur» when it gave them (the sponsor block's
|
|
685
|
+
`termsUrl` / `refundPolicyUrl`, https only, opened in a new tab) — and the
|
|
686
|
+
description; **on desktop**, the description first and the buy zone pinned at the bottom
|
|
687
|
+
of the panel. The description folds: «Beskrivelse» opens on a tap.
|
|
688
|
+
|
|
689
|
+
### The hand-over to Vipps (0.28)
|
|
690
|
+
|
|
691
|
+
Between the tap on Vipps (detail, cart or checkout) and the Vipps page, the SDK shows
|
|
692
|
+
a full-screen «Åpner Vipps …» with a soft fade — no empty moment, and it takes every
|
|
693
|
+
tap, so a second payment cannot start. Events on `document`:
|
|
694
|
+
|
|
695
|
+
| Event | Detail | When |
|
|
696
|
+
|---|---|---|
|
|
697
|
+
| `vio:payment-redirect-start` | `{ method, sponsorId, source }` | At the tap. **Cancelable**: `preventDefault()` and the SDK draws no overlay — draw your own |
|
|
698
|
+
| `vio:payment-redirect-error` | `… , reason` | It could not start. The overlay goes; the checkout opens with Vipps preselected |
|
|
699
|
+
| `vio:payment-redirect-cancel` | `… , reason` | `'express-off'` / `'not-started'`: the checkout opens instead (no error). `'back'`: the shopper came back with the browser's back button. `'cancelled'`: back through Vipps' own cancel (with `checkoutId`) |
|
|
700
|
+
|
|
701
|
+
`source` is `'detail'`, `'cart'` or `'checkout'`. The overlay is `.vio-redirect-overlay`
|
|
702
|
+
in the light DOM.
|
|
703
|
+
|
|
704
|
+
**The detail's Vipps button pays the whole cart.** «Kjøp nå med Vipps» on the product
|
|
705
|
+
detail adds the product to the sponsor's cart and the checkout is built on that cart —
|
|
706
|
+
every line in it. It goes straight to Vipps only when the cart was **empty**; with lines
|
|
707
|
+
already in it, the product is added and the checkout opens first (`vio:checkout-open`
|
|
708
|
+
with `paymentMethod: 'vipps'`, no hand-over events), every line and the total on screen,
|
|
709
|
+
so what the Vipps page lists is what the shopper saw. The cart's own Vipps button always
|
|
710
|
+
pays the whole cart — the lines are right there. **Apple Pay on the detail follows the same
|
|
711
|
+
rule**: its sheet's amount is fixed before the tap, so it opens only on an empty cart, for the
|
|
712
|
+
product × the chosen quantity; otherwise the checkout opens (`paymentMethod: 'apple-pay'`).
|
|
713
|
+
|
|
714
|
+
### What the shopper pays is what the page shows — the cart, the server, the other tabs
|
|
715
|
+
|
|
716
|
+
The cart lives in this tab's memory and in `localStorage` (`vio.cart.v1`, one entry per
|
|
717
|
+
sponsor with the backend cart id), and the checkout — and the Vipps / Stripe page — is
|
|
718
|
+
built on the **backend** cart, every line of it. Three things keep the two the same:
|
|
719
|
+
|
|
720
|
+
- **Before a checkout is created or a payment link minted**, and **when `<vio-cart>` or
|
|
721
|
+
`<vio-checkout>` opens**, the SDK asks the server for the cart (`GetCart`) and draws that
|
|
722
|
+
answer: a line another tab added, a cart another tab emptied. `Vio.cart.refreshFromServer(sponsorId)`
|
|
723
|
+
does the same on demand and resolves with the sponsor's cart. A `GetCart` that fails leaves
|
|
724
|
+
the cart as it was (and warns) — the checkout then goes on with the lines the last mutation
|
|
725
|
+
answered, as before.
|
|
726
|
+
- **Other tabs of the site are heard.** A payment in one tab (its cart cleared) empties the
|
|
727
|
+
drawer of the others and the next add there opens a new backend cart; a line added in one
|
|
728
|
+
tab is in the others without a reload. Through the browser's `storage` event — a tab that
|
|
729
|
+
is mid-add for a sponsor keeps its own lines until its answer lands.
|
|
730
|
+
- **An open checkout follows its cart**: its lines and its total are recomputed when the
|
|
731
|
+
cart changes under it (the widgets that captured an amount at mount ask to be refreshed,
|
|
732
|
+
as they already did for a changed cart).
|
|
733
|
+
|
|
734
|
+
`vio:cart-updated` on `document` says when either of the first two happened:
|
|
735
|
+
|
|
736
|
+
```ts
|
|
737
|
+
document.addEventListener('vio:cart-updated', (e) => {
|
|
738
|
+
const { sponsorId, cartId, items, itemCount, subtotal, currency, source } = e.detail
|
|
739
|
+
// source: 'server' (GetCart, right before a checkout / on opening) | 'storage' (another tab)
|
|
740
|
+
})
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
`items` are copies of the lines (`CartLineItem[]`). A host that draws its own cart or badge
|
|
744
|
+
redraws it from this event; `<vio-cart>` and the badge wired to `Vio.cart`'s `change` need nothing.
|
|
745
|
+
|
|
561
746
|
## Headless / SSR
|
|
562
747
|
|
|
563
748
|
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):
|