@mohasinac/appkit 4.59.0 → 4.60.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 (531) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +62 -5
  3. package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
  4. package/dist/_internal/server/features/brands/actions.js +78 -4
  5. package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
  6. package/dist/_internal/server/features/checkout/actions.js +668 -183
  7. package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
  8. package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
  9. package/dist/_internal/server/features/gateway-events/index.js +10 -0
  10. package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
  11. package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
  12. package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
  13. package/dist/_internal/server/features/gateway-events/redact.js +151 -0
  14. package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
  15. package/dist/_internal/server/features/grievance/actions.js +173 -0
  16. package/dist/_internal/server/features/grievance/index.d.ts +8 -0
  17. package/dist/_internal/server/features/grievance/index.js +8 -0
  18. package/dist/_internal/server/features/grouped/data.js +28 -3
  19. package/dist/_internal/server/features/lottery/actions.js +99 -4
  20. package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
  21. package/dist/_internal/server/features/order-completion/actions.js +336 -0
  22. package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
  23. package/dist/_internal/server/features/order-completion/index.js +7 -0
  24. package/dist/_internal/server/features/orders/actions.d.ts +13 -0
  25. package/dist/_internal/server/features/orders/actions.js +56 -2
  26. package/dist/_internal/server/features/orders/adapters.js +26 -0
  27. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
  28. package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
  29. package/dist/_internal/server/features/orders/index.d.ts +2 -1
  30. package/dist/_internal/server/features/orders/index.js +1 -1
  31. package/dist/_internal/server/features/orders/service.d.ts +60 -0
  32. package/dist/_internal/server/features/orders/service.js +74 -0
  33. package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
  34. package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
  35. package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
  36. package/dist/_internal/server/features/payment-cases/index.js +9 -0
  37. package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
  38. package/dist/_internal/server/features/payouts/actions.js +84 -10
  39. package/dist/_internal/server/features/points/adjust.d.ts +61 -0
  40. package/dist/_internal/server/features/points/adjust.js +121 -0
  41. package/dist/_internal/server/features/points/award.d.ts +65 -0
  42. package/dist/_internal/server/features/points/award.js +145 -0
  43. package/dist/_internal/server/features/price-index/market.d.ts +33 -0
  44. package/dist/_internal/server/features/price-index/market.js +54 -1
  45. package/dist/_internal/server/features/products/data.js +40 -22
  46. package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
  47. package/dist/_internal/server/features/protection-claims/actions.js +228 -0
  48. package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
  49. package/dist/_internal/server/features/protection-claims/index.js +8 -0
  50. package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
  51. package/dist/_internal/server/features/refunds/actions.js +259 -17
  52. package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
  53. package/dist/_internal/server/features/refunds/capacity.js +37 -0
  54. package/dist/_internal/server/features/reviews/actions.js +3 -2
  55. package/dist/_internal/server/features/search/index-build/sources.js +20 -1
  56. package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
  57. package/dist/_internal/server/features/shipping/reconcile.js +34 -3
  58. package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
  59. package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
  60. package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
  61. package/dist/_internal/server/features/stores/tabs.js +24 -4
  62. package/dist/_internal/server/functions/https.d.ts +31 -1
  63. package/dist/_internal/server/functions/https.js +52 -1
  64. package/dist/_internal/server/functions/scheduled.d.ts +72 -1
  65. package/dist/_internal/server/functions/scheduled.js +124 -1
  66. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
  67. package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
  68. package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
  69. package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
  70. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
  71. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
  72. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  73. package/dist/_internal/server/jobs/core/index.js +2 -0
  74. package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
  75. package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
  76. package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
  77. package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
  78. package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
  79. package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
  80. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
  81. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
  82. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
  83. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
  84. package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
  85. package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
  86. package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
  87. package/dist/_internal/server/jobs/handlers/index.js +5 -0
  88. package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
  89. package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
  90. package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
  91. package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
  92. package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
  93. package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
  94. package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
  95. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  96. package/dist/_internal/shared/actions/action-registry.js +71 -0
  97. package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
  98. package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
  99. package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
  100. package/dist/_internal/shared/checkout/lanes.js +46 -0
  101. package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
  102. package/dist/_internal/shared/checkout/order-math.js +251 -3
  103. package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
  104. package/dist/_internal/shared/checkout/refundable-base.js +122 -0
  105. package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
  106. package/dist/_internal/shared/features/auctions/config.js +30 -1
  107. package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
  108. package/dist/_internal/shared/features/brands/schema.js +18 -0
  109. package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
  110. package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
  111. package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
  112. package/dist/_internal/shared/features/print/label-profiles.js +350 -0
  113. package/dist/_internal/shared/features/products/to-product-item.js +33 -5
  114. package/dist/_internal/shared/fees/calculator.d.ts +31 -2
  115. package/dist/_internal/shared/fees/calculator.js +103 -5
  116. package/dist/_internal/shared/fees/india-states.d.ts +195 -0
  117. package/dist/_internal/shared/fees/india-states.js +190 -0
  118. package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
  119. package/dist/_internal/shared/fees/payout-hold.js +74 -0
  120. package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
  121. package/dist/_internal/shared/obligations/deadlines.js +155 -0
  122. package/dist/_internal/shared/shipping/quote.d.ts +96 -0
  123. package/dist/_internal/shared/shipping/quote.js +67 -0
  124. package/dist/_internal/shared/time/business-days.d.ts +91 -0
  125. package/dist/_internal/shared/time/business-days.js +185 -0
  126. package/dist/cli/index.js +0 -2
  127. package/dist/client.d.ts +9 -4
  128. package/dist/client.js +26 -3
  129. package/dist/configs/tailwind.d.ts +0 -12
  130. package/dist/configs/tailwind.js +28 -15
  131. package/dist/constants/api-endpoints.d.ts +51 -9
  132. package/dist/constants/api-endpoints.js +23 -7
  133. package/dist/constants/index.d.ts +1 -1
  134. package/dist/constants/index.js +1 -1
  135. package/dist/contracts/index.d.ts +1 -1
  136. package/dist/contracts/payment.d.ts +131 -0
  137. package/dist/features/about/components/AboutView.js +1 -1
  138. package/dist/features/about/components/DeveloperView.js +5 -2
  139. package/dist/features/about/components/FAQPageView.js +2 -1
  140. package/dist/features/about/components/FeesView.js +2 -1
  141. package/dist/features/about/components/HelpPageView.js +2 -1
  142. package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
  143. package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
  144. package/dist/features/about/components/HowEmiWorksView.js +2 -1
  145. package/dist/features/about/components/HowOffersWorkView.js +2 -1
  146. package/dist/features/about/components/HowOrdersWorkView.js +2 -1
  147. package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
  148. package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
  149. package/dist/features/about/components/HowReviewsWorkView.js +2 -1
  150. package/dist/features/about/components/PolicyPageView.js +2 -1
  151. package/dist/features/about/components/ProseHero.d.ts +73 -0
  152. package/dist/features/about/components/ProseHero.js +18 -0
  153. package/dist/features/about/components/SecurityPrivacyView.js +2 -1
  154. package/dist/features/about/components/ShippingPolicyView.js +2 -1
  155. package/dist/features/about/components/TrackOrderView.js +2 -1
  156. package/dist/features/about/components/UnauthorizedView.js +1 -1
  157. package/dist/features/about/index.d.ts +2 -0
  158. package/dist/features/about/index.js +5 -0
  159. package/dist/features/account/actions/address-actions.d.ts +26 -2
  160. package/dist/features/account/actions/address-actions.js +25 -1
  161. package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
  162. package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
  163. package/dist/features/account/components/UserOrdersView.js +17 -0
  164. package/dist/features/account/components/UserSidebar.js +1 -1
  165. package/dist/features/account/hooks/useNotifications.js +29 -0
  166. package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
  167. package/dist/features/admin/components/AdminBeysView.js +2 -2
  168. package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
  169. package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
  170. package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
  171. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
  172. package/dist/features/admin/components/AdminCategoriesView.js +1 -1
  173. package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
  174. package/dist/features/admin/components/AdminContactEditorView.js +1 -1
  175. package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
  176. package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
  177. package/dist/features/admin/components/AdminFaqsView.js +2 -2
  178. package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
  179. package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
  180. package/dist/features/admin/components/AdminGuideHubView.js +1 -1
  181. package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
  182. package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
  183. package/dist/features/admin/components/AdminPartsView.js +2 -2
  184. package/dist/features/admin/components/AdminProductsView.js +75 -4
  185. package/dist/features/admin/components/AdminSidebar.js +2 -2
  186. package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
  187. package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
  188. package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
  189. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
  190. package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
  191. package/dist/features/admin/components/AdminUsersView.js +72 -1
  192. package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
  193. package/dist/features/admin/components/AdminViewCards.js +4 -4
  194. package/dist/features/admin/components/DataListingView.d.ts +28 -0
  195. package/dist/features/admin/components/DataListingView.js +175 -14
  196. package/dist/features/admin/components/DataTable.d.ts +12 -1
  197. package/dist/features/admin/components/DataTable.js +3 -3
  198. package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
  199. package/dist/features/admin/components/OwnerQuickActions.js +60 -2
  200. package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
  201. package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
  202. package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
  203. package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
  204. package/dist/features/admin/components/index.d.ts +4 -0
  205. package/dist/features/admin/components/index.js +6 -0
  206. package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
  207. package/dist/features/admin/constants/filter-tabs.js +106 -0
  208. package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
  209. package/dist/features/admin/repository/site-settings.repository.js +82 -1
  210. package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
  211. package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
  212. package/dist/features/admin/schemas/firestore.js +5 -1
  213. package/dist/features/admin/schemas/site-settings-form.js +30 -1
  214. package/dist/features/auctions/actions/bid-actions.js +13 -1
  215. package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
  216. package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
  217. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
  218. package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
  219. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
  220. package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
  221. package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
  222. package/dist/features/audit-log/schemas/firestore.js +14 -0
  223. package/dist/features/auth/permissions/constants.d.ts +17 -1
  224. package/dist/features/auth/permissions/constants.js +18 -0
  225. package/dist/features/blog/components/BlogPostView.js +1 -1
  226. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
  227. package/dist/features/categories/api/[id]/description-template/route.js +107 -1
  228. package/dist/features/categories/components/BrandDetailPageView.js +2 -2
  229. package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
  230. package/dist/features/categories/components/BundleDetailView.js +1 -1
  231. package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
  232. package/dist/features/categories/components/CategoryForm.js +12 -1
  233. package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
  234. package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
  235. package/dist/features/categories/repository/market-price-index.repository.js +105 -0
  236. package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
  237. package/dist/features/categories/repository/price-observation.repository.js +132 -0
  238. package/dist/features/events/components/EventSaleBanner.js +1 -1
  239. package/dist/features/events/components/EventStatusBadge.js +1 -1
  240. package/dist/features/faq/components/FAQAccordion.js +2 -1
  241. package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
  242. package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
  243. package/dist/features/faq/components/RelatedFAQs.js +1 -1
  244. package/dist/features/filters/FilterFacetSection.js +1 -1
  245. package/dist/features/filters/SwitchFilter.js +1 -1
  246. package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
  247. package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
  248. package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
  249. package/dist/features/gateway-events/schemas/firestore.js +125 -0
  250. package/dist/features/gateway-events/schemas/index.d.ts +81 -0
  251. package/dist/features/gateway-events/schemas/index.js +79 -0
  252. package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
  253. package/dist/features/homepage/components/AnnouncementBar.js +37 -1
  254. package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
  255. package/dist/features/homepage/components/BrandsSection.js +1 -1
  256. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  257. package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
  258. package/dist/features/homepage/components/HeroCarousel.js +2 -2
  259. package/dist/features/homepage/components/SocialPostCard.js +1 -1
  260. package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
  261. package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
  262. package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
  263. package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
  264. package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
  265. package/dist/features/inventory/components/ReceiveStationView.js +413 -0
  266. package/dist/features/inventory/components/operator-message.d.ts +29 -0
  267. package/dist/features/inventory/components/operator-message.js +41 -0
  268. package/dist/features/inventory/index.d.ts +13 -0
  269. package/dist/features/inventory/index.js +28 -0
  270. package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
  271. package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
  272. package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
  273. package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
  274. package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
  275. package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
  276. package/dist/features/inventory/schemas/firestore.d.ts +197 -0
  277. package/dist/features/inventory/schemas/firestore.js +159 -0
  278. package/dist/features/layout/AppLayoutShell.d.ts +12 -1
  279. package/dist/features/layout/AppLayoutShell.js +25 -12
  280. package/dist/features/layout/AutoBreadcrumbs.js +162 -3
  281. package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
  282. package/dist/features/layout/BottomNavbar.d.ts +1 -1
  283. package/dist/features/layout/BottomNavbar.js +2 -2
  284. package/dist/features/media/MediaAvatar.js +1 -1
  285. package/dist/features/media/MediaImage.d.ts +24 -4
  286. package/dist/features/media/MediaImage.js +44 -4
  287. package/dist/features/media/MediaLightbox.js +1 -1
  288. package/dist/features/media/modals/ImageCropModal.js +1 -1
  289. package/dist/features/media/modals/ImageEditor.js +1 -1
  290. package/dist/features/media/upload/MediaUploadField.js +91 -3
  291. package/dist/features/obligations/constants/policies.d.ts +92 -0
  292. package/dist/features/obligations/constants/policies.js +378 -0
  293. package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
  294. package/dist/features/obligations/repository/obligations.repository.js +158 -0
  295. package/dist/features/obligations/schemas/firestore.d.ts +160 -0
  296. package/dist/features/obligations/schemas/firestore.js +87 -0
  297. package/dist/features/orders/actions/order-actions.d.ts +43 -4
  298. package/dist/features/orders/actions/order-actions.js +60 -3
  299. package/dist/features/orders/actions/refund-actions.js +70 -10
  300. package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
  301. package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
  302. package/dist/features/orders/components/OrdersList.js +2 -2
  303. package/dist/features/orders/components/RefundHistoryTable.js +3 -1
  304. package/dist/features/orders/components/index.d.ts +2 -0
  305. package/dist/features/orders/components/index.js +6 -0
  306. package/dist/features/orders/constants/transitions.d.ts +59 -0
  307. package/dist/features/orders/constants/transitions.js +104 -0
  308. package/dist/features/orders/repository/orders.repository.d.ts +109 -6
  309. package/dist/features/orders/repository/orders.repository.js +464 -21
  310. package/dist/features/orders/schemas/firestore.d.ts +270 -0
  311. package/dist/features/orders/schemas/firestore.js +24 -0
  312. package/dist/features/orders/schemas/index.d.ts +61 -0
  313. package/dist/features/orders/schemas/index.js +22 -0
  314. package/dist/features/orders/types/index.d.ts +79 -0
  315. package/dist/features/parts/components/PartsIndexPageView.js +8 -1
  316. package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
  317. package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
  318. package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
  319. package/dist/features/payment-cases/schemas/firestore.js +105 -0
  320. package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
  321. package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
  322. package/dist/features/payments/repository/payout.repository.d.ts +0 -1
  323. package/dist/features/payments/repository/payout.repository.js +0 -1
  324. package/dist/features/points/repository/points.repository.d.ts +122 -0
  325. package/dist/features/points/repository/points.repository.js +304 -1
  326. package/dist/features/points/schemas/adjust-form.d.ts +58 -0
  327. package/dist/features/points/schemas/adjust-form.js +63 -0
  328. package/dist/features/points/schemas/firestore.d.ts +108 -1
  329. package/dist/features/points/schemas/firestore.js +84 -0
  330. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  331. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  332. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  333. package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
  334. package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
  335. package/dist/features/price-index/market-stats.d.ts +82 -0
  336. package/dist/features/price-index/market-stats.js +230 -0
  337. package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
  338. package/dist/features/print/components/BoxLabelPrintView.js +66 -0
  339. package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
  340. package/dist/features/print/components/LabelProfilePicker.js +38 -0
  341. package/dist/features/print/components/labels.d.ts +117 -0
  342. package/dist/features/print/components/labels.js +258 -0
  343. package/dist/features/print/components/labels.style.css +46 -0
  344. package/dist/features/print/index.d.ts +6 -0
  345. package/dist/features/print/index.js +16 -0
  346. package/dist/features/products/columns/productTableColumns.js +5 -4
  347. package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
  348. package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
  349. package/dist/features/products/components/PrizeDrawCollage.js +1 -1
  350. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  351. package/dist/features/products/components/PrizeRevealModal.js +2 -2
  352. package/dist/features/products/components/ProductDetailPageView.js +18 -4
  353. package/dist/features/products/components/ProductForm.js +34 -23
  354. package/dist/features/products/components/ProductGrid.js +86 -62
  355. package/dist/features/products/constants/card-badges.d.ts +97 -0
  356. package/dist/features/products/constants/card-badges.js +100 -0
  357. package/dist/features/products/repository/products.repository.js +34 -1
  358. package/dist/features/products/schemas/firestore.d.ts +46 -3
  359. package/dist/features/products/schemas/firestore.js +21 -0
  360. package/dist/features/promotions/components/PromotionsView.js +1 -1
  361. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
  362. package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
  363. package/dist/features/promotions/schemas/firestore.js +30 -3
  364. package/dist/features/promotions/schemas/index.d.ts +6 -6
  365. package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
  366. package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
  367. package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
  368. package/dist/features/protection-claims/schemas/firestore.js +130 -0
  369. package/dist/features/reviews/actions/review-actions.js +3 -2
  370. package/dist/features/reviews/components/ReviewModal.js +1 -1
  371. package/dist/features/reviews/components/ReviewsList.js +1 -1
  372. package/dist/features/reviews/schemas/firestore.d.ts +20 -1
  373. package/dist/features/scams/constants/scam-types.d.ts +2 -0
  374. package/dist/features/scams/constants/scam-types.js +18 -0
  375. package/dist/features/scams/schemas/report-form.d.ts +3 -3
  376. package/dist/features/scams/schemas/report-form.js +26 -1
  377. package/dist/features/search/components/Search.js +9 -9
  378. package/dist/features/search/components/SearchResultsClient.js +18 -3
  379. package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
  380. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  381. package/dist/features/seller/actions/offer-actions.js +16 -1
  382. package/dist/features/seller/actions/seller-actions.js +14 -3
  383. package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
  384. package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
  385. package/dist/features/seller/components/PrintCenterView.js +41 -30
  386. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  387. package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
  388. package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
  389. package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
  390. package/dist/features/seller/components/SellerProductShell.js +123 -13
  391. package/dist/features/seller/components/SellerReviewsView.js +4 -2
  392. package/dist/features/seller/components/SellerSidebar.js +1 -1
  393. package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
  394. package/dist/features/seller/schemas/coupon-form.js +27 -0
  395. package/dist/features/seller/schemas/product-form.d.ts +3 -0
  396. package/dist/features/seller/schemas/product-form.js +5 -0
  397. package/dist/features/shell/FormShell.js +2 -2
  398. package/dist/features/shell/QuickFormDrawer.js +14 -0
  399. package/dist/features/shell/SectionForm.d.ts +19 -0
  400. package/dist/features/shell/SectionForm.js +133 -9
  401. package/dist/features/shell/SectionForm.style.css +54 -0
  402. package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
  403. package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
  404. package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
  405. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  406. package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
  407. package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
  408. package/dist/features/stores/components/StoreGuideHubView.js +1 -1
  409. package/dist/features/stores/components/StoreHeader.js +1 -1
  410. package/dist/features/support/schemas/firestore.d.ts +73 -2
  411. package/dist/features/support/schemas/firestore.js +59 -0
  412. package/dist/index.d.ts +16 -24
  413. package/dist/index.js +38 -47
  414. package/dist/next/ErrorBoundary.js +1 -1
  415. package/dist/next/routing/route-map.d.ts +24 -0
  416. package/dist/next/routing/route-map.js +40 -0
  417. package/dist/providers/payment-manual/index.d.ts +15 -1
  418. package/dist/providers/payment-manual/index.js +37 -0
  419. package/dist/providers/payment-phonepe/index.d.ts +14 -1
  420. package/dist/providers/payment-phonepe/index.js +36 -0
  421. package/dist/providers/payment-razorpay/index.d.ts +44 -1
  422. package/dist/providers/payment-razorpay/index.js +99 -0
  423. package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
  424. package/dist/providers/shipping-shiprocket/index.js +18 -0
  425. package/dist/schemas/registry.d.ts +44 -11
  426. package/dist/server-entry.d.ts +2 -0
  427. package/dist/server-entry.js +27 -0
  428. package/dist/server.d.ts +42 -0
  429. package/dist/server.js +93 -0
  430. package/dist/styles.css +418 -149
  431. package/dist/tailwind-utilities.css +1 -1
  432. package/dist/tokens/themes/default-dark.js +15 -12
  433. package/dist/tokens/themes/default-light.js +14 -11
  434. package/dist/tokens/tokens.css +106 -23
  435. package/dist/ui/DataTable.style.css +4 -4
  436. package/dist/ui/columns/cell-renderers.d.ts +20 -0
  437. package/dist/ui/columns/cell-renderers.js +26 -1
  438. package/dist/ui/columns/index.d.ts +1 -1
  439. package/dist/ui/columns/index.js +4 -1
  440. package/dist/ui/components/Badge.style.css +1 -1
  441. package/dist/ui/components/Breadcrumb.js +19 -1
  442. package/dist/ui/components/BulkActionBar.style.css +4 -4
  443. package/dist/ui/components/Button.style.css +1 -1
  444. package/dist/ui/components/Checkbox.style.css +1 -1
  445. package/dist/ui/components/FallbackShell.js +1 -1
  446. package/dist/ui/components/Form.d.ts +14 -1
  447. package/dist/ui/components/Form.js +2 -2
  448. package/dist/ui/components/FormField.js +18 -4
  449. package/dist/ui/components/FormField.style.css +43 -3
  450. package/dist/ui/components/GatedPrice.style.css +1 -1
  451. package/dist/ui/components/IconButton.style.css +1 -1
  452. package/dist/ui/components/Input.js +22 -1
  453. package/dist/ui/components/Input.style.css +38 -22
  454. package/dist/ui/components/ListingFilterDrawer.js +1 -1
  455. package/dist/ui/components/ListingToolbar.js +1 -1
  456. package/dist/ui/components/PaginatedSelect.js +0 -0
  457. package/dist/ui/components/Radio.style.css +1 -1
  458. package/dist/ui/components/Repeater.d.ts +53 -0
  459. package/dist/ui/components/Repeater.js +7 -0
  460. package/dist/ui/components/RichTextEditor.d.ts +20 -1
  461. package/dist/ui/components/RichTextEditor.js +23 -2
  462. package/dist/ui/components/Select.style.css +7 -21
  463. package/dist/ui/components/Semantic.js +27 -3
  464. package/dist/ui/components/TagInput.style.css +4 -9
  465. package/dist/ui/components/Textarea.style.css +4 -18
  466. package/dist/ui/components/Toggle.style.css +1 -1
  467. package/dist/ui/components/Typography.d.ts +3 -3
  468. package/dist/ui/components/Typography.js +15 -15
  469. package/dist/ui/components/ViewToggle.style.css +4 -4
  470. package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
  471. package/dist/ui/forms/AddAnotherToggle.js +56 -0
  472. package/dist/ui/forms/ColorPickerField.js +15 -2
  473. package/dist/ui/forms/FieldCheckbox.js +15 -2
  474. package/dist/ui/forms/FieldInput.d.ts +22 -1
  475. package/dist/ui/forms/FieldInput.js +19 -5
  476. package/dist/ui/forms/FieldLabel.d.ts +35 -0
  477. package/dist/ui/forms/FieldLabel.js +70 -0
  478. package/dist/ui/forms/FieldSelect.d.ts +4 -1
  479. package/dist/ui/forms/FieldSelect.js +27 -5
  480. package/dist/ui/forms/FieldTextarea.d.ts +4 -1
  481. package/dist/ui/forms/FieldTextarea.js +22 -5
  482. package/dist/ui/forms/FormShell.d.ts +17 -1
  483. package/dist/ui/forms/FormShell.js +36 -2
  484. package/dist/ui/forms/FormShell.style.css +41 -33
  485. package/dist/ui/forms/index.d.ts +5 -1
  486. package/dist/ui/forms/index.js +2 -0
  487. package/dist/ui/icons/icon-registry.d.ts +1 -0
  488. package/dist/ui/icons/icon-registry.js +17 -1
  489. package/dist/utils/id-generators.d.ts +80 -0
  490. package/dist/utils/id-generators.js +93 -0
  491. package/dist/utils/search-txt-builders.d.ts +12 -0
  492. package/dist/utils/search-txt-builders.js +17 -0
  493. package/package.json +1 -1
  494. package/scripts/seed-cli.mjs +106 -5
  495. package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
  496. package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
  497. package/dist/features/loyalty/api/balance/route.d.ts +0 -12
  498. package/dist/features/loyalty/api/balance/route.js +0 -41
  499. package/dist/features/loyalty/api/earn/route.d.ts +0 -12
  500. package/dist/features/loyalty/api/earn/route.js +0 -38
  501. package/dist/features/loyalty/api/history/route.d.ts +0 -12
  502. package/dist/features/loyalty/api/history/route.js +0 -58
  503. package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
  504. package/dist/features/loyalty/api/redeem/route.js +0 -38
  505. package/dist/features/loyalty/columns/index.d.ts +0 -17
  506. package/dist/features/loyalty/columns/index.js +0 -40
  507. package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
  508. package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
  509. package/dist/features/loyalty/components/index.d.ts +0 -1
  510. package/dist/features/loyalty/components/index.js +0 -1
  511. package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
  512. package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
  513. package/dist/features/loyalty/index.d.ts +0 -6
  514. package/dist/features/loyalty/index.js +0 -6
  515. package/dist/features/loyalty/manifest.d.ts +0 -2
  516. package/dist/features/loyalty/manifest.js +0 -16
  517. package/dist/features/loyalty/messages/en.json +0 -19
  518. package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
  519. package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
  520. package/dist/features/loyalty/schemas/index.d.ts +0 -85
  521. package/dist/features/loyalty/schemas/index.js +0 -40
  522. package/dist/features/loyalty/server.d.ts +0 -6
  523. package/dist/features/loyalty/server.js +0 -6
  524. package/dist/features/loyalty/types/index.d.ts +0 -31
  525. package/dist/features/loyalty/types/index.js +0 -1
  526. package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
  527. package/dist/features/loyalty/types/loyalty-config.js +0 -1
  528. package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
  529. package/dist/features/loyalty/types/loyalty-math.js +0 -27
  530. package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
  531. package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
