@mohasinac/appkit 4.11.3 → 4.12.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 (160) hide show
  1. package/dist/_internal/server/features/auctions/service.d.ts +7 -1
  2. package/dist/_internal/server/features/auctions/service.js +13 -5
  3. package/dist/_internal/server/features/checkout/actions.d.ts +36 -18
  4. package/dist/_internal/server/features/checkout/actions.js +127 -45
  5. package/dist/_internal/server/features/orders/actions.js +5 -3
  6. package/dist/_internal/server/features/orders/adapters.js +13 -0
  7. package/dist/_internal/server/features/products/list-public.d.ts +28 -0
  8. package/dist/_internal/server/features/products/list-public.js +92 -0
  9. package/dist/_internal/server/functions/firestore.d.ts +1 -2
  10. package/dist/_internal/server/functions/firestore.js +1 -9
  11. package/dist/_internal/server/jobs/core/auctionSettlement.js +5 -2
  12. package/dist/_internal/server/jobs/core/index.d.ts +0 -1
  13. package/dist/_internal/server/jobs/core/index.js +0 -1
  14. package/dist/_internal/server/jobs/core/offerExpiry.js +33 -1
  15. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -2
  16. package/dist/_internal/server/jobs/handlers/index.js +1 -2
  17. package/dist/_internal/shared/checkout/lanes.d.ts +20 -0
  18. package/dist/_internal/shared/checkout/lanes.js +28 -0
  19. package/dist/_internal/shared/features/auctions/config.d.ts +34 -0
  20. package/dist/_internal/shared/features/auctions/config.js +47 -3
  21. package/dist/_internal/shared/features/checkout/config.d.ts +4 -0
  22. package/dist/_internal/shared/features/checkout/config.js +8 -0
  23. package/dist/_internal/shared/fees/calculator.d.ts +34 -2
  24. package/dist/_internal/shared/fees/calculator.js +45 -1
  25. package/dist/_internal/shared/listing-types/auction/config.js +1 -1
  26. package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
  27. package/dist/_internal/shared/listing-types/live/config.js +1 -1
  28. package/dist/_internal/shared/listing-types/pre-order/config.js +1 -1
  29. package/dist/client.d.ts +7 -1
  30. package/dist/client.js +4 -1
  31. package/dist/constants/api-endpoints.d.ts +6 -0
  32. package/dist/constants/api-endpoints.js +2 -0
  33. package/dist/features/account/components/NotificationBell.js +1 -1
  34. package/dist/features/admin/components/AdminAddressesView.js +18 -10
  35. package/dist/features/admin/components/AdminArtView.d.ts +9 -0
  36. package/dist/features/admin/components/AdminArtView.js +11 -35
  37. package/dist/features/admin/components/AdminClassifiedView.d.ts +9 -0
  38. package/dist/features/admin/components/AdminClassifiedView.js +11 -35
  39. package/dist/features/admin/components/AdminDigitalCodesView.d.ts +9 -0
  40. package/dist/features/admin/components/AdminDigitalCodesView.js +11 -42
  41. package/dist/features/admin/components/AdminLiveView.d.ts +9 -0
  42. package/dist/features/admin/components/AdminLiveView.js +11 -41
  43. package/dist/features/admin/components/AdminNotificationsView.js +4 -1
  44. package/dist/features/admin/components/AdminOrderEditorView.d.ts +14 -1
  45. package/dist/features/admin/components/AdminOrderEditorView.js +18 -5
  46. package/dist/features/admin/components/AdminOrdersView.js +15 -1
  47. package/dist/features/admin/components/AdminPaymentMethodsView.js +15 -10
  48. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  49. package/dist/features/admin/components/AdminSiteSettingsView.js +4 -2
  50. package/dist/features/admin/components/AdminStickersView.d.ts +9 -0
  51. package/dist/features/admin/components/AdminStickersView.js +11 -35
  52. package/dist/features/admin/hooks/useAdminListing.js +7 -2
  53. package/dist/features/admin/schemas/firestore.d.ts +5 -0
  54. package/dist/features/admin/schemas/firestore.js +1 -0
  55. package/dist/features/auctions/actions/bid-actions.js +73 -11
  56. package/dist/features/auctions/components/AuctionDetailPageView.js +8 -2
  57. package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
  58. package/dist/features/auctions/components/PlaceBidFormClient.js +89 -14
  59. package/dist/features/auctions/schemas/bid-input.d.ts +19 -6
  60. package/dist/features/auctions/schemas/bid-input.js +19 -7
  61. package/dist/features/auth/repository/user.repository.js +6 -0
  62. package/dist/features/cart/components/CartDrawer.d.ts +9 -1
  63. package/dist/features/cart/components/CartDrawer.js +4 -3
  64. package/dist/features/cart/components/CartPriceBreakdown.d.ts +61 -0
  65. package/dist/features/cart/components/CartPriceBreakdown.js +25 -0
  66. package/dist/features/cart/components/StoreAddonsPicker.d.ts +47 -0
  67. package/dist/features/cart/components/StoreAddonsPicker.js +21 -0
  68. package/dist/features/cart/components/index.d.ts +4 -0
  69. package/dist/features/cart/components/index.js +2 -0
  70. package/dist/features/cart/repository/cart.repository.d.ts +20 -2
  71. package/dist/features/cart/repository/cart.repository.js +49 -0
  72. package/dist/features/cart/schemas/firestore.d.ts +26 -0
  73. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  74. package/dist/features/filters/FilterFacetSection.js +1 -1
  75. package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
  76. package/dist/features/layout/BackToTop.js +1 -1
  77. package/dist/features/layout/BottomActions.js +38 -5
  78. package/dist/features/layout/BottomActionsContext.d.ts +12 -0
  79. package/dist/features/layout/BottomActionsContext.js +5 -0
  80. package/dist/features/layout/FooterLayout.js +2 -2
  81. package/dist/features/layout/TitleBarLayout.js +1 -1
  82. package/dist/features/layout/hooks/useBottomActions.d.ts +6 -0
  83. package/dist/features/layout/hooks/useBottomActions.js +11 -1
  84. package/dist/features/orders/components/OrderAddonBadges.d.ts +36 -0
  85. package/dist/features/orders/components/OrderAddonBadges.js +28 -0
  86. package/dist/features/orders/components/index.d.ts +1 -0
  87. package/dist/features/orders/components/index.js +1 -0
  88. package/dist/features/orders/repository/orders.repository.d.ts +32 -0
  89. package/dist/features/orders/repository/orders.repository.js +14 -0
  90. package/dist/features/orders/types/index.d.ts +22 -0
  91. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  92. package/dist/features/products/components/ArtStickersListView.js +5 -4
  93. package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
  94. package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
  95. package/dist/features/products/components/PrizeDrawCollage.js +1 -1
  96. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  97. package/dist/features/products/components/PrizeDrawsListingView.js +18 -33
  98. package/dist/features/products/components/ProductDetailPageView.js +1 -1
  99. package/dist/features/products/components/ProductGrid.js +3 -3
  100. package/dist/features/products/components/ProductsIndexListing.js +87 -11
  101. package/dist/features/products/components/ProductsIndexPageView.js +14 -2
  102. package/dist/features/products/config/listing-type-listing-config.d.ts +31 -0
  103. package/dist/features/products/config/listing-type-listing-config.js +74 -0
  104. package/dist/features/products/hooks/useProducts.js +14 -0
  105. package/dist/features/products/repository/products.repository.d.ts +0 -4
  106. package/dist/features/products/repository/products.repository.js +0 -15
  107. package/dist/features/products/types/index.d.ts +15 -0
  108. package/dist/features/promotions/repository/coupons.repository.d.ts +12 -0
  109. package/dist/features/promotions/repository/coupons.repository.js +12 -0
  110. package/dist/features/seller/components/SellerOrdersView.js +2 -1
  111. package/dist/features/seller/components/SellerSidebar.js +1 -1
  112. package/dist/features/stores/components/StoreArtStickersPageView.d.ts +21 -0
  113. package/dist/features/stores/components/StoreArtStickersPageView.js +30 -0
  114. package/dist/features/stores/components/StoreAuctionsPageView.d.ts +4 -1
  115. package/dist/features/stores/components/StoreAuctionsPageView.js +17 -17
  116. package/dist/features/stores/components/StoreClassifiedsListing.js +7 -0
  117. package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -1
  118. package/dist/features/stores/components/StoreClassifiedsPageView.js +16 -12
  119. package/dist/features/stores/components/StoreDetailLayoutView.js +4 -3
  120. package/dist/features/stores/components/StoreDigitalCodesListing.js +5 -0
  121. package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -1
  122. package/dist/features/stores/components/StoreDigitalCodesPageView.js +16 -12
  123. package/dist/features/stores/components/StoreLiveItemsListing.js +7 -0
  124. package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -1
  125. package/dist/features/stores/components/StoreLiveItemsPageView.js +16 -12
  126. package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -1
  127. package/dist/features/stores/components/StorePreOrdersPageView.js +17 -17
  128. package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +4 -8
  129. package/dist/features/stores/components/StorePrizeDrawsPageView.js +17 -22
  130. package/dist/features/stores/components/StoreProductsListing.d.ts +7 -1
  131. package/dist/features/stores/components/StoreProductsListing.js +3 -2
  132. package/dist/features/stores/components/StoreProductsPageView.d.ts +1 -1
  133. package/dist/features/stores/components/StoreProductsPageView.js +16 -21
  134. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +875 -0
  135. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +1 -1
  136. package/dist/jobs.d.ts +1 -1
  137. package/dist/jobs.js +1 -1
  138. package/dist/next/routing/route-map.d.ts +2 -0
  139. package/dist/next/routing/route-map.js +5 -0
  140. package/dist/seed/homepage-sections-seed-data.js +5 -1
  141. package/dist/seed/reviews-seed-data.js +52 -0
  142. package/dist/seed/site-settings-seed-data.js +18 -0
  143. package/dist/server-entry.d.ts +1 -0
  144. package/dist/server-entry.js +1 -0
  145. package/dist/styles.css +66 -1
  146. package/dist/tailwind-utilities.css +1 -1
  147. package/dist/tokens/color-pairs.js +1 -0
  148. package/dist/tokens/tokens.css +33 -0
  149. package/dist/ui/components/Button.style.css +21 -0
  150. package/dist/ui/components/Checkbox.d.ts +10 -1
  151. package/dist/ui/components/Checkbox.js +14 -2
  152. package/dist/ui/components/Checkbox.style.css +11 -0
  153. package/dist/ui/components/ImageLightbox.js +7 -4
  154. package/dist/ui/components/surface-tokens.d.ts +7 -0
  155. package/dist/ui/components/surface-tokens.js +7 -0
  156. package/package.json +1 -1
  157. package/dist/_internal/server/jobs/core/onBidPlaced.d.ts +0 -15
  158. package/dist/_internal/server/jobs/core/onBidPlaced.js +0 -63
  159. package/dist/_internal/server/jobs/handlers/onBidPlaced.d.ts +0 -3
  160. package/dist/_internal/server/jobs/handlers/onBidPlaced.js +0 -9
