@mohasinac/appkit 2.6.1 → 2.6.3

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 (302) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +38 -0
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +75 -0
  3. package/dist/_internal/client/features/layout/RoleGuard.d.ts +15 -0
  4. package/dist/_internal/client/features/layout/RoleGuard.js +25 -0
  5. package/dist/_internal/client/features/layout/index.d.ts +5 -0
  6. package/dist/_internal/client/features/layout/index.js +4 -0
  7. package/dist/_internal/server/features/brands/actions.d.ts +3 -3
  8. package/dist/_internal/server/features/brands/actions.js +72 -5
  9. package/dist/_internal/server/features/brands/data.d.ts +8 -8
  10. package/dist/_internal/server/features/brands/data.js +10 -11
  11. package/dist/_internal/server/features/brands/service.d.ts +2 -2
  12. package/dist/_internal/server/features/brands/service.js +5 -5
  13. package/dist/_internal/server/features/categories/og.d.ts +33 -0
  14. package/dist/_internal/server/features/categories/og.js +75 -0
  15. package/dist/_internal/server/features/checkout/actions.d.ts +24 -0
  16. package/dist/_internal/server/features/checkout/actions.js +442 -13
  17. package/dist/_internal/server/features/checkout/index.d.ts +1 -1
  18. package/dist/_internal/server/features/checkout/index.js +1 -1
  19. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +59 -0
  20. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +99 -0
  21. package/dist/_internal/server/features/grouped/data.js +12 -5
  22. package/dist/_internal/server/features/homepage/data.d.ts +1 -1
  23. package/dist/_internal/server/features/homepage/data.js +2 -2
  24. package/dist/_internal/server/features/media/contextGuards.d.ts +52 -0
  25. package/dist/_internal/server/features/media/contextGuards.js +198 -0
  26. package/dist/_internal/server/features/orders/adapters.js +12 -0
  27. package/dist/_internal/server/features/products/data.d.ts +1 -1
  28. package/dist/_internal/server/features/sublisting-categories/data.d.ts +1 -1
  29. package/dist/_internal/server/features/sublisting-categories/data.js +2 -2
  30. package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +24 -0
  31. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +86 -0
  32. package/dist/_internal/server/jobs/handlers/bundleStockSync.d.ts +18 -0
  33. package/dist/_internal/server/jobs/handlers/bundleStockSync.js +80 -0
  34. package/dist/_internal/server/jobs/handlers/index.d.ts +8 -0
  35. package/dist/_internal/server/jobs/handlers/index.js +13 -0
  36. package/dist/_internal/server/jobs/handlers/listingProcessor.js +13 -3
  37. package/dist/_internal/server/jobs/handlers/onProductStockChange.d.ts +17 -0
  38. package/dist/_internal/server/jobs/handlers/onProductStockChange.js +136 -0
  39. package/dist/_internal/server/jobs/handlers/onProductWrite.js +17 -1
  40. package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +9 -0
  41. package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +29 -0
  42. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +10 -0
  43. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +58 -0
  44. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +10 -0
  45. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +65 -0
  46. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +9 -0
  47. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +45 -0
  48. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +30 -0
  49. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +94 -0
  50. package/dist/_internal/shared/features/brands/schema.d.ts +3 -3
  51. package/dist/_internal/shared/features/cart/schema.d.ts +8 -8
  52. package/dist/_internal/shared/features/cart/schema.js +1 -1
  53. package/dist/_internal/shared/features/categories/bundle-config.d.ts +17 -0
  54. package/dist/_internal/shared/features/categories/bundle-config.js +17 -0
  55. package/dist/_internal/shared/features/layout/config.d.ts +35 -0
  56. package/dist/_internal/shared/features/layout/config.js +58 -0
  57. package/dist/_internal/shared/features/layout/index.d.ts +3 -0
  58. package/dist/_internal/shared/features/layout/index.js +2 -0
  59. package/dist/_internal/shared/features/layout/types.d.ts +137 -0
  60. package/dist/_internal/shared/features/layout/types.js +13 -0
  61. package/dist/_internal/shared/features/products/types.d.ts +1 -1
  62. package/dist/_internal/shared/listing-types/_registry.d.ts +57 -0
  63. package/dist/_internal/shared/listing-types/_registry.js +28 -0
  64. package/dist/_internal/shared/listing-types/auction/config.d.ts +7 -0
  65. package/dist/_internal/shared/listing-types/auction/config.js +8 -0
  66. package/dist/_internal/shared/listing-types/auction/ctas.d.ts +1 -0
  67. package/dist/_internal/shared/listing-types/auction/ctas.js +2 -0
  68. package/dist/_internal/shared/listing-types/auction/og.d.ts +1 -0
  69. package/dist/_internal/shared/listing-types/auction/og.js +1 -0
  70. package/dist/_internal/shared/listing-types/auction/schema.d.ts +1 -0
  71. package/dist/_internal/shared/listing-types/auction/schema.js +1 -0
  72. package/dist/_internal/shared/listing-types/auction/seed-factory.d.ts +1 -0
  73. package/dist/_internal/shared/listing-types/auction/seed-factory.js +1 -0
  74. package/dist/_internal/shared/listing-types/capabilities.d.ts +41 -0
  75. package/dist/_internal/shared/listing-types/capabilities.js +75 -0
  76. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +7 -0
  77. package/dist/_internal/shared/listing-types/pre-order/config.js +8 -0
  78. package/dist/_internal/shared/listing-types/pre-order/ctas.d.ts +1 -0
  79. package/dist/_internal/shared/listing-types/pre-order/ctas.js +2 -0
  80. package/dist/_internal/shared/listing-types/pre-order/og.d.ts +1 -0
  81. package/dist/_internal/shared/listing-types/pre-order/og.js +1 -0
  82. package/dist/_internal/shared/listing-types/pre-order/schema.d.ts +1 -0
  83. package/dist/_internal/shared/listing-types/pre-order/schema.js +1 -0
  84. package/dist/_internal/shared/listing-types/pre-order/seed-factory.d.ts +1 -0
  85. package/dist/_internal/shared/listing-types/pre-order/seed-factory.js +1 -0
  86. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +7 -0
  87. package/dist/_internal/shared/listing-types/prize-draw/config.js +8 -0
  88. package/dist/_internal/shared/listing-types/prize-draw/ctas.d.ts +1 -0
  89. package/dist/_internal/shared/listing-types/prize-draw/ctas.js +2 -0
  90. package/dist/_internal/shared/listing-types/prize-draw/og.d.ts +1 -0
  91. package/dist/_internal/shared/listing-types/prize-draw/og.js +1 -0
  92. package/dist/_internal/shared/listing-types/prize-draw/schema.d.ts +1 -0
  93. package/dist/_internal/shared/listing-types/prize-draw/schema.js +1 -0
  94. package/dist/_internal/shared/listing-types/prize-draw/seed-factory.d.ts +1 -0
  95. package/dist/_internal/shared/listing-types/prize-draw/seed-factory.js +1 -0
  96. package/dist/_internal/shared/listing-types/standard/config.d.ts +7 -0
  97. package/dist/_internal/shared/listing-types/standard/config.js +8 -0
  98. package/dist/_internal/shared/listing-types/standard/ctas.d.ts +1 -0
  99. package/dist/_internal/shared/listing-types/standard/ctas.js +3 -0
  100. package/dist/_internal/shared/listing-types/standard/og.d.ts +1 -0
  101. package/dist/_internal/shared/listing-types/standard/og.js +1 -0
  102. package/dist/_internal/shared/listing-types/standard/schema.d.ts +1 -0
  103. package/dist/_internal/shared/listing-types/standard/schema.js +1 -0
  104. package/dist/_internal/shared/listing-types/standard/seed-factory.d.ts +1 -0
  105. package/dist/_internal/shared/listing-types/standard/seed-factory.js +1 -0
  106. package/dist/_internal/shared/media/limits.d.ts +33 -0
  107. package/dist/_internal/shared/media/limits.js +97 -0
  108. package/dist/_internal/shared/schema-versions.d.ts +76 -0
  109. package/dist/_internal/shared/schema-versions.js +82 -0
  110. package/dist/client.d.ts +9 -0
  111. package/dist/client.js +7 -0
  112. package/dist/constants/api-endpoints.d.ts +6 -3
  113. package/dist/constants/api-endpoints.js +2 -1
  114. package/dist/errors/messages.d.ts +1 -1
  115. package/dist/errors/messages.js +1 -1
  116. package/dist/features/account/migrations.d.ts +2 -0
  117. package/dist/features/account/migrations.js +10 -0
  118. package/dist/features/admin/components/AdminMediaView.js +1 -1
  119. package/dist/features/admin/components/AdminProductsView.js +7 -3
  120. package/dist/features/admin/migrations.d.ts +2 -0
  121. package/dist/features/admin/migrations.js +10 -0
  122. package/dist/features/admin/types/product.types.d.ts +1 -1
  123. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +1 -1
  124. package/dist/features/auctions/migrations.d.ts +2 -0
  125. package/dist/features/auctions/migrations.js +10 -0
  126. package/dist/features/auctions/schemas/index.d.ts +3 -3
  127. package/dist/features/auctions/schemas/index.js +1 -1
  128. package/dist/features/auth/migrations.d.ts +2 -0
  129. package/dist/features/auth/migrations.js +10 -0
  130. package/dist/features/blog/migrations.d.ts +2 -0
  131. package/dist/features/blog/migrations.js +10 -0
  132. package/dist/features/brands/migrations.d.ts +2 -0
  133. package/dist/features/brands/migrations.js +10 -0
  134. package/dist/features/bundles/components/BundlesByCategoryListing.d.ts +6 -0
  135. package/dist/features/bundles/components/BundlesByCategoryListing.js +50 -0
  136. package/dist/features/bundles/components/index.d.ts +2 -0
  137. package/dist/features/bundles/components/index.js +1 -0
  138. package/dist/features/bundles/migrations.d.ts +2 -0
  139. package/dist/features/bundles/migrations.js +10 -0
  140. package/dist/features/bundles/schemas/index.d.ts +1 -0
  141. package/dist/features/bundles/schemas/index.js +1 -0
  142. package/dist/features/bundles/schemas/zod.d.ts +377 -0
  143. package/dist/features/bundles/schemas/zod.js +71 -0
  144. package/dist/features/cart/migrations.d.ts +2 -0
  145. package/dist/features/cart/migrations.js +10 -0
  146. package/dist/features/cart/schemas/firestore.d.ts +2 -2
  147. package/dist/features/categories/components/BrandDetailPageView.js +35 -4
  148. package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
  149. package/dist/features/categories/components/BrandDetailTabs.js +22 -8
  150. package/dist/features/categories/components/CategoryBundlesListing.d.ts +6 -0
  151. package/dist/features/categories/components/CategoryBundlesListing.js +74 -0
  152. package/dist/features/categories/components/CategoryDetailPageView.js +29 -4
  153. package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
  154. package/dist/features/categories/components/CategoryDetailTabs.js +22 -8
  155. package/dist/features/categories/migrations.d.ts +2 -0
  156. package/dist/features/categories/migrations.js +10 -0
  157. package/dist/features/categories/repository/categories.repository.d.ts +29 -0
  158. package/dist/features/categories/repository/categories.repository.js +83 -0
  159. package/dist/features/categories/schemas/firestore.d.ts +59 -2
  160. package/dist/features/categories/schemas/firestore.js +6 -0
  161. package/dist/features/categories/types/index.d.ts +11 -3
  162. package/dist/features/events/migrations.d.ts +2 -0
  163. package/dist/features/events/migrations.js +10 -0
  164. package/dist/features/faq/migrations.d.ts +2 -0
  165. package/dist/features/faq/migrations.js +10 -0
  166. package/dist/features/grouped/migrations.d.ts +2 -0
  167. package/dist/features/grouped/migrations.js +10 -0
  168. package/dist/features/grouped/schemas/firestore.d.ts +29 -10
  169. package/dist/features/grouped/schemas/firestore.js +10 -5
  170. package/dist/features/history/migrations.d.ts +2 -0
  171. package/dist/features/history/migrations.js +10 -0
  172. package/dist/features/homepage/hooks/useFeaturedAuctions.js +2 -2
  173. package/dist/features/homepage/hooks/useFeaturedPreOrders.js +2 -2
  174. package/dist/features/homepage/lib/section-renderer.js +5 -3
  175. package/dist/features/media/AvatarUpload.js +6 -28
  176. package/dist/features/media/hooks/useMedia.d.ts +31 -15
  177. package/dist/features/media/hooks/useMedia.js +48 -13
  178. package/dist/features/media/upload/ImageUpload.js +1 -1
  179. package/dist/features/media/upload/MediaUploadField.js +1 -1
  180. package/dist/features/messages/migrations.d.ts +2 -0
  181. package/dist/features/messages/migrations.js +10 -0
  182. package/dist/features/orders/components/OrdersList.js +10 -1
  183. package/dist/features/orders/migrations.d.ts +2 -0
  184. package/dist/features/orders/migrations.js +10 -0
  185. package/dist/features/orders/repository/orders.repository.d.ts +16 -0
  186. package/dist/features/orders/repository/orders.repository.js +49 -0
  187. package/dist/features/orders/schemas/firestore.d.ts +8 -0
  188. package/dist/features/orders/types/index.d.ts +12 -0
  189. package/dist/features/orders/utils/order-splitter.d.ts +2 -2
  190. package/dist/features/orders/utils/order-splitter.js +5 -0
  191. package/dist/features/payments/migrations.d.ts +2 -0
  192. package/dist/features/payments/migrations.js +10 -0
  193. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -1
  194. package/dist/features/products/actions/product-actions.d.ts +1 -1
  195. package/dist/features/products/api/[id]/route.js +34 -0
  196. package/dist/features/products/api/route.js +1 -19
  197. package/dist/features/products/components/CompareOverlay.d.ts +1 -1
  198. package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +24 -0
  199. package/dist/features/products/components/MarketplacePrizeDrawCard.js +102 -0
  200. package/dist/features/products/components/PrizeDrawCollage.d.ts +32 -0
  201. package/dist/features/products/components/PrizeDrawCollage.js +22 -0
  202. package/dist/features/products/components/PrizeDrawDetailPageView.d.ts +27 -0
  203. package/dist/features/products/components/PrizeDrawDetailPageView.js +118 -0
  204. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +19 -0
  205. package/dist/features/products/components/PrizeDrawEntryActions.js +48 -0
  206. package/dist/features/products/components/PrizeDrawItemsEditor.d.ts +13 -0
  207. package/dist/features/products/components/PrizeDrawItemsEditor.js +97 -0
  208. package/dist/features/products/components/PrizeDrawsIndexListing.d.ts +8 -0
  209. package/dist/features/products/components/PrizeDrawsIndexListing.js +128 -0
  210. package/dist/features/products/components/PrizeDrawsListingView.d.ts +15 -0
  211. package/dist/features/products/components/PrizeDrawsListingView.js +49 -0
  212. package/dist/features/products/components/PrizeRevealModal.d.ts +34 -0
  213. package/dist/features/products/components/PrizeRevealModal.js +124 -0
  214. package/dist/features/products/components/ProductDetailPageView.js +13 -1
  215. package/dist/features/products/components/ProductForm.js +35 -2
  216. package/dist/features/products/components/ProductGrid.js +3 -1
  217. package/dist/features/products/components/index.d.ts +16 -0
  218. package/dist/features/products/components/index.js +8 -0
  219. package/dist/features/products/constants/listing-tabs.d.ts +113 -0
  220. package/dist/features/products/constants/listing-tabs.js +43 -0
  221. package/dist/features/products/index.d.ts +1 -0
  222. package/dist/features/products/index.js +1 -0
  223. package/dist/features/products/migrations.d.ts +2 -0
  224. package/dist/features/products/migrations.js +10 -0
  225. package/dist/features/products/repository/products.repository.d.ts +11 -7
  226. package/dist/features/products/repository/products.repository.js +49 -24
  227. package/dist/features/products/schemas/firestore.d.ts +3 -3
  228. package/dist/features/products/schemas/firestore.js +2 -2
  229. package/dist/features/products/schemas/index.d.ts +5 -5
  230. package/dist/features/products/schemas/index.js +3 -1
  231. package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
  232. package/dist/features/products/types/index.d.ts +17 -1
  233. package/dist/features/products/utils/listing-type.d.ts +7 -4
  234. package/dist/features/products/utils/listing-type.js +8 -4
  235. package/dist/features/promotions/actions/coupon-actions.d.ts +1 -1
  236. package/dist/features/promotions/hooks/useCouponValidate.d.ts +1 -1
  237. package/dist/features/promotions/migrations.d.ts +2 -0
  238. package/dist/features/promotions/migrations.js +10 -0
  239. package/dist/features/promotions/repository/coupons.repository.d.ts +1 -1
  240. package/dist/features/promotions/schemas/index.d.ts +2 -2
  241. package/dist/features/reviews/migrations.d.ts +2 -0
  242. package/dist/features/reviews/migrations.js +10 -0
  243. package/dist/features/scams/migrations.d.ts +2 -0
  244. package/dist/features/scams/migrations.js +10 -0
  245. package/dist/features/search/api/route.d.ts +1 -1
  246. package/dist/features/search/api/route.js +3 -3
  247. package/dist/features/search/components/Search.d.ts +1 -1
  248. package/dist/features/search/schemas/index.d.ts +3 -3
  249. package/dist/features/search/schemas/index.js +3 -1
  250. package/dist/features/search/types/index.d.ts +2 -2
  251. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  252. package/dist/features/seller/components/SellerProductsView.js +20 -6
  253. package/dist/features/seller/migrations.d.ts +2 -0
  254. package/dist/features/seller/migrations.js +10 -0
  255. package/dist/features/seller/schemas/index.d.ts +2 -2
  256. package/dist/features/stores/components/StoreBundlesPageView.d.ts +12 -0
  257. package/dist/features/stores/components/StoreBundlesPageView.js +24 -0
  258. package/dist/features/stores/components/StoreDetailLayoutView.js +15 -3
  259. package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +11 -0
  260. package/dist/features/stores/components/StorePrizeDrawsPageView.js +27 -0
  261. package/dist/features/stores/components/index.d.ts +2 -0
  262. package/dist/features/stores/migrations.d.ts +2 -0
  263. package/dist/features/stores/migrations.js +10 -0
  264. package/dist/features/stores/schemas/index.d.ts +2 -2
  265. package/dist/features/stores/types/index.d.ts +1 -1
  266. package/dist/features/sublisting/migrations.d.ts +2 -0
  267. package/dist/features/sublisting/migrations.js +10 -0
  268. package/dist/features/sublisting/schemas/firestore.d.ts +2 -0
  269. package/dist/features/support/migrations.d.ts +2 -0
  270. package/dist/features/support/migrations.js +10 -0
  271. package/dist/features/wishlist/migrations.d.ts +2 -0
  272. package/dist/features/wishlist/migrations.js +10 -0
  273. package/dist/features/wishlist/types/index.d.ts +1 -1
  274. package/dist/index.d.ts +26 -18
  275. package/dist/index.js +41 -24
  276. package/dist/jobs.d.ts +1 -1
  277. package/dist/jobs.js +4 -0
  278. package/dist/next/routing/route-map.d.ts +4 -0
  279. package/dist/next/routing/route-map.js +2 -0
  280. package/dist/providers/db-firebase/admin-app-lite.js +21 -0
  281. package/dist/providers/db-firebase/filter-aliases.d.ts +2 -2
  282. package/dist/repositories/index.d.ts +0 -5
  283. package/dist/repositories/index.js +5 -4
  284. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  285. package/dist/seed/categories-seed-data.js +1105 -6
  286. package/dist/seed/faq-seed-data.js +160 -0
  287. package/dist/seed/grouped-listings-seed-data.js +32 -32
  288. package/dist/seed/homepage-sections-seed-data.js +52 -6
  289. package/dist/seed/index.d.ts +1 -3
  290. package/dist/seed/index.js +4 -3
  291. package/dist/seed/manifest.js +8 -13
  292. package/dist/seed/products-prize-draws-seed-data.d.ts +17 -0
  293. package/dist/seed/products-prize-draws-seed-data.js +313 -0
  294. package/dist/seo/json-ld.d.ts +1 -1
  295. package/dist/server-entry.d.ts +2 -2
  296. package/dist/server-entry.js +5 -3
  297. package/dist/server.d.ts +9 -2
  298. package/dist/server.js +11 -5
  299. package/dist/tailwind-utilities.css +1 -1
  300. package/dist/validation/schemas.d.ts +8 -8
  301. package/package.json +1 -1
  302. package/scripts/seed-cli.mjs +2 -4