@@ -52,6 +52,21 @@ export declare class PointsRepository {
52
52
  orderId?: string;
53
53
  adminReason?: string;
54
54
  actorUid?: string;
55
+ /**
56
+ * When this batch stops being spendable. Decision 4: three months.
57
+ *
58
+ * 🛑 Optional, and ABSENT MEANS NEVER EXPIRES — which is the right default
59
+ * for every entry written before the field existed. Treating absence as
60
+ * "expired" would retroactively void live balances.
61
+ *
62
+ * A caller crediting points SHOULD pass it; `pointsExpiryFrom(now)` is the
63
+ * one helper that computes it, so the three-month figure has one owner.
64
+ * Spends and reversals deliberately pass nothing: a spend has nothing to
65
+ * expire, and a reversal restores points whose original clock was already
66
+ * consumed — a fresh three months there would be a quiet extension nobody
67
+ * granted.
68
+ */
69
+ expiresAt?: Date;
55
70
  }): Promise<{
56
71
  entryId: string;
57
72
  balance: number;
@@ -75,7 +90,114 @@ export declare class PointsRepository {
75
90
  entryId: string;
76
91
  balance: number;
77
92
  }>;
93
+ /**
94
+ * Expire every lapsed batch, debiting whatever was never spent.
95
+ *
96
+ * ## 🛑 Expiry is a LEDGER ENTRY, never a silent balance edit
97
+ *
98
+ * The balance is DEFINED as the sum of the ledger. Quietly lowering the
99
+ * mirror would make the two disagree by exactly the expired amount, and the
100
+ * reconciliation job would then report drift forever with no way to tell it
101
+ * from a real fault. An adjustment row with a stated reason is the honest
102
+ * representation: the points left, and the row says why.
103
+ *
104
+ * 🛑 `actorUid: "system:points-expiry"` is a sentinel and this is the ONE
105
+ * place a system actor may write an adjustment — because expiry is a rule
106
+ * with a date attached, not a discretionary decision. Every other adjustment
107
+ * is a human's, which is why `adminReason` is mandatory on the kind.
108
+ *
109
+ * Marks the batch fully `consumed` in the same pass, and THAT is the
110
+ * idempotency key: without it an expiry job that ran twice would debit the
111
+ * buyer twice for one lapse.
112
+ */
113
+ expireLapsedBatches(now: Date, limit?: number): Promise<{
114
+ examined: number;
115
+ expired: number;
116
+ points: number;
117
+ }>;
118
+ /**
119
+ * Return points a cancelled order had consumed — `redemption_reversal`.
120
+ *
121
+ * ## 🛑 Points come back as POINTS, never as money
122
+ *
123
+ * The schema header states it as a legal boundary rather than a preference:
124
+ * *"never refunded as cash when an order is cancelled; points SPENT on a
125
+ * cancelled order are returned as points."* Paying them out in rupees is what
126
+ * would make this stored value, and stored value in India is PPI territory
127
+ * with RBI authorisation attached. This is the writer that makes that stated
128
+ * rule true — the kind has existed with no writer since it was declared (D48).
129
+ *
130
+ * 🛑 NO fresh `expiresAt`. A reversal restores points whose original clock has
131
+ * already been consumed; granting three new months would be a quiet extension
132
+ * nobody authorised, and it would reward cancelling an order with a longer
133
+ * window than keeping it.
134
+ *
135
+ * Idempotent on the ORDER: one reversal per order, found by querying for an
136
+ * existing `redemption_reversal` carrying the same `orderId`. An append-only
137
+ * ledger cannot net off a double reversal, so the check has to happen before
138
+ * the write.
139
+ */
140
+ reverseRedemptionForOrder(orderId: string, reason: string): Promise<{
141
+ reversed: number;
142
+ }>;
78
143
  /** Most recent entries first, for the buyer's own points page. */
79
144
  listForUser(userId: string, limit?: number): Promise<PointsLedgerEntry[]>;
145
+ /**
146
+ * Prove the mirror agrees with the ledger, and repair it where it does not.
147
+ *
148
+ * ## This is the job `sumLedger`'s own docstring has promised since day one
149
+ *
150
+ * That method says it exists "to repair the mirror and to let a
151
+ * reconciliation job prove the two agree" — and no such job was ever written
152
+ * (D49). So `users/{uid}.pointsBalance` could diverge from the entries
153
+ * indefinitely with nothing reporting it, which is Root Cause #102 exactly:
154
+ * a counter that cannot be checked drifts silently, because a wrong number
155
+ * raises nothing.
156
+ *
157
+ * ## 🛑 It enumerates USERS, not ledger entries
158
+ *
159
+ * Root Cause #102's reconciler could only ever ADD, because it built its
160
+ * tally from the events that existed rather than the entities that do — so a
161
+ * row whose last event was deleted was never visited and never reset. The
162
+ * same trap here: a user whose entries were all removed keeps a stale
163
+ * non-zero mirror forever if you only walk the ledger.
164
+ *
165
+ * So the candidate set is the UNION of two reads:
166
+ *
167
+ * - every user document carrying a `pointsBalance` — catches a mirror with
168
+ * no entries behind it, which is the case a ledger walk cannot see
169
+ * - the distinct `userId`s in a bounded page of the ledger — catches
170
+ * entries whose user has no mirror yet
171
+ *
172
+ * ## Repair direction, and why it still opens a case
173
+ *
174
+ * The ledger is append-only and is the DEFINITION of the balance, so repair
175
+ * is always mirror ← `sumLedger`. There is no judgement in it.
176
+ *
177
+ * 🛑 But a repaired mirror hides the writer that broke it. Every drift is
178
+ * therefore returned for the caller to raise a `ledger_drift` case: silent
179
+ * self-healing would make a persistently broken write path look like a
180
+ * healthy system, which is the failure this whole phase exists to remove.
181
+ *
182
+ * Absence of a mirror is NOT drift. `getBalance` already falls back to
183
+ * summing the ledger, so a user with entries and no mirror reads correctly —
184
+ * it is merely uncomputed. It is still written, because paying the scan on
185
+ * every balance read is what the mirror exists to avoid, but it is counted
186
+ * separately and never reported as a fault.
187
+ *
188
+ * Saturation is reported rather than hidden: at the page limit there are more
189
+ * users to check than were checked, which is a fact about the programme's
190
+ * size, not an error — but it must not read as "all clear".
191
+ */
192
+ reconcileLedgerMirror(limit?: number): Promise<{
193
+ examined: number;
194
+ drifted: {
195
+ userId: string;
196
+ mirror: number;
197
+ ledger: number;
198
+ }[];
199
+ initialised: number;
200
+ saturated: boolean;
201
+ }>;
80
202
  }
