@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.
- package/README.md +9 -0
- package/dist/{chunk-U2QG4IVJ.js → chunk-CMNTHF66.js} +1321 -57
- package/dist/chunk-CMNTHF66.js.map +1 -0
- package/dist/{chunk-XU5LGUBU.js → chunk-NUNGU6LL.js} +1774 -269
- package/dist/chunk-NUNGU6LL.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/core/index.d.ts +623 -120
- 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-IrVSMVcA.d.ts} +384 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +2 -1
- package/dist/chunk-QYQQ5J6E.js +0 -3
- package/dist/chunk-U2QG4IVJ.js.map +0 -1
- package/dist/chunk-XU5LGUBU.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) {
|
|
@@ -1306,18 +1339,18 @@ query Config {
|
|
|
1306
1339
|
`;
|
|
1307
1340
|
async function getCartGraphQLOptions(sponsorId) {
|
|
1308
1341
|
const facade = globalThis.__VIO_FACADE__;
|
|
1309
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
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
|
|
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 (!
|
|
2666
|
-
clearTimeout(
|
|
2667
|
-
const { resolve } =
|
|
2668
|
-
|
|
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 (!
|
|
2681
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
2751
|
-
|
|
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
|
-
|
|
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
|
|
3341
|
-
*
|
|
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
|
-
{
|
|
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
|
|
5427
|
+
let pending2;
|
|
4221
5428
|
try {
|
|
4222
|
-
|
|
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,
|
|
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,
|
|
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-
|
|
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
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
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
|