@mohasinac/appkit 4.62.1 → 4.63.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 (261) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -29
  3. package/dist/_internal/client/features/layout/filterNavItems.d.ts +28 -0
  4. package/dist/_internal/client/features/layout/filterNavItems.js +35 -0
  5. package/dist/_internal/server/features/ads/booking.d.ts +118 -0
  6. package/dist/_internal/server/features/ads/booking.js +326 -0
  7. package/dist/_internal/server/features/checkout/actions.d.ts +51 -11
  8. package/dist/_internal/server/features/checkout/actions.js +335 -80
  9. package/dist/_internal/server/features/checkout/expand-order-items.d.ts +1 -2
  10. package/dist/_internal/server/features/checkout/expand-order-items.js +32 -0
  11. package/dist/_internal/server/features/checkout/freeze-parts.d.ts +50 -0
  12. package/dist/_internal/server/features/checkout/freeze-parts.js +94 -0
  13. package/dist/_internal/server/features/checkout/value-otp-gate.d.ts +55 -0
  14. package/dist/_internal/server/features/checkout/value-otp-gate.js +60 -0
  15. package/dist/_internal/server/features/dashboard/attention.d.ts +128 -0
  16. package/dist/_internal/server/features/dashboard/attention.js +183 -0
  17. package/dist/_internal/server/features/dashboard/data.d.ts +47 -0
  18. package/dist/_internal/server/features/dashboard/data.js +82 -0
  19. package/dist/_internal/server/features/inventory/reservations.d.ts +99 -0
  20. package/dist/_internal/server/features/inventory/reservations.js +273 -0
  21. package/dist/_internal/server/features/offers/destinations.d.ts +40 -0
  22. package/dist/_internal/server/features/offers/destinations.js +65 -0
  23. package/dist/_internal/server/features/orders/adapters.js +21 -0
  24. package/dist/_internal/server/features/orders/amend-delivery.d.ts +56 -0
  25. package/dist/_internal/server/features/orders/amend-delivery.js +124 -0
  26. package/dist/_internal/server/features/orders/balance.d.ts +89 -0
  27. package/dist/_internal/server/features/orders/balance.js +256 -0
  28. package/dist/_internal/server/features/pricing/actions.d.ts +58 -0
  29. package/dist/_internal/server/features/pricing/actions.js +144 -0
  30. package/dist/_internal/server/features/shipping/book-split.d.ts +86 -0
  31. package/dist/_internal/server/features/shipping/book-split.js +130 -0
  32. package/dist/_internal/server/features/shipping/book.d.ts +61 -0
  33. package/dist/_internal/server/features/shipping/book.js +176 -0
  34. package/dist/_internal/server/features/shipping/courier-block.js +26 -1
  35. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  36. package/dist/_internal/server/features/site-settings/adapters.js +14 -0
  37. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  38. package/dist/_internal/server/features/stores/adapters.js +14 -0
  39. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  40. package/dist/_internal/server/functions/scheduled.js +57 -1
  41. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +2 -0
  42. package/dist/_internal/server/jobs/core/jobRunners.js +45 -0
  43. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +4 -1
  44. package/dist/_internal/server/jobs/core/reservationExpiry.d.ts +17 -0
  45. package/dist/_internal/server/jobs/core/reservationExpiry.js +40 -0
  46. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +15 -0
  47. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  48. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  49. package/dist/_internal/server/jobs/handlers/reservationExpiry.d.ts +2 -0
  50. package/dist/_internal/server/jobs/handlers/reservationExpiry.js +2 -0
  51. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  52. package/dist/_internal/shared/actions/action-registry.js +268 -0
  53. package/dist/_internal/shared/checkout/order-math.d.ts +31 -2
  54. package/dist/_internal/shared/checkout/order-math.js +22 -36
  55. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  56. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  57. package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +18 -0
  58. package/dist/_internal/shared/checkout/rules/payment-gate.js +25 -0
  59. package/dist/_internal/shared/checkout/split.d.ts +0 -12
  60. package/dist/_internal/shared/checkout/split.js +22 -2
  61. package/dist/_internal/shared/content/disclaimers.js +7 -7
  62. package/dist/_internal/shared/content/published-values.d.ts +6 -1
  63. package/dist/_internal/shared/content/published-values.js +8 -0
  64. package/dist/_internal/shared/features/admin/integrations.d.ts +32 -2
  65. package/dist/_internal/shared/features/admin/integrations.js +19 -9
  66. package/dist/_internal/shared/features/dashboard/hub-sections.d.ts +72 -0
  67. package/dist/_internal/shared/features/dashboard/hub-sections.js +75 -0
  68. package/dist/_internal/shared/features/emi/choice.d.ts +98 -0
  69. package/dist/_internal/shared/features/emi/choice.js +69 -0
  70. package/dist/_internal/shared/features/emi/group.d.ts +84 -0
  71. package/dist/_internal/shared/features/emi/group.js +126 -0
  72. package/dist/_internal/shared/features/ledger/posting.d.ts +107 -0
  73. package/dist/_internal/shared/features/ledger/posting.js +165 -0
  74. package/dist/_internal/shared/features/ledger/reports.d.ts +76 -0
  75. package/dist/_internal/shared/features/ledger/reports.js +127 -0
  76. package/dist/_internal/shared/features/ledger/rules.d.ts +170 -0
  77. package/dist/_internal/shared/features/ledger/rules.js +221 -0
  78. package/dist/_internal/shared/features/offers/coordination.d.ts +122 -0
  79. package/dist/_internal/shared/features/offers/coordination.js +80 -0
  80. package/dist/_internal/shared/features/orders/delivery-snapshot.d.ts +114 -0
  81. package/dist/_internal/shared/features/orders/delivery-snapshot.js +116 -0
  82. package/dist/_internal/shared/features/payouts/pay-period.d.ts +91 -0
  83. package/dist/_internal/shared/features/payouts/pay-period.js +104 -0
  84. package/dist/_internal/shared/features/points/accepts.d.ts +1 -8
  85. package/dist/_internal/shared/features/points/accepts.js +12 -6
  86. package/dist/_internal/shared/features/pricing/rate-registry.d.ts +150 -0
  87. package/dist/_internal/shared/features/pricing/rate-registry.js +246 -0
  88. package/dist/_internal/shared/features/shipping/parcel.d.ts +123 -0
  89. package/dist/_internal/shared/features/shipping/parcel.js +139 -0
  90. package/dist/_internal/shared/features/site-settings/fees.d.ts +16 -8
  91. package/dist/_internal/shared/features/site-settings/fees.js +12 -8
  92. package/dist/_internal/shared/fees/explain.d.ts +81 -0
  93. package/dist/_internal/shared/fees/explain.js +101 -0
  94. package/dist/_internal/shared/history/index.d.ts +1 -1
  95. package/dist/_internal/shared/history/index.js +1 -1
  96. package/dist/_internal/shared/history/types.d.ts +7 -1
  97. package/dist/_internal/shared/history/types.js +25 -0
  98. package/dist/client.d.ts +11 -1
  99. package/dist/client.js +13 -0
  100. package/dist/constants/api-endpoints.d.ts +72 -0
  101. package/dist/constants/api-endpoints.js +41 -0
  102. package/dist/constants/field-names.d.ts +41 -0
  103. package/dist/constants/field-names.js +47 -0
  104. package/dist/features/account/components/UserOrdersView.js +28 -2
  105. package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
  106. package/dist/features/admin/components/AdminIntegrationsView.js +74 -0
  107. package/dist/features/admin/components/AdminOrderEditorView.d.ts +16 -1
  108. package/dist/features/admin/components/AdminOrderEditorView.js +72 -3
  109. package/dist/features/admin/components/AdminOrdersView.js +20 -1
  110. package/dist/features/admin/schemas/firestore.d.ts +32 -0
  111. package/dist/features/ads/schemas/booking-write.d.ts +120 -0
  112. package/dist/features/ads/schemas/booking-write.js +75 -0
  113. package/dist/features/ads/schemas/creative-form.d.ts +18 -0
  114. package/dist/features/ads/schemas/creative-form.js +51 -0
  115. package/dist/features/ads/schemas/firestore.d.ts +140 -0
  116. package/dist/features/ads/schemas/firestore.js +109 -0
  117. package/dist/features/auctions/repository/bid.repository.js +3 -1
  118. package/dist/features/audit-log/schemas/firestore.d.ts +110 -0
  119. package/dist/features/audit-log/schemas/firestore.js +74 -0
  120. package/dist/features/auth/actions/profile-actions.d.ts +69 -1
  121. package/dist/features/auth/actions/profile-actions.js +62 -1
  122. package/dist/features/auth/schemas/firestore.d.ts +16 -2
  123. package/dist/features/auth/schemas/firestore.js +2 -1
  124. package/dist/features/blog/actions/blog-actions.d.ts +6 -0
  125. package/dist/features/blog/actions/blog-actions.js +17 -2
  126. package/dist/features/blog/api/route.js +4 -1
  127. package/dist/features/blog/columns/index.js +5 -0
  128. package/dist/features/blog/components/BlogIndexPageView.js +2 -1
  129. package/dist/features/blog/repository/blog.repository.d.ts +4 -0
  130. package/dist/features/blog/repository/blog.repository.js +5 -0
  131. package/dist/features/blog/schemas/blog-form.d.ts +3 -0
  132. package/dist/features/blog/schemas/blog-form.js +1 -0
  133. package/dist/features/blog/schemas/firestore.d.ts +38 -1
  134. package/dist/features/blog/schemas/firestore.js +23 -0
  135. package/dist/features/blog/schemas/index.d.ts +6 -0
  136. package/dist/features/blog/schemas/index.js +2 -0
  137. package/dist/features/blog/types/index.d.ts +2 -0
  138. package/dist/features/cart/components/CartPriceBreakdown.d.ts +12 -1
  139. package/dist/features/cart/components/CartPriceBreakdown.js +24 -5
  140. package/dist/features/cart/repository/cart.repository.d.ts +14 -0
  141. package/dist/features/cart/repository/cart.repository.js +20 -0
  142. package/dist/features/cart/schemas/firestore.d.ts +24 -0
  143. package/dist/features/dashboard/components/DashboardHubView.d.ts +83 -0
  144. package/dist/features/dashboard/components/DashboardHubView.js +56 -0
  145. package/dist/features/dashboard/components/DashboardSectionsClient.d.ts +23 -0
  146. package/dist/features/dashboard/components/DashboardSectionsClient.js +66 -0
  147. package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.d.ts +55 -0
  148. package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.js +175 -0
  149. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +24 -0
  150. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +28 -0
  151. package/dist/features/dispute-reasons/schemas/firestore.d.ts +3 -5
  152. package/dist/features/events/components/EventSaleBanner.js +1 -1
  153. package/dist/features/faq/components/FAQPageContent.js +2 -2
  154. package/dist/features/gateway-events/schemas/index.d.ts +2 -2
  155. package/dist/features/homepage/schemas/index.d.ts +8 -8
  156. package/dist/features/inventory/schemas/stock-reservation.d.ts +114 -0
  157. package/dist/features/inventory/schemas/stock-reservation.js +103 -0
  158. package/dist/features/layout/AppLayoutShell.d.ts +9 -1
  159. package/dist/features/layout/AppLayoutShell.js +12 -4
  160. package/dist/features/layout/BottomActions.js +2 -1
  161. package/dist/features/layout/BottomNavbar.d.ts +10 -2
  162. package/dist/features/layout/SideActionDock.js +52 -2
  163. package/dist/features/layout/TitleBarLayout.js +13 -4
  164. package/dist/features/layout/TitleBarLayout.style.css +59 -0
  165. package/dist/features/layout/useCtaReveal.d.ts +16 -1
  166. package/dist/features/layout/useCtaReveal.js +17 -26
  167. package/dist/features/ledger/repository/journal.repository.d.ts +110 -0
  168. package/dist/features/ledger/repository/journal.repository.js +312 -0
  169. package/dist/features/ledger/schemas/expenses.d.ts +87 -0
  170. package/dist/features/ledger/schemas/expenses.js +27 -0
  171. package/dist/features/ledger/schemas/firestore.d.ts +183 -0
  172. package/dist/features/ledger/schemas/firestore.js +69 -0
  173. package/dist/features/orders/components/BalancePaymentPanel.d.ts +12 -0
  174. package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
  175. package/dist/features/orders/components/RaiseDisputeForm.js +19 -11
  176. package/dist/features/orders/repository/orders.repository.d.ts +28 -1
  177. package/dist/features/orders/repository/orders.repository.js +60 -6
  178. package/dist/features/orders/schemas/balance.d.ts +40 -0
  179. package/dist/features/orders/schemas/balance.js +47 -0
  180. package/dist/features/orders/schemas/firestore.d.ts +202 -0
  181. package/dist/features/orders/schemas/firestore.js +43 -0
  182. package/dist/features/orders/schemas/index.d.ts +2 -2
  183. package/dist/features/orders/types/index.d.ts +21 -0
  184. package/dist/features/payments/repository/payout.repository.js +3 -2
  185. package/dist/features/payments/schemas/firestore.d.ts +92 -1
  186. package/dist/features/payments/schemas/firestore.js +23 -0
  187. package/dist/features/pricing/components/AdminPricingView.d.ts +13 -0
  188. package/dist/features/pricing/components/AdminPricingView.js +157 -0
  189. package/dist/features/products/schemas/firestore.d.ts +26 -1
  190. package/dist/features/products/schemas/firestore.js +2 -0
  191. package/dist/features/products/schemas/index.d.ts +2 -2
  192. package/dist/features/reviews/repository/reviews.repository.d.ts +14 -0
  193. package/dist/features/reviews/repository/reviews.repository.js +16 -0
  194. package/dist/features/scams/repository/scammer.repository.js +2 -1
  195. package/dist/features/scams/schemas/firestore.d.ts +2 -1
  196. package/dist/features/scams/schemas/firestore.js +2 -1
  197. package/dist/features/seller/actions/offer-actions.js +56 -0
  198. package/dist/features/seller/actions/seller-actions.d.ts +1 -1
  199. package/dist/features/seller/actions/seller-actions.js +4 -0
  200. package/dist/features/seller/components/DispatchForm.d.ts +11 -0
  201. package/dist/features/seller/components/DispatchForm.js +190 -0
  202. package/dist/features/seller/components/SellerOrdersView.js +94 -2
  203. package/dist/features/seller/components/index.d.ts +1 -0
  204. package/dist/features/seller/components/index.js +1 -0
  205. package/dist/features/seller/repository/offer.repository.js +4 -3
  206. package/dist/features/seller/schemas/firestore.d.ts +23 -2
  207. package/dist/features/seller/schemas/firestore.js +3 -2
  208. package/dist/features/seller/schemas/index.d.ts +4 -4
  209. package/dist/features/seller/schemas/order-forms.d.ts +61 -4
  210. package/dist/features/seller/schemas/order-forms.js +41 -8
  211. package/dist/features/shipments/schemas/firestore.d.ts +25 -0
  212. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +39 -0
  213. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +55 -0
  214. package/dist/features/shipping-boxes/schemas/firestore.d.ts +2 -4
  215. package/dist/features/stores/repository/store.repository.d.ts +18 -0
  216. package/dist/features/stores/repository/store.repository.js +27 -1
  217. package/dist/features/stores/schemas/firestore.d.ts +41 -0
  218. package/dist/features/stores/schemas/firestore.js +20 -0
  219. package/dist/features/stores/schemas/index.d.ts +6 -6
  220. package/dist/features/support/repository/support.repository.js +72 -6
  221. package/dist/features/support/schemas/firestore.d.ts +17 -0
  222. package/dist/features/support/schemas/index.d.ts +2 -2
  223. package/dist/index.d.ts +5 -0
  224. package/dist/index.js +19 -0
  225. package/dist/next/routing/route-map.d.ts +92 -0
  226. package/dist/next/routing/route-map.js +53 -0
  227. package/dist/react/contexts/SessionContext.d.ts +17 -1
  228. package/dist/schemas/registry.d.ts +22 -16
  229. package/dist/security/pii-schemas.d.ts +1 -1
  230. package/dist/security/pii-schemas.js +45 -0
  231. package/dist/server.d.ts +27 -1
  232. package/dist/server.js +144 -0
  233. package/dist/styles.css +349 -1
  234. package/dist/tailwind-utilities.css +1 -1
  235. package/dist/ui/components/Button.d.ts +10 -1
  236. package/dist/ui/components/Button.js +25 -3
  237. package/dist/ui/components/Chart.d.ts +63 -0
  238. package/dist/ui/components/Chart.js +169 -0
  239. package/dist/ui/components/Chart.style.css +214 -0
  240. package/dist/ui/components/Disclaimer.js +10 -1
  241. package/dist/ui/components/OverflowRow.js +54 -2
  242. package/dist/ui/components/OverflowRow.style.css +38 -0
  243. package/dist/ui/components/RichTextEditor.js +99 -8
  244. package/dist/ui/components/chart-spec.d.ts +68 -0
  245. package/dist/ui/components/chart-spec.js +140 -0
  246. package/dist/ui/components/index.style.css +1 -0
  247. package/dist/ui/components/surface-tokens.d.ts +1 -1
  248. package/dist/ui/components/surface-tokens.js +9 -0
  249. package/dist/ui/components/toolbar-collapse.d.ts +13 -0
  250. package/dist/ui/components/toolbar-collapse.js +10 -1
  251. package/dist/ui/index.d.ts +6 -0
  252. package/dist/ui/index.js +3 -0
  253. package/dist/ui/rich-text/RichText.d.ts +10 -1
  254. package/dist/ui/rich-text/RichText.js +33 -2
  255. package/dist/ui/rich-text/RichText.style.css +35 -0
  256. package/dist/ui/rich-text/RichTextRenderer.d.ts +30 -1
  257. package/dist/ui/rich-text/RichTextRenderer.js +34 -2
  258. package/dist/ui/rich-text/chart-blocks.d.ts +62 -0
  259. package/dist/ui/rich-text/chart-blocks.js +87 -0
  260. package/package.json +1 -1
  261. package/scripts/seed-cli.mjs +3 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Correcting a delivery address after the order was placed.