81
203
  export declare const pointsRepository: PointsRepository;
@@ -1,5 +1,5 @@
1
1
  import { getAdminDb } from "../../../providers/db-firebase/index.js";
2
- import { POINTS_FIELDS, POINTS_LEDGER_COLLECTION, isPointsEntryWellFormed, } from "../schemas/firestore.js";
2
+ import { POINTS_FIELDS, POINTS_LEDGER_COLLECTION, POINTS_EXPIRY_MONTHS, isPointsEntryWellFormed, } from "../schemas/firestore.js";
3
3
  import { USER_COLLECTION } from "../../auth/schemas/firestore.js";
4
4
  /**
5
5
  * The points ledger: append-only entries, and a balance that is their sum.
@@ -90,6 +90,7 @@ export class PointsRepository {
90
90
  ...(input.orderId ? { orderId: input.orderId } : {}),
91
91
  ...(input.adminReason ? { adminReason: input.adminReason } : {}),
92
92
  ...(input.actorUid ? { actorUid: input.actorUid } : {}),
93
+ ...(input.expiresAt ? { expiresAt: input.expiresAt } : {}),
93
94
  createdAt: new Date(),
94
95
  updatedAt: new Date(),
95
96
  };
@@ -116,6 +117,74 @@ export class PointsRepository {
116
117
  const db = getAdminDb();
117
118
  const entryRef = db.collection(POINTS_LEDGER_COLLECTION).doc();
118
119
  const userRef = db.collection(USER_COLLECTION).doc(input.userId);
120
+ const now = new Date();
121
+ /*
122
+ * ── FIFO: which BATCHES does this spend draw from? ────────────────────
123
+ *
124
+ * 🛑 Queried OUTSIDE the transaction, and that is a Firestore constraint
125
+ * rather than a preference: a transaction must complete all reads before
126
+ * any write, and a collection QUERY cannot be mixed with the per-document
127
+ * updates below it. The part that must not race — the balance check — stays
128
+ * inside.
129
+ *
130
+ * The residual risk is two concurrent redemptions picking the same batches
131
+ * and over-consuming one, leaving a `consumed` figure above that entry's
132
+ * `amount`. That is an artefact on an EXPIRY calculation, not on a balance:
133
+ * the balance is guarded transactionally and still cannot go negative. The
134
+ * reconciliation job reports it, and a lock here would buy nothing a buyer
135
+ * can see.
136
+ */
137
+ const credits = await db
138
+ .collection(POINTS_LEDGER_COLLECTION)
139
+ .where(POINTS_FIELDS.USER_ID, "==", input.userId)
140
+ .where(POINTS_FIELDS.AMOUNT, ">", 0)
141
+ .orderBy(POINTS_FIELDS.AMOUNT)
142
+ .limit(200)
143
+ .get();
144
+ /*
145
+ * 🛑 Re-sorted in memory, because the QUERY CANNOT RETURN FIFO ORDER.
146
+ * Firestore forces an inequality's field to lead the `orderBy` (Root Cause
147
+ * #59), so `amount > 0` means the results arrive smallest-credit-first.
148
+ * Skipping this sort would consume the SMALLEST batches first — which looks
149
+ * exactly like FIFO on a ledger where every credit happens to be the same
150
+ * size, and silently is not.
151
+ *
152
+ * Oldest first, so a buyer never loses points they could have spent.
153
+ * Newest-first silently maximises expiry: the house winning by a mechanism
154
+ * nobody announced.
155
+ */
156
+ const batches = credits.docs
157
+ .map((d) => d.data())
158
+ .filter((e) => {
159
+ /*
160
+ * An expired batch is not spendable. Absent `expiresAt` means never
161
+ * expires — correct for every entry written before the field existed.
162
+ */
163
+ if (!e.expiresAt)
164
+ return true;
165
+ const exp = e.expiresAt instanceof Date ? e.expiresAt : new Date(e.expiresAt);
166
+ return exp.getTime() > now.getTime();
167
+ })
168
+ .sort((a, b) => {
169
+ const at = a.createdAt instanceof Date ? a.createdAt.getTime() : 0;
170
+ const bt = b.createdAt instanceof Date ? b.createdAt.getTime() : 0;
171
+ return at - bt;
172
+ });
173
+ let toDraw = input.points;
174
+ const draws = [];
175
+ for (const batch of batches) {
176
+ if (toDraw <= 0)
177
+ break;
178
+ const remaining = Math.round((batch.amount - (batch.consumed ?? 0)) * 100) / 100;
179
+ if (remaining <= 0)
180
+ continue;
181
+ const take = Math.min(remaining, toDraw);
182
+ draws.push({
183
+ id: batch.id,
184
+ consumed: Math.round(((batch.consumed ?? 0) + take) * 100) / 100,
185
+ });
186
+ toDraw = Math.round((toDraw - take) * 100) / 100;
187
+ }
119
188
  const balance = await db.runTransaction(async (tx) => {
120
189
  const userSnap = await tx.get(userRef);
121
190
  const current = typeof userSnap.data()?.pointsBalance === "number"
@@ -134,12 +203,138 @@ export class PointsRepository {
134
203
  description: input.description,
135
204
  createdAt: new Date(),
136
205
  updatedAt: new Date(),
206
+ /*
207
+ * Which batches paid for this spend, recorded ON the spend rather than
208
+ * derived later — a reversal needs to know what to give back, and by
209
+ * then those batches may have expired. "Return it where it came from"
210
+ * is only answerable if the spend wrote it down.
211
+ */
212
+ ...(draws.length ? { drawnFrom: draws.map((d) => d.id) } : {}),
137
213
  });
214
+ for (const d of draws) {
215
+ tx.update(db.collection(POINTS_LEDGER_COLLECTION).doc(d.id), {
216
+ consumed: d.consumed,
217
+ updatedAt: new Date(),
218
+ });
219
+ }
138
220
  tx.set(userRef, { pointsBalance: next }, { merge: true });
139
221
  return next;
140
222
  });
