@faststore/api 1.10.30 → 1.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/__generated__/schema.d.ts +94 -0
- package/dist/api.cjs.development.js +249 -25
- package/dist/api.cjs.development.js.map +1 -1
- package/dist/api.cjs.production.min.js +1 -1
- package/dist/api.cjs.production.min.js.map +1 -1
- package/dist/api.esm.js +249 -25
- package/dist/api.esm.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/platforms/vtex/index.d.ts +5 -0
- package/dist/platforms/vtex/resolvers/skuVariations.d.ts +6 -0
- package/dist/platforms/vtex/utils/skuVariants.d.ts +15 -0
- package/package.json +3 -3
- package/src/__generated__/schema.ts +99 -0
- package/src/platforms/vtex/index.ts +2 -0
- package/src/platforms/vtex/resolvers/collection.ts +4 -1
- package/src/platforms/vtex/resolvers/productGroup.ts +4 -3
- package/src/platforms/vtex/resolvers/skuVariations.ts +47 -0
- package/src/platforms/vtex/resolvers/validateCart.ts +97 -40
- package/src/platforms/vtex/utils/skuVariants.ts +206 -0
- package/src/typeDefs/index.ts +3 -1
- package/src/typeDefs/productGroup.graphql +6 -0
- package/src/typeDefs/skuVariants.graphql +87 -0
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,11 @@ export declare const getResolvers: (options: Options) => {
|
|
|
71
71
|
}, unknown, any>>;
|
|
72
72
|
StoreSearchResult: Record<string, import("./platforms/vtex").Resolver<Pick<import("./platforms/vtex/clients/search").SearchArgs, "sort" | "selectedFacets" | "hideUnavailableItems" | "query" | "page" | "count" | "fuzzy">, unknown, any>>;
|
|
73
73
|
StorePropertyValue: Record<string, import("./platforms/vtex").Resolver<import("./__generated__/schema").IStorePropertyValue, unknown, any>>;
|
|
74
|
+
SkuVariants: Record<string, import("./platforms/vtex").Resolver<import("./platforms/vtex/clients/search/types/ProductSearchResult").Item & {
|
|
75
|
+
isVariantOf: import("./platforms/vtex/clients/search/types/ProductSearchResult").Product;
|
|
76
|
+
} & {
|
|
77
|
+
attachmentsValues?: import("./platforms/vtex/clients/commerce/types/OrderForm").Attachment[] | undefined;
|
|
78
|
+
}, unknown, any>>;
|
|
74
79
|
ObjectOrString: import("graphql").GraphQLScalarType;
|
|
75
80
|
Query: {
|
|
76
81
|
product: (_: unknown, { locator }: import("./__generated__/schema").QueryProductArgs, ctx: import("./platforms/vtex").Context) => Promise<import("./platforms/vtex/utils/enhanceSku").EnhancedSku>;
|
|
@@ -101,6 +101,11 @@ export declare const getResolvers: (_: Options) => {
|
|
|
101
101
|
}, unknown, any>>;
|
|
102
102
|
StoreSearchResult: Record<string, Resolver<Pick<SearchArgs, "hideUnavailableItems" | "query" | "page" | "count" | "sort" | "selectedFacets" | "fuzzy">, unknown, any>>;
|
|
103
103
|
StorePropertyValue: Record<string, Resolver<import("../..").IStorePropertyValue, unknown, any>>;
|
|
104
|
+
SkuVariants: Record<string, Resolver<import("./clients/search/types/ProductSearchResult").Item & {
|
|
105
|
+
isVariantOf: import("./clients/search/types/ProductSearchResult").Product;
|
|
106
|
+
} & {
|
|
107
|
+
attachmentsValues?: import("./clients/commerce/types/OrderForm").Attachment[] | undefined;
|
|
108
|
+
}, unknown, any>>;
|
|
104
109
|
ObjectOrString: import("graphql").GraphQLScalarType;
|
|
105
110
|
Query: {
|
|
106
111
|
product: (_: unknown, { locator }: import("../..").QueryProductArgs, ctx: Context) => Promise<import("./utils/enhanceSku").EnhancedSku>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Resolver } from '..';
|
|
2
|
+
import type { PromiseType } from '../../../typings';
|
|
3
|
+
import type { StoreProduct } from './product';
|
|
4
|
+
declare type Root = PromiseType<ReturnType<typeof StoreProduct.isVariantOf>>;
|
|
5
|
+
export declare const SkuVariants: Record<string, Resolver<Root>>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StoreProduct as StoreProductType } from '../../..';
|
|
2
|
+
import type { Product, Item } from '../clients/search/types/ProductSearchResult';
|
|
3
|
+
export declare type SkuVariants = StoreProductType[];
|
|
4
|
+
export declare type SkuVariantsByName = Record<string, Array<FormattedSkuVariant>>;
|
|
5
|
+
declare type FormattedSkuVariant = {
|
|
6
|
+
alt: string;
|
|
7
|
+
src: string;
|
|
8
|
+
label: string;
|
|
9
|
+
value: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function createSlugsMap(variants: Item[], dominantVariantName: string, baseSlug: string): Record<string, string>;
|
|
12
|
+
export declare function getActiveSkuVariations(variations: Item['variations']): Record<string, string>;
|
|
13
|
+
export declare function getVariantsByName(skuSpecifications: Product['skuSpecifications']): Record<string, string[]>;
|
|
14
|
+
export declare function getFormattedVariations(variants: Item[], dominantVariantName: string, dominantVariantValue: string): Record<string, FormattedSkuVariant[]>;
|
|
15
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"express-graphql": "^0.12.0",
|
|
38
38
|
"graphql": "^15.6.0",
|
|
39
39
|
"jest-transform-graphql": "^2.1.0",
|
|
40
|
-
"shared": "^1.10.
|
|
40
|
+
"shared": "^1.10.34",
|
|
41
41
|
"ts-jest": "25.5.1",
|
|
42
42
|
"tsdx": "^0.14.1",
|
|
43
43
|
"tslib": "^2.3.1",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"graphql": "^15.6.0"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "3e3bfc6bd9aa68ffc170b9979d5e3eeb5e06088e"
|
|
50
50
|
}
|
|
@@ -9,7 +9,69 @@ export type Scalars = {
|
|
|
9
9
|
Boolean: boolean;
|
|
10
10
|
Int: number;
|
|
11
11
|
Float: number;
|
|
12
|
+
/**
|
|
13
|
+
* Example:
|
|
14
|
+
*
|
|
15
|
+
* ```json
|
|
16
|
+
* {
|
|
17
|
+
* Color: 'Red', Size: '42'
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
ActiveVariations: any;
|
|
22
|
+
/**
|
|
23
|
+
* Example:
|
|
24
|
+
*
|
|
25
|
+
* ```json
|
|
26
|
+
* {
|
|
27
|
+
* Color: [
|
|
28
|
+
* {
|
|
29
|
+
* src: "https://storecomponents.vtexassets.com/...",
|
|
30
|
+
* alt: "...",
|
|
31
|
+
* label: "...",
|
|
32
|
+
* value: "..."
|
|
33
|
+
* },
|
|
34
|
+
* {
|
|
35
|
+
* src: "https://storecomponents.vtexassets.com/...",
|
|
36
|
+
* alt: "...",
|
|
37
|
+
* label: "...",
|
|
38
|
+
* value: "..."
|
|
39
|
+
* }
|
|
40
|
+
* ],
|
|
41
|
+
* Size: [
|
|
42
|
+
* {
|
|
43
|
+
* src: "https://storecomponents.vtexassets.com/...",
|
|
44
|
+
* alt: "...",
|
|
45
|
+
* label: "...",
|
|
46
|
+
* value: "..."
|
|
47
|
+
* }
|
|
48
|
+
* ]
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
FormattedVariants: any;
|
|
12
53
|
ObjectOrString: any;
|
|
54
|
+
/**
|
|
55
|
+
* Example:
|
|
56
|
+
*
|
|
57
|
+
* ```json
|
|
58
|
+
* {
|
|
59
|
+
* 'Color-Red-Size-40': 'classic-shoes-37'
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
SlugsMap: any;
|
|
64
|
+
/**
|
|
65
|
+
* Example:
|
|
66
|
+
*
|
|
67
|
+
* ```json
|
|
68
|
+
* {
|
|
69
|
+
* Color: [ "Red", "Blue", "Green" ],
|
|
70
|
+
* Size: [ "40", "41" ]
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
VariantsByName: any;
|
|
13
75
|
};
|
|
14
76
|
|
|
15
77
|
/** Person data input to the newsletter. */
|
|
@@ -206,6 +268,37 @@ export type QuerySearchArgs = {
|
|
|
206
268
|
term?: Maybe<Scalars['String']>;
|
|
207
269
|
};
|
|
208
270
|
|
|
271
|
+
export type SkuVariants = {
|
|
272
|
+
__typename?: 'SkuVariants';
|
|
273
|
+
/** SKU property values for the current SKU. */
|
|
274
|
+
activeVariations?: Maybe<Scalars['ActiveVariations']>;
|
|
275
|
+
/** All available options for each SKU variant property, indexed by their name. */
|
|
276
|
+
allVariantsByName?: Maybe<Scalars['VariantsByName']>;
|
|
277
|
+
/**
|
|
278
|
+
* Available options for each varying SKU property, taking into account the
|
|
279
|
+
* `dominantVariantName` property. Returns all available options for the
|
|
280
|
+
* dominant property, and only options that can be combined with its current
|
|
281
|
+
* value for other properties.
|
|
282
|
+
*/
|
|
283
|
+
availableVariations?: Maybe<Scalars['FormattedVariants']>;
|
|
284
|
+
/**
|
|
285
|
+
* Maps property value combinations to their respective SKU's slug. Enables
|
|
286
|
+
* us to retrieve the slug for the SKU that matches the currently selected
|
|
287
|
+
* variations in O(1) time.
|
|
288
|
+
*/
|
|
289
|
+
slugsMap?: Maybe<Scalars['SlugsMap']>;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
export type SkuVariantsAvailableVariationsArgs = {
|
|
294
|
+
dominantVariantName: Scalars['String'];
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
export type SkuVariantsSlugsMapArgs = {
|
|
299
|
+
dominantVariantName: Scalars['String'];
|
|
300
|
+
};
|
|
301
|
+
|
|
209
302
|
/** Aggregate offer information, for a given SKU that is available to be fulfilled by multiple sellers. */
|
|
210
303
|
export type StoreAggregateOffer = {
|
|
211
304
|
__typename?: 'StoreAggregateOffer';
|
|
@@ -556,6 +649,12 @@ export type StoreProductGroup = {
|
|
|
556
649
|
name: Scalars['String'];
|
|
557
650
|
/** Product group ID. */
|
|
558
651
|
productGroupID: Scalars['String'];
|
|
652
|
+
/**
|
|
653
|
+
* Object containing data structures to facilitate handling different SKU
|
|
654
|
+
* variant properties. Specially useful for implementing SKU selection
|
|
655
|
+
* components.
|
|
656
|
+
*/
|
|
657
|
+
skuVariants?: Maybe<SkuVariants>;
|
|
559
658
|
};
|
|
560
659
|
|
|
561
660
|
/** Properties that can be associated with products and products groups. */
|
|
@@ -20,6 +20,7 @@ import { Query } from './resolvers/query'
|
|
|
20
20
|
import { StoreReview } from './resolvers/review'
|
|
21
21
|
import { StoreSearchResult } from './resolvers/searchResult'
|
|
22
22
|
import { StoreSeo } from './resolvers/seo'
|
|
23
|
+
import { SkuVariants } from './resolvers/skuVariations'
|
|
23
24
|
import ChannelMarshal from './utils/channel'
|
|
24
25
|
import type { Loaders } from './loaders'
|
|
25
26
|
import type { Clients } from './clients'
|
|
@@ -80,6 +81,7 @@ const Resolvers = {
|
|
|
80
81
|
StoreProductGroup,
|
|
81
82
|
StoreSearchResult,
|
|
82
83
|
StorePropertyValue,
|
|
84
|
+
SkuVariants,
|
|
83
85
|
ObjectOrString,
|
|
84
86
|
Query,
|
|
85
87
|
Mutation,
|
|
@@ -11,8 +11,11 @@ const isBrand = (x: any): x is Brand | CollectionPageType =>
|
|
|
11
11
|
x.type === 'brand' ||
|
|
12
12
|
(isCollectionPageType(x) && x.pageType.toLowerCase() === 'brand')
|
|
13
13
|
|
|
14
|
+
const isCollection = (x: Root): x is CollectionPageType =>
|
|
15
|
+
isCollectionPageType(x) && x.pageType.toLowerCase() === 'collection'
|
|
16
|
+
|
|
14
17
|
const slugifyRoot = (root: Root) => {
|
|
15
|
-
if (isBrand(root)) {
|
|
18
|
+
if (isBrand(root) || isCollection(root)) {
|
|
16
19
|
return slugify(root.name)
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -12,15 +12,16 @@ export const StoreProductGroup: Record<string, Resolver<Root>> = {
|
|
|
12
12
|
hasVariant: (root) =>
|
|
13
13
|
root.isVariantOf.items.map((item) => enhanceSku(item, root.isVariantOf)),
|
|
14
14
|
productGroupID: ({ isVariantOf }) => isVariantOf.productId,
|
|
15
|
-
name: (
|
|
15
|
+
name: (root) => root.isVariantOf.productName,
|
|
16
|
+
skuVariants: (root) => root,
|
|
16
17
|
additionalProperty: ({ isVariantOf: { specificationGroups } }) =>
|
|
17
18
|
specificationGroups
|
|
18
|
-
//
|
|
19
|
+
// Filter sku specifications so we don't mix them with product specs.
|
|
19
20
|
.filter(
|
|
20
21
|
(specificationGroup) =>
|
|
21
22
|
!BLOCKED_SPECIFICATIONS.has(specificationGroup.name)
|
|
22
23
|
)
|
|
23
|
-
// Transform specs back into product specs
|
|
24
|
+
// Transform specs back into product specs.
|
|
24
25
|
.flatMap(({ specifications }) =>
|
|
25
26
|
specifications.flatMap(({ name, values }) =>
|
|
26
27
|
values.map((value) => ({
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Resolver } from '..'
|
|
2
|
+
import type { PromiseType } from '../../../typings'
|
|
3
|
+
import type { StoreProduct } from './product'
|
|
4
|
+
import {
|
|
5
|
+
createSlugsMap,
|
|
6
|
+
getActiveSkuVariations,
|
|
7
|
+
getFormattedVariations,
|
|
8
|
+
getVariantsByName,
|
|
9
|
+
} from '../utils/skuVariants'
|
|
10
|
+
|
|
11
|
+
type Root = PromiseType<ReturnType<typeof StoreProduct.isVariantOf>>
|
|
12
|
+
|
|
13
|
+
type SlugsMapArgs = {
|
|
14
|
+
dominantVariantName: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const SkuVariants: Record<string, Resolver<Root>> = {
|
|
18
|
+
activeVariations: (root) => getActiveSkuVariations(root.variations),
|
|
19
|
+
allVariantsByName: (root) =>
|
|
20
|
+
getVariantsByName(root.isVariantOf.skuSpecifications),
|
|
21
|
+
|
|
22
|
+
slugsMap: (root, args) =>
|
|
23
|
+
createSlugsMap(
|
|
24
|
+
root.isVariantOf.items,
|
|
25
|
+
// Since `dominantVariantProperty` is a required argument, we can safely
|
|
26
|
+
// access it.
|
|
27
|
+
(args as SlugsMapArgs).dominantVariantName,
|
|
28
|
+
root.isVariantOf.linkText
|
|
29
|
+
),
|
|
30
|
+
|
|
31
|
+
availableVariations: (root, args) => {
|
|
32
|
+
// Since `dominantVariantProperty` is a required argument, we can safely
|
|
33
|
+
// access it.
|
|
34
|
+
const dominantVariantName = (args as SlugsMapArgs).dominantVariantName
|
|
35
|
+
const activeVariations = getActiveSkuVariations(root.variations)
|
|
36
|
+
|
|
37
|
+
const activeDominantVariationValue = activeVariations[dominantVariantName]
|
|
38
|
+
|
|
39
|
+
const filteredFormattedVariations = getFormattedVariations(
|
|
40
|
+
root.isVariantOf.items,
|
|
41
|
+
dominantVariantName,
|
|
42
|
+
activeDominantVariationValue
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return filteredFormattedVariations
|
|
46
|
+
},
|
|
47
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import deepEquals from 'fast-deep-equal'
|
|
2
2
|
|
|
3
3
|
import { md5 } from '../utils/md5'
|
|
4
|
+
import {
|
|
5
|
+
attachmentToPropertyValue,
|
|
6
|
+
getPropertyId,
|
|
7
|
+
VALUE_REFERENCES,
|
|
8
|
+
} from '../utils/propertyValue'
|
|
9
|
+
|
|
4
10
|
import type {
|
|
5
11
|
IStoreCart,
|
|
6
12
|
IStoreOffer,
|
|
@@ -13,12 +19,6 @@ import type {
|
|
|
13
19
|
OrderFormItem,
|
|
14
20
|
} from '../clients/commerce/types/OrderForm'
|
|
15
21
|
import type { Context } from '..'
|
|
16
|
-
import {
|
|
17
|
-
attachmentToPropertyValue,
|
|
18
|
-
getPropertyId,
|
|
19
|
-
VALUE_REFERENCES,
|
|
20
|
-
} from '../utils/propertyValue'
|
|
21
|
-
|
|
22
22
|
type Indexed<T> = T & { index?: number }
|
|
23
23
|
|
|
24
24
|
const isAttachment = (value: IStorePropertyValue) =>
|
|
@@ -28,7 +28,7 @@ const getId = (item: IStoreOffer) =>
|
|
|
28
28
|
[
|
|
29
29
|
item.itemOffered.sku,
|
|
30
30
|
item.seller.identifier,
|
|
31
|
-
item.price,
|
|
31
|
+
item.price < 0.01 ? 'Gift' : undefined,
|
|
32
32
|
item.itemOffered.additionalProperty
|
|
33
33
|
?.filter(isAttachment)
|
|
34
34
|
.map(getPropertyId)
|
|
@@ -39,7 +39,7 @@ const getId = (item: IStoreOffer) =>
|
|
|
39
39
|
|
|
40
40
|
const orderFormItemToOffer = (
|
|
41
41
|
item: OrderFormItem,
|
|
42
|
-
index?: number
|
|
42
|
+
index?: number,
|
|
43
43
|
): Indexed<IStoreOffer> => ({
|
|
44
44
|
listPrice: item.listPrice / 100,
|
|
45
45
|
price: item.sellingPrice / 100,
|
|
@@ -55,7 +55,7 @@ const orderFormItemToOffer = (
|
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
const offerToOrderItemInput = (
|
|
58
|
-
offer: Indexed<IStoreOffer
|
|
58
|
+
offer: Indexed<IStoreOffer>,
|
|
59
59
|
): OrderFormInputItem => ({
|
|
60
60
|
quantity: offer.quantity,
|
|
61
61
|
seller: offer.seller.identifier,
|
|
@@ -69,14 +69,18 @@ const offerToOrderItemInput = (
|
|
|
69
69
|
})),
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
-
const groupById = (offers: IStoreOffer[]): Map<string, IStoreOffer> =>
|
|
72
|
+
const groupById = (offers: IStoreOffer[]): Map<string, IStoreOffer[]> =>
|
|
73
73
|
offers.reduce((acc, item) => {
|
|
74
74
|
const id = getId(item)
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
if (!acc.has(id)) {
|
|
77
|
+
acc.set(id, [])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
acc.get(id)?.push(item)
|
|
77
81
|
|
|
78
82
|
return acc
|
|
79
|
-
}, new Map<string, IStoreOffer>())
|
|
83
|
+
}, new Map<string, IStoreOffer[]>())
|
|
80
84
|
|
|
81
85
|
const equals = (storeOrder: IStoreOrder, orderForm: OrderForm) => {
|
|
82
86
|
const pick = (item: Indexed<IStoreOffer>, index: number) => ({
|
|
@@ -96,9 +100,42 @@ const equals = (storeOrder: IStoreOrder, orderForm: OrderForm) => {
|
|
|
96
100
|
return isSameOrder && orderItemsAreSync
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
const joinItems = (form: OrderForm) => {
|
|
104
|
+
const itemsById = form.items
|
|
105
|
+
.reduce((acc, item) => {
|
|
106
|
+
const id = getId(orderFormItemToOffer(item))
|
|
107
|
+
|
|
108
|
+
if (!acc[id]) {
|
|
109
|
+
acc[id] = []
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
acc[id].push(item)
|
|
113
|
+
|
|
114
|
+
return acc
|
|
115
|
+
}, {} as Record<string, OrderFormItem[]>)
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...form,
|
|
119
|
+
items: Object.values(itemsById).map((items) => {
|
|
120
|
+
const [item] = items
|
|
121
|
+
const quantity = items.reduce((acc, i) => acc + i.quantity, 0)
|
|
122
|
+
const totalPrice = items.reduce(
|
|
123
|
+
(acc, i) => acc + i.quantity * i.sellingPrice,
|
|
124
|
+
0,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
...item,
|
|
129
|
+
quantity,
|
|
130
|
+
sellingPrice: totalPrice / quantity,
|
|
131
|
+
}
|
|
132
|
+
}),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
99
136
|
const orderFormToCart = async (
|
|
100
137
|
form: OrderForm,
|
|
101
|
-
skuLoader: Context['loaders']['skuLoader']
|
|
138
|
+
skuLoader: Context['loaders']['skuLoader'],
|
|
102
139
|
) => {
|
|
103
140
|
return {
|
|
104
141
|
order: {
|
|
@@ -119,7 +156,7 @@ const getOrderFormEtag = ({ items }: OrderForm) => md5(JSON.stringify(items))
|
|
|
119
156
|
|
|
120
157
|
const setOrderFormEtag = async (
|
|
121
158
|
form: OrderForm,
|
|
122
|
-
commerce: Context['clients']['commerce']
|
|
159
|
+
commerce: Context['clients']['commerce'],
|
|
123
160
|
) => {
|
|
124
161
|
try {
|
|
125
162
|
const orderForm = await commerce.checkout.setCustomData({
|
|
@@ -132,7 +169,7 @@ const setOrderFormEtag = async (
|
|
|
132
169
|
return orderForm
|
|
133
170
|
} catch (err) {
|
|
134
171
|
console.error(
|
|
135
|
-
'Error while setting custom data to orderForm.\n Make sure to add the following custom app to the orderForm: \n{"fields":["cartEtag"],"id":"faststore","major":1}.\n More info at: https://developers.vtex.com/vtex-rest-api/docs/customizable-fields-with-checkout-api'
|
|
172
|
+
'Error while setting custom data to orderForm.\n Make sure to add the following custom app to the orderForm: \n{"fields":["cartEtag"],"id":"faststore","major":1}.\n More info at: https://developers.vtex.com/vtex-rest-api/docs/customizable-fields-with-checkout-api',
|
|
136
173
|
)
|
|
137
174
|
|
|
138
175
|
throw err
|
|
@@ -146,7 +183,7 @@ const setOrderFormEtag = async (
|
|
|
146
183
|
*/
|
|
147
184
|
const isOrderFormStale = (form: OrderForm) => {
|
|
148
185
|
const faststoreData = form.customData?.customApps.find(
|
|
149
|
-
(app) => app.id === 'faststore'
|
|
186
|
+
(app) => app.id === 'faststore',
|
|
150
187
|
)
|
|
151
188
|
|
|
152
189
|
const oldEtag = faststoreData?.fields?.cartEtag
|
|
@@ -176,7 +213,7 @@ const isOrderFormStale = (form: OrderForm) => {
|
|
|
176
213
|
export const validateCart = async (
|
|
177
214
|
_: unknown,
|
|
178
215
|
{ cart: { order } }: { cart: IStoreCart },
|
|
179
|
-
ctx: Context
|
|
216
|
+
ctx: Context,
|
|
180
217
|
) => {
|
|
181
218
|
const { enableOrderFormSync } = ctx.storage.flags
|
|
182
219
|
const { orderNumber, acceptedOffer } = order
|
|
@@ -197,7 +234,9 @@ export const validateCart = async (
|
|
|
197
234
|
const isStale = isOrderFormStale(orderForm)
|
|
198
235
|
|
|
199
236
|
if (isStale === true && orderNumber) {
|
|
200
|
-
const newOrderForm = await setOrderFormEtag(orderForm, commerce)
|
|
237
|
+
const newOrderForm = await setOrderFormEtag(orderForm, commerce).then(
|
|
238
|
+
joinItems,
|
|
239
|
+
)
|
|
201
240
|
|
|
202
241
|
return orderFormToCart(newOrderForm, skuLoader)
|
|
203
242
|
}
|
|
@@ -206,37 +245,54 @@ export const validateCart = async (
|
|
|
206
245
|
// Step2: Process items from both browser and checkout so they have the same shape
|
|
207
246
|
const browserItemsById = groupById(acceptedOffer)
|
|
208
247
|
const originItemsById = groupById(orderForm.items.map(orderFormItemToOffer))
|
|
209
|
-
const
|
|
210
|
-
const
|
|
248
|
+
const originItems = Array.from(originItemsById.entries()); // items on the VTEX platform backend
|
|
249
|
+
const browserItems = Array.from(browserItemsById.entries()); // items on the user's browser
|
|
211
250
|
|
|
212
251
|
// Step3: Compute delta changes
|
|
213
|
-
const { itemsToAdd, itemsToUpdate } = browserItems
|
|
214
|
-
(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
252
|
+
const { itemsToAdd, itemsToUpdate } = browserItems
|
|
253
|
+
.reduce(
|
|
254
|
+
(acc, [id, items]) => {
|
|
255
|
+
const maybeOriginItem = originItemsById.get(id)
|
|
256
|
+
|
|
257
|
+
// Adding new items to cart
|
|
258
|
+
if (!maybeOriginItem) {
|
|
259
|
+
items.forEach((item) => acc.itemsToAdd.push(item))
|
|
260
|
+
|
|
261
|
+
return acc
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Update existing items
|
|
265
|
+
const [head, ...tail] = maybeOriginItem
|
|
266
|
+
const totalQuantity = items.reduce(
|
|
267
|
+
(acc, curr) => acc + curr.quantity,
|
|
268
|
+
0,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
// set total quantity to first item
|
|
220
272
|
acc.itemsToUpdate.push({
|
|
221
|
-
...
|
|
222
|
-
quantity:
|
|
273
|
+
...head,
|
|
274
|
+
quantity: totalQuantity,
|
|
223
275
|
})
|
|
224
|
-
}
|
|
225
276
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
277
|
+
// Remove all the rest
|
|
278
|
+
tail.forEach((item) =>
|
|
279
|
+
acc.itemsToUpdate.push({ ...item, quantity: 0 })
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return acc
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
itemsToAdd: [] as IStoreOffer[],
|
|
286
|
+
itemsToUpdate: [] as IStoreOffer[],
|
|
287
|
+
},
|
|
288
|
+
)
|
|
233
289
|
|
|
234
290
|
const itemsToDelete = originItems
|
|
235
|
-
.filter((
|
|
236
|
-
.map((item) => ({ ...item, quantity: 0 }))
|
|
291
|
+
.filter(([id]) => !browserItemsById.has(id))
|
|
292
|
+
.flatMap(([, items]) => items.map((item) => ({ ...item, quantity: 0 })))
|
|
237
293
|
|
|
238
294
|
const changes = [...itemsToAdd, ...itemsToUpdate, ...itemsToDelete].map(
|
|
239
|
-
offerToOrderItemInput
|
|
295
|
+
offerToOrderItemInput,
|
|
240
296
|
)
|
|
241
297
|
|
|
242
298
|
if (changes.length === 0) {
|
|
@@ -254,6 +310,7 @@ export const validateCart = async (
|
|
|
254
310
|
.then((form) =>
|
|
255
311
|
enableOrderFormSync ? setOrderFormEtag(form, commerce) : form
|
|
256
312
|
)
|
|
313
|
+
.then(joinItems)
|
|
257
314
|
|
|
258
315
|
// Step5: If no changes detected before/after updating orderForm, the order is validated
|
|
259
316
|
if (equals(order, updatedOrderForm)) {
|