@pradip1995/commerce-core 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 (82) hide show
  1. package/README.md +15 -0
  2. package/package.json +70 -0
  3. package/src/analytics/ga4-ecommerce.ts +96 -0
  4. package/src/config.ts +36 -0
  5. package/src/constants.tsx +84 -0
  6. package/src/context/modal-context.tsx +40 -0
  7. package/src/context/wishlist-context.tsx +96 -0
  8. package/src/data/cart/abandoned.ts +111 -0
  9. package/src/data/cart/buyNow.ts +184 -0
  10. package/src/data/cart/checkout.ts +487 -0
  11. package/src/data/cart/index.ts +7 -0
  12. package/src/data/cart/mutations.ts +189 -0
  13. package/src/data/cart/promotions.ts +121 -0
  14. package/src/data/cart/region.ts +66 -0
  15. package/src/data/cart/retrieve.ts +162 -0
  16. package/src/data/categories.ts +90 -0
  17. package/src/data/collections.ts +109 -0
  18. package/src/data/contact.ts +143 -0
  19. package/src/data/cookies.ts +170 -0
  20. package/src/data/customer-registration.ts +365 -0
  21. package/src/data/customer.ts +638 -0
  22. package/src/data/dynamic-config.ts +420 -0
  23. package/src/data/fulfillment.ts +95 -0
  24. package/src/data/guest.ts +357 -0
  25. package/src/data/locale-actions.ts +74 -0
  26. package/src/data/locales.ts +28 -0
  27. package/src/data/newsletter.ts +41 -0
  28. package/src/data/notifications.ts +22 -0
  29. package/src/data/onboarding.ts +9 -0
  30. package/src/data/orders.ts +500 -0
  31. package/src/data/payment-details.ts +68 -0
  32. package/src/data/payment.ts +32 -0
  33. package/src/data/products.ts +424 -0
  34. package/src/data/regions.ts +64 -0
  35. package/src/data/returns.ts +305 -0
  36. package/src/data/reviews.ts +279 -0
  37. package/src/data/swaps.ts +154 -0
  38. package/src/data/variants.ts +38 -0
  39. package/src/data/wishlist.ts +292 -0
  40. package/src/domain/cart/abandoned-carts.ts +49 -0
  41. package/src/domain/cart/buy-now.ts +15 -0
  42. package/src/domain/cart/checkout.ts +25 -0
  43. package/src/domain/cart/index.ts +8 -0
  44. package/src/domain/cart/metadata.ts +21 -0
  45. package/src/domain/cart/payment.ts +21 -0
  46. package/src/domain/cart/phone.ts +17 -0
  47. package/src/domain/cart/reorder.ts +19 -0
  48. package/src/domain/cart/validation.ts +43 -0
  49. package/src/domain/product/pricing.ts +49 -0
  50. package/src/domain/product/variant-selection.ts +193 -0
  51. package/src/firebase.ts +48 -0
  52. package/src/hooks/index.ts +8 -0
  53. package/src/hooks/use-add-to-cart.ts +63 -0
  54. package/src/hooks/use-cart.ts +132 -0
  55. package/src/hooks/use-checkout.ts +62 -0
  56. package/src/hooks/use-in-view.tsx +29 -0
  57. package/src/hooks/use-product-actions.ts +190 -0
  58. package/src/hooks/use-product-reviews.ts +18 -0
  59. package/src/hooks/use-product-variant.ts +142 -0
  60. package/src/hooks/use-server-action.ts +30 -0
  61. package/src/hooks/use-toggle-state.tsx +46 -0
  62. package/src/hooks/use-wishlist.ts +3 -0
  63. package/src/theme/inline-vars.ts +12 -0
  64. package/src/types/account.ts +21 -0
  65. package/src/types/cart.ts +13 -0
  66. package/src/types/home.ts +52 -0
  67. package/src/types/layout.ts +29 -0
  68. package/src/types/product-card.ts +17 -0
  69. package/src/util/compare-addresses.ts +28 -0
  70. package/src/util/env.ts +3 -0
  71. package/src/util/get-locale-header.ts +8 -0
  72. package/src/util/get-percentage-diff.ts +6 -0
  73. package/src/util/get-product-price.ts +78 -0
  74. package/src/util/google-oauth.ts +28 -0
  75. package/src/util/isEmpty.ts +11 -0
  76. package/src/util/medusa-error.ts +18 -0
  77. package/src/util/money.ts +26 -0
  78. package/src/util/order-status.tsx +179 -0
  79. package/src/util/product.ts +431 -0
  80. package/src/util/repeat.ts +5 -0
  81. package/src/util/returns.ts +71 -0
  82. package/src/util/sort-products.ts +48 -0