141
223
  return { entryId: entryRef.id, balance };
142
224
  }
225
+ /**
226
+ * Expire every lapsed batch, debiting whatever was never spent.
227
+ *
228
+ * ## 🛑 Expiry is a LEDGER ENTRY, never a silent balance edit
229
+ *
230
+ * The balance is DEFINED as the sum of the ledger. Quietly lowering the
231
+ * mirror would make the two disagree by exactly the expired amount, and the
232
+ * reconciliation job would then report drift forever with no way to tell it
233
+ * from a real fault. An adjustment row with a stated reason is the honest
234
+ * representation: the points left, and the row says why.
235
+ *
236
+ * 🛑 `actorUid: "system:points-expiry"` is a sentinel and this is the ONE
237
+ * place a system actor may write an adjustment — because expiry is a rule
238
+ * with a date attached, not a discretionary decision. Every other adjustment
239
+ * is a human's, which is why `adminReason` is mandatory on the kind.
240
+ *
241
+ * Marks the batch fully `consumed` in the same pass, and THAT is the
242
+ * idempotency key: without it an expiry job that ran twice would debit the
243
+ * buyer twice for one lapse.
244
+ */
245
+ async expireLapsedBatches(now, limit = 200) {
246
+ const db = getAdminDb();
247
+ const snap = await db
248
+ .collection(POINTS_LEDGER_COLLECTION)
249
+ .where(POINTS_FIELDS.EXPIRES_AT, "<=", now)
250
+ .orderBy(POINTS_FIELDS.EXPIRES_AT, "asc")
251
+ .limit(limit)
252
+ .get();
253
+ let expired = 0;
254
+ let points = 0;
255
+ for (const doc of snap.docs) {
256
+ const e = doc.data();
257
+ /*
258
+ * Credits only. A redemption carries no `expiresAt` so should never
259
+ * appear here — but the guard is free, and the cost of being wrong is
260
+ * debiting a buyer for a spend they already made.
261
+ */
262
+ if (e.amount <= 0)
263
+ continue;
264
+ const remaining = Math.round((e.amount - (e.consumed ?? 0)) * 100) / 100;
265
+ if (remaining <= 0)
266
+ continue;
267
+ await this.append({
268
+ userId: e.userId,
269
+ kind: "admin_adjustment",
270
+ amount: -remaining,
271
+ description: `${remaining} points expired.`,
272
+ adminReason: `Automatic expiry after ${POINTS_EXPIRY_MONTHS} months — batch ${e.id}.`,
273
+ actorUid: "system:points-expiry",
274
+ });
275
+ await doc.ref.update({ consumed: e.amount, updatedAt: now });
276
+ expired += 1;
277
+ points = Math.round((points + remaining) * 100) / 100;
278
+ }
279
+ return { examined: snap.size, expired, points };
280
+ }
281
+ /**
282
+ * Return points a cancelled order had consumed — `redemption_reversal`.
283
+ *
284
+ * ## 🛑 Points come back as POINTS, never as money
285
+ *
286
+ * The schema header states it as a legal boundary rather than a preference:
287
+ * *"never refunded as cash when an order is cancelled; points SPENT on a
288
+ * cancelled order are returned as points."* Paying them out in rupees is what
289
+ * would make this stored value, and stored value in India is PPI territory
290
+ * with RBI authorisation attached. This is the writer that makes that stated
291
+ * rule true — the kind has existed with no writer since it was declared (D48).
292
+ *
293
+ * 🛑 NO fresh `expiresAt`. A reversal restores points whose original clock has
294
+ * already been consumed; granting three new months would be a quiet extension
295
+ * nobody authorised, and it would reward cancelling an order with a longer
296
+ * window than keeping it.
297
+ *
298
+ * Idempotent on the ORDER: one reversal per order, found by querying for an
299
+ * existing `redemption_reversal` carrying the same `orderId`. An append-only
300
+ * ledger cannot net off a double reversal, so the check has to happen before
301
+ * the write.
302
+ */
303
+ async reverseRedemptionForOrder(orderId, reason) {
304
+ const db = getAdminDb();
305
+ const already = await db
306
+ .collection(POINTS_LEDGER_COLLECTION)
307
+ .where(POINTS_FIELDS.ORDER_ID, "==", orderId)
308
+ .where(POINTS_FIELDS.KIND, "==", "redemption_reversal")
309
+ .limit(1)
310
+ .get();
311
+ if (!already.empty)
312
+ return { reversed: 0 };
313
+ const spends = await db
314
+ .collection(POINTS_LEDGER_COLLECTION)
315
+ .where(POINTS_FIELDS.ORDER_ID, "==", orderId)
316
+ .where(POINTS_FIELDS.KIND, "==", "redemption")
317
+ .get();
318
+ if (spends.empty)
319
+ return { reversed: 0 };
320
+ const rows = spends.docs.map((d) => d.data());
321
+ /*
322
+ * `Math.abs`, because a redemption is stored NEGATIVE and a reversal must
323
+ * be positive — `isPointsEntryWellFormed` refuses the wrong sign, so a
324
+ * missing `abs` here would throw rather than silently credit a negative.
325
+ */
326
+ const total = Math.round(rows.reduce((sum, r) => sum + Math.abs(r.amount), 0) * 100) / 100;
327
+ if (total <= 0)
328
+ return { reversed: 0 };
329
+ await this.append({
330
+ userId: rows[0].userId,
331
+ kind: "redemption_reversal",
332
+ amount: total,
333
+ orderId,
334
+ description: `${total} points returned — ${reason}`,
335
+ });
336
+ return { reversed: total };
337
+ }
143
338
  /** Most recent entries first, for the buyer's own points page. */
