@faststore/api 1.10.34 → 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 +9 -0
- package/dist/api.cjs.development.js +53 -16
- 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 +53 -16
- package/dist/api.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/platforms/vtex/resolvers/validateCart.ts +97 -40
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",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"graphql": "^15.6.0"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "3e3bfc6bd9aa68ffc170b9979d5e3eeb5e06088e"
|
|
50
50
|
}
|
|
@@ -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)) {
|