@@ -8,19 +8,64 @@
8
8
  */
9
9
  import { ApiError, ValidationError, NotFoundError, ERROR_MESSAGES } from "../../../../errors";
10
10
  import { serverLogger } from "../../../../monitoring";
11
- import { unitOfWork, siteSettingsRepository, userRepository, storeRepository, couponsRepository, } from "../../../../repositories";
11
+ import { unitOfWork, siteSettingsRepository, userRepository, storeRepository, couponsRepository, notificationRepository, } from "../../../../repositories";
12
12
  import { failedCheckoutRepository } from "../../../../features/checkout/repository/failed-checkout.repository";
13
13
  import { sendOrderConfirmationEmail } from "../../../../features/contact/server";
14
14
  import { splitCartIntoOrderGroups } from "../../../../features/orders/index";
15
15
  import { resolveDate } from "../../../../utils";
16
- import { getAdminDb } from "../../../../providers/db-firebase";
16
+ import { getAdminDb, getAdminRealtimeDb, RTDB_PATHS, } from "../../../../providers/db-firebase";
17
17
  import { PRODUCT_COLLECTION, ProductStatusValues } from "../../../../features/products/schemas/firestore";
18
18
  import { CART_COLLECTION } from "../../../../features/cart/schemas/index";
19
19
  import { consentOtpRef, consentOtpRateLimitRef, CONSENT_OTP_MAX_BYPASS_CREDITS, } from "../../../../features/auth/server";
