@vio-live/web-sdk 0.11.0 → 0.19.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.
@@ -13,10 +13,12 @@ var __decorateClass = (decorators, target, key, kind) => {
13
13
 
14
14
  // src/core/configuration.ts
15
15
  var DEFAULT_URLS = {
16
+ // The Vio Backend development environment was removed on 2026-09-16; its REST
17
+ // and events hosts now point at staging (Commerce GraphQL dev is unaffected).
16
18
  development: {
17
- apiBase: "https://api-dev.vio.live",
19
+ apiBase: "https://api-staging.vio.live",
18
20
  graphQLBase: "https://graph-ql-dev.vio.live",
19
- eventsBase: "https://events-dev.vio.live"
21
+ eventsBase: "https://events-staging.vio.live"
20
22
  },
21
23
  // No separate staging GraphQL deployment yet — shares dev's.
22
24
  testing: {
@@ -351,6 +353,19 @@ var GET_PRODUCTS_BY_IDS = (
351
353
  }
352
354
  `
353
355
  );
356
+ var GET_PRODUCT_SELLER = (
357
+ /* GraphQL */
358
+ `
359
+ query GetProductSeller($productIds: [Int!], $useCache: Boolean!) {
360
+ Channel {
361
+ GetProductsByIds(product_ids: $productIds, useCache: $useCache) {
362
+ id
363
+ supplier_company
364
+ }
365
+ }
366
+ }
367
+ `
368
+ );
354
369
  var ChannelProduct = class {
355
370
  constructor(client) {
356
371
  this.client = client;
@@ -372,6 +387,24 @@ var ChannelProduct = class {
372
387
  const data = await this.client.request(GET_PRODUCTS_BY_IDS, variables);
373
388
  return data.Channel?.GetProductsByIds ?? [];
374
389
  }
390
+ /**
391
+ * The seller's Company name for one product, or null (none set, or an API
392
+ * that does not have the field yet). Never throws.
393
+ *
394
+ * Never from the cache, on purpose: graphql caches product answers by
395
+ * path whatever fields the query asked, so this narrow query, written to
396
+ * the cache, would hand the next full product query a product with no
397
+ * price and no images. `useCache: false` neither reads nor writes it.
398
+ */
399
+ async getSellerCompany(productId) {
400
+ try {
401
+ const data = await this.client.request(GET_PRODUCT_SELLER, { productIds: [productId], useCache: false });
402
+ const name = String(data.Channel?.GetProductsByIds?.[0]?.supplier_company ?? "").trim();
403
+ return name || null;
404
+ } catch {
405
+ return null;
406
+ }
407
+ }
375
408
  };
376
409
  var Channel = class {
377
410
  constructor(client) {
@@ -1306,18 +1339,18 @@ query Config {
1306
1339
  `;
1307
1340
  async function getCartGraphQLOptions(sponsorId) {
1308
1341
  const facade = globalThis.__VIO_FACADE__;
1309
- if (facade && typeof facade.bootstrap === "function" && !facade.bootstrapCache) {
1342
+ if (!Configuration.isInitialized) {
1343
+ await Configuration.whenReady();
1344
+ }
1345
+ if (Configuration.isInitialized && facade && typeof facade.bootstrap === "function" && !facade.bootstrapCache) {
1310
1346
  try {
1311
1347
  await facade.bootstrap();
1312
1348
  } catch (err) {
1313
1349
  if (typeof console !== "undefined") {
1314
- console.warn("[Vio] getCartGraphQLOptions bootstrap await failed:", err);
1350
+ console.warn("[Vio] getCartGraphQLOptions bootstrap failed:", err);
1315
1351
  }
1316
1352
  }
1317
1353
  }
1318
- if (!Configuration.isInitialized) {
1319
- await Configuration.whenReady();
1320
- }
1321
1354
  const cfg = Configuration.isInitialized ? Configuration.get() : { apiKey: void 0, graphQLBase: "https://graph-ql-dev.vio.live" };
1322
1355
  const anyCfg = cfg;
1323
1356
  let commerceKey = anyCfg.apiKey;
@@ -1329,6 +1362,11 @@ async function getCartGraphQLOptions(sponsorId) {
1329
1362
  sponsorResolved = true;
1330
1363
  }
1331
1364
  }
1365
+ if (sponsorId && !sponsorResolved && typeof console !== "undefined") {
1366
+ console.warn(
1367
+ `[Vio] no commerce key for sponsor ${sponsorId} \u2014 falling back to the platform apiKey, which the commerce API will reject. Is the sponsor configured, and did Vio.init run before this call?`
1368
+ );
1369
+ }
1332
1370
  return {
1333
1371
  endpoint: anyCfg.graphQLBase || "https://graph-ql-dev.vio.live",
1334
1372
  apiKey: anyCfg.apiKey,
@@ -1420,6 +1458,21 @@ async function getCheckout(checkoutId, options) {
1420
1458
  const json = await executeCartGraphQL(GET_CHECKOUT_QUERY, { checkoutId }, options);
1421
1459
  return json?.data?.Checkout?.GetCheckout;
1422
1460
  }
1461
+ var GET_CHECKOUT_ORDER_NUMBER_QUERY = `
1462
+ query GetCheckoutOrderNumber($checkoutId: String!) {
1463
+ Checkout {
1464
+ GetCheckout(checkout_id: $checkoutId) {
1465
+ id
1466
+ order_id
1467
+ }
1468
+ }
1469
+ }
1470
+ `;
1471
+ async function getCheckoutOrderNumber(checkoutId, options) {
1472
+ const json = await executeCartGraphQL(GET_CHECKOUT_ORDER_NUMBER_QUERY, { checkoutId }, options);
1473
+ const n = Number(json?.data?.Checkout?.GetCheckout?.order_id);
1474
+ return Number.isInteger(n) && n > 0 ? n : null;
1475
+ }
1423
1476
  async function getAvailablePaymentMethods(options) {
1424
1477
  const json = await executeCartGraphQL(GET_AVAILABLE_PAYMENT_METHODS_QUERY, {}, options);
1425
1478
  return json?.data?.Payment?.GetAvailablePaymentMethods;
@@ -1469,10 +1522,13 @@ async function confirmPaymentApplePay(variables, options) {
1469
1522
 
1470
1523
  // src/core/checkout/payments/vipps.ts
1471
1524
  var CREATE_PAYMENT_VIPPS_MUTATION = `
1472
- mutation CreatePaymentVipps($checkoutId: String!, $email: String!, $returnUrl: String!) {
1525
+ mutation CreatePaymentVipps($checkoutId: String!, $email: String, $returnUrl: String!, $express: Boolean) {
1473
1526
  Payment {
1474
- CreatePaymentVipps(checkout_id: $checkoutId, email: $email, return_url: $returnUrl) {
1527
+ CreatePaymentVipps(checkout_id: $checkoutId, email: $email, return_url: $returnUrl, express: $express) {
1475
1528
  payment_url
1529
+ reference
1530
+ express
1531
+ reason
1476
1532
  }
1477
1533
  }
1478
1534
  }
@@ -2159,6 +2215,97 @@ async function prepareApplePay(config) {
2159
2215
  }
2160
2216
  }
2161
2217
 
2218
+ // src/core/version.ts
2219
+ var SDK_VERSION = "0.19.0";
2220
+
2221
+ // src/core/checkout/payments/stripe-embedded.ts
2222
+ var CREATE_PAYMENT_INTENT_STRIPE_MUTATION = `
2223
+ mutation CreatePaymentIntentStripe($checkoutId: String!) {
2224
+ Payment {
2225
+ CreatePaymentIntentStripe(checkout_id: $checkoutId) {
2226
+ client_secret
2227
+ publishable_key
2228
+ customer
2229
+ }
2230
+ }
2231
+ }
2232
+ `;
2233
+ async function createStripeIntent(checkoutId, options) {
2234
+ const json = await executeCartGraphQL(
2235
+ CREATE_PAYMENT_INTENT_STRIPE_MUTATION,
2236
+ { checkoutId },
2237
+ options
2238
+ );
2239
+ return json?.data?.Payment?.CreatePaymentIntentStripe ?? null;
2240
+ }
2241
+ var SETTLED = /* @__PURE__ */ new Set(["succeeded"]);
2242
+ var IN_FLIGHT = /* @__PURE__ */ new Set(["processing", "requires_capture"]);
2243
+ async function mountStripeElement(container, intent, options) {
2244
+ if (!intent?.client_secret || !intent.publishable_key) {
2245
+ throw new Error("[Stripe] the payment is missing its client secret or publishable key");
2246
+ }
2247
+ await loadStripeJs();
2248
+ const Stripe = globalThis.Stripe;
2249
+ if (typeof Stripe !== "function") {
2250
+ throw new Error("[Stripe] stripe.js loaded but window.Stripe is missing");
2251
+ }
2252
+ const stripe = intent.stripe_account ? Stripe(intent.publishable_key, { stripeAccount: intent.stripe_account }) : Stripe(intent.publishable_key);
2253
+ const appearance = { theme: "stripe" };
2254
+ const variables = {};
2255
+ if (options.theme?.accent) variables.colorPrimary = options.theme.accent;
2256
+ if (options.theme?.radiusMd) variables.borderRadius = options.theme.radiusMd;
2257
+ if (Object.keys(variables).length > 0) appearance.variables = variables;
2258
+ const elements = stripe.elements({
2259
+ clientSecret: intent.client_secret,
2260
+ appearance,
2261
+ locale: options.locale ?? "nb"
2262
+ });
2263
+ const element = elements.create("payment", { layout: "tabs" });
2264
+ if (options.onReady) element.on("ready", () => options.onReady?.());
2265
+ if (options.onLoadError) {
2266
+ element.on(
2267
+ "loaderror",
2268
+ (event) => options.onLoadError?.(String(event?.error?.message ?? "Stripe could not load the form"))
2269
+ );
2270
+ }
2271
+ container.innerHTML = "";
2272
+ element.mount(container);
2273
+ let confirming = false;
2274
+ return {
2275
+ async confirm() {
2276
+ if (confirming) return { status: "pending" };
2277
+ confirming = true;
2278
+ try {
2279
+ const result = await stripe.confirmPayment({
2280
+ elements,
2281
+ confirmParams: { return_url: options.returnUrl },
2282
+ redirect: "if_required"
2283
+ });
2284
+ if (result?.error) {
2285
+ return {
2286
+ status: "failed",
2287
+ message: String(result.error.message ?? ""),
2288
+ code: String(result.error.code ?? "")
2289
+ };
2290
+ }
2291
+ const paymentIntent = result?.paymentIntent;
2292
+ const status = String(paymentIntent?.status ?? "");
2293
+ if (SETTLED.has(status)) return { status: "succeeded", paymentIntentId: paymentIntent?.id };
2294
+ if (IN_FLIGHT.has(status)) return { status: "pending", paymentIntentId: paymentIntent?.id };
2295
+ return { status: "failed", code: status };
2296
+ } finally {
2297
+ confirming = false;
2298
+ }
2299
+ },
2300
+ unmount() {
2301
+ try {
2302
+ element.unmount();
2303
+ } catch {
2304
+ }
2305
+ }
2306
+ };
2307
+ }
2308
+
2162
2309
  // src/core/checkout/payments/klarna.ts
2163
2310
  var KLARNA_SDK_URL = "https://js.klarna.com/web-sdk/v2/klarna.mjs";
2164
2311
  var sdkCache = /* @__PURE__ */ new Map();
@@ -2519,15 +2666,23 @@ async function createKlarnaPaymentsWidget(input) {
2519
2666
  }
2520
2667
 
2521
2668
  // src/core/checkout/payments/kustom.ts
2522
- var CREATE_PAYMENT_KUSTOM_MUTATION = `
2523
- mutation CreatePaymentKustom($checkoutId: String!, $countryCode: String!, $href: String!, $email: String) {
2524
- Payment {
2525
- CreatePaymentKustom(checkout_id: $checkoutId, country_code: $countryCode, href: $href, email: $email) {
2669
+ var KUSTOM_ORDER_FIELDS = `
2526
2670
  order_id
2527
2671
  status
2528
2672
  html_snippet
2529
2673
  purchase_country
2530
2674
  purchase_currency
2675
+ checkout_id
2676
+ environment
2677
+ total_price
2678
+ shipping_name
2679
+ shipping_price
2680
+ order_created
2681
+ reference`;
2682
+ var CREATE_PAYMENT_KUSTOM_MUTATION = `
2683
+ mutation CreatePaymentKustom($checkoutId: String!, $countryCode: String!, $href: String!, $email: String, $client: String) {
2684
+ Payment {
2685
+ CreatePaymentKustom(checkout_id: $checkoutId, country_code: $countryCode, href: $href, email: $email, client: $client) {${KUSTOM_ORDER_FIELDS}
2531
2686
  }
2532
2687
  }
2533
2688
  }
@@ -2535,12 +2690,15 @@ mutation CreatePaymentKustom($checkoutId: String!, $countryCode: String!, $href:
2535
2690
  var GET_KUSTOM_ORDER_QUERY = `
2536
2691
  query GetKustomOrder($orderId: String!) {
2537
2692
  Payment {
2538
- GetKustomOrder(order_id: $orderId) {
2539
- order_id
2540
- status
2541
- html_snippet
2542
- purchase_country
2543
- purchase_currency
2693
+ GetKustomOrder(order_id: $orderId) {${KUSTOM_ORDER_FIELDS}
2694
+ }
2695
+ }
2696
+ }
2697
+ `;
2698
+ var SYNC_PAYMENT_KUSTOM_MUTATION = `
2699
+ mutation SyncPaymentKustom($checkoutId: String!) {
2700
+ Payment {
2701
+ SyncPaymentKustom(checkout_id: $checkoutId) {${KUSTOM_ORDER_FIELDS}
2544
2702
  }
2545
2703
  }
2546
2704
  }
@@ -2553,6 +2711,10 @@ async function getKustomOrder(orderId, options) {
2553
2711
  const json = await executeCartGraphQL(GET_KUSTOM_ORDER_QUERY, { orderId }, options);
2554
2712
  return json?.data?.Payment?.GetKustomOrder ?? null;
2555
2713
  }
2714
+ async function syncPaymentKustom(checkoutId, options) {
2715
+ const json = await executeCartGraphQL(SYNC_PAYMENT_KUSTOM_MUTATION, { checkoutId }, options);
2716
+ return json?.data?.Payment?.SyncPaymentKustom ?? null;
2717
+ }
2556
2718
  function kustomCleanHref() {
2557
2719
  if (typeof window === "undefined" || !window.location) return "";
2558
2720
  return `${window.location.origin}${window.location.pathname}`;
@@ -2569,6 +2731,64 @@ function renderKustomSnippet(container, htmlSnippet) {
2569
2731
  oldScript.parentNode?.replaceChild(newScript, oldScript);
2570
2732
  }
2571
2733
  }
2734
+ function kustomApi() {
2735
+ if (typeof window === "undefined") return null;
2736
+ const fn = window._klarnaCheckout;
2737
+ return typeof fn === "function" ? fn : null;
2738
+ }
2739
+ function suspendKustom() {
2740
+ const api = kustomApi();
2741
+ if (!api) return false;
2742
+ api((a) => a.suspend());
2743
+ return true;
2744
+ }
2745
+ function resumeKustom() {
2746
+ const api = kustomApi();
2747
+ if (!api) return false;
2748
+ api((a) => a.resume());
2749
+ return true;
2750
+ }
2751
+ function installKustomListeners(handlers) {
2752
+ let alive = true;
2753
+ const api = kustomApi();
2754
+ if (api) {
2755
+ api((a) => {
2756
+ a.on({
2757
+ load: (data) => alive && handlers.onLoad?.(data),
2758
+ order_total_change: (data) => {
2759
+ const total = Number(data?.order_total);
2760
+ if (alive && Number.isFinite(total)) handlers.onOrderTotalChange?.(total);
2761
+ },
2762
+ shipping_option_change: (data) => alive && handlers.onShippingOptionChange?.(data ?? {}),
2763
+ can_not_complete_order: () => alive && handlers.onCannotComplete?.(),
2764
+ network_error: () => alive && handlers.onNetworkError?.(),
2765
+ redirect_initiated: () => alive && handlers.onRedirectInitiated?.()
2766
+ });
2767
+ });
2768
+ }
2769
+ return {
2770
+ destroy() {
2771
+ alive = false;
2772
+ }
2773
+ };
2774
+ }
2775
+ var KUSTOM_RETURN_QUERY_KEYS = [
2776
+ "order_id",
2777
+ "payment_processor",
2778
+ "checkout_id",
2779
+ "vio_payment",
2780
+ "vio_method"
2781
+ ];
2782
+ function kustomReturnOutcome(params) {
2783
+ const orderId = params.get("order_id") || "";
2784
+ if (params.get("payment_processor") === "KUSTOM" && orderId) {
2785
+ return { kind: "confirmation", orderId, checkoutId: params.get("checkout_id") || void 0 };
2786
+ }
2787
+ if (params.get("vio_payment") === "rejected" && params.get("vio_method") === "kustom") {
2788
+ return { kind: "rejected", checkoutId: params.get("checkout_id") || void 0 };
2789
+ }
2790
+ return null;
2791
+ }
2572
2792
 
2573
2793
  // src/core/checkout/payments/qliro.ts
2574
2794
  function readVioTheme() {
@@ -2654,7 +2874,8 @@ function installQliroListeners(options) {
2654
2874
  const win = globalThis;
2655
2875
  let q1 = null;
2656
2876
  let destroyed = false;
2657
- let pending = null;
2877
+ let orderUpdatedRegistered = false;
2878
+ let pending2 = null;
2658
2879
  const emit = (event) => {
2659
2880
  try {
2660
2881
  onEvent?.(event);
@@ -2662,10 +2883,10 @@ function installQliroListeners(options) {
2662
2883
  }
2663
2884
  };
2664
2885
  const settle = () => {
2665
- if (!pending) return;
2666
- clearTimeout(pending.timer);
2667
- const { resolve } = pending;
2668
- pending = null;
2886
+ if (!pending2) return;
2887
+ clearTimeout(pending2.timer);
2888
+ const { resolve } = pending2;
2889
+ pending2 = null;
2669
2890
  try {
2670
2891
  q1?.unlock();
2671
2892
  } catch {
@@ -2677,8 +2898,8 @@ function installQliroListeners(options) {
2677
2898
  return !echo || echo === version;
2678
2899
  };
2679
2900
  const settleIfAcknowledged = () => {
2680
- if (!pending) return;
2681
- if (pending.echoes.some((echo) => acknowledges(echo, pending.version))) {
2901
+ if (!pending2) return;
2902
+ if (pending2.echoes.some((echo) => acknowledges(echo, pending2.version))) {
2682
2903
  settle();
2683
2904
  }
2684
2905
  };
@@ -2691,12 +2912,6 @@ function installQliroListeners(options) {
2691
2912
  if (destroyed) return;
2692
2913
  q1 = instance;
2693
2914
  instance.onCheckoutLoaded(() => emit({ type: "loaded" }));
2694
- instance.onOrderUpdated((order) => {
2695
- if (!pending) return;
2696
- const echoed = order?.merchantUpdateVersion ?? order?.MerchantUpdateVersion ?? null;
2697
- pending.echoes.push(echoed);
2698
- settleIfAcknowledged();
2699
- });
2700
2915
  instance.onShippingMethodChanged(
2701
2916
  (shipping) => emit({ type: "shipping-changed", shipping })
2702
2917
  );
@@ -2720,13 +2935,22 @@ function installQliroListeners(options) {
2720
2935
  const controller = {
2721
2936
  async sync() {
2722
2937
  if (destroyed) return null;
2723
- if (pending) settle();
2938
+ if (pending2) settle();
2724
2939
  try {
2725
2940
  q1?.lock();
2941
+ if (q1 && !orderUpdatedRegistered) {
2942
+ orderUpdatedRegistered = true;
2943
+ q1.onOrderUpdated((order) => {
2944
+ if (!pending2) return;
2945
+ const echoed = order?.merchantUpdateVersion ?? order?.MerchantUpdateVersion ?? null;
2946
+ pending2.echoes.push(echoed);
2947
+ settleIfAcknowledged();
2948
+ });
2949
+ }
2726
2950
  } catch {
2727
2951
  }
2728
2952
  const settled = new Promise((resolve) => {
2729
- pending = {
2953
+ pending2 = {
2730
2954
  version: null,
2731
2955
  echoes: [],
2732
2956
  resolve,
@@ -2747,8 +2971,8 @@ function installQliroListeners(options) {
2747
2971
  await settled;
2748
2972
  return result;
2749
2973
  }
2750
- if (pending) {
2751
- pending.version = result.update_version;
2974
+ if (pending2) {
2975
+ pending2.version = result.update_version;
2752
2976
  settleIfAcknowledged();
2753
2977
  }
2754
2978
  await settled;
@@ -2805,6 +3029,441 @@ async function getWalleyOrder(checkoutId, options) {
2805
3029
  }
2806
3030
  var WALLEY_PURCHASE_COMPLETED_EVENT = "walleyCheckoutPurchaseCompleted";
2807
3031
 
3032
+ // src/core/checkout/payments/nexi.ts
3033
+ var NEXI_SHIPPING_FIELDS = `
3034
+ ok
3035
+ reason
3036
+ order_id
3037
+ total_price
3038
+ shipping_name
3039
+ shipping_price
3040
+ shipping_id
3041
+ options { id name price }
3042
+ country
3043
+ postal_code
3044
+ address_source
3045
+ changed`;
3046
+ var NEXI_ORDER_FIELDS = `
3047
+ order_id
3048
+ status
3049
+ checkout_key
3050
+ checkout_js_url
3051
+ purchase_country
3052
+ purchase_currency
3053
+ total_price
3054
+ payment_method
3055
+ card_last4
3056
+ email
3057
+ shipping {${NEXI_SHIPPING_FIELDS}
3058
+ }`;
3059
+ var CREATE_PAYMENT_NEXI_MUTATION = `
3060
+ mutation CreatePaymentNexi($checkoutId: String!, $countryCode: String!, $href: String!, $email: String) {
3061
+ Payment {
3062
+ CreatePaymentNexi(checkout_id: $checkoutId, country_code: $countryCode, href: $href, email: $email) {${NEXI_ORDER_FIELDS}
3063
+ }
3064
+ }
3065
+ }
3066
+ `;
3067
+ var GET_NEXI_ORDER_QUERY = `
3068
+ query GetNexiOrder($checkoutId: String!) {
3069
+ Payment {
3070
+ GetNexiOrder(checkout_id: $checkoutId) {${NEXI_ORDER_FIELDS}
3071
+ }
3072
+ }
3073
+ }
3074
+ `;
3075
+ var UPDATE_NEXI_SHIPPING_MUTATION = `
3076
+ mutation UpdateNexiShipping($checkoutId: String!, $countryCode: String!, $postalCode: String, $shippingId: String) {
3077
+ Payment {
3078
+ UpdateNexiShipping(checkout_id: $checkoutId, country_code: $countryCode, postal_code: $postalCode, shipping_id: $shippingId) {${NEXI_SHIPPING_FIELDS}
3079
+ }
3080
+ }
3081
+ }
3082
+ `;
3083
+ async function createPaymentNexi(variables, options) {
3084
+ const json = await executeCartGraphQL(CREATE_PAYMENT_NEXI_MUTATION, variables, options);
3085
+ return json?.data?.Payment?.CreatePaymentNexi ?? null;
3086
+ }
3087
+ async function getNexiOrder(checkoutId, options) {
3088
+ const json = await executeCartGraphQL(GET_NEXI_ORDER_QUERY, { checkoutId }, options);
3089
+ return json?.data?.Payment?.GetNexiOrder ?? null;
3090
+ }
3091
+ async function updateNexiShipping(variables, options) {
3092
+ const json = await executeCartGraphQL(UPDATE_NEXI_SHIPPING_MUTATION, variables, options);
3093
+ return json?.data?.Payment?.UpdateNexiShipping ?? null;
3094
+ }
3095
+ var NEXI_PAID_STATUSES = /* @__PURE__ */ new Set(["Reserved", "Charged"]);
3096
+ function isNexiPaid(status) {
3097
+ return NEXI_PAID_STATUSES.has(String(status ?? ""));
3098
+ }
3099
+ var NEXI_LANGUAGES = {
3100
+ NO: "nb-NO",
3101
+ SE: "sv-SE",
3102
+ DK: "da-DK",
3103
+ FI: "fi-FI",
3104
+ DE: "de-DE",
3105
+ NL: "nl-NL",
3106
+ PL: "pl-PL",
3107
+ FR: "fr-FR",
3108
+ ES: "es-ES",
3109
+ IT: "it-IT",
3110
+ EE: "ee-EE",
3111
+ LV: "lv-LV",
3112
+ LT: "lt-LT",
3113
+ SK: "sk-SK"
3114
+ };
3115
+ function nexiLanguageFor(countryCode) {
3116
+ return NEXI_LANGUAGES[String(countryCode ?? "").toUpperCase()] ?? "en-GB";
3117
+ }
3118
+ function nexiThemeFrom(theme) {
3119
+ if (!theme) return void 0;
3120
+ const out = {};
3121
+ if (theme.accent) out.primaryColor = theme.accent;
3122
+ if (theme.surface) out.panelColor = theme.surface;
3123
+ if (theme.radiusMd && /^\d+(\.\d+)?px$/.test(theme.radiusMd)) out.buttonRadius = theme.radiusMd;
3124
+ return Object.keys(out).length ? out : void 0;
3125
+ }
3126
+ var scriptLoads = /* @__PURE__ */ new Map();
3127
+ function loadNexiCheckoutJs(url) {
3128
+ const g = globalThis;
3129
+ if (g.Dibs?.Checkout && g.__vioNexiCheckoutJs === url) return Promise.resolve();
3130
+ const inFlight = scriptLoads.get(url);
3131
+ if (inFlight) return inFlight;
3132
+ const load = new Promise((resolve, reject) => {
3133
+ const doc = g.document;
3134
+ if (!doc?.head) {
3135
+ reject(new Error("[Nexi] no document to load checkout.js into"));
3136
+ return;
3137
+ }
3138
+ const script = doc.createElement("script");
3139
+ script.src = url;
3140
+ script.async = true;
3141
+ script.onload = () => {
3142
+ if (g.Dibs?.Checkout) {
3143
+ g.__vioNexiCheckoutJs = url;
3144
+ resolve();
3145
+ } else {
3146
+ scriptLoads.delete(url);
3147
+ reject(new Error("[Nexi] checkout.js loaded but Dibs.Checkout is missing"));
3148
+ }
3149
+ };
3150
+ script.onerror = () => {
3151
+ scriptLoads.delete(url);
3152
+ reject(new Error(`[Nexi] could not load ${url}`));
3153
+ };
3154
+ doc.head.appendChild(script);
3155
+ });
3156
+ scriptLoads.set(url, load);
3157
+ return load;
3158
+ }
3159
+ async function mountNexi(container, order, options = {}) {
3160
+ if (!order?.order_id || !order.checkout_key || !order.checkout_js_url) {
3161
+ throw new Error("[Nexi] order is missing paymentId, checkout_key or checkout_js_url");
3162
+ }
3163
+ await loadNexiCheckoutJs(order.checkout_js_url);
3164
+ if (!container.id) container.id = "vio-nexi-checkout-container";
3165
+ const Dibs = globalThis.Dibs;
3166
+ const checkout = new Dibs.Checkout({
3167
+ checkoutKey: order.checkout_key,
3168
+ paymentId: order.order_id,
3169
+ containerId: container.id,
3170
+ language: options.language ?? "en-GB",
3171
+ ...options.theme ? { theme: options.theme } : {}
3172
+ });
3173
+ return checkout;
3174
+ }
3175
+ var NEXI_PENDING_KEY = "vio.nexi.pending.v1";
3176
+ var NEXI_PENDING_TTL_MS = 48 * 60 * 60 * 1e3;
3177
+ function storage() {
3178
+ try {
3179
+ const s = globalThis.sessionStorage;
3180
+ return s ?? null;
3181
+ } catch {
3182
+ return null;
3183
+ }
3184
+ }
3185
+ function rememberNexiPending(pending2) {
3186
+ try {
3187
+ storage()?.setItem(NEXI_PENDING_KEY, JSON.stringify({ ...pending2, createdAt: Date.now() }));
3188
+ } catch {
3189
+ }
3190
+ }
3191
+ function readNexiPending() {
3192
+ try {
3193
+ const raw = storage()?.getItem(NEXI_PENDING_KEY);
3194
+ if (!raw) return null;
3195
+ const p = JSON.parse(raw);
3196
+ if (!p?.checkoutId || !p?.paymentId) return null;
3197
+ if (Date.now() - Number(p.createdAt ?? 0) > NEXI_PENDING_TTL_MS) {
3198
+ clearNexiPending();
3199
+ return null;
3200
+ }
3201
+ return {
3202
+ checkoutId: String(p.checkoutId),
3203
+ sponsorId: Number(p.sponsorId ?? 0),
3204
+ paymentId: String(p.paymentId),
3205
+ createdAt: Number(p.createdAt ?? 0)
3206
+ };
3207
+ } catch {
3208
+ return null;
3209
+ }
3210
+ }
3211
+ function clearNexiPending() {
3212
+ try {
3213
+ storage()?.removeItem(NEXI_PENDING_KEY);
3214
+ } catch {
3215
+ }
3216
+ }
3217
+ function nexiReturnPaymentId(search) {
3218
+ try {
3219
+ const s = search ?? globalThis.location?.search ?? "";
3220
+ const id = new URLSearchParams(s).get("paymentId");
3221
+ return id && /^[0-9a-f]{32}$/i.test(id) ? id : null;
3222
+ } catch {
3223
+ return null;
3224
+ }
3225
+ }
3226
+
3227
+ // src/core/checkout/payments/adyen.ts
3228
+ var ADYEN_SESSION_FIELDS = `
3229
+ order_id
3230
+ session_id
3231
+ session_data
3232
+ client_key
3233
+ environment
3234
+ country_code
3235
+ shopper_locale
3236
+ purchase_currency
3237
+ amount
3238
+ total_price
3239
+ shipping_name
3240
+ shipping_price
3241
+ expires_at`;
3242
+ var ADYEN_PAYMENT_FIELDS = `
3243
+ order_id
3244
+ status
3245
+ result_code
3246
+ psp_reference
3247
+ payment_method
3248
+ purchase_country
3249
+ purchase_currency
3250
+ total_price
3251
+ shipping_name
3252
+ shipping_price
3253
+ email
3254
+ order_created`;
3255
+ var CREATE_PAYMENT_ADYEN_MUTATION = `
3256
+ mutation CreatePaymentAdyen($checkoutId: String!, $returnUrl: String!, $countryCode: String, $channel: String, $email: String, $client: String) {
3257
+ Payment {
3258
+ CreatePaymentAdyen(checkout_id: $checkoutId, return_url: $returnUrl, country_code: $countryCode, channel: $channel, email: $email, client: $client) {${ADYEN_SESSION_FIELDS}
3259
+ }
3260
+ }
3261
+ }
3262
+ `;
3263
+ var GET_ADYEN_PAYMENT_QUERY = `
3264
+ query GetAdyenPayment($checkoutId: String!, $sessionResult: String, $redirectResult: String) {
3265
+ Payment {
3266
+ GetAdyenPayment(checkout_id: $checkoutId, session_result: $sessionResult, redirect_result: $redirectResult) {${ADYEN_PAYMENT_FIELDS}
3267
+ }
3268
+ }
3269
+ }
3270
+ `;
3271
+ var CONFIRM_ADYEN_PAYMENT_MUTATION = `
3272
+ mutation ConfirmAdyenPayment($checkoutId: String!, $sessionId: String!) {
3273
+ Payment {
3274
+ ConfirmAdyenPayment(checkout_id: $checkoutId, session_id: $sessionId) {
3275
+ ok
3276
+ reason
3277
+ }
3278
+ }
3279
+ }
3280
+ `;
3281
+ async function createPaymentAdyen(variables, options) {
3282
+ const json = await executeCartGraphQL(CREATE_PAYMENT_ADYEN_MUTATION, variables, options);
3283
+ return json?.data?.Payment?.CreatePaymentAdyen ?? null;
3284
+ }
3285
+ async function getAdyenPayment(variables, options) {
3286
+ const json = await executeCartGraphQL(GET_ADYEN_PAYMENT_QUERY, variables, options);
3287
+ return json?.data?.Payment?.GetAdyenPayment ?? null;
3288
+ }
3289
+ async function confirmAdyenPayment(variables, options) {
3290
+ const json = await executeCartGraphQL(CONFIRM_ADYEN_PAYMENT_MUTATION, variables, options);
3291
+ return json?.data?.Payment?.ConfirmAdyenPayment ?? null;
3292
+ }
3293
+ var ADYEN_WEB_VERSION = "6.45.0";
3294
+ var ADYEN_WEB_JS_SRI = "sha384-7KW64UN6T0xZoacpNpRg2KVxMMVwfqwUNTDmalkE1roPBtjOpWe8JuJoUU7OCMvF";
3295
+ var ADYEN_WEB_CSS_SRI = "sha384-A4V58Hp1NTZmMvKn29pBh7249tw4TmfdL2szCtpKvaYfR/ARbXlXRYU20SLp/1VW";
3296
+ var ADYEN_ENVIRONMENTS = ["test", "live", "live-us", "live-au", "live-apse", "live-in"];
3297
+ function adyenAssetUrls(environment) {
3298
+ const env = ADYEN_ENVIRONMENTS.includes(environment) ? environment : "test";
3299
+ const base = `https://checkoutshopper-${env}.cdn.adyen.com/checkoutshopper/sdk/${ADYEN_WEB_VERSION}`;
3300
+ return { js: `${base}/adyen.js`, css: `${base}/adyen.css` };
3301
+ }
3302
+ var assetLoads = /* @__PURE__ */ new Map();
3303
+ function loadAdyenWeb(environment) {
3304
+ const g = globalThis;
3305
+ const { js, css } = adyenAssetUrls(environment);
3306
+ if (g.AdyenWeb?.AdyenCheckout && g.__vioAdyenWeb === js) return Promise.resolve();
3307
+ const inFlight = assetLoads.get(js);
3308
+ if (inFlight) return inFlight;
3309
+ const load = new Promise((resolve, reject) => {
3310
+ const doc = g.document;
3311
+ if (!doc?.head) {
3312
+ reject(new Error("[Adyen] no document to load Adyen Web into"));
3313
+ return;
3314
+ }
3315
+ if (!doc.head.querySelector(`link[data-vio-adyen="${ADYEN_WEB_VERSION}"]`)) {
3316
+ const link = doc.createElement("link");
3317
+ link.rel = "stylesheet";
3318
+ link.href = css;
3319
+ link.integrity = ADYEN_WEB_CSS_SRI;
3320
+ link.crossOrigin = "anonymous";
3321
+ link.setAttribute("data-vio-adyen", ADYEN_WEB_VERSION);
3322
+ doc.head.appendChild(link);
3323
+ }
3324
+ const script = doc.createElement("script");
3325
+ script.src = js;
3326
+ script.async = true;
3327
+ script.integrity = ADYEN_WEB_JS_SRI;
3328
+ script.crossOrigin = "anonymous";
3329
+ script.onload = () => {
3330
+ if (g.AdyenWeb?.AdyenCheckout && g.AdyenWeb?.Dropin) {
3331
+ g.__vioAdyenWeb = js;
3332
+ resolve();
3333
+ } else {
3334
+ reject(new Error("[Adyen] adyen.js loaded but AdyenWeb.AdyenCheckout is missing"));
3335
+ }
3336
+ };
3337
+ script.onerror = () => {
3338
+ reject(new Error(`[Adyen] could not load ${js}`));
3339
+ };
3340
+ doc.head.appendChild(script);
3341
+ }).finally(() => {
3342
+ assetLoads.delete(js);
3343
+ });
3344
+ assetLoads.set(js, load);
3345
+ return load;
3346
+ }
3347
+ var AdyenOriginError = class extends Error {
3348
+ constructor(origin) {
3349
+ super(
3350
+ `[Adyen] ${origin} is not an allowed origin of this Adyen client key \u2014 add it under Developers > API credentials > Client settings > Allowed origins.`
3351
+ );
3352
+ this.origin = origin;
3353
+ this.name = "AdyenOriginError";
3354
+ }
3355
+ };
3356
+ var looksLikeOriginProblem = (err) => /origin|client\s?key|14_0381|401|403/i.test(String(err?.message ?? err ?? ""));
3357
+ async function mountAdyen(container, session, handlers, options = {}) {
3358
+ if (!session?.session_id || !session.session_data || !session.client_key) {
3359
+ throw new Error("[Adyen] session is missing session_id, session_data or client_key");
3360
+ }
3361
+ await loadAdyenWeb(session.environment);
3362
+ const { AdyenCheckout, Dropin } = globalThis.AdyenWeb;
3363
+ if (options.theme?.accent) {
3364
+ container.style.setProperty("--adyen-sdk-color-background-always-dark", options.theme.accent);
3365
+ }
3366
+ if (options.theme?.radiusMd && /^\d+(\.\d+)?px$/.test(options.theme.radiusMd)) {
3367
+ container.style.setProperty("--adyen-sdk-border-radius-m", options.theme.radiusMd);
3368
+ }
3369
+ let paying = false;
3370
+ let checkout;
3371
+ try {
3372
+ checkout = await AdyenCheckout({
3373
+ session: { id: session.session_id, sessionData: session.session_data },
3374
+ clientKey: session.client_key,
3375
+ environment: session.environment,
3376
+ locale: session.shopper_locale,
3377
+ countryCode: session.country_code,
3378
+ amount: { value: session.amount, currency: session.purchase_currency },
3379
+ beforeSubmit: async (data, _component, actions) => {
3380
+ if (paying) {
3381
+ actions.reject();
3382
+ return;
3383
+ }
3384
+ paying = true;
3385
+ let proceed = false;
3386
+ try {
3387
+ proceed = await handlers.onBeforePay();
3388
+ } catch {
3389
+ proceed = false;
3390
+ }
3391
+ if (proceed) {
3392
+ actions.resolve(data);
3393
+ } else {
3394
+ paying = false;
3395
+ actions.reject();
3396
+ }
3397
+ },
3398
+ onPaymentCompleted: (result) => {
3399
+ paying = false;
3400
+ handlers.onCompleted(result ?? {});
3401
+ },
3402
+ onPaymentFailed: (result) => {
3403
+ paying = false;
3404
+ handlers.onFailed(result ?? {});
3405
+ },
3406
+ onError: (error) => {
3407
+ paying = false;
3408
+ handlers.onError?.(error);
3409
+ }
3410
+ });
3411
+ } catch (err) {
3412
+ if (looksLikeOriginProblem(err)) {
3413
+ throw new AdyenOriginError(globalThis.location?.origin ?? "this page");
3414
+ }
3415
+ throw err;
3416
+ }
3417
+ const dropin = new Dropin(checkout, {
3418
+ // Vio's own confirmation follows; Adyen's success animation would sit
3419
+ // between the payment and it.
3420
+ disableFinalAnimation: true,
3421
+ instantPaymentTypes: ["applepay", "googlepay"]
3422
+ }).mount(container);
3423
+ return {
3424
+ sessionId: session.session_id,
3425
+ unmount: () => {
3426
+ try {
3427
+ dropin.unmount();
3428
+ } catch {
3429
+ }
3430
+ }
3431
+ };
3432
+ }
3433
+ var ADYEN_RETURN_METHOD = "adyen";
3434
+ function adyenReturnParams(search = globalThis?.location?.search ?? "") {
3435
+ try {
3436
+ const params = new URLSearchParams(search);
3437
+ const redirectResult = params.get("redirectResult");
3438
+ if (!redirectResult) return null;
3439
+ return { redirectResult, sessionId: params.get("sessionId") };
3440
+ } catch {
3441
+ return null;
3442
+ }
3443
+ }
3444
+ var ADYEN_RETURN_QUERY_KEYS = [
3445
+ "vio_payment",
3446
+ "vio_method",
3447
+ "vio_sponsor",
3448
+ "checkout_id",
3449
+ "sessionId",
3450
+ "redirectResult"
3451
+ ];
3452
+ function adyenReturnOutcome(payment) {
3453
+ switch (payment?.status) {
3454
+ case "authorised":
3455
+ return "paid";
3456
+ case "refused":
3457
+ case "cancelled":
3458
+ case "error":
3459
+ return "failed";
3460
+ // pending (Swish, Trustly…) and anything unrecognised: the webhook
3461
+ // decides, and offering to pay again is the one thing not to do.
3462
+ default:
3463
+ return "pending";
3464
+ }
3465
+ }
3466
+
2808
3467
  // src/core/checkout/checkout-manager.ts
2809
3468
  function describeError(err) {
2810
3469
  if (err instanceof Error) return err.message;
@@ -2993,8 +3652,33 @@ function buildCheckoutAddress(address) {
2993
3652
  country_code
2994
3653
  };
2995
3654
  }
3655
+ function adyenReturnUrl(params) {
3656
+ let page = null;
3657
+ if (typeof window !== "undefined" && window.location?.href) {
3658
+ try {
3659
+ page = new URL(window.location.href);
3660
+ ADYEN_RETURN_QUERY_KEYS.forEach((k) => page?.searchParams.delete(k));
3661
+ } catch {
3662
+ page = null;
3663
+ }
3664
+ }
3665
+ const local = page?.protocol === "http:" && /^(localhost|127\.0\.0\.1)$/.test(page.hostname);
3666
+ const build = (base) => {
3667
+ if (base && local) {
3668
+ const url = new URL(base.toString());
3669
+ Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v)));
3670
+ return url.toString();
3671
+ }
3672
+ return resolveHttpsReturnUrl(base ? base.toString() : null, params);
3673
+ };
3674
+ const full = build(page);
3675
+ if (full.length <= 1024 || !page) return full;
3676
+ const bare = new URL(page.toString());
3677
+ bare.search = "";
3678
+ return build(bare);
3679
+ }
2996
3680
  function resolveHttpsReturnUrl(overrideUrl, params = null) {
2997
- let url = void 0;
3681
+ let url = overrideUrl ?? void 0;
2998
3682
  if (!url && typeof window !== "undefined" && window.location) {
2999
3683
  url = window.location.href;
3000
3684
  }
@@ -3032,23 +3716,45 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3032
3716
  super();
3033
3717
  this.cartManager = cartManager;
3034
3718
  this.state = null;
3719
+ /** Counts `open()` calls — see CheckoutState.session. */
3720
+ this.sessionSeq = 0;
3035
3721
  this.applePayAvailableCache = null;
3036
3722
  /** Gateway hints learned from CreatePaymentApplePay (per session). The
3037
3723
  * backend's gateway_merchant_id may be a publishable key (pk_…) or a Stripe
3038
3724
  * Connect account (acct_…) — stored defensively by prefix. */
3039
3725
  this.applePayPublishableKey = null;
3040
3726
  this.applePayConnectedAccount = null;
3727
+ /** Stripe Connect account of the channel, from its Stripe config. */
3728
+ this.stripeConnectAccount = null;
3041
3729
  this.klarnaAvailableCache = null;
3042
3730
  /** Live q1 listeners while a Qliro widget is mounted. */
3043
3731
  this.qliroController = null;
3732
+ /** Live Dibs.Checkout handle while a Nexi widget is mounted. */
3733
+ this.nexiHandle = null;
3734
+ /** Live Drop-in while an Adyen session is mounted. */
3735
+ this.adyenHandle = null;
3736
+ this.stripeHandle = null;
3737
+ /**
3738
+ * The rate the shopper wants. Before an address it is only remembered (the
3739
+ * suggested one, or their pick); every re-pricing asks for it, and the
3740
+ * backend charges it if it reaches the address.
3741
+ */
3742
+ this.nexiPreferredShipping = null;
3743
+ /** The rates offered before an address was typed. */
3744
+ this.nexiShippingPreview = null;
3745
+ /** What the last `address-changed` was priced with — a picked rate re-prices the same address. */
3746
+ this.nexiShippingContext = null;
3044
3747
  this.klarnaOrderInFlight = false;
3045
3748
  /** Last backend shippings fetched (per-supplier), for UI reuse. */
3046
3749
  this.lastFetchedShippings = [];
3750
+ /** See `lastShippingsResolved`. */
3751
+ this.shippingsResolved = false;
3047
3752
  /** Per-sponsor payment methods cache + in-flight dedupe. */
3048
3753
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3049
3754
  this.paymentMethodsCache = /* @__PURE__ */ new Map();
3050
3755
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3051
3756
  this.paymentMethodsPromises = /* @__PURE__ */ new Map();
3757
+ this.kustomController = null;
3052
3758
  preloadStripeJs();
3053
3759
  }
3054
3760
  open(sponsorId) {
@@ -3058,6 +3764,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3058
3764
  }
3059
3765
  this.state = {
3060
3766
  sponsorId,
3767
+ session: ++this.sessionSeq,
3061
3768
  subtotal: this.cartManager.subtotalForSponsor(sponsorId),
3062
3769
  // Page-level currency wins over whatever the persisted cart carries —
3063
3770
  // the host may have switched currency since the cart was created.
@@ -3135,6 +3842,20 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3135
3842
  const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
3136
3843
  return getCheckout(checkoutId, opts);
3137
3844
  }
3845
+ /**
3846
+ * The Vio order number (#4497) a checkout became, or null: not born yet,
3847
+ * or an API that does not say it. Never throws — it is a line on a
3848
+ * receipt, and a receipt does not fail for it.
3849
+ */
3850
+ async getOrderNumber(checkoutId, sponsorId) {
3851
+ if (!checkoutId) return null;
3852
+ try {
3853
+ const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
3854
+ return await getCheckoutOrderNumber(checkoutId, opts);
3855
+ } catch {
3856
+ return null;
3857
+ }
3858
+ }
3138
3859
  /**
3139
3860
  * Payment methods enabled for the sponsor (backend-configured). Memoised per
3140
3861
  * sponsor with in-flight dedupe — several UI components ask on every open.
@@ -3174,6 +3895,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3174
3895
  * this at all.
3175
3896
  */
3176
3897
  async fetchAvailableShippings(sponsorId) {
3898
+ this.shippingsResolved = false;
3177
3899
  try {
3178
3900
  const spId = sponsorId ?? this.state?.sponsorId;
3179
3901
  if (!spId) return [];
@@ -3186,6 +3908,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3186
3908
  if (!cartId) return [];
3187
3909
  const opts = await getCartGraphQLOptions(spId);
3188
3910
  const supplierGroups = await getLineItemsBySupplier(cartId, opts);
3911
+ this.shippingsResolved = Array.isArray(supplierGroups);
3189
3912
  const firstGroup = Array.isArray(supplierGroups) ? supplierGroups[0] : null;
3190
3913
  const shippings = firstGroup?.available_shippings ?? [];
3191
3914
  if (Array.isArray(shippings) && shippings.length > 0) {
@@ -3208,6 +3931,10 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3208
3931
  preselected: idx === 0
3209
3932
  };
3210
3933
  });
3934
+ mapped.sort((a, b) => a.price - b.price);
3935
+ mapped.forEach((m, idx) => {
3936
+ m.preselected = idx === 0;
3937
+ });
3211
3938
  this.lastFetchedShippings = mapped;
3212
3939
  return mapped;
3213
3940
  }
@@ -3218,6 +3945,15 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3218
3945
  }
3219
3946
  return [];
3220
3947
  }
3948
+ /**
3949
+ * Whether the last `fetchAvailableShippings` got an answer from the backend,
3950
+ * possibly an empty one, rather than failing or finding no cart. The two
3951
+ * return the same empty list, and only an answer means the cart has
3952
+ * nothing it can ship with.
3953
+ */
3954
+ get lastShippingsResolved() {
3955
+ return this.shippingsResolved;
3956
+ }
3221
3957
  /** Set the chosen shipping per supplier on the backend cart. */
3222
3958
  async updateShippingsBySupplier(data, sponsorId) {
3223
3959
  const spId = sponsorId ?? this.state?.sponsorId;
@@ -3260,12 +3996,13 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3260
3996
  const addr = typeof formData === "object" && formData !== null ? formData : this.state?.address;
3261
3997
  const addressObj = !isVipps ? buildCheckoutAddress(addr) : null;
3262
3998
  const emailVal = (typeof formData === "string" ? formData : formData?.email) || this.state?.address?.email || "";
3263
- if (!emailVal || !emailVal.includes("@")) {
3999
+ const hasEmail = !!emailVal && emailVal.includes("@");
4000
+ if (!hasEmail && !isVipps) {
3264
4001
  throw new Error("[CheckoutManager] email is required to start the payment");
3265
4002
  }
3266
4003
  const updateVars = {
3267
4004
  checkoutId,
3268
- email: emailVal,
4005
+ ...hasEmail ? { email: emailVal } : {},
3269
4006
  buyerAcceptsPurchaseConditions: true,
3270
4007
  buyerAcceptsTermsConditions: true,
3271
4008
  ...extraUpdateVars
@@ -3337,10 +4074,21 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3337
4074
  throw new Error("Stripe payment link generation failed: missing checkout_url");
3338
4075
  }
3339
4076
  /**
3340
- * Vipps payment link: same flow as Stripe — CreatePaymentVipps → redirect to
3341
- * payment_url.
4077
+ * Vipps: create the payment via Vio Commerce and redirect to Vipps'
4078
+ * landing page (on a phone it opens the app; on a desktop it offers to
4079
+ * send the payment to the phone).
4080
+ *
4081
+ * `express: true` asks for Vipps Express — address and delivery chosen
4082
+ * inside the app, the email handed back with the payment — which is what
4083
+ * the Vipps buttons on the product and the cart ask for. When the cart
4084
+ * cannot have it (several suppliers, digital goods, no rate for the
4085
+ * country) the backend creates NO payment and answers `express: false`
4086
+ * with the reason; this resolves with that answer, without redirecting,
4087
+ * so the caller can open the ordinary checkout instead. Without the flag
4088
+ * the backend decides: Express while the cart has no delivery yet, a
4089
+ * plain payment once the shopper chose one in our form.
3342
4090
  */
3343
- async startVippsPayment(sponsorId, formData) {
4091
+ async startVippsPayment(sponsorId, formData, options = {}) {
3344
4092
  const { spId, checkoutId, emailVal, opts } = await this.prepareRedirectPayment(
3345
4093
  sponsorId,
3346
4094
  formData,
@@ -3353,15 +4101,31 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3353
4101
  checkout_id: checkoutId
3354
4102
  });
3355
4103
  const vippsRes = await createPaymentVipps(
3356
- { checkoutId, email: emailVal, returnUrl },
4104
+ {
4105
+ checkoutId,
4106
+ ...emailVal && emailVal.includes("@") ? { email: emailVal } : {},
4107
+ returnUrl,
4108
+ ...options.express !== void 0 ? { express: options.express } : {}
4109
+ },
3357
4110
  opts
3358
4111
  );
3359
4112
  if (vippsRes?.payment_url) {
3360
4113
  _CheckoutManager.redirectTo(vippsRes.payment_url);
3361
4114
  return vippsRes;
3362
4115
  }
4116
+ if (options.express === true && vippsRes?.express === false) {
4117
+ return vippsRes;
4118
+ }
3363
4119
  throw new Error("Vipps payment link generation failed: missing payment_url");
3364
4120
  }
4121
+ /**
4122
+ * The Vipps Express button: the shopper taps it and the app opens with
4123
+ * address and delivery already there. Resolves with `{ express: false,
4124
+ * reason }` — and nothing redirected — when this cart cannot have it.
4125
+ */
4126
+ async startVippsExpress(sponsorId) {
4127
+ return this.startVippsPayment(sponsorId, void 0, { express: true });
4128
+ }
3365
4129
  /** Vipps' own payment state for the checkout — see getVippsStatus in
3366
4130
  * cart-queries.ts for why this exists instead of trusting GetCheckout. */
3367
4131
  async getVippsStatus(checkoutId, sponsorId) {
@@ -3373,7 +4137,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3373
4137
  * and render its html_snippet into `container`. The widget collects
3374
4138
  * address, shipping and payment by itself; the SDK renders nothing else.
3375
4139
  */
3376
- async mountKustomCheckout(container, sponsorId) {
4140
+ async mountKustomCheckout(container, sponsorId, handlers = {}) {
3377
4141
  const spId = sponsorId ?? this.state?.sponsorId;
3378
4142
  if (!spId) throw new Error("[CheckoutManager] no sponsor for Kustom");
3379
4143
  const cart = this.cartManager.getCart(spId);
@@ -3400,16 +4164,42 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3400
4164
  checkoutId,
3401
4165
  countryCode: getGlobalCountryCode(),
3402
4166
  href,
3403
- email: this.state?.address?.email || void 0
4167
+ email: this.state?.address?.email || void 0,
4168
+ client: `web-sdk ${SDK_VERSION}`
3404
4169
  },
3405
4170
  opts
3406
4171
  );
3407
4172
  if (!order?.html_snippet) {
3408
4173
  throw new Error("Kustom order creation failed: missing html_snippet");
3409
4174
  }
4175
+ this.destroyKustomListeners();
3410
4176
  renderKustomSnippet(container, order.html_snippet);
4177
+ this.kustomController = installKustomListeners(handlers);
3411
4178
  return order;
3412
4179
  }
4180
+ /** Disarm the widget's listeners with the widget they belong to. */
4181
+ destroyKustomListeners() {
4182
+ this.kustomController?.destroy();
4183
+ this.kustomController = null;
4184
+ }
4185
+ /**
4186
+ * The cart changed under an open Kustom widget: lock it, push the cart
4187
+ * into the SAME order, and let the widget refresh itself. Kustom's own
4188
+ * rule — never a new order for a cart change. The widget auto-resumes
4189
+ * after 10 s if the sync never comes back.
4190
+ */
4191
+ async syncKustomOrder(sponsorId) {
4192
+ const spId = sponsorId ?? this.state?.sponsorId;
4193
+ const checkoutId = this.state?.checkoutId;
4194
+ if (!spId || !checkoutId) return null;
4195
+ const opts = await getCartGraphQLOptions(spId);
4196
+ suspendKustom();
4197
+ try {
4198
+ return await syncPaymentKustom(checkoutId, opts);
4199
+ } finally {
4200
+ resumeKustom();
4201
+ }
4202
+ }
3413
4203
  /** Re-read a Kustom order — used on the confirmation return trip. */
3414
4204
  async getKustomOrder(orderId, sponsorId) {
3415
4205
  const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
@@ -3560,6 +4350,344 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3560
4350
  const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
3561
4351
  return getWalleyOrder(checkoutId, opts);
3562
4352
  }
4353
+ /**
4354
+ * Mount the Nexi embedded checkout — Nexi's own JS SDK, not a snippet.
4355
+ *
4356
+ * `onCompleted` fires on Nexi's `payment-completed` (no redirect: Nexi
4357
+ * renders no receipt, the component shows Vio's confirmation). Shipping is
4358
+ * Vio's: Nexi holds its pay button until `address-changed` has been
4359
+ * re-priced through `UpdateNexiShipping` — the widget is frozen meanwhile
4360
+ * and `onShipping` reports the outcome (NO_SHIPPING keeps it held).
4361
+ *
4362
+ * A session interrupted by a third-party redirect inside Nexi (Vipps,
4363
+ * Swish, MobilePay) is remembered in sessionStorage and resumed on the
4364
+ * same paymentId instead of creating a new payment — only when `resume`
4365
+ * names it. Every other opening creates a payment for the cart as it is.
4366
+ */
4367
+ async mountNexiCheckout(container, sponsorId, handlers = {}, resume) {
4368
+ const spId = sponsorId ?? this.state?.sponsorId;
4369
+ if (!spId) throw new Error("[CheckoutManager] no sponsor for Nexi");
4370
+ const cart = this.cartManager.getCart(spId);
4371
+ let cartId = cart?.cartId;
4372
+ if (!cartId) cartId = await this.cartManager.ensureCartId(spId);
4373
+ if (!cartId) {
4374
+ throw new Error(`[CheckoutManager] Failed to obtain cart_id for sponsor ${spId}`);
4375
+ }
4376
+ const opts = await getCartGraphQLOptions(spId);
4377
+ const stored = resume ? readNexiPending() : null;
4378
+ const pending2 = stored && stored.paymentId === resume?.paymentId ? stored : null;
4379
+ let checkoutId = this.state?.checkoutId;
4380
+ if (!checkoutId && pending2 && pending2.sponsorId === spId) {
4381
+ checkoutId = pending2.checkoutId;
4382
+ if (this.state) this.state = { ...this.state, checkoutId };
4383
+ }
4384
+ if (!checkoutId) {
4385
+ const checkoutRes = await this.createCheckout(spId);
4386
+ checkoutId = checkoutRes?.id;
4387
+ if (checkoutId && this.state) {
4388
+ this.state = { ...this.state, checkoutId };
4389
+ }
4390
+ }
4391
+ if (!checkoutId) {
4392
+ throw new Error("[CheckoutManager] no backend checkout for the Nexi payment");
4393
+ }
4394
+ const ownedCheckoutId = checkoutId;
4395
+ let order = null;
4396
+ if (pending2 && pending2.checkoutId === ownedCheckoutId) {
4397
+ try {
4398
+ order = await getNexiOrder(ownedCheckoutId, opts);
4399
+ } catch {
4400
+ order = null;
4401
+ }
4402
+ if (order && isNexiPaid(order.status)) {
4403
+ clearNexiPending();
4404
+ handlers.onCompleted?.(order);
4405
+ return order;
4406
+ }
4407
+ if (order && (order.status !== "Created" || order.order_id !== pending2.paymentId)) {
4408
+ order = null;
4409
+ }
4410
+ }
4411
+ if (!order) {
4412
+ const created = await createPaymentNexi(
4413
+ {
4414
+ checkoutId: ownedCheckoutId,
4415
+ countryCode: getGlobalCountryCode(),
4416
+ href: kustomCleanHref(),
4417
+ email: this.state?.address?.email || void 0
4418
+ },
4419
+ opts
4420
+ );
4421
+ if (!created?.order_id || !created.checkout_key) {
4422
+ throw new Error("Nexi payment creation failed: missing paymentId/checkout key");
4423
+ }
4424
+ order = created;
4425
+ rememberNexiPending({ checkoutId: ownedCheckoutId, sponsorId: spId, paymentId: order.order_id });
4426
+ }
4427
+ this.destroyNexi();
4428
+ const handle = await mountNexi(container, order, {
4429
+ language: nexiLanguageFor(getGlobalCountryCode()),
4430
+ theme: nexiThemeFrom(readVioTheme())
4431
+ });
4432
+ this.nexiHandle = handle;
4433
+ const mounted = order;
4434
+ const carried = order.shipping;
4435
+ this.nexiPreferredShipping = carried?.shipping_id ?? null;
4436
+ this.nexiShippingPreview = null;
4437
+ if (carried?.ok && carried.country) {
4438
+ this.nexiShippingContext = {
4439
+ handle,
4440
+ checkoutId: ownedCheckoutId,
4441
+ address: { countryCode: carried.country, postalCode: carried.postal_code },
4442
+ fromShopper: carried.address_source !== "market",
4443
+ opts,
4444
+ onShipping: handlers.onShipping
4445
+ };
4446
+ handlers.onShipping?.(carried);
4447
+ } else if (carried?.reason === "AWAITING_ADDRESS") {
4448
+ this.nexiShippingPreview = carried;
4449
+ this.nexiShippingContext = {
4450
+ handle,
4451
+ checkoutId: ownedCheckoutId,
4452
+ address: null,
4453
+ opts,
4454
+ onShipping: handlers.onShipping
4455
+ };
4456
+ handlers.onShipping?.(carried);
4457
+ }
4458
+ const onAddress = (address) => {
4459
+ this.nexiShippingContext = {
4460
+ handle,
4461
+ checkoutId: ownedCheckoutId,
4462
+ address,
4463
+ fromShopper: true,
4464
+ opts,
4465
+ onShipping: handlers.onShipping
4466
+ };
4467
+ void this.repriceNexiShipping(
4468
+ handle,
4469
+ ownedCheckoutId,
4470
+ address,
4471
+ opts,
4472
+ handlers.onShipping,
4473
+ this.nexiPreferredShipping ?? void 0
4474
+ );
4475
+ };
4476
+ handle.on("address-changed", onAddress);
4477
+ handle.on("applepay-contact-updated", onAddress);
4478
+ handle.on("pay-initialized", () => {
4479
+ void this.finalizeNexiPayment(handle, ownedCheckoutId, opts, handlers.onShipping);
4480
+ });
4481
+ handle.on("payment-completed", (result) => {
4482
+ clearNexiPending();
4483
+ handlers.onCompleted?.({
4484
+ ...mounted,
4485
+ order_id: String(result?.paymentId ?? mounted.order_id),
4486
+ status: "Reserved"
4487
+ });
4488
+ });
4489
+ return order;
4490
+ }
4491
+ /**
4492
+ * The shopper picked another rate from the `options` Nexi's last
4493
+ * re-pricing returned: charge that one instead. Nexi has no shipping picker,
4494
+ * so the checkout shows the rates and calls this. A no-op until an address
4495
+ * has been typed in the widget.
4496
+ */
4497
+ async pickNexiShipping(shippingId) {
4498
+ const ctx = this.nexiShippingContext;
4499
+ if (!ctx || ctx.handle !== this.nexiHandle) return;
4500
+ this.nexiPreferredShipping = shippingId;
4501
+ if (!ctx.address) {
4502
+ const preview = this.nexiShippingPreview;
4503
+ const picked = preview?.options?.find((o) => o.id === shippingId);
4504
+ if (preview && picked) {
4505
+ this.nexiShippingPreview = {
4506
+ ...preview,
4507
+ shipping_id: picked.id,
4508
+ shipping_name: picked.name,
4509
+ shipping_price: picked.price
4510
+ };
4511
+ ctx.onShipping?.(this.nexiShippingPreview);
4512
+ }
4513
+ return;
4514
+ }
4515
+ await this.repriceNexiShipping(ctx.handle, ctx.checkoutId, ctx.address, ctx.opts, ctx.onShipping, shippingId);
4516
+ }
4517
+ /**
4518
+ * `pay-initialized` → confirm the shipping for the address Nexi will ship
4519
+ * to → `payment-order-finalized`.
4520
+ *
4521
+ * The payment already carries a shipping (set at creation, re-priced on
4522
+ * every address and pick), so normally nothing changes and Nexi charges.
4523
+ * When the shopper's address was never announced — Nexi fills it in for a
4524
+ * shopper it recognises — the backend reads it from the payment. If that
4525
+ * changes the amount, the payment is STOPPED and the widget reloaded: an
4526
+ * amount that changes while Nexi charges is what failed on 2026-09-16/17
4527
+ * (payments 085277846ae44f7498ae94e112435d63, f6116e6d68294a88927d35b05d907f10).
4528
+ */
4529
+ async finalizeNexiPayment(handle, checkoutId, opts, onShipping) {
4530
+ const ctx = this.nexiShippingContext?.handle === handle ? this.nexiShippingContext : null;
4531
+ const address = ctx?.fromShopper ? ctx.address : null;
4532
+ let proceed = false;
4533
+ let reload = false;
4534
+ try {
4535
+ const result = await updateNexiShipping(
4536
+ {
4537
+ checkoutId,
4538
+ countryCode: String(address?.countryCode ?? address?.country ?? ""),
4539
+ postalCode: address?.postalCode ? String(address.postalCode) : void 0,
4540
+ shippingId: this.nexiPreferredShipping ?? void 0
4541
+ },
4542
+ opts
4543
+ );
4544
+ if (result?.ok) {
4545
+ if (result.shipping_id) this.nexiPreferredShipping = result.shipping_id;
4546
+ if (ctx && result.country) {
4547
+ ctx.address = { countryCode: result.country, postalCode: result.postal_code };
4548
+ ctx.fromShopper = true;
4549
+ }
4550
+ reload = result.changed === true;
4551
+ proceed = !reload;
4552
+ }
4553
+ onShipping?.(result ? { ...result, ...reload ? { repriced_at_pay: true } : {} } : result);
4554
+ } catch (err) {
4555
+ onShipping?.(null, err);
4556
+ }
4557
+ try {
4558
+ handle.send?.("payment-order-finalized", proceed);
4559
+ } catch {
4560
+ }
4561
+ if (reload) {
4562
+ try {
4563
+ handle.thawCheckout();
4564
+ } catch {
4565
+ }
4566
+ }
4567
+ }
4568
+ /** address-changed → freeze → UpdateNexiShipping → thaw. */
4569
+ async repriceNexiShipping(handle, checkoutId, address, opts, onShipping, shippingId) {
4570
+ const countryCode = String(address?.countryCode ?? address?.country ?? "");
4571
+ if (!countryCode) return;
4572
+ try {
4573
+ handle.freezeCheckout();
4574
+ } catch {
4575
+ }
4576
+ try {
4577
+ const result = await updateNexiShipping(
4578
+ {
4579
+ checkoutId,
4580
+ countryCode,
4581
+ postalCode: address?.postalCode ? String(address.postalCode) : void 0,
4582
+ shippingId
4583
+ },
4584
+ opts
4585
+ );
4586
+ if (result?.ok && result.shipping_id) this.nexiPreferredShipping = result.shipping_id;
4587
+ onShipping?.(result);
4588
+ } catch (err) {
4589
+ onShipping?.(null, err);
4590
+ } finally {
4591
+ try {
4592
+ handle.thawCheckout();
4593
+ } catch {
4594
+ }
4595
+ }
4596
+ }
4597
+ /** Release the Dibs.Checkout listeners with the widget they belong to. */
4598
+ destroyNexi() {
4599
+ try {
4600
+ this.nexiHandle?.cleanup();
4601
+ } catch {
4602
+ }
4603
+ this.nexiHandle = null;
4604
+ this.nexiShippingContext = null;
4605
+ this.nexiPreferredShipping = null;
4606
+ this.nexiShippingPreview = null;
4607
+ }
4608
+ /** Re-read the Nexi payment owned by a checkout. */
4609
+ async getNexiOrder(checkoutId, sponsorId) {
4610
+ const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
4611
+ return getNexiOrder(checkoutId, opts);
4612
+ }
4613
+ /**
4614
+ * Adyen: FORM-FIRST. Saves the shopper's form and shipping pick on the
4615
+ * checkout (Adyen collects neither), creates a session with that final
4616
+ * amount and mounts Adyen's Drop-in on it. A session is never updated —
4617
+ * after ANY change the caller destroys this mount and calls again.
4618
+ *
4619
+ * At "Pay" the backend only confirms that the session still pays for the
4620
+ * checkout; when it does not, Adyen is stopped and `onReplaced` tells the
4621
+ * caller to remount. Completion is asked of Adyen through the backend
4622
+ * (`GetAdyenPayment`) — the browser's own result is never the answer.
4623
+ */
4624
+ async mountAdyenCheckout(container, sponsorId, formData, callbacks) {
4625
+ this.destroyAdyen();
4626
+ const { spId, checkoutId, emailVal, opts } = await this.prepareRedirectPayment(
4627
+ sponsorId,
4628
+ formData,
4629
+ { paymentMethod: "Adyen" }
4630
+ );
4631
+ const session = await createPaymentAdyen(
4632
+ {
4633
+ checkoutId,
4634
+ returnUrl: adyenReturnUrl({
4635
+ vio_payment: "return",
4636
+ vio_method: ADYEN_RETURN_METHOD,
4637
+ vio_sponsor: spId,
4638
+ checkout_id: checkoutId
4639
+ }),
4640
+ countryCode: getGlobalCountryCode(),
4641
+ channel: "Web",
4642
+ email: emailVal,
4643
+ client: `web-sdk ${SDK_VERSION}`
4644
+ },
4645
+ opts
4646
+ );
4647
+ if (!session?.session_id) {
4648
+ throw new Error("[CheckoutManager] Adyen session was not created");
4649
+ }
4650
+ const theme = readVioTheme();
4651
+ this.adyenHandle = await mountAdyen(
4652
+ container,
4653
+ session,
4654
+ {
4655
+ onBeforePay: async () => {
4656
+ const verdict = await confirmAdyenPayment(
4657
+ { checkoutId, sessionId: session.session_id },
4658
+ opts
4659
+ );
4660
+ if (verdict?.ok === true) return true;
4661
+ callbacks.onReplaced(String(verdict?.reason ?? "UNKNOWN"));
4662
+ return false;
4663
+ },
4664
+ onCompleted: (result) => {
4665
+ void getAdyenPayment({ checkoutId, sessionResult: result?.sessionResult }, opts).catch(() => null).then((payment) => callbacks.onCompleted(payment, session));
4666
+ },
4667
+ onFailed: (result) => callbacks.onFailed(result, session),
4668
+ onError: (error) => callbacks.onError?.(error)
4669
+ },
4670
+ { theme: { accent: theme?.accent, radiusMd: theme?.radiusMd } }
4671
+ );
4672
+ return session;
4673
+ }
4674
+ /** Unmount Drop-in. Safe to call when nothing is mounted. */
4675
+ destroyAdyen() {
4676
+ try {
4677
+ this.adyenHandle?.unmount();
4678
+ } catch {
4679
+ }
4680
+ this.adyenHandle = null;
4681
+ }
4682
+ /**
4683
+ * Outcome of the Adyen session a checkout owns. With `redirectResult` (the
4684
+ * return from Vipps, Klarna, Trustly or a redirect-3DS) the redirect is
4685
+ * finalised on the server.
4686
+ */
4687
+ async getAdyenPayment(checkoutId, proof = {}, sponsorId) {
4688
+ const opts = await getCartGraphQLOptions(sponsorId ?? this.state?.sponsorId);
4689
+ return getAdyenPayment({ checkoutId, ...proof }, opts);
4690
+ }
3563
4691
  /* eslint-enable @typescript-eslint/no-explicit-any */
3564
4692
  setAddress(address) {
3565
4693
  if (!this.state) return;
@@ -3577,6 +4705,83 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3577
4705
  );
3578
4706
  }
3579
4707
  // MARK: - Apple Pay (Stripe Payment Request)
4708
+ /**
4709
+ * Which Stripe flow this channel offers: `native` pays on our own page,
4710
+ * `link` sends the shopper to a page hosted by Stripe. The channel's two
4711
+ * toggles decide it and the answer travels in the method's config
4712
+ * (api-microservice, `stripe-offer.ts`). A channel that predates that
4713
+ * field answers `link`, which is what every client did before.
4714
+ */
4715
+ /**
4716
+ * Does this channel's Vipps offer Express — the buy-now buttons that open
4717
+ * the app with address and delivery? Vio Commerce announces it as
4718
+ * `config.express` on the Vipps method, the way Stripe announces `mode`;
4719
+ * the seller switches it on its Vipps row. Unknown means yes: Express is
4720
+ * the default, and the backend refuses it with a reason if it cannot.
4721
+ */
4722
+ async getVippsExpressEnabled(sponsorId) {
4723
+ try {
4724
+ const methods = await this.getAvailablePaymentMethods(sponsorId);
4725
+ const vipps = methods?.find((m) => String(m?.name ?? "").toLowerCase() === "vipps");
4726
+ const express = vipps?.config?.find((c) => c?.name === "express")?.value;
4727
+ return express !== "false";
4728
+ } catch {
4729
+ return true;
4730
+ }
4731
+ }
4732
+ async getStripeMode(sponsorId) {
4733
+ try {
4734
+ const methods = await this.getAvailablePaymentMethods(sponsorId);
4735
+ const stripe = methods?.find((m) => String(m?.name ?? "").toLowerCase() === "stripe");
4736
+ const mode = stripe?.config?.find((c) => c?.name === "mode")?.value;
4737
+ return mode === "native" ? "native" : "link";
4738
+ } catch {
4739
+ return "link";
4740
+ }
4741
+ }
4742
+ /**
4743
+ * Stripe's Payment Element, mounted on our own page for a checkout that is
4744
+ * ready to be paid. Creating it mints a PaymentIntent, so it happens when
4745
+ * the shopper asks for the payment step, never while they type.
4746
+ *
4747
+ * The order is not born here: `confirm()` only says what Stripe told the
4748
+ * browser. The `payment_intent.succeeded` webhook creates it server-side,
4749
+ * and the caller then reads the checkout to see it land.
4750
+ */
4751
+ async mountStripeCheckout(container, sponsorId, formData, callbacks) {
4752
+ this.destroyStripe();
4753
+ const { spId, checkoutId, opts } = await this.prepareRedirectPayment(sponsorId, formData, {
4754
+ paymentMethod: "Stripe"
4755
+ });
4756
+ const minted = await createStripeIntent(checkoutId, opts);
4757
+ if (!minted?.client_secret) {
4758
+ throw new Error("[CheckoutManager] Stripe payment was not created");
4759
+ }
4760
+ const account = minted.stripe_account ?? await this.resolveStripeConnectAccount(spId);
4761
+ const intent = account ? { ...minted, stripe_account: account } : minted;
4762
+ const theme = readVioTheme();
4763
+ const handle = await mountStripeElement(container, intent, {
4764
+ returnUrl: resolveHttpsReturnUrl(null, {
4765
+ vio_payment: "success",
4766
+ vio_method: "stripe",
4767
+ vio_sponsor: spId,
4768
+ checkout_id: checkoutId
4769
+ }),
4770
+ theme: { accent: theme?.accent, radiusMd: theme?.radiusMd },
4771
+ onReady: callbacks?.onReady,
4772
+ onLoadError: callbacks?.onLoadError
4773
+ });
4774
+ this.stripeHandle = handle;
4775
+ return { handle, intent, checkoutId };
4776
+ }
4777
+ /** Unmount the Payment Element. Safe when nothing is mounted. */
4778
+ destroyStripe() {
4779
+ try {
4780
+ this.stripeHandle?.unmount();
4781
+ } catch {
4782
+ }
4783
+ this.stripeHandle = null;
4784
+ }
3580
4785
  /**
3581
4786
  * The Stripe publishable key for a sponsor, taken from its own commerce
3582
4787
  * channel (`GetAvailablePaymentMethods` → Stripe → config.publishableKey).
@@ -3594,6 +4799,8 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
3594
4799
  const methods = await this.getAvailablePaymentMethods(sponsorId);
3595
4800
  const stripe = methods?.find((m) => String(m?.name ?? "").toLowerCase() === "stripe");
3596
4801
  const key = stripe?.config?.find((c) => c?.name === "publishableKey")?.value;
4802
+ const account = stripe?.config?.find((c) => c?.name === "stripeAccount")?.value;
4803
+ this.stripeConnectAccount = typeof account === "string" && account.startsWith("acct_") ? account : null;
3597
4804
  if (typeof key === "string" && key.startsWith("pk_")) {
3598
4805
  this.applePayPublishableKey = key;
3599
4806
  return key;
@@ -4217,9 +5424,9 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4217
5424
  if (typeof sessionStorage === "undefined") return;
4218
5425
  const raw = sessionStorage.getItem(KLARNA_PENDING_KEY);
4219
5426
  if (!raw) return;
4220
- let pending;
5427
+ let pending2;
4221
5428
  try {
4222
- pending = JSON.parse(raw);
5429
+ pending2 = JSON.parse(raw);
4223
5430
  } catch {
4224
5431
  sessionStorage.removeItem(KLARNA_PENDING_KEY);
4225
5432
  return;
@@ -4227,7 +5434,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4227
5434
  const fromUrl = readKlarnaTokenFromUrl();
4228
5435
  if (fromUrl) {
4229
5436
  cleanUrlParams([fromUrl.param]);
4230
- await this.completeKlarnaOrder(fromUrl.token, pending, { authorizationToken: fromUrl.token });
5437
+ await this.completeKlarnaOrder(fromUrl.token, pending2, { authorizationToken: fromUrl.token });
4231
5438
  return;
4232
5439
  }
4233
5440
  if (typeof window !== "undefined" && typeof console !== "undefined" && window.location.search) {
@@ -4241,7 +5448,7 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4241
5448
  await listenForKlarnaCompletion({
4242
5449
  config: { clientId: cfg.klarnaClientId, locale: getGlobalLocale(), environment: cfg.klarnaEnvironment },
4243
5450
  onComplete: (result) => {
4244
- void this.completeKlarnaOrder(result.authorizationToken, pending, result);
5451
+ void this.completeKlarnaOrder(result.authorizationToken, pending2, result);
4245
5452
  },
4246
5453
  onError: (err) => {
4247
5454
  if (typeof console !== "undefined") {
@@ -4354,7 +5561,18 @@ var CheckoutManager = class _CheckoutManager extends EventTarget {
4354
5561
  * (Stripe will route to the platform account in that case).
4355
5562
  */
4356
5563
  findStripeConnectAccount() {
4357
- return void 0;
5564
+ return this.stripeConnectAccount ?? void 0;
5565
+ }
5566
+ /** The sponsor's connected account, read fresh from its channel config. */
5567
+ async resolveStripeConnectAccount(sponsorId) {
5568
+ try {
5569
+ const methods = await this.getAvailablePaymentMethods(sponsorId);
5570
+ const stripe = methods?.find((m) => String(m?.name ?? "").toLowerCase() === "stripe");
5571
+ const account = stripe?.config?.find((c) => c?.name === "stripeAccount")?.value;
5572
+ return typeof account === "string" && account.startsWith("acct_") ? account : void 0;
5573
+ } catch {
5574
+ return void 0;
5575
+ }
4358
5576
  }
4359
5577
  emit() {
4360
5578
  this.dispatchEvent(
@@ -4368,9 +5586,6 @@ function toSmallestUnit(amount, currency) {
4368
5586
  return Math.round(amount * 100);
4369
5587
  }
4370
5588
 
4371
- // src/core/version.ts
4372
- var SDK_VERSION = "0.11.0";
4373
-
4374
5589
  // src/core/analytics/analytics-manager.ts
4375
5590
  var SESSION_TTL_MS = 30 * 60 * 1e3;
4376
5591
  var FLUSH_INTERVAL_MS = 5e3;
@@ -4997,7 +6212,7 @@ var VioFacade = class {
4997
6212
  return this.analyticsManager;
4998
6213
  }
4999
6214
  // MARK: - Backend-bound
5000
- /** Lazy VioApi for REST endpoints on `api-dev.vio.live`. */
6215
+ /** Lazy VioApi for REST endpoints on `api-staging.vio.live`. */
5001
6216
  get api() {
5002
6217
  if (!this.apiInstance) {
5003
6218
  const cfg = Configuration.get();
@@ -5065,6 +6280,55 @@ var VioFacade = class {
5065
6280
  var _vioGlobal = globalThis;
5066
6281
  var Vio = _vioGlobal.__VIO_FACADE__ ?? (_vioGlobal.__VIO_FACADE__ = new VioFacade());
5067
6282
 
5068
- export { ADD_ITEM_MUTATION, AnalyticsManager, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, Vio, VioApi, VioApiError, __decorateClass, addItem, checkApplePayAvailability, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, executeCartGraphQL, formatPrice, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, primaryImageUrl, showApplePaySheet, updateCheckout, updateItem, updateShippingsBySupplier };
5069
- //# sourceMappingURL=chunk-U2QG4IVJ.js.map
5070
- //# sourceMappingURL=chunk-U2QG4IVJ.js.map
6283
+ // src/core/checkout/payments/vipps-button.ts
6284
+ var VIPPS_BUTTON_SCRIPT = "https://cdn.vippsmobilepay.com/js/button/button.js";
6285
+ var VIPPS_BUTTON_TAG = "vipps-mobilepay-button";
6286
+ var pending = null;
6287
+ function ensureVippsButton(timeoutMs = 0) {
6288
+ if (typeof document === "undefined" || typeof customElements === "undefined") {
6289
+ return Promise.resolve(false);
6290
+ }
6291
+ if (customElements.get(VIPPS_BUTTON_TAG)) return Promise.resolve(true);
6292
+ if (!pending) {
6293
+ pending = new Promise((resolve) => {
6294
+ let settled = false;
6295
+ const done = (ok) => {
6296
+ if (settled) return;
6297
+ settled = true;
6298
+ if (timer) clearTimeout(timer);
6299
+ resolve(ok);
6300
+ };
6301
+ const timer = timeoutMs > 0 ? setTimeout(() => done(false), timeoutMs) : void 0;
6302
+ customElements.whenDefined(VIPPS_BUTTON_TAG).then(
6303
+ () => done(true),
6304
+ () => done(false)
6305
+ );
6306
+ if (!document.querySelector(`script[src="${VIPPS_BUTTON_SCRIPT}"]`)) {
6307
+ const script = document.createElement("script");
6308
+ script.async = true;
6309
+ script.src = VIPPS_BUTTON_SCRIPT;
6310
+ script.onerror = () => done(false);
6311
+ document.head.appendChild(script);
6312
+ }
6313
+ });
6314
+ void pending.then((ok) => {
6315
+ if (!ok) pending = null;
6316
+ });
6317
+ }
6318
+ return pending;
6319
+ }
6320
+ function vippsButtonAttributes(verb = "buy", opts = {}) {
6321
+ return {
6322
+ brand: "vipps",
6323
+ language: opts.language ?? "no",
6324
+ verb,
6325
+ variant: "primary",
6326
+ rounded: "true",
6327
+ stretched: opts.stretched === false ? "false" : "true",
6328
+ ...opts.compact ? { compact: "true" } : {}
6329
+ };
6330
+ }
6331
+
6332
+ export { ADD_ITEM_MUTATION, ADYEN_RETURN_METHOD, ADYEN_RETURN_QUERY_KEYS, AdyenOriginError, AnalyticsManager, CREATE_CART_MUTATION, CREATE_CHECKOUT_MUTATION, CREATE_PAYMENT_STRIPE_MUTATION, CREATE_PAYMENT_VIPPS_MUTATION, CartManager, CheckoutManager, CommerceClient, Configuration, DELETE_ITEM_MUTATION, GET_AVAILABLE_PAYMENT_METHODS_QUERY, GET_CART_QUERY, GET_CHECKOUT_QUERY, GET_LINE_ITEMS_BY_SUPPLIER_QUERY, GET_VIPPS_STATUS_QUERY, KUSTOM_RETURN_QUERY_KEYS, SDK_VERSION, UPDATE_CHECKOUT_MUTATION, UPDATE_ITEM_MUTATION, UPDATE_SHIPPINGS_BY_SUPPLIER_MUTATION, VIPPS_BUTTON_SCRIPT, VIPPS_BUTTON_TAG, Vio, VioApi, VioApiError, __decorateClass, addItem, adyenReturnOutcome, adyenReturnParams, checkApplePayAvailability, createCart, createCheckout, createCommerceClient, createPaymentStripe, createPaymentVipps, createVioApi, deleteItem, displayPrice, ensureVippsButton, executeCartGraphQL, formatPrice, getAvailablePaymentMethods, getCart, getCartGraphQLOptions, getCheckout, getCustomerSessionId, getGlobalCurrency, getLineItemsBySupplier, getVippsStatus, kustomReturnOutcome, nexiReturnPaymentId, primaryImageUrl, readNexiPending, showApplePaySheet, updateCheckout, updateItem, updateShippingsBySupplier, vippsButtonAttributes };
6333
+ //# sourceMappingURL=chunk-CMNTHF66.js.map
6334
+ //# sourceMappingURL=chunk-CMNTHF66.js.map