144
339
  async listForUser(userId, limit = 50) {
145
340
  const db = getAdminDb();
@@ -151,5 +346,113 @@ export class PointsRepository {
151
346
  .get();
152
347
  return rows.docs.map((d) => d.data());
153
348
  }
349
+ /**
350
+ * Prove the mirror agrees with the ledger, and repair it where it does not.
351
+ *
352
+ * ## This is the job `sumLedger`'s own docstring has promised since day one
353
+ *
354
+ * That method says it exists "to repair the mirror and to let a
355
+ * reconciliation job prove the two agree" — and no such job was ever written
356
+ * (D49). So `users/{uid}.pointsBalance` could diverge from the entries
357
+ * indefinitely with nothing reporting it, which is Root Cause #102 exactly:
358
+ * a counter that cannot be checked drifts silently, because a wrong number
359
+ * raises nothing.
360
+ *
361
+ * ## 🛑 It enumerates USERS, not ledger entries
362
+ *
363
+ * Root Cause #102's reconciler could only ever ADD, because it built its
364
+ * tally from the events that existed rather than the entities that do — so a
365
+ * row whose last event was deleted was never visited and never reset. The
366
+ * same trap here: a user whose entries were all removed keeps a stale
367
+ * non-zero mirror forever if you only walk the ledger.
368
+ *
369
+ * So the candidate set is the UNION of two reads:
370
+ *
371
+ * - every user document carrying a `pointsBalance` — catches a mirror with
372
+ * no entries behind it, which is the case a ledger walk cannot see
373
+ * - the distinct `userId`s in a bounded page of the ledger — catches
374
+ * entries whose user has no mirror yet
375
+ *
376
+ * ## Repair direction, and why it still opens a case
377
+ *
378
+ * The ledger is append-only and is the DEFINITION of the balance, so repair
379
+ * is always mirror ← `sumLedger`. There is no judgement in it.
380
+ *
381
+ * 🛑 But a repaired mirror hides the writer that broke it. Every drift is
382
+ * therefore returned for the caller to raise a `ledger_drift` case: silent
383
+ * self-healing would make a persistently broken write path look like a
384
+ * healthy system, which is the failure this whole phase exists to remove.
385
+ *
386
+ * Absence of a mirror is NOT drift. `getBalance` already falls back to
387
+ * summing the ledger, so a user with entries and no mirror reads correctly —
388
+ * it is merely uncomputed. It is still written, because paying the scan on
389
+ * every balance read is what the mirror exists to avoid, but it is counted
390
+ * separately and never reported as a fault.
391
+ *
392
+ * Saturation is reported rather than hidden: at the page limit there are more
393
+ * users to check than were checked, which is a fact about the programme's
394
+ * size, not an error — but it must not read as "all clear".
395
+ */
396
+ async reconcileLedgerMirror(limit = 500) {
397
+ const db = getAdminDb();
398
+ const mirrorSnap = await db
399
+ .collection(USER_COLLECTION)
400
+ .select("pointsBalance")
401
+ .limit(limit)
402
+ .get();
403
+ const ledgerSnap = await db
404
+ .collection(POINTS_LEDGER_COLLECTION)
405
+ .select(POINTS_FIELDS.USER_ID)
406
+ .limit(limit)
407
+ .get();
408
+ const mirrors = new Map();
409
+ for (const d of mirrorSnap.docs) {
410
+ const v = d.data()?.pointsBalance;
411
+ if (typeof v === "number")
412
+ mirrors.set(d.id, v);
413
+ }
414
+ const candidates = new Set(mirrors.keys());
415
+ for (const d of ledgerSnap.docs) {
416
+ const uid = d.data()?.[POINTS_FIELDS.USER_ID];
417
+ if (typeof uid === "string" && uid)
418
+ candidates.add(uid);
419
+ }
420
+ const drifted = [];
421
+ let initialised = 0;
422
+ for (const userId of candidates) {
423
+ const ledger = await this.sumLedger(userId);
424
+ const mirror = mirrors.get(userId);
425
+ if (mirror === undefined) {
426
+ // Uncomputed, not drifted — see the docstring.
427
+ if (ledger !== 0) {
428
+ await db
429
+ .collection(USER_COLLECTION)
430
+ .doc(userId)
431
+ .set({ pointsBalance: ledger }, { merge: true });
432
+ initialised += 1;
433
+ }
434
+ continue;
435
+ }
436
+ /*
437
+ * Half-paisa epsilon, matching every other money comparison here:
438
+ * `0.1 + 0.2 > 0.3` is true in IEEE-754, so an exact `!==` would report
439
+ * drift on a balance that is correct to the last paisa — and a reconciler
440
+ * that cries wolf nightly is one people stop reading.
441
+ */
442
+ if (Math.abs(mirror - ledger) > 0.005) {
443
+ drifted.push({ userId, mirror, ledger });
444
+ await db
445
+ .collection(USER_COLLECTION)
446
+ .doc(userId)
447
+ .set({ pointsBalance: ledger }, { merge: true });
448
+ }
449
+ }
450
+ return {
451
+ examined: candidates.size,
452
+ drifted,
453
+ initialised,
454
+ saturated: mirrorSnap.size === limit || ledgerSnap.size === limit,
455
+ };
456
+ }
154
457
  }
