@laioutr/app-shopware 0.15.4 → 0.17.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/dist/module.json +1 -1
- package/dist/module.mjs +7 -2
- package/dist/runtime/app/components/ShopwareEmbedFrame.d.vue.ts +13 -3
- package/dist/runtime/app/components/ShopwareEmbedFrame.vue +48 -5
- package/dist/runtime/app/components/ShopwareEmbedFrame.vue.d.ts +13 -3
- package/dist/runtime/app/composables/useShopwareEmbedBridge.d.ts +3 -1
- package/dist/runtime/app/composables/useShopwareEmbedBridge.js +7 -3
- package/dist/runtime/app/const/bridge.d.ts +20 -0
- package/dist/runtime/app/const/bridge.js +7 -0
- package/dist/runtime/app/lib/createBridgeHandler.d.ts +3 -0
- package/dist/runtime/app/lib/createBridgeHandler.js +7 -1
- package/dist/runtime/app/lib/createOrderHandoffRefresher.d.ts +24 -0
- package/dist/runtime/app/lib/createOrderHandoffRefresher.js +21 -0
- package/dist/runtime/app/lib/parseBridgeMessage.js +2 -1
- package/dist/runtime/app/sections/SectionShopwareCheckout.vue +42 -4
- package/dist/runtime/server/const/checkout.d.ts +1 -1
- package/dist/runtime/server/const/checkout.js +10 -2
- package/dist/runtime/server/orchestr/product/base.resolver.js +18 -2
- package/dist/runtime/server/orchestr/product/variants.link.js +6 -4
- package/dist/runtime/server/orchestr/product-variant/base.resolver.js +9 -4
- package/dist/runtime/server/orchestr-helper/requestedFields.d.ts +1 -3
- package/dist/runtime/server/orchestr-helper/requestedFields.js +54 -42
- package/dist/runtime/server/routes/checkout.js +12 -1
- package/dist/runtime/server/routes/order-handoff.post.d.ts +14 -0
- package/dist/runtime/server/routes/order-handoff.post.js +33 -0
- package/dist/runtime/server/shopware-helper/fetchAllProductVariants.d.ts +2 -1
- package/dist/runtime/server/shopware-helper/fetchAllProductVariants.js +39 -11
- package/dist/runtime/server/shopware-helper/optionGroupsMapper.d.ts +23 -0
- package/dist/runtime/server/shopware-helper/optionGroupsMapper.js +37 -0
- package/dist/runtime/server/shopware-helper/resolveCheckout.d.ts +2 -0
- package/dist/runtime/server/shopware-helper/resolveCheckout.js +4 -3
- package/dist/runtime/server/shopware-helper/sessionHandoff.d.ts +6 -0
- package/dist/runtime/server/shopware-helper/sessionHandoff.js +6 -1
- package/dist/runtime/server/shopware-helper/wellKnownOptionName.d.ts +2 -0
- package/dist/runtime/server/shopware-helper/wellKnownOptionName.js +278 -0
- package/dist/runtime/shared/const/checkout.d.ts +33 -0
- package/dist/runtime/shared/const/checkout.js +5 -0
- package/package.json +3 -3
|
@@ -1,47 +1,5 @@
|
|
|
1
1
|
import { MediaIncludes } from "../const/includes.js";
|
|
2
2
|
import { mergeIncludes } from "../shopware-helper/criteria.js";
|
|
3
|
-
const addAssociation = (name, add, association = {}) => add ? { [name]: association } : {};
|
|
4
|
-
export const resolveProductFields = async ({ loadVariants }, resolveCriteria) => {
|
|
5
|
-
const variantFields = loadVariants ? await resolveProductVariantFields(resolveCriteria) : void 0;
|
|
6
|
-
return resolveCriteria("product", {
|
|
7
|
-
associations: {
|
|
8
|
-
cover: { associations: { media: {} } },
|
|
9
|
-
media: { associations: { media: {} } },
|
|
10
|
-
...addAssociation("children", loadVariants, variantFields ? { associations: variantFields.associations } : {})
|
|
11
|
-
},
|
|
12
|
-
includes: mergeIncludes(
|
|
13
|
-
{
|
|
14
|
-
product: [
|
|
15
|
-
"id",
|
|
16
|
-
"parentId",
|
|
17
|
-
"name",
|
|
18
|
-
"seoUrls",
|
|
19
|
-
"productNumber",
|
|
20
|
-
"ean",
|
|
21
|
-
"translated",
|
|
22
|
-
"manufacturer",
|
|
23
|
-
"description",
|
|
24
|
-
"cover",
|
|
25
|
-
"metaTitle",
|
|
26
|
-
"metaDescription",
|
|
27
|
-
"cover",
|
|
28
|
-
"media",
|
|
29
|
-
"minPurchase",
|
|
30
|
-
"purchaseSteps",
|
|
31
|
-
"maxPurchase",
|
|
32
|
-
"calculatedPrice",
|
|
33
|
-
"calculatedPrices",
|
|
34
|
-
"children",
|
|
35
|
-
"ratingAverage",
|
|
36
|
-
"productReviews"
|
|
37
|
-
],
|
|
38
|
-
product_media: ["id", "mediaId", "media"],
|
|
39
|
-
media: MediaIncludes
|
|
40
|
-
},
|
|
41
|
-
variantFields?.includes ?? {}
|
|
42
|
-
)
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
3
|
export const resolveProductVariantFields = async (resolveCriteria) => resolveCriteria("product-variant", {
|
|
46
4
|
associations: {
|
|
47
5
|
cover: { associations: { media: {} } },
|
|
@@ -86,3 +44,57 @@ export const resolveProductVariantFields = async (resolveCriteria) => resolveCri
|
|
|
86
44
|
property_group: ["id", "name", "translated"]
|
|
87
45
|
}
|
|
88
46
|
});
|
|
47
|
+
export const resolveProductFields = async (resolveCriteria) => {
|
|
48
|
+
const variantFields = await resolveProductVariantFields(resolveCriteria);
|
|
49
|
+
return resolveCriteria("product", {
|
|
50
|
+
associations: {
|
|
51
|
+
cover: { associations: { media: {} } },
|
|
52
|
+
media: { associations: { media: {} } },
|
|
53
|
+
// Only the parent carries the configurator, which is what defines the option
|
|
54
|
+
// axes; `properties` on the same row are filterable facets and never do.
|
|
55
|
+
// `media` needs its own association: without it the option's swatch image is
|
|
56
|
+
// never returned, whatever the group's displayType claims.
|
|
57
|
+
configuratorSettings: { associations: { option: { associations: { group: {}, media: {} } } } },
|
|
58
|
+
// Only the variant projection's `includes` are merged below, never its
|
|
59
|
+
// associations, so the selected options need associating here too or the
|
|
60
|
+
// default variant reports none.
|
|
61
|
+
options: { associations: { group: {} } }
|
|
62
|
+
},
|
|
63
|
+
includes: mergeIncludes(
|
|
64
|
+
{
|
|
65
|
+
product: [
|
|
66
|
+
"id",
|
|
67
|
+
"parentId",
|
|
68
|
+
"name",
|
|
69
|
+
"seoUrls",
|
|
70
|
+
"productNumber",
|
|
71
|
+
"ean",
|
|
72
|
+
"translated",
|
|
73
|
+
"manufacturer",
|
|
74
|
+
"description",
|
|
75
|
+
"cover",
|
|
76
|
+
"metaTitle",
|
|
77
|
+
"metaDescription",
|
|
78
|
+
"cover",
|
|
79
|
+
"media",
|
|
80
|
+
"minPurchase",
|
|
81
|
+
"purchaseSteps",
|
|
82
|
+
"maxPurchase",
|
|
83
|
+
"calculatedPrice",
|
|
84
|
+
"calculatedPrices",
|
|
85
|
+
"ratingAverage",
|
|
86
|
+
"productReviews",
|
|
87
|
+
"configuratorSettings"
|
|
88
|
+
],
|
|
89
|
+
product_media: ["id", "mediaId", "media"],
|
|
90
|
+
media: MediaIncludes,
|
|
91
|
+
product_configurator_setting: ["id", "optionId", "option", "position"],
|
|
92
|
+
// Merged with the variant projection's narrower list, which carries neither
|
|
93
|
+
// the swatch fields nor the ordering the configurator is authored in.
|
|
94
|
+
property_group_option: ["colorHexCode", "media", "position"],
|
|
95
|
+
property_group: ["displayType", "position"]
|
|
96
|
+
},
|
|
97
|
+
variantFields.includes
|
|
98
|
+
)
|
|
99
|
+
});
|
|
100
|
+
};
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { consola } from "consola";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createError,
|
|
4
|
+
defineEventHandler,
|
|
5
|
+
getCookie,
|
|
6
|
+
getQuery,
|
|
7
|
+
getRequestURL,
|
|
8
|
+
sendRedirect,
|
|
9
|
+
useRuntimeConfig
|
|
10
|
+
} from "#imports";
|
|
11
|
+
import { RETRY_ORDER_QUERY_KEY } from "../const/checkout.js";
|
|
3
12
|
import { CONTEXT_TOKEN_COOKIE } from "../const/cookieKeys.js";
|
|
4
13
|
import { bootstrapGuestContextToken } from "../shopware-helper/bootstrapContextToken.js";
|
|
5
14
|
import { persistContextToken } from "../shopware-helper/persistContextToken.js";
|
|
@@ -17,10 +26,12 @@ export default defineEventHandler(async (event) => {
|
|
|
17
26
|
log.error("Failed to bootstrap a guest context token", cause);
|
|
18
27
|
}
|
|
19
28
|
}
|
|
29
|
+
const retryOrderId = getQuery(event)[RETRY_ORDER_QUERY_KEY];
|
|
20
30
|
const plan = await resolveCheckout({
|
|
21
31
|
config,
|
|
22
32
|
contextToken,
|
|
23
33
|
origin: getRequestURL(event).origin,
|
|
34
|
+
retryOrderId: typeof retryOrderId === "string" ? retryOrderId : null,
|
|
24
35
|
mint: mintSessionHandoffCode
|
|
25
36
|
});
|
|
26
37
|
if (plan.kind === "error") {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mint a single-use session-handoff code for the embedded checkout's top-level order submit.
|
|
3
|
+
*
|
|
4
|
+
* The confirm form targets `_top` so redirect-based payment providers are never framed, and
|
|
5
|
+
* the storefront route it posts to redeems this code to install a session in the top-level
|
|
6
|
+
* cookie jar before forwarding to Shopware's order endpoint. Codes expire in about a minute,
|
|
7
|
+
* so the section re-mints while the shopper sits on the confirm page rather than once on load.
|
|
8
|
+
*
|
|
9
|
+
* The shopper reaches confirm through the checkout handoff, which bootstraps a context token
|
|
10
|
+
* when none exists — so a missing cookie here means the session was lost, not that the
|
|
11
|
+
* shopper is new. Fail rather than mint against a fresh guest cart the order would not match.
|
|
12
|
+
*/
|
|
13
|
+
declare const _default: any;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { consola } from "consola";
|
|
2
|
+
import { createError, defineEventHandler, getCookie, getRequestURL, readBody, useRuntimeConfig } from "#imports";
|
|
3
|
+
import { CHECKOUT_REDIRECT_ROUTE } from "../const/checkout.js";
|
|
4
|
+
import { CONTEXT_TOKEN_COOKIE } from "../const/cookieKeys.js";
|
|
5
|
+
import { mintSessionHandoffCode } from "../shopware-helper/sessionHandoff.js";
|
|
6
|
+
const log = consola.withTag("shopware/order-handoff");
|
|
7
|
+
export default defineEventHandler(async (event) => {
|
|
8
|
+
const config = useRuntimeConfig()["@laioutr/app-shopware"];
|
|
9
|
+
const contextToken = getCookie(event, CONTEXT_TOKEN_COOKIE);
|
|
10
|
+
if (!contextToken) {
|
|
11
|
+
throw createError({ statusCode: 409, statusMessage: "No cart context to hand off" });
|
|
12
|
+
}
|
|
13
|
+
const origin = getRequestURL(event).origin;
|
|
14
|
+
const body = await readBody(event).catch(() => ({}));
|
|
15
|
+
const finishUrl = typeof body?.finishUrl === "string" ? body.finishUrl : void 0;
|
|
16
|
+
const checkoutUrl = typeof body?.checkoutUrl === "string" ? body.checkoutUrl : void 0;
|
|
17
|
+
try {
|
|
18
|
+
const code = await mintSessionHandoffCode({
|
|
19
|
+
endpoint: config.endpoint,
|
|
20
|
+
accessToken: config.accessToken,
|
|
21
|
+
contextToken,
|
|
22
|
+
loginSuccessCallback: config.checkoutLoginCallbackUrl ?? origin,
|
|
23
|
+
logoutSuccessCallback: config.checkoutLogoutCallbackUrl ?? origin,
|
|
24
|
+
redirectRoute: CHECKOUT_REDIRECT_ROUTE,
|
|
25
|
+
finishSuccessCallback: finishUrl,
|
|
26
|
+
checkoutCallback: checkoutUrl
|
|
27
|
+
});
|
|
28
|
+
return { code };
|
|
29
|
+
} catch (cause) {
|
|
30
|
+
log.error("Failed to mint an order handoff code", cause);
|
|
31
|
+
throw createError({ statusCode: 502, statusMessage: "Order handoff mint failed" });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ResolveCriteria } from '../types/criteria.js';
|
|
2
2
|
import { StorefrontClient } from '../types/shopware.js';
|
|
3
|
-
export declare const fetchAllProducts: (storefrontClient: StorefrontClient, { productIds, loadVariants, resolveCriteria }: {
|
|
3
|
+
export declare const fetchAllProducts: (storefrontClient: StorefrontClient, { productIds, loadVariants, resolveCriteria, maxLimit, }: {
|
|
4
4
|
productIds: string[];
|
|
5
5
|
loadVariants: boolean;
|
|
6
6
|
resolveCriteria: ResolveCriteria;
|
|
7
|
+
maxLimit: number;
|
|
7
8
|
}) => Promise<{
|
|
8
9
|
active?: boolean;
|
|
9
10
|
apiAlias: "product";
|
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
import { toRequestCriteria } from "./criteria.js";
|
|
2
|
-
import { resolveProductFields } from "../orchestr-helper/requestedFields.js";
|
|
3
|
-
|
|
2
|
+
import { resolveProductFields, resolveProductVariantFields } from "../orchestr-helper/requestedFields.js";
|
|
3
|
+
const readVariants = async (storefrontClient, { parentIds, resolveCriteria, maxLimit }) => {
|
|
4
|
+
const criteria = await resolveProductVariantFields(resolveCriteria);
|
|
5
|
+
const variants = [];
|
|
6
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7
|
+
for (let page = 1; ; page++) {
|
|
8
|
+
const response = await storefrontClient.invoke("readProduct post /product", {
|
|
9
|
+
body: {
|
|
10
|
+
page,
|
|
11
|
+
limit: maxLimit,
|
|
12
|
+
filter: [{ type: "equalsAny", field: "parentId", value: parentIds }],
|
|
13
|
+
...toRequestCriteria(criteria)
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const elements = response.data.elements ?? [];
|
|
17
|
+
const fresh = elements.filter((element) => element.id && !seen.has(element.id));
|
|
18
|
+
for (const element of fresh) seen.add(element.id);
|
|
19
|
+
variants.push(...fresh);
|
|
20
|
+
if (elements.length < maxLimit || fresh.length === 0) break;
|
|
21
|
+
}
|
|
22
|
+
return variants;
|
|
23
|
+
};
|
|
24
|
+
export const fetchAllProducts = async (storefrontClient, {
|
|
25
|
+
productIds,
|
|
26
|
+
loadVariants,
|
|
27
|
+
resolveCriteria,
|
|
28
|
+
maxLimit
|
|
29
|
+
}) => {
|
|
4
30
|
if (productIds.length === 0) {
|
|
5
31
|
return [];
|
|
6
32
|
}
|
|
7
|
-
const criteria = await resolveProductFields(
|
|
8
|
-
const response = await
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
33
|
+
const criteria = await resolveProductFields(resolveCriteria);
|
|
34
|
+
const [response, variants] = await Promise.all([
|
|
35
|
+
storefrontClient.invoke("readProduct post /product", {
|
|
36
|
+
body: {
|
|
37
|
+
ids: productIds,
|
|
38
|
+
...toRequestCriteria(criteria)
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
loadVariants ? readVariants(storefrontClient, { parentIds: productIds, resolveCriteria, maxLimit }) : []
|
|
42
|
+
]);
|
|
14
43
|
const products = response.data.elements ?? [];
|
|
15
|
-
|
|
16
|
-
return all;
|
|
44
|
+
return [...variants, ...products];
|
|
17
45
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ShopwareProduct } from '../types/shopware.js';
|
|
2
|
+
import type { Swatch } from '@laioutr-core/core-types/common';
|
|
3
|
+
/**
|
|
4
|
+
* The option axes a product offers, read from its configurator.
|
|
5
|
+
*
|
|
6
|
+
* The configurator is what defines a variant; `properties` on the same product are
|
|
7
|
+
* filterable facets and never do. Only the parent carries it, so this must be given
|
|
8
|
+
* the parent row rather than the variant the rest of the projection reads from.
|
|
9
|
+
*
|
|
10
|
+
* Per-value stock and a per-value variant id are deliberately absent: both would
|
|
11
|
+
* mean loading every child variant, which is the cost this component exists to
|
|
12
|
+
* avoid. A consumer that needs them falls back to the variants link.
|
|
13
|
+
*/
|
|
14
|
+
export declare const mapProductOptionGroups: (product: Pick<ShopwareProduct, "configuratorSettings">) => {
|
|
15
|
+
groups: {
|
|
16
|
+
name: string;
|
|
17
|
+
wellKnownName: import("@laioutr-core/canonical-types/entity/product-variant").WellKnownOptionName | undefined;
|
|
18
|
+
values: {
|
|
19
|
+
value: string;
|
|
20
|
+
swatch: Swatch | undefined;
|
|
21
|
+
}[];
|
|
22
|
+
}[];
|
|
23
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mapMedia } from "./mediaMapper.js";
|
|
2
|
+
import { swTranslated } from "./swTranslated.js";
|
|
3
|
+
import { guessWellKnownName } from "./wellKnownOptionName.js";
|
|
4
|
+
const byPosition = (a, b) => (a.position ?? 0) - (b.position ?? 0);
|
|
5
|
+
const bySettingThenOption = (a, b) => (a.position ?? 0) - (b.position ?? 0) || (a.option.position ?? 0) - (b.option.position ?? 0);
|
|
6
|
+
const mapSwatch = (option) => {
|
|
7
|
+
const hex = swTranslated(option, "colorHexCode") ?? option.colorHexCode;
|
|
8
|
+
if (hex) return ["color", hex];
|
|
9
|
+
const media = option.media ? mapMedia(option.media) : void 0;
|
|
10
|
+
return media?.type === "image" ? ["image", media] : void 0;
|
|
11
|
+
};
|
|
12
|
+
export const mapProductOptionGroups = (product) => {
|
|
13
|
+
const settings = (product.configuratorSettings ?? []).filter((setting) => !!setting.option);
|
|
14
|
+
const groups = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const setting of settings) {
|
|
16
|
+
const group = setting.option.group;
|
|
17
|
+
const key = group?.id ?? swTranslated(group, "name") ?? "";
|
|
18
|
+
const existing = groups.get(key);
|
|
19
|
+
if (existing) {
|
|
20
|
+
existing.settings.push(setting);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
groups.set(key, { name: swTranslated(group, "name") ?? "", position: group?.position ?? 0, settings: [setting] });
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
groups: [...groups.values()].sort(byPosition).map((group) => ({
|
|
27
|
+
name: group.name,
|
|
28
|
+
wellKnownName: guessWellKnownName(group.name),
|
|
29
|
+
// The merchant's configurator order, which is what keeps a size run out of
|
|
30
|
+
// alphabetical order.
|
|
31
|
+
values: [...group.settings].sort(bySettingThenOption).map((setting) => ({
|
|
32
|
+
value: swTranslated(setting.option, "name") ?? "",
|
|
33
|
+
swatch: mapSwatch(setting.option)
|
|
34
|
+
}))
|
|
35
|
+
}))
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -21,6 +21,8 @@ export interface ResolveCheckoutDeps {
|
|
|
21
21
|
contextToken: string | null | undefined;
|
|
22
22
|
/** Request origin used for the login/logout success callbacks. */
|
|
23
23
|
origin: string;
|
|
24
|
+
/** Order to retry payment for, from the storefront's `retry-order` bounce. */
|
|
25
|
+
retryOrderId?: string | null;
|
|
24
26
|
/** Injected minter (real: {@link mintSessionHandoffCode}) — keeps this decision logic pure/testable. */
|
|
25
27
|
mint: (params: MintSessionHandoffParams) => Promise<string>;
|
|
26
28
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildConnectSessionUrl } from "./checkoutUrl.js";
|
|
2
|
-
import { CHECKOUT_REDIRECT_ROUTE } from "../const/checkout.js";
|
|
2
|
+
import { CHECKOUT_REDIRECT_ROUTE, CHECKOUT_RETRY_ROUTE, RETRY_FRAME_MARKER_KEY } from "../const/checkout.js";
|
|
3
3
|
export const resolveCheckout = async (deps) => {
|
|
4
|
-
const { config, contextToken, origin, mint } = deps;
|
|
4
|
+
const { config, contextToken, origin, retryOrderId, mint } = deps;
|
|
5
5
|
if (!contextToken) {
|
|
6
6
|
return { kind: "redirect", url: "/" };
|
|
7
7
|
}
|
|
@@ -15,7 +15,8 @@ export const resolveCheckout = async (deps) => {
|
|
|
15
15
|
contextToken,
|
|
16
16
|
loginSuccessCallback: config.checkoutLoginCallbackUrl ?? origin,
|
|
17
17
|
logoutSuccessCallback: config.checkoutLogoutCallbackUrl ?? origin,
|
|
18
|
-
redirectRoute: CHECKOUT_REDIRECT_ROUTE
|
|
18
|
+
redirectRoute: retryOrderId ? CHECKOUT_RETRY_ROUTE : CHECKOUT_REDIRECT_ROUTE,
|
|
19
|
+
...retryOrderId ? { redirectRouteParams: { orderId: retryOrderId, [RETRY_FRAME_MARKER_KEY]: "1" } } : {}
|
|
19
20
|
});
|
|
20
21
|
return { kind: "redirect", url: buildConnectSessionUrl({ storefrontUrl: config.storefrontUrl, code }) };
|
|
21
22
|
} catch (cause) {
|
|
@@ -11,6 +11,12 @@ export interface MintSessionHandoffParams {
|
|
|
11
11
|
logoutSuccessCallback: string;
|
|
12
12
|
/** Internal Shopware route the handoff lands on after redeem. */
|
|
13
13
|
redirectRoute: string;
|
|
14
|
+
/** Absolute URL of the laioutr page shown after a completed order. */
|
|
15
|
+
finishSuccessCallback?: string;
|
|
16
|
+
/** Absolute URL of the laioutr checkout page, used to re-frame a payment retry. */
|
|
17
|
+
checkoutCallback?: string;
|
|
18
|
+
/** Parameters for `redirectRoute`; the retry route is keyed by `orderId`. */
|
|
19
|
+
redirectRouteParams?: Record<string, string>;
|
|
14
20
|
}
|
|
15
21
|
/**
|
|
16
22
|
* Mint a single-use session-handoff code against the `LaioutrConnector` plugin's
|
|
@@ -13,10 +13,15 @@ export const mintSessionHandoffCode = async (params) => {
|
|
|
13
13
|
"sw-access-key": params.accessToken,
|
|
14
14
|
"sw-context-token": params.contextToken
|
|
15
15
|
},
|
|
16
|
+
// Undefined keys are omitted rather than sent as null, so a plugin build that predates
|
|
17
|
+
// the return trip still accepts the call.
|
|
16
18
|
body: {
|
|
17
19
|
"login-success-callback": params.loginSuccessCallback,
|
|
18
20
|
"logout-success-callback": params.logoutSuccessCallback,
|
|
19
|
-
"redirect-route": params.redirectRoute
|
|
21
|
+
"redirect-route": params.redirectRoute,
|
|
22
|
+
...params.finishSuccessCallback ? { "finish-success-callback": params.finishSuccessCallback } : {},
|
|
23
|
+
...params.checkoutCallback ? { "checkout-callback": params.checkoutCallback } : {},
|
|
24
|
+
...params.redirectRouteParams ? { "redirect-route-params": params.redirectRouteParams } : {}
|
|
20
25
|
}
|
|
21
26
|
});
|
|
22
27
|
} catch (err) {
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const OPTION_NAME_ALIASES = {
|
|
2
|
+
color: [
|
|
3
|
+
"color",
|
|
4
|
+
// en (US), es
|
|
5
|
+
"colour",
|
|
6
|
+
// en (GB)
|
|
7
|
+
"farbe",
|
|
8
|
+
// de
|
|
9
|
+
"farbton",
|
|
10
|
+
// de — shade rather than colour, and a real axis name in the wild
|
|
11
|
+
"kleur",
|
|
12
|
+
// nl
|
|
13
|
+
"couleur",
|
|
14
|
+
// fr
|
|
15
|
+
"colore",
|
|
16
|
+
// it
|
|
17
|
+
"cor",
|
|
18
|
+
// pt
|
|
19
|
+
"kolor",
|
|
20
|
+
// pl
|
|
21
|
+
"barva",
|
|
22
|
+
// cs, sl
|
|
23
|
+
"farba",
|
|
24
|
+
// sk
|
|
25
|
+
"boja",
|
|
26
|
+
// hr
|
|
27
|
+
"sz\xEDn",
|
|
28
|
+
// hu
|
|
29
|
+
"culoare",
|
|
30
|
+
// ro
|
|
31
|
+
"\u0446\u0432\u044F\u0442",
|
|
32
|
+
// bg
|
|
33
|
+
"\u03C7\u03C1\u03CE\u03BC\u03B1",
|
|
34
|
+
// el
|
|
35
|
+
"f\xE4rg",
|
|
36
|
+
// sv
|
|
37
|
+
"farve",
|
|
38
|
+
// da
|
|
39
|
+
"farge",
|
|
40
|
+
// no
|
|
41
|
+
"v\xE4ri",
|
|
42
|
+
// fi
|
|
43
|
+
"litur",
|
|
44
|
+
// is
|
|
45
|
+
"v\xE4rv",
|
|
46
|
+
// et
|
|
47
|
+
"kr\u0101sa",
|
|
48
|
+
// lv
|
|
49
|
+
"spalva",
|
|
50
|
+
// lt
|
|
51
|
+
"renk",
|
|
52
|
+
// tr
|
|
53
|
+
"\u8272",
|
|
54
|
+
// ja
|
|
55
|
+
"\u30AB\u30E9\u30FC",
|
|
56
|
+
// ja
|
|
57
|
+
"\uC0C9\uC0C1",
|
|
58
|
+
// ko
|
|
59
|
+
"\uCEEC\uB7EC",
|
|
60
|
+
// ko
|
|
61
|
+
"\u989C\u8272",
|
|
62
|
+
// zh-Hans
|
|
63
|
+
"\u984F\u8272"
|
|
64
|
+
// zh-Hant
|
|
65
|
+
],
|
|
66
|
+
size: [
|
|
67
|
+
"size",
|
|
68
|
+
// en
|
|
69
|
+
"gr\xF6\xDFe",
|
|
70
|
+
// de
|
|
71
|
+
"groesse",
|
|
72
|
+
// de, ASCII transliteration — "oe" survives diacritic folding
|
|
73
|
+
"maat",
|
|
74
|
+
// nl
|
|
75
|
+
"grootte",
|
|
76
|
+
// nl
|
|
77
|
+
"taille",
|
|
78
|
+
// fr
|
|
79
|
+
"pointure",
|
|
80
|
+
// fr, footwear
|
|
81
|
+
"taglia",
|
|
82
|
+
// it
|
|
83
|
+
"misura",
|
|
84
|
+
// it
|
|
85
|
+
"talla",
|
|
86
|
+
// es
|
|
87
|
+
"tama\xF1o",
|
|
88
|
+
// es
|
|
89
|
+
"tamanho",
|
|
90
|
+
// pt
|
|
91
|
+
"rozmiar",
|
|
92
|
+
// pl
|
|
93
|
+
"velikost",
|
|
94
|
+
// cs, sl
|
|
95
|
+
"ve\u013Ekos\u0165",
|
|
96
|
+
// sk
|
|
97
|
+
"veli\u010Dina",
|
|
98
|
+
// hr
|
|
99
|
+
"m\xE9ret",
|
|
100
|
+
// hu
|
|
101
|
+
"m\u0103rime",
|
|
102
|
+
// ro
|
|
103
|
+
"\u0440\u0430\u0437\u043C\u0435\u0440",
|
|
104
|
+
// bg
|
|
105
|
+
"\u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2",
|
|
106
|
+
// el
|
|
107
|
+
"storlek",
|
|
108
|
+
// sv
|
|
109
|
+
"st\xF8rrelse",
|
|
110
|
+
// da, no
|
|
111
|
+
"koko",
|
|
112
|
+
// fi
|
|
113
|
+
"st\xE6r\xF0",
|
|
114
|
+
// is
|
|
115
|
+
"suurus",
|
|
116
|
+
// et
|
|
117
|
+
"izm\u0113rs",
|
|
118
|
+
// lv
|
|
119
|
+
"dydis",
|
|
120
|
+
// lt
|
|
121
|
+
"beden",
|
|
122
|
+
// tr
|
|
123
|
+
"\u30B5\u30A4\u30BA",
|
|
124
|
+
// ja
|
|
125
|
+
"\uC0AC\uC774\uC988",
|
|
126
|
+
// ko
|
|
127
|
+
"\uD06C\uAE30",
|
|
128
|
+
// ko
|
|
129
|
+
"\u5C3A\u5BF8",
|
|
130
|
+
// zh
|
|
131
|
+
"\u5C3A\u7801",
|
|
132
|
+
// zh-Hans
|
|
133
|
+
"\u5C3A\u78BC"
|
|
134
|
+
// zh-Hant
|
|
135
|
+
],
|
|
136
|
+
material: [
|
|
137
|
+
"material",
|
|
138
|
+
// en, de, es, pt, ro, sv, sl, and cs/sk `materiál` once folded
|
|
139
|
+
"materiaal",
|
|
140
|
+
// nl
|
|
141
|
+
"mati\xE8re",
|
|
142
|
+
// fr
|
|
143
|
+
"mat\xE9riau",
|
|
144
|
+
// fr
|
|
145
|
+
"materiale",
|
|
146
|
+
// it, da, no
|
|
147
|
+
"materia\u0142",
|
|
148
|
+
// pl
|
|
149
|
+
"materijal",
|
|
150
|
+
// hr
|
|
151
|
+
"anyag",
|
|
152
|
+
// hu
|
|
153
|
+
"\u043C\u0430\u0442\u0435\u0440\u0438\u0430\u043B",
|
|
154
|
+
// bg
|
|
155
|
+
"\u03C5\u03BB\u03B9\u03BA\u03CC",
|
|
156
|
+
// el
|
|
157
|
+
"materiaali",
|
|
158
|
+
// fi
|
|
159
|
+
"efni",
|
|
160
|
+
// is
|
|
161
|
+
"materjal",
|
|
162
|
+
// et
|
|
163
|
+
"materi\u0101ls",
|
|
164
|
+
// lv
|
|
165
|
+
"med\u017Eiaga",
|
|
166
|
+
// lt
|
|
167
|
+
"malzeme",
|
|
168
|
+
// tr
|
|
169
|
+
"\u7D20\u6750",
|
|
170
|
+
// ja
|
|
171
|
+
"\u6750\u8CEA",
|
|
172
|
+
// ja, zh-Hant
|
|
173
|
+
"\uC18C\uC7AC",
|
|
174
|
+
// ko
|
|
175
|
+
"\uC7AC\uC9C8",
|
|
176
|
+
// ko
|
|
177
|
+
"\u6750\u6599",
|
|
178
|
+
// zh
|
|
179
|
+
"\u6750\u8D28"
|
|
180
|
+
// zh-Hans
|
|
181
|
+
],
|
|
182
|
+
style: [
|
|
183
|
+
"style",
|
|
184
|
+
// en, fr
|
|
185
|
+
"stil",
|
|
186
|
+
// de, sv, da, no, hr, tr
|
|
187
|
+
"stijl",
|
|
188
|
+
// nl
|
|
189
|
+
"stile",
|
|
190
|
+
// it
|
|
191
|
+
"estilo",
|
|
192
|
+
// es, pt
|
|
193
|
+
"styl",
|
|
194
|
+
// pl, cs, and sk `štýl` once folded
|
|
195
|
+
"slog",
|
|
196
|
+
// sl
|
|
197
|
+
"st\xEDlus",
|
|
198
|
+
// hu
|
|
199
|
+
"\u0441\u0442\u0438\u043B",
|
|
200
|
+
// bg
|
|
201
|
+
"\u03C3\u03C4\u03C5\u03BB",
|
|
202
|
+
// el
|
|
203
|
+
"tyyli",
|
|
204
|
+
// fi
|
|
205
|
+
"st\xEDll",
|
|
206
|
+
// is
|
|
207
|
+
"stiil",
|
|
208
|
+
// et
|
|
209
|
+
"stils",
|
|
210
|
+
// lv
|
|
211
|
+
"stilius",
|
|
212
|
+
// lt
|
|
213
|
+
"\u30B9\u30BF\u30A4\u30EB",
|
|
214
|
+
// ja
|
|
215
|
+
"\uC2A4\uD0C0\uC77C",
|
|
216
|
+
// ko
|
|
217
|
+
"\u6B3E\u5F0F"
|
|
218
|
+
// zh
|
|
219
|
+
],
|
|
220
|
+
type: [
|
|
221
|
+
"type",
|
|
222
|
+
// en, nl, fr, da, no
|
|
223
|
+
"typ",
|
|
224
|
+
// de, pl, cs, sk, sv
|
|
225
|
+
"tipo",
|
|
226
|
+
// it, es, pt
|
|
227
|
+
"tip",
|
|
228
|
+
// sl, hr, tr
|
|
229
|
+
"vrsta",
|
|
230
|
+
// sl, hr
|
|
231
|
+
"rodzaj",
|
|
232
|
+
// pl
|
|
233
|
+
"t\xEDpus",
|
|
234
|
+
// hu
|
|
235
|
+
"\u0442\u0438\u043F",
|
|
236
|
+
// bg
|
|
237
|
+
"\u03C4\u03CD\u03C0\u03BF\u03C2",
|
|
238
|
+
// el
|
|
239
|
+
"tyyppi",
|
|
240
|
+
// fi
|
|
241
|
+
"tegund",
|
|
242
|
+
// is
|
|
243
|
+
"t\xFC\xFCp",
|
|
244
|
+
// et
|
|
245
|
+
"veids",
|
|
246
|
+
// lv
|
|
247
|
+
"tipas",
|
|
248
|
+
// lt
|
|
249
|
+
"\u30BF\u30A4\u30D7",
|
|
250
|
+
// ja
|
|
251
|
+
"\u7A2E\u985E",
|
|
252
|
+
// ja
|
|
253
|
+
"\uC720\uD615",
|
|
254
|
+
// ko
|
|
255
|
+
"\uD0C0\uC785",
|
|
256
|
+
// ko
|
|
257
|
+
"\u7C7B\u578B",
|
|
258
|
+
// zh-Hans
|
|
259
|
+
"\u985E\u578B"
|
|
260
|
+
// zh-Hant
|
|
261
|
+
]
|
|
262
|
+
};
|
|
263
|
+
const normalizeOptionName = (name) => name.trim().toLowerCase().replace(/ß/g, "ss").normalize("NFD").replace(/\p{M}/gu, "");
|
|
264
|
+
const OPTION_NAME_LOOKUP = new Map(
|
|
265
|
+
Object.entries(OPTION_NAME_ALIASES).flatMap(
|
|
266
|
+
([wellKnownName, aliases]) => aliases.map((alias) => [normalizeOptionName(alias), wellKnownName])
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
export const guessWellKnownName = (name) => {
|
|
270
|
+
const normalized = normalizeOptionName(name);
|
|
271
|
+
const exact = OPTION_NAME_LOOKUP.get(normalized);
|
|
272
|
+
if (exact) return exact;
|
|
273
|
+
for (const token of normalized.split(/[^\p{L}\p{N}]+/u)) {
|
|
274
|
+
const match = OPTION_NAME_LOOKUP.get(token);
|
|
275
|
+
if (match) return match;
|
|
276
|
+
}
|
|
277
|
+
return void 0;
|
|
278
|
+
};
|