@mohasinac/appkit 4.62.1 → 4.63.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/.build-stamp.json +3 -3
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -29
- package/dist/_internal/client/features/layout/filterNavItems.d.ts +28 -0
- package/dist/_internal/client/features/layout/filterNavItems.js +35 -0
- package/dist/_internal/server/features/ads/booking.d.ts +118 -0
- package/dist/_internal/server/features/ads/booking.js +326 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +51 -11
- package/dist/_internal/server/features/checkout/actions.js +335 -80
- package/dist/_internal/server/features/checkout/expand-order-items.d.ts +1 -2
- package/dist/_internal/server/features/checkout/expand-order-items.js +32 -0
- package/dist/_internal/server/features/checkout/freeze-parts.d.ts +50 -0
- package/dist/_internal/server/features/checkout/freeze-parts.js +94 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.d.ts +55 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.js +60 -0
- package/dist/_internal/server/features/dashboard/attention.d.ts +128 -0
- package/dist/_internal/server/features/dashboard/attention.js +183 -0
- package/dist/_internal/server/features/dashboard/data.d.ts +47 -0
- package/dist/_internal/server/features/dashboard/data.js +82 -0
- package/dist/_internal/server/features/inventory/reservations.d.ts +99 -0
- package/dist/_internal/server/features/inventory/reservations.js +273 -0
- package/dist/_internal/server/features/offers/destinations.d.ts +40 -0
- package/dist/_internal/server/features/offers/destinations.js +65 -0
- package/dist/_internal/server/features/orders/adapters.js +21 -0
- package/dist/_internal/server/features/orders/amend-delivery.d.ts +56 -0
- package/dist/_internal/server/features/orders/amend-delivery.js +124 -0
- package/dist/_internal/server/features/orders/balance.d.ts +89 -0
- package/dist/_internal/server/features/orders/balance.js +256 -0
- package/dist/_internal/server/features/pricing/actions.d.ts +58 -0
- package/dist/_internal/server/features/pricing/actions.js +144 -0
- package/dist/_internal/server/features/shipping/book-split.d.ts +86 -0
- package/dist/_internal/server/features/shipping/book-split.js +130 -0
- package/dist/_internal/server/features/shipping/book.d.ts +61 -0
- package/dist/_internal/server/features/shipping/book.js +176 -0
- package/dist/_internal/server/features/shipping/courier-block.js +26 -1
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +14 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +14 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +57 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +2 -0
- package/dist/_internal/server/jobs/core/jobRunners.js +45 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +4 -1
- package/dist/_internal/server/jobs/core/reservationExpiry.d.ts +17 -0
- package/dist/_internal/server/jobs/core/reservationExpiry.js +40 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +15 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.js +2 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +268 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +31 -2
- package/dist/_internal/shared/checkout/order-math.js +22 -36
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +18 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +25 -0
- package/dist/_internal/shared/checkout/split.d.ts +0 -12
- package/dist/_internal/shared/checkout/split.js +22 -2
- package/dist/_internal/shared/content/disclaimers.js +7 -7
- package/dist/_internal/shared/content/published-values.d.ts +6 -1
- package/dist/_internal/shared/content/published-values.js +8 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +32 -2
- package/dist/_internal/shared/features/admin/integrations.js +19 -9
- package/dist/_internal/shared/features/dashboard/hub-sections.d.ts +72 -0
- package/dist/_internal/shared/features/dashboard/hub-sections.js +75 -0
- package/dist/_internal/shared/features/emi/choice.d.ts +98 -0
- package/dist/_internal/shared/features/emi/choice.js +69 -0
- package/dist/_internal/shared/features/emi/group.d.ts +84 -0
- package/dist/_internal/shared/features/emi/group.js +126 -0
- package/dist/_internal/shared/features/ledger/posting.d.ts +107 -0
- package/dist/_internal/shared/features/ledger/posting.js +165 -0
- package/dist/_internal/shared/features/ledger/reports.d.ts +76 -0
- package/dist/_internal/shared/features/ledger/reports.js +127 -0
- package/dist/_internal/shared/features/ledger/rules.d.ts +170 -0
- package/dist/_internal/shared/features/ledger/rules.js +221 -0
- package/dist/_internal/shared/features/offers/coordination.d.ts +122 -0
- package/dist/_internal/shared/features/offers/coordination.js +80 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.d.ts +114 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.js +116 -0
- package/dist/_internal/shared/features/payouts/pay-period.d.ts +91 -0
- package/dist/_internal/shared/features/payouts/pay-period.js +104 -0
- package/dist/_internal/shared/features/points/accepts.d.ts +1 -8
- package/dist/_internal/shared/features/points/accepts.js +12 -6
- package/dist/_internal/shared/features/pricing/rate-registry.d.ts +150 -0
- package/dist/_internal/shared/features/pricing/rate-registry.js +246 -0
- package/dist/_internal/shared/features/shipping/parcel.d.ts +123 -0
- package/dist/_internal/shared/features/shipping/parcel.js +139 -0
- package/dist/_internal/shared/features/site-settings/fees.d.ts +16 -8
- package/dist/_internal/shared/features/site-settings/fees.js +12 -8
- package/dist/_internal/shared/fees/explain.d.ts +81 -0
- package/dist/_internal/shared/fees/explain.js +101 -0
- package/dist/_internal/shared/history/index.d.ts +1 -1
- package/dist/_internal/shared/history/index.js +1 -1
- package/dist/_internal/shared/history/types.d.ts +7 -1
- package/dist/_internal/shared/history/types.js +25 -0
- package/dist/client.d.ts +11 -1
- package/dist/client.js +13 -0
- package/dist/constants/api-endpoints.d.ts +72 -0
- package/dist/constants/api-endpoints.js +41 -0
- package/dist/constants/field-names.d.ts +41 -0
- package/dist/constants/field-names.js +47 -0
- package/dist/features/account/components/UserOrdersView.js +28 -2
- package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
- package/dist/features/admin/components/AdminIntegrationsView.js +74 -0
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +16 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +72 -3
- package/dist/features/admin/components/AdminOrdersView.js +20 -1
- package/dist/features/admin/schemas/firestore.d.ts +32 -0
- package/dist/features/ads/schemas/booking-write.d.ts +157 -0
- package/dist/features/ads/schemas/booking-write.js +88 -0
- package/dist/features/ads/schemas/creative-form.d.ts +18 -0
- package/dist/features/ads/schemas/creative-form.js +51 -0
- package/dist/features/ads/schemas/firestore.d.ts +140 -0
- package/dist/features/ads/schemas/firestore.js +109 -0
- package/dist/features/auctions/repository/bid.repository.js +3 -1
- package/dist/features/audit-log/schemas/firestore.d.ts +110 -0
- package/dist/features/audit-log/schemas/firestore.js +74 -0
- package/dist/features/auth/actions/profile-actions.d.ts +69 -1
- package/dist/features/auth/actions/profile-actions.js +62 -1
- package/dist/features/auth/schemas/firestore.d.ts +16 -2
- package/dist/features/auth/schemas/firestore.js +2 -1
- package/dist/features/blog/actions/blog-actions.d.ts +6 -0
- package/dist/features/blog/actions/blog-actions.js +17 -2
- package/dist/features/blog/api/route.js +4 -1
- package/dist/features/blog/columns/index.js +5 -0
- package/dist/features/blog/components/BlogIndexPageView.js +2 -1
- package/dist/features/blog/repository/blog.repository.d.ts +4 -0
- package/dist/features/blog/repository/blog.repository.js +5 -0
- package/dist/features/blog/schemas/blog-form.d.ts +3 -0
- package/dist/features/blog/schemas/blog-form.js +1 -0
- package/dist/features/blog/schemas/firestore.d.ts +38 -1
- package/dist/features/blog/schemas/firestore.js +23 -0
- package/dist/features/blog/schemas/index.d.ts +6 -0
- package/dist/features/blog/schemas/index.js +2 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +12 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +24 -5
- package/dist/features/cart/repository/cart.repository.d.ts +14 -0
- package/dist/features/cart/repository/cart.repository.js +20 -0
- package/dist/features/cart/schemas/firestore.d.ts +24 -0
- package/dist/features/dashboard/components/DashboardHubView.d.ts +83 -0
- package/dist/features/dashboard/components/DashboardHubView.js +56 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.d.ts +23 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.js +66 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.d.ts +55 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.js +175 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +24 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +28 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +3 -5
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/faq/components/FAQPageContent.js +2 -2
- package/dist/features/gateway-events/schemas/index.d.ts +2 -2
- package/dist/features/homepage/schemas/index.d.ts +8 -8
- package/dist/features/inventory/schemas/stock-reservation.d.ts +114 -0
- package/dist/features/inventory/schemas/stock-reservation.js +103 -0
- package/dist/features/layout/AppLayoutShell.d.ts +9 -1
- package/dist/features/layout/AppLayoutShell.js +12 -4
- package/dist/features/layout/BottomActions.js +2 -1
- package/dist/features/layout/BottomNavbar.d.ts +10 -2
- package/dist/features/layout/SideActionDock.js +52 -2
- package/dist/features/layout/TitleBarLayout.js +13 -4
- package/dist/features/layout/TitleBarLayout.style.css +59 -0
- package/dist/features/layout/useCtaReveal.d.ts +16 -1
- package/dist/features/layout/useCtaReveal.js +17 -26
- package/dist/features/ledger/repository/journal.repository.d.ts +110 -0
- package/dist/features/ledger/repository/journal.repository.js +312 -0
- package/dist/features/ledger/schemas/expenses.d.ts +87 -0
- package/dist/features/ledger/schemas/expenses.js +27 -0
- package/dist/features/ledger/schemas/firestore.d.ts +183 -0
- package/dist/features/ledger/schemas/firestore.js +69 -0
- package/dist/features/orders/components/BalancePaymentPanel.d.ts +12 -0
- package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
- package/dist/features/orders/components/RaiseDisputeForm.js +19 -11
- package/dist/features/orders/repository/orders.repository.d.ts +28 -1
- package/dist/features/orders/repository/orders.repository.js +60 -6
- package/dist/features/orders/schemas/balance.d.ts +40 -0
- package/dist/features/orders/schemas/balance.js +47 -0
- package/dist/features/orders/schemas/firestore.d.ts +202 -0
- package/dist/features/orders/schemas/firestore.js +43 -0
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/orders/types/index.d.ts +21 -0
- package/dist/features/payments/repository/payout.repository.js +3 -2
- package/dist/features/payments/schemas/firestore.d.ts +92 -1
- package/dist/features/payments/schemas/firestore.js +23 -0
- package/dist/features/pricing/components/AdminPricingView.d.ts +13 -0
- package/dist/features/pricing/components/AdminPricingView.js +157 -0
- package/dist/features/products/schemas/firestore.d.ts +26 -1
- package/dist/features/products/schemas/firestore.js +2 -0
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/reviews/repository/reviews.repository.d.ts +14 -0
- package/dist/features/reviews/repository/reviews.repository.js +16 -0
- package/dist/features/scams/repository/scammer.repository.js +2 -1
- package/dist/features/scams/schemas/firestore.d.ts +2 -1
- package/dist/features/scams/schemas/firestore.js +2 -1
- package/dist/features/seller/actions/offer-actions.js +56 -0
- package/dist/features/seller/actions/seller-actions.d.ts +1 -1
- package/dist/features/seller/actions/seller-actions.js +4 -0
- package/dist/features/seller/components/DispatchForm.d.ts +11 -0
- package/dist/features/seller/components/DispatchForm.js +190 -0
- package/dist/features/seller/components/SellerOrdersView.js +94 -2
- package/dist/features/seller/components/index.d.ts +1 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/seller/repository/offer.repository.js +4 -3
- package/dist/features/seller/schemas/firestore.d.ts +23 -2
- package/dist/features/seller/schemas/firestore.js +3 -2
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/seller/schemas/order-forms.d.ts +61 -4
- package/dist/features/seller/schemas/order-forms.js +41 -8
- package/dist/features/shipments/schemas/firestore.d.ts +25 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +39 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +55 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +2 -4
- package/dist/features/stores/repository/store.repository.d.ts +18 -0
- package/dist/features/stores/repository/store.repository.js +27 -1
- package/dist/features/stores/schemas/firestore.d.ts +41 -0
- package/dist/features/stores/schemas/firestore.js +20 -0
- package/dist/features/stores/schemas/index.d.ts +6 -6
- package/dist/features/support/repository/support.repository.js +72 -6
- package/dist/features/support/schemas/firestore.d.ts +17 -0
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +19 -0
- package/dist/next/routing/route-map.d.ts +92 -0
- package/dist/next/routing/route-map.js +53 -0
- package/dist/react/contexts/SessionContext.d.ts +17 -1
- package/dist/schemas/registry.d.ts +22 -16
- package/dist/security/pii-schemas.d.ts +1 -1
- package/dist/security/pii-schemas.js +45 -0
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +10 -1
- package/dist/server.d.ts +29 -1
- package/dist/server.js +158 -0
- package/dist/styles.css +349 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.d.ts +10 -1
- package/dist/ui/components/Button.js +25 -3
- package/dist/ui/components/Chart.d.ts +63 -0
- package/dist/ui/components/Chart.js +169 -0
- package/dist/ui/components/Chart.style.css +214 -0
- package/dist/ui/components/Disclaimer.js +10 -1
- package/dist/ui/components/OverflowRow.js +54 -2
- package/dist/ui/components/OverflowRow.style.css +38 -0
- package/dist/ui/components/RichTextEditor.js +99 -8
- package/dist/ui/components/chart-spec.d.ts +68 -0
- package/dist/ui/components/chart-spec.js +140 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/components/surface-tokens.d.ts +1 -1
- package/dist/ui/components/surface-tokens.js +9 -0
- package/dist/ui/components/toolbar-collapse.d.ts +13 -0
- package/dist/ui/components/toolbar-collapse.js +10 -1
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/rich-text/RichText.d.ts +10 -1
- package/dist/ui/rich-text/RichText.js +33 -2
- package/dist/ui/rich-text/RichText.style.css +35 -0
- package/dist/ui/rich-text/RichTextRenderer.d.ts +30 -1
- package/dist/ui/rich-text/RichTextRenderer.js +34 -2
- package/dist/ui/rich-text/chart-blocks.d.ts +62 -0
- package/dist/ui/rich-text/chart-blocks.js +87 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +3 -0
|
@@ -16,7 +16,7 @@ import { roundShippingUp, } from "../../../shared/shipping/quote.js";
|
|
|
16
16
|
import { unitOfWork, adminNotificationsRepository, siteSettingsRepository, userRepository, storeRepository, couponsRepository, claimedCouponsRepository, addressesRepository, productRepository, } from "../../../../repositories/index.js";
|
|
17
17
|
import { isIntraStateSupply } from "../../../shared/fees/calculator.js";
|
|
18
18
|
import { resolveStateKey, INDIA_STATES } from "../../../shared/fees/india-states.js";
|
|
19
|
-
import { allocatePointsAcrossGroups, computePreOrderDepositAmount, lineTotalFor, resolvePointsTender, sumGroupGst, unitPriceFor, } from "../../../shared/checkout/order-math.js";
|
|
19
|
+
import { allocatePointsAcrossGroups, pointsBaseForGroup, computePreOrderDepositAmount, lineTotalFor, resolvePointsTender, sumGroupGst, unitPriceFor, } from "../../../shared/checkout/order-math.js";
|
|
20
20
|
/*
|
|
21
21
|
* D46/D47 — points as a checkout TENDER.
|
|
22
22
|
*
|
|
@@ -50,11 +50,13 @@ import { assertCheckoutLane, assertLockedLinesStillValid, finalizeLockedLines, }
|
|
|
50
50
|
import { activeLane, defaultCheckoutItems, resolveCheckoutItems, laneOf, } from "../../../shared/checkout/lanes.js";
|
|
51
51
|
import { computeCodHandlingFee, computeGiftWrapFee, computeShipmentProtectionFee, computeSaleProtectionFee, computeCheckoutFees, allocateCheckoutFees, } from "../../../shared/fees/calculator.js";
|
|
52
52
|
import { getAdminDb, getAdminRealtimeDb, RTDB_PATHS, } from "../../../../providers/db-firebase/index.js";
|
|
53
|
+
import { snapshotDeliveryAddress, } from "../../../shared/features/orders/delivery-snapshot.js";
|
|
53
54
|
import { PRODUCT_COLLECTION, PRODUCT_CODES_SUBCOLLECTION } from "../../../../features/products/schemas/firestore.js";
|
|
54
55
|
import { CART_COLLECTION } from "../../../../features/cart/schemas/index.js";
|
|
55
56
|
// S-SBUNI-RULES 2026-05-13 — bundle cart-line stock fan-out helpers.
|
|
56
57
|
import { getCartItemMemberIds, getExpandedDecrements, bucketCartItemsByStock, isMultiMemberLine, } from "./bundle-expansion.js";
|
|
57
58
|
import { expandCartLineToOrderRows } from "./expand-order-items.js";
|
|
59
|
+
import { freezePartsOnItems } from "./freeze-parts.js";
|
|
58
60
|
import { OrderStatusValues, PaymentStatusValues, PaymentMethodValues, OutOfStockPolicyValues, } from "../../../../features/orders/schemas/index.js";
|
|
59
61
|
import { getDefaultCurrency } from "../../../../core/index.js";
|
|
60
62
|
import { getProviders } from "../../../../contracts/index.js";
|
|
@@ -67,10 +69,11 @@ import { verifyPaymentSignature as verifyRazorpayPaymentSignature } from "../../
|
|
|
67
69
|
import { resolveKeys } from "../../../../core/integration-keys.js";
|
|
68
70
|
import { paiseToRupees } from "../../../../core/money.js";
|
|
69
71
|
import { CHECKOUT_DEFAULT_COMMISSIONS, CHECKOUT_DEFAULT_EMI_SETTINGS } from "../../../shared/features/checkout/config.js";
|
|
70
|
-
import {
|
|
72
|
+
import { emiTenderFor, resolveEmiChoice } from "../../../shared/features/emi/choice.js";
|
|
73
|
+
import { amountDueNow, emiOrderFields, groupEmiGate, groupEmiQuote, groupEmiSchedule, } from "../../../shared/features/emi/group.js";
|
|
71
74
|
import { formatShippingAddress } from "./data.js";
|
|
72
75
|
import { enforceMaxPerUserForCart } from "./prize-bundle-gates.js";
|
|
73
|
-
import { getListingRule, runSyncPreflight, assertPaymentMethodAllowed, } from "../../../shared/checkout/rules/index.js";
|
|
76
|
+
import { getListingRule, runSyncPreflight, assertPaymentMethodAllowed, assertEmiOptInAllowed, } from "../../../shared/checkout/rules/index.js";
|
|
74
77
|
// The `GroupCouponDiscountResult` type is deliberately NOT imported: every
|
|
75
78
|
// call site here destructures the result inline, so naming the type would be
|
|
76
79
|
// an unused import.
|
|
@@ -82,7 +85,7 @@ import { sendNotification } from "../../../../features/admin/actions/notificatio
|
|
|
82
85
|
import { resolveDisplayedUpiId } from "./upi-resolution.js";
|
|
83
86
|
import { resolveSupplierIdentity } from "./supplier-identity.js";
|
|
84
87
|
import { PAYMENT_WINDOW_MS } from "../../../../features/orders/constants/payment-window.js";
|
|
85
|
-
import {
|
|
88
|
+
import { assertCheckoutValueOtp } from "./value-otp-gate.js";
|
|
86
89
|
const AUDIT_LOG_FAIL_MSG = "checkout: audit log failed (non-critical)";
|
|
87
90
|
/**
|
|
88
91
|
* How long a `gatewayOrderClaims` holder owns the right to place an order
|
|
@@ -733,7 +736,7 @@ async function settlePointsTender(input) {
|
|
|
733
736
|
* so the caller can accumulate the checkout-wide `total`.
|
|
734
737
|
*/
|
|
735
738
|
async function createOrderForGroup(group, orderType, ctx) {
|
|
736
|
-
const { paymentMethod, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, buyerState, gstSettings, siteContactUpiVpa, storeAddons, platformFeeByStore, selectedShippingQuote, useAllPoints, pointsBudget, } = ctx;
|
|
739
|
+
const { paymentMethod, emiOptIn, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, deliverySnapshot, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, buyerState, gstSettings, siteContactUpiVpa, storeAddons, platformFeeByStore, selectedShippingQuote, useAllPoints, pointsBudget, } = ctx;
|
|
737
740
|
const firstItem = group[0].item;
|
|
738
741
|
const firstProduct = group[0].product;
|
|
739
742
|
// Add-ons are this store's choice, not the cart's — see OrderGroupContext.
|
|
@@ -767,11 +770,24 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
767
770
|
// A multi-member line expands into one row PER MEMBER sharing a groupSlug, so
|
|
768
771
|
// each row carries its own HSN/GST and can be cancelled independently; the
|
|
769
772
|
// receipt still collapses them under one header.
|
|
770
|
-
const
|
|
773
|
+
const baseOrderItems = group.flatMap(({ item, product }) => {
|
|
771
774
|
const lt = (product.listingType ?? "standard");
|
|
772
775
|
const itemRule = getListingRule(lt);
|
|
773
776
|
return expandCartLineToOrderRows(item, product, ctx.productById).map((baseLine) => itemRule.decorateOrderItem(baseLine, product));
|
|
774
777
|
});
|
|
778
|
+
/*
|
|
779
|
+
* Part 14 — freeze the parts a composed listing was sold as containing.
|
|
780
|
+
*
|
|
781
|
+
* A separate pass because composition lives in `modelCompositions` keyed by
|
|
782
|
+
* the model's CATEGORY leaf, not on the product, so it needs a read — and
|
|
783
|
+
* `expandCartLineToOrderRows` is pure by design.
|
|
784
|
+
*
|
|
785
|
+
* 🛑 Usually free: it returns immediately unless some line's listing type
|
|
786
|
+
* has a composition at all, and then reads only the DISTINCT leaves,
|
|
787
|
+
* capped. It never throws — a parts list we could not read must not fail a
|
|
788
|
+
* payment (see the module header).
|
|
789
|
+
*/
|
|
790
|
+
const orderItems = await freezePartsOnItems(baseOrderItems);
|
|
775
791
|
const totalQuantity = group.reduce((sum, { item }) => sum + item.quantity, 0);
|
|
776
792
|
/*
|
|
777
793
|
* D44: the group's real goods value, so free-above-threshold can fire.
|
|
@@ -830,14 +846,30 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
830
846
|
* simply did not exist yet. The split now sits immediately after
|
|
831
847
|
* `orderTotal`, which is the only place every term is known.
|
|
832
848
|
*/
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
849
|
+
/*
|
|
850
|
+
* 🛑 Keyed on `emiOptIn`, NOT on the tender. An EMI order's `paymentMethod`
|
|
851
|
+
* is now whatever the buyer picked to pay the token with — `online`,
|
|
852
|
+
* `upi_manual` or `cash` — so testing `paymentMethod === "emi"` here would
|
|
853
|
+
* build a schedule for nobody and every instalment plan would silently
|
|
854
|
+
* vanish into a full-price order.
|
|
855
|
+
*/
|
|
856
|
+
const emiGroupInput = {
|
|
857
|
+
emiOptIn,
|
|
858
|
+
emiTenureMonths,
|
|
859
|
+
groupSubtotal: groupTotal,
|
|
860
|
+
sellerEmiEnabled: storeEmiEnabled,
|
|
861
|
+
};
|
|
862
|
+
/*
|
|
863
|
+
* 🛑 REFUSES on an ineligible EMI order rather than quietly dropping the
|
|
864
|
+
* plan. The preview deliberately does the opposite — see `groupEmiGate`'s
|
|
865
|
+
* docstring. Silently producing a full-price order from an EMI request is
|
|
866
|
+
* money the buyer did not agree to.
|
|
867
|
+
*/
|
|
868
|
+
const emiGate = groupEmiGate(emiGroupInput, emiSettings);
|
|
869
|
+
if (emiOptIn && emiTenureMonths && !emiGate.applies) {
|
|
870
|
+
throw new ValidationError(`EMI is not available for this order (${emiGate.reason}). Minimum order value per seller is ₹${emiSettings.minOrderValue}.`);
|
|
840
871
|
}
|
|
872
|
+
const emiSchedule = groupEmiSchedule(emiGroupInput, emiSettings) ?? undefined;
|
|
841
873
|
// Handling fee charged to the buyer for choosing COD, on top of the order
|
|
842
874
|
// total — not deducted from the deposit/remaining split above.
|
|
843
875
|
const codHandlingFee = paymentMethod === "cod" ? computeCodHandlingFee(groupTotal, commissions) : 0;
|
|
@@ -1063,10 +1095,18 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
1063
1095
|
// Deadline + displayed UPI apply only to methods with a buyer-uploaded
|
|
1064
1096
|
// proof step — never cod (paid on delivery), online (paid before order
|
|
1065
1097
|
// creation), or admin_bypass (already marked paid above).
|
|
1098
|
+
/*
|
|
1099
|
+
* 🛑 `PaymentMethodValues.EMI` was a third arm here and is gone on purpose,
|
|
1100
|
+
* not by oversight. It is unreachable after `resolveEmiChoice` — an EMI
|
|
1101
|
+
* order's tender is `online` / `upi_manual` / `cash` — and leaving it would
|
|
1102
|
+
* assert that "EMI" is a way of paying, which is exactly the conflation 0f
|
|
1103
|
+
* removed. An EMI buyer paying by bank transfer still gets the window,
|
|
1104
|
+
* because their tender IS `upi_manual`; one paying online does not, because
|
|
1105
|
+
* the token is captured before the order exists.
|
|
1106
|
+
*/
|
|
1066
1107
|
const hasPaymentWindow = !adminBypass &&
|
|
1067
1108
|
(paymentMethod === PaymentMethodValues.UPI_MANUAL ||
|
|
1068
|
-
paymentMethod === PaymentMethodValues.CASH
|
|
1069
|
-
paymentMethod === PaymentMethodValues.EMI);
|
|
1109
|
+
paymentMethod === PaymentMethodValues.CASH);
|
|
1070
1110
|
const paymentDeadline = hasPaymentWindow
|
|
1071
1111
|
? new Date(Date.now() + PAYMENT_WINDOW_MS)
|
|
1072
1112
|
: undefined;
|
|
@@ -1111,6 +1151,16 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
1111
1151
|
});
|
|
1112
1152
|
if (pointsApplied > 0)
|
|
1113
1153
|
pointsBudget.remaining -= pointsApplied;
|
|
1154
|
+
/*
|
|
1155
|
+
* What is COLLECTED, composing both tenders that reduce it. On a plain order
|
|
1156
|
+
* this equals `orderTotal`, so the field is omitted below and every
|
|
1157
|
+
* `amountDue ?? totalPrice` reader is unaffected.
|
|
1158
|
+
*/
|
|
1159
|
+
const amountDueOnOrder = amountDueNow({
|
|
1160
|
+
orderTotal,
|
|
1161
|
+
pointsApplied,
|
|
1162
|
+
emiRemainingBalance: emiSchedule?.remainingBalance ?? 0,
|
|
1163
|
+
});
|
|
1114
1164
|
// Both creation paths call the SAME builder — two hand-written copies is the
|
|
1115
1165
|
// create-vs-create axis of Root Cause #39, and these two functions have
|
|
1116
1166
|
// already drifted once before (add-on fees, Root Cause #65).
|
|
@@ -1140,7 +1190,23 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
1140
1190
|
* that predate this.
|
|
1141
1191
|
*/
|
|
1142
1192
|
pointsApplied: pointsApplied > 0 ? pointsApplied : undefined,
|
|
1143
|
-
|
|
1193
|
+
/*
|
|
1194
|
+
* 🛑 WHAT IS COLLECTED NOW, and on an EMI order that is the token — not
|
|
1195
|
+
* the whole total.
|
|
1196
|
+
*
|
|
1197
|
+
* This was `pointsApplied > 0 ? amountDue : undefined`, so an EMI order
|
|
1198
|
+
* carried no `amountDue` at all and every reader fell back to
|
|
1199
|
+
* `order.total`. The buyer's manual-payment page therefore asked an EMI
|
|
1200
|
+
* buyer to transfer the FULL order value up front and then showed them a
|
|
1201
|
+
* schedule of monthly instalments: `emiTokenAmount` was computed, stored,
|
|
1202
|
+
* and read by nothing (measured — zero readers across both repos).
|
|
1203
|
+
*
|
|
1204
|
+
* `amountDueNow` subtracts the deferred balance as well as the points, so
|
|
1205
|
+
* the two tenders compose. Written only when it differs from the total, so
|
|
1206
|
+
* every historical order keeps resolving through the same
|
|
1207
|
+
* `amountDue ?? totalPrice` fallback its readers already use.
|
|
1208
|
+
*/
|
|
1209
|
+
amountDue: amountDueOnOrder !== orderTotal ? amountDueOnOrder : undefined,
|
|
1144
1210
|
currency: firstItem.currency ?? getDefaultCurrency(),
|
|
1145
1211
|
storeId: firstItem.storeId || undefined,
|
|
1146
1212
|
storeName: firstItem.storeName || undefined,
|
|
@@ -1173,6 +1239,12 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
1173
1239
|
displayedUpiId,
|
|
1174
1240
|
adminBypassBy: adminBypassBy,
|
|
1175
1241
|
shippingAddress,
|
|
1242
|
+
/*
|
|
1243
|
+
* 🛑 Spread BEFORE `placeOfSupply`, which now prefers the snapshot's
|
|
1244
|
+
* state. Spreading after would let an absent snapshot overwrite a
|
|
1245
|
+
* state the digital-cart fallback had already resolved.
|
|
1246
|
+
*/
|
|
1247
|
+
...(deliverySnapshot ?? {}),
|
|
1176
1248
|
notes,
|
|
1177
1249
|
shippingFee: shippingFee > 0 ? shippingFee : undefined,
|
|
1178
1250
|
depositAmount: adminBypass ? undefined : depositAmount,
|
|
@@ -1225,7 +1297,12 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
1225
1297
|
* before, so an order could not state why it was taxed the way it was, and
|
|
1226
1298
|
* GSTR-1's mandatory place-of-supply column had nothing to read.
|
|
1227
1299
|
*/
|
|
1228
|
-
|
|
1300
|
+
/*
|
|
1301
|
+
* Part 14 — the snapshot's state is authoritative, because it is the
|
|
1302
|
+
* field the invoice and GSTR-1 actually read. `buyerState` stays as
|
|
1303
|
+
* the fallback for a digital cart, which has no delivery address.
|
|
1304
|
+
*/
|
|
1305
|
+
placeOfSupply: deliverySnapshot?.deliveryState || buyerState || undefined,
|
|
1229
1306
|
/* The supplier of record AS IT STANDS NOW. A tax invoice without the
|
|
1230
1307
|
* supplier's GSTIN is not a tax invoice, and the invoice page cannot read
|
|
1231
1308
|
* it from site settings — `gst` is in PRIVATE_SITE_FIELDS. See the schema.
|
|
@@ -1263,23 +1340,19 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
1263
1340
|
cgst: gstBreakdown?.cgst || undefined,
|
|
1264
1341
|
sgst: gstBreakdown?.sgst || undefined,
|
|
1265
1342
|
igst: gstBreakdown?.igst || undefined,
|
|
1266
|
-
emiEnabled: !adminBypass && !!emiSchedule ? true : undefined,
|
|
1267
|
-
emiTenureMonths: !adminBypass && emiSchedule ? emiTenureMonths : undefined,
|
|
1268
|
-
emiTokenAmount: !adminBypass ? emiSchedule?.tokenAmount : undefined,
|
|
1269
|
-
emiSurchargeAmount: !adminBypass ? emiSchedule?.surchargeAmount : undefined,
|
|
1270
1343
|
/*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
1344
|
+
* The eleven `emi*` fields, from one builder shared with the gateway
|
|
1345
|
+
* path — which carried none of them, so every order paid online lost its
|
|
1346
|
+
* instalment plan. D45's note about `emiSurchargeSellerShare` being
|
|
1347
|
+
* computed and discarded now lives on `emiOrderFields` itself.
|
|
1348
|
+
*
|
|
1349
|
+
* 🛑 `adminBypass` nulls the whole group by passing no schedule, rather
|
|
1350
|
+
* than by repeating `!adminBypass &&` eleven times. The bypass marks an
|
|
1351
|
+
* order paid without a payment, so there is no token and no plan — and
|
|
1352
|
+
* `resolveEmiChoice` now refuses that combination outright, making this
|
|
1353
|
+
* a second line of defence rather than the only one.
|
|
1276
1354
|
*/
|
|
1277
|
-
|
|
1278
|
-
emiInstallments: !adminBypass && emiSchedule
|
|
1279
|
-
? emiSchedule.installments.map((i) => ({ index: i.index, dueDate: i.dueDate, amount: i.amount, status: "pending" }))
|
|
1280
|
-
: undefined,
|
|
1281
|
-
emiRemainingBalance: !adminBypass ? emiSchedule?.remainingBalance : undefined,
|
|
1282
|
-
emiComplete: !adminBypass && emiSchedule ? false : undefined,
|
|
1355
|
+
...emiOrderFields(adminBypass ? null : (emiSchedule ?? null), emiTenureMonths),
|
|
1283
1356
|
couponCode: appliedDiscounts[0]?.code,
|
|
1284
1357
|
couponDiscount: couponDiscount > 0 ? couponDiscount : undefined,
|
|
1285
1358
|
appliedDiscounts: appliedDiscounts.length > 0 ? appliedDiscounts : undefined,
|
|
@@ -1342,15 +1415,17 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
1342
1415
|
* auth + rate-limiting before calling this and supply the resolved user fields.
|
|
1343
1416
|
*/
|
|
1344
1417
|
export async function createCheckoutOrderAction(input) {
|
|
1345
|
-
const { userId: uid, userName, userEmail, addressId,
|
|
1418
|
+
const { userId: uid, userName, userEmail, addressId, notes, excludedProductIds = [], adminBypass = false, adminBypassBy, outOfStockPolicy = OutOfStockPolicyValues.SKIP_ITEMS, } = input;
|
|
1419
|
+
/*
|
|
1420
|
+
* The TENDER, resolved before anything else because the COD and manual-UPI
|
|
1421
|
+
* guards below are about the tender and run before the cart read. The
|
|
1422
|
+
* ARRANGEMENT (`emiOptIn` + tenure) lives on the cart and is resolved after
|
|
1423
|
+
* it — see the block below the cart load.
|
|
1424
|
+
*/
|
|
1425
|
+
const paymentMethod = emiTenderFor(input.paymentMethod);
|
|
1346
1426
|
const siteSettings = await siteSettingsRepository.getSingleton();
|
|
1347
1427
|
const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
|
|
1348
1428
|
const emiSettings = siteSettings?.emi ?? CHECKOUT_DEFAULT_EMI_SETTINGS;
|
|
1349
|
-
if (paymentMethod === "emi") {
|
|
1350
|
-
if (!emiTenureMonths || !emiSettings.tenureOptions.includes(emiTenureMonths)) {
|
|
1351
|
-
throw new ValidationError(`emiTenureMonths must be one of: ${emiSettings.tenureOptions.join(", ")}`);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
1429
|
// W1-43 — enforce COD toggle.
|
|
1355
1430
|
if (!adminBypass &&
|
|
1356
1431
|
paymentMethod === "cod" &&
|
|
@@ -1367,6 +1442,30 @@ export async function createCheckoutOrderAction(input) {
|
|
|
1367
1442
|
if (!cart.items || cart.items.length === 0) {
|
|
1368
1443
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.CART_EMPTY);
|
|
1369
1444
|
}
|
|
1445
|
+
/*
|
|
1446
|
+
* ── The two payment axes, resolved ONCE, AFTER the cart is loaded ────────
|
|
1447
|
+
*
|
|
1448
|
+
* Everything below reads `paymentMethod` (the tender) and `emiOptIn` (the
|
|
1449
|
+
* arrangement) from here, never from `input`. A second place deciding "is
|
|
1450
|
+
* this an EMI order" is the drift this normaliser exists to prevent, and
|
|
1451
|
+
* the legacy `paymentMethod: "emi"` mapping only holds if exactly one
|
|
1452
|
+
* expression performs it.
|
|
1453
|
+
*
|
|
1454
|
+
* 🛑 It has to sit below the cart read because the plan LIVES on the cart,
|
|
1455
|
+
* not in the request — see `CartDocument.emiPlan`. The cart is loaded here
|
|
1456
|
+
* anyway, so this costs nothing.
|
|
1457
|
+
*/
|
|
1458
|
+
const emiChoice = resolveEmiChoice({
|
|
1459
|
+
paymentMethod: input.paymentMethod,
|
|
1460
|
+
cartEmiPlan: cart.emiPlan ?? null,
|
|
1461
|
+
legacyTenureMonths: input.emiTenureMonths,
|
|
1462
|
+
});
|
|
1463
|
+
if (!emiChoice.ok)
|
|
1464
|
+
throw new ValidationError(emiChoice.reason);
|
|
1465
|
+
const { emiOptIn, emiTenureMonths } = emiChoice.choice;
|
|
1466
|
+
if (emiOptIn && (!emiTenureMonths || !emiSettings.tenureOptions.includes(emiTenureMonths))) {
|
|
1467
|
+
throw new ValidationError(`emiTenureMonths must be one of: ${emiSettings.tenureOptions.join(", ")}`);
|
|
1468
|
+
}
|
|
1370
1469
|
const excludedSet = new Set(excludedProductIds);
|
|
1371
1470
|
const selectedSet = cart.selectedItemIds?.length
|
|
1372
1471
|
? new Set(cart.selectedItemIds)
|
|
@@ -1389,16 +1488,13 @@ export async function createCheckoutOrderAction(input) {
|
|
|
1389
1488
|
// otherwise a buyer could dodge it by splitting a big cart across
|
|
1390
1489
|
// sellers. Skipped for COD (physical hand-off at the door is its own
|
|
1391
1490
|
// verification) and admin-bypass checkouts.
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1491
|
+
await assertCheckoutValueOtp({
|
|
1492
|
+
uid,
|
|
1493
|
+
amount: cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
1494
|
+
threshold: siteSettings?.payment?.otpCheckoutThreshold,
|
|
1495
|
+
paymentMethod,
|
|
1496
|
+
adminBypass,
|
|
1497
|
+
});
|
|
1402
1498
|
/*
|
|
1403
1499
|
* COD value cap — the SERVER half of the gate.
|
|
1404
1500
|
*
|
|
@@ -1434,6 +1530,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
1434
1530
|
}
|
|
1435
1531
|
const isDigitalCart = cartIsDigitalOnly(cartItems);
|
|
1436
1532
|
let shippingAddress;
|
|
1533
|
+
let deliverySnapshot;
|
|
1437
1534
|
let resolvedAddress = null;
|
|
1438
1535
|
if (!isDigitalCart) {
|
|
1439
1536
|
if (!addressId) {
|
|
@@ -1454,6 +1551,13 @@ export async function createCheckoutOrderAction(input) {
|
|
|
1454
1551
|
throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
|
|
1455
1552
|
}
|
|
1456
1553
|
shippingAddress = formatShippingAddress(resolvedAddress);
|
|
1554
|
+
/*
|
|
1555
|
+
* 🛑 REVEALED first. `findById` returns this PII encrypted, and
|
|
1556
|
+
* `snapshotDeliveryAddress` demands `AddressPlain` precisely so that
|
|
1557
|
+
* freezing `enc:v1:…` onto an invoice is a compile error rather than
|
|
1558
|
+
* something discovered on a printed label.
|
|
1559
|
+
*/
|
|
1560
|
+
deliverySnapshot = snapshotDeliveryAddress(unitOfWork.addresses.revealAddress(resolvedAddress));
|
|
1457
1561
|
}
|
|
1458
1562
|
const db = getAdminDb();
|
|
1459
1563
|
// SB6-C — pre-tx fetch products so we can run the maxPerUser cap check
|
|
@@ -1493,6 +1597,19 @@ export async function createCheckoutOrderAction(input) {
|
|
|
1493
1597
|
* is the only safe place for this, and the method is decided here.
|
|
1494
1598
|
*/
|
|
1495
1599
|
assertPaymentMethodAllowed(cartItems, preTxProductById, paymentMethod);
|
|
1600
|
+
/*
|
|
1601
|
+
* 🛑 The second axis needs its own assertion, and omitting it would have
|
|
1602
|
+
* been a silent regression of a rule that already exists.
|
|
1603
|
+
*
|
|
1604
|
+
* `prizeDrawRule.blockedPaymentMethods` lists `"emi"` because winner
|
|
1605
|
+
* assignment waits on confirmed payment. Once EMI stopped being a tender,
|
|
1606
|
+
* that entry stopped matching anything — the gate above would see
|
|
1607
|
+
* `upi_manual` (explicitly ALLOWED for prize draws) and wave through an
|
|
1608
|
+
* instalment plan on a listing type that forbids one. This reads the SAME
|
|
1609
|
+
* declaration rather than a second list, so a rule written once still
|
|
1610
|
+
* governs both axes.
|
|
1611
|
+
*/
|
|
1612
|
+
assertEmiOptInAllowed(cartItems, preTxProductById, emiOptIn);
|
|
1496
1613
|
const preTxPairs = cartItems
|
|
1497
1614
|
.map((item) => {
|
|
1498
1615
|
const [firstMember] = getCartItemMemberIds(item);
|
|
@@ -1661,6 +1778,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
1661
1778
|
const pointsBudget = { remaining: useAllPoints ? await safePointsBalance(uid) : 0 };
|
|
1662
1779
|
const groupCtx = {
|
|
1663
1780
|
paymentMethod,
|
|
1781
|
+
emiOptIn,
|
|
1664
1782
|
emiTenureMonths,
|
|
1665
1783
|
emiSettings,
|
|
1666
1784
|
commissions,
|
|
@@ -1671,6 +1789,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
1671
1789
|
userName,
|
|
1672
1790
|
userEmail,
|
|
1673
1791
|
shippingAddress,
|
|
1792
|
+
deliverySnapshot,
|
|
1674
1793
|
notes,
|
|
1675
1794
|
adminBypass,
|
|
1676
1795
|
adminBypassBy,
|
|
@@ -1770,6 +1889,7 @@ const EMPTY_PRICING_PREVIEW = {
|
|
|
1770
1889
|
shipmentProtectionFee: 0,
|
|
1771
1890
|
saleProtectionFee: 0,
|
|
1772
1891
|
emiSurcharge: 0,
|
|
1892
|
+
emiDeferred: 0,
|
|
1773
1893
|
gstAmount: 0,
|
|
1774
1894
|
couponDiscount: 0,
|
|
1775
1895
|
platformFee: 0,
|
|
@@ -1791,10 +1911,29 @@ const EMPTY_PRICING_PREVIEW = {
|
|
|
1791
1911
|
* stock, creates an order, or writes coupon usage.
|
|
1792
1912
|
*/
|
|
1793
1913
|
export async function previewCheckoutPricing(input) {
|
|
1794
|
-
const { userId: uid, addressId,
|
|
1914
|
+
const { userId: uid, addressId, excludedProductIds = [], lane, } = input;
|
|
1915
|
+
/*
|
|
1916
|
+
* 🛑 Same normaliser as `createCheckoutOrderAction`, and on an INCOMPATIBLE
|
|
1917
|
+
* combination this prices the tender WITHOUT instalments rather than
|
|
1918
|
+
* throwing.
|
|
1919
|
+
*
|
|
1920
|
+
* The preview exists so a buyer can see consequences while they fiddle with
|
|
1921
|
+
* controls; a 500 in the middle of that blanks the Order Summary and reads
|
|
1922
|
+
* as a broken page. The order action still refuses, which is where refusing
|
|
1923
|
+
* belongs — the same split this function already makes for EMI eligibility
|
|
1924
|
+
* two hundred lines below.
|
|
1925
|
+
*/
|
|
1926
|
+
const paymentMethod = emiTenderFor(input.paymentMethod);
|
|
1795
1927
|
const siteSettings = await siteSettingsRepository.getSingleton();
|
|
1796
1928
|
const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
|
|
1797
1929
|
const cart = await unitOfWork.carts.getOrCreate(uid);
|
|
1930
|
+
const emiChoice = resolveEmiChoice({
|
|
1931
|
+
paymentMethod: input.paymentMethod,
|
|
1932
|
+
cartEmiPlan: cart.emiPlan ?? null,
|
|
1933
|
+
legacyTenureMonths: input.emiTenureMonths,
|
|
1934
|
+
});
|
|
1935
|
+
const emiOptIn = emiChoice.ok && emiChoice.choice.emiOptIn;
|
|
1936
|
+
const emiTenureMonths = emiChoice.ok ? emiChoice.choice.emiTenureMonths : undefined;
|
|
1798
1937
|
const excludedSet = new Set(excludedProductIds);
|
|
1799
1938
|
const selectedSet = cart.selectedItemIds?.length ? new Set(cart.selectedItemIds) : null;
|
|
1800
1939
|
// An explicit lane wins: the cart page prices whichever tab is open, which may
|
|
@@ -1882,13 +2021,14 @@ export async function previewCheckoutPricing(input) {
|
|
|
1882
2021
|
* ineligible EMI order, which is where refusing belongs. An ineligible
|
|
1883
2022
|
* cart simply previews no surcharge, because the tenure never applies.
|
|
1884
2023
|
*/
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2024
|
+
const groupEmiPreviewQuote = groupEmiQuote({ emiOptIn, emiTenureMonths, groupSubtotal: groupTotal, sellerEmiEnabled: storeEmiEnabled }, siteSettings?.emi);
|
|
2025
|
+
const groupEmiSurcharge = groupEmiPreviewQuote?.surchargeAmount ?? 0;
|
|
2026
|
+
/*
|
|
2027
|
+
* What the buyer pays TODAY on an instalment order — the token plus every
|
|
2028
|
+
* additive charge. Surfaced so the Order Summary can show both figures
|
|
2029
|
+
* rather than one number the buyer has to interpret.
|
|
2030
|
+
*/
|
|
2031
|
+
const groupEmiDeferred = groupEmiPreviewQuote?.remainingBalance ?? 0;
|
|
1892
2032
|
const groupGiftWrapFee = computeGiftWrapFee(addons.giftWrapAddon ?? false, commissions);
|
|
1893
2033
|
/*
|
|
1894
2034
|
* C5e — the same three tiers `createOrderForGroup` enforces, so the figure
|
|
@@ -1988,6 +2128,7 @@ export async function previewCheckoutPricing(input) {
|
|
|
1988
2128
|
shippingFee: groupShippingFee,
|
|
1989
2129
|
codHandlingFee: groupCodHandlingFee,
|
|
1990
2130
|
emiSurcharge: groupEmiSurcharge,
|
|
2131
|
+
emiDeferred: groupEmiDeferred,
|
|
1991
2132
|
giftWrapFee: groupGiftWrapFee,
|
|
1992
2133
|
shipmentProtectionFee: groupShipmentProtectionFee,
|
|
1993
2134
|
saleProtectionFee: groupSaleProtectionFee,
|
|
@@ -2027,6 +2168,7 @@ export async function previewCheckoutPricing(input) {
|
|
|
2027
2168
|
const shippingFee = sum((s) => s.shippingFee);
|
|
2028
2169
|
const codHandlingFee = sum((s) => s.codHandlingFee);
|
|
2029
2170
|
const emiSurcharge = sum((s) => s.emiSurcharge);
|
|
2171
|
+
const emiDeferred = sum((s) => s.emiDeferred);
|
|
2030
2172
|
const giftWrapFeeTotal = sum((s) => s.giftWrapFee);
|
|
2031
2173
|
const shipmentProtectionFeeTotal = sum((s) => s.shipmentProtectionFee);
|
|
2032
2174
|
const saleProtectionFeeTotal = sum((s) => s.saleProtectionFee);
|
|
@@ -2128,6 +2270,7 @@ export async function previewCheckoutPricing(input) {
|
|
|
2128
2270
|
shippingFee,
|
|
2129
2271
|
codHandlingFee,
|
|
2130
2272
|
emiSurcharge,
|
|
2273
|
+
emiDeferred,
|
|
2131
2274
|
giftWrapFee: giftWrapFeeTotal,
|
|
2132
2275
|
shipmentProtectionFee: shipmentProtectionFeeTotal,
|
|
2133
2276
|
saleProtectionFee: saleProtectionFeeTotal,
|
|
@@ -2140,7 +2283,12 @@ export async function previewCheckoutPricing(input) {
|
|
|
2140
2283
|
pointsApplied,
|
|
2141
2284
|
// Equal to `total` when no points apply, so every existing reader of
|
|
2142
2285
|
// either figure keeps getting the right number.
|
|
2143
|
-
|
|
2286
|
+
/*
|
|
2287
|
+
* 🛑 Both tenders, through the SAME helper the two order writers use —
|
|
2288
|
+
* so the figure the buyer is shown and the figure they are charged are
|
|
2289
|
+
* one expression, not two that agree today.
|
|
2290
|
+
*/
|
|
2291
|
+
amountDue: amountDueNow({ orderTotal: total, pointsApplied, emiRemainingBalance: emiDeferred }),
|
|
2144
2292
|
pointsBalance: previewBalance,
|
|
2145
2293
|
stores,
|
|
2146
2294
|
};
|
|
@@ -2292,7 +2440,16 @@ async function refundDroppedItemsForPhonePeCheckout(input) {
|
|
|
2292
2440
|
* from the inline version this replaced.
|
|
2293
2441
|
*/
|
|
2294
2442
|
async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
2295
|
-
const { appliedCoupons, cartSubtotal, storeAddons, platformFeeByStore, siteSettings, uid, userName, userEmail, merchantOrderId, phonepeOrderId, transactionId, shippingAddress, notes, outOfStockPolicy, unavailablePaid, orderIds, emailsToSend, couponUsageAccumulator, productById: phonepeProductById, buyerState, useAllPoints, pointsBudget,
|
|
2443
|
+
const { appliedCoupons, cartSubtotal, storeAddons, platformFeeByStore, siteSettings, uid, userName, userEmail, merchantOrderId, phonepeOrderId, transactionId, shippingAddress, notes, outOfStockPolicy, unavailablePaid, orderIds, emailsToSend, couponUsageAccumulator, productById: phonepeProductById, buyerState, useAllPoints, pointsBudget,
|
|
2444
|
+
/*
|
|
2445
|
+
* 🛑 All three were DECLARED on the params above and never destructured,
|
|
2446
|
+
* so the body referenced them as free names: `emiOptIn`/`emiTenureMonths`
|
|
2447
|
+
* in the schedule call and `deliverySnapshot` in `placeOfSupply`. The
|
|
2448
|
+
* interface promised them and the function could not see them — which is
|
|
2449
|
+
* why appkit's own `tsc` is the gate for this file and not a reading of
|
|
2450
|
+
* the diff.
|
|
2451
|
+
*/
|
|
2452
|
+
emiOptIn, emiTenureMonths, deliverySnapshot, } = ctx;
|
|
2296
2453
|
/*
|
|
2297
2454
|
* Which online gateway actually took the money, read off the registered
|
|
2298
2455
|
* provider rather than passed in — exactly one is registered per process
|
|
@@ -2308,7 +2465,21 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
|
2308
2465
|
// seller group here, duplicated from resolveShippingCost.
|
|
2309
2466
|
// D44: `groupTotal` is computed just above — pass it so a free-above
|
|
2310
2467
|
// threshold can fire and an unpriceable store is recorded.
|
|
2311
|
-
const { shippingFee, storeOwnerId, storeState, storeTaxIdentity, storeProtections, acceptsPoints, sharedPurchaseRates } = await resolveShippingCost(firstItem.storeId, { goodsValue: groupTotal });
|
|
2468
|
+
const { shippingFee, storeOwnerId, storeState, storeTaxIdentity, storeProtections, acceptsPoints, sharedPurchaseRates, storeEmiEnabled } = await resolveShippingCost(firstItem.storeId, { goodsValue: groupTotal });
|
|
2469
|
+
/*
|
|
2470
|
+
* The instalment schedule for THIS seller group, through the same helper the
|
|
2471
|
+
* COD/manual writer uses — not a copy of its block. Two hand-written
|
|
2472
|
+
* copies is the create-vs-create axis of Root Cause #39, and these two
|
|
2473
|
+
* functions have already drifted once over add-on fees (#65).
|
|
2474
|
+
*
|
|
2475
|
+
* 🛑 Gate FAILURES are silent here, unlike the manual path, and that is
|
|
2476
|
+
* deliberate rather than sloppy: the money is already captured by the time
|
|
2477
|
+
* this runs, so throwing would produce a charged-with-no-order (the D64
|
|
2478
|
+
* class). `/api/payment/create-order` applies the same gate BEFORE capture,
|
|
2479
|
+
* which is where a refusal belongs; if it declined EMI, it captured the full
|
|
2480
|
+
* total and no schedule is wanted here either.
|
|
2481
|
+
*/
|
|
2482
|
+
const phonepeEmiSchedule = groupEmiSchedule({ emiOptIn, emiTenureMonths, groupSubtotal: groupTotal, sellerEmiEnabled: storeEmiEnabled }, siteSettings?.emi);
|
|
2312
2483
|
/*
|
|
2313
2484
|
* Same supplier-of-record rule as `createOrderForGroup`, through the same
|
|
2314
2485
|
* function. This path (PhonePe verify / webhook) is the SECOND place an
|
|
@@ -2446,7 +2617,7 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
|
2446
2617
|
});
|
|
2447
2618
|
// D42: rounded once on the sum — see the COD/UPI path's 🛑 for why a raw
|
|
2448
2619
|
// float sum of seven 2dp terms reached Firestore as 1011.8000000000001.
|
|
2449
|
-
const orderTotal = roundRupees(Math.max(0, groupTotal - couponDiscount) + shippingFee + giftWrapFee + shipmentProtectionFee + saleProtectionFee + platformFee + platformFeeGst + phonepeSharedQuote.total);
|
|
2620
|
+
const orderTotal = roundRupees(Math.max(0, groupTotal - couponDiscount) + shippingFee + giftWrapFee + shipmentProtectionFee + saleProtectionFee + platformFee + platformFeeGst + (phonepeEmiSchedule?.surchargeAmount ?? 0) + phonepeSharedQuote.total);
|
|
2450
2621
|
/*
|
|
2451
2622
|
* ── POINTS TENDER (D46/D47) — the same STAGE as the COD/manual path ───────
|
|
2452
2623
|
*
|
|
@@ -2463,7 +2634,7 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
|
2463
2634
|
*/
|
|
2464
2635
|
const pointsOptedIn = useAllPoints === true;
|
|
2465
2636
|
const pointsAccepted = pointsOptedIn && acceptsPoints;
|
|
2466
|
-
const { pointsApplied
|
|
2637
|
+
const { pointsApplied } = resolvePointsTender({
|
|
2467
2638
|
orderTotal,
|
|
2468
2639
|
balance: pointsAccepted ? pointsBudget.remaining : 0,
|
|
2469
2640
|
accepted: pointsAccepted,
|
|
@@ -2471,17 +2642,37 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
|
2471
2642
|
});
|
|
2472
2643
|
if (pointsApplied > 0)
|
|
2473
2644
|
pointsBudget.remaining -= pointsApplied;
|
|
2645
|
+
/*
|
|
2646
|
+
* 🛑 `resolvePointsTender`'s own `amountDue` is DISCARDED here — it knows
|
|
2647
|
+
* only about points, and on an instalment order the deferred balance reduces
|
|
2648
|
+
* the collection too. `amountDueNow` composes both, and is the same
|
|
2649
|
+
* expression the manual writer and the pricing preview use, so the figure
|
|
2650
|
+
* recorded in `paymentRecord.amount` below cannot drift from the figure
|
|
2651
|
+
* `/api/payment/create-order` actually captured.
|
|
2652
|
+
*/
|
|
2653
|
+
const amountDue = amountDueNow({
|
|
2654
|
+
orderTotal,
|
|
2655
|
+
pointsApplied,
|
|
2656
|
+
emiRemainingBalance: phonepeEmiSchedule?.remainingBalance ?? 0,
|
|
2657
|
+
});
|
|
2474
2658
|
// S-SBUNI-RULES 2026-05-13 — order-item decoration via rule registry.
|
|
2475
2659
|
// Same expansion as the COD/UPI path. This was a THIRD hand-rolled copy of
|
|
2476
2660
|
// the price rule (`isBundle ? item.price : product.price`) that — like the
|
|
2477
2661
|
// two in the consumer's checkout route — silently omitted `lockedPrice`, so
|
|
2478
2662
|
// an accepted offer paid through PhonePe was RECORDED at the seller's list
|
|
2479
2663
|
// price. `lineTotalFor` inside the expansion is the single definition.
|
|
2480
|
-
const
|
|
2664
|
+
const basePhonepeItems = group.flatMap(({ item, product }) => {
|
|
2481
2665
|
const lt = (product?.listingType ?? "standard");
|
|
2482
2666
|
const itemRule = getListingRule(lt);
|
|
2483
2667
|
return expandCartLineToOrderRows(item, product, phonepeProductById).map((baseLine) => itemRule.decorateOrderItem(baseLine, product));
|
|
2484
2668
|
});
|
|
2669
|
+
/*
|
|
2670
|
+
* Part 14 — the parts freeze, on THIS path too. The comment four lines
|
|
2671
|
+
* above records the cost of the last thing added to one path and not the
|
|
2672
|
+
* other: an accepted offer paid through PhonePe was recorded at the
|
|
2673
|
+
* seller's list price.
|
|
2674
|
+
*/
|
|
2675
|
+
const orderItems = await freezePartsOnItems(basePhonepeItems);
|
|
2485
2676
|
const totalQuantity = group.reduce((sum, { item }) => sum + item.quantity, 0);
|
|
2486
2677
|
const imageUrls = [
|
|
2487
2678
|
...new Set(group
|
|
@@ -2510,7 +2701,13 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
|
2510
2701
|
// D46/D47 — see the identical pair in `createOrderForGroup`. `totalPrice`
|
|
2511
2702
|
// stays the full value of the supply; points reduce what is COLLECTED.
|
|
2512
2703
|
pointsApplied: pointsApplied > 0 ? pointsApplied : undefined,
|
|
2513
|
-
|
|
2704
|
+
/*
|
|
2705
|
+
* Written whenever it differs from the total — points OR instalments. It
|
|
2706
|
+
* was `pointsApplied > 0 ? … : undefined`, so an EMI order carried no
|
|
2707
|
+
* `amountDue` and every reader fell back to the full total.
|
|
2708
|
+
*/
|
|
2709
|
+
amountDue: amountDue !== orderTotal ? amountDue : undefined,
|
|
2710
|
+
...emiOrderFields(phonepeEmiSchedule, emiTenureMonths),
|
|
2514
2711
|
currency: firstItem.currency ?? getDefaultCurrency(),
|
|
2515
2712
|
storeId: firstItem.storeId || undefined,
|
|
2516
2713
|
storeName: firstItem.storeName || undefined,
|
|
@@ -2599,7 +2796,12 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
|
2599
2796
|
// Phase 7 — the place of supply, matching `createOrderForGroup` field for
|
|
2600
2797
|
// field. This path recorded NONE of the tax breakdown until C0c defect 1,
|
|
2601
2798
|
// which is precisely why every addition here has to be made twice.
|
|
2602
|
-
|
|
2799
|
+
/*
|
|
2800
|
+
* Part 14 — the snapshot's state is authoritative, because it is the
|
|
2801
|
+
* field the invoice and GSTR-1 actually read. `buyerState` stays as
|
|
2802
|
+
* the fallback for a digital cart, which has no delivery address.
|
|
2803
|
+
*/
|
|
2804
|
+
placeOfSupply: deliverySnapshot?.deliveryState || buyerState || undefined,
|
|
2603
2805
|
// Our registration at the moment of supply — see the schema. Mirrors the
|
|
2604
2806
|
// COD/UPI path so both produce an invoiceable order.
|
|
2605
2807
|
supplierGstin: supplier.gstin,
|
|
@@ -2871,6 +3073,7 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
|
|
|
2871
3073
|
await assertLockedLinesStillValid(settleItems, uid);
|
|
2872
3074
|
const isDigitalCartPp = cartIsDigitalOnly(settleItems);
|
|
2873
3075
|
let shippingAddress;
|
|
3076
|
+
let deliverySnapshot;
|
|
2874
3077
|
let resolvedAddressPp = null;
|
|
2875
3078
|
if (!isDigitalCartPp) {
|
|
2876
3079
|
if (!addressId) {
|
|
@@ -2885,6 +3088,13 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
|
|
|
2885
3088
|
throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
|
|
2886
3089
|
}
|
|
2887
3090
|
shippingAddress = formatShippingAddress(resolvedAddressPp);
|
|
3091
|
+
/*
|
|
3092
|
+
* 🛑 REVEALED first. `findById` returns this PII encrypted, and
|
|
3093
|
+
* `snapshotDeliveryAddress` demands `AddressPlain` precisely so that
|
|
3094
|
+
* freezing `enc:v1:…` onto an invoice is a compile error rather than
|
|
3095
|
+
* something discovered on a printed label.
|
|
3096
|
+
*/
|
|
3097
|
+
deliverySnapshot = snapshotDeliveryAddress(unitOfWork.addresses.revealAddress(resolvedAddressPp));
|
|
2888
3098
|
}
|
|
2889
3099
|
// SB-UNI-5 2026-05-13 — bundle-aware product fetch + validation. Each
|
|
2890
3100
|
// cart line gets paired with a "representative" product (first member id
|
|
@@ -3034,6 +3244,36 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
|
|
|
3034
3244
|
goodsValue: g.items.reduce((sum, { item, product }) => sum + lineTotalFor(item, product), 0),
|
|
3035
3245
|
})));
|
|
3036
3246
|
const expectedShippingFee = expectedShippingFees.reduce((sum, r) => sum + r.shippingFee, 0);
|
|
3247
|
+
/*
|
|
3248
|
+
* ── The instalment terms, which this guard must expect BOTH of ──────────
|
|
3249
|
+
*
|
|
3250
|
+
* 🛑 Two terms, pulling in opposite directions, and omitting either one
|
|
3251
|
+
* breaks the guard in the direction that costs the buyer an order:
|
|
3252
|
+
*
|
|
3253
|
+
* + surcharge — the cost of credit, a real addition to the total
|
|
3254
|
+
* − deferred — the financed principal plus that surcharge, which is NOT
|
|
3255
|
+
* collected today
|
|
3256
|
+
*
|
|
3257
|
+
* With neither term the guard would expect the un-surcharged FULL total
|
|
3258
|
+
* while the gateway captured only the token, trip `paid < expected − 1`,
|
|
3259
|
+
* and throw **after the money was taken** — a charged-with-no-order, the
|
|
3260
|
+
* D64 class, on every single EMI order. That is strictly worse than the
|
|
3261
|
+
* overcharge this guard exists to catch, which is the same trap D44's note
|
|
3262
|
+
* above records one defect over.
|
|
3263
|
+
*
|
|
3264
|
+
* Built from the same per-group goods values and the same `groupEmiQuote`
|
|
3265
|
+
* the capture route and the order writer call, so the only way the three
|
|
3266
|
+
* can disagree is a change made to one and not the others.
|
|
3267
|
+
*/
|
|
3268
|
+
const guardEmiPlanTenure = cart.emiPlan?.tenureMonths;
|
|
3269
|
+
const expectedEmiQuotes = expectedShippingGroups.map((g, i) => groupEmiQuote({
|
|
3270
|
+
emiOptIn: guardEmiPlanTenure !== undefined,
|
|
3271
|
+
emiTenureMonths: guardEmiPlanTenure,
|
|
3272
|
+
groupSubtotal: g.items.reduce((sum, { item, product }) => sum + lineTotalFor(item, product), 0),
|
|
3273
|
+
sellerEmiEnabled: expectedShippingFees[i]?.storeEmiEnabled === true,
|
|
3274
|
+
}, siteSettings?.emi));
|
|
3275
|
+
const expectedEmiSurcharge = expectedEmiQuotes.reduce((sum, q) => sum + (q?.surchargeAmount ?? 0), 0);
|
|
3276
|
+
const expectedEmiDeferred = expectedEmiQuotes.reduce((sum, q) => sum + (q?.remainingBalance ?? 0), 0);
|
|
3037
3277
|
// Add-ons are per store, so the expected total sums each group's own
|
|
3038
3278
|
// selection — a single cart-wide figure would over- or under-count as soon
|
|
3039
3279
|
// as the buyer picked add-ons for only some sellers.
|
|
@@ -3105,22 +3345,28 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
|
|
|
3105
3345
|
lineTotal: lineTotalFor(item, product),
|
|
3106
3346
|
})),
|
|
3107
3347
|
}))).perGroup;
|
|
3108
|
-
|
|
3109
|
-
|
|
3348
|
+
/*
|
|
3349
|
+
* The per-group add-on fee, lifted out of the `map` below so that
|
|
3350
|
+
* callback has no block body. Its statements sat at brace-depth 6
|
|
3351
|
+
* inside `if → if → callback`, which DEEP_NESTING blocks — and the
|
|
3352
|
+
* add-on sum is the one part of this base the sibling money path
|
|
3353
|
+
* (`POST /api/payment/create-order`) computes earlier rather than
|
|
3354
|
+
* inline, so it is also the natural seam.
|
|
3355
|
+
*/
|
|
3356
|
+
const guardAddonFee = (storeId, goods) => {
|
|
3110
3357
|
const addons = cart.storeAddons?.[storeId] ?? {};
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
});
|
|
3358
|
+
return (computeGiftWrapFee(addons.giftWrapAddon ?? false, commissionRates) +
|
|
3359
|
+
computeShipmentProtectionFee(goods, addons.shipmentProtectionAddon ?? false, commissionRates));
|
|
3360
|
+
};
|
|
3361
|
+
const guardBases = expectedShippingGroups.map((g, i) => pointsBaseForGroup({
|
|
3362
|
+
goods: guardSubtotals[i][1],
|
|
3363
|
+
couponDiscount: guardCouponByGroup[i]?.couponDiscount ?? 0,
|
|
3364
|
+
shippingFee: expectedShippingFees[i]?.shippingFee ?? 0,
|
|
3365
|
+
addonFee: guardAddonFee(g.items[0].item.storeId, guardSubtotals[i][1]),
|
|
3366
|
+
platformFee: guardFeeByGroup.get(g.items[0].item.storeId)?.platformFee ?? 0,
|
|
3367
|
+
gstOnFee: guardFeeByGroup.get(g.items[0].item.storeId)?.gstOnFee ?? 0,
|
|
3368
|
+
acceptsPoints: expectedShippingFees[i]?.acceptsPoints === true,
|
|
3369
|
+
}));
|
|
3124
3370
|
expectedPointsApplied = allocatePointsAcrossGroups(guardBases, guardBalance, true).pointsApplied;
|
|
3125
3371
|
}
|
|
3126
3372
|
}
|
|
@@ -3128,7 +3374,9 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
|
|
|
3128
3374
|
expectedPlatformFee +
|
|
3129
3375
|
expectedGstOnFee +
|
|
3130
3376
|
expectedAddonFees +
|
|
3131
|
-
expectedShippingFee
|
|
3377
|
+
expectedShippingFee +
|
|
3378
|
+
expectedEmiSurcharge -
|
|
3379
|
+
expectedEmiDeferred -
|
|
3132
3380
|
expectedPointsApplied));
|
|
3133
3381
|
// Reuses the order already fetched above to confirm COMPLETED — PhonePe's
|
|
3134
3382
|
// own amount is in its wire-format smallest unit. // audit-money-units-ok: describes the PhonePe boundary conversion this line performs, not our storage convention
|
|
@@ -3244,6 +3492,13 @@ export async function verifyAndPlaceGatewayOrderAction(input) {
|
|
|
3244
3492
|
productById: productByIdPaid,
|
|
3245
3493
|
useAllPoints,
|
|
3246
3494
|
pointsBudget,
|
|
3495
|
+
/*
|
|
3496
|
+
* Resolved from the CART, exactly as the capture route did — so the
|
|
3497
|
+
* schedule written onto the order is built from the same tenure the
|
|
3498
|
+
* gateway was asked to capture against.
|
|
3499
|
+
*/
|
|
3500
|
+
emiOptIn: cart.emiPlan?.tenureMonths !== undefined,
|
|
3501
|
+
emiTenureMonths: cart.emiPlan?.tenureMonths,
|
|
3247
3502
|
});
|
|
3248
3503
|
}
|
|
3249
3504
|
await flushCouponUsageAccumulator(couponUsageAccumulator, uid);
|