@faststore/api 1.12.36 → 1.12.38
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/LICENSE +21 -0
- package/dist/api.cjs.development.js +86 -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 +86 -25
- package/dist/api.esm.js.map +1 -1
- package/dist/platforms/vtex/clients/commerce/index.d.ts +3 -2
- package/dist/platforms/vtex/clients/commerce/types/Session.d.ts +4 -0
- package/dist/platforms/vtex/clients/index.d.ts +3 -2
- package/dist/platforms/vtex/resolvers/validateCart.d.ts +1 -1
- package/dist/platforms/vtex/utils/getCookies.d.ts +1 -0
- package/package.json +4 -3
- package/src/platforms/vtex/clients/commerce/index.ts +45 -18
- package/src/platforms/vtex/clients/commerce/types/Session.ts +5 -0
- package/src/platforms/vtex/resolvers/validateCart.ts +109 -82
- package/src/platforms/vtex/utils/getCookies.ts +8 -0
|
@@ -26,7 +26,7 @@ export declare const VtexCommerce: ({ account, environment }: Options, ctx: Cont
|
|
|
26
26
|
pagetype: (slug: string) => Promise<PortalPagetype>;
|
|
27
27
|
};
|
|
28
28
|
products: {
|
|
29
|
-
crossselling: ({ type, productId, groupByProduct }: {
|
|
29
|
+
crossselling: ({ type, productId, groupByProduct, }: {
|
|
30
30
|
type: ValueOf<typeof FACET_CROSS_SELLING_MAP>;
|
|
31
31
|
productId: string;
|
|
32
32
|
groupByProduct?: boolean | undefined;
|
|
@@ -35,7 +35,7 @@ export declare const VtexCommerce: ({ account, environment }: Options, ctx: Cont
|
|
|
35
35
|
};
|
|
36
36
|
checkout: {
|
|
37
37
|
simulation: (args: SimulationArgs, { salesChannel }?: SimulationOptions) => Promise<Simulation>;
|
|
38
|
-
shippingData: ({ id, body }: {
|
|
38
|
+
shippingData: ({ id, body, }: {
|
|
39
39
|
id: string;
|
|
40
40
|
body: unknown;
|
|
41
41
|
}) => Promise<OrderForm>;
|
|
@@ -60,6 +60,7 @@ export declare const VtexCommerce: ({ account, environment }: Options, ctx: Cont
|
|
|
60
60
|
address: ({ postalCode, country, }: AddressInput) => Promise<Address>;
|
|
61
61
|
};
|
|
62
62
|
session: (search: string) => Promise<Session>;
|
|
63
|
+
getSessionOrder: () => Promise<Session>;
|
|
63
64
|
subscribeToNewsletter: (data: {
|
|
64
65
|
name: string;
|
|
65
66
|
email: string;
|
|
@@ -5,6 +5,7 @@ export interface Session {
|
|
|
5
5
|
export interface Namespaces {
|
|
6
6
|
profile?: Profile;
|
|
7
7
|
store?: Store;
|
|
8
|
+
checkout?: Checkout;
|
|
8
9
|
}
|
|
9
10
|
export interface Value {
|
|
10
11
|
value: string;
|
|
@@ -22,3 +23,6 @@ export interface Profile {
|
|
|
22
23
|
firstName?: Value;
|
|
23
24
|
lastName?: Value;
|
|
24
25
|
}
|
|
26
|
+
export interface Checkout {
|
|
27
|
+
orderFormId?: Value;
|
|
28
|
+
}
|
|
@@ -20,7 +20,7 @@ export declare const getClients: (options: Options, ctx: Context) => {
|
|
|
20
20
|
pagetype: (slug: string) => Promise<import("./commerce/types/Portal").PortalPagetype>;
|
|
21
21
|
};
|
|
22
22
|
products: {
|
|
23
|
-
crossselling: ({ type, productId, groupByProduct }: {
|
|
23
|
+
crossselling: ({ type, productId, groupByProduct, }: {
|
|
24
24
|
type: "whoboughtalsobought" | "whosawalsosaw" | "similars" | "whosawalsobought" | "accessories" | "suggestions";
|
|
25
25
|
productId: string;
|
|
26
26
|
groupByProduct?: boolean | undefined;
|
|
@@ -29,7 +29,7 @@ export declare const getClients: (options: Options, ctx: Context) => {
|
|
|
29
29
|
};
|
|
30
30
|
checkout: {
|
|
31
31
|
simulation: (args: import("./commerce/types/Simulation").SimulationArgs, { salesChannel }?: import("./commerce/types/Simulation").SimulationOptions) => Promise<import("./commerce/types/Simulation").Simulation>;
|
|
32
|
-
shippingData: ({ id, body }: {
|
|
32
|
+
shippingData: ({ id, body, }: {
|
|
33
33
|
id: string;
|
|
34
34
|
body: unknown;
|
|
35
35
|
}) => Promise<import("./commerce/types/OrderForm").OrderForm>;
|
|
@@ -54,6 +54,7 @@ export declare const getClients: (options: Options, ctx: Context) => {
|
|
|
54
54
|
address: ({ postalCode, country, }: import("./commerce/types/Address").AddressInput) => Promise<import("./commerce/types/Address").Address>;
|
|
55
55
|
};
|
|
56
56
|
session: (search: string) => Promise<import("./commerce/types/Session").Session>;
|
|
57
|
+
getSessionOrder: () => Promise<import("./commerce/types/Session").Session>;
|
|
57
58
|
subscribeToNewsletter: (data: {
|
|
58
59
|
name: string;
|
|
59
60
|
email: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { MutationValidateCartArgs } from '../../../__generated__/schema';
|
|
2
1
|
import type { Context } from '..';
|
|
2
|
+
import type { MutationValidateCartArgs } from '../../../__generated__/schema';
|
|
3
3
|
/**
|
|
4
4
|
* This resolver implements the optimistic cart behavior. The main idea in here
|
|
5
5
|
* is that we receive a cart from the UI (as query params) and we validate it with
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getCookie: (name: string, cookie: string) => string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/api",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.38",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"p-limit": "^3.1.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@faststore/shared": "^1.12.
|
|
33
|
+
"@faststore/shared": "^1.12.37",
|
|
34
34
|
"@graphql-codegen/cli": "2.2.0",
|
|
35
35
|
"@graphql-codegen/typescript": "2.2.2",
|
|
36
36
|
"concurrently": "^6.2.1",
|
|
@@ -45,5 +45,6 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"graphql": "^15.6.0"
|
|
48
|
-
}
|
|
48
|
+
},
|
|
49
|
+
"gitHead": "659eb1ff4c96f85004b3edd1f924f5753fc7a275"
|
|
49
50
|
}
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
} from './types/Simulation'
|
|
16
16
|
import type { Session } from './types/Session'
|
|
17
17
|
import type { Channel } from '../../utils/channel'
|
|
18
|
+
import { getCookie } from '../../utils/getCookies'
|
|
18
19
|
import type { SalesChannel } from './types/SalesChannel'
|
|
19
20
|
import { MasterDataResponse } from './types/Newsletter'
|
|
20
21
|
import type { Address, AddressInput } from './types/Address'
|
|
@@ -51,20 +52,22 @@ export const VtexCommerce = (
|
|
|
51
52
|
fetchAPI(`${base}/api/catalog_system/pub/portal/pagetype/${slug}`),
|
|
52
53
|
},
|
|
53
54
|
products: {
|
|
54
|
-
crossselling: (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
crossselling: ({
|
|
56
|
+
type,
|
|
57
|
+
productId,
|
|
58
|
+
groupByProduct = true,
|
|
59
|
+
}: {
|
|
60
|
+
type: ValueOf<typeof FACET_CROSS_SELLING_MAP>
|
|
61
|
+
productId: string
|
|
62
|
+
groupByProduct?: boolean
|
|
63
|
+
}): Promise<PortalProduct[]> => {
|
|
61
64
|
const params = new URLSearchParams({
|
|
62
65
|
sc: ctx.storage.channel.salesChannel,
|
|
63
66
|
groupByProduct: groupByProduct.toString(),
|
|
64
67
|
})
|
|
65
68
|
|
|
66
69
|
return fetchAPI(
|
|
67
|
-
`${base}/api/catalog_system/pub/products/crossselling/${type}/${productId}?${params}
|
|
70
|
+
`${base}/api/catalog_system/pub/products/crossselling/${type}/${productId}?${params}`
|
|
68
71
|
)
|
|
69
72
|
},
|
|
70
73
|
},
|
|
@@ -86,16 +89,20 @@ export const VtexCommerce = (
|
|
|
86
89
|
}
|
|
87
90
|
)
|
|
88
91
|
},
|
|
89
|
-
shippingData: (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
shippingData: ({
|
|
93
|
+
id,
|
|
94
|
+
body,
|
|
95
|
+
}: {
|
|
96
|
+
id: string
|
|
97
|
+
body: unknown
|
|
98
|
+
}): Promise<OrderForm> => {
|
|
92
99
|
return fetchAPI(
|
|
93
100
|
`${base}/api/checkout/pub/orderForm/${id}/attachments/shippingData`,
|
|
94
101
|
{
|
|
95
102
|
...BASE_INIT,
|
|
96
103
|
body: JSON.stringify(body),
|
|
97
|
-
}
|
|
98
|
-
)
|
|
104
|
+
}
|
|
105
|
+
)
|
|
99
106
|
},
|
|
100
107
|
orderForm: ({
|
|
101
108
|
id,
|
|
@@ -159,7 +166,7 @@ export const VtexCommerce = (
|
|
|
159
166
|
...BASE_INIT,
|
|
160
167
|
body: JSON.stringify({ value }),
|
|
161
168
|
method: 'PUT',
|
|
162
|
-
}
|
|
169
|
+
}
|
|
163
170
|
)
|
|
164
171
|
},
|
|
165
172
|
region: async ({
|
|
@@ -189,14 +196,34 @@ export const VtexCommerce = (
|
|
|
189
196
|
'items',
|
|
190
197
|
'profile.id,profile.email,profile.firstName,profile.lastName,store.channel,store.countryCode,store.cultureInfo,store.currencyCode,store.currencySymbol'
|
|
191
198
|
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
if (getCookie('vtex_session', ctx.headers.cookie)) {
|
|
200
|
+
// cookie set
|
|
201
|
+
return fetchAPI(`${base}/api/sessions?${params.toString()}`, {
|
|
202
|
+
method: 'GET',
|
|
203
|
+
headers: {
|
|
204
|
+
'content-type': 'application/json',
|
|
205
|
+
cookie: ctx.headers.cookie,
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
} else {
|
|
209
|
+
// cookie unset -> create session
|
|
210
|
+
return fetchAPI(`${base}/api/sessions?${params.toString()}`, {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
headers: {
|
|
213
|
+
'content-type': 'application/json',
|
|
214
|
+
cookie: ctx.headers.cookie,
|
|
215
|
+
},
|
|
216
|
+
body: '{}',
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
getSessionOrder: (): Promise<Session> => {
|
|
221
|
+
return fetchAPI(`${base}/api/sessions?items=checkout.orderFormId`, {
|
|
222
|
+
method: 'GET',
|
|
195
223
|
headers: {
|
|
196
224
|
'content-type': 'application/json',
|
|
197
225
|
cookie: ctx.headers.cookie,
|
|
198
226
|
},
|
|
199
|
-
body: '{}',
|
|
200
227
|
})
|
|
201
228
|
},
|
|
202
229
|
subscribeToNewsletter: (data: {
|
|
@@ -6,6 +6,7 @@ export interface Session {
|
|
|
6
6
|
export interface Namespaces {
|
|
7
7
|
profile?: Profile
|
|
8
8
|
store?: Store
|
|
9
|
+
checkout?: Checkout
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export interface Value {
|
|
@@ -26,3 +27,7 @@ export interface Profile {
|
|
|
26
27
|
firstName?: Value
|
|
27
28
|
lastName?: Value
|
|
28
29
|
}
|
|
30
|
+
|
|
31
|
+
export interface Checkout {
|
|
32
|
+
orderFormId?: Value
|
|
33
|
+
}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import deepEquals from 'fast-deep-equal'
|
|
2
2
|
|
|
3
|
+
import { mutateChannelContext, mutateLocaleContext } from '../utils/contex'
|
|
4
|
+
import { getCookie } from '../utils/getCookies'
|
|
3
5
|
import { md5 } from '../utils/md5'
|
|
4
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
attachmentToPropertyValue,
|
|
8
|
+
getPropertyId,
|
|
9
|
+
VALUE_REFERENCES
|
|
10
|
+
} from '../utils/propertyValue'
|
|
5
11
|
|
|
12
|
+
import type { Context } from '..'
|
|
6
13
|
import type {
|
|
7
|
-
IStoreSession,
|
|
8
14
|
IStoreOffer,
|
|
9
15
|
IStoreOrder,
|
|
10
|
-
IStorePropertyValue,
|
|
11
|
-
|
|
12
|
-
MutationValidateCartArgs,
|
|
16
|
+
IStorePropertyValue, IStoreSession, Maybe,
|
|
17
|
+
MutationValidateCartArgs
|
|
13
18
|
} from '../../../__generated__/schema'
|
|
14
19
|
import type {
|
|
15
20
|
OrderForm,
|
|
16
21
|
OrderFormInputItem,
|
|
17
|
-
OrderFormItem
|
|
22
|
+
OrderFormItem
|
|
18
23
|
} from '../clients/commerce/types/OrderForm'
|
|
19
|
-
|
|
24
|
+
|
|
20
25
|
type Indexed<T> = T & { index?: number }
|
|
21
26
|
|
|
22
27
|
const isAttachment = (value: IStorePropertyValue) =>
|
|
@@ -37,7 +42,7 @@ const getId = (item: IStoreOffer) =>
|
|
|
37
42
|
|
|
38
43
|
const orderFormItemToOffer = (
|
|
39
44
|
item: OrderFormItem,
|
|
40
|
-
index?: number
|
|
45
|
+
index?: number
|
|
41
46
|
): Indexed<IStoreOffer> => ({
|
|
42
47
|
listPrice: item.listPrice / 100,
|
|
43
48
|
price: item.sellingPrice / 100,
|
|
@@ -53,7 +58,7 @@ const orderFormItemToOffer = (
|
|
|
53
58
|
})
|
|
54
59
|
|
|
55
60
|
const offerToOrderItemInput = (
|
|
56
|
-
offer: Indexed<IStoreOffer
|
|
61
|
+
offer: Indexed<IStoreOffer>
|
|
57
62
|
): OrderFormInputItem => ({
|
|
58
63
|
quantity: offer.quantity,
|
|
59
64
|
seller: offer.seller.identifier,
|
|
@@ -99,18 +104,17 @@ const equals = (storeOrder: IStoreOrder, orderForm: OrderForm) => {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
const joinItems = (form: OrderForm) => {
|
|
102
|
-
const itemsById = form.items
|
|
103
|
-
|
|
104
|
-
const id = getId(orderFormItemToOffer(item))
|
|
107
|
+
const itemsById = form.items.reduce((acc, item) => {
|
|
108
|
+
const id = getId(orderFormItemToOffer(item))
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
if (!acc[id]) {
|
|
111
|
+
acc[id] = []
|
|
112
|
+
}
|
|
109
113
|
|
|
110
|
-
|
|
114
|
+
acc[id].push(item)
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
return acc
|
|
117
|
+
}, {} as Record<string, OrderFormItem[]>)
|
|
114
118
|
|
|
115
119
|
return {
|
|
116
120
|
...form,
|
|
@@ -119,7 +123,7 @@ const joinItems = (form: OrderForm) => {
|
|
|
119
123
|
const quantity = items.reduce((acc, i) => acc + i.quantity, 0)
|
|
120
124
|
const totalPrice = items.reduce(
|
|
121
125
|
(acc, i) => acc + i.quantity * i.sellingPrice,
|
|
122
|
-
0
|
|
126
|
+
0
|
|
123
127
|
)
|
|
124
128
|
|
|
125
129
|
return {
|
|
@@ -133,14 +137,14 @@ const joinItems = (form: OrderForm) => {
|
|
|
133
137
|
|
|
134
138
|
const orderFormToCart = async (
|
|
135
139
|
form: OrderForm,
|
|
136
|
-
skuLoader: Context['loaders']['skuLoader']
|
|
140
|
+
skuLoader: Context['loaders']['skuLoader']
|
|
137
141
|
) => {
|
|
138
142
|
return {
|
|
139
143
|
order: {
|
|
140
144
|
orderNumber: form.orderFormId,
|
|
141
145
|
acceptedOffer: form.items.map(async (item) => ({
|
|
142
146
|
...item,
|
|
143
|
-
product: await skuLoader.load(item.id),
|
|
147
|
+
product: await skuLoader.load(item.id),
|
|
144
148
|
})),
|
|
145
149
|
},
|
|
146
150
|
messages: form.messages.map(({ text, status }) => ({
|
|
@@ -154,7 +158,7 @@ const getOrderFormEtag = ({ items }: OrderForm) => md5(JSON.stringify(items))
|
|
|
154
158
|
|
|
155
159
|
const setOrderFormEtag = async (
|
|
156
160
|
form: OrderForm,
|
|
157
|
-
commerce: Context['clients']['commerce']
|
|
161
|
+
commerce: Context['clients']['commerce']
|
|
158
162
|
) => {
|
|
159
163
|
try {
|
|
160
164
|
const orderForm = await commerce.checkout.setCustomData({
|
|
@@ -167,7 +171,7 @@ const setOrderFormEtag = async (
|
|
|
167
171
|
return orderForm
|
|
168
172
|
} catch (err) {
|
|
169
173
|
console.error(
|
|
170
|
-
'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'
|
|
174
|
+
'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'
|
|
171
175
|
)
|
|
172
176
|
|
|
173
177
|
throw err
|
|
@@ -181,7 +185,7 @@ const setOrderFormEtag = async (
|
|
|
181
185
|
*/
|
|
182
186
|
const isOrderFormStale = (form: OrderForm) => {
|
|
183
187
|
const faststoreData = form.customData?.customApps.find(
|
|
184
|
-
(app) => app.id === 'faststore'
|
|
188
|
+
(app) => app.id === 'faststore'
|
|
185
189
|
)
|
|
186
190
|
|
|
187
191
|
const oldEtag = faststoreData?.fields?.cartEtag
|
|
@@ -195,28 +199,42 @@ const isOrderFormStale = (form: OrderForm) => {
|
|
|
195
199
|
return newEtag !== oldEtag
|
|
196
200
|
}
|
|
197
201
|
|
|
202
|
+
async function getOrderNumberFromSession(
|
|
203
|
+
headers: Record<string, string> = {},
|
|
204
|
+
commerce: Context['clients']['commerce']
|
|
205
|
+
) {
|
|
206
|
+
|
|
207
|
+
const cookieSession = getCookie('vtex_session', headers.cookie)
|
|
208
|
+
|
|
209
|
+
if (cookieSession) {
|
|
210
|
+
const { namespaces } = await commerce.getSessionOrder()
|
|
211
|
+
return namespaces.checkout?.orderFormId?.value
|
|
212
|
+
}
|
|
213
|
+
return ;
|
|
214
|
+
}
|
|
215
|
+
|
|
198
216
|
// Returns the regionalized orderForm
|
|
199
217
|
const getOrderForm = async (
|
|
200
218
|
id: string,
|
|
201
219
|
session: Maybe<IStoreSession> | undefined,
|
|
202
|
-
{ clients: { commerce } }: Context
|
|
220
|
+
{ clients: { commerce } }: Context
|
|
203
221
|
) => {
|
|
204
222
|
const orderForm = await commerce.checkout.orderForm({
|
|
205
223
|
id,
|
|
206
|
-
})
|
|
224
|
+
})
|
|
207
225
|
|
|
208
226
|
// Stores that are not yet providing the session while validating the cart
|
|
209
227
|
// should not be able to update the shipping data
|
|
210
228
|
//
|
|
211
229
|
// This was causing errors while validating regionalizated carts
|
|
212
230
|
// because the following code was trying to change the shippingData to an undefined address/session
|
|
213
|
-
if(!session) {
|
|
231
|
+
if (!session) {
|
|
214
232
|
return orderForm
|
|
215
233
|
}
|
|
216
234
|
|
|
217
235
|
const shouldUpdateShippingData =
|
|
218
236
|
typeof session.postalCode === 'string' &&
|
|
219
|
-
orderForm.shippingData?.address?.postalCode != session.postalCode
|
|
237
|
+
orderForm.shippingData?.address?.postalCode != session.postalCode
|
|
220
238
|
|
|
221
239
|
if (shouldUpdateShippingData) {
|
|
222
240
|
return commerce.checkout.shippingData({
|
|
@@ -224,11 +242,11 @@ const getOrderForm = async (
|
|
|
224
242
|
body: {
|
|
225
243
|
selectedAddresses: [session],
|
|
226
244
|
},
|
|
227
|
-
})
|
|
245
|
+
})
|
|
228
246
|
}
|
|
229
247
|
|
|
230
|
-
return orderForm
|
|
231
|
-
}
|
|
248
|
+
return orderForm
|
|
249
|
+
}
|
|
232
250
|
|
|
233
251
|
/**
|
|
234
252
|
* This resolver implements the optimistic cart behavior. The main idea in here
|
|
@@ -246,84 +264,93 @@ const getOrderForm = async (
|
|
|
246
264
|
export const validateCart = async (
|
|
247
265
|
_: unknown,
|
|
248
266
|
{ cart: { order }, session }: MutationValidateCartArgs,
|
|
249
|
-
ctx: Context
|
|
267
|
+
ctx: Context
|
|
250
268
|
) => {
|
|
251
|
-
const {
|
|
252
|
-
const { orderNumber, acceptedOffer } = order
|
|
269
|
+
const { orderNumber: orderNumberFromCart, acceptedOffer } = order
|
|
253
270
|
const {
|
|
254
271
|
clients: { commerce },
|
|
255
272
|
loaders: { skuLoader },
|
|
273
|
+
headers,
|
|
256
274
|
} = ctx
|
|
257
275
|
|
|
276
|
+
const channel = session?.channel
|
|
277
|
+
const locale = session?.locale
|
|
278
|
+
|
|
279
|
+
if (channel) {
|
|
280
|
+
mutateChannelContext(ctx, channel)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (locale) {
|
|
284
|
+
mutateLocaleContext(ctx, locale)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const orderNumberFromSession = await getOrderNumberFromSession(
|
|
288
|
+
headers,
|
|
289
|
+
commerce
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
const orderNumber = orderNumberFromSession ?? orderNumberFromCart ?? ''
|
|
293
|
+
|
|
258
294
|
// Step1: Get OrderForm from VTEX Commerce
|
|
259
295
|
const orderForm = await getOrderForm(orderNumber, session, ctx)
|
|
260
296
|
|
|
261
297
|
// Step1.5: Check if another system changed the orderForm with this orderNumber
|
|
262
298
|
// If so, this means the user interacted with this cart elsewhere and expects
|
|
263
299
|
// to see this new cart state instead of what's stored on the user's browser.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
joinItems,
|
|
300
|
+
const isStale = isOrderFormStale(orderForm)
|
|
301
|
+
|
|
302
|
+
if (isStale && orderNumber) {
|
|
303
|
+
const newOrderForm = await setOrderFormEtag(orderForm, commerce).then(
|
|
304
|
+
joinItems
|
|
270
305
|
)
|
|
271
|
-
|
|
272
|
-
return orderFormToCart(newOrderForm, skuLoader)
|
|
273
|
-
}
|
|
306
|
+
return orderFormToCart(newOrderForm, skuLoader)
|
|
274
307
|
}
|
|
275
308
|
|
|
276
309
|
// Step2: Process items from both browser and checkout so they have the same shape
|
|
277
310
|
const browserItemsById = groupById(acceptedOffer)
|
|
278
311
|
const originItemsById = groupById(orderForm.items.map(orderFormItemToOffer))
|
|
279
|
-
const originItems = Array.from(originItemsById.entries())
|
|
280
|
-
const browserItems = Array.from(browserItemsById.entries())
|
|
312
|
+
const originItems = Array.from(originItemsById.entries()) // items on the VTEX platform backend
|
|
313
|
+
const browserItems = Array.from(browserItemsById.entries()) // items on the user's browser
|
|
281
314
|
|
|
282
315
|
// Step3: Compute delta changes
|
|
283
|
-
const { itemsToAdd, itemsToUpdate } = browserItems
|
|
284
|
-
|
|
285
|
-
(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
items.forEach((item) => acc.itemsToAdd.push(item))
|
|
291
|
-
|
|
292
|
-
return acc
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Update existing items
|
|
296
|
-
const [head, ...tail] = maybeOriginItem
|
|
297
|
-
const totalQuantity = items.reduce(
|
|
298
|
-
(acc, curr) => acc + curr.quantity,
|
|
299
|
-
0,
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
// set total quantity to first item
|
|
303
|
-
acc.itemsToUpdate.push({
|
|
304
|
-
...head,
|
|
305
|
-
quantity: totalQuantity,
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
// Remove all the rest
|
|
309
|
-
tail.forEach((item) =>
|
|
310
|
-
acc.itemsToUpdate.push({ ...item, quantity: 0 })
|
|
311
|
-
)
|
|
316
|
+
const { itemsToAdd, itemsToUpdate } = browserItems.reduce(
|
|
317
|
+
(acc, [id, items]) => {
|
|
318
|
+
const maybeOriginItem = originItemsById.get(id)
|
|
319
|
+
|
|
320
|
+
// Adding new items to cart
|
|
321
|
+
if (!maybeOriginItem) {
|
|
322
|
+
items.forEach((item) => acc.itemsToAdd.push(item))
|
|
312
323
|
|
|
313
324
|
return acc
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Update existing items
|
|
328
|
+
const [head, ...tail] = maybeOriginItem
|
|
329
|
+
const totalQuantity = items.reduce((acc, curr) => acc + curr.quantity, 0)
|
|
330
|
+
|
|
331
|
+
// set total quantity to first item
|
|
332
|
+
acc.itemsToUpdate.push({
|
|
333
|
+
...head,
|
|
334
|
+
quantity: totalQuantity,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
// Remove all the rest
|
|
338
|
+
tail.forEach((item) => acc.itemsToUpdate.push({ ...item, quantity: 0 }))
|
|
339
|
+
|
|
340
|
+
return acc
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
itemsToAdd: [] as IStoreOffer[],
|
|
344
|
+
itemsToUpdate: [] as IStoreOffer[],
|
|
345
|
+
}
|
|
346
|
+
)
|
|
320
347
|
|
|
321
348
|
const itemsToDelete = originItems
|
|
322
349
|
.filter(([id]) => !browserItemsById.has(id))
|
|
323
350
|
.flatMap(([, items]) => items.map((item) => ({ ...item, quantity: 0 })))
|
|
324
351
|
|
|
325
352
|
const changes = [...itemsToAdd, ...itemsToUpdate, ...itemsToDelete].map(
|
|
326
|
-
offerToOrderItemInput
|
|
353
|
+
offerToOrderItemInput
|
|
327
354
|
)
|
|
328
355
|
|
|
329
356
|
if (changes.length === 0) {
|
|
@@ -339,7 +366,7 @@ export const validateCart = async (
|
|
|
339
366
|
})
|
|
340
367
|
// update orderForm etag so we know last time we touched this orderForm
|
|
341
368
|
.then((form) =>
|
|
342
|
-
|
|
369
|
+
setOrderFormEtag(form, commerce)
|
|
343
370
|
)
|
|
344
371
|
.then(joinItems)
|
|
345
372
|
|