20
- import { OrderStatusValues, PaymentStatusValues } from "../../../../features/orders/schemas/index";
20
+ import { OrderStatusValues, PaymentStatusValues, PaymentMethodValues, } from "../../../../features/orders/schemas/index";
21
21
  import { getDefaultCurrency } from "../../../../core/index";
22
+ import { verifyPaymentSignatureWithKeys, fetchRazorpayOrder, paiseToRupees, } from "../../../../providers/payment-razorpay/index";
22
23
  import { CHECKOUT_DEFAULT_COMMISSIONS } from "../../../shared/features/checkout/config";
23
24
  import { formatShippingAddress } from "./data";
25
+ import { enforceMaxPerUserForCart, enforcePrizePoolCap, computePrizeRevealDeadline, } from "./prize-bundle-gates";
26
+ /**
27
+ * Fire-and-forget in-app notifications when an order is created.
28
+ *
29
+ * The Cloud Function `onOrderStatusChange` only fires on status transitions
30
+ * (not on creates), so a brand-new order never produces an in-app row for
31
+ * either party. We emit the buyer + seller rows here at the create boundary.
32
+ */
33
+ function emitOrderPlacedNotifications(args) {
34
+ const { orderId, buyerUid, buyerName, storeOwnerId, productLabel, paid } = args;
35
+ const buyerNotif = notificationRepository
36
+ .create({
37
+ userId: buyerUid,
38
+ type: "order_placed",
39
+ priority: "normal",
40
+ title: "Order placed",
41
+ message: `Your order for ${productLabel} has been placed.`,
42
+ relatedId: orderId,
43
+ relatedType: "order",
44
+ actionUrl: `/user/orders/view/${orderId}`,
45
+ })
46
+ .catch((err) => serverLogger.warn("Failed to create buyer order_placed notification", {
47
+ err,
48
+ orderId,
49
+ }));
50
+ const sellerNotif = storeOwnerId
51
+ ? notificationRepository
52
+ .create({
53
+ userId: storeOwnerId,
54
+ type: "order_placed",
55
+ priority: "high",
56
+ title: "New order received",
57
+ message: `${paid ? "New paid order" : "New order"} from ${buyerName || "a buyer"} for ${productLabel}.`,
58
+ relatedId: orderId,
59
+ relatedType: "order",
60
+ actionUrl: `/store/orders/${orderId}/view`,
61
+ })
62
+ .catch((err) => serverLogger.warn("Failed to create seller order_placed notification", {
63
+ err,
64
+ orderId,
65
+ }))
66
+ : Promise.resolve();
67
+ void Promise.all([buyerNotif, sellerNotif]);
68
+ }
24
69
  /**
25
70
  * Place order(s) from the user's cart in a single Firestore transaction.
26
71
  *
@@ -54,6 +99,22 @@ export async function createCheckoutOrderAction(input) {
54
99
  const shippingAddress = formatShippingAddress(address);
55
100
  const db = getAdminDb();
56
101
  const otpRef = consentOtpRef(db, uid, addressId);
102
+ // SB6-C — pre-tx fetch products so we can run the maxPerUser cap check
103
+ // (count queries can't run inside a Firestore transaction). The same
104
+ // product docs are re-read inside the transaction below for stock + prize
105
+ // pool checks; this is intentional (the tx is the source of truth for the
106
+ // counts we actually mutate).
107
+ const preTxProductRefs = cartItems.map((item) => db.collection(PRODUCT_COLLECTION).doc(item.productId));
108
+ const preTxProductSnaps = await Promise.all(preTxProductRefs.map((r) => r.get()));
109
+ const preTxPairs = cartItems
110
+ .map((item, i) => {
111
+ const data = preTxProductSnaps[i].exists
112
+ ? preTxProductSnaps[i].data()
113
+ : undefined;
114
+ return data ? { item, product: data } : null;
115
+ })
116
+ .filter((pair) => pair !== null);
117
+ await enforceMaxPerUserForCart({ userId: uid, items: preTxPairs });
57
118
  let stockResult;
58
119
  try {
59
120
  stockResult = await db.runTransaction(async (tx) => {
@@ -88,10 +149,20 @@ export async function createCheckoutOrderAction(input) {
88
149
  });
89
150
  }
90
151
  else {
91
- tx.update(productRefs[i], {
152
+ // SB6-C — prize-draw pool cap (in-tx, atomic with availableQuantity).
153
+ enforcePrizePoolCap({
154
+ productSnapshot: productData,
155
+ requestedQuantity: item.quantity,
156
+ });
157
+ const productUpdate = {
92
158
  availableQuantity: productData.availableQuantity - item.quantity,
93
159
  updatedAt: new Date(),
94
- });
160
+ };
161
+ if (productData.listingType === "prize-draw") {
162
+ productUpdate.prizeCurrentEntries =
163
+ (productData.prizeCurrentEntries ?? 0) + item.quantity;
164
+ }
165
+ tx.update(productRefs[i], productUpdate);
95
166
  availableItems.push({ item, product: productData });
96
167
  }
97
168
  }
@@ -146,19 +217,38 @@ export async function createCheckoutOrderAction(input) {
146
217
  const firstItem = group[0].item;
147
218
  const groupTotal = group.reduce((sum, { item }) => sum + item.price * item.quantity, 0);
148
219
  total += groupTotal;
149
- const orderItems = group.map(({ item }) => ({
150
- productId: item.productId,
151
- productTitle: item.productTitle,
152
- quantity: item.quantity,
153
- unitPrice: item.price,
154
- totalPrice: item.price * item.quantity,
155
- }));
220
+ // SB8-F when a line is a prize-draw entry, stamp the listingType +
221
+ // reveal status + deadline so the user-orders surface can render the
222
+ // reveals-remaining badge immediately after order creation.
223
+ const groupRevealDeadline = orderType === "prize-draw"
224
+ ? computePrizeRevealDeadline(group[0].product)
225
+ : undefined;
226
+ const orderItems = group.map(({ item, product }) => {
227
+ const isPrizeDrawLine = product.listingType === "prize-draw";
228
+ return {
229
+ productId: item.productId,
230
+ productTitle: item.productTitle,
231
+ quantity: item.quantity,
232
+ unitPrice: item.price,
233
+ totalPrice: item.price * item.quantity,
234
+ ...(isPrizeDrawLine
235
+ ? {
236
+ listingType: "prize-draw",
237
+ prizeRevealStatus: product.prizeRevealStatus === "open" ? "open" : "pending",
238
+ prizeRevealDeadline: groupRevealDeadline?.toISOString(),
239
+ }
240
+ : product.listingType
241
+ ? { listingType: product.listingType }
242
+ : {}),
243
+ };
244
+ });
156
245
  const totalQuantity = group.reduce((sum, { item }) => sum + item.quantity, 0);
157
246
  let shippingFee = 0;
247
+ let storeOwnerId;
158
248
  const storeId = firstItem.storeId;
159
249
  if (storeId) {
160
250
  const store = await storeRepository.findById(storeId);
161
- const storeOwnerId = store?.ownerId;
251
+ storeOwnerId = store?.ownerId;
162
252
  if (storeOwnerId) {
163
253
  const sellerUser = await userRepository.findById(storeOwnerId);
164
254
  const shippingConfig = sellerUser?.shippingConfig;
@@ -237,6 +327,17 @@ export async function createCheckoutOrderAction(input) {
237
327
  .map(({ product }) => product.mainImage)
238
328
  .filter((url) => typeof url === "string" && url.length > 0)),
239
329
  ];
330
+ // SB-UNI-D — "bundle" order-type removed; bundle cart lines will expand
331
+ // to N product order lines at checkout (forward-looking feature, not
332
+ // wired yet). Prize-draw fields only.
333
+ const isPrizeDrawOrder = orderType === "prize-draw";
334
+ const prizeDrawFields = isPrizeDrawOrder
335
+ ? {
336
+ prizeDrawProductId: group[0].product.id,
337
+ isNonRefundable: true,
338
+ prizeRevealDeadline: computePrizeRevealDeadline(group[0].product),
339
+ }
340
+ : {};
240
341
  const order = await unitOfWork.orders.create({
241
342
  productId: firstItem.productId,
242
343
  productTitle: firstItem.productTitle,
@@ -264,6 +365,7 @@ export async function createCheckoutOrderAction(input) {
264
365
  couponDiscount: couponDiscount > 0 ? couponDiscount : undefined,
265
366
  appliedDiscounts: appliedDiscounts.length > 0 ? appliedDiscounts : undefined,
266
367
  imageUrls: imageUrls.length > 0 ? imageUrls : undefined,
368
+ ...prizeDrawFields,
267
369
  });
268
370
  orderIds.push(order.id);
269
371
  for (const code of groupCouponCodes) {
@@ -285,6 +387,14 @@ export async function createCheckoutOrderAction(input) {
285
387
  items: orderItems,
286
388
  });
287
389
  }
390
+ emitOrderPlacedNotifications({
391
+ orderId: order.id,
392
+ buyerUid: uid,
393
+ buyerName: userName,
394
+ storeOwnerId,
395
+ productLabel: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
396
+ paid: false,
397
+ });
288
398
  }
289
399
  if (couponUsageAccumulator.size > 0) {
290
400
  Promise.all([...couponUsageAccumulator.values()].map(({ couponId, code, orderIds: ids, totalDiscount }) => couponsRepository.applyCoupon(couponId, code, uid, ids, totalDiscount))).catch((err) => serverLogger.error("Failed to record coupon usage:", err));
@@ -325,3 +435,322 @@ export async function attachPaymentAction(input) {
325
435
  updatedAt: new Date(),
326
436
  });
327
437
  }
438
+ /**
439
+ * Place order(s) from the user's cart after a Razorpay payment is verified.
440
+ * Mirrors the existing /api/payment/verify route handler.
441
+ *
442
+ * Consumers must authenticate the user before calling. The action performs:
443
+ * 1. HMAC signature verification (rejects forged callbacks)
444
+ * 2. Cart re-validation against current product prices/stock
445
+ * 3. Amount cross-check against the Razorpay order record
446
+ * 4. Atomic stock decrement + cart clear via unitOfWork batch
447
+ * 5. Multi-coupon pro-rating per order group
448
+ * 6. order_placed notifications (buyer + seller)
449
+ * 7. Confirmation email + RTDB success signal (both fire-and-forget)
450
+ */
451
+ export async function verifyAndPlaceRazorpayOrderAction(input) {
452
+ const { userId: uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, addressId, notes, } = input;
453
+ const siteSettings = await siteSettingsRepository.getSingleton();
454
+ const razorpayFeePercent = siteSettings?.commissions?.razorpayFeePercent ?? 5;
455
+ const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
456
+ const isValid = await verifyPaymentSignatureWithKeys({
457
+ razorpay_order_id,
458
+ razorpay_payment_id,
459
+ razorpay_signature,
460
+ });
461
+ if (!isValid) {
462
+ serverLogger.warn(`Payment signature verification failed for user ${uid}`);
463
+ failedCheckoutRepository
464
+ .logPayment(uid, "signature_mismatch", "HMAC signature invalid", {
465
+ gatewayOrderId: razorpay_order_id,
466
+ gatewayPaymentId: razorpay_payment_id,
467
+ addressId,
468
+ })
469
+ .catch(() => { });
470
+ throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PAYMENT_FAILED);
471
+ }
472
+ const cart = await unitOfWork.carts.getOrCreate(uid);
473
+ if (!cart.items || cart.items.length === 0) {
474
+ throw new ValidationError(ERROR_MESSAGES.CHECKOUT.CART_EMPTY);
475
+ }
476
+ const address = await unitOfWork.addresses.findById(uid, addressId);
477
+ if (!address) {
478
+ throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
479
+ }
480
+ const shippingAddress = formatShippingAddress(address);
481
+ const db = getAdminDb();
482
+ const otpRef = consentOtpRef(db, uid, addressId);
483
+ {
484
+ const otpSnap = await otpRef.get();
485
+ const otpData = otpSnap.exists
486
+ ? otpSnap.data()
487
+ : null;
488
+ const isConsentValid = otpData?.verified === true &&
489
+ otpData.expiresAt &&
490
+ (resolveDate(otpData.expiresAt)?.getTime() ?? 0) > Date.now();
491
+ if (!isConsentValid) {
492
+ const reason = !otpData ? "otp_not_verified" : "consent_expired";
493
+ failedCheckoutRepository
494
+ .logPayment(uid, reason, "Consent OTP missing or expired at payment verify time", {
495
+ gatewayOrderId: razorpay_order_id,
496
+ gatewayPaymentId: razorpay_payment_id,
497
+ addressId,
498
+ })
499
+ .catch(() => { });
500
+ throw new ApiError(403, "Order verification required. Please complete OTP verification and retry.");
501
+ }
502
+ }
503
+ const productChecks = await Promise.all(cart.items.map(async (item) => {
504
+ const product = await unitOfWork.products.findById(item.productId);
505
+ return { item, product };
506
+ }));
507
+ // SB6-C — maxPerUser cap check before we touch any state.
508
+ await enforceMaxPerUserForCart({
509
+ userId: uid,
510
+ items: productChecks
511
+ .filter((p) => p.product !== null && p.product !== undefined)
512
+ .map(({ item, product }) => ({ item, product })),
513
+ });
514
+ // SB6-C — prize-pool cap. Runs against the freshly-read product snapshots.
515
+ for (const { item, product } of productChecks) {
516
+ if (!product)
517
+ continue;
518
+ enforcePrizePoolCap({
519
+ productSnapshot: product,
520
+ requestedQuantity: item.quantity,
521
+ });
522
+ }
523
+ for (const { item, product } of productChecks) {
524
+ if (!product || product.status !== ProductStatusValues.PUBLISHED) {
525
+ failedCheckoutRepository
526
+ .logPayment(uid, "product_unavailable", `Product ${item.productId} not published`, {
527
+ gatewayOrderId: razorpay_order_id,
528
+ gatewayPaymentId: razorpay_payment_id,
529
+ addressId,
530
+ })
531
+ .catch(() => { });
532
+ throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PRODUCT_UNAVAILABLE);
533
+ }
534
+ if (product.availableQuantity < item.quantity) {
535
+ failedCheckoutRepository
536
+ .logPayment(uid, "stock_insufficient", `Product ${item.productId} has ${product.availableQuantity} left, requested ${item.quantity}`, {
537
+ gatewayOrderId: razorpay_order_id,
538
+ gatewayPaymentId: razorpay_payment_id,
539
+ addressId,
540
+ })
541
+ .catch(() => { });
542
+ throw new ValidationError(ERROR_MESSAGES.CHECKOUT.INSUFFICIENT_STOCK);
543
+ }
544
+ }
545
+ {
546
+ const cartSubtotalRs = productChecks.reduce((sum, { item, product }) => sum + product.price * item.quantity, 0);
547
+ const expectedPlatformFee = Math.round(cartSubtotalRs * (razorpayFeePercent / 100) * 100) / 100;
548
+ const expectedPaymentAmountRs = cartSubtotalRs + expectedPlatformFee;
549
+ const rzpOrderRecord = await fetchRazorpayOrder(razorpay_order_id);
550
+ const paidAmountRs = paiseToRupees(rzpOrderRecord.amount);
551
+ if (paidAmountRs < expectedPaymentAmountRs - 1) {
552
+ serverLogger.warn(`Payment amount mismatch for user ${uid}: paid ₹${paidAmountRs}, expected ≥ ₹${expectedPaymentAmountRs}`);
553
+ failedCheckoutRepository
554
+ .logPayment(uid, "amount_mismatch", `Paid ₹${paidAmountRs}, expected ≥ ₹${expectedPaymentAmountRs}`, {
555
+ gatewayOrderId: razorpay_order_id,
556
+ gatewayPaymentId: razorpay_payment_id,
557
+ amountRs: paidAmountRs,
558
+ addressId,
559
+ })
560
+ .catch(() => { });
561
+ throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PAYMENT_FAILED);
562
+ }
563
+ }
564
+ const appliedCoupons = cart.appliedCoupons ?? [];
565
+ const orderGroups = splitCartIntoOrderGroups(productChecks);
566
+ const orderIds = [];
567
+ let total = 0;
568
+ const emailsToSend = [];
569
+ const cartSubtotal = orderGroups.reduce((s, { items: g }) => s +
570
+ g.reduce((gs, { item, product }) => gs + product.price * item.quantity, 0), 0);
571
+ for (const { items: group, orderType } of orderGroups) {
572
+ const firstItem = group[0].item;
573
+ const groupTotal = group.reduce((sum, { item, product }) => sum + product.price * item.quantity, 0);
574
+ let shippingFee = 0;
575
+ let storeOwnerId;
576
+ const storeId = firstItem.storeId;
577
+ if (storeId) {
578
+ const store = await storeRepository.findById(storeId);
579
+ storeOwnerId = store?.ownerId;
580
+ const sellerUser = storeOwnerId ? await userRepository.findById(storeOwnerId) : null;
581
+ const shippingConfig = sellerUser?.shippingConfig;
582
+ if (shippingConfig?.isConfigured) {
583
+ if (shippingConfig.method === "custom") {
584
+ shippingFee = shippingConfig.customShippingPrice ?? 0;
585
+ }
586
+ else if (shippingConfig.method === "shiprocket") {
587
+ const percentFee = groupTotal * (commissions.platformShippingPercent / 100);
588
+ shippingFee = Math.max(percentFee, commissions.platformShippingFixedMin);
589
+ }
590
+ }
591
+ }
592
+ let couponDiscount = 0;
593
+ const appliedDiscounts = [];
594
+ for (const coupon of appliedCoupons) {
595
+ let couponGroupDiscount = 0;
596
+ const isSellerScoped = coupon.scope === "seller" && coupon.storeId;
597
+ if (isSellerScoped) {
598
+ if (coupon.storeId !== firstItem.storeId)
599
+ continue;
600
+ if (coupon.applicableItemIds?.length) {
601
+ const eligibleTotal = group
602
+ .filter(({ item }) => coupon.applicableItemIds.includes(item.itemId))
603
+ .reduce((s, { item, product }) => s + product.price * item.quantity, 0);
604
+ couponGroupDiscount =
605
+ eligibleTotal > 0
606
+ ? Math.min(Math.round((eligibleTotal / groupTotal) * coupon.discountAmount * 100) / 100, eligibleTotal)
607
+ : 0;
608
+ }
609
+ else {
610
+ couponGroupDiscount = Math.min(coupon.discountAmount, groupTotal);
611
+ }
612
+ }
613
+ else if (cartSubtotal > 0) {
614
+ couponGroupDiscount = Math.min(Math.round((groupTotal / cartSubtotal) * coupon.discountAmount * 100) / 100, groupTotal);
615
+ }
616
+ if (couponGroupDiscount > 0) {
617
+ couponDiscount += couponGroupDiscount;
618
+ appliedDiscounts.push({
619
+ code: coupon.code,
620
+ couponId: coupon.couponId,
621
+ type: "coupon",
622
+ discountAmount: couponGroupDiscount,
623
+ scope: coupon.scope,
624
+ storeId: coupon.storeId,
625
+ });
626
+ }
627
+ }
628
+ couponDiscount = Math.min(couponDiscount, groupTotal);
629
+ const platformFee = Math.round(groupTotal * (razorpayFeePercent / 100) * 100) / 100;
630
+ const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee;
631
+ total += orderTotal;
632
+ // SB8-F — stamp listingType + reveal-status + deadline for prize-draw lines.
633
+ const groupRevealDeadlineRzp = orderType === "prize-draw" && group[0].product
634
+ ? computePrizeRevealDeadline(group[0].product)
635
+ : undefined;
636
+ const orderItems = group.map(({ item, product }) => {
637
+ const isPrizeDrawLine = product?.listingType === "prize-draw";
638
+ return {
639
+ productId: item.productId,
640
+ productTitle: item.productTitle,
641
+ quantity: item.quantity,
642
+ unitPrice: product.price,
643
+ totalPrice: product.price * item.quantity,
644
+ ...(isPrizeDrawLine
645
+ ? {
646
+ listingType: "prize-draw",
647
+ prizeRevealStatus: product?.prizeRevealStatus === "open"
648
+ ? "open"
649
+ : "pending",
650
+ prizeRevealDeadline: groupRevealDeadlineRzp?.toISOString(),
651
+ }
652
+ : product?.listingType
653
+ ? { listingType: product.listingType }
654
+ : {}),
655
+ };
656
+ });
657
+ const totalQuantity = group.reduce((sum, { item }) => sum + item.quantity, 0);
658
+ const imageUrls = [
659
+ ...new Set(group
660
+ .map(({ product }) => product?.mainImage)
661
+ .filter((url) => typeof url === "string" && url.length > 0)),
662
+ ];
663
+ // SB-UNI-D — "bundle" order-type removed; bundle cart lines will expand
664
+ // to N product order lines at checkout (forward-looking, not wired).
665
+ const isPrizeDrawOrder = orderType === "prize-draw";
666
+ const prizeDrawFields = isPrizeDrawOrder && group[0].product
667
+ ? {
668
+ prizeDrawProductId: group[0].product.id,
669
+ isNonRefundable: true,
670
+ prizeRevealDeadline: computePrizeRevealDeadline(group[0].product),
671
+ }
672
+ : {};
673
+ const order = await unitOfWork.orders.create({
674
+ productId: firstItem.productId,
675
+ productTitle: firstItem.productTitle,
676
+ userId: uid,
677
+ userName,
678
+ userEmail,
679
+ quantity: totalQuantity,
680
+ unitPrice: group[0].product.price,
681
+ totalPrice: orderTotal,
682
+ currency: firstItem.currency ?? getDefaultCurrency(),
683
+ storeId: firstItem.storeId || undefined,
684
+ storeName: firstItem.storeName || undefined,
685
+ items: orderItems,
686
+ orderType,
687
+ offerId: firstItem.offerId ?? undefined,
688
+ status: OrderStatusValues.CONFIRMED,
689
+ paymentStatus: PaymentStatusValues.PAID,
690
+ paymentMethod: PaymentMethodValues.ONLINE,
691
+ paymentId: razorpay_payment_id,
692
+ shippingAddress,
693
+ notes,
694
+ platformFee,
695
+ shippingFee: shippingFee > 0 ? shippingFee : undefined,
696
+ couponCode: appliedDiscounts[0]?.code,
697
+ couponDiscount: couponDiscount > 0 ? couponDiscount : undefined,
698
+ appliedDiscounts: appliedDiscounts.length > 0 ? appliedDiscounts : undefined,
699
+ imageUrls: imageUrls.length > 0 ? imageUrls : undefined,
700
+ ...prizeDrawFields,
701
+ });
702
+ orderIds.push(order.id);
703
+ if (userEmail) {
704
+ emailsToSend.push({
705
+ to: userEmail,
706
+ userName,
707
+ orderId: order.id,
708
+ productTitle: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
709
+ quantity: totalQuantity,
710
+ totalPrice: orderTotal,
711
+ currency: firstItem.currency ?? getDefaultCurrency(),
712
+ shippingAddress,
713
+ paymentMethod: PaymentMethodValues.ONLINE,
714
+ items: orderItems,
715
+ });
716
+ }
717
+ emitOrderPlacedNotifications({
718
+ orderId: order.id,
719
+ buyerUid: uid,
720
+ buyerName: userName,
721
+ storeOwnerId,
722
+ productLabel: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
723
+ paid: true,
724
+ });
725
+ }
726
+ await unitOfWork.runBatch((batch) => {
727
+ for (const { item, product } of productChecks) {
728
+ if (!product)
729
+ continue;
730
+ const prizeBump = product.listingType === "prize-draw"
731
+ ? {
732
+ prizeCurrentEntries: (product.prizeCurrentEntries ?? 0) + item.quantity,
733
+ }
734
+ : {};
735
+ unitOfWork.products.updateInBatch(batch, item.productId, {
736
+ availableQuantity: product.availableQuantity - item.quantity,
737
+ ...prizeBump,
738
+ });
739
+ }
740
+ unitOfWork.carts.updateInBatch(batch, uid, {
741
+ items: [],
742
+ appliedCoupons: [],
743
+ selectedItemIds: null,
744
+ });
745
+ });
746
+ otpRef.delete().catch(() => { });
747
+ if (emailsToSend.length > 0) {
748
+ Promise.all(emailsToSend.map((e) => sendOrderConfirmationEmail(e))).catch((err) => serverLogger.error("Order confirmation email error:", err));
749
+ }
750
+ serverLogger.info(`verifyAndPlaceRazorpayOrderAction: ${orderIds.length} order(s) placed for uid=${uid} — payment ${razorpay_payment_id}`);
751
+ getAdminRealtimeDb()
752
+ .ref(`${RTDB_PATHS.PAYMENT_EVENTS}/${razorpay_order_id}`)
753
+ .update({ status: "success", orderIds, updatedAt: Date.now() })
754
+ .catch((err) => serverLogger.warn("Payment event RTDB signal failed (non-critical)", { err }));
755
+ return { orderIds, total, itemCount: orderIds.length };
756
+ }
@@ -1,3 +1,3 @@
1
- export { createCheckoutOrderAction, attachPaymentAction, type CreateCheckoutOrderInput, } from "./actions";
1
+ export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlaceRazorpayOrderAction, type CreateCheckoutOrderInput, type VerifyAndPlaceRazorpayOrderInput, } from "./actions";
2
2
  export { formatShippingAddress, type CheckoutOrderResult, } from "./data";
