medusa-storefront-data 1.0.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 (105) hide show
  1. package/dist/config.d.ts +3 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +31 -0
  4. package/dist/cookies.d.ts +23 -0
  5. package/dist/cookies.d.ts.map +1 -0
  6. package/dist/cookies.js +140 -0
  7. package/dist/server/cart.d.ts +92 -0
  8. package/dist/server/cart.d.ts.map +1 -0
  9. package/dist/server/cart.js +827 -0
  10. package/dist/server/categories.d.ts +3 -0
  11. package/dist/server/categories.d.ts.map +1 -0
  12. package/dist/server/categories.js +71 -0
  13. package/dist/server/collections.d.ts +8 -0
  14. package/dist/server/collections.d.ts.map +1 -0
  15. package/dist/server/collections.js +84 -0
  16. package/dist/server/customer-registration.d.ts +142 -0
  17. package/dist/server/customer-registration.d.ts.map +1 -0
  18. package/dist/server/customer-registration.js +295 -0
  19. package/dist/server/customer.d.ts +48 -0
  20. package/dist/server/customer.d.ts.map +1 -0
  21. package/dist/server/customer.js +462 -0
  22. package/dist/server/dynamic-config.d.ts +125 -0
  23. package/dist/server/dynamic-config.d.ts.map +1 -0
  24. package/dist/server/dynamic-config.js +263 -0
  25. package/dist/server/fulfillment.d.ts +4 -0
  26. package/dist/server/fulfillment.d.ts.map +1 -0
  27. package/dist/server/fulfillment.js +72 -0
  28. package/dist/server/guest.d.ts +109 -0
  29. package/dist/server/guest.d.ts.map +1 -0
  30. package/dist/server/guest.js +304 -0
  31. package/dist/server/index.d.ts +21 -0
  32. package/dist/server/index.d.ts.map +1 -0
  33. package/dist/server/index.js +20 -0
  34. package/dist/server/locale-actions.d.ts +14 -0
  35. package/dist/server/locale-actions.d.ts.map +1 -0
  36. package/dist/server/locale-actions.js +63 -0
  37. package/dist/server/locales.d.ts +10 -0
  38. package/dist/server/locales.d.ts.map +1 -0
  39. package/dist/server/locales.js +20 -0
  40. package/dist/server/notifications.d.ts +2 -0
  41. package/dist/server/notifications.d.ts.map +1 -0
  42. package/dist/server/notifications.js +20 -0
  43. package/dist/server/onboarding.d.ts +2 -0
  44. package/dist/server/onboarding.d.ts.map +1 -0
  45. package/dist/server/onboarding.js +8 -0
  46. package/dist/server/orders.d.ts +69 -0
  47. package/dist/server/orders.d.ts.map +1 -0
  48. package/dist/server/orders.js +371 -0
  49. package/dist/server/payment-details.d.ts +5 -0
  50. package/dist/server/payment-details.d.ts.map +1 -0
  51. package/dist/server/payment-details.js +53 -0
  52. package/dist/server/payment.d.ts +2 -0
  53. package/dist/server/payment.d.ts.map +1 -0
  54. package/dist/server/payment.js +25 -0
  55. package/dist/server/products.d.ts +58 -0
  56. package/dist/server/products.d.ts.map +1 -0
  57. package/dist/server/products.js +285 -0
  58. package/dist/server/regions.d.ts +5 -0
  59. package/dist/server/regions.d.ts.map +1 -0
  60. package/dist/server/regions.js +54 -0
  61. package/dist/server/returns.d.ts +29 -0
  62. package/dist/server/returns.d.ts.map +1 -0
  63. package/dist/server/returns.js +236 -0
  64. package/dist/server/swaps.d.ts +14 -0
  65. package/dist/server/swaps.d.ts.map +1 -0
  66. package/dist/server/swaps.js +123 -0
  67. package/dist/server/variants.d.ts +3 -0
  68. package/dist/server/variants.d.ts.map +1 -0
  69. package/dist/server/variants.js +26 -0
  70. package/dist/util/get-locale-header.d.ts +4 -0
  71. package/dist/util/get-locale-header.d.ts.map +1 -0
  72. package/dist/util/get-locale-header.js +7 -0
  73. package/dist/util/medusa-error.d.ts +2 -0
  74. package/dist/util/medusa-error.d.ts.map +1 -0
  75. package/dist/util/medusa-error.js +18 -0
  76. package/package.json +152 -0
  77. package/src/config.ts +39 -0
  78. package/src/cookies.ts +171 -0
  79. package/src/middleware.ts +2 -0
  80. package/src/server/cart.ts +1054 -0
  81. package/src/server/categories.ts +94 -0
  82. package/src/server/collections.ts +113 -0
  83. package/src/server/customer-registration.ts +349 -0
  84. package/src/server/customer.ts +581 -0
  85. package/src/server/dynamic-config.ts +403 -0
  86. package/src/server/fulfillment.ts +97 -0
  87. package/src/server/guest.ts +333 -0
  88. package/src/server/index.ts +21 -0
  89. package/src/server/locale-actions.ts +74 -0
  90. package/src/server/locales.ts +28 -0
  91. package/src/server/notifications.ts +22 -0
  92. package/src/server/onboarding.ts +9 -0
  93. package/src/server/orders.ts +467 -0
  94. package/src/server/payment-details.ts +69 -0
  95. package/src/server/payment.ts +35 -0
  96. package/src/server/products.ts +378 -0
  97. package/src/server/regions.ts +66 -0
  98. package/src/server/returns.ts +294 -0
  99. package/src/server/swaps.ts +150 -0
  100. package/src/server/variants.ts +38 -0
  101. package/src/server/wishlist.ts +64 -0
  102. package/src/services/middleware.ts +54 -0
  103. package/src/util/get-locale-header.ts +8 -0
  104. package/src/util/medusa-error.ts +19 -0
  105. package/src/util/sort-products.ts +47 -0
