@decocms/apps-vtex 7.24.0 → 7.26.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.
@@ -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
- export const DEFAULT_EXPECTED_SECTIONS = [
14
- "items",
15
- "totalizers",
16
- "clientProfileData",
17
- "shippingData",
18
- "paymentData",
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
- const sc = getVtexConfig().salesChannel;
33
- return sc ? `sc=${sc}` : "";
32
+ const sc = getVtexConfig().salesChannel;
33
+ return sc ? `sc=${sc}` : "";
34
34
  }
35
35
 
36
36
  function appendSc(params: URLSearchParams): URLSearchParams {
37
- const sc = getVtexConfig().salesChannel;
38
- if (sc) params.set("sc", sc);
39
- return params;
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
- 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
- };
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
- orderFormId?: string;
58
+ orderFormId?: string;
59
59
  }
60
60
 
61
61
  export async function getOrCreateCart(props: GetOrCreateCartProps): Promise<OrderForm> {
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);
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
- orderFormId: string;
85
- orderItems: Array<{
86
- id: string;
87
- seller: string;
88
- quantity: number;
89
- index?: number;
90
- price?: number;
91
- }>;
92
- allowedOutdatedData?: string[];
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
- 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);
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
- orderFormId: string;
108
- orderItems: Array<{ index: number; quantity: number }>;
109
- allowedOutdatedData?: string[];
110
- noSplitItem?: boolean;
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
- 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);
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
- orderFormId: string;
133
+ orderFormId: string;
134
134
  }
135
135
 
136
136
  export async function removeAllItems(props: RemoveAllItemsProps): Promise<OrderForm> {
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);
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
- orderFormId: string;
148
- text: string;
147
+ orderFormId: string;
148
+ text: string;
149
149
  }
150
150
 
151
151
  export async function addCouponToCart(props: AddCouponToCartProps): Promise<OrderForm> {
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);
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
- id: number | string;
167
- quantity: number;
168
- seller: string;
166
+ id: number | string;
167
+ quantity: number;
168
+ seller: string;
169
169
  }
170
170
 
171
171
  export interface SimulateCartProps {
172
- items: SimulationItem[];
173
- postalCode: string;
174
- country?: string;
175
- RnbBehavior?: 0 | 1;
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
- 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
- });
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
- orderFormId: string;
203
- itemIndex: number;
204
- offeringId: string | number;
205
- expectedOrderFormSections?: string[];
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
- 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);
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
- orderFormId: string;
231
- itemIndex: number;
232
- offeringId: string | number;
233
- expectedOrderFormSections?: string[];
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
- 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);
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
- orderFormId: string;
259
- attachment: string;
260
- body: Record<string, unknown>;
261
- expectedOrderFormSections?: string[];
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
- props: UpdateOrderFormAttachmentProps,
265
+ props: UpdateOrderFormAttachmentProps,
266
266
  ): Promise<OrderForm> {
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);
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
- orderFormId: string;
286
- itemIndex: number;
287
- attachment: string;
288
- content: Record<string, unknown>;
289
- noSplitItem?: boolean;
290
- expectedOrderFormSections?: string[];
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
- 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);
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
- orderFormId: string;
318
- itemIndex: number;
319
- attachment: string;
320
- content: Record<string, unknown>;
321
- noSplitItem?: boolean;
322
- expectedOrderFormSections?: string[];
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
- 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);
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
- orderFormId: string;
354
- itemIndex: number;
355
- price: number;
353
+ orderFormId: string;
354
+ itemIndex: number;
355
+ price: number;
356
356
  }
357
357
 
358
358
  export async function updateItemPrice(props: UpdateItemPriceProps): Promise<OrderForm> {
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
- );
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
- orderFormId: string;
372
- giftId: string;
373
- selectedGifts: Array<{ id: string; seller: string; quantity: number }>;
374
- expectedOrderFormSections?: string[];
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
- 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);
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
- orderFormId: string;
404
- paymentSystem: number;
403
+ orderFormId: string;
404
+ paymentSystem: number;
405
405
  }
406
406
 
407
407
  export async function getInstallments(props: GetInstallmentsProps) {
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
- );
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
- orderFormId: string;
422
- fields: Record<string, unknown>;
423
- ignoreProfileData?: boolean;
421
+ orderFormId: string;
422
+ fields: Record<string, unknown>;
423
+ ignoreProfileData?: boolean;
424
424
  }
425
425
 
426
426
  export async function updateOrderFormProfile(
427
- props: UpdateOrderFormProfileProps,
427
+ props: UpdateOrderFormProfileProps,
428
428
  ): Promise<OrderForm> {
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);
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
- orderFormId: string;
439
+ orderFormId: string;
440
440
  }
441
441
 
442
442
  export async function changeToAnonymousUser(props: ChangeToAnonymousUserProps): Promise<OrderForm> {
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}`);
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
- orderFormId: string;
450
+ orderFormId: string;
451
451
  }
452
452
 
453
453
  export async function clearOrderFormMessages(
454
- props: ClearOrderFormMessagesProps,
454
+ props: ClearOrderFormMessagesProps,
455
455
  ): Promise<OrderForm> {
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
- );
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
- id: string;
472
- name: string;
471
+ id: string;
472
+ name: string;
473
473
  }
474
474
 
475
475
  export interface RegionResult {
476
- id: string;
477
- sellers: Seller[];
476
+ id: string;
477
+ sellers: Seller[];
478
478
  }
479
479
 
480
480
  export interface GetSellersByRegionProps {
481
- postalCode: string;
482
- salesChannel?: string;
481
+ postalCode: string;
482
+ salesChannel?: string;
483
483
  }
484
484
 
485
485
  export async function getSellersByRegion(
486
- props: GetSellersByRegionProps,
486
+ props: GetSellersByRegionProps,
487
487
  ): Promise<RegionResult | null> {
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;
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
- orderFormId: string;
498
- postalCode: string;
499
- country?: string;
497
+ orderFormId: string;
498
+ postalCode: string;
499
+ country?: string;
500
500
  }
501
501
 
502
502
  export async function setShippingPostalCode(props: SetShippingPostalCodeProps): Promise<boolean> {
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
- }
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
  }