@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,154 @@
1
+ "use server"
2
+
3
+ import { sdk } from "@core/config"
4
+ import { HttpTypes } from "@medusajs/types"
5
+ import { cookies } from "next/headers"
6
+ import { getAuthHeaders } from "./cookies"
7
+ import { revalidateTag } from "next/cache"
8
+
9
+ /**
10
+ * Create an exchange (swap) request.
11
+ * Supports both authenticated customers and guest users.
12
+ */
13
+ export const createSwapRequest = async (
14
+ prevState: any,
15
+ formData: FormData
16
+ ): Promise<{
17
+ success: boolean
18
+ error: string | null
19
+ swap: any | null
20
+ }> => {
21
+ const orderId = formData.get("order_id") as string
22
+ const returnItemsJson = formData.get("return_items") as string
23
+ const newItemsJson = formData.get("new_items") as string
24
+ const reason = formData.get("reason") as string
25
+ const note = formData.get("note") as string
26
+
27
+ if (!orderId) return { success: false, error: "Order ID is required", swap: null }
28
+ if (!returnItemsJson)
29
+ return { success: false, error: "Items to return are required", swap: null }
30
+ if (!newItemsJson)
31
+ return { success: false, error: "New items are required", swap: null }
32
+
33
+ let returnItems: any[] = []
34
+ let newItems: any[] = []
35
+ try {
36
+ returnItems = JSON.parse(returnItemsJson)
37
+ newItems = JSON.parse(newItemsJson)
38
+ } catch (e) {
39
+ return { success: false, error: "Invalid items data format", swap: null }
40
+ }
41
+
42
+ if (returnItems.length === 0) {
43
+ return {
44
+ success: false,
45
+ error: "At least one item must be selected for return",
46
+ swap: null,
47
+ }
48
+ }
49
+
50
+ const headers = {
51
+ ...(await getAuthHeaders()),
52
+ }
53
+
54
+ try {
55
+ const payload = {
56
+ order_id: orderId,
57
+ return_items: returnItems,
58
+ new_items: newItems,
59
+ reason: reason || "Size exchange",
60
+ note: note || "Exchange requested from storefront",
61
+ }
62
+
63
+ const cookieStore = await cookies()
64
+ const guestToken =
65
+ cookieStore.get("_medusa_guest_jwt")?.value ||
66
+ cookieStore.get("_medusa_guest_token")?.value
67
+ const token = cookieStore.get("_medusa_jwt")?.value
68
+
69
+ let swapData: any
70
+
71
+ // Check if we should use the guest endpoint
72
+ if (guestToken && !token) {
73
+ const response = await sdk.client.fetch<any>(
74
+ `/store/guest-orders/${orderId}/swaps`,
75
+ {
76
+ method: "POST",
77
+ body: {
78
+ return_items: payload.return_items,
79
+ new_items: payload.new_items,
80
+ reason: payload.reason,
81
+ note: payload.note,
82
+ },
83
+ headers: {
84
+ ...headers,
85
+ Authorization: `Bearer ${guestToken}`,
86
+ },
87
+ cache: "no-store",
88
+ }
89
+ )
90
+ swapData = response.swap || response
91
+ } else {
92
+ const response = await sdk.client.fetch<any>(`/store/swaps`, {
93
+ method: "POST",
94
+ body: payload,
95
+ headers,
96
+ cache: "no-store",
97
+ })
98
+ swapData = response.swap || response
99
+ }
100
+
101
+ revalidateTag("orders")
102
+
103
+ return { success: true, error: null, swap: swapData }
104
+ } catch (error: any) {
105
+ // Extract error message from response if possible
106
+ let errorMsg = "Failed to create exchange request"
107
+ if (error.response?.data?.message) {
108
+ errorMsg = error.response.data.message
109
+ } else if (error.message) {
110
+ errorMsg = error.message
111
+ }
112
+
113
+ return { success: false, error: errorMsg, swap: null }
114
+ }
115
+ }
116
+
117
+ /**
118
+ * List swaps for an order or customer
119
+ */
120
+ export const listSwaps = async (orderId?: string) => {
121
+ const headers = {
122
+ ...(await getAuthHeaders()),
123
+ }
124
+
125
+ // Authenticated flow
126
+ if ((headers as any).authorization) {
127
+ return sdk.client.fetch<any>(`/store/swaps`, {
128
+ method: "GET",
129
+ query: orderId ? { order_id: orderId } : {},
130
+ headers,
131
+ cache: "no-store",
132
+ })
133
+ }
134
+
135
+ // Guest flow
136
+ if (orderId) {
137
+ const cookieStore = await cookies()
138
+ const guestToken =
139
+ cookieStore.get("_medusa_guest_jwt")?.value ||
140
+ cookieStore.get("_medusa_guest_token")?.value
141
+ if (guestToken) {
142
+ return sdk.client.fetch<any>(`/store/guest-orders/${orderId}/swaps`, {
143
+ method: "GET",
144
+ headers: {
145
+ ...headers,
146
+ Authorization: `Bearer ${guestToken}`,
147
+ },
148
+ cache: "no-store",
149
+ })
150
+ }
151
+ }
152
+
153
+ return { swaps: [], count: 0 }
154
+ }
@@ -0,0 +1,38 @@
1
+ "use server"
2
+
3
+ import { sdk } from "@core/config"
4
+ import { HttpTypes } from "@medusajs/types"
5
+
6
+ import { getAuthHeaders, getCacheOptions } from "./cookies"
7
+
8
+ export const retrieveVariant = async (
9
+ variant_id: string
10
+ ): Promise<HttpTypes.StoreProductVariant | null> => {
11
+ const authHeaders = await getAuthHeaders()
12
+
13
+ if (!authHeaders) return null
14
+
15
+ const headers = {
16
+ ...authHeaders,
17
+ }
18
+
19
+ const next = {
20
+ ...(await getCacheOptions("variants")),
21
+ }
22
+
23
+ return await sdk.client
24
+ .fetch<{ variant: HttpTypes.StoreProductVariant }>(
25
+ `/store/product-variants/${variant_id}`,
26
+ {
27
+ method: "GET",
28
+ query: {
29
+ fields: "*images",
30
+ },
31
+ headers,
32
+ next,
33
+ cache: "force-cache",
34
+ }
35
+ )
36
+ .then(({ variant }) => variant)
37
+ .catch(() => null)
38
+ }
@@ -0,0 +1,292 @@
1
+ "use server"
2
+
3
+ import { getAuthHeaders } from "./cookies"
4
+ import { HttpTypes } from "@medusajs/types"
5
+
6
+ const getBaseUrl = () => {
7
+ return process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
8
+ }
9
+
10
+ const getPublishableKey = () => {
11
+ return process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
12
+ }
13
+
14
+ /**
15
+ * Add a product to wishlist
16
+ */
17
+ export async function addToWishlist(productId: string) {
18
+ try {
19
+ const authHeaders = await getAuthHeaders()
20
+ const publishableKey = getPublishableKey()
21
+ const baseUrl = getBaseUrl()
22
+
23
+ if (!publishableKey) {
24
+ return {
25
+ success: false,
26
+ error: "Configuration error: Publishable API key is missing.",
27
+ }
28
+ }
29
+
30
+ if (!authHeaders || !("authorization" in authHeaders)) {
31
+ return {
32
+ success: false,
33
+ error: "Please login to add items to wishlist.",
34
+ }
35
+ }
36
+
37
+ const headers: Record<string, string> = {
38
+ "Content-Type": "application/json",
39
+ "x-publishable-api-key": publishableKey,
40
+ ...authHeaders,
41
+ }
42
+
43
+ const response = await fetch(`${baseUrl}/store/wishlist`, {
44
+ method: "POST",
45
+ headers,
46
+ body: JSON.stringify({
47
+ product_id: productId,
48
+ }),
49
+ })
50
+
51
+ if (!response.ok) {
52
+ if (response.status === 401 || response.status === 403) {
53
+ return {
54
+ success: false,
55
+ error: "Login required",
56
+ }
57
+ }
58
+ const error = await response.json().catch(() => ({ message: "Unknown error" }))
59
+ return {
60
+ success: false,
61
+ error: error.message || `HTTP ${response.status}: Failed to add to wishlist`,
62
+ }
63
+ }
64
+
65
+ const data = await response.json()
66
+ return {
67
+ success: true,
68
+ data,
69
+ }
70
+ } catch (error: any) {
71
+ return {
72
+ success: false,
73
+ error: error.message || "An error occurred while adding to wishlist",
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Remove a product from wishlist
80
+ */
81
+ export async function removeFromWishlist(productId: string) {
82
+ try {
83
+ const authHeaders = await getAuthHeaders()
84
+ const publishableKey = getPublishableKey()
85
+ const baseUrl = getBaseUrl()
86
+
87
+ if (!publishableKey) {
88
+ return {
89
+ success: false,
90
+ error: "Configuration error: Publishable API key is missing.",
91
+ }
92
+ }
93
+
94
+ if (!authHeaders || !("authorization" in authHeaders)) {
95
+ return {
96
+ success: false,
97
+ error: "Please login to remove items from wishlist.",
98
+ }
99
+ }
100
+
101
+ const headers: Record<string, string> = {
102
+ "Content-Type": "application/json",
103
+ "x-publishable-api-key": publishableKey,
104
+ ...authHeaders,
105
+ }
106
+
107
+ const response = await fetch(`${baseUrl}/store/wishlist/${productId}`, {
108
+ method: "DELETE",
109
+ headers,
110
+ })
111
+
112
+ if (!response.ok) {
113
+ if (response.status === 401 || response.status === 403) {
114
+ return {
115
+ success: false,
116
+ error: "Login required",
117
+ }
118
+ }
119
+ const error = await response.json().catch(() => ({ message: "Unknown error" }))
120
+ return {
121
+ success: false,
122
+ error:
123
+ error.message || `HTTP ${response.status}: Failed to remove from wishlist`,
124
+ }
125
+ }
126
+
127
+ return {
128
+ success: true,
129
+ }
130
+ } catch (error: any) {
131
+ return {
132
+ success: false,
133
+ error: error.message || "An error occurred while removing from wishlist",
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Get wishlist items with full product details
140
+ */
141
+ export async function getWishlist(
142
+ includeDetails: boolean = true,
143
+ countryCode?: string
144
+ ) {
145
+ try {
146
+ const authHeaders = await getAuthHeaders()
147
+ const publishableKey = getPublishableKey()
148
+ const baseUrl = getBaseUrl()
149
+
150
+ if (!publishableKey) {
151
+ return {
152
+ success: false,
153
+ error: "Configuration error: Publishable API key is missing.",
154
+ data: [],
155
+ }
156
+ }
157
+
158
+ if (!authHeaders || !("authorization" in authHeaders)) {
159
+ // Return empty array if not logged in
160
+ return {
161
+ success: true,
162
+ data: [],
163
+ }
164
+ }
165
+
166
+ const headers: Record<string, string> = {
167
+ "Content-Type": "application/json",
168
+ "x-publishable-api-key": publishableKey,
169
+ ...authHeaders,
170
+ }
171
+
172
+ const url = new URL(`${baseUrl}/store/wishlist`)
173
+ if (includeDetails) {
174
+ url.searchParams.set("include_details", "true")
175
+ // Using the same aggressive expansion style that worked for the cart
176
+ url.searchParams.set(
177
+ "fields",
178
+ "id,product_id,*product,product.thumbnail,product.images"
179
+ )
180
+ }
181
+
182
+ const response = await fetch(url.toString(), {
183
+ method: "GET",
184
+ headers,
185
+ cache: "no-store",
186
+ })
187
+
188
+ if (!response.ok) {
189
+ // If 401 or 403, user is not authenticated, return empty array
190
+ if (response.status === 401 || response.status === 403) {
191
+ return {
192
+ success: true,
193
+ data: [],
194
+ }
195
+ }
196
+
197
+ const error = await response.json().catch(() => ({ message: "Unknown error" }))
198
+ return {
199
+ success: false,
200
+ error: error.message || `HTTP ${response.status}: Failed to fetch wishlist`,
201
+ data: [],
202
+ }
203
+ }
204
+
205
+ const data = await response.json()
206
+ let wishlistItems = data.wishlist || (Array.isArray(data) ? data : [])
207
+
208
+ // If we have items and details are requested, fetch full product details separately
209
+ if (includeDetails && wishlistItems.length > 0) {
210
+ const productIds = wishlistItems
211
+ .map((item: any) => item.product_id || item.id)
212
+ .filter(Boolean)
213
+
214
+ try {
215
+ const { sdk } = await import("@core/config")
216
+
217
+ let regionId: string | undefined = undefined
218
+ if (countryCode) {
219
+ const { getRegion } = await import("./regions")
220
+ const region = await getRegion(countryCode)
221
+ regionId = region?.id
222
+ }
223
+
224
+ const productsResponse = await sdk.store.product.list(
225
+ // Fetch comprehensive product details including variants, options and inventory for availability checks
226
+ {
227
+ id: productIds,
228
+ fields:
229
+ "*thumbnail,*images,*variants,*variants.options,*options,*options.values,*variants.calculated_price,*variants.inventory_quantity,*variants.manage_inventory,*variants.allow_backorder",
230
+ ...(regionId ? { region_id: regionId } : {}),
231
+ },
232
+ { next: { tags: ["products"] } }
233
+ )
234
+
235
+ const fullProducts =
236
+ productsResponse.products as unknown as HttpTypes.StoreProduct[]
237
+
238
+ // Merge full product data back into wishlist items
239
+ wishlistItems = wishlistItems.map((item: any) => {
240
+ const itemId = item.product_id || item.id
241
+ const fullProduct = fullProducts.find((p: any) => p.id === itemId)
242
+
243
+ if (fullProduct) {
244
+ // Prefer the full product data from the SDK as it has the relations we need
245
+ return {
246
+ ...item,
247
+ product: fullProduct,
248
+ }
249
+ }
250
+ return item
251
+ })
252
+ } catch (e) {
253
+ // Handle error silently
254
+ }
255
+ }
256
+
257
+ return {
258
+ success: true,
259
+ data: wishlistItems,
260
+ }
261
+ } catch (error: any) {
262
+ return {
263
+ success: false,
264
+ error: error.message || "An error occurred while fetching wishlist",
265
+ data: [],
266
+ }
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Get wishlist product IDs only (for checking if product is in wishlist)
272
+ */
273
+ export async function getWishlistProductIds(): Promise<string[]> {
274
+ try {
275
+ const result = await getWishlist(false)
276
+ if (result.success && Array.isArray(result.data)) {
277
+ const ids = result.data
278
+ .map((item: any) => {
279
+ if (typeof item === "string") return item
280
+ if (item.product_id) return item.product_id
281
+ if (item.id) return item.id
282
+ if (item.product?.id) return item.product.id
283
+ return null
284
+ })
285
+ .filter((id: string | null): id is string => id !== null)
286
+ return ids
287
+ }
288
+ return []
289
+ } catch (error) {
290
+ return []
291
+ }
292
+ }
@@ -0,0 +1,49 @@
1
+ import { HttpTypes } from "@medusajs/types"
2
+ import { isBuyNowCart, isReorderCart } from "./metadata"
3
+
4
+ export type AbandonedCartsResult = {
5
+ buyNowCarts: HttpTypes.StoreCart[]
6
+ reorderCarts: HttpTypes.StoreCart[]
7
+ }
8
+
9
+ export function filterOutCurrentCart(
10
+ carts: HttpTypes.StoreCart[],
11
+ currentCartId?: string | null
12
+ ): HttpTypes.StoreCart[] {
13
+ if (!currentCartId) {
14
+ return carts
15
+ }
16
+
17
+ return carts.filter((cart) => cart.id !== currentCartId)
18
+ }
19
+
20
+ export function partitionAbandonedCarts(
21
+ carts: HttpTypes.StoreCart[]
22
+ ): AbandonedCartsResult {
23
+ return {
24
+ buyNowCarts: carts.filter((cart) => isBuyNowCart(cart.metadata)),
25
+ reorderCarts: carts.filter((cart) => isReorderCart(cart.metadata)),
26
+ }
27
+ }
28
+
29
+ export function hasAbandonedCarts(abandoned: AbandonedCartsResult): boolean {
30
+ return abandoned.buyNowCarts.length > 0 || abandoned.reorderCarts.length > 0
31
+ }
32
+
33
+ export function getDefaultAbandonedTab(
34
+ abandoned: AbandonedCartsResult
35
+ ): "buy_now" | "reorder" {
36
+ if (abandoned.buyNowCarts.length > 0) {
37
+ return "buy_now"
38
+ }
39
+
40
+ return "reorder"
41
+ }
42
+
43
+ export function getCartTotalItemCount(cart: HttpTypes.StoreCart): number {
44
+ return cart.items?.reduce((total, item) => total + item.quantity, 0) ?? 0
45
+ }
46
+
47
+ export function getAbandonedTotalItemCount(carts: HttpTypes.StoreCart[]): number {
48
+ return carts.reduce((total, cart) => total + getCartTotalItemCount(cart), 0)
49
+ }
@@ -0,0 +1,15 @@
1
+ export function getBuyNowCheckoutUrl(
2
+ countryCode: string,
3
+ cartId: string,
4
+ skipToPayment: boolean
5
+ ): string {
6
+ if (skipToPayment) {
7
+ return `/${countryCode}/checkout?step=payment&cart_id=${cartId}`
8
+ }
9
+
10
+ return `/${countryCode}/checkout?cart_id=${cartId}`
11
+ }
12
+
13
+ export function shouldSkipToPayment(shippingOptionsCount: number): boolean {
14
+ return shippingOptionsCount > 0
15
+ }
@@ -0,0 +1,25 @@
1
+ export function isCheckoutPath(pathname: string): boolean {
2
+ return pathname.includes("/checkout")
3
+ }
4
+
5
+ export function shouldCheckEphemeralCartMetadata(options: {
6
+ explicitCartId?: string
7
+ pathname: string
8
+ }): boolean {
9
+ return !options.explicitCartId && !isCheckoutPath(options.pathname)
10
+ }
11
+
12
+ export type PostOrderCookieAction = "remove_buy_now" | "remove_main_cart"
13
+
14
+ export function getPostOrderCookieAction(
15
+ hasBuyNowCookie: boolean
16
+ ): PostOrderCookieAction {
17
+ return hasBuyNowCookie ? "remove_buy_now" : "remove_main_cart"
18
+ }
19
+
20
+ export function getResumeAbandonedCheckoutUrl(
21
+ countryCode: string,
22
+ cartId: string
23
+ ): string {
24
+ return `/${countryCode}/checkout?cart_id=${cartId}`
25
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./metadata"
2
+ export * from "./abandoned-carts"
3
+ export * from "./buy-now"
4
+ export * from "./reorder"
5
+ export * from "./checkout"
6
+ export * from "./validation"
7
+ export * from "./phone"
8
+ export * from "./payment"
@@ -0,0 +1,21 @@
1
+ export type CartMetadata = Record<string, unknown>
2
+
3
+ export function isBuyNowCart(metadata?: CartMetadata | null): boolean {
4
+ return metadata?.is_buy_now === true
5
+ }
6
+
7
+ export function isReorderCart(metadata?: CartMetadata | null): boolean {
8
+ return metadata?.is_reorder === true
9
+ }
10
+
11
+ export function isEphemeralCart(metadata?: CartMetadata | null): boolean {
12
+ return isBuyNowCart(metadata) || isReorderCart(metadata)
13
+ }
14
+
15
+ export function createBuyNowMetadata(): CartMetadata {
16
+ return { is_buy_now: true }
17
+ }
18
+
19
+ export function createReorderMetadata(): CartMetadata {
20
+ return { is_reorder: true }
21
+ }
@@ -0,0 +1,21 @@
1
+ export function requiresRazorpayContact(providerId: string): boolean {
2
+ return providerId.includes("razorpay")
3
+ }
4
+
5
+ export function validateRazorpayPreflight(options: {
6
+ providerId: string
7
+ phone?: string
8
+ email?: string
9
+ }): void {
10
+ if (requiresRazorpayContact(options.providerId) && !options.phone) {
11
+ throw new Error(
12
+ "CRITICAL Error: Phone number is missing from your address. Razorpay requires a contact number for verification. Please enter your phone number."
13
+ )
14
+ }
15
+
16
+ if (requiresRazorpayContact(options.providerId) && !options.email) {
17
+ throw new Error(
18
+ "CRITICAL Error: Email is missing from your cart. Razorpay requires an email address. Please enter your email."
19
+ )
20
+ }
21
+ }
@@ -0,0 +1,17 @@
1
+ export function normalizePhoneForRazorpay(phone?: string): string | undefined {
2
+ if (!phone) {
3
+ return undefined
4
+ }
5
+
6
+ const cleaned = String(phone).replace(/\D/g, "")
7
+
8
+ if (cleaned.length === 10) {
9
+ return `+91${cleaned}`
10
+ }
11
+
12
+ if (cleaned.length > 10 && !String(phone).startsWith("+")) {
13
+ return `+${cleaned}`
14
+ }
15
+
16
+ return phone
17
+ }
@@ -0,0 +1,19 @@
1
+ import { createReorderMetadata } from "./metadata"
2
+
3
+ export function extractNewCartIdFromReorderResponse(
4
+ data: Record<string, unknown>
5
+ ): string | undefined {
6
+ const cart = data.cart as { id?: string } | undefined
7
+ const reorder = data.reorder as { cart_id?: string } | undefined
8
+
9
+ return (
10
+ cart?.id ||
11
+ (data.id as string | undefined) ||
12
+ (data.cart_id as string | undefined) ||
13
+ reorder?.cart_id
14
+ )
15
+ }
16
+
17
+ export function buildReorderCartMetadata() {
18
+ return createReorderMetadata()
19
+ }