@decocms/apps-vtex 7.24.0 → 7.25.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/package.json +12 -5
- package/src/actions/checkout.ts +475 -338
- package/src/hooks/__tests__/createCart.render.test.ts +114 -0
- package/src/hooks/__tests__/createCart.test.ts +92 -0
- package/src/hooks/cartQuery.ts +204 -0
- package/src/hooks/createCart.ts +427 -0
- package/src/hooks/index.ts +6 -0
- package/src/invoke.ts +159 -95
- package/src/loaders/cart/attachments.ts +53 -0
- package/src/loaders/cart/full.ts +67 -0
- package/src/loaders/cart/gifts.ts +43 -0
- package/src/loaders/cart/orderFormId.ts +31 -0
- package/src/loaders/cart/shipping.test.ts +38 -1
- package/src/loaders/cart/shipping.ts +67 -1
- package/src/loaders/cart/summary.ts +36 -0
- package/src/manifest.gen.ts +48 -38
- package/src/utils/__tests__/cartProjection.test.ts +118 -0
- package/src/utils/cartProjection.ts +96 -0
package/src/actions/checkout.ts
CHANGED
|
@@ -7,47 +7,47 @@
|
|
|
7
7
|
* @see https://developers.vtex.com/docs/api-reference/checkout-api
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
type CartProjection,
|
|
12
|
+
type CartSection,
|
|
13
|
+
defaultSectionsFor,
|
|
14
|
+
SECTIONS_FULL,
|
|
15
|
+
} from "@decocms/apps-commerce/types";
|
|
10
16
|
import { getVtexConfig, vtexFetchWithCookies } from "../client";
|
|
11
17
|
import type { OrderForm } from "../types";
|
|
18
|
+
import {
|
|
19
|
+
type ProjectOrderFormOptions,
|
|
20
|
+
projectOrderForm,
|
|
21
|
+
type VtexCartProjectionResult,
|
|
22
|
+
} from "../utils/cartProjection";
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"sellers",
|
|
20
|
-
"messages",
|
|
21
|
-
"marketingData",
|
|
22
|
-
"clientPreferencesData",
|
|
23
|
-
"storePreferencesData",
|
|
24
|
-
"giftRegistryData",
|
|
25
|
-
"ratesAndBenefitsData",
|
|
26
|
-
"openTextField",
|
|
27
|
-
"commercialConditionData",
|
|
28
|
-
"customData",
|
|
29
|
-
];
|
|
24
|
+
/**
|
|
25
|
+
* Legacy full-section default. Kept as an exported symbol (loaders/cart.ts and
|
|
26
|
+
* older callers import it), but sourced from the agnostic `SECTIONS_FULL`
|
|
27
|
+
* preset so the "which sections" list lives in exactly one place.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_EXPECTED_SECTIONS: string[] = SECTIONS_FULL;
|
|
30
30
|
|
|
31
31
|
function scParam(): string {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const sc = getVtexConfig().salesChannel;
|
|
33
|
+
return sc ? `sc=${sc}` : "";
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function appendSc(params: URLSearchParams): URLSearchParams {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const sc = getVtexConfig().salesChannel;
|
|
38
|
+
if (sc) params.set("sc", sc);
|
|
39
|
+
return params;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function forceHttpsOnAssets(orderForm: OrderForm): OrderForm {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
if (!orderForm?.items) return orderForm;
|
|
44
|
+
return {
|
|
45
|
+
...orderForm,
|
|
46
|
+
items: orderForm.items.map((item: any) => ({
|
|
47
|
+
...item,
|
|
48
|
+
imageUrl: item.imageUrl?.replace(/^http:/, "https:"),
|
|
49
|
+
})),
|
|
50
|
+
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// ---------------------------------------------------------------------------
|
|
@@ -55,107 +55,107 @@ function forceHttpsOnAssets(orderForm: OrderForm): OrderForm {
|
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
|
|
57
57
|
export interface GetOrCreateCartProps {
|
|
58
|
-
|
|
58
|
+
orderFormId?: string;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export async function getOrCreateCart(props: GetOrCreateCartProps): Promise<OrderForm> {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
62
|
+
const { orderFormId } = props;
|
|
63
|
+
const sc = scParam();
|
|
64
|
+
|
|
65
|
+
if (orderFormId) {
|
|
66
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
67
|
+
`/api/checkout/pub/orderForm/${orderFormId}${sc ? `?${sc}` : ""}`,
|
|
68
|
+
);
|
|
69
|
+
return forceHttpsOnAssets(result);
|
|
70
|
+
}
|
|
71
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
72
|
+
`/api/checkout/pub/orderForm${sc ? `?${sc}` : ""}`,
|
|
73
|
+
{
|
|
74
|
+
method: "POST",
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
expectedOrderFormSections: DEFAULT_EXPECTED_SECTIONS,
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
return forceHttpsOnAssets(result);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export interface AddItemsToCartProps {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
orderFormId: string;
|
|
85
|
+
orderItems: Array<{
|
|
86
|
+
id: string;
|
|
87
|
+
seller: string;
|
|
88
|
+
quantity: number;
|
|
89
|
+
index?: number;
|
|
90
|
+
price?: number;
|
|
91
|
+
}>;
|
|
92
|
+
allowedOutdatedData?: string[];
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
export async function addItemsToCart(props: AddItemsToCartProps): Promise<OrderForm> {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
const { orderFormId, orderItems, allowedOutdatedData = ["paymentData"] } = props;
|
|
97
|
+
const params = appendSc(new URLSearchParams());
|
|
98
|
+
for (const d of allowedOutdatedData) params.append("allowedOutdatedData", d);
|
|
99
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
100
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items?${params}`,
|
|
101
|
+
{ method: "POST", body: JSON.stringify({ orderItems }) },
|
|
102
|
+
);
|
|
103
|
+
return forceHttpsOnAssets(result);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export interface UpdateCartItemsProps {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
orderFormId: string;
|
|
108
|
+
orderItems: Array<{ index: number; quantity: number }>;
|
|
109
|
+
allowedOutdatedData?: string[];
|
|
110
|
+
noSplitItem?: boolean;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
export async function updateCartItems(props: UpdateCartItemsProps): Promise<OrderForm> {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
114
|
+
const { orderFormId, orderItems, allowedOutdatedData = ["paymentData"], noSplitItem } = props;
|
|
115
|
+
const params = appendSc(new URLSearchParams());
|
|
116
|
+
for (const d of allowedOutdatedData) {
|
|
117
|
+
params.append("allowedOutdatedData", d);
|
|
118
|
+
}
|
|
119
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
120
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items/update?${params}`,
|
|
121
|
+
{
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
orderItems,
|
|
125
|
+
noSplitItem: Boolean(noSplitItem),
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
return forceHttpsOnAssets(result);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
export interface RemoveAllItemsProps {
|
|
133
|
-
|
|
133
|
+
orderFormId: string;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
export async function removeAllItems(props: RemoveAllItemsProps): Promise<OrderForm> {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
const { orderFormId } = props;
|
|
138
|
+
const sc = scParam();
|
|
139
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
140
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items/removeAll${sc ? `?${sc}` : ""}`,
|
|
141
|
+
{ method: "POST", body: JSON.stringify({}) },
|
|
142
|
+
);
|
|
143
|
+
return forceHttpsOnAssets(result);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
export interface AddCouponToCartProps {
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
orderFormId: string;
|
|
148
|
+
text: string;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
export async function addCouponToCart(props: AddCouponToCartProps): Promise<OrderForm> {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
const { orderFormId, text } = props;
|
|
153
|
+
const sc = scParam();
|
|
154
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
155
|
+
`/api/checkout/pub/orderForm/${orderFormId}/coupons${sc ? `?${sc}` : ""}`,
|
|
156
|
+
{ method: "POST", body: JSON.stringify({ text }) },
|
|
157
|
+
);
|
|
158
|
+
return forceHttpsOnAssets(result);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
// ---------------------------------------------------------------------------
|
|
@@ -163,35 +163,35 @@ export async function addCouponToCart(props: AddCouponToCartProps): Promise<Orde
|
|
|
163
163
|
// ---------------------------------------------------------------------------
|
|
164
164
|
|
|
165
165
|
export interface SimulationItem {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
id: number | string;
|
|
167
|
+
quantity: number;
|
|
168
|
+
seller: string;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
export interface SimulateCartProps {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
items: SimulationItem[];
|
|
173
|
+
postalCode: string;
|
|
174
|
+
country?: string;
|
|
175
|
+
RnbBehavior?: 0 | 1;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
export async function simulateCart(props: SimulateCartProps) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
179
|
+
const { items, postalCode, country, RnbBehavior = 1 } = props;
|
|
180
|
+
const config = getVtexConfig();
|
|
181
|
+
const params = appendSc(new URLSearchParams({ RnbBehavior: String(RnbBehavior) }));
|
|
182
|
+
// Uses vtexFetchWithCookies so any Set-Cookie VTEX returns on the
|
|
183
|
+
// orderForm-scoped simulation reaches the browser via RequestContext.
|
|
184
|
+
// Without this, the segment/ownership cookies VTEX may rotate during
|
|
185
|
+
// simulation are dropped, and the storefront's local orderFormId
|
|
186
|
+
// drifts away from VTEX's checkout.vtex.com server cookie.
|
|
187
|
+
return vtexFetchWithCookies<any>(`/api/checkout/pub/orderForms/simulation?${params}`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
items,
|
|
191
|
+
postalCode,
|
|
192
|
+
country: country ?? config.country ?? "BRA",
|
|
193
|
+
}),
|
|
194
|
+
});
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// ---------------------------------------------------------------------------
|
|
@@ -199,55 +199,55 @@ export async function simulateCart(props: SimulateCartProps) {
|
|
|
199
199
|
// ---------------------------------------------------------------------------
|
|
200
200
|
|
|
201
201
|
export interface AddOfferingProps {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
orderFormId: string;
|
|
203
|
+
itemIndex: number;
|
|
204
|
+
offeringId: string | number;
|
|
205
|
+
expectedOrderFormSections?: string[];
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
export async function addOffering(props: AddOfferingProps): Promise<OrderForm> {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
209
|
+
const {
|
|
210
|
+
orderFormId,
|
|
211
|
+
itemIndex,
|
|
212
|
+
offeringId,
|
|
213
|
+
expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS,
|
|
214
|
+
} = props;
|
|
215
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
216
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items/${itemIndex}/offerings`,
|
|
217
|
+
{
|
|
218
|
+
method: "POST",
|
|
219
|
+
body: JSON.stringify({
|
|
220
|
+
expectedOrderFormSections,
|
|
221
|
+
id: offeringId,
|
|
222
|
+
info: null,
|
|
223
|
+
}),
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
return forceHttpsOnAssets(result);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
export interface RemoveOfferingProps {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
orderFormId: string;
|
|
231
|
+
itemIndex: number;
|
|
232
|
+
offeringId: string | number;
|
|
233
|
+
expectedOrderFormSections?: string[];
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
export async function removeOffering(props: RemoveOfferingProps): Promise<OrderForm> {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
237
|
+
const {
|
|
238
|
+
orderFormId,
|
|
239
|
+
itemIndex,
|
|
240
|
+
offeringId,
|
|
241
|
+
expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS,
|
|
242
|
+
} = props;
|
|
243
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
244
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items/${itemIndex}/offerings/${offeringId}/remove`,
|
|
245
|
+
{
|
|
246
|
+
method: "POST",
|
|
247
|
+
body: JSON.stringify({ expectedOrderFormSections }),
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
return forceHttpsOnAssets(result);
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
// ---------------------------------------------------------------------------
|
|
@@ -255,94 +255,94 @@ export async function removeOffering(props: RemoveOfferingProps): Promise<OrderF
|
|
|
255
255
|
// ---------------------------------------------------------------------------
|
|
256
256
|
|
|
257
257
|
export interface UpdateOrderFormAttachmentProps {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
258
|
+
orderFormId: string;
|
|
259
|
+
attachment: string;
|
|
260
|
+
body: Record<string, unknown>;
|
|
261
|
+
expectedOrderFormSections?: string[];
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
export async function updateOrderFormAttachment(
|
|
265
|
-
|
|
265
|
+
props: UpdateOrderFormAttachmentProps,
|
|
266
266
|
): Promise<OrderForm> {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
267
|
+
const {
|
|
268
|
+
orderFormId,
|
|
269
|
+
attachment,
|
|
270
|
+
body,
|
|
271
|
+
expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS,
|
|
272
|
+
} = props;
|
|
273
|
+
if (!orderFormId) throw new Error("Order form ID is required");
|
|
274
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
275
|
+
`/api/checkout/pub/orderForm/${orderFormId}/attachments/${attachment}`,
|
|
276
|
+
{
|
|
277
|
+
method: "POST",
|
|
278
|
+
body: JSON.stringify({ expectedOrderFormSections, ...body }),
|
|
279
|
+
},
|
|
280
|
+
);
|
|
281
|
+
return forceHttpsOnAssets(result);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
export interface UpdateItemAttachmentProps {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
285
|
+
orderFormId: string;
|
|
286
|
+
itemIndex: number;
|
|
287
|
+
attachment: string;
|
|
288
|
+
content: Record<string, unknown>;
|
|
289
|
+
noSplitItem?: boolean;
|
|
290
|
+
expectedOrderFormSections?: string[];
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
export async function updateItemAttachment(props: UpdateItemAttachmentProps): Promise<OrderForm> {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
294
|
+
const {
|
|
295
|
+
orderFormId,
|
|
296
|
+
itemIndex,
|
|
297
|
+
attachment,
|
|
298
|
+
content,
|
|
299
|
+
noSplitItem = true,
|
|
300
|
+
expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS,
|
|
301
|
+
} = props;
|
|
302
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
303
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items/${itemIndex}/attachments/${attachment}`,
|
|
304
|
+
{
|
|
305
|
+
method: "POST",
|
|
306
|
+
body: JSON.stringify({
|
|
307
|
+
content,
|
|
308
|
+
noSplitItem,
|
|
309
|
+
expectedOrderFormSections,
|
|
310
|
+
}),
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
return forceHttpsOnAssets(result);
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
export interface RemoveItemAttachmentProps {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
317
|
+
orderFormId: string;
|
|
318
|
+
itemIndex: number;
|
|
319
|
+
attachment: string;
|
|
320
|
+
content: Record<string, unknown>;
|
|
321
|
+
noSplitItem?: boolean;
|
|
322
|
+
expectedOrderFormSections?: string[];
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
export async function removeItemAttachment(props: RemoveItemAttachmentProps): Promise<OrderForm> {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
326
|
+
const {
|
|
327
|
+
orderFormId,
|
|
328
|
+
itemIndex,
|
|
329
|
+
attachment,
|
|
330
|
+
content,
|
|
331
|
+
noSplitItem = true,
|
|
332
|
+
expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS,
|
|
333
|
+
} = props;
|
|
334
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
335
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items/${itemIndex}/attachments/${attachment}`,
|
|
336
|
+
{
|
|
337
|
+
method: "DELETE",
|
|
338
|
+
body: JSON.stringify({
|
|
339
|
+
content,
|
|
340
|
+
noSplitItem,
|
|
341
|
+
expectedOrderFormSections,
|
|
342
|
+
}),
|
|
343
|
+
},
|
|
344
|
+
);
|
|
345
|
+
return forceHttpsOnAssets(result);
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
// ---------------------------------------------------------------------------
|
|
@@ -350,17 +350,17 @@ export async function removeItemAttachment(props: RemoveItemAttachmentProps): Pr
|
|
|
350
350
|
// ---------------------------------------------------------------------------
|
|
351
351
|
|
|
352
352
|
export interface UpdateItemPriceProps {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
353
|
+
orderFormId: string;
|
|
354
|
+
itemIndex: number;
|
|
355
|
+
price: number;
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
export async function updateItemPrice(props: UpdateItemPriceProps): Promise<OrderForm> {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
359
|
+
const { orderFormId, itemIndex, price } = props;
|
|
360
|
+
return vtexFetchWithCookies<OrderForm>(
|
|
361
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items/${itemIndex}/price`,
|
|
362
|
+
{ method: "PUT", body: JSON.stringify({ price }) },
|
|
363
|
+
);
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
// ---------------------------------------------------------------------------
|
|
@@ -368,31 +368,31 @@ export async function updateItemPrice(props: UpdateItemPriceProps): Promise<Orde
|
|
|
368
368
|
// ---------------------------------------------------------------------------
|
|
369
369
|
|
|
370
370
|
export interface UpdateSelectableGiftsProps {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
orderFormId: string;
|
|
372
|
+
giftId: string;
|
|
373
|
+
selectedGifts: Array<{ id: string; seller: string; quantity: number }>;
|
|
374
|
+
expectedOrderFormSections?: string[];
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
export async function updateSelectableGifts(props: UpdateSelectableGiftsProps): Promise<OrderForm> {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
378
|
+
const {
|
|
379
|
+
orderFormId,
|
|
380
|
+
giftId,
|
|
381
|
+
selectedGifts,
|
|
382
|
+
expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS,
|
|
383
|
+
} = props;
|
|
384
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
385
|
+
`/api/checkout/pub/orderForm/${orderFormId}/selectable-gifts/${giftId}`,
|
|
386
|
+
{
|
|
387
|
+
method: "POST",
|
|
388
|
+
body: JSON.stringify({
|
|
389
|
+
expectedOrderFormSections,
|
|
390
|
+
selectedGifts,
|
|
391
|
+
id: giftId,
|
|
392
|
+
}),
|
|
393
|
+
},
|
|
394
|
+
);
|
|
395
|
+
return forceHttpsOnAssets(result);
|
|
396
396
|
}
|
|
397
397
|
|
|
398
398
|
// ---------------------------------------------------------------------------
|
|
@@ -400,17 +400,17 @@ export async function updateSelectableGifts(props: UpdateSelectableGiftsProps):
|
|
|
400
400
|
// ---------------------------------------------------------------------------
|
|
401
401
|
|
|
402
402
|
export interface GetInstallmentsProps {
|
|
403
|
-
|
|
404
|
-
|
|
403
|
+
orderFormId: string;
|
|
404
|
+
paymentSystem: number;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
export async function getInstallments(props: GetInstallmentsProps) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
408
|
+
const { orderFormId, paymentSystem } = props;
|
|
409
|
+
const params = new URLSearchParams({ paymentSystem: String(paymentSystem) });
|
|
410
|
+
appendSc(params);
|
|
411
|
+
return vtexFetchWithCookies<any>(
|
|
412
|
+
`/api/checkout/pub/orderForm/${orderFormId}/installments?${params}`,
|
|
413
|
+
);
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
// ---------------------------------------------------------------------------
|
|
@@ -418,49 +418,49 @@ export async function getInstallments(props: GetInstallmentsProps) {
|
|
|
418
418
|
// ---------------------------------------------------------------------------
|
|
419
419
|
|
|
420
420
|
export interface UpdateOrderFormProfileProps {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
421
|
+
orderFormId: string;
|
|
422
|
+
fields: Record<string, unknown>;
|
|
423
|
+
ignoreProfileData?: boolean;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
export async function updateOrderFormProfile(
|
|
427
|
-
|
|
427
|
+
props: UpdateOrderFormProfileProps,
|
|
428
428
|
): Promise<OrderForm> {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
429
|
+
const { orderFormId, fields, ignoreProfileData } = props;
|
|
430
|
+
const body = ignoreProfileData ? { ...fields, ignoreProfileData: true } : fields;
|
|
431
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
432
|
+
`/api/checkout/pub/orderForm/${orderFormId}/profile`,
|
|
433
|
+
{ method: "PATCH", body: JSON.stringify(body) },
|
|
434
|
+
);
|
|
435
|
+
return forceHttpsOnAssets(result);
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
export interface ChangeToAnonymousUserProps {
|
|
439
|
-
|
|
439
|
+
orderFormId: string;
|
|
440
440
|
}
|
|
441
441
|
|
|
442
442
|
export async function changeToAnonymousUser(props: ChangeToAnonymousUserProps): Promise<OrderForm> {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
443
|
+
const { orderFormId } = props;
|
|
444
|
+
// This endpoint rotates the orderForm ownership cookies — must use
|
|
445
|
+
// vtexFetchWithCookies so the new cookies reach the browser.
|
|
446
|
+
return vtexFetchWithCookies<OrderForm>(`/api/checkout/changeToAnonymousUser/${orderFormId}`);
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
export interface ClearOrderFormMessagesProps {
|
|
450
|
-
|
|
450
|
+
orderFormId: string;
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
export async function clearOrderFormMessages(
|
|
454
|
-
|
|
454
|
+
props: ClearOrderFormMessagesProps,
|
|
455
455
|
): Promise<OrderForm> {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
456
|
+
const { orderFormId } = props;
|
|
457
|
+
return vtexFetchWithCookies<OrderForm>(
|
|
458
|
+
`/api/checkout/pub/orderForm/${orderFormId}/messages/clear`,
|
|
459
|
+
{
|
|
460
|
+
method: "POST",
|
|
461
|
+
body: JSON.stringify({}),
|
|
462
|
+
},
|
|
463
|
+
);
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
// ---------------------------------------------------------------------------
|
|
@@ -468,55 +468,192 @@ export async function clearOrderFormMessages(
|
|
|
468
468
|
// ---------------------------------------------------------------------------
|
|
469
469
|
|
|
470
470
|
export interface Seller {
|
|
471
|
-
|
|
472
|
-
|
|
471
|
+
id: string;
|
|
472
|
+
name: string;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
export interface RegionResult {
|
|
476
|
-
|
|
477
|
-
|
|
476
|
+
id: string;
|
|
477
|
+
sellers: Seller[];
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
export interface GetSellersByRegionProps {
|
|
481
|
-
|
|
482
|
-
|
|
481
|
+
postalCode: string;
|
|
482
|
+
salesChannel?: string;
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
export async function getSellersByRegion(
|
|
486
|
-
|
|
486
|
+
props: GetSellersByRegionProps,
|
|
487
487
|
): Promise<RegionResult | null> {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
488
|
+
const { postalCode, salesChannel } = props;
|
|
489
|
+
const params = new URLSearchParams({ country: "BRA", postalCode });
|
|
490
|
+
const sc = salesChannel ?? getVtexConfig().salesChannel;
|
|
491
|
+
if (sc) params.set("sc", sc);
|
|
492
|
+
const resp = await vtexFetchWithCookies<RegionResult[]>(`/api/checkout/pub/regions/?${params}`);
|
|
493
|
+
return resp[0]?.sellers?.length > 0 ? resp[0] : null;
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
export interface SetShippingPostalCodeProps {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
497
|
+
orderFormId: string;
|
|
498
|
+
postalCode: string;
|
|
499
|
+
country?: string;
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
export async function setShippingPostalCode(props: SetShippingPostalCodeProps): Promise<boolean> {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
503
|
+
const { orderFormId, postalCode, country = "BRA" } = props;
|
|
504
|
+
try {
|
|
505
|
+
// VTEX docs note that /attachments/shippingData can rotate the
|
|
506
|
+
// CheckoutOrderFormOwnership cookie. vtexFetchWithCookies ensures
|
|
507
|
+
// any such Set-Cookie reaches the browser via RequestContext,
|
|
508
|
+
// keeping the storefront and VTEX bound to the same orderForm.
|
|
509
|
+
await vtexFetchWithCookies<any>(
|
|
510
|
+
`/api/checkout/pub/orderForm/${orderFormId}/attachments/shippingData`,
|
|
511
|
+
{
|
|
512
|
+
method: "POST",
|
|
513
|
+
body: JSON.stringify({
|
|
514
|
+
selectedAddresses: [{ postalCode, country }],
|
|
515
|
+
}),
|
|
516
|
+
},
|
|
517
|
+
);
|
|
518
|
+
return true;
|
|
519
|
+
} catch {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
// Cart v2 — granular sections + server-side projection
|
|
526
|
+
//
|
|
527
|
+
// The v1 CRUD above always returns the raw OrderForm with all 15 sections.
|
|
528
|
+
// The v2 variants let the caller pick (a) which `sections` VTEX computes and
|
|
529
|
+
// (b) which `projection` reaches the client — defaulting to the minimum
|
|
530
|
+
// (`SECTIONS_MINIMAL` + `"summary+items"`). They are additive: v1 stays for
|
|
531
|
+
// back-compat, v2 is opt-in via the new loaders/hooks.
|
|
532
|
+
//
|
|
533
|
+
// @see `@decocms/apps-commerce/types/cart` for the contract.
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
/** Shared fragmentation knobs for every v2 cart operation. */
|
|
537
|
+
export interface CartV2Options {
|
|
538
|
+
/** Sections to request from VTEX. Defaults to the projection's preset. */
|
|
539
|
+
sections?: CartSection[];
|
|
540
|
+
/** Client-facing shape. Default: `"summary+items"`. */
|
|
541
|
+
projection?: CartProjection;
|
|
542
|
+
/** Storefront overrides forwarded when `projection === "minicart"`. */
|
|
543
|
+
minicartOptions?: ProjectOrderFormOptions;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function resolveFragmentation(opts: CartV2Options): {
|
|
547
|
+
sections: string[];
|
|
548
|
+
projection: CartProjection;
|
|
549
|
+
} {
|
|
550
|
+
const projection = opts.projection ?? "summary+items";
|
|
551
|
+
const sections = opts.sections ?? defaultSectionsFor(projection);
|
|
552
|
+
return { sections, projection };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export interface GetOrCreateCartV2Props extends CartV2Options {
|
|
556
|
+
orderFormId?: string;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* v2 of {@link getOrCreateCart}. Fetches (or creates, when no id) the cart with
|
|
561
|
+
* only the requested sections and returns the requested projection.
|
|
562
|
+
*/
|
|
563
|
+
export async function getOrCreateCartV2(
|
|
564
|
+
props: GetOrCreateCartV2Props,
|
|
565
|
+
): Promise<VtexCartProjectionResult> {
|
|
566
|
+
const { orderFormId } = props;
|
|
567
|
+
const { sections, projection } = resolveFragmentation(props);
|
|
568
|
+
const sc = scParam();
|
|
569
|
+
const body = JSON.stringify({ expectedOrderFormSections: sections });
|
|
570
|
+
|
|
571
|
+
const result = orderFormId
|
|
572
|
+
? await vtexFetchWithCookies<OrderForm>(
|
|
573
|
+
`/api/checkout/pub/orderForm/${orderFormId}${sc ? `?${sc}` : ""}`,
|
|
574
|
+
{ method: "POST", body },
|
|
575
|
+
)
|
|
576
|
+
: await vtexFetchWithCookies<OrderForm>(`/api/checkout/pub/orderForm${sc ? `?${sc}` : ""}`, {
|
|
577
|
+
method: "POST",
|
|
578
|
+
body,
|
|
579
|
+
});
|
|
580
|
+
return projectOrderForm(forceHttpsOnAssets(result), projection, props.minicartOptions);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export interface AddItemsToCartV2Props extends CartV2Options {
|
|
584
|
+
orderFormId: string;
|
|
585
|
+
orderItems: Array<{
|
|
586
|
+
id: string;
|
|
587
|
+
seller: string;
|
|
588
|
+
quantity: number;
|
|
589
|
+
index?: number;
|
|
590
|
+
price?: number;
|
|
591
|
+
}>;
|
|
592
|
+
allowedOutdatedData?: string[];
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** v2 of {@link addItemsToCart} — fragmented request + projected response. */
|
|
596
|
+
export async function addItemsToCartV2(
|
|
597
|
+
props: AddItemsToCartV2Props,
|
|
598
|
+
): Promise<VtexCartProjectionResult> {
|
|
599
|
+
const { orderFormId, orderItems, allowedOutdatedData = ["paymentData"] } = props;
|
|
600
|
+
const { sections, projection } = resolveFragmentation(props);
|
|
601
|
+
const params = appendSc(new URLSearchParams());
|
|
602
|
+
for (const d of allowedOutdatedData) params.append("allowedOutdatedData", d);
|
|
603
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
604
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items?${params}`,
|
|
605
|
+
{
|
|
606
|
+
method: "POST",
|
|
607
|
+
body: JSON.stringify({ orderItems, expectedOrderFormSections: sections }),
|
|
608
|
+
},
|
|
609
|
+
);
|
|
610
|
+
return projectOrderForm(forceHttpsOnAssets(result), projection, props.minicartOptions);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export interface UpdateCartItemsV2Props extends CartV2Options {
|
|
614
|
+
orderFormId: string;
|
|
615
|
+
orderItems: Array<{ index: number; quantity: number }>;
|
|
616
|
+
allowedOutdatedData?: string[];
|
|
617
|
+
noSplitItem?: boolean;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** v2 of {@link updateCartItems} — used for quantity change and item removal (`quantity: 0`). */
|
|
621
|
+
export async function updateCartItemsV2(
|
|
622
|
+
props: UpdateCartItemsV2Props,
|
|
623
|
+
): Promise<VtexCartProjectionResult> {
|
|
624
|
+
const { orderFormId, orderItems, allowedOutdatedData = ["paymentData"], noSplitItem } = props;
|
|
625
|
+
const { sections, projection } = resolveFragmentation(props);
|
|
626
|
+
const params = appendSc(new URLSearchParams());
|
|
627
|
+
for (const d of allowedOutdatedData) params.append("allowedOutdatedData", d);
|
|
628
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
629
|
+
`/api/checkout/pub/orderForm/${orderFormId}/items/update?${params}`,
|
|
630
|
+
{
|
|
631
|
+
method: "POST",
|
|
632
|
+
body: JSON.stringify({
|
|
633
|
+
orderItems,
|
|
634
|
+
noSplitItem: Boolean(noSplitItem),
|
|
635
|
+
expectedOrderFormSections: sections,
|
|
636
|
+
}),
|
|
637
|
+
},
|
|
638
|
+
);
|
|
639
|
+
return projectOrderForm(forceHttpsOnAssets(result), projection, props.minicartOptions);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export interface AddCouponToCartV2Props extends CartV2Options {
|
|
643
|
+
orderFormId: string;
|
|
644
|
+
text: string;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** v2 of {@link addCouponToCart} — a coupon typically needs the drawer projection. */
|
|
648
|
+
export async function addCouponToCartV2(
|
|
649
|
+
props: AddCouponToCartV2Props,
|
|
650
|
+
): Promise<VtexCartProjectionResult> {
|
|
651
|
+
const { orderFormId, text } = props;
|
|
652
|
+
const { sections, projection } = resolveFragmentation(props);
|
|
653
|
+
const sc = scParam();
|
|
654
|
+
const result = await vtexFetchWithCookies<OrderForm>(
|
|
655
|
+
`/api/checkout/pub/orderForm/${orderFormId}/coupons${sc ? `?${sc}` : ""}`,
|
|
656
|
+
{ method: "POST", body: JSON.stringify({ text, expectedOrderFormSections: sections }) },
|
|
657
|
+
);
|
|
658
|
+
return projectOrderForm(forceHttpsOnAssets(result), projection, props.minicartOptions);
|
|
522
659
|
}
|