@@ -2,7 +2,13 @@ import { type BidIncrementTier } from "../../../shared/features/auctions/config"
2
2
  import type { ProductDocument } from "../../../shared/features/products/types";
3
3
  /** Assert an auction exists, is published, and is still active. Returns the product. */
4
4
  export declare function assertAuctionActive(auctionId: string): Promise<ProductDocument>;
5
- /** Compute the minimum valid bid given the current state and the admin's tier table. */
5
+ /**
6
+ * Compute the minimum valid bid given the current state and the admin's tier table.
7
+ *
8
+ * Delegates to the shared `resolveMinBid` so this path can't drift from
9
+ * `placeBid`'s — including the no-bids case, where the starting bid is itself
10
+ * acceptable rather than needing to be beaten by an increment.
11
+ */
6
12
  export declare function computeMinBid(product: ProductDocument, tiers: BidIncrementTier[]): number;
7
13
  /** Assert a bid amount is valid against the current auction state. */
8
14
  export declare function assertBidAmount(product: ProductDocument, amount: number, tiers: BidIncrementTier[]): void;
@@ -1,7 +1,7 @@
1
1
  import { productRepository } from "../../../../repositories";
2
2
  import { isAuctionListing } from "../../../../features/products/utils/listing-type";
3
3
  import { AuctionNotFoundError, AuctionEndedError, BidTooLowError, BidOnOwnAuctionError, } from "../../../shared/features/auctions/errors";