3
3
  export { CHECKOUT_DEFAULT_COMMISSIONS, CHECKOUT_PAYMENT_METHODS, type CheckoutPaymentMethod, } from "../../../shared/features/checkout/config";
@@ -1,3 +1,3 @@
1
- export { createCheckoutOrderAction, attachPaymentAction, } from "./actions";
1
+ export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlaceRazorpayOrderAction, } from "./actions";
2
2
  export { formatShippingAddress, } from "./data";
3
3
  export { CHECKOUT_DEFAULT_COMMISSIONS, CHECKOUT_PAYMENT_METHODS, } from "../../../shared/features/checkout/config";
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Prize-draw + bundle + maxPerUser gates for the checkout pipeline (SB6-C, SB8-A).
3
+ *
4
+ * Shared helper used by both `createCheckoutOrderAction` (COD/UPI-manual path)
5
+ * and `verifyAndPlaceRazorpayOrderAction` (Razorpay path). Pulled out here so
6
+ * the two transactional flows enforce the same rules.
7
+ */
8
+ import type { ProductDocument } from "../../../../features/products/schemas/firestore";
9
+ import type { CartItemDocument } from "../../../../features/cart/schemas/firestore";
10
+ export interface PerUserCapViolation {
11
+ productId: string;
12
+ productTitle: string;
13
+ allowance: number;
14
+ alreadyPurchased: number;
15
+ requested: number;
16
+ }
17
+ /**
18
+ * Pre-transaction read: for every cart item whose product has `maxPerUser`
19
+ * set, count the user's existing active orders and reject the checkout if
20
+ * the new request would push them over the cap.
21
+ *
22
+ * Active = pending / confirmed / processing / shipped / delivered. Cancelled
23
+ * + refunded orders do NOT count (stock was returned to circulation).
24
+ *
25
+ * Throws ValidationError with code `MAX_PER_USER` listing every violating
26
+ * product. The check is informational-rich on purpose — the UI surfaces all
27
+ * blocked items at once instead of revealing them one at a time.
28
+ */
29
+ export declare function enforceMaxPerUserForCart(args: {
30
+ userId: string;
31
+ items: Array<{
32
+ item: CartItemDocument;
33
+ product: ProductDocument;
34
+ }>;
35
+ }): Promise<void>;
36
+ /**
37
+ * SB8-A — compute the deadline by which the buyer must claim their prize
38
+ * via the reveal API.
39
+ *
40
+ * Rules:
41
+ * - If the reveal window has not yet opened:
42
+ * deadline = min(revealWindowStart + revealDeadlineDays, revealWindowEnd)
43
+ * - If the window is already open:
44
+ * deadline = min(now + revealDeadlineDays, revealWindowEnd)
45
+ *
46
+ * Returns `undefined` if the product lacks the prize-draw fields.
47
+ */
48
+ export declare function computePrizeRevealDeadline(product: Pick<ProductDocument, "prizeRevealWindowStart" | "prizeRevealWindowEnd" | "prizeRevealDeadlineDays">, now?: Date): Date | undefined;
49
+ /**
50
+ * Inside the existing checkout transaction, validate the prize-pool cap for
51
+ * each prize-draw line. Caller must pass the fresh product snapshot read
52
+ * within the transaction (so `prizeCurrentEntries` is up-to-date).
53
+ *
54
+ * Throws ValidationError if any item would overflow the pool.
55
+ */
56
+ export declare function enforcePrizePoolCap(args: {
57
+ productSnapshot: ProductDocument;
58
+ requestedQuantity: number;
59
+ }): void;