@mohasinac/appkit 4.61.0 → 4.62.1

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 (281) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +19 -3
  3. package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
  5. package/dist/_internal/server/features/checkout/actions.d.ts +80 -0
  6. package/dist/_internal/server/features/checkout/actions.js +542 -26
  7. package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
  8. package/dist/_internal/server/features/invoicing/allocate.js +162 -0
  9. package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
  10. package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
  11. package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
  12. package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
  13. package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
  14. package/dist/_internal/server/features/invoicing/issue.js +202 -0
  15. package/dist/_internal/server/features/orders/actions.d.ts +8 -1
  16. package/dist/_internal/server/features/orders/actions.js +81 -8
  17. package/dist/_internal/server/features/orders/adapters.js +33 -0
  18. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
  19. package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
  20. package/dist/_internal/server/features/points/award.js +46 -4
  21. package/dist/_internal/server/features/refunds/actions.js +26 -0
  22. package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
  23. package/dist/_internal/server/features/shipping/courier-block.js +239 -0
  24. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  25. package/dist/_internal/server/features/site-settings/adapters.js +15 -0
  26. package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
  27. package/dist/_internal/server/features/stores/adapters.js +51 -0
  28. package/dist/_internal/server/functions/scheduled.js +38 -1
  29. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +9 -2
  30. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
  31. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
  32. package/dist/_internal/server/jobs/core/index.d.ts +2 -1
  33. package/dist/_internal/server/jobs/core/index.js +2 -1
  34. package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
  35. package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
  36. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
  37. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +62 -0
  38. package/dist/_internal/server/jobs/core/paymentReconcile.js +87 -97
  39. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
  40. package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
  41. package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
  42. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +10 -2
  43. package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
  44. package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
  45. package/dist/_internal/shared/actions/action-registry.js +177 -9
  46. package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
  47. package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
  48. package/dist/_internal/shared/checkout/order-math.d.ts +145 -0
  49. package/dist/_internal/shared/checkout/order-math.js +173 -0
  50. package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
  51. package/dist/_internal/shared/checkout/refundable-base.js +13 -1
  52. package/dist/_internal/shared/checkout/split.d.ts +87 -0
  53. package/dist/_internal/shared/checkout/split.js +100 -0
  54. package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
  55. package/dist/_internal/shared/content/disclaimers.js +217 -0
  56. package/dist/_internal/shared/content/published-values.d.ts +88 -0
  57. package/dist/_internal/shared/content/published-values.js +198 -0
  58. package/dist/_internal/shared/features/admin/integrations.d.ts +109 -0
  59. package/dist/_internal/shared/features/admin/integrations.js +253 -0
  60. package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
  61. package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
  62. package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
  63. package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
  64. package/dist/_internal/shared/features/points/accepts.d.ts +36 -0
  65. package/dist/_internal/shared/features/points/accepts.js +43 -0
  66. package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
  67. package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
  68. package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
  69. package/dist/_internal/shared/fees/calculator.d.ts +33 -1
  70. package/dist/_internal/shared/fees/calculator.js +32 -2
  71. package/dist/_internal/shared/media/limits.d.ts +10 -0
  72. package/dist/_internal/shared/media/limits.js +10 -0
  73. package/dist/_internal/shared/types/action-result.js +15 -1
  74. package/dist/client/api/surface-error.js +16 -2
  75. package/dist/client/api/useApiMutation.js +17 -1
  76. package/dist/client.d.ts +12 -1
  77. package/dist/client.js +31 -1
  78. package/dist/constants/api-endpoints.d.ts +45 -0
  79. package/dist/constants/api-endpoints.js +15 -0
  80. package/dist/constants/field-names.d.ts +18 -0
  81. package/dist/constants/field-names.js +28 -0
  82. package/dist/contracts/index.d.ts +1 -1
  83. package/dist/contracts/shipping.d.ts +45 -0
  84. package/dist/contracts/shipping.js +15 -0
  85. package/dist/core/server-action.js +19 -3
  86. package/dist/errors/error-mapping.js +26 -1
  87. package/dist/features/about/components/AboutView.js +1 -1
  88. package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
  89. package/dist/features/account/components/UserOffersPanel.js +5 -3
  90. package/dist/features/account/components/UserOrdersView.js +19 -33
  91. package/dist/features/admin/components/AdminActionIndexView.js +100 -5
  92. package/dist/features/admin/components/AdminAddressClustersView.js +21 -2
  93. package/dist/features/admin/components/AdminBlogEditorView.js +8 -8
  94. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
  95. package/dist/features/admin/components/AdminCarouselView.js +96 -4
  96. package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
  97. package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
  98. package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
  99. package/dist/features/admin/components/AdminInvoicingView.js +65 -0
  100. package/dist/features/admin/components/AdminMediaView.js +36 -3
  101. package/dist/features/admin/components/AdminNavigationView.js +4 -1
  102. package/dist/features/admin/components/AdminOffersView.js +2 -1
  103. package/dist/features/admin/components/AdminOrderEditorView.d.ts +18 -1
  104. package/dist/features/admin/components/AdminOrderEditorView.js +11 -2
  105. package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
  106. package/dist/features/admin/components/AdminOrdersView.js +67 -5
  107. package/dist/features/admin/components/AdminPaymentClustersView.js +19 -2
  108. package/dist/features/admin/components/AdminProductEditorView.js +2 -2
  109. package/dist/features/admin/components/AdminSectionsView.d.ts +10 -0
  110. package/dist/features/admin/components/AdminSectionsView.js +486 -224
  111. package/dist/features/admin/components/OwnerQuickActions.js +24 -2
  112. package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
  113. package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
  114. package/dist/features/admin/constants/filter-tabs.js +6 -0
  115. package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
  116. package/dist/features/admin/hooks/useAdminListingData.js +16 -0
  117. package/dist/features/admin/hooks/useOptimisticRowFlag.d.ts +33 -0
  118. package/dist/features/admin/hooks/useOptimisticRowFlag.js +87 -0
  119. package/dist/features/admin/repository/notification.repository.d.ts +15 -0
  120. package/dist/features/admin/repository/notification.repository.js +32 -0
  121. package/dist/features/admin/schemas/action-index-form.d.ts +34 -0
  122. package/dist/features/admin/schemas/action-index-form.js +115 -0
  123. package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
  124. package/dist/features/admin/schemas/firestore.d.ts +30 -0
  125. package/dist/features/admin/schemas/firestore.js +5 -0
  126. package/dist/features/audit-log/schemas/firestore.d.ts +30 -0
  127. package/dist/features/audit-log/schemas/firestore.js +15 -0
  128. package/dist/features/auth/components/RegisterForm.js +2 -1
  129. package/dist/features/auth/permissions/constants.js +13 -0
  130. package/dist/features/cart/components/CartPriceBreakdown.d.ts +13 -0
  131. package/dist/features/cart/components/CartPriceBreakdown.js +6 -2
  132. package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
  133. package/dist/features/cart/components/PointsTenderPicker.js +15 -0
  134. package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
  135. package/dist/features/cart/components/index.d.ts +2 -0
  136. package/dist/features/cart/components/index.js +1 -0
  137. package/dist/features/cart/repository/cart.repository.d.ts +19 -0
  138. package/dist/features/cart/repository/cart.repository.js +27 -0
  139. package/dist/features/cart/schemas/firestore.d.ts +92 -0
  140. package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
  141. package/dist/features/contact/email.d.ts +27 -10
  142. package/dist/features/contact/email.js +20 -3
  143. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +38 -0
  144. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +80 -0
  145. package/dist/features/dispute-reasons/schemas/firestore.d.ts +64 -0
  146. package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
  147. package/dist/features/events/components/AdminEventEditorView.js +8 -6
  148. package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
  149. package/dist/features/faq/components/FAQAccordion.js +29 -4
  150. package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
  151. package/dist/features/invoicing/schemas/firestore.js +45 -0
  152. package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
  153. package/dist/features/invoicing/utils/financial-year.js +53 -0
  154. package/dist/features/jobs/actions/enqueue-job.js +18 -1
  155. package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
  156. package/dist/features/jobs/repository/jobs.repository.js +84 -1
  157. package/dist/features/jobs/schemas/firestore.d.ts +80 -0
  158. package/dist/features/jobs/schemas/firestore.js +37 -0
  159. package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
  160. package/dist/features/lottery/schemas/zod.d.ts +6 -6
  161. package/dist/features/media/MediaImage.js +22 -1
  162. package/dist/features/media/upload/ImageUpload.js +1 -1
  163. package/dist/features/obligations/constants/policies.js +24 -1
  164. package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
  165. package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
  166. package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
  167. package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
  168. package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
  169. package/dist/features/orders/components/RaiseDisputeForm.js +111 -0
  170. package/dist/features/orders/components/RefundRequestView.js +22 -4
  171. package/dist/features/orders/components/index.d.ts +4 -0
  172. package/dist/features/orders/components/index.js +2 -0
  173. package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
  174. package/dist/features/orders/constants/order-lane-tabs.js +88 -0
  175. package/dist/features/orders/repository/orders.repository.d.ts +57 -0
  176. package/dist/features/orders/repository/orders.repository.js +145 -0
  177. package/dist/features/orders/schemas/firestore.d.ts +196 -1
  178. package/dist/features/orders/schemas/index.d.ts +60 -12
  179. package/dist/features/orders/schemas/index.js +12 -0
  180. package/dist/features/orders/types/index.d.ts +26 -0
  181. package/dist/features/orders/utils/courier-block.d.ts +25 -0
  182. package/dist/features/orders/utils/courier-block.js +23 -0
  183. package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
  184. package/dist/features/payment-cases/schemas/firestore.js +1 -0
  185. package/dist/features/products/components/GroupMemberPicker.js +4 -1
  186. package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
  187. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
  188. package/dist/features/products/components/ProductDetailActions.js +2 -1
  189. package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
  190. package/dist/features/products/components/ProductDetailPageView.js +2 -2
  191. package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
  192. package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
  193. package/dist/features/promotions/actions/coupon-stacking.js +21 -2
  194. package/dist/features/promotions/components/CouponCard.js +13 -0
  195. package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
  196. package/dist/features/promotions/constants/coupon-help.js +9 -1
  197. package/dist/features/promotions/schemas/firestore.d.ts +24 -0
  198. package/dist/features/promotions/schemas/firestore.js +4 -0
  199. package/dist/features/promotions/types/index.d.ts +19 -1
  200. package/dist/features/scams/components/ScamProfileView.js +2 -2
  201. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  202. package/dist/features/search/action-index/tab-entries.js +1 -1
  203. package/dist/features/seller/actions/seller-actions.js +33 -3
  204. package/dist/features/seller/components/FulfillmentView.js +3 -2
  205. package/dist/features/seller/components/SellerAddressesView.js +32 -4
  206. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +62 -3
  207. package/dist/features/seller/components/SellerBundlesView.js +53 -14
  208. package/dist/features/seller/components/SellerGroupedListingsView.js +23 -3
  209. package/dist/features/seller/components/SellerListingTypeView.js +56 -10
  210. package/dist/features/seller/components/SellerOrdersView.js +129 -27
  211. package/dist/features/seller/components/SellerPayoutMethodsView.js +33 -1
  212. package/dist/features/seller/components/SellerPayoutSettingsView.js +48 -9
  213. package/dist/features/seller/components/SellerProductsView.js +5 -1
  214. package/dist/features/seller/components/SellerShippingConfigsView.js +33 -1
  215. package/dist/features/seller/components/SellerShippingView.js +2 -1
  216. package/dist/features/seller/components/SellerStoreCategoriesView.js +33 -1
  217. package/dist/features/seller/components/SellerSupportView.js +69 -2
  218. package/dist/features/seller/hooks/useSellerListingData.js +25 -0
  219. package/dist/features/shell/SectionForm.js +20 -16
  220. package/dist/features/shell/build-sections.js +14 -1
  221. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +54 -1
  222. package/dist/features/shipments/components/AdminShipmentProjectionsView.js +3 -2
  223. package/dist/features/shipments/schemas/validation.d.ts +4 -4
  224. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +43 -0
  225. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +90 -0
  226. package/dist/features/shipping-boxes/schemas/firestore.d.ts +82 -0
  227. package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
  228. package/dist/features/sticker-trace/components/StickerCommitPanel.js +2 -1
  229. package/dist/features/sticker-trace/components/StickerPrintPanel.js +2 -1
  230. package/dist/features/sticker-trace/components/StickerRetryPanel.js +24 -4
  231. package/dist/features/sticker-trace/components/StickerTraceStudio.js +2 -1
  232. package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
  233. package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
  234. package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
  235. package/dist/features/stores/schemas/firestore.d.ts +71 -0
  236. package/dist/features/stores/types/index.d.ts +19 -0
  237. package/dist/features/support/schemas/index.d.ts +6 -6
  238. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
  239. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
  240. package/dist/index.d.ts +11 -2
  241. package/dist/index.js +17 -2
  242. package/dist/next/routing/route-map.d.ts +24 -0
  243. package/dist/next/routing/route-map.js +19 -0
  244. package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
  245. package/dist/providers/shipping-shiprocket/index.js +84 -1
  246. package/dist/schemas/registry.d.ts +51 -22
  247. package/dist/schemas/sieve.d.ts +5 -5
  248. package/dist/server-entry.d.ts +2 -2
  249. package/dist/server-entry.js +8 -1
  250. package/dist/server.d.ts +11 -2
  251. package/dist/server.js +40 -2
  252. package/dist/styles.css +64 -80
  253. package/dist/tailwind-utilities.css +1 -1
  254. package/dist/tokens/themes/default-dark.js +1 -0
  255. package/dist/tokens/themes/default-light.js +1 -0
  256. package/dist/tokens/themes/required.d.ts +1 -1
  257. package/dist/tokens/themes/required.js +1 -0
  258. package/dist/tokens/tokens.css +15 -0
  259. package/dist/ui/components/Checkbox.js +10 -2
  260. package/dist/ui/components/DateInput.js +8 -1
  261. package/dist/ui/components/Disclaimer.d.ts +19 -0
  262. package/dist/ui/components/Disclaimer.js +74 -0
  263. package/dist/ui/components/Input.js +22 -2
  264. package/dist/ui/components/PaginatedSelect.d.ts +8 -19
  265. package/dist/ui/components/PaginatedSelect.js +43 -9
  266. package/dist/ui/components/Radio.js +8 -1
  267. package/dist/ui/components/Select.js +10 -2
  268. package/dist/ui/components/TagInput.d.ts +8 -1
  269. package/dist/ui/components/TagInput.js +4 -3
  270. package/dist/ui/components/Textarea.js +10 -2
  271. package/dist/ui/components/tabs-overflow.js +64 -11
  272. package/dist/ui/forms/FieldCheckbox.js +1 -1
  273. package/dist/ui/forms/FormErrorList.d.ts +0 -16
  274. package/dist/ui/forms/FormErrorList.js +34 -1
  275. package/dist/ui/forms/apply-zod-issues.d.ts +19 -1
  276. package/dist/ui/forms/apply-zod-issues.js +23 -1
  277. package/dist/ui/index.d.ts +2 -0
  278. package/dist/ui/index.js +6 -0
  279. package/dist/ui/rich-text/RichText.style.css +48 -79
  280. package/package.json +1 -1
  281. package/scripts/seed-cli.mjs +1831 -1822