4
- import { AUCTION_SNIPING_WINDOW_SECONDS, AUCTION_DEFAULT_EXTENSION_MINUTES, resolveMinBidIncrement, } from "../../../shared/features/auctions/config";
4
+ import { AUCTION_SNIPING_WINDOW_SECONDS, AUCTION_DEFAULT_EXTENSION_MINUTES, resolveMinBid, } from "../../../shared/features/auctions/config";
5
5
  /** Assert an auction exists, is published, and is still active. Returns the product. */
6
6
  export async function assertAuctionActive(auctionId) {
7
7
  const product = await productRepository.findByIdOrSlug(auctionId).catch(() => null);
@@ -16,11 +16,19 @@ export async function assertAuctionActive(auctionId) {
16
16
  throw new AuctionEndedError(auctionId);
17
17
  return product;
18
18
  }
19
- /** Compute the minimum valid bid given the current state and the admin's tier table. */
19
+ /**
20
+ * Compute the minimum valid bid given the current state and the admin's tier table.
21
+ *
22
+ * Delegates to the shared `resolveMinBid` so this path can't drift from
23
+ * `placeBid`'s — including the no-bids case, where the starting bid is itself
24
+ * acceptable rather than needing to be beaten by an increment.
25
+ */
20
26
  export function computeMinBid(product, tiers) {
21
- const current = product.currentBid ?? product.startingBid ?? 0;
22
- const increment = resolveMinBidIncrement(current, tiers, product.minBidIncrement);
23
- return current + increment;
27
+ const hasBids = (product.currentBid ?? 0) > 0 || (product.bidCount ?? 0) > 0;
28
+ const current = hasBids
29
+ ? (product.currentBid ?? product.startingBid ?? 0)
30
+ : (product.startingBid ?? 0);
31
+ return resolveMinBid(current, tiers, product.minBidIncrement, { hasBids });
24
32
  }
25
33
  /** Assert a bid amount is valid against the current auction state. */
26
34
  export function assertBidAmount(product, amount, tiers) {
@@ -1,3 +1,4 @@
1
+ import { type CartLane } from "../../../shared/checkout/lanes";
1
2
  import type { OutOfStockPolicy } from "../../../../features/orders/schemas/index";
2
3
  import { type CheckoutPaymentMethod } from "../../../shared/features/checkout/config";
3
4
  import { type CheckoutOrderResult } from "./data";
@@ -23,14 +24,6 @@ export interface CreateCheckoutOrderInput {
23
24
  * omits the field.
24
25
  */
25
26
  outOfStockPolicy?: OutOfStockPolicy;
26
- /** Buyer opted into the ₹10 WhatsApp order-updates addon at checkout. Defaults false (unchecked). Only actually charged/honored when siteSettings.commissions.whatsappNotifyFeeEnabled is also true. */
27
- whatsappNotifyAddon?: boolean;
28
- /** Buyer opted into gift wrap at checkout. Defaults false (unchecked). Only actually charged/honored when siteSettings.commissions.giftWrapFeeEnabled is also true. */
29
- giftWrapAddon?: boolean;
30
- /** Optional gift message, only meaningful when giftWrapAddon is true. */
31
- giftWrapMessage?: string;
32
- /** Buyer opted into shipment protection at checkout. Defaults false (unchecked). Only actually charged/honored when siteSettings.commissions.shipmentProtectionFeeEnabled is also true. */
33
- shipmentProtectionAddon?: boolean;
34
27
  }
35
28
  /**
36
29
  * Exported so `/api/payment/create-order` (pre-charge amount) and the
@@ -61,9 +54,29 @@ export interface CheckoutPricingPreviewInput {
61
54
  addressId?: string;
62
55
  paymentMethod: CheckoutPaymentMethod;
63
56
  excludedProductIds?: string[];
64
- whatsappNotifyAddon?: boolean;
65
- giftWrapAddon?: boolean;
66
- shipmentProtectionAddon?: boolean;
57
+ /**
58
+ * Which cart lane to price. Only one lane is payable in a single checkout, so
59
+ * a preview spanning lanes would be a number the buyer can never be charged.
60
+ *
61
+ * Passed in rather than derived from `activeLane` server-side because the cart
62
+ * page previews whichever tab the buyer is looking at — including a lane that
63
+ * is currently gated off.
64
+ */
65
+ lane?: CartLane;
66
+ }
67
+ /** One store's slice of the preview — what that seller's order will cost. */
68
+ export interface CheckoutPricingPreviewStore {
69
+ storeId: string;
70
+ storeName: string;
71
+ subtotal: number;
72
+ shippingFee: number;
73
+ codHandlingFee: number;
74
+ whatsappNotifyFee: number;
75
+ giftWrapFee: number;
76
+ shipmentProtectionFee: number;
77
+ gstAmount: number;
78
+ couponDiscount: number;
79
+ total: number;
67
80
  }
68
81
  export interface CheckoutPricingPreview {
69
82
  subtotal: number;
@@ -74,7 +87,19 @@ export interface CheckoutPricingPreview {
74
87
  shipmentProtectionFee: number;
75
88
  gstAmount: number;
76
89
  couponDiscount: number;
90
+ /** Buyer-facing platform commission, capped, charged once for this checkout. */
91
+ platformFee: number;
92
+ /** GST on the (capped) platform commission. */
93
+ gstOnFee: number;
77
94
  total: number;
95
+ /**
96
+ * Per-store breakdown, in group order. Every figure here was already being
97
+ * computed inside the per-group loop and then discarded into an aggregate —
98
+ * surfacing it is what lets each seller card show its own fees.
99
+ *
100
+ * A store with no selected items forms no group and therefore never appears.
101
+ */
102
+ stores: CheckoutPricingPreviewStore[];
78
103
  }
79
104
  /**
80
105
  * Read-only "what will I actually be charged" preview for the checkout
@@ -113,12 +138,5 @@ export interface VerifyAndPlaceRazorpayOrderInput {
113
138
  * on the Razorpay path.
114
139
  */
115
140
  outOfStockPolicy?: OutOfStockPolicy;
116
- /** Buyer opted into the ₹10 WhatsApp order-updates addon at checkout — must match the value sent to /api/payment/create-order so the pre-charged amount and the placed order agree. */
117
- whatsappNotifyAddon?: boolean;
118
- /** Buyer opted into gift wrap at checkout — must match the value sent to /api/payment/create-order. */
119
- giftWrapAddon?: boolean;
120
- giftWrapMessage?: string;
121
- /** Buyer opted into shipment protection at checkout — must match the value sent to /api/payment/create-order. */
122
- shipmentProtectionAddon?: boolean;
123
141
  }
124
142
  export declare function verifyAndPlaceRazorpayOrderAction(input: VerifyAndPlaceRazorpayOrderInput): Promise<CheckoutOrderResult>;
@@ -20,7 +20,7 @@ import { splitCartIntoOrderGroups } from "../../../../features/orders/index";
20
20
  import { resolveCouponCategorySlugs } from "../../../../features/promotions/actions/coupon-actions";
21
21
  import { assertCheckoutLane, assertLockedLinesStillValid, finalizeLockedLines, } from "./locked-lines";
22
22
  import { activeLane, laneOf } from "../../../shared/checkout/lanes";
23
- import { computeCodHandlingFee, computeWhatsAppNotifyFee, computeGiftWrapFee, computeShipmentProtectionFee, } from "../../../shared/fees/calculator";
23
+ import { computeCodHandlingFee, computeWhatsAppNotifyFee, computeGiftWrapFee, computeShipmentProtectionFee, computeCheckoutFees, allocateCheckoutFees, } from "../../../shared/fees/calculator";
24
24
  import { getAdminDb, getAdminRealtimeDb, RTDB_PATHS, } from "../../../../providers/db-firebase";
25
25
  import { PRODUCT_COLLECTION, PRODUCT_CODES_SUBCOLLECTION } from "../../../../features/products/schemas/firestore";
26
26
  import { CART_COLLECTION } from "../../../../features/cart/schemas/index";
@@ -367,9 +367,12 @@ function dispatchOrderConfirmationEmails(emailsToSend) {
367
367
  * so the caller can accumulate the checkout-wide `total`.
368
368
  */
369
369
  async function createOrderForGroup(group, orderType, ctx) {
370
- const { paymentMethod, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, buyerState, gstSettings, siteContactUpiVpa, whatsappNotifyAddon, giftWrapAddon, giftWrapMessage, shipmentProtectionAddon, } = ctx;
370
+ 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, } = ctx;
371
371
  const firstItem = group[0].item;
372
372
  const firstProduct = group[0].product;
373
+ // Add-ons are this store's choice, not the cart's — see OrderGroupContext.
374
+ const { whatsappNotifyAddon = false, giftWrapAddon = false, giftWrapMessage, shipmentProtectionAddon = false, } = storeAddons?.[firstItem.storeId] ?? {};
375
+ const { platformFee = 0, gstOnFee: platformFeeGst = 0 } = platformFeeByStore.get(firstItem.storeId) ?? {};
373
376
  const groupTotal = group.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0);
374
377
  // S-SBUNI-RULES 2026-05-13 — order-item decoration via rule registry.
375
378
  const orderItems = group.map(({ item, product }) => {
@@ -456,7 +459,10 @@ async function createOrderForGroup(group, orderType, ctx) {
456
459
  groupCouponCodes.add(discount.code);
457
460
  }
458
461
  }
459
- const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + whatsappNotifyFee + giftWrapFee + shipmentProtectionFee + (emiSchedule?.surchargeAmount ?? 0) + (gstBreakdown?.gstAmount ?? 0);
462
+ // The platform commission is now charged on EVERY payment method. It used to
463
+ // be added only on the Razorpay path, so COD / UPI-manual / cash / EMI buyers
464
+ // silently never paid it.
465
+ const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + whatsappNotifyFee + giftWrapFee + shipmentProtectionFee + platformFee + platformFeeGst + (emiSchedule?.surchargeAmount ?? 0) + (gstBreakdown?.gstAmount ?? 0);
460
466
  const imageUrls = [
461
467
  ...new Set(group
462
468
  .map(({ product }) => product.mainImage)
@@ -510,6 +516,9 @@ async function createOrderForGroup(group, orderType, ctx) {
510
516
  depositAmount: adminBypass ? undefined : depositAmount,
511
517
  codRemainingAmount: adminBypass ? undefined : codRemainingAmount,
512
518
  codHandlingFee: !adminBypass && codHandlingFee > 0 ? codHandlingFee : undefined,
519
+ // This group's pro-rata share of the checkout's single capped commission —
520
+ // the shares across a multi-store checkout sum to exactly what was charged.
521
+ platformFee: !adminBypass && platformFee > 0 ? platformFee : undefined,
513
522
  whatsappNotifyAddon: !adminBypass && whatsappNotifyFee > 0 ? true : undefined,
514
523
  whatsappNotifyFee: !adminBypass && whatsappNotifyFee > 0 ? whatsappNotifyFee : undefined,
515
524
  giftWrapAddon: !adminBypass && giftWrapFee > 0 ? true : undefined,
@@ -591,7 +600,7 @@ async function createOrderForGroup(group, orderType, ctx) {
591
600
  * auth + rate-limiting before calling this and supply the resolved user fields.
592
601
  */
593
602
  export async function createCheckoutOrderAction(input) {
594
- const { userId: uid, userName, userEmail, addressId, paymentMethod, emiTenureMonths, notes, excludedProductIds = [], adminBypass = false, adminBypassBy, outOfStockPolicy = OutOfStockPolicyValues.SKIP_ITEMS, whatsappNotifyAddon = false, giftWrapAddon = false, giftWrapMessage, shipmentProtectionAddon = false, } = input;
603
+ const { userId: uid, userName, userEmail, addressId, paymentMethod, emiTenureMonths, notes, excludedProductIds = [], adminBypass = false, adminBypassBy, outOfStockPolicy = OutOfStockPolicyValues.SKIP_ITEMS, } = input;
595
604
  const siteSettings = await siteSettingsRepository.getSingleton();
596
605
  const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
597
606
  const emiSettings = siteSettings?.emi ?? CHECKOUT_DEFAULT_EMI_SETTINGS;
@@ -798,6 +807,12 @@ export async function createCheckoutOrderAction(input) {
798
807
  const emailsToSend = [];
799
808
  const cartSubtotal = orderGroups.reduce((s, { items: g }) => s + g.reduce((gs, { item, product }) => gs + unitPriceFor(item, product) * item.quantity, 0), 0);
800
809
  const couponUsageAccumulator = new Map();
810
+ // One commission on one transaction, capped, then apportioned across the
811
+ // orders this cart splits into — see allocateCheckoutFees.
812
+ const platformFeeByStore = allocateCheckoutFees(orderGroups.map(({ items: g }) => [
813
+ g[0].item.storeId,
814
+ g.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0),
815
+ ]), computeCheckoutFees(cartSubtotal, commissions));
801
816
  const groupCtx = {
802
817
  paymentMethod,
803
818
  emiTenureMonths,
@@ -821,10 +836,8 @@ export async function createCheckoutOrderAction(input) {
821
836
  buyerState: resolvedAddress?.state,
822
837
  gstSettings: siteSettings?.gst,
823
838
  siteContactUpiVpa: siteSettings?.contact?.upiVpa,
824
- whatsappNotifyAddon,
825
- giftWrapAddon,
826
- giftWrapMessage,
827
- shipmentProtectionAddon,
839
+ storeAddons: cart.storeAddons,
840
+ platformFeeByStore,
828
841
  };
829
842
  for (const { items: group, orderType } of orderGroups) {
830
843
  total += await createOrderForGroup(group, orderType, groupCtx);
@@ -850,7 +863,10 @@ const EMPTY_PRICING_PREVIEW = {
850
863
  shipmentProtectionFee: 0,
851
864
  gstAmount: 0,
852
865
  couponDiscount: 0,
866
+ platformFee: 0,
867
+ gstOnFee: 0,
853
868
  total: 0,
869
+ stores: [],
854
870
  };
855
871
  /**
856
872
  * Read-only "what will I actually be charged" preview for the checkout
@@ -862,13 +878,16 @@ const EMPTY_PRICING_PREVIEW = {
862
878
  * stock, creates an order, or writes coupon usage.
863
879
  */
864
880
  export async function previewCheckoutPricing(input) {
865
- const { userId: uid, addressId, paymentMethod, excludedProductIds = [], whatsappNotifyAddon = false, giftWrapAddon = false, shipmentProtectionAddon = false, } = input;
881
+ const { userId: uid, addressId, paymentMethod, excludedProductIds = [], lane, } = input;
866
882
  const siteSettings = await siteSettingsRepository.getSingleton();
867
883
  const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
868
884
  const cart = await unitOfWork.carts.getOrCreate(uid);
869
885
  const excludedSet = new Set(excludedProductIds);
870
886
  const selectedSet = cart.selectedItemIds?.length ? new Set(cart.selectedItemIds) : null;
871
- const previewLane = activeLane(cart.items ?? []);
887
+ // An explicit lane wins: the cart page prices whichever tab is open, which may
888
+ // not be the one that is currently payable. Falling back to activeLane keeps
889
+ // the checkout page's existing behaviour unchanged.
890
+ const previewLane = lane ?? activeLane(cart.items ?? []);
872
891
  const cartItems = (cart.items ?? []).filter((item) => !excludedSet.has(item.productId) &&
873
892
  (!selectedSet || selectedSet.has(item.itemId)) &&
874
893
  // A preview never throws — it just shows the lane the buyer can actually
@@ -894,45 +913,75 @@ export async function previewCheckoutPricing(input) {
894
913
  // Same re-validation the real order placement runs, so the preview never
895
914
  // shows a discount the order won't actually honour.
896
915
  const { coupons: appliedCoupons } = await revalidateAppliedCoupons(cart.appliedCoupons ?? [], checks, uid);
897
- let shippingFee = 0;
898
- let codHandlingFee = 0;
899
- let whatsappNotifyFeeTotal = 0;
900
- let giftWrapFeeTotal = 0;
901
- let shipmentProtectionFeeTotal = 0;
902
- let gstAmount = 0;
903
- let couponDiscount = 0;
916
+ // Each group's figures are kept, not just summed away — the aggregates below
917
+ // are derived FROM this array, so a store card and the summary panel can never
918
+ // disagree about the same fee.
919
+ const stores = [];
904
920
  for (const { items: group } of orderGroups) {
905
921
  const firstItem = group[0].item;
906
922
  const groupTotal = group.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0);
907
923
  const { shippingFee: groupShippingFee, storeState } = await resolveShippingCost(firstItem.storeId);
908
- shippingFee += groupShippingFee;
924
+ // Add-ons are per store, read off the cart document — the same entry
925
+ // createOrderForGroup will read when the order is actually placed.
926
+ const addons = cart.storeAddons?.[firstItem.storeId] ?? {};
909
927
  // Mirrors createOrderForGroup exactly — the COD handling fee is only
910
928
  // ever charged for literal "cod" (pay the courier), never upi_manual or
911
929
  // cash (both pre-paid via UPI/bank transfer before dispatch).
912
- if (paymentMethod === "cod")
913
- codHandlingFee += computeCodHandlingFee(groupTotal, commissions);
914
- whatsappNotifyFeeTotal += computeWhatsAppNotifyFee(whatsappNotifyAddon, commissions);
915
- giftWrapFeeTotal += computeGiftWrapFee(giftWrapAddon, commissions);
916
- shipmentProtectionFeeTotal += computeShipmentProtectionFee(groupTotal, shipmentProtectionAddon, commissions);
930
+ const groupCodHandlingFee = paymentMethod === "cod" ? computeCodHandlingFee(groupTotal, commissions) : 0;
931
+ const groupWhatsappFee = computeWhatsAppNotifyFee(addons.whatsappNotifyAddon ?? false, commissions);
932
+ const groupGiftWrapFee = computeGiftWrapFee(addons.giftWrapAddon ?? false, commissions);
933
+ const groupShipmentProtectionFee = computeShipmentProtectionFee(groupTotal, addons.shipmentProtectionAddon ?? false, commissions);
934
+ let groupGstAmount = 0;
917
935
  if (siteSettings?.gst?.enabled && resolvedAddress?.state) {
918
936
  const intraState = !!storeState && storeState === resolvedAddress.state;
919
937
  for (const { item, product } of group) {
920
938
  const lineTotal = unitPriceFor(item, product) * item.quantity;
921
939
  const rate = product?.gstRate ?? 0;
922
940
  if (rate > 0)
923
- gstAmount += calculateGst(rate, intraState, lineTotal).gstAmount;
941
+ groupGstAmount += calculateGst(rate, intraState, lineTotal).gstAmount;
924
942
  }
925
943
  }
926
944
  const groupLines = group.map(({ item, product }) => ({ itemId: item.itemId, lineTotal: unitPriceFor(item, product) * item.quantity }));
927
945
  const { couponDiscount: groupCouponDiscount } = computeGroupCouponDiscount(appliedCoupons, groupLines, groupTotal, cartSubtotal, firstItem.storeId);
928
- couponDiscount += groupCouponDiscount;
946
+ stores.push({
947
+ storeId: firstItem.storeId,
948
+ storeName: firstItem.storeName || firstItem.storeId,
949
+ subtotal: groupTotal,
950
+ shippingFee: groupShippingFee,
951
+ codHandlingFee: groupCodHandlingFee,
952
+ whatsappNotifyFee: groupWhatsappFee,
953
+ giftWrapFee: groupGiftWrapFee,
954
+ shipmentProtectionFee: groupShipmentProtectionFee,
955
+ gstAmount: groupGstAmount,
956
+ couponDiscount: groupCouponDiscount,
957
+ total: Math.max(0, groupTotal - groupCouponDiscount) +
958
+ groupShippingFee +
959
+ groupCodHandlingFee +
960
+ groupWhatsappFee +
961
+ groupGiftWrapFee +
962
+ groupShipmentProtectionFee +
963
+ groupGstAmount,
964
+ });
929
965
  }
966
+ const sum = (pick) => stores.reduce((acc, s) => acc + pick(s), 0);
967
+ const shippingFee = sum((s) => s.shippingFee);
968
+ const codHandlingFee = sum((s) => s.codHandlingFee);
969
+ const whatsappNotifyFeeTotal = sum((s) => s.whatsappNotifyFee);
970
+ const giftWrapFeeTotal = sum((s) => s.giftWrapFee);
971
+ const shipmentProtectionFeeTotal = sum((s) => s.shipmentProtectionFee);
972
+ const gstAmount = sum((s) => s.gstAmount);
973
+ const couponDiscount = sum((s) => s.couponDiscount);
974
+ // One capped commission for the whole checkout, on every payment method —
975
+ // deliberately not per store, so it is not added to the per-store rows above.
976
+ const { platformFee, gstOnFee } = computeCheckoutFees(cartSubtotal, commissions);
930
977
  const total = Math.max(0, cartSubtotal - couponDiscount) +
931
978
  shippingFee +
932
979
  codHandlingFee +
933
980
  whatsappNotifyFeeTotal +
934
981
  giftWrapFeeTotal +
935
982
  shipmentProtectionFeeTotal +
983
+ platformFee +
984
+ gstOnFee +
936
985
  gstAmount;
937
986
  return {
938
987
  subtotal: cartSubtotal,
@@ -943,7 +992,10 @@ export async function previewCheckoutPricing(input) {
943
992
  shipmentProtectionFee: shipmentProtectionFeeTotal,
944
993
  gstAmount,
945
994
  couponDiscount,
995
+ platformFee,
996
+ gstOnFee,
946
997
  total,
998
+ stores,
947
999
  };
948
1000
  }
949
1001
  /**
@@ -1064,7 +1116,7 @@ async function refundDroppedItemsForRazorpayCheckout(input) {
1064
1116
  * from the inline version this replaced.
1065
1117
  */
1066
1118
  async function createRazorpayGroupOrder(group, orderType, ctx) {
1067
- const { appliedCoupons, cartSubtotal, platformFeePercent, gstPercent, whatsappNotifyFee, giftWrapFee, giftWrapMessage, shipmentProtectionAddon, siteSettings, uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, shippingAddress, notes, outOfStockPolicy, unavailablePaid, orderIds, emailsToSend, couponUsageAccumulator, } = ctx;
1119
+ const { appliedCoupons, cartSubtotal, storeAddons, platformFeeByStore, siteSettings, uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, shippingAddress, notes, outOfStockPolicy, unavailablePaid, orderIds, emailsToSend, couponUsageAccumulator, } = ctx;
1068
1120
  const firstItem = group[0].item;
1069
1121
  const groupTotal = group.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0);
1070
1122
  // Reuses the same store/seller lookup as the COD/UPI path above instead
@@ -1083,11 +1135,25 @@ async function createRazorpayGroupOrder(group, orderType, ctx) {
1083
1135
  groupCouponCodes.add(discount.code);
1084
1136
  }
1085
1137
  }
1086
- const rawPlatformFee = roundRupees(groupTotal * (platformFeePercent / 100));
1087
- const gstOnFee = roundRupees(rawPlatformFee * (gstPercent / 100));
1088
- const platformFee = rawPlatformFee + gstOnFee;
1089
- const shipmentProtectionFee = computeShipmentProtectionFee(groupTotal, shipmentProtectionAddon, siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS);
1090
- const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + whatsappNotifyFee + giftWrapFee + shipmentProtectionFee;
1138
+ // Was a per-group, uncapped `groupTotal × platformFeePercent` computed inline
1139
+ // here the only path that charged the commission at all, and it charged a
1140
+ // different (larger, multiplied-per-store) number than the cap now allows.
1141
+ // Both the amount and its allocation are now decided once for the checkout.
1142
+ // Kept as two separate values, exactly as the COD/manual path does. Folding the
1143
+ // GST into `platformFee` made `OrderDocument.platformFee` mean "fee" on one path
1144
+ // and "fee + GST" on the other, so no revenue rollup could read it without
1145
+ // knowing which path created the row. The buyer pays the same either way —
1146
+ // both components are still added to orderTotal below.
1147
+ const { platformFee = 0, gstOnFee: platformFeeGst = 0 } = platformFeeByStore.get(firstItem.storeId) ?? {};
1148
+ // Add-ons are this store's choice, read off the cart doc — previously one
1149
+ // cart-wide fee was passed in and billed against every group.
1150
+ const commissionRates = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
1151
+ const addons = storeAddons?.[firstItem.storeId] ?? {};
1152
+ const whatsappNotifyFee = computeWhatsAppNotifyFee(addons.whatsappNotifyAddon ?? false, commissionRates);
1153
+ const giftWrapFee = computeGiftWrapFee(addons.giftWrapAddon ?? false, commissionRates);
1154
+ const giftWrapMessage = addons.giftWrapMessage;
1155
+ const shipmentProtectionFee = computeShipmentProtectionFee(groupTotal, addons.shipmentProtectionAddon ?? false, commissionRates);
1156
+ const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + whatsappNotifyFee + giftWrapFee + shipmentProtectionFee + platformFee + platformFeeGst;
1091
1157
  // P-8 GST — deliberately NOT wired into this Razorpay-verify path. The
1092
1158
  // amount-mismatch check above (expectedPaymentAmountRs) compares against
1093
1159
  // what the buyer already paid via the Razorpay order created earlier in
@@ -1232,12 +1298,11 @@ async function createRazorpayGroupOrder(group, orderType, ctx) {
1232
1298
  return orderTotal;
1233
1299
  }
1234
1300
  export async function verifyAndPlaceRazorpayOrderAction(input) {
1235
- const { userId: uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, addressId, notes, outOfStockPolicy = OutOfStockPolicyValues.CANCEL_ORDER, whatsappNotifyAddon = false, giftWrapAddon = false, giftWrapMessage, shipmentProtectionAddon = false, } = input;
1301
+ const { userId: uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, addressId, notes, outOfStockPolicy = OutOfStockPolicyValues.CANCEL_ORDER, } = input;
1302
+ // Add-on fees are no longer resolved here. They are per store, and this
1303
+ // function has no single store — each group reads its own selection off
1304
+ // `cart.storeAddons` inside createRazorpayGroupOrder.
1236
1305
  const siteSettings = await siteSettingsRepository.getSingleton();
1237
- const platformFeePercent = siteSettings?.commissions?.platformFeePercent ?? 5;
1238
- const gstPercent = siteSettings?.commissions?.gstPercent ?? 18;
1239
- const whatsappNotifyFee = computeWhatsAppNotifyFee(whatsappNotifyAddon, siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS);
1240
- const giftWrapFee = computeGiftWrapFee(giftWrapAddon, siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS);
1241
1306
  const isValid = await verifyPaymentSignatureWithKeys({
1242
1307
  razorpay_order_id,
1243
1308
  razorpay_payment_id,
@@ -1360,8 +1425,12 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
1360
1425
  const unit = isBundle ? item.price : product.price;
1361
1426
  return sum + unit * item.quantity;
1362
1427
  }, 0);
1363
- const expectedPlatformFee = roundRupees(cartSubtotalRs * (platformFeePercent / 100));
1364
- const expectedGstOnFee = roundRupees(expectedPlatformFee * (gstPercent / 100));
1428
+ // Must use the SAME helper /api/payment/create-order used to compute what
1429
+ // it charged. This block used to re-derive the fee inline from the raw
1430
+ // percentages, so the moment the cap landed it would have expected more
1431
+ // than was actually collected and rejected every legitimate payment.
1432
+ const commissionRates = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
1433
+ const { platformFee: expectedPlatformFee, gstOnFee: expectedGstOnFee } = computeCheckoutFees(cartSubtotalRs, commissionRates);
1365
1434
  // Same per-seller-group shipping sum /api/payment/create-order charged
1366
1435
  // upfront (and the same resolveShippingCost createRazorpayGroupOrder
1367
1436
  // below will use to build each order's recorded totalPrice) — keeps this
@@ -1369,7 +1438,19 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
1369
1438
  const expectedShippingGroups = splitCartIntoOrderGroups(bucketedPaid.available);
1370
1439
  const expectedShippingFees = await Promise.all(expectedShippingGroups.map((g) => resolveShippingCost(g.items[0].item.storeId)));
1371
1440
  const expectedShippingFee = expectedShippingFees.reduce((sum, r) => sum + r.shippingFee, 0);
1372
- const expectedPaymentAmountRs = cartSubtotalRs + expectedPlatformFee + expectedGstOnFee + expectedShippingFee;
1441
+ // Add-ons are per store, so the expected total sums each group's own
1442
+ // selection — a single cart-wide figure would over- or under-count as soon
1443
+ // as the buyer picked add-ons for only some sellers.
1444
+ const expectedAddonFees = expectedShippingGroups.reduce((sum, g) => {
1445
+ const storeId = g.items[0].item.storeId;
1446
+ const addons = cart.storeAddons?.[storeId] ?? {};
1447
+ const groupSubtotal = g.items.reduce((gs, { item, product }) => gs + unitPriceFor(item, product) * item.quantity, 0);
1448
+ return (sum +
1449
+ computeWhatsAppNotifyFee(addons.whatsappNotifyAddon ?? false, commissionRates) +
1450
+ computeGiftWrapFee(addons.giftWrapAddon ?? false, commissionRates) +
1451
+ computeShipmentProtectionFee(groupSubtotal, addons.shipmentProtectionAddon ?? false, commissionRates));
1452
+ }, 0);
1453
+ const expectedPaymentAmountRs = cartSubtotalRs + expectedPlatformFee + expectedGstOnFee + expectedAddonFees + expectedShippingFee;
1373
1454
  const rzpOrderRecord = await fetchRazorpayOrder(razorpay_order_id);
1374
1455
  const paidAmountRs = paiseToRupees(rzpOrderRecord.amount);
1375
1456
  if (paidAmountRs < expectedPaymentAmountRs - 1) {
@@ -1397,16 +1478,17 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
1397
1478
  const cartSubtotal = orderGroups.reduce((s, { items: g }) => s +
1398
1479
  g.reduce((gs, { item, product }) => gs + unitPriceFor(item, product) * item.quantity, 0), 0);
1399
1480
  const couponUsageAccumulator = new Map();
1481
+ // Same single capped commission + pro-rata split as the COD/UPI path.
1482
+ const platformFeeByStore = allocateCheckoutFees(orderGroups.map(({ items: g }) => [
1483
+ g[0].item.storeId,
1484
+ g.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0),
1485
+ ]), computeCheckoutFees(cartSubtotal, siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS));
1400
1486
  for (const { items: group, orderType } of orderGroups) {
1401
1487
  total += await createRazorpayGroupOrder(group, orderType, {
1402
1488
  appliedCoupons,
1403
1489
  cartSubtotal,
1404
- platformFeePercent,
1405
- gstPercent,
1406
- whatsappNotifyFee,
1407
- giftWrapFee,
1408
- giftWrapMessage,
1409
- shipmentProtectionAddon,
1490
+ storeAddons: cart.storeAddons,
1491
+ platformFeeByStore,
1410
1492
  siteSettings,
1411
1493
  uid,
1412
1494
  userName,
@@ -11,7 +11,7 @@ import { sendNotification } from "../../../../features/admin/actions/notificatio
11
11
  import { restoreStockForOrder } from "../checkout/stock-restore";
12
12
  import { getAdminDb } from "../../../../providers/db-firebase";
13
13
  import { enqueueJob } from "../../../../features/jobs/actions/enqueue-job";
14
- import { PAYMENT_WINDOW_MS, PAYMENT_FRAUD_REJECTED_REASON } from "../../../../features/orders/constants/payment-window";
14
+ import { PAYMENT_WINDOW_MS, PAYMENT_FRAUD_REJECTED_REASON, isManualPaymentMethod } from "../../../../features/orders/constants/payment-window";
15
15
  import { normalizeError } from "../../../../errors/normalize";
16
16
  import { serverLogger } from "../../../../monitoring";
17
17
  export async function createOrderAction(input) {
@@ -131,8 +131,10 @@ export async function attachPaymentProofAction(orderId, proof) {
131
131
  throw new OrderNotFoundError(orderId);
132
132
  if (!isAdminUser(user) && order.userId !== user.uid)
133
133
  throw new OrderOwnershipError(orderId);
134
- const pm = order.paymentMethod ?? "";
135
- if (pm !== "cash" && pm !== "upi_manual" && pm !== "emi") {
134
+ // Use the shared predicate rather than re-inlining the method list — this
135
+ // was the second of the two sites that had drifted from it (the other being
136
+ // AdminOrderEditorView, which was missing `emi` outright).
137
+ if (!isManualPaymentMethod(order.paymentMethod ?? "")) {
136
138
  throw new ValidationError("Payment proof can only be attached to cash, UPI, or EMI orders");
137
139
  }
138
140
  if (order.paymentProofUrl) {
@@ -62,7 +62,20 @@ export function orderDocumentToOrder(doc) {
62
62
  total: doc.totalPrice,
63
63
  currency: doc.currency,
64
64
  couponCode: doc.couponCode,
65
+ couponDiscount: doc.couponDiscount,
65
66
  appliedDiscounts: doc.appliedDiscounts,
67
+ // Add-ons are operational, not decorative: the status-change notifier reads
68
+ // whatsappNotifyAddon, and the packer needs giftWrapMessage in hand. An
69
+ // adapter that drops them makes every downstream surface render its
70
+ // "not applicable" fallback instead (Root Cause #57).
71
+ whatsappNotifyAddon: doc.whatsappNotifyAddon,
72
+ whatsappNotifyFee: doc.whatsappNotifyFee,
73
+ giftWrapAddon: doc.giftWrapAddon,
74
+ giftWrapFee: doc.giftWrapFee,
75
+ giftWrapMessage: doc.giftWrapMessage,
76
+ shipmentProtectionAddon: doc.shipmentProtectionAddon,
77
+ shipmentProtectionFee: doc.shipmentProtectionFee,
78
+ platformFee: doc.platformFee,
66
79
  trackingNumber: doc.trackingNumber,
67
80
  shippingCarrier: doc.shippingCarrier,
68
81
  trackingUrl: doc.trackingUrl,
@@ -49,6 +49,16 @@ export interface PublicProductListInput {
49
49
  freeShipping?: boolean;
50
50
  isPartOfBundle?: boolean;
51
51
  features?: readonly string[];
52
+ /** Pipe-joined multi-select over the `tags` array field. */
53
+ tags?: readonly string[];
54
+ /** Pipe-joined multi-select over `sublistingCategoryId`. */
55
+ sublistingCategoryIds?: readonly string[];
56
+ /**
57
+ * Per-listing-type facets (classified meetup city, digital-code delivery
58
+ * method, live-item species, …), keyed by TABLE_KEY. Resolved to their
59
+ * nested Firestore paths via `TYPE_FACET_FIELD`.
60
+ */
61
+ typeFacets?: Record<string, string>;
52
62
  preOrderProductionStatus?: string;
53
63
  prizeRevealStatus?: string;
54
64
  /** Hide sold-out rows. Applied in memory — never pushed into Firestore. */
@@ -127,4 +137,22 @@ export declare function parsePublicProductParams(params: URLSearchParams | Recor
127
137
  hideEndedByDefault?: boolean;
128
138
  }): PublicProductListInput;
129
139
  export declare function listPublicProducts(input: PublicProductListInput, opts?: PublicProductListOptions): Promise<PublicProductListResult | null>;
140
+ /**
141
+ * One store tab's worth of listings.
142
+ *
143
+ * Every `Store*PageView` used to hand-roll this: `productRepository.list()`
144
+ * with an inline `sieveAnd(...)` and a `.catch(() => null)`. That is the exact
145
+ * shape that made `/art` render empty — an unsafe inequality (or any missing
146
+ * index) becomes FAILED_PRECONDITION, the catch turns it into `null`, and the
147
+ * page shows "no results" with nothing logged. Eight views carried it, and
148
+ * `audit-listing-filter-parity` had none of them registered.
149
+ *
150
+ * Routing them through `listPublicProducts` also gives every store tab
151
+ * something it never had: the URL's own sort/filter/page params reach the SSR
152
+ * fetch, so the first paint matches what the toolbar says is selected.
153
+ */
154
+ export declare function listStoreProducts(storeId: string, listingTypes: readonly string[], searchParams?: URLSearchParams | Record<string, string | string[] | undefined>, defaults?: {
155
+ pageSize?: number;
156
+ sorts?: string;
157
+ }): Promise<PublicProductListResult | null>;
130
158
  export {};