@@ -0,0 +1,827 @@
1
+ "use server";
2
+ import { sdk } from "../config";
3
+ import medusaError from "../util/medusa-error";
4
+ import { revalidateTag, revalidatePath } from "next/cache";
5
+ import { redirect } from "next/navigation";
6
+ import { getAuthHeaders, getCacheOptions, getCacheTag, getCartId, removeCartId, setCartId, getHoldCartId, setBuyNowCartId, removeBuyNowCartId, } from "../cookies";
7
+ import { getRegion } from "./regions";
8
+ import { getLocale } from "./locale-actions";
9
+ import { retrieveCustomer } from "./customer";
10
+ import { cache } from "react";
11
+ /**
12
+ * Retrieves a cart by its ID. If no ID is provided, it will use the cart ID from the cookies.
13
+ * @param cartId - optional - The ID of the cart to retrieve.
14
+ * @returns The cart object if found, or null if not found.
15
+ */
16
+ export const retrieveCart = cache(async (cartId, fields) => {
17
+ let id = cartId || (await getCartId());
18
+ const heldId = await getHoldCartId();
19
+ // AUTO-RESTORE LOGIC:
20
+ // If we're not explicitly asking for a specific cartId and we have an active cart
21
+ if (!cartId && id) {
22
+ try {
23
+ const headerList = await headers();
24
+ const pathname = headerList.get("x-pathname") || headerList.get("referer") || "";
25
+ // Skip metadata check if we are clearly in checkout to avoid extra API call
26
+ if (!pathname.includes("/checkout")) {
27
+ const currentCart = await sdk.client
28
+ .fetch(`/store/carts/${id}`, {
29
+ method: "GET",
30
+ query: { fields: "metadata" },
31
+ headers: await getAuthHeaders(),
32
+ cache: "no-store",
33
+ })
34
+ .then(({ cart }) => cart)
35
+ .catch(() => null);
36
+ if (currentCart?.metadata?.is_buy_now || currentCart?.metadata?.is_reorder) {
37
+ await removeBuyNowCartId();
38
+ id = await getCartId(); // Re-fetch ID which will now fall back to the real main cart
39
+ revalidateTag("carts");
40
+ }
41
+ }
42
+ }
43
+ catch (e) {
44
+ // Silent fail
45
+ }
46
+ }
47
+ fields ??=
48
+ "*items, *region, *items.product, *items.product.thumbnail, *items.product.images, *items.product.options, *items.product.variants, +items.product.variants.inventory_quantity, +items.product.variants.manage_inventory, +items.product.variants.allow_backorder, *items.product.variants.options, *items.variant, +items.variant.inventory_quantity, +items.variant.manage_inventory, +items.variant.allow_backorder, *items.variant.images, *items.variant.product, *items.variant.product.thumbnail, *items.variant.product.images, *items.variant.options, *items.thumbnail, *items.metadata, +items.total, +items.adjustments, *promotions, +shipping_methods.name, +shipping_methods.adjustments";
49
+ if (!id) {
50
+ return null;
51
+ }
52
+ const headers = {
53
+ ...(await getAuthHeaders()),
54
+ };
55
+ const next = {
56
+ ...(await getCacheOptions("carts")),
57
+ };
58
+ return await sdk.client
59
+ .fetch(`/store/carts/${id}`, {
60
+ method: "GET",
61
+ query: {
62
+ fields,
63
+ },
64
+ headers,
65
+ next,
66
+ cache: "no-store",
67
+ })
68
+ .then(({ cart }) => {
69
+ return cart;
70
+ })
71
+ .catch(() => null);
72
+ });
73
+ export async function getOrSetCart(countryCode) {
74
+ const region = await getRegion(countryCode);
75
+ if (!region) {
76
+ throw new Error(`Region not found for country code: ${countryCode}`);
77
+ }
78
+ let cart = await retrieveCart(undefined, "id,region_id");
79
+ const headers = {
80
+ ...(await getAuthHeaders()),
81
+ };
82
+ if (!cart) {
83
+ const locale = await getLocale();
84
+ const cartResp = await sdk.store.cart.create({ region_id: region.id, locale: locale || undefined }, {}, headers);
85
+ cart = cartResp.cart;
86
+ await setCartId(cart.id);
87
+ const cartCacheTag = await getCacheTag("carts");
88
+ revalidateTag(cartCacheTag);
89
+ }
90
+ if (cart && cart?.region_id !== region.id) {
91
+ await sdk.store.cart.update(cart.id, { region_id: region.id }, {}, headers);
92
+ const cartCacheTag = await getCacheTag("carts");
93
+ revalidateTag(cartCacheTag);
94
+ }
95
+ return cart;
96
+ }
97
+ export async function updateCart(data) {
98
+ const cartId = await getCartId();
99
+ if (!cartId) {
100
+ throw new Error("No existing cart found, please create one before updating");
101
+ }
102
+ const headers = {
103
+ ...(await getAuthHeaders()),
104
+ };
105
+ return sdk.store.cart
106
+ .update(cartId, data, {}, headers)
107
+ .then(async ({ cart }) => {
108
+ const cartCacheTag = await getCacheTag("carts");
109
+ revalidateTag(cartCacheTag);
110
+ const fulfillmentCacheTag = await getCacheTag("fulfillment");
111
+ revalidateTag(fulfillmentCacheTag);
112
+ return cart;
113
+ })
114
+ .catch(medusaError);
115
+ }
116
+ export async function addToCart({ variantId, quantity, countryCode, }) {
117
+ if (!variantId) {
118
+ throw new Error("Missing variant ID when adding to cart");
119
+ }
120
+ const cart = await getOrSetCart(countryCode);
121
+ if (!cart) {
122
+ throw new Error("Error retrieving or creating cart");
123
+ }
124
+ const headers = {
125
+ ...(await getAuthHeaders()),
126
+ };
127
+ await sdk.store.cart
128
+ .createLineItem(cart.id, {
129
+ variant_id: variantId,
130
+ quantity,
131
+ }, {}, headers)
132
+ .then(async () => {
133
+ const cartCacheTag = await getCacheTag("carts");
134
+ revalidateTag(cartCacheTag);
135
+ const fulfillmentCacheTag = await getCacheTag("fulfillment");
136
+ revalidateTag(fulfillmentCacheTag);
137
+ })
138
+ .catch(medusaError);
139
+ }
140
+ export async function buyNow({ variantId, quantity, countryCode, }) {
141
+ if (!variantId) {
142
+ throw new Error("Missing variant ID when adding to cart");
143
+ }
144
+ const region = await getRegion(countryCode);
145
+ if (!region) {
146
+ throw new Error(`Region not found for country code: ${countryCode}`);
147
+ }
148
+ const headers = {
149
+ ...(await getAuthHeaders()),
150
+ };
151
+ const locale = await getLocale();
152
+ // 1. Create a NEW cart regardless of existing one
153
+ const cartResp = await sdk.store.cart.create({
154
+ region_id: region.id,
155
+ locale: locale || undefined,
156
+ metadata: { is_buy_now: true }
157
+ }, {}, headers);
158
+ const cart = cartResp.cart;
159
+ // Save the new cart id in "buy_now_cart_id" cookie
160
+ await removeBuyNowCartId();
161
+ await setBuyNowCartId(cart.id);
162
+ // Ensure this new cart is not merged with existing ones
163
+ try {
164
+ await sdk.client.fetch(`/store/carts/merge/skip`, {
165
+ method: "POST",
166
+ body: { cart_id: cart.id },
167
+ headers,
168
+ });
169
+ }
170
+ catch (e) {
171
+ console.error("Failed to skip cart merge for buy now cart:", e);
172
+ }
173
+ // 2. Add the item to this new cart
174
+ await sdk.store.cart.createLineItem(cart.id, {
175
+ variant_id: variantId,
176
+ quantity,
177
+ }, {}, headers);
178
+ // 3. (REMOVED: No longer overwriting the main cart ID, because getCartId handles buy_now_cart_id priority)
179
+ // 4. OPTIMIZATION: If user is logged in and has addresses, auto-fill and skip to payment
180
+ const customer = await retrieveCustomer().catch(() => null);
181
+ const defaultAddress = customer?.addresses?.find(a => a.is_default_shipping || a.metadata?.is_default === "true" || a.metadata?.is_default === true) || customer?.addresses?.[0];
182
+ let skipToPayment = false;
183
+ if (defaultAddress) {
184
+ try {
185
+ // Set addresses
186
+ await sdk.store.cart.update(cart.id, {
187
+ shipping_address: {
188
+ first_name: defaultAddress.first_name,
189
+ last_name: defaultAddress.last_name,
190
+ address_1: defaultAddress.address_1,
191
+ address_2: defaultAddress.address_2,
192
+ city: defaultAddress.city,
193
+ country_code: defaultAddress.country_code,
194
+ postal_code: defaultAddress.postal_code,
195
+ province: defaultAddress.province,
196
+ phone: defaultAddress.phone,
197
+ company: defaultAddress.company,
198
+ },
199
+ billing_address: {
200
+ first_name: defaultAddress.first_name,
201
+ last_name: defaultAddress.last_name,
202
+ address_1: defaultAddress.address_1,
203
+ address_2: defaultAddress.address_2,
204
+ city: defaultAddress.city,
205
+ country_code: defaultAddress.country_code,
206
+ postal_code: defaultAddress.postal_code,
207
+ province: defaultAddress.province,
208
+ phone: defaultAddress.phone,
209
+ company: defaultAddress.company,
210
+ },
211
+ email: customer.email
212
+ }, {}, headers);
213
+ // Fetch and set first shipping method
214
+ const { shipping_options } = await sdk.client.fetch("/store/shipping-options", {
215
+ query: { cart_id: cart.id },
216
+ headers,
217
+ cache: "no-store",
218
+ });
219
+ if (shipping_options?.length > 0) {
220
+ await sdk.store.cart.addShippingMethod(cart.id, { option_id: shipping_options[0].id }, {}, headers);
221
+ skipToPayment = true;
222
+ }
223
+ }
224
+ catch (e) {
225
+ // Fallback to normal checkout if anything fails
226
+ console.error("Failed to auto-fill address and shipping:", e);
227
+ }
228
+ }
229
+ const cartCacheTag = await getCacheTag("carts");
230
+ revalidateTag(cartCacheTag);
231
+ revalidatePath(`/${countryCode}/checkout`, "page");
232
+ // 5. Redirect to checkout (default or skipped to payment)
233
+ if (skipToPayment) {
234
+ redirect(`/${countryCode}/checkout?step=payment&cart_id=${cart.id}`);
235
+ }
236
+ else {
237
+ redirect(`/${countryCode}/checkout?cart_id=${cart.id}`);
238
+ }
239
+ }
240
+ export async function updateLineItem({ lineId, quantity, }) {
241
+ if (!lineId) {
242
+ throw new Error("Missing lineItem ID when updating line item");
243
+ }
244
+ const cartId = await getCartId();
245
+ if (!cartId) {
246
+ throw new Error("Missing cart ID when updating line item");
247
+ }
248
+ const headers = {
249
+ ...(await getAuthHeaders()),
250
+ };
251
+ await sdk.store.cart
252
+ .updateLineItem(cartId, lineId, { quantity }, {}, headers)
253
+ .then(async () => {
254
+ const cartCacheTag = await getCacheTag("carts");
255
+ revalidateTag(cartCacheTag);
256
+ const fulfillmentCacheTag = await getCacheTag("fulfillment");
257
+ revalidateTag(fulfillmentCacheTag);
258
+ })
259
+ .catch(medusaError);
260
+ }
261
+ export async function deleteLineItem(lineId) {
262
+ if (!lineId) {
263
+ throw new Error("Missing lineItem ID when deleting line item");
264
+ }
265
+ const cartId = await getCartId();
266
+ if (!cartId) {
267
+ throw new Error("Missing cart ID when deleting line item");
268
+ }
269
+ const headers = {
270
+ ...(await getAuthHeaders()),
271
+ };
272
+ await sdk.store.cart
273
+ .deleteLineItem(cartId, lineId, {}, headers)
274
+ .then(async () => {
275
+ const cartCacheTag = await getCacheTag("carts");
276
+ revalidateTag(cartCacheTag);
277
+ const fulfillmentCacheTag = await getCacheTag("fulfillment");
278
+ revalidateTag(fulfillmentCacheTag);
279
+ })
280
+ .catch(medusaError);
281
+ }
282
+ export async function updateLineItemVariant({ lineId, variantId, quantity, countryCode, }) {
283
+ if (!lineId) {
284
+ throw new Error("Missing lineItem ID when updating variant");
285
+ }
286
+ if (!variantId) {
287
+ throw new Error("Missing variant ID when updating variant");
288
+ }
289
+ // Delete the old line item
290
+ await deleteLineItem(lineId);
291
+ // Add new line item with the new variant
292
+ await addToCart({
293
+ variantId,
294
+ quantity,
295
+ countryCode,
296
+ });
297
+ }
298
+ export async function setShippingMethod({ cartId, shippingMethodId, }) {
299
+ const headers = {
300
+ ...(await getAuthHeaders()),
301
+ };
302
+ return sdk.store.cart
303
+ .addShippingMethod(cartId, { option_id: shippingMethodId }, {}, headers)
304
+ .then(async () => {
305
+ const cartCacheTag = await getCacheTag("carts");
306
+ revalidateTag(cartCacheTag);
307
+ })
308
+ .catch(medusaError);
309
+ }
310
+ /**
311
+ * Sets a shipping method silently without triggering a full page revalidation.
312
+ */
313
+ export async function setShippingMethodSilently({ cartId, shippingMethodId, }) {
314
+ const headers = {
315
+ ...(await getAuthHeaders()),
316
+ };
317
+ try {
318
+ await sdk.store.cart.addShippingMethod(cartId, { option_id: shippingMethodId }, {}, headers);
319
+ return { success: true };
320
+ }
321
+ catch (e) {
322
+ return { success: false };
323
+ }
324
+ }
325
+ export async function initiatePaymentSession(cart, data) {
326
+ const headers = {
327
+ ...(await getAuthHeaders()),
328
+ };
329
+ // 1. RE-FETCH cart with NO CACHE to ensure we have the latest data from the DB
330
+ const latestCart = await retrieveCart(cart.id);
331
+ if (!latestCart)
332
+ throw new Error("Cart not found");
333
+ // Helper to normalize phone numbers for Razorpay (requires +91 for India if 10 digits)
334
+ const normalize = (p) => {
335
+ if (!p)
336
+ return undefined;
337
+ const cleaned = String(p).replace(/\D/g, "");
338
+ // India specific optimization (+91)
339
+ if (cleaned.length === 10)
340
+ return `+91${cleaned}`;
341
+ if (cleaned.length > 10 && !String(p).startsWith("+"))
342
+ return `+${cleaned}`;
343
+ return p;
344
+ };
345
+ const rawPhone = latestCart.shipping_address?.phone || latestCart.billing_address?.phone;
346
+ const phone = normalize(rawPhone);
347
+ // 2. Critical Fix for Razorpay: Ensure normalized phone is in BOTH shipping and billing addresses
348
+ if (phone) {
349
+ const addressUpdates = {};
350
+ // Clean address helper to ensure we pass a clean object to the update API
351
+ const cleanAddress = (addr) => {
352
+ if (!addr)
353
+ return null;
354
+ return {
355
+ first_name: addr.first_name,
356
+ last_name: addr.last_name,
357
+ address_1: addr.address_1,
358
+ address_2: addr.address_2 || "",
359
+ city: addr.city,
360
+ country_code: addr.country_code,
361
+ postal_code: addr.postal_code,
362
+ province: addr.province,
363
+ company: addr.company,
364
+ phone: phone
365
+ };
366
+ };
367
+ // Update if phone is missing in DB
368
+ if (latestCart.shipping_address && !latestCart.shipping_address.phone) {
369
+ addressUpdates.shipping_address = cleanAddress(latestCart.shipping_address);
370
+ }
371
+ if (latestCart.billing_address && !latestCart.billing_address.phone) {
372
+ addressUpdates.billing_address = cleanAddress(latestCart.billing_address) || cleanAddress(latestCart.shipping_address);
373
+ }
374
+ if (Object.keys(addressUpdates).length > 0) {
375
+ await sdk.store.cart.update(latestCart.id, addressUpdates, {}, headers);
376
+ }
377
+ }
378
+ // 3. Final re-fetch (direct SDK call to bypass any lib-level cache)
379
+ const cartResponse = await sdk.store.cart.retrieve(cart.id, { fields: "*shipping_address,*billing_address" }, headers);
380
+ const finalCart = cartResponse.cart;
381
+ // 4. PRE-FLIGHT VALIDATION: Only strictly block for Razorpay, be more lenient for COD
382
+ const isRazorpayProvider = data.provider_id.includes("razorpay");
383
+ const displayPhone = finalCart.shipping_address?.phone || finalCart.billing_address?.phone || phone;
384
+ if (isRazorpayProvider && !displayPhone) {
385
+ throw new Error(`CRITICAL Error: Phone number is missing from your address. Razorpay requires a contact number for verification. Please enter your phone number.`);
386
+ }
387
+ if (isRazorpayProvider && !finalCart.email) {
388
+ throw new Error(`CRITICAL Error: Email is missing from your cart. Razorpay requires an email address. Please enter your email.`);
389
+ }
390
+ // 5. ENHANCED DATA FOR RAZORPAY:
391
+ const enhancedData = {
392
+ ...data,
393
+ data: {
394
+ ...(data.data || {}),
395
+ billing_address: finalCart.billing_address || finalCart.shipping_address,
396
+ customer: {
397
+ email: finalCart.email,
398
+ phone: displayPhone,
399
+ first_name: finalCart.billing_address?.first_name || finalCart.shipping_address?.first_name,
400
+ last_name: finalCart.billing_address?.last_name || finalCart.shipping_address?.last_name,
401
+ }
402
+ }
403
+ };
404
+ return sdk.store.payment
405
+ .initiatePaymentSession(finalCart, enhancedData, {}, headers)
406
+ .then(async (resp) => {
407
+ const cartCacheTag = await getCacheTag("carts");
408
+ revalidateTag(cartCacheTag);
409
+ return resp;
410
+ })
411
+ .catch((e) => {
412
+ const errorMsg = e.message || e.response?.data?.message || "Unknown error";
413
+ throw new Error(`Razorpay Initialization Failed: ${errorMsg}`);
414
+ });
415
+ }
416
+ export async function applyPromotions(codes) {
417
+ const cartId = await getCartId();
418
+ if (!cartId) {
419
+ throw new Error("No existing cart found");
420
+ }
421
+ const headers = {
422
+ ...(await getAuthHeaders()),
423
+ };
424
+ return sdk.store.cart
425
+ .update(cartId, { promo_codes: codes }, {}, headers)
426
+ .then(async ({ cart }) => {
427
+ const cartCacheTag = await getCacheTag("carts");
428
+ revalidateTag(cartCacheTag);
429
+ const fulfillmentCacheTag = await getCacheTag("fulfillment");
430
+ revalidateTag(fulfillmentCacheTag);
431
+ return cart;
432
+ })
433
+ .catch(medusaError);
434
+ }
435
+ export async function applyGiftCard(code) {
436
+ // const cartId = getCartId()
437
+ // if (!cartId) return "No cartId cookie found"
438
+ // try {
439
+ // await updateCart(cartId, { gift_cards: [{ code }] }).then(() => {
440
+ // revalidateTag("cart")
441
+ // })
442
+ // } catch (error: any) {
443
+ // throw error
444
+ // }
445
+ }
446
+ export async function removeDiscount(code) {
447
+ // const cartId = getCartId()
448
+ // if (!cartId) return "No cartId cookie found"
449
+ // try {
450
+ // await deleteDiscount(cartId, code)
451
+ // revalidateTag("cart")
452
+ // } catch (error: any) {
453
+ // throw error
454
+ // }
455
+ }
456
+ export async function removeGiftCard(codeToRemove, giftCards
457
+ // giftCards: GiftCard[]
458
+ ) {
459
+ // const cartId = getCartId()
460
+ // if (!cartId) return "No cartId cookie found"
461
+ // try {
462
+ // await updateCart(cartId, {
463
+ // gift_cards: [...giftCards]
464
+ // .filter((gc) => gc.code !== codeToRemove)
465
+ // .map((gc) => ({ code: gc.code })),
466
+ // }).then(() => {
467
+ // revalidateTag("cart")
468
+ // })
469
+ // } catch (error: any) {
470
+ // throw error
471
+ // }
472
+ }
473
+ export async function submitPromotionForm(currentState, formData) {
474
+ const code = formData.get("code");
475
+ try {
476
+ await applyPromotions([code]);
477
+ }
478
+ catch (e) {
479
+ return e.message;
480
+ }
481
+ }
482
+ // TODO: Pass a POJO instead of a form entity here
483
+ export async function setAddresses(currentState, formData) {
484
+ try {
485
+ if (!formData) {
486
+ throw new Error("No form data found when setting addresses");
487
+ }
488
+ const cartId = await getCartId();
489
+ if (!cartId) {
490
+ throw new Error("No existing cart found when setting addresses");
491
+ }
492
+ const shippingCountryCode = formData.get("shipping_address.country_code")?.toLowerCase() || "in";
493
+ const normalize = (p) => {
494
+ if (!p)
495
+ return undefined;
496
+ const cleaned = String(p).replace(/\D/g, "");
497
+ if (cleaned.length === 10)
498
+ return `+91${cleaned}`;
499
+ if (cleaned.length > 10 && !String(p).startsWith("+"))
500
+ return `+${cleaned}`;
501
+ return String(p);
502
+ };
503
+ const rawPhone = formData.get("shipping_address.phone");
504
+ const phone = normalize(rawPhone);
505
+ const shippingAddress = {
506
+ first_name: formData.get("shipping_address.first_name"),
507
+ last_name: formData.get("shipping_address.last_name"),
508
+ address_1: formData.get("shipping_address.address_1"),
509
+ address_2: formData.get("shipping_address.address_2") || "",
510
+ company: formData.get("shipping_address.company"),
511
+ postal_code: formData.get("shipping_address.postal_code"),
512
+ city: formData.get("shipping_address.city"),
513
+ country_code: shippingCountryCode,
514
+ province: formData.get("shipping_address.province"),
515
+ phone: phone,
516
+ };
517
+ const data = {
518
+ shipping_address: shippingAddress,
519
+ email: formData.get("email"),
520
+ };
521
+ // Save address to customer profile if logged in
522
+ const authHeaders = await getAuthHeaders();
523
+ if (authHeaders && "authorization" in authHeaders) {
524
+ try {
525
+ // Fetch current customer to check if address already exists
526
+ const { customer } = await sdk.client.fetch(`/store/customers/me`, {
527
+ method: "GET",
528
+ query: { fields: "*addresses" },
529
+ headers: authHeaders,
530
+ cache: "no-store",
531
+ });
532
+ const addressExists = customer?.addresses?.some((a) => a.address_1 === shippingAddress.address_1 &&
533
+ a.postal_code === shippingAddress.postal_code &&
534
+ a.city === shippingAddress.city &&
535
+ a.first_name === shippingAddress.first_name &&
536
+ a.last_name === shippingAddress.last_name);
537
+ if (!addressExists) {
538
+ await sdk.store.customer.createAddress(shippingAddress, {}, authHeaders);
539
+ const customerCacheTag = await getCacheTag("customers");
540
+ revalidateTag(customerCacheTag);
541
+ }
542
+ }
543
+ catch (e) {
544
+ // Silently fail when saving address to customer profile
545
+ }
546
+ }
547
+ const sameAsBilling = formData.get("same_as_billing");
548
+ if (sameAsBilling === "on") {
549
+ data.billing_address = { ...data.shipping_address };
550
+ }
551
+ if (sameAsBilling !== "on") {
552
+ const bPhone = normalize(formData.get("billing_address.phone")) || phone;
553
+ data.billing_address = {
554
+ first_name: formData.get("billing_address.first_name"),
555
+ last_name: formData.get("billing_address.last_name"),
556
+ address_1: formData.get("billing_address.address_1"),
557
+ address_2: formData.get("billing_address.address_2") || "",
558
+ company: formData.get("billing_address.company"),
559
+ postal_code: formData.get("billing_address.postal_code"),
560
+ city: formData.get("billing_address.city"),
561
+ country_code: shippingCountryCode,
562
+ province: formData.get("billing_address.province"),
563
+ phone: bPhone,
564
+ };
565
+ }
566
+ await updateCart(data);
567
+ }
568
+ catch (e) {
569
+ return e.message;
570
+ }
571
+ revalidateTag("carts");
572
+ }
573
+ /**
574
+ * Places an order for a cart. If no cart ID is provided, it will use the cart ID from the cookies.
575
+ * @param cartId - optional - The ID of the cart to place an order for.
576
+ * @returns The cart object if the order was successful, or null if not.
577
+ */
578
+ export async function placeOrder(cartId) {
579
+ const id = cartId || (await getCartId());
580
+ if (!id) {
581
+ throw new Error("No existing cart found when placing an order");
582
+ }
583
+ const headers = {
584
+ ...(await getAuthHeaders()),
585
+ };
586
+ const cartRes = await sdk.store.cart
587
+ .complete(id, {}, headers)
588
+ .then(async (cartRes) => {
589
+ const cartCacheTag = await getCacheTag("carts");
590
+ revalidateTag(cartCacheTag);
591
+ return cartRes;
592
+ })
593
+ .catch(medusaError);
594
+ if (cartRes?.type === "order") {
595
+ const countryCode = cartRes.order.shipping_address?.country_code?.toLowerCase();
596
+ const orderCacheTag = await getCacheTag("orders");
597
+ revalidateTag(orderCacheTag);
598
+ // NEW LOGIC: Check if we just completed a Buy Now cart
599
+ const { cookies: nextCookies } = await import("next/headers");
600
+ const cookiesStore = await nextCookies();
601
+ const buyNowId = cookiesStore.get("buy_now_cart_id")?.value;
602
+ if (buyNowId) {
603
+ // If a Buy Now cart was completed, remove its cookie ONLY.
604
+ // Do NOT touch _medusa_cart_id, so the main cart is preserved!
605
+ await removeBuyNowCartId();
606
+ }
607
+ else {
608
+ // Regular checkout: clear the main cart
609
+ await removeCartId();
610
+ }
611
+ redirect(`/${countryCode}/order/${cartRes?.order.id}/confirmed`);
612
+ }
613
+ return cartRes.cart;
614
+ }
615
+ /**
616
+ * Updates the countrycode param and revalidates the regions cache
617
+ * @param regionId
618
+ * @param countryCode
619
+ */
620
+ export async function updateRegion(countryCode, currentPath) {
621
+ const cartId = await getCartId();
622
+ const region = await getRegion(countryCode);
623
+ if (!region) {
624
+ throw new Error(`Region not found for country code: ${countryCode}`);
625
+ }
626
+ if (cartId) {
627
+ await updateCart({ region_id: region.id });
628
+ const cartCacheTag = await getCacheTag("carts");
629
+ revalidateTag(cartCacheTag);
630
+ }
631
+ const regionCacheTag = await getCacheTag("regions");
632
+ revalidateTag(regionCacheTag);
633
+ const productsCacheTag = await getCacheTag("products");
634
+ revalidateTag(productsCacheTag);
635
+ redirect(`/${countryCode}${currentPath}`);
636
+ }
637
+ export async function listCartOptions() {
638
+ const cartId = await getCartId();
639
+ const headers = {
640
+ ...(await getAuthHeaders()),
641
+ };
642
+ const next = {
643
+ ...(await getCacheOptions("shippingOptions")),
644
+ };
645
+ return await sdk.client.fetch("/store/shipping-options", {
646
+ query: { cart_id: cartId },
647
+ next,
648
+ headers,
649
+ cache: "force-cache",
650
+ });
651
+ }
652
+ export async function addCustomerAddressToCart(currentState, formData) {
653
+ try {
654
+ const headers = {
655
+ ...(await getAuthHeaders()),
656
+ };
657
+ const shippingCountryCode = formData.get("shipping_address.country_code")?.toLowerCase() || "in";
658
+ // Parse address data from formData (which uses shipping_address. prefix)
659
+ const addressData = {
660
+ first_name: formData.get("shipping_address.first_name"),
661
+ last_name: formData.get("shipping_address.last_name"),
662
+ company: formData.get("shipping_address.company"),
663
+ address_1: formData.get("shipping_address.address_1"),
664
+ address_2: formData.get("shipping_address.address_2"),
665
+ city: formData.get("shipping_address.city"),
666
+ postal_code: formData.get("shipping_address.postal_code"),
667
+ province: formData.get("shipping_address.province"),
668
+ country_code: shippingCountryCode,
669
+ phone: formData.get("shipping_address.phone"),
670
+ metadata: {
671
+ is_default: formData.get("is_default") === "true",
672
+ address_type: formData.get("address_type"),
673
+ },
674
+ };
675
+ const addressId = formData.get("address_id");
676
+ // 1. Add/Update Customer Address Book
677
+ // We try/catch this separately so if it fails (e.g. user not logged in), we still try to proceed with checkout
678
+ try {
679
+ if (addressId) {
680
+ await sdk.store.customer.updateAddress(addressId, addressData, {}, headers);
681
+ }
682
+ else {
683
+ await sdk.store.customer.createAddress(addressData, {}, headers);
684
+ }
685
+ const customerCacheTag = await getCacheTag("customers");
686
+ revalidateTag(customerCacheTag);
687
+ }
688
+ catch (e) {
689
+ // verify if it is an auth error, maybe we shouldn't fail silently?
690
+ // But for checkout flow, the priority is to proceed.
691
+ }
692
+ // 2. Set as Cart Shipping Address
693
+ const cartId = await getCartId();
694
+ if (!cartId) {
695
+ throw new Error("No existing cart found when setting addresses");
696
+ }
697
+ const cartData = {
698
+ shipping_address: {
699
+ first_name: addressData.first_name,
700
+ last_name: addressData.last_name,
701
+ company: addressData.company,
702
+ address_1: addressData.address_1,
703
+ address_2: addressData.address_2,
704
+ city: addressData.city,
705
+ postal_code: addressData.postal_code,
706
+ province: addressData.province,
707
+ country_code: addressData.country_code,
708
+ phone: addressData.phone,
709
+ },
710
+ email: formData.get("email"),
711
+ };
712
+ const sameAsBilling = formData.get("same_as_billing");
713
+ if (sameAsBilling === "on") {
714
+ cartData.billing_address = cartData.shipping_address;
715
+ }
716
+ else {
717
+ // If not same as billing, we should ideally handle billing address too,
718
+ // but AddAddressModal usually forces same_as_billing="on" (Line 52 in AddAddressModal).
719
+ // If we ever change that, we'll need logic here. For now, AddAddressModal is strictly for "Add New Shippping Address".
720
+ }
721
+ await updateCart(cartData);
722
+ }
723
+ catch (e) {
724
+ return e.message;
725
+ }
726
+ revalidateTag("carts");
727
+ // No redirect if we are already in checkout to prevent disrupting the user
728
+ }
729
+ /**
730
+ * Updates address in the background without redirecting.
731
+ * Used for real-time shipping calculation when pincode is entered.
732
+ */
733
+ export async function updateAddressSilently(data) {
734
+ const cartId = await getCartId();
735
+ if (!cartId)
736
+ return;
737
+ try {
738
+ const headers = {
739
+ ...(await getAuthHeaders()),
740
+ };
741
+ await sdk.store.cart.update(cartId, data, {}, headers);
742
+ // Revalidate tags to ensure CartTotals gets new shipping info
743
+ const cartCacheTag = await getCacheTag("carts");
744
+ revalidateTag(cartCacheTag);
745
+ const fulfillmentCacheTag = await getCacheTag("fulfillment");
746
+ revalidateTag(fulfillmentCacheTag);
747
+ return { success: true };
748
+ }
749
+ catch (e) {
750
+ return { success: false };
751
+ }
752
+ }
753
+ /**
754
+ * Updates cart metadata silently without triggering a full page revalidation.
755
+ */
756
+ export async function updateCartMetadataSilently(metadata) {
757
+ const cartId = await getCartId();
758
+ if (!cartId)
759
+ return;
760
+ try {
761
+ const headers = {
762
+ ...(await getAuthHeaders()),
763
+ };
764
+ await sdk.store.cart.update(cartId, { metadata }, {}, headers);
765
+ // We intentionally DO NOT call revalidateTag here to prevent page refresh loops
766
+ return { success: true };
767
+ }
768
+ catch (e) {
769
+ return { success: false };
770
+ }
771
+ }
772
+ export async function getAbandonedCarts() {
773
+ const headers = {
774
+ ...(await getAuthHeaders()),
775
+ };
776
+ // If no authorization, return empty arrays
777
+ if (!headers["authorization"]) {
778
+ return { buyNowCarts: [], reorderCarts: [] };
779
+ }
780
+ try {
781
+ // Just fetch all carts for the customer and filter locally
782
+ const res = await sdk.client.fetch(`/store/carts`, {
783
+ method: "GET",
784
+ query: {
785
+ fields: "*items, *items.product, *items.product.thumbnail, *items.variant, *items.metadata, +total",
786
+ },
787
+ headers,
788
+ cache: "no-store",
789
+ }).catch(() => null);
790
+ let buyNowCarts = [];
791
+ let reorderCarts = [];
792
+ if (res && res.carts) {
793
+ // Filter out the main active cart
794
+ const currentCartId = await getCartId();
795
+ const filteredCarts = res.carts.filter(c => c.id !== currentCartId);
796
+ buyNowCarts = filteredCarts.filter(c => c.metadata?.is_buy_now === true);
797
+ reorderCarts = filteredCarts.filter(c => c.metadata?.is_reorder === true);
798
+ }
799
+ return { buyNowCarts, reorderCarts };
800
+ }
801
+ catch (e) {
802
+ console.error("Failed to fetch abandoned carts:", e);
803
+ return { buyNowCarts: [], reorderCarts: [] };
804
+ }
805
+ }
806
+ export async function resumeAbandonedCart(cartId, countryCode) {
807
+ // Set as buy now cart so it temporarily overrides the main cart
808
+ // without deleting the main cart cookie (_medusa_cart_id)
809
+ await setBuyNowCartId(cartId);
810
+ redirect(`/${countryCode}/checkout?cart_id=${cartId}`);
811
+ }
812
+ export async function deleteCart(cartId) {
813
+ const headers = await getAuthHeaders();
814
+ try {
815
+ await sdk.client.fetch(`/store/carts/${cartId}`, {
816
+ method: "DELETE",
817
+ headers,
818
+ cache: "no-store",
819
+ });
820
+ revalidateTag("carts");
821
+ return { success: true };
822
+ }
823
+ catch (e) {
824
+ console.error("Failed to delete cart:", e);
825
+ return { success: false, error: e.message };
826
+ }
827
+ }