@@ -16,7 +16,18 @@ import { roundShippingUp, } from "../../../shared/shipping/quote.js";
16
16
  import { unitOfWork, adminNotificationsRepository, siteSettingsRepository, userRepository, storeRepository, couponsRepository, claimedCouponsRepository, addressesRepository, productRepository, } from "../../../../repositories/index.js";
17
17
  import { isIntraStateSupply } from "../../../shared/fees/calculator.js";
18
18
  import { resolveStateKey, INDIA_STATES } from "../../../shared/fees/india-states.js";
19
- import { computePreOrderDepositAmount, lineTotalFor, sumGroupGst, unitPriceFor, } from "../../../shared/checkout/order-math.js";
19
+ import { allocatePointsAcrossGroups, computePreOrderDepositAmount, lineTotalFor, resolvePointsTender, sumGroupGst, unitPriceFor, } from "../../../shared/checkout/order-math.js";
20
+ /*
21
+ * D46/D47 — points as a checkout TENDER.
22
+ *
23
+ * `storeAcceptsPoints` resolves decision 22's per-store opt-in (and the
24
+ * platform store's always-accepts, which is decided in code); the repository
25
+ * supplies the balance and owns the ledger write. The ARITHMETIC lives in
26
+ * `resolvePointsTender` above, beside every other money rule, so the tender
27
+ * cannot become a sixth disagreeing total (D37/D38/D40).
28
+ */
29
+ import { storeAcceptsPoints } from "../../../shared/features/points/accepts.js";
30
+ import { pointsRepository } from "../../../../features/points/repository/points.repository.js";
20
31
  import { COD_MAX_ORDER_VALUE_FALLBACK } from "../../../../features/admin/schemas/firestore.js";