3
+ *
4
+ * ## 🛑 This APPENDS. It never touches the snapshot.
5
+ *
6
+ * `delivery*` on the order is what the buyer chose when they paid — it is
7
+ * what the invoice states and what a dispute argues over. A correction is a
8
+ * new fact about where the parcel should go, not a revision of what was
9
+ * agreed, and collapsing the two is how an invoice silently restates itself.
10
+ *
11
+ * `deliveryPartyFor` reads the latest amendment; `placeOfSupplyFrom` reads
12
+ * the snapshot. Tax is fixed at the time of supply.
13
+ *
14
+ * ## 🛑 There is a window, and it closes at dispatch
15
+ *
16
+ * Once a tracking id exists the parcel is with the courier and a new address
17
+ * changes nothing we control — accepting one would tell the buyer their
18
+ * correction took effect when it did not. That is a worse outcome than
19
+ * refusing, which at least routes them to support.
20
+ */
21
+ import { type DeliveryAmendment, type OrderDocument } from "../../../../features/orders/schemas/firestore";
22
+ import { type HistoryActor } from "../../../shared/history";
23
+ /**
24
+ * How many corrections one order may carry.
25
+ *
26
+ * 🛑 Bounded for the same reason `statusHistory` is: this array rides on the
27
+ * order document, which has a 1 MiB ceiling and is read by every list query.
28
+ * Unlike `statusHistory` it does NOT evict — reaching the cap is refused,
29
+ * because dropping the oldest amendment would silently discard an address a
30
+ * parcel may already have gone to.
31
+ *
32
+ * Ten is far past any honest use. A buyer correcting an address eleven times
33
+ * is a support conversation, not a form submission.
34
+ */
35
+ export declare const DELIVERY_AMENDMENT_MAX = 10;
36
+ export interface AmendDeliveryInput {
37
+ orderId: string;
38
+ /** An `addresses` row belonging to the actor. Never a loose address. */
39
+ addressId: string;
40
+ reason?: string;
41
+ }
42
+ /**
43
+ * 🛑 The new address is a REFERENCE the caller must own, resolved here — not
44
+ * a free-form address object.
45
+ *
46
+ * Accepting fields directly would let a correction route a parcel anywhere
47
+ * with no record that the destination was ever the buyer's, which is the
48
+ * same hole `assertOwnedAddresses` closes for coordinated purchases. The
49
+ * resolved values are then COPIED into the amendment, because the row can be
50
+ * edited or deleted afterwards and a label must not depend on that.
51
+ */
52
+ export declare function amendDeliveryAddress(input: AmendDeliveryInput, actor: HistoryActor & {
53
+ uid: string;
54
+ }): Promise<DeliveryAmendment>;
55
+ /** Plaintext amendments, for a surface that renders them. */
56
+ export declare function revealAmendments(order: OrderDocument): DeliveryAmendment[];
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Correcting a delivery address after the order was placed.
3
+ *
4
+ * ## 🛑 This APPENDS. It never touches the snapshot.
5
+ *
6
+ * `delivery*` on the order is what the buyer chose when they paid — it is
7
+ * what the invoice states and what a dispute argues over. A correction is a
8
+ * new fact about where the parcel should go, not a revision of what was
9
+ * agreed, and collapsing the two is how an invoice silently restates itself.
10
+ *
11
+ * `deliveryPartyFor` reads the latest amendment; `placeOfSupplyFrom` reads
12
+ * the snapshot. Tax is fixed at the time of supply.
13
+ *
14
+ * ## 🛑 There is a window, and it closes at dispatch
15
+ *
16
+ * Once a tracking id exists the parcel is with the courier and a new address
17
+ * changes nothing we control — accepting one would tell the buyer their
18
+ * correction took effect when it did not. That is a worse outcome than
19
+ * refusing, which at least routes them to support.
20
+ */
21
+ import { addressesRepository } from "../../../../features/addresses/repository/addresses.repository.js";
22
+ import { orderRepository } from "../../../../features/orders/repository/orders.repository.js";
23
+ import { AMENDMENT_PII_FIELDS, } from "../../../../features/orders/schemas/firestore.js";
24
+ import { revealPii } from "../../../../security/pii-accessors.js";
25
+ import { ValidationError } from "../../../../errors/validation-error.js";
26
+ import { NotFoundError } from "../../../../errors/not-found-error.js";
27
+ import { HISTORY_ACTOR_ROLE } from "../../../shared/history/index.js";
28
+ /**
29
+ * How many corrections one order may carry.
30
+ *
31
+ * 🛑 Bounded for the same reason `statusHistory` is: this array rides on the
32
+ * order document, which has a 1 MiB ceiling and is read by every list query.
33
+ * Unlike `statusHistory` it does NOT evict — reaching the cap is refused,
34
+ * because dropping the oldest amendment would silently discard an address a
35
+ * parcel may already have gone to.
36
+ *
37
+ * Ten is far past any honest use. A buyer correcting an address eleven times
38
+ * is a support conversation, not a form submission.
39
+ */
40
+ export const DELIVERY_AMENDMENT_MAX = 10;
41
+ /**
42
+ * 🛑 The new address is a REFERENCE the caller must own, resolved here — not
43
+ * a free-form address object.
44
+ *
45
+ * Accepting fields directly would let a correction route a parcel anywhere
46
+ * with no record that the destination was ever the buyer's, which is the
47
+ * same hole `assertOwnedAddresses` closes for coordinated purchases. The
48
+ * resolved values are then COPIED into the amendment, because the row can be
49
+ * edited or deleted afterwards and a label must not depend on that.
50
+ */
51
+ export async function amendDeliveryAddress(input, actor) {
52
+ const order = (await orderRepository.findById(input.orderId));
53
+ if (!order)
54
+ throw new NotFoundError("Order not found.");
55
+ /*
56
+ * Ownership. A seller or admin may correct on a buyer's behalf — that is
57
+ * the support flow — but a BUYER may only touch their own order.
58
+ */
59
+ if (actor.role === HISTORY_ACTOR_ROLE.BUYER && order.userId !== actor.uid) {
60
+ throw new NotFoundError("Order not found.");
61
+ }
62
+ if (order.trackingNumber) {
63
+ throw new ValidationError("This order has already been dispatched, so its address can no longer be changed.", { addressId: "Already dispatched — contact support." });
64
+ }
65
+ const existing = order.deliveryAmendments ?? [];
66
+ if (existing.length >= DELIVERY_AMENDMENT_MAX) {
67
+ throw new ValidationError("This order has been corrected too many times. Please contact support.", { addressId: `Limit of ${DELIVERY_AMENDMENT_MAX} corrections reached.` });
68
+ }
69
+ const addressDoc = await addressesRepository.findById(input.addressId);
70
+ /*
71
+ * Owned by the ORDER'S BUYER, not by the actor. A support agent correcting
72
+ * on someone's behalf must still pick from that buyer's own addresses —
73
+ * checking the actor would let staff route a parcel to their own address.
74
+ */
75
+ if (!addressDoc || addressDoc.ownerType !== "user" || addressDoc.ownerId !== order.userId) {
76
+ throw new ValidationError("That delivery address is not available.", {
77
+ addressId: "That address cannot be used.",
78
+ });
79
+ }
80
+ const address = addressesRepository.revealAddress(addressDoc);
81
+ const amendment = {
82
+ at: new Date(),
83
+ actorUid: actor.uid,
84
+ actorRole: actor.role,
85
+ ...(input.reason?.trim() ? { reason: input.reason.trim() } : {}),
86
+ fullName: String(address.fullName ?? ""),
87
+ phone: String(address.phone ?? ""),
88
+ addressLine1: String(address.addressLine1 ?? ""),
89
+ ...(address.addressLine2 ? { addressLine2: String(address.addressLine2) } : {}),
90
+ ...(address.landmark ? { landmark: String(address.landmark) } : {}),
91
+ city: address.city ?? "",
92
+ state: address.state ?? "",
93
+ pincode: address.postalCode ?? "",
94
+ country: address.country ?? "",
95
+ };
96
+ /*
97
+ * 🛑 NOT encrypted here. `ORDER_PII_FIELDS` declares
98
+ * `deliveryAmendments[].fullName` and friends, and the repository's write
99
+ * hook runs that list through `encryptInArray` — which the encryptor has
100
+ * always supported.
101
+ *
102
+ * The first draft of this file encrypted by hand, on the belief that
103
+ * `encryptPiiFields` "is a flat top-level loop that never descends". That
104
+ * is CLAUDE.md's wording and it is false: the function handles `a.b` and
105
+ * `a[].b` in both directions, and `CONVERSATION_PII_FIELDS` has been using
106
+ * the dotted form in production. Encrypting here as well would double-
107
+ * encrypt — the guard is a `enc:v1:` prefix test, so the second pass is a
108
+ * no-op today and a silent corruption the day the prefix changes.
109
+ */
110
+ /*
111
+ * 🛑 Whole array, not `arrayUnion`. `arrayUnion` cannot enforce the cap —
112
+ * the same reason `withHistory`'s batch paths fold in memory — and it
113
+ * de-duplicates by value, so two identical corrections would collapse into
114
+ * one and the record would under-state what happened.
115
+ */
116
+ await orderRepository.update(input.orderId, {
117
+ deliveryAmendments: [...existing, amendment],
118
+ });
119
+ return amendment;
120
+ }
121
+ /** Plaintext amendments, for a surface that renders them. */
122
+ export function revealAmendments(order) {
123
+ return (order.deliveryAmendments ?? []).map((a) => revealPii(a, AMENDMENT_PII_FIELDS));
124
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * D55 — collecting a pre-order balance.
3
+ *
4
+ * ## 🛑 `balanceDue` has been computed and rendered since Part 2, and
5
+ * nothing has ever collected it
6
+ *
7
+ * `OrderSourcePreOrder.balanceDue` is written at order creation and
8
+ * `OrderProvenanceCard` prints it — so `/user/pre-orders` has been
9
+ * advertising a figure the product has no way to accept. That is worse than
10
+ * omitting it: a buyer who sees "Balance due ₹4,000" and finds no control
11
+ * reasonably concludes the site is broken, and a seller waiting on it has no
12
+ * mechanism to chase.
13
+ *
14
+ * ## Manual only, and that is a decision rather than a stopping point
15
+ *
16
+ * The manual path needs no mandate, no scheduled debit and no account
17
+ * change, so it is what makes the balance payable AT ALL today. The auto
18
+ * path is blocked on RBI requirements and on Razorpay recurring being
19
+ * enabled against live keys — see the schema's own note, which declares the
20
+ * union so adding `auto` is a compile-checked change.
21
+ *
22
+ * ## 🛑 It reuses the manual-payment REVIEW flow and none of its STORAGE
23
+ *
24
+ * The vocabulary is the same (submit a proof, an admin approves or rejects
25
+ * with a note) and the fields are separate, because a pre-order has two
26
+ * payments months apart. Writing the balance's proof over the deposit's
27
+ * would destroy the evidence for the payment that created the order — and
28
+ * `paymentWindowTimeout`, which sweeps `pending`/`pending` orders with no
29
+ * `paymentProofUrl`, would then cancel and restock a paid one.
30
+ */
31
+ import type { OrderDocument } from "../../../../features/orders/schemas/firestore";
32
+ /**
33
+ * How long a buyer gets, once the balance is opened for collection.
34
+ *
35
+ * 🛑 Days, not the 15 MINUTES the deposit window uses. That window exists to
36
+ * stop an unpaid order holding stock; this one is a bill falling due on goods
37
+ * that have arrived, and the buyer may legitimately need to move money. A
38
+ * 15-minute balance window would cancel real orders for no benefit.
39
+ */
40
+ export declare const BALANCE_WINDOW_DAYS = 7;
41
+ /** The balance a pre-order still owes, or `null` when it is not one. */
42
+ export declare function balanceDueOn(order: OrderDocument): number | null;
43
+ /**
44
+ * Open the balance for collection — the seller's "the goods have landed"
45
+ * signal.
46
+ *
47
+ * 🛑 **Opening is an EVENT, not a derived state.** The balance could be
48
+ * computed as payable the moment the order exists, and that would be wrong:
49
+ * a pre-order's whole premise is that the goods do not exist yet, so asking
50
+ * for the balance before they do is asking a buyer to fund inventory. The
51
+ * seller opens it when stock arrives, and `dueBy` runs from that moment.
52
+ *
53
+ * 🛑 Idempotent. A seller clicking twice must not restart the buyer's
54
+ * window — which, since `dueBy` is what a reminder and any future
55
+ * auto-cancel would read, would silently extend the deadline every time
56
+ * anyone touched the button.
57
+ *
58
+ * 🛑 **Ownership is checked HERE, and it has to be**: the route that calls
59
+ * this is role-gated to sellers, and every seller passes that gate. Without
60
+ * this block any seller could open — and therefore start the payment clock
61
+ * on, and notify the buyer about — another seller's pre-order.
62
+ *
63
+ * It was missing, and the reason it was invisible is worth recording: the
64
+ * route also carried `permission: "orders:write"` alongside a `roles` array
65
+ * holding `seller`, which `getServerPermissions()` resolves for `employee`
66
+ * only — so every seller got a 403 before reaching this function at all. A
67
+ * broken gate masked an absent one, exactly as in Root Cause #98.
68
+ */
69
+ export declare function openBalanceCollection(orderId: string, actor: {
70
+ uid: string;
71
+ role: string;
72
+ }, now?: Date): Promise<OrderDocument>;
73
+ /**
74
+ * The buyer submits a proof.
75
+ *
76
+ * 🛑 Allowed from `awaiting` AND from `rejected`. A rejected proof is the
77
+ * commonest reason to need a second attempt — a blurred screenshot, the
78
+ * wrong reference — and locking the buyer out after one rejection turns a
79
+ * recoverable mistake into a support ticket.
80
+ */
81
+ export declare function submitBalanceProof(orderId: string, buyerUid: string, proofUrl: string, transactionId: string, now?: Date): Promise<void>;
82
+ /**
83
+ * An admin approves or rejects the balance proof.
84
+ *
85
+ * 🛑 A rejection REQUIRES a note, for the same reason a reversal does: the
86
+ * buyer has to know what to send instead, and "rejected" alone guarantees
87
+ * the next attempt is the same proof again.
88
+ */
89
+ export declare function reviewBalanceProof(orderId: string, reviewerUid: string, approve: boolean, note?: string, now?: Date): Promise<void>;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * D55 — collecting a pre-order balance.
3
+ *
4
+ * ## 🛑 `balanceDue` has been computed and rendered since Part 2, and
5
+ * nothing has ever collected it
6
+ *
7
+ * `OrderSourcePreOrder.balanceDue` is written at order creation and
8
+ * `OrderProvenanceCard` prints it — so `/user/pre-orders` has been
9
+ * advertising a figure the product has no way to accept. That is worse than
10
+ * omitting it: a buyer who sees "Balance due ₹4,000" and finds no control
11
+ * reasonably concludes the site is broken, and a seller waiting on it has no
12
+ * mechanism to chase.
13
+ *
14
+ * ## Manual only, and that is a decision rather than a stopping point
15
+ *
16
+ * The manual path needs no mandate, no scheduled debit and no account
17
+ * change, so it is what makes the balance payable AT ALL today. The auto
18
+ * path is blocked on RBI requirements and on Razorpay recurring being
19
+ * enabled against live keys — see the schema's own note, which declares the
20
+ * union so adding `auto` is a compile-checked change.
21
+ *
22
+ * ## 🛑 It reuses the manual-payment REVIEW flow and none of its STORAGE
23
+ *
24
+ * The vocabulary is the same (submit a proof, an admin approves or rejects
25
+ * with a note) and the fields are separate, because a pre-order has two
26
+ * payments months apart. Writing the balance's proof over the deposit's
27
+ * would destroy the evidence for the payment that created the order — and
28
+ * `paymentWindowTimeout`, which sweeps `pending`/`pending` orders with no
29
+ * `paymentProofUrl`, would then cancel and restock a paid one.
30
+ */
31
+ import { orderRepository } from "../../../../features/orders/repository/orders.repository.js";
32
+ import { ValidationError } from "../../../../errors/validation-error.js";
33
+ import { NotFoundError } from "../../../../errors/not-found-error.js";
34
+ import { AuthorizationError } from "../../../../errors/authorization-error.js";
35
+ import { storeRepository } from "../../../../features/stores/repository/store.repository.js";
36
+ import { sendNotification } from "../../../../features/admin/actions/notification-actions.js";
37
+ import { serverLogger } from "../../../../monitoring/server-logger.js";
38
+ /*
39
+ * 🛑 ONE message, for the same reason the ads collector names its own: an
40
+ * order that is not the caller's answers 404 rather than 403, so "no such
41
+ * order" and "not yours" must read identically. Two matching literals are a
42
+ * coincidence; a constant is the rule.
43
+ */
44
+ const ORDER_NOT_FOUND = "Order not found.";
45
+ /**
46
+ * How long a buyer gets, once the balance is opened for collection.
47
+ *
48
+ * 🛑 Days, not the 15 MINUTES the deposit window uses. That window exists to
49
+ * stop an unpaid order holding stock; this one is a bill falling due on goods
50
+ * that have arrived, and the buyer may legitimately need to move money. A
51
+ * 15-minute balance window would cancel real orders for no benefit.
52
+ */
53
+ export const BALANCE_WINDOW_DAYS = 7;
54
+ /** The balance a pre-order still owes, or `null` when it is not one. */
55
+ export function balanceDueOn(order) {
56
+ const ctx = order.sourceContext;
57
+ if (!ctx || ctx.path !== "pre-order")
58
+ return null;
59
+ return ctx.balanceDue > 0 ? ctx.balanceDue : null;
60
+ }
61
+ /**
62
+ * Open the balance for collection — the seller's "the goods have landed"
63
+ * signal.
64
+ *
65
+ * 🛑 **Opening is an EVENT, not a derived state.** The balance could be
66
+ * computed as payable the moment the order exists, and that would be wrong:
67
+ * a pre-order's whole premise is that the goods do not exist yet, so asking
68
+ * for the balance before they do is asking a buyer to fund inventory. The
69
+ * seller opens it when stock arrives, and `dueBy` runs from that moment.
70
+ *
71
+ * 🛑 Idempotent. A seller clicking twice must not restart the buyer's
72
+ * window — which, since `dueBy` is what a reminder and any future
73
+ * auto-cancel would read, would silently extend the deadline every time
74
+ * anyone touched the button.
75
+ *
76
+ * 🛑 **Ownership is checked HERE, and it has to be**: the route that calls
77
+ * this is role-gated to sellers, and every seller passes that gate. Without
78
+ * this block any seller could open — and therefore start the payment clock
79
+ * on, and notify the buyer about — another seller's pre-order.
80
+ *
81
+ * It was missing, and the reason it was invisible is worth recording: the
82
+ * route also carried `permission: "orders:write"` alongside a `roles` array
83
+ * holding `seller`, which `getServerPermissions()` resolves for `employee`
84
+ * only — so every seller got a 403 before reaching this function at all. A
85
+ * broken gate masked an absent one, exactly as in Root Cause #98.
86
+ */
87
+ export async function openBalanceCollection(orderId, actor, now = new Date()) {
88
+ const order = (await orderRepository.findById(orderId));
89
+ if (!order)
90
+ throw new NotFoundError(ORDER_NOT_FOUND);
91
+ /*
92
+ * 🛑 `order.storeId` is the store SLUG, never the seller's Firebase UID —
93
+ * two namespaces by design (§ "Store Identity Architecture"). Comparing it
94
+ * to `actor.uid` returns false for every legitimate seller, which is the
95
+ * other half of Root Cause #98. Resolve the store first.
96
+ */
97
+ if (actor.role !== "admin") {
98
+ const store = await storeRepository.findByOwnerId(actor.uid);
99
+ if (!store || order.storeId !== store.id) {
100
+ throw new AuthorizationError("You do not own this order");
101
+ }
102
+ }
103
+ const due = balanceDueOn(order);
104
+ if (due == null) {
105
+ throw new ValidationError("This order has no pre-order balance.", {
106
+ orderId: "Only a pre-order carries a balance.",
107
+ });
108
+ }
109
+ if (order.balance)
110
+ return order; // already open — see the idempotency note.
111
+ const dueBy = new Date(now.getTime() + BALANCE_WINDOW_DAYS * 24 * 60 * 60 * 1000);
112
+ await orderRepository.update(orderId, {
113
+ balance: {
114
+ /*
115
+ * 🛑 Snapshotted HERE, from `sourceContext`, and never read live
116
+ * again. The gap between deposit and balance is months — the worst in
117
+ * the codebase for D61 — so a price or tax change in between must not
118
+ * move what the buyer was told they owe.
119
+ */
120
+ amount: due,
121
+ method: "manual",
122
+ status: "awaiting",
123
+ dueBy,
124
+ },
125
+ });
126
+ await sendNotification({
127
+ userId: order.userId,
128
+ type: "order_confirmed",
129
+ priority: "high",
130
+ title: "Your pre-order balance is due",
131
+ message: `The balance of ₹${due.toFixed(2)} on order ${orderId} is now payable. Your item is ready to ship once it clears.`,
132
+ relatedId: orderId,
133
+ relatedType: "order",
134
+ });
135
+ return { ...order, balance: { amount: due, method: "manual", status: "awaiting", dueBy } };
136
+ }
137
+ /**
138
+ * The buyer submits a proof.
139
+ *
140
+ * 🛑 Allowed from `awaiting` AND from `rejected`. A rejected proof is the
141
+ * commonest reason to need a second attempt — a blurred screenshot, the
142
+ * wrong reference — and locking the buyer out after one rejection turns a
143
+ * recoverable mistake into a support ticket.
144
+ */
145
+ export async function submitBalanceProof(orderId, buyerUid, proofUrl, transactionId, now = new Date()) {
146
+ const order = (await orderRepository.findById(orderId));
147
+ if (!order)
148
+ throw new NotFoundError(ORDER_NOT_FOUND);
149
+ /*
150
+ * 🛑 Ownership checked against `userId`, which is the buyer field on
151
+ * `OrderDocument` — NOT `buyerId`, which does not exist. A query or guard
152
+ * written against `buyerId` returns nothing and raises nothing, which is
153
+ * indistinguishable from "not your order" (CLAUDE.md records this costing
154
+ * real time once already).
155
+ */
156
+ if (order.userId !== buyerUid) {
157
+ throw new NotFoundError(ORDER_NOT_FOUND);
158
+ }
159
+ if (!order.balance) {
160
+ throw new ValidationError("No balance has been opened on this order yet.", {
161
+ orderId: "The seller opens the balance when the goods arrive.",
162
+ });
163
+ }
164
+ if (order.balance.status === "paid") {
165
+ throw new ValidationError("This balance is already paid.", {
166
+ orderId: "Nothing further is owed.",
167
+ });
168
+ }
169
+ if (order.balance.status === "submitted") {
170
+ throw new ValidationError("A proof is already awaiting review.", {
171
+ orderId: "Wait for the current one to be reviewed before sending another.",
172
+ });
173
+ }
174
+ if (!transactionId.trim()) {
175
+ throw new ValidationError("Enter the transaction reference.", {
176
+ transactionId: "The UTR or reference from your bank or UPI app.",
177
+ });
178
+ }
179
+ await orderRepository.update(orderId, {
180
+ balance: {
181
+ ...order.balance,
182
+ status: "submitted",
183
+ proofUrl,
184
+ transactionId: transactionId.trim(),
185
+ submittedAt: now,
186
+ /*
187
+ * 🛑 The previous rejection's note is CLEARED on resubmission. Leaving
188
+ * it would show the buyer a stale "your proof was unreadable" beside a
189
+ * proof they have just replaced.
190
+ */
191
+ reviewNote: undefined,
192
+ },
193
+ });
194
+ }
195
+ /**
196
+ * An admin approves or rejects the balance proof.
197
+ *
198
+ * 🛑 A rejection REQUIRES a note, for the same reason a reversal does: the
199
+ * buyer has to know what to send instead, and "rejected" alone guarantees
200
+ * the next attempt is the same proof again.
201
+ */
202
+ export async function reviewBalanceProof(orderId, reviewerUid, approve, note, now = new Date()) {
203
+ const order = (await orderRepository.findById(orderId));
204
+ if (!order)
205
+ throw new NotFoundError(ORDER_NOT_FOUND);
206
+ if (order.balance?.status !== "submitted") {
207
+ throw new ValidationError("There is no submitted balance proof to review.", {
208
+ orderId: `The balance is "${order.balance?.status ?? "not opened"}".`,
209
+ });
210
+ }
211
+ if (!approve && !note?.trim()) {
212
+ throw new ValidationError("A rejection needs a reason.", {
213
+ note: "Say what was wrong, or the buyer will send the same proof again.",
214
+ });
215
+ }
216
+ await orderRepository.update(orderId, {
217
+ balance: {
218
+ ...order.balance,
219
+ status: approve ? "paid" : "rejected",
220
+ reviewedBy: reviewerUid,
221
+ reviewedAt: now,
222
+ ...(note?.trim() ? { reviewNote: note.trim() } : {}),
223
+ },
224
+ });
225
+ /*
226
+ * 🛑 The LEDGER is not posted here, and that is deliberate.
227
+ *
228
+ * A balance payment is a second capture against an order whose sale was
229
+ * already posted in full at creation — `orderPlacedPosting` recognises the
230
+ * whole `totalPrice`, not the deposit. Posting again would double the
231
+ * sale. What actually changes is which asset holds the money, and that is
232
+ * the settlement's job: the gateway receivable clears when the settlement
233
+ * lands, exactly as it does for a single-payment order.
234
+ *
235
+ * Recording this is left to whoever wires the deposit/balance split into
236
+ * `orderPlacedPosting`, which is a change to that rule rather than an
237
+ * extra entry here. An extra entry would balance and be wrong, which is
238
+ * the failure mode Part 4's whole design is about.
239
+ */
240
+ serverLogger.info("balance proof reviewed", {
241
+ orderId,
242
+ approved: approve,
243
+ reviewerUid,
244
+ });
245
+ await sendNotification({
246
+ userId: order.userId,
247
+ type: approve ? "order_confirmed" : "payment_review",
248
+ priority: "high",
249
+ title: approve ? "Balance received" : "We could not accept your payment proof",
250
+ message: approve
251
+ ? `Your balance on order ${orderId} has been confirmed. It is ready to ship.`
252
+ : `Your balance proof on order ${orderId} was not accepted: ${note?.trim() ?? ""}`,
253
+ relatedId: orderId,
254
+ relatedType: "order",
255
+ });
256
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The ONE write path for a platform rate.
3
+ *
4
+ * ## Why there is exactly one
5
+ *
6
+ * Rates live on `siteSettings`, which `PUT /api/admin/site` can already write
7
+ * — so this is not the only way the numbers CAN move. It is the only way they
8
+ * move with a recorded reason, an effective date and an `adminAuditLog` row,
9
+ * and that difference is the whole point of `/admin/pricing`.
10
+ *
11
+ * The general settings form stays able to write them. Locking it out would
12
+ * mean an admin fixing a typo in the COD ceiling gets a 400 from the page that
13
+ * has always owned it, and the fix would be to come here — which is a worse
14
+ * outcome than an occasional unlogged edit. What this buys is that the
15
+ * DELIBERATE act of repricing has one door with a record behind it.
16
+ *
17
+ * ## 🛑 Forward-only, enforced at the WRITE
18
+ *
19
+ * `rejectRateChange` is a pure function and lives in the registry so the form
20
+ * can run it too — but the form running it is a courtesy, and this is the
21
+ * enforcement. A backdated change here would restate figures already invoiced
22
+ * and already paid out; see the registry header.
23
+ */
24
+ /**
25
+ * How many changes the log keeps, newest first.
26
+ *
27
+ * Sized so a year of ordinary repricing fits: 16 rates changed a few times
28
+ * each is well under this, and the page shows only the most recent per rate
29
+ * anyway. The cap exists because the array rides on a document every request
30
+ * reads, not because 200 entries is a lot of data.
31
+ */
32
+ export declare const RATE_CHANGE_LOG_MAX = 200;
33
+ export interface ApplyRateChangeInput {
34
+ path: string;
35
+ newValue: number;
36
+ /** Omit for "now". Never earlier than now — see `rejectRateChange`. */
37
+ effectiveFrom?: Date;
38
+ reason?: string;
39
+ }
40
+ export interface ApplyRateChangeResult {
41
+ path: string;
42
+ previousValue: number | null;
43
+ newValue: number;
44
+ effectiveFrom: Date;
45
+ }
46
+ /**
47
+ * Change one rate.
48
+ *
49
+ * 🛑 **One rate per call, on purpose.** A bulk form would need one reason
50
+ * covering a heterogeneous set, which is the same argument that removed the
51
+ * bulk offer-cancel: one shared reason across unlike records is worse audit
52
+ * data than no bulk action. Repricing three things is three decisions.
53
+ */
54
+ export declare function applyRateChange(input: ApplyRateChangeInput, actor: {
55
+ uid: string;
56
+ name?: string;
57
+ isAdmin: boolean;
58
+ }, now?: Date): Promise<ApplyRateChangeResult>;