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