21
32
  import { CHECKOUT_ERROR_CODES } from "../../../../errors/error-codes.js";
22
33
  import { failedCheckoutRepository } from "../../../../features/checkout/repository/failed-checkout.repository.js";
@@ -31,6 +42,9 @@ import { DEFAULT_STORE_SHIPPING_POLICY, quoteShipping, toStoreShippingPolicy, }
31
42
  */
32
43
  import { paymentCasesRepository } from "../../../../features/payment-cases/repository/payment-cases.repository.js";
33
44
  import { computeProtectionFees } from "../../../shared/features/shipping/protections.js";
45
+ /* S5a — one payer, N destinations. Pure: the rates come in snapshotted, and
46
+ * an UNPRICED arrangement is refused rather than quoted at ₹0 (D44's lesson). */
47
+ import { countDestinations, quoteSharedPurchase, } from "../../../shared/features/shipping/shared-purchase.js";
34
48
  import { resolveCouponCategorySlugs } from "../../../../features/promotions/actions/coupon-actions.js";
35
49
  import { assertCheckoutLane, assertLockedLinesStillValid, finalizeLockedLines, } from "./locked-lines.js";
36
50
  import { activeLane, defaultCheckoutItems, resolveCheckoutItems, laneOf, } from "../../../shared/checkout/lanes.js";
@@ -262,6 +276,10 @@ opts) {
262
276
  shippingPolicy: DEFAULT_STORE_SHIPPING_POLICY,
263
277
  shippingUnavailable: false,
264
278
  storeProtections: { shipping: false, sale: false },
279
+ // No store, so no seller to absorb a redemption.
280
+ acceptsPoints: false,
281
+ // No store, so nobody to pack N parcels.
282
+ sharedPurchaseRates: { allowSharedPurchase: false },
265
283
  // No store, so no seller registration — the caller falls back to the
266
284
  // platform's via resolveSupplierIdentity.
267
285
  storeTaxIdentity: undefined,
@@ -381,6 +399,13 @@ opts) {
381
399
  shipping: store?.supportsShippingProtection === true,
382
400
  sale: store?.supportsSaleProtection === true,
383
401
  },
402
+ acceptsPoints: storeAcceptsPoints(store, storeId),
403
+ // S5a — raw, so the caller can tell "not configured" from ₹0.
404
+ sharedPurchaseRates: {
405
+ allowSharedPurchase: store?.allowSharedPurchase === true,
406
+ sharedPurchaseHandlingFee: store?.sharedPurchaseHandlingFee,
407
+ sharedPurchaseExtraShippingPerDestination: store?.sharedPurchaseExtraShippingPerDestination,
408
+ },
384
409
  /*
385
410
  * The seller's own tax registration, returned from the store read this
386
411
  * function ALREADY does.
@@ -594,6 +619,112 @@ function dispatchOrderConfirmationEmails(emailsToSend) {
594
619
  return;
595
620
  Promise.all(emailsToSend.map((e) => sendOrderConfirmationEmail(e))).catch((err) => serverLogger.error("Order confirmation email error:", err));
596
621
  }
622
+ /**
623
+ * The buyer's points balance, or **0** if it cannot be read.
624
+ *
625
+ * 🛑 Failing OPEN to zero is the safe direction and the only safe direction: a
626
+ * balance we could not read becomes "no discount applied", so the buyer is
627
+ * charged the full amount and their points are untouched. The inverse — guessing
628
+ * a balance — would reduce the charge against points that may not exist, which
629
+ * is the shape of the deleted D46 block (it debited points and reduced nothing;
630
+ * this is the same mistake with the signs swapped).
631
+ *
632
+ * A buyer who opted in and sees no discount can retry. There is no recoverable
633
+ * outcome on the other side.
634
+ */
635
+ async function safePointsBalance(userId) {
636
+ try {
637
+ return await pointsRepository.getBalance(userId);
638
+ }
639
+ catch (err) {
640
+ const normalized = normalizeError(err);
641
+ serverLogger.warn("[checkout] points balance unreadable — charging full amount", {
642
+ userId,
643
+ error: normalized.message,
644
+ });
645
+ return 0;
646
+ }
647
+ }
648
+ /**
649
+ * Debit the points an order was discounted by — the second half of the tender.
650
+ *
651
+ * 🛑 ONE implementation, called from BOTH order-creation paths. They are
652
+ * ~200-line twins that have already drifted once (add-on fees, Root Cause
653
+ * #65), and a money rule reachable by copy-paste gets copied (Root Cause #75).
654
+ *
655
+ * Runs AFTER the order exists and BEFORE the gateway is asked for anything.
656
+ * That ordering is the whole D46 fix: the deleted block ran after capture, so a
657
+ * redemption that succeeded took points from a buyer who had just paid full
658
+ * price while its own comment claimed "the balance intact".
659
+ *
660
+ * 🛑 A REFUSED REDEEM REVERTS THE DISCOUNT; it is never swallowed. `redeem`
661
+ * re-reads the balance inside its own transaction and refuses rather than going
662
+ * negative — so a refusal means the budget was wrong and the order is recording
663
+ * a discount nobody funded. Reverting is the only honest response: the buyer
664
+ * pays full price, which they can see and query, where an unfunded discount is
665
+ * a silent under-collection on every order that hits it.
666
+ *
667
+ * `orderId` is the natural idempotency key — this runs once per created order,
668
+ * and an order is created once.
669
+ */
670
+ async function settlePointsTender(input) {
671
+ if (input.pointsApplied <= 0)
672
+ return;
673
+ try {
674
+ await pointsRepository.redeem({
675
+ userId: input.userId,
676
+ points: input.pointsApplied,
677
+ orderId: input.orderId,
678
+ description: `Points applied to order ${input.orderId}`,
679
+ });
680
+ }
681
+ catch (err) {
682
+ const normalized = normalizeError(err);
683
+ serverLogger.error("[checkout] points redemption refused — reverting the discount", {
684
+ orderId: input.orderId,
685
+ userId: input.userId,
686
+ pointsApplied: input.pointsApplied,
687
+ error: normalized.message,
688
+ });
689
+ // Hand the budget back, so a later group in the same checkout is not
690
+ // charged for a spend that did not happen.
691
+ input.pointsBudget.remaining += input.pointsApplied;
692
+ await unitOfWork.orders
693
+ .update(input.orderId, { pointsApplied: undefined, amountDue: undefined })
694
+ .catch((e) => {
695
+ /*
696
+ * 🛑 The one genuinely bad outcome, so it is reported rather than
697
+ * logged: the order still says `amountDue`, the points were never
698
+ * taken, and the gateway is about to be asked for the discounted
699
+ * figure. A case names the amount and the order; `safeRead`'s silent
700
+ * degrade would be wrong here because nothing downstream can detect it.
701
+ */
702
+ const inner = normalizeError(e);
703
+ serverLogger.error("[checkout] points revert FAILED — order under-collects", {
704
+ orderId: input.orderId,
705
+ amountUnfunded: input.pointsApplied,
706
+ error: inner.message,
707
+ });
708
+ void paymentCasesRepository()
709
+ .openOrObserve({
710
+ kind: "amount_mismatch",
711
+ // The problem is this order's unfunded discount, so the subject is
712
+ // the order — one case however many times it is observed.
713
+ subjectId: input.orderId,
714
+ severity: "error",
715
+ entityType: "order",
716
+ entityId: input.orderId,
717
+ orderIds: [input.orderId],
718
+ title: "Order carries a points discount that was never funded",
719
+ detail: `Redemption of ${input.pointsApplied} points was refused and the ` +
720
+ `revert also failed, so this order will be captured at its ` +
721
+ `discounted amountDue with no matching ledger debit.`,
722
+ amountAtRisk: input.pointsApplied,
723
+ })
724
+ .catch(() => undefined);
725
+ });
726
+ }
727
+ }
597
728
  /**
598
729
  * Places one order for one seller-group of the checkout cart — fee math
599
730
  * (COD deposit/handling, EMI schedule), coupon apportionment, order-doc
@@ -602,7 +733,7 @@ function dispatchOrderConfirmationEmails(emailsToSend) {
602
733
  * so the caller can accumulate the checkout-wide `total`.
603
734
  */
