@decocms/apps-vtex 7.2.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.
Files changed (109) hide show
  1. package/package.json +67 -0
  2. package/src/README.md +6 -0
  3. package/src/__tests__/client-segment-cookie.test.ts +255 -0
  4. package/src/__tests__/client-set-cookie-forward.test.ts +257 -0
  5. package/src/actions/address.ts +250 -0
  6. package/src/actions/analytics/sendEvent.ts +86 -0
  7. package/src/actions/auth.ts +333 -0
  8. package/src/actions/checkout.ts +522 -0
  9. package/src/actions/index.ts +11 -0
  10. package/src/actions/masterData.ts +168 -0
  11. package/src/actions/misc.ts +188 -0
  12. package/src/actions/newsletter.ts +105 -0
  13. package/src/actions/orders.ts +34 -0
  14. package/src/actions/profile.ts +201 -0
  15. package/src/actions/session.ts +85 -0
  16. package/src/actions/trigger.ts +43 -0
  17. package/src/actions/wishlist.ts +114 -0
  18. package/src/client.ts +667 -0
  19. package/src/commerceLoaders.ts +261 -0
  20. package/src/hooks/__tests__/createUseCart.test.ts +184 -0
  21. package/src/hooks/__tests__/createUseUser.test.ts +48 -0
  22. package/src/hooks/__tests__/createUseWishlist.test.ts +87 -0
  23. package/src/hooks/createUseCart.ts +360 -0
  24. package/src/hooks/createUseUser.ts +153 -0
  25. package/src/hooks/createUseWishlist.ts +242 -0
  26. package/src/hooks/index.ts +19 -0
  27. package/src/hooks/useAutocomplete.ts +83 -0
  28. package/src/hooks/useCart.ts +243 -0
  29. package/src/hooks/useUser.ts +78 -0
  30. package/src/hooks/useWishlist.ts +119 -0
  31. package/src/index.ts +17 -0
  32. package/src/invoke.ts +181 -0
  33. package/src/loaders/ProductDetailsPage.ts +1 -0
  34. package/src/loaders/ProductList.ts +1 -0
  35. package/src/loaders/ProductListingPage.ts +1 -0
  36. package/src/loaders/address.ts +115 -0
  37. package/src/loaders/autocomplete.ts +58 -0
  38. package/src/loaders/brands.ts +44 -0
  39. package/src/loaders/cart.ts +52 -0
  40. package/src/loaders/catalog.ts +163 -0
  41. package/src/loaders/collections.ts +55 -0
  42. package/src/loaders/index.ts +19 -0
  43. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +20 -0
  44. package/src/loaders/intelligentSearch/productDetailsPage.ts +95 -0
  45. package/src/loaders/intelligentSearch/productList.ts +154 -0
  46. package/src/loaders/intelligentSearch/productListingPage.ts +506 -0
  47. package/src/loaders/intelligentSearch/suggestions.ts +46 -0
  48. package/src/loaders/legacy/productDetailsPage.ts +1 -0
  49. package/src/loaders/legacy/productList.ts +1 -0
  50. package/src/loaders/legacy/relatedProductsLoader.ts +81 -0
  51. package/src/loaders/legacy.ts +635 -0
  52. package/src/loaders/logistics.ts +111 -0
  53. package/src/loaders/minicart.ts +78 -0
  54. package/src/loaders/navbar.ts +26 -0
  55. package/src/loaders/orders.ts +92 -0
  56. package/src/loaders/pageType.ts +68 -0
  57. package/src/loaders/payment.ts +97 -0
  58. package/src/loaders/productListFull.ts +159 -0
  59. package/src/loaders/profile.ts +138 -0
  60. package/src/loaders/promotion.ts +30 -0
  61. package/src/loaders/search.ts +124 -0
  62. package/src/loaders/session.ts +83 -0
  63. package/src/loaders/user.ts +87 -0
  64. package/src/loaders/wishlist.ts +89 -0
  65. package/src/loaders/wishlistProducts.ts +69 -0
  66. package/src/loaders/workflow/products.ts +64 -0
  67. package/src/loaders/workflow.ts +316 -0
  68. package/src/logo.png +0 -0
  69. package/src/middleware.ts +240 -0
  70. package/src/mod.ts +152 -0
  71. package/src/registry.ts +9 -0
  72. package/src/types.ts +248 -0
  73. package/src/utils/__tests__/cookieSanitizer.test.ts +162 -0
  74. package/src/utils/__tests__/fetch.test.ts +80 -0
  75. package/src/utils/__tests__/fetchCache.test.ts +112 -0
  76. package/src/utils/__tests__/instrumentedFetch.test.ts +158 -0
  77. package/src/utils/__tests__/intelligentSearch.test.ts +88 -0
  78. package/src/utils/__tests__/minicart.test.ts +184 -0
  79. package/src/utils/__tests__/operationRouter.test.ts +227 -0
  80. package/src/utils/__tests__/resourceRange.test.ts +32 -0
  81. package/src/utils/__tests__/sitemap.test.ts +185 -0
  82. package/src/utils/__tests__/slugify.test.ts +31 -0
  83. package/src/utils/__tests__/transform.test.ts +698 -0
  84. package/src/utils/accountLoaders.ts +203 -0
  85. package/src/utils/authHelpers.ts +85 -0
  86. package/src/utils/batch.ts +18 -0
  87. package/src/utils/cookieSanitizer.ts +173 -0
  88. package/src/utils/cookies.ts +167 -0
  89. package/src/utils/enrichment.ts +560 -0
  90. package/src/utils/fetch.ts +107 -0
  91. package/src/utils/fetchCache.ts +222 -0
  92. package/src/utils/index.ts +19 -0
  93. package/src/utils/instrumentedFetch.ts +78 -0
  94. package/src/utils/intelligentSearch.ts +96 -0
  95. package/src/utils/legacy.ts +139 -0
  96. package/src/utils/minicart.ts +139 -0
  97. package/src/utils/operationRouter.ts +139 -0
  98. package/src/utils/pickAndOmit.ts +25 -0
  99. package/src/utils/proxy.ts +435 -0
  100. package/src/utils/resourceRange.ts +10 -0
  101. package/src/utils/segment.ts +152 -0
  102. package/src/utils/similars.ts +37 -0
  103. package/src/utils/sitemap.ts +282 -0
  104. package/src/utils/slugCache.ts +28 -0
  105. package/src/utils/slugify.ts +13 -0
  106. package/src/utils/transform.ts +1509 -0
  107. package/src/utils/types.ts +1884 -0
  108. package/src/utils/vtexId.ts +138 -0
  109. package/tsconfig.json +7 -0