@@ -0,0 +1,184 @@
1
+ "use server"
2
+
3
+ import { sdk } from "@core/config"
4
+ import medusaError from "@core/util/medusa-error"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import { revalidateTag, revalidatePath } from "next/cache"
7
+ import { redirect } from "next/navigation"
8
+ import { headers as getRequestHeaders } from "next/headers"
9
+ import {
10
+ getAuthHeaders,
11
+ getCacheOptions,
12
+ getCacheTag,
13
+ getCartId,
14
+ removeCartId,
15
+ setCartId,
16
+ getHoldCartId,
17
+ setHoldCartId,
18
+ removeHoldCartId,
19
+ setBuyNowCartId,
20
+ removeBuyNowCartId,
21
+ } from "../cookies"
22
+ import { getRegion } from "../regions"
23
+ import { getLocale } from "@core/data/locale-actions"
24
+ import { retrieveCustomer, transferCart } from "@core/data/customer"
25
+ import { cache } from "react"
26
+ import {
27
+ createBuyNowMetadata,
28
+ filterOutCurrentCart,
29
+ getBuyNowCheckoutUrl,
30
+ getPostOrderCookieAction,
31
+ getResumeAbandonedCheckoutUrl,
32
+ isEphemeralCart,
33
+ normalizePhoneForRazorpay,
34
+ partitionAbandonedCarts,
35
+ shouldCheckEphemeralCartMetadata,
36
+ shouldSkipToPayment,
37
+ validateAddToCartInput,
38
+ validateCartId,
39
+ validateLineItemDeleteInput,
40
+ validateLineItemUpdateInput,
41
+ validateRazorpayPreflight,
42
+ } from "@core/domain/cart"
43
+
44
+ export async function buyNow({
45
+ variantId,
46
+ quantity,
47
+ countryCode,
48
+ }: {
49
+ variantId: string
50
+ quantity: number
51
+ countryCode: string
52
+ }) {
53
+ validateAddToCartInput({ variantId, quantity })
54
+
55
+ const region = await getRegion(countryCode)
56
+
57
+ if (!region) {
58
+ throw new Error(`Region not found for country code: ${countryCode}`)
59
+ }
60
+
61
+ const headers = {
62
+ ...(await getAuthHeaders()),
63
+ }
64
+
65
+ const locale = await getLocale()
66
+
67
+ // 1. Create a NEW cart regardless of existing one
68
+ const cartResp = await sdk.store.cart.create(
69
+ {
70
+ region_id: region.id,
71
+ locale: locale || undefined,
72
+ metadata: createBuyNowMetadata(),
73
+ },
74
+ {},
75
+ headers
76
+ )
77
+ const cart = cartResp.cart
78
+
79
+ // Save the new cart id in "buy_now_cart_id" cookie
80
+ await removeBuyNowCartId()
81
+ await setBuyNowCartId(cart.id)
82
+
83
+ // Ensure this new cart is not merged with existing ones
84
+ try {
85
+ await sdk.client.fetch(`/store/carts/merge/skip`, {
86
+ method: "POST",
87
+ body: { cart_id: cart.id },
88
+ headers,
89
+ })
90
+ } catch (e) {
91
+ console.error("Failed to skip cart merge for buy now cart:", e)
92
+ }
93
+
94
+ // 2. Add the item to this new cart
95
+ await sdk.store.cart.createLineItem(
96
+ cart.id,
97
+ {
98
+ variant_id: variantId,
99
+ quantity,
100
+ },
101
+ {},
102
+ headers
103
+ )
104
+
105
+ // 3. (REMOVED: No longer overwriting the main cart ID, because getCartId handles buy_now_cart_id priority)
106
+
107
+ // 4. OPTIMIZATION: If user is logged in and has addresses, auto-fill and skip to payment
108
+ const customer = await retrieveCustomer().catch(() => null)
109
+ const defaultAddress =
110
+ customer?.addresses?.find(
111
+ (a) =>
112
+ a.is_default_shipping ||
113
+ a.metadata?.is_default === "true" ||
114
+ a.metadata?.is_default === true
115
+ ) || customer?.addresses?.[0]
116
+
117
+ let skipToPayment = false
118
+
119
+ if (defaultAddress) {
120
+ try {
121
+ // Set addresses
122
+ await sdk.store.cart.update(
123
+ cart.id,
124
+ {
125
+ shipping_address: {
126
+ first_name: defaultAddress.first_name,
127
+ last_name: defaultAddress.last_name,
128
+ address_1: defaultAddress.address_1,
129
+ address_2: defaultAddress.address_2,
130
+ city: defaultAddress.city,
131
+ country_code: defaultAddress.country_code,
132
+ postal_code: defaultAddress.postal_code,
133
+ province: defaultAddress.province,
134
+ phone: defaultAddress.phone,
135
+ company: defaultAddress.company,
136
+ },
137
+ billing_address: {
138
+ first_name: defaultAddress.first_name,
139
+ last_name: defaultAddress.last_name,
140
+ address_1: defaultAddress.address_1,
141
+ address_2: defaultAddress.address_2,
142
+ city: defaultAddress.city,
143
+ country_code: defaultAddress.country_code,
144
+ postal_code: defaultAddress.postal_code,
145
+ province: defaultAddress.province,
146
+ phone: defaultAddress.phone,
147
+ company: defaultAddress.company,
148
+ },
149
+ email: customer?.email,
150
+ },
151
+ {},
152
+ headers
153
+ )
154
+
155
+ // Fetch and set first shipping method
156
+ const { shipping_options } = await sdk.client.fetch<{
157
+ shipping_options: HttpTypes.StoreCartShippingOption[]
158
+ }>("/store/shipping-options", {
159
+ query: { cart_id: cart.id },
160
+ headers,
161
+ cache: "no-store",
162
+ })
163
+
164
+ if (shouldSkipToPayment(shipping_options?.length ?? 0)) {
165
+ await sdk.store.cart.addShippingMethod(
166
+ cart.id,
167
+ { option_id: shipping_options[0].id },
168
+ {},
169
+ headers
170
+ )
171
+ skipToPayment = true
172
+ }
173
+ } catch (e) {
174
+ // Fallback to normal checkout if anything fails
175
+ console.error("Failed to auto-fill address and shipping:", e)
176
+ }
177
+ }
178
+
179
+ const cartCacheTag = await getCacheTag("carts")
180
+ revalidateTag(cartCacheTag)
181
+ revalidatePath(`/${countryCode}/checkout`, "page")
182
+
183
+ redirect(getBuyNowCheckoutUrl(countryCode, cart.id, skipToPayment))
184
+ }
@@ -0,0 +1,487 @@
1
+ "use server"
2
+
3
+ import { sdk } from "@core/config"
4
+ import medusaError from "@core/util/medusa-error"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import { revalidateTag, revalidatePath } from "next/cache"
7
+ import { redirect } from "next/navigation"
8
+ import { headers as getRequestHeaders } from "next/headers"
9
+ import {
10
+ getAuthHeaders,
11
+ getCacheOptions,
12
+ getCacheTag,
13
+ getCartId,
14
+ removeCartId,
15
+ setCartId,
16
+ getHoldCartId,
17
+ setHoldCartId,
18
+ removeHoldCartId,
19
+ setBuyNowCartId,
20
+ removeBuyNowCartId,
21
+ } from "../cookies"
22
+ import { getRegion } from "../regions"
23
+ import { getLocale } from "@core/data/locale-actions"
24
+ import { retrieveCustomer, transferCart } from "@core/data/customer"
25
+ import { cache } from "react"
26
+ import {
27
+ createBuyNowMetadata,
28
+ filterOutCurrentCart,
29
+ getBuyNowCheckoutUrl,
30
+ getPostOrderCookieAction,
31
+ getResumeAbandonedCheckoutUrl,
32
+ isEphemeralCart,
33
+ normalizePhoneForRazorpay,
34
+ partitionAbandonedCarts,
35
+ shouldCheckEphemeralCartMetadata,
36
+ shouldSkipToPayment,
37
+ validateAddToCartInput,
38
+ validateCartId,
39
+ validateLineItemDeleteInput,
40
+ validateLineItemUpdateInput,
41
+ validateRazorpayPreflight,
42
+ } from "@core/domain/cart"
43
+ import { retrieveCart } from "./retrieve"
44
+ import { updateCart } from "./mutations"
45
+
46
+ export async function setShippingMethod({
47
+ cartId,
48
+ shippingMethodId,
49
+ }: {
50
+ cartId: string
51
+ shippingMethodId: string
52
+ }) {
53
+ const headers = {
54
+ ...(await getAuthHeaders()),
55
+ }
56
+
57
+ return sdk.store.cart
58
+ .addShippingMethod(cartId, { option_id: shippingMethodId }, {}, headers)
59
+ .then(async () => {
60
+ const cartCacheTag = await getCacheTag("carts")
61
+ revalidateTag(cartCacheTag)
62
+ })
63
+ .catch(medusaError)
64
+ }
65
+
66
+ /**
67
+ * Sets a shipping method silently without triggering a full page revalidation.
68
+ */
69
+
70
+ export async function setShippingMethodSilently({
71
+ cartId,
72
+ shippingMethodId,
73
+ }: {
74
+ cartId: string
75
+ shippingMethodId: string
76
+ }) {
77
+ const headers = {
78
+ ...(await getAuthHeaders()),
79
+ }
80
+
81
+ try {
82
+ await sdk.store.cart.addShippingMethod(
83
+ cartId,
84
+ { option_id: shippingMethodId },
85
+ {},
86
+ headers
87
+ )
88
+ return { success: true }
89
+ } catch (e) {
90
+ return { success: false }
91
+ }
92
+ }
93
+
94
+ export async function initiatePaymentSession(
95
+ cart: HttpTypes.StoreCart,
96
+ data: HttpTypes.StoreInitializePaymentSession
97
+ ) {
98
+ const headers = {
99
+ ...(await getAuthHeaders()),
100
+ }
101
+
102
+ // 1. RE-FETCH cart with NO CACHE to ensure we have the latest data from the DB
103
+ const latestCart = await retrieveCart(cart.id)
104
+ if (!latestCart) throw new Error("Cart not found")
105
+
106
+ const rawPhone =
107
+ latestCart.shipping_address?.phone || latestCart.billing_address?.phone
108
+ const phone = normalizePhoneForRazorpay(rawPhone)
109
+
110
+ // 2. Critical Fix for Razorpay: Ensure normalized phone is in BOTH shipping and billing addresses
111
+ if (phone) {
112
+ const addressUpdates: any = {}
113
+
114
+ // Clean address helper to ensure we pass a clean object to the update API
115
+ const cleanAddress = (addr: any) => {
116
+ if (!addr) return null
117
+ return {
118
+ first_name: addr.first_name,
119
+ last_name: addr.last_name,
120
+ address_1: addr.address_1,
121
+ address_2: addr.address_2 || "",
122
+ city: addr.city,
123
+ country_code: addr.country_code,
124
+ postal_code: addr.postal_code,
125
+ province: addr.province,
126
+ company: addr.company,
127
+ phone: phone,
128
+ }
129
+ }
130
+
131
+ // Update if phone is missing in DB
132
+ if (latestCart.shipping_address && !latestCart.shipping_address.phone) {
133
+ addressUpdates.shipping_address = cleanAddress(latestCart.shipping_address)
134
+ }
135
+
136
+ if (latestCart.billing_address && !latestCart.billing_address.phone) {
137
+ addressUpdates.billing_address =
138
+ cleanAddress(latestCart.billing_address) ||
139
+ cleanAddress(latestCart.shipping_address)
140
+ }
141
+
142
+ if (Object.keys(addressUpdates).length > 0) {
143
+ await sdk.store.cart.update(latestCart.id, addressUpdates, {}, headers)
144
+ }
145
+ }
146
+
147
+ // 3. Final re-fetch (direct SDK call to bypass any lib-level cache)
148
+ const cartResponse = await sdk.store.cart.retrieve(
149
+ cart.id,
150
+ { fields: "*shipping_address,*billing_address" },
151
+ headers
152
+ )
153
+ const finalCart = cartResponse.cart
154
+
155
+ const displayPhone =
156
+ finalCart.shipping_address?.phone || finalCart.billing_address?.phone || phone
157
+
158
+ validateRazorpayPreflight({
159
+ providerId: data.provider_id,
160
+ phone: displayPhone,
161
+ email: finalCart.email,
162
+ })
163
+
164
+ // 5. ENHANCED DATA FOR RAZORPAY:
165
+ const enhancedData = {
166
+ ...data,
167
+ data: {
168
+ ...(data.data || {}),
169
+ billing_address: finalCart.billing_address || finalCart.shipping_address,
170
+ customer: {
171
+ email: finalCart.email,
172
+ phone: displayPhone,
173
+ first_name:
174
+ finalCart.billing_address?.first_name ||
175
+ finalCart.shipping_address?.first_name,
176
+ last_name:
177
+ finalCart.billing_address?.last_name || finalCart.shipping_address?.last_name,
178
+ },
179
+ },
180
+ }
181
+
182
+ return sdk.store.payment
183
+ .initiatePaymentSession(finalCart, enhancedData, {}, headers)
184
+ .then(async (resp) => {
185
+ const cartCacheTag = await getCacheTag("carts")
186
+ revalidateTag(cartCacheTag)
187
+ return resp
188
+ })
189
+ .catch((e) => {
190
+ const errorMsg = e.message || e.response?.data?.message || "Unknown error"
191
+ throw new Error(`Razorpay Initialization Failed: ${errorMsg}`)
192
+ })
193
+ }
194
+
195
+ export async function setAddresses(currentState: unknown, formData: FormData) {
196
+ try {
197
+ if (!formData) {
198
+ throw new Error("No form data found when setting addresses")
199
+ }
200
+ const cartId = await getCartId()
201
+ if (!cartId) {
202
+ throw new Error("No existing cart found when setting addresses")
203
+ }
204
+
205
+ const shippingCountryCode =
206
+ (formData.get("shipping_address.country_code") as string)?.toLowerCase() || "in"
207
+
208
+ const rawPhone = formData.get("shipping_address.phone") as string
209
+ const phone = normalizePhoneForRazorpay(rawPhone)
210
+
211
+ const shippingAddress = {
212
+ first_name: formData.get("shipping_address.first_name") as string,
213
+ last_name: formData.get("shipping_address.last_name") as string,
214
+ address_1: formData.get("shipping_address.address_1") as string,
215
+ address_2: (formData.get("shipping_address.address_2") as string) || "",
216
+ company: formData.get("shipping_address.company") as string,
217
+ postal_code: formData.get("shipping_address.postal_code") as string,
218
+ city: formData.get("shipping_address.city") as string,
219
+ country_code: shippingCountryCode,
220
+ province: formData.get("shipping_address.province") as string,
221
+ phone: phone as string,
222
+ }
223
+
224
+ const data = {
225
+ shipping_address: shippingAddress,
226
+ email: formData.get("email"),
227
+ } as any
228
+
229
+ // Save address to customer profile if logged in
230
+ const authHeaders = await getAuthHeaders()
231
+ if (authHeaders && "authorization" in authHeaders) {
232
+ try {
233
+ // Fetch current customer to check if address already exists
234
+ const { customer } = await sdk.client.fetch<{
235
+ customer: HttpTypes.StoreCustomer
236
+ }>(`/store/customers/me`, {
237
+ method: "GET",
238
+ query: { fields: "*addresses" },
239
+ headers: authHeaders as Record<string, string>,
240
+ cache: "no-store",
241
+ })
242
+
243
+ const addressExists = customer?.addresses?.some(
244
+ (a: any) =>
245
+ a.address_1 === shippingAddress.address_1 &&
246
+ a.postal_code === shippingAddress.postal_code &&
247
+ a.city === shippingAddress.city &&
248
+ a.first_name === shippingAddress.first_name &&
249
+ a.last_name === shippingAddress.last_name
250
+ )
251
+
252
+ if (!addressExists) {
253
+ await sdk.store.customer.createAddress(
254
+ shippingAddress,
255
+ {},
256
+ authHeaders as Record<string, string>
257
+ )
258
+ const customerCacheTag = await getCacheTag("customers")
259
+ revalidateTag(customerCacheTag)
260
+ }
261
+ } catch (e) {
262
+ // Silently fail when saving address to customer profile
263
+ }
264
+ }
265
+
266
+ const sameAsBilling = formData.get("same_as_billing")
267
+ if (sameAsBilling === "on") {
268
+ data.billing_address = { ...data.shipping_address }
269
+ }
270
+
271
+ if (sameAsBilling !== "on") {
272
+ const bPhone =
273
+ normalizePhoneForRazorpay(formData.get("billing_address.phone") as string) ||
274
+ phone
275
+ data.billing_address = {
276
+ first_name: formData.get("billing_address.first_name"),
277
+ last_name: formData.get("billing_address.last_name"),
278
+ address_1: formData.get("billing_address.address_1"),
279
+ address_2: formData.get("billing_address.address_2") || "",
280
+ company: formData.get("billing_address.company"),
281
+ postal_code: formData.get("billing_address.postal_code"),
282
+ city: formData.get("billing_address.city"),
283
+ country_code: shippingCountryCode,
284
+ province: formData.get("billing_address.province"),
285
+ phone: bPhone,
286
+ }
287
+ }
288
+ await updateCart(data)
289
+ } catch (e: any) {
290
+ return e.message
291
+ }
292
+
293
+ revalidateTag("carts")
294
+ }
295
+
296
+ /**
297
+ * Places an order for a cart. If no cart ID is provided, it will use the cart ID from the cookies.
298
+ * @param cartId - optional - The ID of the cart to place an order for.
299
+ * @returns The cart object if the order was successful, or null if not.
300
+ */
301
+
302
+ export async function placeOrder(cartId?: string) {
303
+ const id = cartId || (await getCartId())
304
+
305
+ if (!id) {
306
+ throw new Error("No existing cart found when placing an order")
307
+ }
308
+
309
+ const headers = {
310
+ ...(await getAuthHeaders()),
311
+ }
312
+
313
+ const cartRes = await sdk.store.cart
314
+ .complete(id, {}, headers)
315
+ .then(async (cartRes) => {
316
+ const cartCacheTag = await getCacheTag("carts")
317
+ revalidateTag(cartCacheTag)
318
+ return cartRes
319
+ })
320
+ .catch(medusaError)
321
+
322
+ if (cartRes?.type === "order") {
323
+ const countryCode = cartRes.order.shipping_address?.country_code?.toLowerCase()
324
+
325
+ const orderCacheTag = await getCacheTag("orders")
326
+ revalidateTag(orderCacheTag)
327
+
328
+ // NEW LOGIC: Check if we just completed a Buy Now cart
329
+ const { cookies: nextCookies } = await import("next/headers")
330
+ const cookiesStore = await nextCookies()
331
+ const buyNowId = cookiesStore.get("buy_now_cart_id")?.value
332
+
333
+ const cookieAction = getPostOrderCookieAction(!!buyNowId)
334
+
335
+ if (cookieAction === "remove_buy_now") {
336
+ await removeBuyNowCartId()
337
+ } else {
338
+ await removeCartId()
339
+ }
340
+
341
+ redirect(`/${countryCode}/order/${cartRes?.order.id}/confirmed`)
342
+ }
343
+
344
+ return cartRes.cart
345
+ }
346
+
347
+ /**
348
+ * Updates the countrycode param and revalidates the regions cache
349
+ * @param regionId
350
+ * @param countryCode
351
+ */
352
+
353
+ export async function addCustomerAddressToCart(
354
+ currentState: unknown,
355
+ formData: FormData
356
+ ) {
357
+ try {
358
+ const headers = {
359
+ ...(await getAuthHeaders()),
360
+ }
361
+
362
+ const shippingCountryCode =
363
+ (formData.get("shipping_address.country_code") as string)?.toLowerCase() || "in"
364
+
365
+ // Parse address data from formData (which uses shipping_address. prefix)
366
+ const addressData = {
367
+ first_name: formData.get("shipping_address.first_name") as string,
368
+ last_name: formData.get("shipping_address.last_name") as string,
369
+ company: formData.get("shipping_address.company") as string,
370
+ address_1: formData.get("shipping_address.address_1") as string,
371
+ address_2: formData.get("shipping_address.address_2") as string,
372
+ city: formData.get("shipping_address.city") as string,
373
+ postal_code: formData.get("shipping_address.postal_code") as string,
374
+ province: formData.get("shipping_address.province") as string,
375
+ country_code: shippingCountryCode,
376
+ phone: formData.get("shipping_address.phone") as string,
377
+ metadata: {
378
+ is_default: formData.get("is_default") === "true",
379
+ address_type: formData.get("address_type"),
380
+ },
381
+ }
382
+
383
+ const addressId = formData.get("address_id") as string
384
+
385
+ // 1. Add/Update Customer Address Book
386
+ // We try/catch this separately so if it fails (e.g. user not logged in), we still try to proceed with checkout
387
+ try {
388
+ if (addressId) {
389
+ await sdk.store.customer.updateAddress(addressId, addressData, {}, headers)
390
+ } else {
391
+ await sdk.store.customer.createAddress(addressData, {}, headers)
392
+ }
393
+ const customerCacheTag = await getCacheTag("customers")
394
+ revalidateTag(customerCacheTag)
395
+ } catch (e) {
396
+ // verify if it is an auth error, maybe we shouldn't fail silently?
397
+ // But for checkout flow, the priority is to proceed.
398
+ }
399
+
400
+ // 2. Set as Cart Shipping Address
401
+ const cartId = await getCartId()
402
+ if (!cartId) {
403
+ throw new Error("No existing cart found when setting addresses")
404
+ }
405
+
406
+ const cartData = {
407
+ shipping_address: {
408
+ first_name: addressData.first_name,
409
+ last_name: addressData.last_name,
410
+ company: addressData.company,
411
+ address_1: addressData.address_1,
412
+ address_2: addressData.address_2,
413
+ city: addressData.city,
414
+ postal_code: addressData.postal_code,
415
+ province: addressData.province,
416
+ country_code: addressData.country_code,
417
+ phone: addressData.phone,
418
+ },
419
+ email: formData.get("email"),
420
+ } as any
421
+
422
+ const sameAsBilling = formData.get("same_as_billing")
423
+ if (sameAsBilling === "on") {
424
+ cartData.billing_address = cartData.shipping_address
425
+ } else {
426
+ // If not same as billing, we should ideally handle billing address too,
427
+ // but AddAddressModal usually forces same_as_billing="on" (Line 52 in AddAddressModal).
428
+ // If we ever change that, we'll need logic here. For now, AddAddressModal is strictly for "Add New Shippping Address".
429
+ }
430
+
431
+ await updateCart(cartData)
432
+ } catch (e: any) {
433
+ return e.message
434
+ }
435
+
436
+ revalidateTag("carts")
437
+ // No redirect if we are already in checkout to prevent disrupting the user
438
+ }
439
+
440
+ /**
441
+ * Updates address in the background without redirecting.
442
+ * Used for real-time shipping calculation when pincode is entered.
443
+ */
444
+
445
+ export async function updateAddressSilently(data: any) {
446
+ const cartId = await getCartId()
447
+ if (!cartId) return
448
+
449
+ try {
450
+ const headers = {
451
+ ...(await getAuthHeaders()),
452
+ }
453
+
454
+ await sdk.store.cart.update(cartId, data, {}, headers)
455
+
456
+ // Revalidate tags to ensure CartTotals gets new shipping info
457
+ const cartCacheTag = await getCacheTag("carts")
458
+ revalidateTag(cartCacheTag)
459
+
460
+ const fulfillmentCacheTag = await getCacheTag("fulfillment")
461
+ revalidateTag(fulfillmentCacheTag)
462
+
463
+ return { success: true }
464
+ } catch (e) {
465
+ return { success: false }
466
+ }
467
+ }
468
+ /**
469
+ * Updates cart metadata silently without triggering a full page revalidation.
470
+ */
471
+
472
+ export async function updateCartMetadataSilently(metadata: any) {
473
+ const cartId = await getCartId()
474
+ if (!cartId) return
475
+
476
+ try {
477
+ const headers = {
478
+ ...(await getAuthHeaders()),
479
+ }
480
+
481
+ await sdk.store.cart.update(cartId, { metadata }, {}, headers)
482
+ // We intentionally DO NOT call revalidateTag here to prevent page refresh loops
483
+ return { success: true }
484
+ } catch (e) {
485
+ return { success: false }
486
+ }
487
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./retrieve"
2
+ export * from "./mutations"
3
+ export * from "./buyNow"
4
+ export * from "./checkout"
5
+ export * from "./promotions"
6
+ export * from "./abandoned"
7
+ export * from "./region"