155
458
  export const pointsRepository = new PointsRepository();
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The write contract for a manual points adjustment.
4
+ *
5
+ * ## 🛑 This creates or destroys SPENDABLE MONEY
6
+ *
7
+ * 1 point = ₹1 against a future order, so a credit here is a liability the
8
+ * platform has to honour and a debit takes value a buyer already holds. That
9
+ * is why this is the one ledger kind whose invariant
10
+ * (`isPointsEntryWellFormed`) demands a reason, and why the schema adds two
11
+ * things the invariant cannot express.
12
+ *
13
+ * ## 🛑 A CAP, because the realistic failure is a typo
14
+ *
15
+ * The invariant checks sign and reason, not magnitude — so `50000` where `500`
16
+ * was meant passes every existing check and mints ₹50,000 of liability in one
17
+ * click. `POINTS_ADJUSTMENT_MAX` bounds a single adjustment; a genuinely larger
18
+ * correction is several deliberate ones, each with its own reason and its own
19
+ * audit row, which is the right amount of friction for a number that size.
20
+ *
21
+ * The bound is on the ABSOLUTE value, so it guards the debit direction too: a
22
+ * mistyped debit wipes a balance the buyer earned and there is no undo beyond
23
+ * another adjustment.
24
+ *
25
+ * ## The reason is for the BUYER'S case file, not for us
26
+ *
27
+ * `adminReason` is never shown to the buyer (the schema says so), so its reader
28
+ * is the next admin asking "why does this account have 53 points". A reason
29
+ * like "fix" answers nothing, which is why the floor is a length rather than
30
+ * mere presence — the same reasoning as `paymentCaseResolveFormSchema`'s
31
+ * 10-character floor, and deliberately the same number so an operator meets one
32
+ * rule rather than two.
33
+ *
34
+ * 🛑 `amount` is SIGNED here, unlike `redeem()` which takes a positive and
35
+ * stores a negative. An adjustment genuinely goes both ways and the sign IS the
36
+ * intent, so hiding it behind a separate direction field would mean two inputs
37
+ * that can disagree. `.refine` rejects zero rather than letting `append()`
38
+ * throw on it, so the operator gets a field error instead of a 500.
39
+ */
40
+ export declare const pointsAdjustFormSchema: z.ZodObject<{
41
+ userId: z.ZodString;
42
+ amount: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodNumber, number, number>, number, number>, number, number>, number, number>;
43
+ /** Shown to the BUYER on their points page. */
44
+ description: z.ZodString;
45
+ /** Never shown to the buyer. The next admin's answer to "why?". */
46
+ adminReason: z.ZodString;
47
+ }, "strict", z.ZodTypeAny, {
48
+ amount: number;
49
+ description: string;
50
+ userId: string;
51
+ adminReason: string;
52
+ }, {
53
+ amount: number;
54
+ description: string;
55
+ userId: string;
56
+ adminReason: string;
57
+ }>;
58
+ export type PointsAdjustFormValues = z.infer<typeof pointsAdjustFormSchema>;
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import { POINTS_ADJUSTMENT_MAX, POINTS_ADJUSTMENT_REASON_MIN } from "./firestore.js";
3
+ /**
4
+ * The write contract for a manual points adjustment.
5
+ *
6
+ * ## 🛑 This creates or destroys SPENDABLE MONEY
7
+ *
8
+ * 1 point = ₹1 against a future order, so a credit here is a liability the
9
+ * platform has to honour and a debit takes value a buyer already holds. That
10
+ * is why this is the one ledger kind whose invariant
11
+ * (`isPointsEntryWellFormed`) demands a reason, and why the schema adds two
12
+ * things the invariant cannot express.
13
+ *
14
+ * ## 🛑 A CAP, because the realistic failure is a typo
15
+ *
16
+ * The invariant checks sign and reason, not magnitude — so `50000` where `500`
17
+ * was meant passes every existing check and mints ₹50,000 of liability in one
18
+ * click. `POINTS_ADJUSTMENT_MAX` bounds a single adjustment; a genuinely larger
19
+ * correction is several deliberate ones, each with its own reason and its own
20
+ * audit row, which is the right amount of friction for a number that size.
21
+ *
22
+ * The bound is on the ABSOLUTE value, so it guards the debit direction too: a
23
+ * mistyped debit wipes a balance the buyer earned and there is no undo beyond
24
+ * another adjustment.
25
+ *
26
+ * ## The reason is for the BUYER'S case file, not for us
27
+ *
28
+ * `adminReason` is never shown to the buyer (the schema says so), so its reader
29
+ * is the next admin asking "why does this account have 53 points". A reason
30
+ * like "fix" answers nothing, which is why the floor is a length rather than
31
+ * mere presence — the same reasoning as `paymentCaseResolveFormSchema`'s
32
+ * 10-character floor, and deliberately the same number so an operator meets one
33
+ * rule rather than two.
34
+ *
35
+ * 🛑 `amount` is SIGNED here, unlike `redeem()` which takes a positive and
36
+ * stores a negative. An adjustment genuinely goes both ways and the sign IS the
37
+ * intent, so hiding it behind a separate direction field would mean two inputs
38
+ * that can disagree. `.refine` rejects zero rather than letting `append()`
39
+ * throw on it, so the operator gets a field error instead of a 500.
40
+ */
41
+ export const pointsAdjustFormSchema = z
42
+ .object({
43
+ userId: z.string().trim().min(1, "Pick a user."),
44
+ amount: z
45
+ .number({ invalid_type_error: "Enter a number of points." })
46
+ .refine((v) => Number.isFinite(v), "Enter a number of points.")
47
+ .refine((v) => v !== 0, "An adjustment of zero does nothing.")
48
+ .refine((v) => Math.abs(v) <= POINTS_ADJUSTMENT_MAX, `A single adjustment is capped at ${POINTS_ADJUSTMENT_MAX} points. Split a larger correction.`)
49
+ .refine((v) => Number.isInteger(v), "Points are whole numbers — a fraction of a point cannot be shown to a buyer or spent."),
50
+ /** Shown to the BUYER on their points page. */
51
+ description: z
52
+ .string()
53
+ .trim()
54
+ .min(1, "Say what the buyer will see for this entry.")
55
+ .max(140, "Keep the buyer-facing line under 140 characters."),
56
+ /** Never shown to the buyer. The next admin's answer to "why?". */
57
+ adminReason: z
58
+ .string()
59
+ .trim()
60
+ .min(POINTS_ADJUSTMENT_REASON_MIN, `Give a reason of at least ${POINTS_ADJUSTMENT_REASON_MIN} characters — the reader is the next admin, not the buyer.`)
61
+ .max(500, "Keep the reason under 500 characters."),
62
+ })
63
+ .strict();