@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.
- package/dist/_internal/server/features/auctions/service.d.ts +7 -1
- package/dist/_internal/server/features/auctions/service.js +13 -5
- package/dist/_internal/server/features/checkout/actions.d.ts +36 -18
- package/dist/_internal/server/features/checkout/actions.js +127 -45
- package/dist/_internal/server/features/orders/actions.js +5 -3
- package/dist/_internal/server/features/orders/adapters.js +13 -0
- package/dist/_internal/server/features/products/list-public.d.ts +28 -0
- package/dist/_internal/server/features/products/list-public.js +92 -0
- package/dist/_internal/server/functions/firestore.d.ts +1 -2
- package/dist/_internal/server/functions/firestore.js +1 -9
- package/dist/_internal/server/jobs/core/auctionSettlement.js +5 -2
- package/dist/_internal/server/jobs/core/index.d.ts +0 -1
- package/dist/_internal/server/jobs/core/index.js +0 -1
- package/dist/_internal/server/jobs/core/offerExpiry.js +33 -1
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -2
- package/dist/_internal/server/jobs/handlers/index.js +1 -2
- package/dist/_internal/shared/checkout/lanes.d.ts +20 -0
- package/dist/_internal/shared/checkout/lanes.js +28 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +34 -0
- package/dist/_internal/shared/features/auctions/config.js +47 -3
- package/dist/_internal/shared/features/checkout/config.d.ts +4 -0
- package/dist/_internal/shared/features/checkout/config.js +8 -0
- package/dist/_internal/shared/fees/calculator.d.ts +34 -2
- package/dist/_internal/shared/fees/calculator.js +45 -1
- package/dist/_internal/shared/listing-types/auction/config.js +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
- package/dist/_internal/shared/listing-types/live/config.js +1 -1
- package/dist/_internal/shared/listing-types/pre-order/config.js +1 -1
- package/dist/client.d.ts +7 -1
- package/dist/client.js +4 -1
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/admin/components/AdminAddressesView.js +18 -10
- package/dist/features/admin/components/AdminArtView.d.ts +9 -0
- package/dist/features/admin/components/AdminArtView.js +11 -35
- package/dist/features/admin/components/AdminClassifiedView.d.ts +9 -0
- package/dist/features/admin/components/AdminClassifiedView.js +11 -35
- package/dist/features/admin/components/AdminDigitalCodesView.d.ts +9 -0
- package/dist/features/admin/components/AdminDigitalCodesView.js +11 -42
- package/dist/features/admin/components/AdminLiveView.d.ts +9 -0
- package/dist/features/admin/components/AdminLiveView.js +11 -41
- package/dist/features/admin/components/AdminNotificationsView.js +4 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +14 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +18 -5
- package/dist/features/admin/components/AdminOrdersView.js +15 -1
- package/dist/features/admin/components/AdminPaymentMethodsView.js +15 -10
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +4 -2
- package/dist/features/admin/components/AdminStickersView.d.ts +9 -0
- package/dist/features/admin/components/AdminStickersView.js +11 -35
- package/dist/features/admin/hooks/useAdminListing.js +7 -2
- package/dist/features/admin/schemas/firestore.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.js +1 -0
- package/dist/features/auctions/actions/bid-actions.js +73 -11
- package/dist/features/auctions/components/AuctionDetailPageView.js +8 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
- package/dist/features/auctions/components/PlaceBidFormClient.js +89 -14
- package/dist/features/auctions/schemas/bid-input.d.ts +19 -6
- package/dist/features/auctions/schemas/bid-input.js +19 -7
- package/dist/features/auth/repository/user.repository.js +6 -0
- package/dist/features/cart/components/CartDrawer.d.ts +9 -1
- package/dist/features/cart/components/CartDrawer.js +4 -3
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +61 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +25 -0
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +47 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +21 -0
- package/dist/features/cart/components/index.d.ts +4 -0
- package/dist/features/cart/components/index.js +2 -0
- package/dist/features/cart/repository/cart.repository.d.ts +20 -2
- package/dist/features/cart/repository/cart.repository.js +49 -0
- package/dist/features/cart/schemas/firestore.d.ts +26 -0
- package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
- package/dist/features/layout/BackToTop.js +1 -1
- package/dist/features/layout/BottomActions.js +38 -5
- package/dist/features/layout/BottomActionsContext.d.ts +12 -0
- package/dist/features/layout/BottomActionsContext.js +5 -0
- package/dist/features/layout/FooterLayout.js +2 -2
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/layout/hooks/useBottomActions.d.ts +6 -0
- package/dist/features/layout/hooks/useBottomActions.js +11 -1
- package/dist/features/orders/components/OrderAddonBadges.d.ts +36 -0
- package/dist/features/orders/components/OrderAddonBadges.js +28 -0
- package/dist/features/orders/components/index.d.ts +1 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/repository/orders.repository.d.ts +32 -0
- package/dist/features/orders/repository/orders.repository.js +14 -0
- package/dist/features/orders/types/index.d.ts +22 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/products/components/ArtStickersListView.js +5 -4
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
- package/dist/features/products/components/PrizeDrawCollage.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/PrizeDrawsListingView.js +18 -33
- package/dist/features/products/components/ProductDetailPageView.js +1 -1
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/components/ProductsIndexListing.js +87 -11
- package/dist/features/products/components/ProductsIndexPageView.js +14 -2
- package/dist/features/products/config/listing-type-listing-config.d.ts +31 -0
- package/dist/features/products/config/listing-type-listing-config.js +74 -0
- package/dist/features/products/hooks/useProducts.js +14 -0
- package/dist/features/products/repository/products.repository.d.ts +0 -4
- package/dist/features/products/repository/products.repository.js +0 -15
- package/dist/features/products/types/index.d.ts +15 -0
- package/dist/features/promotions/repository/coupons.repository.d.ts +12 -0
- package/dist/features/promotions/repository/coupons.repository.js +12 -0
- package/dist/features/seller/components/SellerOrdersView.js +2 -1
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/stores/components/StoreArtStickersPageView.d.ts +21 -0
- package/dist/features/stores/components/StoreArtStickersPageView.js +30 -0
- package/dist/features/stores/components/StoreAuctionsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreAuctionsPageView.js +17 -17
- package/dist/features/stores/components/StoreClassifiedsListing.js +7 -0
- package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreClassifiedsPageView.js +16 -12
- package/dist/features/stores/components/StoreDetailLayoutView.js +4 -3
- package/dist/features/stores/components/StoreDigitalCodesListing.js +5 -0
- package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreDigitalCodesPageView.js +16 -12
- package/dist/features/stores/components/StoreLiveItemsListing.js +7 -0
- package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreLiveItemsPageView.js +16 -12
- package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -1
- package/dist/features/stores/components/StorePreOrdersPageView.js +17 -17
- package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +4 -8
- package/dist/features/stores/components/StorePrizeDrawsPageView.js +17 -22
- package/dist/features/stores/components/StoreProductsListing.d.ts +7 -1
- package/dist/features/stores/components/StoreProductsListing.js +3 -2
- package/dist/features/stores/components/StoreProductsPageView.d.ts +1 -1
- package/dist/features/stores/components/StoreProductsPageView.js +16 -21
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +875 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +1 -1
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +1 -1
- package/dist/next/routing/route-map.d.ts +2 -0
- package/dist/next/routing/route-map.js +5 -0
- package/dist/seed/homepage-sections-seed-data.js +5 -1
- package/dist/seed/reviews-seed-data.js +52 -0
- package/dist/seed/site-settings-seed-data.js +18 -0
- package/dist/server-entry.d.ts +1 -0
- package/dist/server-entry.js +1 -0
- package/dist/styles.css +66 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/color-pairs.js +1 -0
- package/dist/tokens/tokens.css +33 -0
- package/dist/ui/components/Button.style.css +21 -0
- package/dist/ui/components/Checkbox.d.ts +10 -1
- package/dist/ui/components/Checkbox.js +14 -2
- package/dist/ui/components/Checkbox.style.css +11 -0
- package/dist/ui/components/ImageLightbox.js +7 -4
- package/dist/ui/components/surface-tokens.d.ts +7 -0
- package/dist/ui/components/surface-tokens.js +7 -0
- package/package.json +1 -1
- package/dist/_internal/server/jobs/core/onBidPlaced.d.ts +0 -15
- package/dist/_internal/server/jobs/core/onBidPlaced.js +0 -63
- package/dist/_internal/server/jobs/handlers/onBidPlaced.d.ts +0 -3
- 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
|
-
/**
|
|
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,
|
|
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
|
-
/**
|
|
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
|
|
22
|
-
const
|
|
23
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
825
|
-
|
|
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 = [],
|
|
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
|
-
|
|
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
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
-
|
|
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
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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,
|
|
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
|
-
|
|
1364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1405
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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 {};
|