@@ -0,0 +1,522 @@
1
+ /**
2
+ * VTEX Checkout API actions.
3
+ * Each function receives a single props object — matching the invoke handler
4
+ * pattern from deco-cx/apps where `setupApps()` registers handlers directly.
5
+ *
6
+ * Ported from deco-cx/apps vtex/actions/cart/*.ts
7
+ * @see https://developers.vtex.com/docs/api-reference/checkout-api
8
+ */
9
+
10
+ import { getVtexConfig, vtexFetchWithCookies } from "../client";
11
+ import type { OrderForm } from "../types";
12
+
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
+ ];
30
+
31
+ function scParam(): string {
32
+ const sc = getVtexConfig().salesChannel;
33
+ return sc ? `sc=${sc}` : "";
34
+ }
35
+
36
+ function appendSc(params: URLSearchParams): URLSearchParams {
37
+ const sc = getVtexConfig().salesChannel;
38
+ if (sc) params.set("sc", sc);
39
+ return params;
40
+ }
41
+
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
+ };
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Cart (OrderForm) — core CRUD
55
+ // ---------------------------------------------------------------------------
56
+
57
+ export interface GetOrCreateCartProps {
58
+ orderFormId?: string;
59
+ }
60
+
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);
81
+ }
82
+
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[];
93
+ }
94
+
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);
104
+ }
105
+
106
+ export interface UpdateCartItemsProps {
107
+ orderFormId: string;
108
+ orderItems: Array<{ index: number; quantity: number }>;
109
+ allowedOutdatedData?: string[];
110
+ noSplitItem?: boolean;
111
+ }
112
+
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);
130
+ }
131
+
132
+ export interface RemoveAllItemsProps {
133
+ orderFormId: string;
134
+ }
135
+
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);
144
+ }
145
+
146
+ export interface AddCouponToCartProps {
147
+ orderFormId: string;
148
+ text: string;
149
+ }
150
+
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);
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Cart — simulation
163
+ // ---------------------------------------------------------------------------
164
+
165
+ export interface SimulationItem {
166
+ id: number | string;
167
+ quantity: number;
168
+ seller: string;
169
+ }
170
+
171
+ export interface SimulateCartProps {
172
+ items: SimulationItem[];
173
+ postalCode: string;
174
+ country?: string;
175
+ RnbBehavior?: 0 | 1;
176
+ }
177
+
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
+ });
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Cart — offerings (services attached to items)
199
+ // ---------------------------------------------------------------------------
200
+
201
+ export interface AddOfferingProps {
202
+ orderFormId: string;
203
+ itemIndex: number;
204
+ offeringId: string | number;
205
+ expectedOrderFormSections?: string[];
206
+ }
207
+
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);
227
+ }
228
+
229
+ export interface RemoveOfferingProps {
230
+ orderFormId: string;
231
+ itemIndex: number;
232
+ offeringId: string | number;
233
+ expectedOrderFormSections?: string[];
234
+ }
235
+
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);
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // Cart — attachments
255
+ // ---------------------------------------------------------------------------
256
+
257
+ export interface UpdateOrderFormAttachmentProps {
258
+ orderFormId: string;
259
+ attachment: string;
260
+ body: Record<string, unknown>;
261
+ expectedOrderFormSections?: string[];
262
+ }
263
+
264
+ export async function updateOrderFormAttachment(
265
+ props: UpdateOrderFormAttachmentProps,
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);
282
+ }
283
+
284
+ export interface UpdateItemAttachmentProps {
285
+ orderFormId: string;
286
+ itemIndex: number;
287
+ attachment: string;
288
+ content: Record<string, unknown>;
289
+ noSplitItem?: boolean;
290
+ expectedOrderFormSections?: string[];
291
+ }
292
+
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);
314
+ }
315
+
316
+ export interface RemoveItemAttachmentProps {
317
+ orderFormId: string;
318
+ itemIndex: number;
319
+ attachment: string;
320
+ content: Record<string, unknown>;
321
+ noSplitItem?: boolean;
322
+ expectedOrderFormSections?: string[];
323
+ }
324
+
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);
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // Cart — price override
350
+ // ---------------------------------------------------------------------------
351
+
352
+ export interface UpdateItemPriceProps {
353
+ orderFormId: string;
354
+ itemIndex: number;
355
+ price: number;
356
+ }
357
+
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
+ );
364
+ }
365
+
366
+ // ---------------------------------------------------------------------------
367
+ // Cart — selectable gifts
368
+ // ---------------------------------------------------------------------------
369
+
370
+ export interface UpdateSelectableGiftsProps {
371
+ orderFormId: string;
372
+ giftId: string;
373
+ selectedGifts: Array<{ id: string; seller: string; quantity: number }>;
374
+ expectedOrderFormSections?: string[];
375
+ }
376
+
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);
396
+ }
397
+
398
+ // ---------------------------------------------------------------------------
399
+ // Cart — installments
400
+ // ---------------------------------------------------------------------------
401
+
402
+ export interface GetInstallmentsProps {
403
+ orderFormId: string;
404
+ paymentSystem: number;
405
+ }
406
+
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
+ );
414
+ }
415
+
416
+ // ---------------------------------------------------------------------------
417
+ // Cart — profile & messages
418
+ // ---------------------------------------------------------------------------
419
+
420
+ export interface UpdateOrderFormProfileProps {
421
+ orderFormId: string;
422
+ fields: Record<string, unknown>;
423
+ ignoreProfileData?: boolean;
424
+ }
425
+
426
+ export async function updateOrderFormProfile(
427
+ props: UpdateOrderFormProfileProps,
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);
436
+ }
437
+
438
+ export interface ChangeToAnonymousUserProps {
439
+ orderFormId: string;
440
+ }
441
+
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}`);
447
+ }
448
+
449
+ export interface ClearOrderFormMessagesProps {
450
+ orderFormId: string;
451
+ }
452
+
453
+ export async function clearOrderFormMessages(
454
+ props: ClearOrderFormMessagesProps,
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
+ );
464
+ }
465
+
466
+ // ---------------------------------------------------------------------------
467
+ // Shipping / Regions
468
+ // ---------------------------------------------------------------------------
469
+
470
+ export interface Seller {
471
+ id: string;
472
+ name: string;
473
+ }
474
+
475
+ export interface RegionResult {
476
+ id: string;
477
+ sellers: Seller[];
478
+ }
479
+
480
+ export interface GetSellersByRegionProps {
481
+ postalCode: string;
482
+ salesChannel?: string;
483
+ }
484
+
485
+ export async function getSellersByRegion(
486
+ props: GetSellersByRegionProps,
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;
494
+ }
495
+
496
+ export interface SetShippingPostalCodeProps {
497
+ orderFormId: string;
498
+ postalCode: string;
499
+ country?: string;
500
+ }
501
+
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
+ }
522
+ }
@@ -0,0 +1,11 @@
1
+ export * from "./address";
2
+ export * from "./auth";
3
+ export * from "./checkout";
4
+ export * from "./masterData";
5
+ export * from "./misc";
6
+ export * from "./newsletter";
7
+ export * from "./orders";
8
+ export * from "./profile";
9
+ export * from "./session";
10
+ export * from "./trigger";
11
+ export * from "./wishlist";
@@ -0,0 +1,168 @@
1
+ /**
2
+ * VTEX MasterData v2 API actions.
3
+ * Generic CRUD operations on data entities.
4
+ */
5
+ import { getVtexConfig, getVtexFetch, vtexFetch, vtexFetchResponse } from "../client";
6
+
7
+ function removeEmptyFields(obj: Record<string, any>): Record<string, any> {
8
+ return Object.fromEntries(
9
+ Object.entries(obj).filter(
10
+ ([_, value]) => value !== "" && value !== undefined && value !== null,
11
+ ),
12
+ );
13
+ }
14
+
15
+ export interface CreateDocumentResult {
16
+ DocumentId: string;
17
+ }
18
+
19
+ export interface CreateDocumentProps {
20
+ entity: string;
21
+ data: Record<string, any>;
22
+ }
23
+
24
+ export async function createDocument(props: CreateDocumentProps): Promise<CreateDocumentResult> {
25
+ const { entity, data } = props;
26
+ return vtexFetch<CreateDocumentResult>(`/api/dataentities/${entity}/documents`, {
27
+ method: "POST",
28
+ body: JSON.stringify(removeEmptyFields(data)),
29
+ });
30
+ }
31
+
32
+ export interface GetDocumentProps {
33
+ entity: string;
34
+ documentId: string;
35
+ }
36
+
37
+ export async function getDocument<T = unknown>(props: GetDocumentProps): Promise<T> {
38
+ const { entity, documentId } = props;
39
+ return vtexFetch<T>(`/api/dataentities/${entity}/documents/${documentId}`);
40
+ }
41
+
42
+ export interface PatchDocumentProps {
43
+ entity: string;
44
+ documentId: string;
45
+ data: Record<string, any>;
46
+ }
47
+
48
+ export async function patchDocument(props: PatchDocumentProps): Promise<void> {
49
+ const { entity, documentId, data } = props;
50
+ await vtexFetch<any>(`/api/dataentities/${entity}/documents/${documentId}`, {
51
+ method: "PATCH",
52
+ body: JSON.stringify(removeEmptyFields(data)),
53
+ });
54
+ }
55
+
56
+ export interface MasterDataSearchResult {
57
+ id: string;
58
+ accountId: string;
59
+ accountName: string;
60
+ dataEntityId: string;
61
+ [key: string]: any;
62
+ }
63
+
64
+ export interface SearchDocumentsProps {
65
+ entity: string;
66
+ filter: string;
67
+ }
68
+
69
+ /**
70
+ * Simple search — kept for backward compat.
71
+ */
72
+ export async function searchDocuments<T = MasterDataSearchResult>(
73
+ props: SearchDocumentsProps,
74
+ ): Promise<T[]> {
75
+ const { entity, filter } = props;
76
+ return vtexFetch<T[]>(`/api/dataentities/${entity}/search?_where=${encodeURIComponent(filter)}`);
77
+ }
78
+
79
+ /**
80
+ * Full MasterData search with pagination, field selection, and sorting.
81
+ * Ported from deco-cx/apps vtex/loaders/masterdata/searchDocuments.ts
82
+ *
83
+ * @see https://developers.vtex.com/docs/api-reference/masterdata-api#get-/api/dataentities/-acronym-/search
84
+ */
85
+ export interface SearchDocumentsFullProps {
86
+ acronym: string;
87
+ fields?: string;
88
+ where?: string;
89
+ sort?: string;
90
+ /** @default 10 (max 100) */
91
+ take?: number;
92
+ /** @default 0 */
93
+ skip?: number;
94
+ }
95
+
96
+ export async function searchDocumentsFull<T = Record<string, unknown>>(
97
+ props: SearchDocumentsFullProps,
98
+ ): Promise<T[]> {
99
+ const { acronym, fields, where, sort, skip = 0, take = 10 } = props;
100
+ const from = Math.max(skip, 0);
101
+ const to = from + Math.min(100, take);
102
+
103
+ const params = new URLSearchParams();
104
+ if (fields) params.set("_fields", fields);
105
+ if (where) params.set("_where", where);
106
+ if (sort) params.set("_sort", sort);
107
+
108
+ const headers: Record<string, string> = {
109
+ accept: "application/vnd.vtex.ds.v10+json",
110
+ "content-type": "application/json",
111
+ "REST-Range": `resources=${from}-${to}`,
112
+ };
113
+
114
+ return vtexFetchResponse(`/api/dataentities/${acronym}/search?${params}`, {
115
+ headers,
116
+ }).then((res) => res.json());
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // Attachments (file upload)
121
+ // ---------------------------------------------------------------------------
122
+
123
+ export interface UploadAttachmentOpts {
124
+ entity: string;
125
+ documentId: string;
126
+ field: string;
127
+ fileName: string;
128
+ /** Base64-encoded file content */
129
+ fileBase64: string;
130
+ contentType: string;
131
+ }
132
+
133
+ /**
134
+ * Upload a file attachment to a MasterData document.
135
+ * Uses the VTEX MasterData attachment API with appKey/appToken auth.
136
+ */
137
+ export async function uploadAttachment(opts: UploadAttachmentOpts): Promise<{ ok: true }> {
138
+ const { entity, documentId, field, fileName, fileBase64, contentType } = opts;
139
+ const config = getVtexConfig();
140
+ const url = `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}/api/dataentities/${entity}/documents/${documentId}/${field}/attachments`;
141
+
142
+ const binary = atob(fileBase64);
143
+ const bytes = new Uint8Array(binary.length);
144
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
145
+
146
+ const blob = new Blob([bytes], { type: contentType });
147
+ const formData = new FormData();
148
+ formData.append("file", blob, fileName);
149
+
150
+ const headers: Record<string, string> = {};
151
+ if (config.appKey && config.appToken) {
152
+ headers["X-VTEX-API-AppKey"] = config.appKey;
153
+ headers["X-VTEX-API-AppToken"] = config.appToken;
154
+ }
155
+
156
+ const response = await getVtexFetch()(url, {
157
+ method: "POST",
158
+ headers,
159
+ body: formData,
160
+ operation: "masterdata.attachment.upload",
161
+ });
162
+
163
+ if (!response.ok) {
164
+ throw new Error(`VTEX attachment upload failed: ${response.status} ${response.statusText}`);
165
+ }
166
+
167
+ return { ok: true };
168
+ }