604
735
  async function createOrderForGroup(group, orderType, ctx) {
605
- const { paymentMethod, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, buyerState, gstSettings, siteContactUpiVpa, storeAddons, platformFeeByStore, selectedShippingQuote, } = ctx;
736
+ const { paymentMethod, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, buyerState, gstSettings, siteContactUpiVpa, storeAddons, platformFeeByStore, selectedShippingQuote, useAllPoints, pointsBudget, } = ctx;
606
737
  const firstItem = group[0].item;
607
738
  const firstProduct = group[0].product;
608
739
  // Add-ons are this store's choice, not the cart's — see OrderGroupContext.
@@ -650,7 +781,7 @@ async function createOrderForGroup(group, orderType, ctx) {
650
781
  * grouped line the two differ.
651
782
  */
652
783
  const groupGoodsValue = group.reduce((sum, { item, product }) => sum + lineTotalFor(item, product), 0);
653
- const { shippingFee: resolvedShippingFee, storeOwnerId, storeEmiEnabled, storeState, storeProtections, storeTaxIdentity } = await resolveShippingCost(firstItem.storeId, { goodsValue: groupGoodsValue });
784
+ const { shippingFee: resolvedShippingFee, storeOwnerId, storeEmiEnabled, storeState, storeProtections, storeTaxIdentity, acceptsPoints, sharedPurchaseRates } = await resolveShippingCost(firstItem.storeId, { goodsValue: groupGoodsValue });
654
785
  /*
655
786
  * 🛑 The buyer's quote WINS over the seller's configured rate, and it is
656
787
  * re-rounded here rather than trusted as sent.
@@ -818,6 +949,47 @@ async function createOrderForGroup(group, orderType, ctx) {
818
949
  groupCouponCodes.add(discount.code);
819
950
  }
820
951
  }
952
+ /*
953
+ * ── S5a: a shared purchase — one payer, N destinations ────────────────────
954
+ *
955
+ * Computed BEFORE `orderTotal` because it is a term in it. The rates come
956
+ * off the store document and are SNAPSHOTTED onto the order below: a
957
+ * coordinator commits against a quoted total, and re-reading a rate at
958
+ * settle would let a seller raise it afterwards. That is D61's rule, and the
959
+ * precedent is the refund path reading `finalSale` from the order snapshot
960
+ * for exactly this reason.
961
+ *
962
+ * 🛑 `countDestinations` counts DISTINCT addresses and collapses every line
963
+ * with no `destinationAddressId` into one bucket — the order's own address.
964
+ * Counting items instead would charge an ordinary three-item order for two
965
+ * extra legs it never asked for.
966
+ */
967
+ const sharedQuote = quoteSharedPurchase({
968
+ destinationCount: countDestinations(group.map(({ item }) => item)),
969
+ rates: sharedPurchaseRates,
970
+ });
971
+ /*
972
+ * 🛑 An UNPRICED arrangement is refused, not given away. The seller opted in
973
+ * without setting a rate, which is not consent to do the extra packing for
974
+ * free — and quoting ₹0 is exactly D44: `shippingUnavailable` carried a
975
+ * docstring saying "the caller MUST act on this rather than trusting
976
+ * `shippingFee: 0`", had zero consumers, and every `estimate`-mode store
977
+ * shipped free on every path as a result.
978
+ */
979
+ if (sharedQuote.unpriced) {
980
+ throw new ValidationError("This seller has not set a price for splitting an order across addresses. " +
981
+ "Send everything to one address, or contact the seller.");
982
+ }
983
+ /*
984
+ * A multi-destination order from a store that has NOT opted in. The quote
985
+ * prices; it does not authorise — so the refusal is here, where the buyer
986
+ * can be told something true and specific.
987
+ */
988
+ if (!sharedQuote.isShared &&
989
+ countDestinations(group.map(({ item }) => item)) > 1) {
990
+ throw new ValidationError("This seller ships one order to one address. Remove the extra delivery addresses, " +
991
+ "or order the items separately.");
992
+ }
821
993
  // The platform commission is now charged on EVERY payment method. It used to
822
994
  // be added only on the online-payment path, so COD / UPI-manual / cash / EMI
823
995
  // buyers silently never paid it.
@@ -848,7 +1020,7 @@ async function createOrderForGroup(group, orderType, ctx) {
848
1020
  * compounds nine half-paisa errors instead of one, which is how a total
849
1021
  * drifts from the sum of the lines shown beside it.
850
1022
  */
851
- roundRupees(Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + giftWrapFee + shipmentProtectionFee + saleProtectionFee + platformFee + platformFeeGst + (emiSchedule?.surchargeAmount ?? 0));
1023
+ roundRupees(Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + giftWrapFee + shipmentProtectionFee + saleProtectionFee + platformFee + platformFeeGst + (emiSchedule?.surchargeAmount ?? 0) + sharedQuote.total);
852
1024
  /*
853
1025
  * ── D43: the COD deposit/remaining split, on the ORDER TOTAL ──────────────
854
1026
  *
@@ -901,6 +1073,44 @@ async function createOrderForGroup(group, orderType, ctx) {
901
1073
  const displayedUpiId = hasPaymentWindow
902
1074
  ? await resolveDisplayedUpiId(firstItem.storeId, siteContactUpiVpa)
903
1075
  : undefined;
1076
+ /*
1077
+ * ── POINTS TENDER (D46/D47) — a STAGE, after `orderTotal` is rounded ──────
1078
+ *
1079
+ * The deleted code redeemed points AFTER the orders existed and after the
1080
+ * gateway had captured `orderTotal`, so a successful redemption took points
1081
+ * from a buyer who had just paid full price — while its own comment claimed
1082
+ * "the balance intact". This is the same feature done as a pipeline stage:
1083
+ * `amountDue` is derived here, and `amountDue` is what the gateway captures
1084
+ * and what the post-capture guard compares.
1085
+ *
1086
+ * 🛑 `useAllPoints` is the only client input and it is a BOOLEAN (decision
1087
+ * 24). The amount comes from the live balance and the 50% cap; a
1088
+ * client-supplied money figure is a client-supplied discount.
1089
+ *
1090
+ * 🛑 Per group, because the ACCEPTING store absorbs the redemption against
1091
+ * its own payout (decision 3). A cart spanning an accepting and a
1092
+ * non-accepting store must not pool the discount — so this reads
1093
+ * `acceptsPoints`, which `resolveShippingCost` resolved from the store
1094
+ * document it had already fetched.
1095
+ *
1096
+ * 🛑 `pointsBudget` is a SHARED MUTABLE REMAINDER, read once per checkout by
1097
+ * the caller and decremented here — the same shape as
1098
+ * `couponUsageAccumulator` and `platformFeeByStore`. Reading the balance
1099
+ * per group would let two groups each spend the whole balance: `redeem`
1100
+ * would then refuse the second one inside its transaction, leaving an order
1101
+ * recorded at a discount that was never funded. One budget, drawn down in
1102
+ * order, is what makes the sum of the groups equal what the buyer has.
1103
+ */
1104
+ const pointsOptedIn = useAllPoints === true;
1105
+ const pointsAccepted = pointsOptedIn && acceptsPoints;
1106
+ const { pointsApplied, amountDue } = resolvePointsTender({
1107
+ orderTotal,
1108
+ balance: pointsAccepted ? pointsBudget.remaining : 0,
1109
+ accepted: pointsAccepted,
1110
+ optedIn: pointsOptedIn,
1111
+ });
1112
+ if (pointsApplied > 0)
1113
+ pointsBudget.remaining -= pointsApplied;
904
1114
  // Both creation paths call the SAME builder — two hand-written copies is the
905
1115
  // create-vs-create axis of Root Cause #39, and these two functions have
906
1116
  // already drifted once before (add-on fees, Root Cause #65).
@@ -914,6 +1124,23 @@ async function createOrderForGroup(group, orderType, ctx) {
914
1124
  quantity: totalQuantity,
915
1125
  unitPrice: unitPriceFor(firstItem, firstProduct),
916
1126
  totalPrice: orderTotal,
1127
+ /*
1128
+ * D46/D47 — what the points paid for, and what is left to collect.
1129
+ *
1130
+ * 🛑 `totalPrice` stays the FULL order value and is not reduced. It is the
1131
+ * value of the supply, it is what the GST breakdown above was computed on,
1132
+ * and it is what `refundableBase` measures against — netting the points
1133
+ * off it would silently shrink the refundable base and the taxable value
1134
+ * at once. Points are a DISCOUNT settled against goods (decision 5), so
1135
+ * they reduce the amount COLLECTED, not the amount supplied.
1136
+ *
1137
+ * 🛑 Both are written only when non-zero, so an order placed without
1138
+ * points carries neither field and every reader's `amountDue ?? totalPrice`
1139
+ * fallback resolves to the right number on the 100% of historical orders
1140
+ * that predate this.
1141
+ */
1142
+ pointsApplied: pointsApplied > 0 ? pointsApplied : undefined,
1143
+ amountDue: pointsApplied > 0 ? amountDue : undefined,
917
1144
  currency: firstItem.currency ?? getDefaultCurrency(),
918
1145
  storeId: firstItem.storeId || undefined,
919
1146
  storeName: firstItem.storeName || undefined,
@@ -971,11 +1198,34 @@ async function createOrderForGroup(group, orderType, ctx) {
971
1198
  shipmentProtectionFee: !adminBypass && shipmentProtectionFee > 0 ? shipmentProtectionFee : undefined,
972
1199
  saleProtectionAddon: !adminBypass && saleProtectionFee > 0 ? true : undefined,
973
1200
  saleProtectionFee: !adminBypass && saleProtectionFee > 0 ? saleProtectionFee : undefined,
1201
+ /*
1202
+ * S5a — the arrangement, snapshotted. Written only when it IS a shared
1203
+ * purchase, so an ordinary order carries no field at all and every reader
1204
+ * can test presence rather than compare a count to 1.
1205
+ *
1206
+ * 🛑 The FEES AS CHARGED, not the store's current rates. That is the whole
1207
+ * point of a snapshot: an order refunded in six months must reverse what
1208
+ * was collected, not what the seller charges by then.
1209
+ */
1210
+ sharedPurchase: sharedQuote.isShared
1211
+ ? {
1212
+ destinationCount: countDestinations(group.map(({ item }) => item)),
1213
+ handlingFee: sharedQuote.handlingFee,
1214
+ extraShippingFee: sharedQuote.extraShippingFee,
1215
+ }
1216
+ : undefined,
974
1217
  taxableAmount: gstBreakdown?.taxableAmount,
975
1218
  /* Exempt goods are a reportable nil-rated supply, kept OUT of
976
1219
  * `taxableAmount` so the taxed base stays the base the tax was charged on,
977
1220
  * and recorded here so the invoice's columns can reconcile. */
978
1221
  exemptAmount: gstBreakdown?.exemptAmount || undefined,
1222
+ /*
1223
+ * Phase 7 — the place of supply, which is the INPUT the CGST/SGST-vs-IGST
1224
+ * split above was computed from. Only the split's RESULT was recorded
1225
+ * before, so an order could not state why it was taxed the way it was, and
1226
+ * GSTR-1's mandatory place-of-supply column had nothing to read.
1227
+ */
1228
+ placeOfSupply: buyerState || undefined,
979
1229
  /* The supplier of record AS IT STANDS NOW. A tax invoice without the
980
1230
  * supplier's GSTIN is not a tax invoice, and the invoice page cannot read
981
1231
  * it from site settings — `gst` is in PRIVATE_SITE_FIELDS. See the schema.
@@ -1039,6 +1289,9 @@ async function createOrderForGroup(group, orderType, ctx) {
1039
1289
  ...extraOrderFields,
1040
1290
  });
1041
1291
  orderIds.push(order.id);
1292
+ // The second half of the points tender — see `settlePointsTender`. Runs
1293
+ // before the caller asks the gateway for anything.
1294
+ await settlePointsTender({ orderId: order.id, userId: uid, pointsApplied, pointsBudget });
1042
1295
  // Close the loop on any locked line this order settled: flip the offer to
1043
1296
  // "paid" (its terminal status had NO server-side writer before 2026-08-21,
1044
1297
  // so an accepted offer could be re-added and re-ordered) and back-link the
@@ -1388,6 +1641,24 @@ export async function createCheckoutOrderAction(input) {
1388
1641
  g[0].item.storeId,
1389
1642
  g.reduce((sum, { item, product }) => sum + lineTotalFor(item, product), 0),
1390
1643
  ]), computeCheckoutFees(cartSubtotal, commissions));
1644
+ /*
1645
+ * ── The checkout's points budget, read ONCE (D46/D47) ─────────────────────
1646
+ *
1647
+ * 🛑 One read for the whole checkout, not one per order group. Rule #6
1648
+ * budgets ~3 sequential Firestore round-trips per request, and a cart
1649
+ * spanning four sellers would otherwise pay four reads to learn one number.
1650
+ *
1651
+ * 🛑 And ZERO when the buyer did not opt in, so an ordinary checkout performs
1652
+ * no points read at all — the common case must not pay for a feature it is
1653
+ * not using.
1654
+ *
1655
+ * 🛑 `adminBypass` is deliberately excluded: that path takes no payment, so
1656
+ * there is nothing for points to reduce, and debiting a buyer's balance for
1657
+ * an order the admin placed free would be a theft with an audit-log entry
1658
+ * saying otherwise.
1659
+ */
1660
+ const useAllPoints = cart.useAllPoints === true && !adminBypass;
1661
+ const pointsBudget = { remaining: useAllPoints ? await safePointsBalance(uid) : 0 };
1391
1662
  const groupCtx = {
1392
1663
  paymentMethod,
1393
1664
  emiTenureMonths,
@@ -1415,6 +1686,8 @@ export async function createCheckoutOrderAction(input) {
1415
1686
  platformFeeByStore,
1416
1687
  selectedShippingQuote: input.selectedShippingQuote,
1417
1688
  productById: checkoutProductById,
1689
+ useAllPoints,
1690
+ pointsBudget,
1418
1691
  };
1419
1692
  for (const { items: group, orderType } of orderGroups) {
1420
1693
  total += await createOrderForGroup(group, orderType, groupCtx);
@@ -1448,18 +1721,26 @@ export async function createCheckoutOrderAction(input) {
1448
1721
  * Deleting it leaves a promise with no mechanism, which is a disappointment.
1449
1722
  * Keeping it left a mechanism that silently takes points, which is a theft.
1450
1723
  *
1451
- * ## What the real tender has to do instead
1452
- *
1453
- * Points are a TENDER, so they belong in the pricing pipeline, not after it:
1454
- * a stage in `computeCheckoutTotal` AFTER rounding — `amountDue = total −
1455
- * pointsApplied` — with the gateway capturing `amountDue` and the
1456
- * post-capture guard comparing against `amountDue`. Anything else makes
1457
- * points a sixth way the captured amount can diverge from the recorded order
1458
- * total, which is the D37/D38/D40 family this plan exists to close.
1459
- *
1460
- * Everything that work needs now exists: `purchase_earn`, per-batch expiry,
1461
- * FIFO consumption, `redemption_reversal` and the seller-funded deduction.
1462
- * What is missing is the pipeline stage and the two document fields.
1724
+ * ## ✅ The real tender was BUILT 2026-10-22 — and it is not here
1725
+ *
1726
+ * 🛑 Do not look for it at this line. Points are a TENDER, so they are a
1727
+ * STAGE inside each order-creation path, immediately after that path's single
1728
+ * `roundRupees` and BEFORE anything is written or captured:
1729
+ *
1730
+ * - `resolvePointsTender` (`_internal/shared/checkout/order-math.ts`) —
1731
+ * the arithmetic, beside every other money rule.
1732
+ * - the stage in `createOrderForGroup` and `createPhonePeGroupOrder`,
1733
+ * which write `pointsApplied` + `amountDue` onto the order.
1734
+ * - `settlePointsTender` — ONE debit implementation shared by both paths,
1735
+ * running after the order exists so the discount and the debit are two
1736
+ * halves of one recorded fact, and reverting the discount if `redeem`
1737
+ * refuses.
1738
+ *
1739
+ * This note stays because the mistake is worth keeping, but a comment saying
1740
+ * a feature is "missing" while it sits 800 lines above is its own trap — it
1741
+ * is how `assertReturnWindowOpen` got reported as absent when my own comment
1742
+ * was shadowing the real answer. The accurate statement is: the tender is
1743
+ * built, and this is the wrong place for it.
1463
1744
  */
1464
1745
  dispatchOrderConfirmationEmails(emailsToSend);
1465
1746
  serverLogger.info(`createCheckoutOrderAction: ${orderIds.length} order(s) placed for uid=${uid}` +
@@ -1493,7 +1774,11 @@ const EMPTY_PRICING_PREVIEW = {
1493
1774
  couponDiscount: 0,
1494
1775
  platformFee: 0,
1495
1776
  gstOnFee: 0,
1777
+ sharedPurchaseFee: 0,
1496
1778
  total: 0,
1779
+ pointsApplied: 0,
1780
+ amountDue: 0,
1781
+ pointsBalance: 0,
1497
1782
  stores: [],
1498
1783
  };
1499
1784
  /**
@@ -1566,7 +1851,7 @@ export async function previewCheckoutPricing(input) {
1566
1851
  * and then the order path (equally unpassed) charged it. Preview and charge
1567
1852
  * agreed, and both were wrong in the same direction.
1568
1853
  */
1569
- const { shippingFee: groupShippingFee, storeState, storeProtections, storeEmiEnabled } = await resolveShippingCost(firstItem.storeId, { goodsValue: groupTotal });
1854
+ const { shippingFee: groupShippingFee, storeState, storeProtections, storeEmiEnabled, acceptsPoints, sharedPurchaseRates: previewSharedRates } = await resolveShippingCost(firstItem.storeId, { goodsValue: groupTotal });
1570
1855
  // Add-ons are per store, read off the cart document — the same entry
1571
1856
  // createOrderForGroup will read when the order is actually placed.
1572
1857
  const addons = cart.storeAddons?.[firstItem.storeId] ?? {};
@@ -1617,6 +1902,28 @@ export async function previewCheckoutPricing(input) {
1617
1902
  * seller cannot flip the switch after the sale).
1618
1903
  */
1619
1904
  const groupHasFinalSale = group.some(({ product }) => product?.finalSale === true);
1905
+ /*
1906
+ * ── S5a in the PREVIEW ───────────────────────────────────────────────────
1907
+ *
1908
+ * 🛑 Here because a coordinator commits against this figure. The plan is
1909
+ * explicit that the fees "must be shown before the coordinator commits" —
1910
+ * a handling fee plus N−1 shipping legs discovered afterwards is exactly
1911
+ * the surprise the arrangement makes expensive.
1912
+ *
1913
+ * The same function and the same `countDestinations` as both order paths,
1914
+ * so the shown figure and the charged one cannot diverge. D37 is the record
1915
+ * of what ONE money term reaching three of four paths costs: the buyer was
1916
+ * shown one number, charged another, and the order recorded the first.
1917
+ *
1918
+ * 🛑 A preview NEVER throws, so an unpriced arrangement contributes 0 here
1919
+ * rather than raising — `quoteSharedPurchase` reports it on `unpriced` and
1920
+ * the ORDER path is what refuses. A preview that threw would render the
1921
+ * whole cart as an error page for a seller's missing config.
1922
+ */
1923
+ const groupSharedPurchaseFee = quoteSharedPurchase({
1924
+ destinationCount: countDestinations(group.map(({ item }) => item)),
1925
+ rates: previewSharedRates,
1926
+ }).total;
1620
1927
  const { shippingProtectionFee: groupShipmentProtectionFee, saleProtectionFee: groupSaleProtectionFee, } = computeProtectionFees({
1621
1928
  subtotal: groupTotal,
1622
1929
  selected: {
@@ -1701,10 +2008,19 @@ export async function previewCheckoutPricing(input) {
1701
2008
  groupEmiSurcharge +
1702
2009
  groupGiftWrapFee +
1703
2010
  groupShipmentProtectionFee +
1704
- groupSaleProtectionFee,
2011
+ groupSaleProtectionFee +
2012
+ // S5a — a term in the per-store total, so the seller card and the
2013
+ // order it previews agree.
2014
+ groupSharedPurchaseFee,
1705
2015
  supportsShippingProtection: storeProtections.shipping,
1706
2016
  supportsSaleProtection: storeProtections.sale,
1707
2017
  hasFinalSaleItem: groupHasFinalSale,
2018
+ acceptsPoints,
2019
+ sharedPurchaseFee: groupSharedPurchaseFee,
2020
+ // Filled by the tender pass below — it needs each store's share of the
2021
+ // one capped commission, which is only allocatable once every group is
2022
+ // known.
2023
+ pointsApplied: 0,
1708
2024
  });
1709
2025
  }
1710
2026
  const sum = (pick) => stores.reduce((acc, s) => acc + pick(s), 0);
@@ -1714,11 +2030,21 @@ export async function previewCheckoutPricing(input) {
1714
2030
  const giftWrapFeeTotal = sum((s) => s.giftWrapFee);
1715
2031
  const shipmentProtectionFeeTotal = sum((s) => s.shipmentProtectionFee);
1716
2032
  const saleProtectionFeeTotal = sum((s) => s.saleProtectionFee);
2033
+ // S5a — summed from the same per-store figures, so the aggregate cannot
2034
+ // disagree with the seller cards it is built from.
2035
+ const sharedPurchaseFeeTotal = sum((s) => s.sharedPurchaseFee);
1717
2036
  const gstAmount = sum((s) => s.gstAmount);
1718
2037
  const couponDiscount = sum((s) => s.couponDiscount);
1719
2038
  // One capped commission for the whole checkout, on every payment method —
1720
2039
  // deliberately not per store, so it is not added to the per-store rows above.
1721
- const { platformFee, gstOnFee } = computeCheckoutFees(cartSubtotal, commissions);
2040
+ // The whole object is kept (not just the two fields) because the points
2041
+ // tender below re-allocates it per store through `allocateCheckoutFees`,
2042
+ // which takes a `CheckoutFees` — and `totalFee` carries the
2043
+ // `minimumTransactionFee` floor, which is exactly the field D41 found had no
2044
+ // consumer anywhere. Destructuring it away here would be how it loses its
2045
+ // second one.
2046
+ const checkoutFees = computeCheckoutFees(cartSubtotal, commissions);
2047
+ const { platformFee, gstOnFee } = checkoutFees;
1722
2048
  /*
1723
2049
  * 🛑 `gstAmount` is NOT a term, and leaving it in was a shown-vs-charged
1724
2050
  * drift waiting on a settings flag.
@@ -1751,8 +2077,52 @@ export async function previewCheckoutPricing(input) {
1751
2077
  giftWrapFeeTotal +
1752
2078
  shipmentProtectionFeeTotal +
1753
2079
  saleProtectionFeeTotal +
2080
+ // S5a — a real additive charge, so it belongs in the column with the other
2081
+ // fees rather than below the total like the tax-inclusive `gstAmount`.
2082
+ sharedPurchaseFeeTotal +
1754
2083
  platformFee +
1755
2084
  gstOnFee;
2085
+ /*
2086
+ * ── THE POINTS TENDER, PREVIEWED (D46/D47) ────────────────────────────────
2087
+ *
2088
+ * 🛑 Applied PER STORE against the SAME base the order path uses, which is
2089
+ * why this runs here and not inside the loop above: the base includes each
2090
+ * store's share of the one capped commission, and that share is only
2091
+ * allocatable once every group is known.
2092
+ *
2093
+ * 🛑 Per store is not a detail — it is the only shape that can agree with
2094
+ * what gets charged. A cart-level application gives the same answer as the
2095
+ * per-group one whenever every store accepts (the sum of per-group 50% caps
2096
+ * equals 50% of the sum), and a DIFFERENT answer the moment one store does
2097
+ * not: with ₹500 at an accepting store, ₹500 at a declining one and a 600
2098
+ * balance, per-store applies ₹250 and cart-level would promise ₹500. The
2099
+ * buyer would be shown a discount half of which could never be honoured.
2100
+ *
2101
+ * The iteration order matches `orderGroups`, and so does the order-creation
2102
+ * loop, so the budget is drawn down in the same sequence in both — which is
2103
+ * what makes the two sums identical rather than merely close.
2104
+ */
2105
+ const previewUseAllPoints = cart.useAllPoints === true;
2106
+ const previewBalance = previewUseAllPoints ? await safePointsBalance(uid) : 0;
2107
+ if (previewUseAllPoints && previewBalance > 0) {
2108
+ const feeByStore = allocateCheckoutFees(stores.map((s) => [s.storeId, s.subtotal]), checkoutFees);
2109
+ const { perGroup } = allocatePointsAcrossGroups(stores.map((s) => {
2110
+ const share = feeByStore.get(s.storeId);
2111
+ return {
2112
+ // The SAME base `createOrderForGroup` applies the tender to: the
2113
+ // store's charged total plus its share of the one capped commission.
2114
+ // `store.total` deliberately excludes that share (it is a checkout
2115
+ // charge, not a per-store one), so it is added back here rather than
2116
+ // by widening a published field.
2117
+ total: roundRupees(s.total + (share?.platformFee ?? 0) + (share?.gstOnFee ?? 0)),
2118
+ accepted: s.acceptsPoints,
2119
+ };
2120
+ }), previewBalance, true);
2121
+ perGroup.forEach((applied, i) => {
2122
+ stores[i].pointsApplied = applied;
2123
+ });
2124
+ }
2125
+ const pointsApplied = sum((s) => s.pointsApplied);
1756
2126
  return {
1757
2127
  subtotal: cartSubtotal,
1758
2128
  shippingFee,
@@ -1761,11 +2131,17 @@ export async function previewCheckoutPricing(input) {
1761
2131
  giftWrapFee: giftWrapFeeTotal,
1762
2132
  shipmentProtectionFee: shipmentProtectionFeeTotal,
1763
2133
  saleProtectionFee: saleProtectionFeeTotal,
2134
+ sharedPurchaseFee: sharedPurchaseFeeTotal,
1764
2135
  gstAmount,
1765
2136
  couponDiscount,
1766
2137
  platformFee,
1767
2138
  gstOnFee,
1768
2139
  total,
2140
+ pointsApplied,
2141
+ // Equal to `total` when no points apply, so every existing reader of
2142
+ // either figure keeps getting the right number.
2143
+ amountDue: roundRupees(total - pointsApplied),
2144
+ pointsBalance: previewBalance,
1769
2145
  stores,
1770
2146
  };
1771
2147
  }
@@ -1916,7 +2292,7 @@ async function refundDroppedItemsForPhonePeCheckout(input) {
1916
2292
  * from the inline version this replaced.
1917
2293
  */
1918
2294
  async function createPhonePeGroupOrder(group, orderType, ctx) {
1919
- const { appliedCoupons, cartSubtotal, storeAddons, platformFeeByStore, siteSettings, uid, userName, userEmail, merchantOrderId, phonepeOrderId, transactionId, shippingAddress, notes, outOfStockPolicy, unavailablePaid, orderIds, emailsToSend, couponUsageAccumulator, productById: phonepeProductById, buyerState, } = ctx;
2295
+ const { appliedCoupons, cartSubtotal, storeAddons, platformFeeByStore, siteSettings, uid, userName, userEmail, merchantOrderId, phonepeOrderId, transactionId, shippingAddress, notes, outOfStockPolicy, unavailablePaid, orderIds, emailsToSend, couponUsageAccumulator, productById: phonepeProductById, buyerState, useAllPoints, pointsBudget, } = ctx;
1920
2296
  /*
1921
2297
  * Which online gateway actually took the money, read off the registered
1922
2298
  * provider rather than passed in — exactly one is registered per process
@@ -1932,7 +2308,7 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
1932
2308
  // seller group here, duplicated from resolveShippingCost.
1933
2309
  // D44: `groupTotal` is computed just above — pass it so a free-above
1934
2310
  // threshold can fire and an unpriceable store is recorded.
1935
- const { shippingFee, storeOwnerId, storeState, storeTaxIdentity, storeProtections } = await resolveShippingCost(firstItem.storeId, { goodsValue: groupTotal });
2311
+ const { shippingFee, storeOwnerId, storeState, storeTaxIdentity, storeProtections, acceptsPoints, sharedPurchaseRates } = await resolveShippingCost(firstItem.storeId, { goodsValue: groupTotal });
1936
2312
  /*
1937
2313
  * Same supplier-of-record rule as `createOrderForGroup`, through the same
1938
2314
  * function. This path (PhonePe verify / webhook) is the SECOND place an
@@ -2050,9 +2426,51 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
2050
2426
  /* Product GST is not a term — prices are tax-inclusive, so it is already
2051
2427
  * inside `groupTotal`. Matches the COD/UPI path exactly; `platformFeeGst`
2052
2428
  * stays because that is GST on our fee, charged on top. */
2429
+ /*
2430
+ * ── S5a: the shared-purchase arrangement, matching the COD/manual path ────
2431
+ *
2432
+ * Same function, same inputs, same position — before `orderTotal`, because
2433
+ * it is a term in it. See the fuller 🛑 at the COD/manual site for why the
2434
+ * rates are snapshotted and why an unpriced arrangement is refused rather
2435
+ * than quoted at ₹0.
2436
+ *
2437
+ * 🛑 On THIS path the money is already captured by the time this runs, so a
2438
+ * refusal here means a charged-with-no-order — the D64 class. That is why
2439
+ * `/api/payment/create-order` must apply the same term: the pre-payment
2440
+ * amount and this total have to agree, which is the parity C0c defect 1 was
2441
+ * about and the reason every addition here has to be made in three places.
2442
+ */
2443
+ const phonepeSharedQuote = quoteSharedPurchase({
2444
+ destinationCount: countDestinations(group.map(({ item }) => item)),
2445
+ rates: sharedPurchaseRates,
2446
+ });
2053
2447
  // D42: rounded once on the sum — see the COD/UPI path's 🛑 for why a raw
2054
2448
  // float sum of seven 2dp terms reached Firestore as 1011.8000000000001.
2055
- const orderTotal = roundRupees(Math.max(0, groupTotal - couponDiscount) + shippingFee + giftWrapFee + shipmentProtectionFee + saleProtectionFee + platformFee + platformFeeGst);
2449
+ const orderTotal = roundRupees(Math.max(0, groupTotal - couponDiscount) + shippingFee + giftWrapFee + shipmentProtectionFee + saleProtectionFee + platformFee + platformFeeGst + phonepeSharedQuote.total);
2450
+ /*
2451
+ * ── POINTS TENDER (D46/D47) — the same STAGE as the COD/manual path ───────
2452
+ *
2453
+ * Same four inputs, same function, same point in the sequence: immediately
2454
+ * after `orderTotal`'s single `roundRupees` and before anything is written.
2455
+ * Computing it here rather than in a shared builder is unavoidable — the two
2456
+ * paths derive `orderTotal` separately — but the ARITHMETIC is
2457
+ * `resolvePointsTender` in both, so they cannot disagree about the amount
2458
+ * the way the add-on fees once did (Root Cause #65).
2459
+ *
2460
+ * 🛑 This is the path whose capture matters most: `amountDue` is what the
2461
+ * Razorpay order is created for, so a divergence here is money taken that
2462
+ * the order does not record.
2463
+ */
2464
+ const pointsOptedIn = useAllPoints === true;
2465
+ const pointsAccepted = pointsOptedIn && acceptsPoints;
2466
+ const { pointsApplied, amountDue } = resolvePointsTender({
2467
+ orderTotal,
2468
+ balance: pointsAccepted ? pointsBudget.remaining : 0,
2469
+ accepted: pointsAccepted,
2470
+ optedIn: pointsOptedIn,
2471
+ });
2472
+ if (pointsApplied > 0)
2473
+ pointsBudget.remaining -= pointsApplied;
2056
2474
  // S-SBUNI-RULES 2026-05-13 — order-item decoration via rule registry.
2057
2475
  // Same expansion as the COD/UPI path. This was a THIRD hand-rolled copy of
2058
2476
  // the price rule (`isBundle ? item.price : product.price`) that — like the
@@ -2089,6 +2507,10 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
2089
2507
  quantity: totalQuantity,
2090
2508
  unitPrice: group[0].product.price,
2091
2509
  totalPrice: orderTotal,
2510
+ // D46/D47 — see the identical pair in `createOrderForGroup`. `totalPrice`
2511
+ // stays the full value of the supply; points reduce what is COLLECTED.
2512
+ pointsApplied: pointsApplied > 0 ? pointsApplied : undefined,
2513
+ amountDue: pointsApplied > 0 ? amountDue : undefined,
2092
2514
  currency: firstItem.currency ?? getDefaultCurrency(),
2093
2515
  storeId: firstItem.storeId || undefined,
2094
2516
  storeName: firstItem.storeName || undefined,
@@ -2120,7 +2542,15 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
2120
2542
  paymentRecord: {
2121
2543
  method: gatewayName === "razorpay" ? "razorpay" : "phonepe",
2122
2544
  transactionId: transactionId ?? merchantOrderId,
2123
- amount: orderTotal,
2545
+ /*
2546
+ * 🛑 What the gateway actually CAPTURED, which is `amountDue` once points
2547
+ * are applied — not the order's value. This record is the evidence a
2548
+ * reconciler compares against the gateway's own figure (Phase 4 R2), so
2549
+ * writing `orderTotal` here would open an `amount_mismatch` case on every
2550
+ * points-funded order: the discount would read as money we failed to
2551
+ * collect.
2552
+ */
2553
+ amount: amountDue,
2124
2554
  paidAt: new Date(),
2125
2555
  verifiedBy: `${gatewayName}-order-status`,
2126
2556
  verificationMethod: "order-status",
@@ -2148,6 +2578,14 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
2148
2578
  // Recorded as well as charged — an order that collects a protection fee
2149
2579
  // without a record of it cannot be reconciled against a later claim.
2150
2580
  saleProtectionFee: saleProtectionFee > 0 ? saleProtectionFee : undefined,
2581
+ // S5a — snapshotted, matching the COD/manual path field for field.
2582
+ sharedPurchase: phonepeSharedQuote.isShared
2583
+ ? {
2584
+ destinationCount: countDestinations(group.map(({ item }) => item)),
2585
+ handlingFee: phonepeSharedQuote.handlingFee,
2586
+ extraShippingFee: phonepeSharedQuote.extraShippingFee,
2587
+ }
2588
+ : undefined,
2151
2589
  /*
2152
2590
  * The tax breakdown, matching `createOrderForGroup` field for field.
2153
2591
  * This path recorded NONE of it before C0c defect 1 — so an online order
@@ -2158,6 +2596,10 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
2158
2596
  */
2159
2597
  taxableAmount: phonepeGst?.taxableAmount,
2160
2598
  exemptAmount: phonepeGst?.exemptAmount || undefined,
2599
+ // Phase 7 — the place of supply, matching `createOrderForGroup` field for
2600
+ // field. This path recorded NONE of the tax breakdown until C0c defect 1,
2601
+ // which is precisely why every addition here has to be made twice.
2602
+ placeOfSupply: buyerState || undefined,
2161
2603
  // Our registration at the moment of supply — see the schema. Mirrors the
2162
2604
  // COD/UPI path so both produce an invoiceable order.
2163
2605
  supplierGstin: supplier.gstin,
@@ -2177,6 +2619,9 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
2177
2619
  ...extraOrderFields,
2178
2620
  });
2179
2621
  orderIds.push(order.id);
2622
+ // The second half of the points tender — one implementation, shared with the
2623
+ // COD/manual path. See `settlePointsTender`.
2624
+ await settlePointsTender({ orderId: order.id, userId: uid, pointsApplied, pointsBudget });
2180
2625
  // Close the loop on any locked line this order settled: flip the offer to
2181
2626
  // "paid" (its terminal status had NO server-side writer before 2026-08-21,
2182
2627
  // so an accepted offer could be re-added and re-ordered) and back-link the
@@ -2538,7 +2983,12 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
2538
2983
  // percentages, so the moment the cap landed it would have expected more
2539
2984
  // than was actually collected and rejected every legitimate payment.
2540
2985
  const commissionRates = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
2541
- const { platformFee: expectedPlatformFee, gstOnFee: expectedGstOnFee } = computeCheckoutFees(cartSubtotalRs, commissionRates);
2986
+ // The whole object is kept so the points tender below can re-allocate it
2987
+ // per group — see the same note in `previewCheckoutPricing`. `totalFee`
2988
+ // carries the `minimumTransactionFee` floor, and destructuring it away is
2989
+ // how it lost every consumer once already (D41).
2990
+ const expectedFees = computeCheckoutFees(cartSubtotalRs, commissionRates);
2991
+ const { platformFee: expectedPlatformFee, gstOnFee: expectedGstOnFee } = expectedFees;
2542
2992
  // Same per-seller-group shipping sum /api/payment/create-order charged
2543
2993
  // upfront (and the same resolveShippingCost createPhonePeGroupOrder
2544
2994
  // below will use to build each order's recorded totalPrice) — keeps this
@@ -2624,11 +3074,62 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
2624
3074
  * would make this guard demand ~₹580 more than the buyer owes on a
2625
3075
  * ₹5,547 cart — rejecting every correctly-charged order.
2626
3076
  */
2627
- const expectedPaymentAmountRs = expectedDiscountedSubtotal +
3077
+ /*
3078
+ * ── D46/D47 — the guard must expect the amount NET OF POINTS ─────────────
3079
+ *
3080
+ * 🛑 Omitting this term would make the guard reject every correctly-charged
3081
+ * points order, which is worse than the overcharge it exists to catch: the
3082
+ * money is already captured by the time this runs, so a false rejection is
3083
+ * a charged-with-no-order — the D64 class.
3084
+ *
3085
+ * The bases are rebuilt here from the same terms in the same order, and the
3086
+ * draw-down is the same shared allocator, so the only way this can diverge
3087
+ * from the capture route is a change made to one and not the other. That is
3088
+ * precisely the drift the four-way export comment on
3089
+ * `allocatePointsAcrossGroups` exists to make visible.
3090
+ */
3091
+ const guardUseAllPoints = cart.useAllPoints === true;
3092
+ let expectedPointsApplied = 0;
3093
+ if (guardUseAllPoints) {
3094
+ const guardBalance = await safePointsBalance(uid);
3095
+ if (guardBalance > 0) {
3096
+ const guardSubtotals = expectedShippingGroups.map((g) => [
3097
+ g.items[0].item.storeId,
3098
+ g.items.reduce((gs, { item, product }) => gs + lineTotalFor(item, product), 0),
3099
+ ]);
3100
+ const guardFeeByGroup = allocateCheckoutFees(guardSubtotals, expectedFees);
3101
+ const guardCouponByGroup = computeCartCouponDiscount(cart.appliedCoupons ?? [], expectedShippingGroups.map((g) => ({
3102
+ storeId: g.items[0].item.storeId,
3103
+ lines: g.items.map(({ item, product }) => ({
3104
+ itemId: item.itemId,
3105
+ lineTotal: lineTotalFor(item, product),
3106
+ })),
3107
+ }))).perGroup;
3108
+ const guardBases = expectedShippingGroups.map((g, i) => {
3109
+ const storeId = g.items[0].item.storeId;
3110
+ const addons = cart.storeAddons?.[storeId] ?? {};
3111
+ const goods = guardSubtotals[i][1];
3112
+ const share = guardFeeByGroup.get(storeId);
3113
+ const groupAddons = computeGiftWrapFee(addons.giftWrapAddon ?? false, commissionRates) +
3114
+ computeShipmentProtectionFee(goods, addons.shipmentProtectionAddon ?? false, commissionRates);
3115
+ return {
3116
+ total: roundRupees(Math.max(0, goods - (guardCouponByGroup[i]?.couponDiscount ?? 0)) +
3117
+ (expectedShippingFees[i]?.shippingFee ?? 0) +
3118
+ groupAddons +
3119
+ (share?.platformFee ?? 0) +
3120
+ (share?.gstOnFee ?? 0)),
3121
+ accepted: expectedShippingFees[i]?.acceptsPoints === true,
3122
+ };
3123
+ });
3124
+ expectedPointsApplied = allocatePointsAcrossGroups(guardBases, guardBalance, true).pointsApplied;
3125
+ }
3126
+ }
3127
+ const expectedPaymentAmountRs = roundRupees(Math.max(0, expectedDiscountedSubtotal +
2628
3128
  expectedPlatformFee +
2629
3129
  expectedGstOnFee +
2630
3130
  expectedAddonFees +
2631
- expectedShippingFee;
3131
+ expectedShippingFee -
3132
+ expectedPointsApplied));
2632
3133
  // Reuses the order already fetched above to confirm COMPLETED — PhonePe's
2633
3134
  // own amount is in its wire-format smallest unit. // audit-money-units-ok: describes the PhonePe boundary conversion this line performs, not our storage convention
2634
3135
  const paidAmountRs = paiseToRupees(gatewayOrder.amount);
@@ -2703,6 +3204,19 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
2703
3204
  g[0].item.storeId,
2704
3205
  g.reduce((sum, { item, product }) => sum + lineTotalFor(item, product), 0),
2705
3206
  ]), computeCheckoutFees(cartSubtotal, siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS));
3207
+ /*
3208
+ * The points budget for this checkout — one read, zero when not opted in.
3209
+ * See the identical pair in the COD/manual path for why it is a shared
3210
+ * remainder rather than a per-group balance read.
3211
+ *
3212
+ * 🛑 On THIS path the money has already been captured by the time these
3213
+ * orders are created, so the budget must match whatever the pre-payment
3214
+ * amount was computed against. That is why `useAllPoints` is read off the
3215
+ * cart here too and not re-decided: the figure quoted to the gateway and the
3216
+ * figure recorded on the orders come from the same cart flag.
3217
+ */
3218
+ const useAllPoints = cart.useAllPoints === true;
3219
+ const pointsBudget = { remaining: useAllPoints ? await safePointsBalance(uid) : 0 };
2706
3220
  for (const { items: group, orderType } of orderGroups) {
2707
3221
  total += await createPhonePeGroupOrder(group, orderType, {
2708
3222
  appliedCoupons,
@@ -2728,6 +3242,8 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
2728
3242
  emailsToSend,
2729
3243
  couponUsageAccumulator,
2730
3244
  productById: productByIdPaid,
3245
+ useAllPoints,
3246
+ pointsBudget,
2731
3247
  });
2732
3248
  }
2733
3249
  await flushCouponUsageAccumulator(couponUsageAccumulator, uid);