@mohasinac/appkit 4.62.1 → 4.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +120 -0
- package/dist/features/ads/schemas/booking-write.js +75 -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.d.ts +27 -1
- package/dist/server.js +144 -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
|
@@ -79,6 +79,129 @@ export declare function normaliseDimensions(d: ParcelSource["dimensionsCm"]): Pa
|
|
|
79
79
|
* whichever tier won on weight would discard information that exists.
|
|
80
80
|
*/
|
|
81
81
|
export declare function resolveParcel(shipment: ParcelSource | null | undefined, product: ParcelSource | null | undefined, category: ParcelSource | null | undefined): Parcel | null;
|
|
82
|
+
/**
|
|
83
|
+
* Divisors, by service class.
|
|
84
|
+
*
|
|
85
|
+
* A carrier sells space, not mass, so it bills the greater of actual weight and
|
|
86
|
+
* `l × w × h ÷ divisor`. The divisor is the whole ballgame on this catalogue:
|
|
87
|
+
* collectible packaging is mostly air, so for a stadium box the volumetric
|
|
88
|
+
* figure is ~5× the actual and no per-kilo negotiation touches it.
|
|
89
|
+
*
|
|
90
|
+
* 🛑 `surfaceTight` is not a typo. Some Indian surface B2C plans quote ÷4000,
|
|
91
|
+
* which is a silent 25% increase over ÷5000 on every bulky parcel and is
|
|
92
|
+
* never the number on the front of the rate card. Read the contract, then set
|
|
93
|
+
* this per carrier — do not assume 5000 because it is the common default.
|
|
94
|
+
*
|
|
95
|
+
* Air uses a LARGER divisor, which means a very light bulky box can genuinely
|
|
96
|
+
* cost less by air than by surface. That is counter-intuitive and real.
|
|
97
|
+
*/
|
|
98
|
+
export declare const VOLUMETRIC_DIVISORS: {
|
|
99
|
+
readonly surfaceTight: 4000;
|
|
100
|
+
readonly standard: 5000;
|
|
101
|
+
readonly air: 6000;
|
|
102
|
+
};
|
|
103
|
+
export declare const DEFAULT_VOLUMETRIC_DIVISOR: 5000;
|
|
104
|
+
/**
|
|
105
|
+
* Where a parcel sits against the two thresholds, and therefore which carrier
|
|
106
|
+
* class is cheaper. See `carrierClassFor` for why these numbers.
|
|
107
|
+
*/
|
|
108
|
+
export declare const VOLUMETRIC_RATIO_THRESHOLDS: {
|
|
109
|
+
/** Below this the two weights are close enough that the rate card decides. */
|
|
110
|
+
readonly private: 1.5;
|
|
111
|
+
/** At or above this the box is mostly air and a postal service wins. */
|
|
112
|
+
readonly postal: 2;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* The first billing slab, in grams — the region where R means nothing.
|
|
116
|
+
*
|
|
117
|
+
* 🛑 This guard exists because the ratio rule is WRONG below it, and the error
|
|
118
|
+
* was caught by checking the rule against real tariffs instead of trusting it.
|
|
119
|
+
*
|
|
120
|
+
* A single boxed bey is 250 g actual / 448 g volumetric — R 1.79, which the
|
|
121
|
+
* thresholds above call `"either"`. The real bill at >2000 km is ₹106 by Speed
|
|
122
|
+
* Post against ₹50 by private courier: private wins by 2×, not "either".
|
|
123
|
+
*
|
|
124
|
+
* The reason is that both figures land inside each carrier's MINIMUM charge, so
|
|
125
|
+
* the comparison is floor against floor and the ratio describes nothing. India
|
|
126
|
+
* Post's 201–500 g floor is ₹90; a private first slab is around ₹42. Whenever
|
|
127
|
+
* both weights sit in that first slab, the lower floor wins and that is private.
|
|
128
|
+
*/
|
|
129
|
+
export declare const FIRST_SLAB_G = 500;
|
|
130
|
+
export interface ChargeableWeight {
|
|
131
|
+
actualG: number;
|
|
132
|
+
/**
|
|
133
|
+
* `null` when the dimension triple is unknown — NEVER 0.
|
|
134
|
+
*
|
|
135
|
+
* A carrier with no dimensions bills actual weight, so `chargeableG` is still
|
|
136
|
+
* correct in that case. But a `0` here would read as "this box has no volume",
|
|
137
|
+
* and `ratio` would compute to 0, which routes a stadium to the private
|
|
138
|
+
* carrier that bills it at 7.3 kg. Unknown has to stay distinguishable from
|
|
139
|
+
* dense — same discipline as `resolveParcel` refusing to return weight 0.
|
|
140
|
+
*/
|
|
141
|
+
volumetricG: number | null;
|
|
142
|
+
/** `max(actual, volumetric)` — what you are billed for. */
|
|
143
|
+
chargeableG: number;
|
|
144
|
+
divisor: number;
|
|
145
|
+
billedOn: "actual" | "volumetric";
|
|
146
|
+
/**
|
|
147
|
+
* **The R value**: volumetric ÷ actual, 2dp. `null` without dimensions.
|
|
148
|
+
*
|
|
149
|
+
* 1 means the box is exactly at the divisor's break-even density. Above 1 you
|
|
150
|
+
* are paying for air; below 1 the volume is free and actual weight bills.
|
|
151
|
+
*/
|
|
152
|
+
ratio: number | null;
|
|
153
|
+
/** Packed density in kg/m³. `null` without dimensions. */
|
|
154
|
+
densityKgM3: number | null;
|
|
155
|
+
/**
|
|
156
|
+
* The density at which volumetric starts to bite, derived from the divisor
|
|
157
|
+
* rather than hardcoded: `1e6 / divisor`. 200 kg/m³ at ÷5000, 250 at ÷4000,
|
|
158
|
+
* ~167 at ÷6000. A parcel below this is billed on volume.
|
|
159
|
+
*/
|
|
160
|
+
breakEvenKgM3: number;
|
|
161
|
+
}
|
|
162
|
+
/** Volumetric weight in grams. `l × w × h ÷ divisor` is kilos, hence ×1000. */
|
|
163
|
+
export declare function volumetricWeightG(d: ParcelDimensionsCm, divisor?: number): number;
|
|
164
|
+
/**
|
|
165
|
+
* What this parcel is billed at, and by how much volume beats mass.
|
|
166
|
+
*
|
|
167
|
+
* Pure arithmetic — it reports, it does not decide. `carrierClassFor` turns the
|
|
168
|
+
* result into a routing opinion, deliberately as a separate step so a surface
|
|
169
|
+
* can show the numbers without adopting the recommendation.
|
|
170
|
+
*/
|
|
171
|
+
export declare function chargeableWeight(parcel: Pick<Parcel, "weightG" | "dimensionsCm">, divisor?: number): ChargeableWeight;
|
|
172
|
+
/**
|
|
173
|
+
* Which carrier class is cheaper for this parcel, on freight alone.
|
|
174
|
+
*
|
|
175
|
+
* India Post Speed Post bills ACTUAL weight and applies no volumetric
|
|
176
|
+
* calculation; every private carrier bills the greater of the two. So the
|
|
177
|
+
* freight comparison reduces to the R value:
|
|
178
|
+
*
|
|
179
|
+
* R < 1.5 the two weights are close; the rate card decides, and a private
|
|
180
|
+
* card is usually cheaper at low weight → `"private"`
|
|
181
|
+
* 1.5 ≤ R < 2 genuinely close; decide on pincode serviceability and
|
|
182
|
+
* whether the item needs insurance → `"either"`
|
|
183
|
+
* R ≥ 2 the box is mostly air and postal is materially cheaper → `"postal"`
|
|
184
|
+
*
|
|
185
|
+
* 🛑 `"unknown"` when dimensions are missing, and callers must not collapse it
|
|
186
|
+
* into a default. Defaulting to `"private"` is precisely the wrong way round:
|
|
187
|
+
* the parcels whose dimensions nobody recorded are disproportionately the big
|
|
188
|
+
* awkward ones, which are the parcels this function exists to divert.
|
|
189
|
+
*
|
|
190
|
+
* 🛑 This is a FREIGHT opinion and nothing more. Above roughly ₹8,000 of order
|
|
191
|
+
* value the decision stops being about freight — the delta is a few hundred
|
|
192
|
+
* rupees against an uninsured loss of the whole item — so an order-value test
|
|
193
|
+
* and serviceability both outrank this. It answers "which is cheaper", never
|
|
194
|
+
* "which should we book".
|
|
195
|
+
*
|
|
196
|
+
* ## Where it is weakest, stated rather than hidden
|
|
197
|
+
*
|
|
198
|
+
* At HIGH weight the per-additional-slab rates start to matter more than the
|
|
199
|
+
* ratio, and those are negotiated. A 8 kg / 14 kg parcel is R 1.75 → `"either"`,
|
|
200
|
+
* while one representative card puts it at ₹991 postal against ₹1,197 private —
|
|
201
|
+
* a real 21% gap the ratio does not see. `"either"` is the honest answer there:
|
|
202
|
+
* it means your own card decides, not that the two are equal.
|
|
203
|
+
*/
|
|
204
|
+
export declare function carrierClassFor(c: Pick<ChargeableWeight, "ratio" | "actualG" | "volumetricG">): "private" | "either" | "postal" | "unknown";
|
|
82
205
|
/**
|
|
83
206
|
* The weight of a whole order group — what a single shipment actually carries.
|
|
84
207
|
*
|
|
@@ -88,6 +88,145 @@ export function resolveParcel(shipment, product, category) {
|
|
|
88
88
|
}
|
|
89
89
|
return { weightG, source, ...(dimensionsCm ? { dimensionsCm } : {}) };
|
|
90
90
|
}
|
|
91
|
+
/* ── Volumetric weight — what the carrier actually bills ──────────────────── */
|
|
92
|
+
/**
|
|
93
|
+
* Divisors, by service class.
|
|
94
|
+
*
|
|
95
|
+
* A carrier sells space, not mass, so it bills the greater of actual weight and
|
|
96
|
+
* `l × w × h ÷ divisor`. The divisor is the whole ballgame on this catalogue:
|
|
97
|
+
* collectible packaging is mostly air, so for a stadium box the volumetric
|
|
98
|
+
* figure is ~5× the actual and no per-kilo negotiation touches it.
|
|
99
|
+
*
|
|
100
|
+
* 🛑 `surfaceTight` is not a typo. Some Indian surface B2C plans quote ÷4000,
|
|
101
|
+
* which is a silent 25% increase over ÷5000 on every bulky parcel and is
|
|
102
|
+
* never the number on the front of the rate card. Read the contract, then set
|
|
103
|
+
* this per carrier — do not assume 5000 because it is the common default.
|
|
104
|
+
*
|
|
105
|
+
* Air uses a LARGER divisor, which means a very light bulky box can genuinely
|
|
106
|
+
* cost less by air than by surface. That is counter-intuitive and real.
|
|
107
|
+
*/
|
|
108
|
+
export const VOLUMETRIC_DIVISORS = {
|
|
109
|
+
surfaceTight: 4000,
|
|
110
|
+
standard: 5000,
|
|
111
|
+
air: 6000,
|
|
112
|
+
};
|
|
113
|
+
export const DEFAULT_VOLUMETRIC_DIVISOR = VOLUMETRIC_DIVISORS.standard;
|
|
114
|
+
/**
|
|
115
|
+
* Where a parcel sits against the two thresholds, and therefore which carrier
|
|
116
|
+
* class is cheaper. See `carrierClassFor` for why these numbers.
|
|
117
|
+
*/
|
|
118
|
+
export const VOLUMETRIC_RATIO_THRESHOLDS = {
|
|
119
|
+
/** Below this the two weights are close enough that the rate card decides. */
|
|
120
|
+
private: 1.5,
|
|
121
|
+
/** At or above this the box is mostly air and a postal service wins. */
|
|
122
|
+
postal: 2,
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* The first billing slab, in grams — the region where R means nothing.
|
|
126
|
+
*
|
|
127
|
+
* 🛑 This guard exists because the ratio rule is WRONG below it, and the error
|
|
128
|
+
* was caught by checking the rule against real tariffs instead of trusting it.
|
|
129
|
+
*
|
|
130
|
+
* A single boxed bey is 250 g actual / 448 g volumetric — R 1.79, which the
|
|
131
|
+
* thresholds above call `"either"`. The real bill at >2000 km is ₹106 by Speed
|
|
132
|
+
* Post against ₹50 by private courier: private wins by 2×, not "either".
|
|
133
|
+
*
|
|
134
|
+
* The reason is that both figures land inside each carrier's MINIMUM charge, so
|
|
135
|
+
* the comparison is floor against floor and the ratio describes nothing. India
|
|
136
|
+
* Post's 201–500 g floor is ₹90; a private first slab is around ₹42. Whenever
|
|
137
|
+
* both weights sit in that first slab, the lower floor wins and that is private.
|
|
138
|
+
*/
|
|
139
|
+
export const FIRST_SLAB_G = 500;
|
|
140
|
+
/** Volumetric weight in grams. `l × w × h ÷ divisor` is kilos, hence ×1000. */
|
|
141
|
+
export function volumetricWeightG(d, divisor = DEFAULT_VOLUMETRIC_DIVISOR) {
|
|
142
|
+
if (!(divisor > 0)) {
|
|
143
|
+
throw new RangeError(`volumetricWeightG: divisor must be > 0, got ${divisor}`);
|
|
144
|
+
}
|
|
145
|
+
return Math.round(((d.l * d.w * d.h) / divisor) * 1000);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* What this parcel is billed at, and by how much volume beats mass.
|
|
149
|
+
*
|
|
150
|
+
* Pure arithmetic — it reports, it does not decide. `carrierClassFor` turns the
|
|
151
|
+
* result into a routing opinion, deliberately as a separate step so a surface
|
|
152
|
+
* can show the numbers without adopting the recommendation.
|
|
153
|
+
*/
|
|
154
|
+
export function chargeableWeight(parcel, divisor = DEFAULT_VOLUMETRIC_DIVISOR) {
|
|
155
|
+
const actualG = parcel.weightG;
|
|
156
|
+
const breakEvenKgM3 = Math.round((1000000 / divisor) * 10) / 10;
|
|
157
|
+
const d = parcel.dimensionsCm;
|
|
158
|
+
if (!d) {
|
|
159
|
+
return {
|
|
160
|
+
actualG,
|
|
161
|
+
volumetricG: null,
|
|
162
|
+
chargeableG: actualG,
|
|
163
|
+
divisor,
|
|
164
|
+
billedOn: "actual",
|
|
165
|
+
ratio: null,
|
|
166
|
+
densityKgM3: null,
|
|
167
|
+
breakEvenKgM3,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const volumetricG = volumetricWeightG(d, divisor);
|
|
171
|
+
const cm3 = d.l * d.w * d.h;
|
|
172
|
+
return {
|
|
173
|
+
actualG,
|
|
174
|
+
volumetricG,
|
|
175
|
+
chargeableG: Math.max(actualG, volumetricG),
|
|
176
|
+
divisor,
|
|
177
|
+
billedOn: volumetricG > actualG ? "volumetric" : "actual",
|
|
178
|
+
ratio: actualG > 0 ? Math.round((volumetricG / actualG) * 100) / 100 : null,
|
|
179
|
+
densityKgM3: cm3 > 0 ? Math.round(((actualG * 1000) / cm3) * 10) / 10 : null,
|
|
180
|
+
breakEvenKgM3,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Which carrier class is cheaper for this parcel, on freight alone.
|
|
185
|
+
*
|
|
186
|
+
* India Post Speed Post bills ACTUAL weight and applies no volumetric
|
|
187
|
+
* calculation; every private carrier bills the greater of the two. So the
|
|
188
|
+
* freight comparison reduces to the R value:
|
|
189
|
+
*
|
|
190
|
+
* R < 1.5 the two weights are close; the rate card decides, and a private
|
|
191
|
+
* card is usually cheaper at low weight → `"private"`
|
|
192
|
+
* 1.5 ≤ R < 2 genuinely close; decide on pincode serviceability and
|
|
193
|
+
* whether the item needs insurance → `"either"`
|
|
194
|
+
* R ≥ 2 the box is mostly air and postal is materially cheaper → `"postal"`
|
|
195
|
+
*
|
|
196
|
+
* 🛑 `"unknown"` when dimensions are missing, and callers must not collapse it
|
|
197
|
+
* into a default. Defaulting to `"private"` is precisely the wrong way round:
|
|
198
|
+
* the parcels whose dimensions nobody recorded are disproportionately the big
|
|
199
|
+
* awkward ones, which are the parcels this function exists to divert.
|
|
200
|
+
*
|
|
201
|
+
* 🛑 This is a FREIGHT opinion and nothing more. Above roughly ₹8,000 of order
|
|
202
|
+
* value the decision stops being about freight — the delta is a few hundred
|
|
203
|
+
* rupees against an uninsured loss of the whole item — so an order-value test
|
|
204
|
+
* and serviceability both outrank this. It answers "which is cheaper", never
|
|
205
|
+
* "which should we book".
|
|
206
|
+
*
|
|
207
|
+
* ## Where it is weakest, stated rather than hidden
|
|
208
|
+
*
|
|
209
|
+
* At HIGH weight the per-additional-slab rates start to matter more than the
|
|
210
|
+
* ratio, and those are negotiated. A 8 kg / 14 kg parcel is R 1.75 → `"either"`,
|
|
211
|
+
* while one representative card puts it at ₹991 postal against ₹1,197 private —
|
|
212
|
+
* a real 21% gap the ratio does not see. `"either"` is the honest answer there:
|
|
213
|
+
* it means your own card decides, not that the two are equal.
|
|
214
|
+
*/
|
|
215
|
+
export function carrierClassFor(c) {
|
|
216
|
+
if (c.ratio == null)
|
|
217
|
+
return "unknown";
|
|
218
|
+
// Both weights inside the first slab → floor vs floor; see FIRST_SLAB_G.
|
|
219
|
+
if (c.actualG <= FIRST_SLAB_G &&
|
|
220
|
+
c.volumetricG != null &&
|
|
221
|
+
c.volumetricG <= FIRST_SLAB_G) {
|
|
222
|
+
return "private";
|
|
223
|
+
}
|
|
224
|
+
if (c.ratio >= VOLUMETRIC_RATIO_THRESHOLDS.postal)
|
|
225
|
+
return "postal";
|
|
226
|
+
if (c.ratio >= VOLUMETRIC_RATIO_THRESHOLDS.private)
|
|
227
|
+
return "either";
|
|
228
|
+
return "private";
|
|
229
|
+
}
|
|
91
230
|
/**
|
|
92
231
|
* The weight of a whole order group — what a single shipment actually carries.
|
|
93
232
|
*
|
|
@@ -38,17 +38,25 @@ export interface BuyerFacingFees {
|
|
|
38
38
|
codHandlingFeeCap?: number;
|
|
39
39
|
codHandlingFeePercent?: number;
|
|
40
40
|
codDepositPercent?: number;
|
|
41
|
+
/** Commission rate applied to the cart subtotal, before the cap. */
|
|
42
|
+
platformFeePercent?: number;
|
|
43
|
+
/** Rupee ceiling on the commission — the reason a ₹39,500 order pays ₹10. */
|
|
44
|
+
platformFeeMax?: number;
|
|
45
|
+
/** GST rate levied on the commission actually charged (never on the uncapped figure). */
|
|
46
|
+
gstPercent?: number;
|
|
47
|
+
/** Rupee floor on `platformFee + gstOnFee` combined. */
|
|
48
|
+
minimumTransactionFee?: number;
|
|
41
49
|
}
|
|
42
50
|
/**
|
|
43
|
-
* Withheld on purpose (
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
51
|
+
* Withheld on purpose (11 of 25): gatewayFeePercent, sellerShippingFixed,
|
|
52
|
+
* platformShippingPercent, platformShippingFixedMin, autoPayoutWindowDays,
|
|
53
|
+
* payoutHoldDays, minPayoutAmount, auctionListingFee, preOrderListingFee,
|
|
54
|
+
* featuredSlotFee, promotedSlotFee — plus `surchargeSellerSharePercent`, which
|
|
55
|
+
* lives on the EMI block and is excluded by `BuyerEmiSettings` below.
|
|
48
56
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
57
|
+
* Every one of those prices something between us and a seller or a gateway.
|
|
58
|
+
* None of them changes what the buyer is asked to pay, so none of them has a
|
|
59
|
+
* reader in a browser.
|
|
52
60
|
*/
|
|
53
61
|
export declare function toBuyerFacingFees(commissions: SiteSettingsDocument["commissions"] | null | undefined): BuyerFacingFees | null;
|
|
54
62
|
/**
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Withheld on purpose (
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Withheld on purpose (11 of 25): gatewayFeePercent, sellerShippingFixed,
|
|
3
|
+
* platformShippingPercent, platformShippingFixedMin, autoPayoutWindowDays,
|
|
4
|
+
* payoutHoldDays, minPayoutAmount, auctionListingFee, preOrderListingFee,
|
|
5
|
+
* featuredSlotFee, promotedSlotFee — plus `surchargeSellerSharePercent`, which
|
|
6
|
+
* lives on the EMI block and is excluded by `BuyerEmiSettings` below.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Every one of those prices something between us and a seller or a gateway.
|
|
9
|
+
* None of them changes what the buyer is asked to pay, so none of them has a
|
|
10
|
+
* reader in a browser.
|
|
11
11
|
*/
|
|
12
12
|
export function toBuyerFacingFees(commissions) {
|
|
13
13
|
if (!commissions)
|
|
@@ -24,6 +24,10 @@ export function toBuyerFacingFees(commissions) {
|
|
|
24
24
|
codHandlingFeeCap: commissions.codHandlingFeeCap,
|
|
25
25
|
codHandlingFeePercent: commissions.codHandlingFeePercent,
|
|
26
26
|
codDepositPercent: commissions.codDepositPercent,
|
|
27
|
+
platformFeePercent: commissions.platformFeePercent,
|
|
28
|
+
platformFeeMax: commissions.platformFeeMax,
|
|
29
|
+
gstPercent: commissions.gstPercent,
|
|
30
|
+
minimumTransactionFee: commissions.minimumTransactionFee,
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
export function toBuyerEmiSettings(emi) {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-language derivations for every fee line a buyer is shown.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 The problem this solves is not presentation. The Order Summary listed
|
|
5
|
+
* `EMI surcharge ₹2,133.00`, `Platform fee ₹10.00` and `GST on platform fee
|
|
6
|
+
* ₹1.80` beside a `₹39,500.00` subtotal, and **none of the three could be
|
|
7
|
+
* checked by the person paying them.** ₹2,133 on ₹39,500 is 5.4% — a buyer has
|
|
8
|
+
* no way to tell whether that is a rate, a tenure, a rounding error or a bug,
|
|
9
|
+
* and the only honest response to an unexplained charge is to distrust it.
|
|
10
|
+
*
|
|
11
|
+
* ── Why the STRINGS live here and not in the component ────────────────────
|
|
12
|
+
*
|
|
13
|
+
* Each sentence is a claim about how a number was computed, and the number is
|
|
14
|
+
* computed in `calculator.ts` / `emi/schedule.ts`. Putting the sentence beside
|
|
15
|
+
* a `<Line>` would make it a second, prose implementation of the same rule —
|
|
16
|
+
* free to drift the moment a cap moves (Root Cause #75: a rule reachable by
|
|
17
|
+
* copy-paste gets copied, and the copy never learns about the branch added
|
|
18
|
+
* later). These functions read the SAME rate fields the calculators read.
|
|
19
|
+
*
|
|
20
|
+
* ── Why they take the CHARGED amount as an argument ───────────────────────
|
|
21
|
+
*
|
|
22
|
+
* They explain the figure actually on screen; they do not recompute it. If a
|
|
23
|
+
* derivation and the rendered amount ever disagree, the explanation must say
|
|
24
|
+
* so rather than quietly print a second, prettier number — so each function
|
|
25
|
+
* ends at the charged value and describes the route to it.
|
|
26
|
+
*
|
|
27
|
+
* 🛑 NOTHING HERE MAY NAME A SELLER-SIDE RATE. `surchargeSellerSharePercent`
|
|
28
|
+
* (the platform/seller split of the EMI surcharge), `gatewayFeePercent`,
|
|
29
|
+
* `payoutHoldDays` and `minPayoutAmount` are `PRIVATE_*_FIELDS`
|
|
30
|
+
* (Root Cause #70). The EMI functions below are typed against
|
|
31
|
+
* `BuyerEmiSettings`, which excludes the split structurally — so the omission
|
|
32
|
+
* is enforced by `tsc`, not by this paragraph.
|
|
33
|
+
*/
|
|
34
|
+
import type { BuyerEmiSettings, BuyerFacingFees } from "../features/site-settings/fees";
|
|
35
|
+
import type { BuyerEmiQuote } from "../features/emi/schedule";
|
|
36
|
+
/** One rendered explanation: a short claim plus the steps behind it. */
|
|
37
|
+
export interface FeeDerivation {
|
|
38
|
+
/** One sentence answering "why am I paying this". */
|
|
39
|
+
summary: string;
|
|
40
|
+
/** The arithmetic, one step per line, in the order it is applied. */
|
|
41
|
+
steps: string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The platform commission.
|
|
45
|
+
*
|
|
46
|
+
* The cap is the interesting half and the reason this needed explaining at
|
|
47
|
+
* all: on a ₹39,500 order the uncapped commission is large and the charge is
|
|
48
|
+
* ₹10, so the number on screen bears no visible relation to the subtotal.
|
|
49
|
+
*/
|
|
50
|
+
export declare function explainPlatformFee(subtotal: number, charged: number, fees: BuyerFacingFees | null, currency?: string): FeeDerivation | null;
|
|
51
|
+
/**
|
|
52
|
+
* GST on the commission.
|
|
53
|
+
*
|
|
54
|
+
* 🛑 Levied on the commission ACTUALLY CHARGED, never on the uncapped figure —
|
|
55
|
+
* which is the one thing a buyer checking ₹1.80 against a ₹39,500 subtotal
|
|
56
|
+
* would get wrong.
|
|
57
|
+
*/
|
|
58
|
+
export declare function explainGstOnPlatformFee(platformFee: number, charged: number, fees: BuyerFacingFees | null, currency?: string): FeeDerivation | null;
|
|
59
|
+
/**
|
|
60
|
+
* The EMI surcharge — the cost of credit, stated as a cost of credit.
|
|
61
|
+
*
|
|
62
|
+
* 🛑 Takes a `BuyerEmiQuote`, which is `computeBuyerEmiQuote`'s return type and
|
|
63
|
+
* carries no surcharge split. `computeEmiSchedule`'s richer result is
|
|
64
|
+
* assignable to it, so the type does not stop a server caller passing the
|
|
65
|
+
* wrong one — but nothing here reads a field that only exists on the wider
|
|
66
|
+
* shape, so no split can be printed either.
|
|
67
|
+
*/
|
|
68
|
+
export declare function explainEmiSurcharge(sellerSubtotal: number, tenureMonths: number, quote: BuyerEmiQuote, emi: BuyerEmiSettings | null, currency?: string): FeeDerivation | null;
|
|
69
|
+
/** COD handling — a cap that bites on most carts, same shape as the commission. */
|
|
70
|
+
export declare function explainCodHandlingFee(subtotal: number, charged: number, fees: BuyerFacingFees | null, currency?: string): FeeDerivation | null;
|
|
71
|
+
/**
|
|
72
|
+
* Every derivation a price breakdown can render, keyed by the line it belongs
|
|
73
|
+
* to. A key with no entry simply renders no disclosure — a line we cannot
|
|
74
|
+
* explain says nothing rather than guessing.
|
|
75
|
+
*/
|
|
76
|
+
export interface FeeDerivations {
|
|
77
|
+
platformFee?: FeeDerivation | null;
|
|
78
|
+
gstOnFee?: FeeDerivation | null;
|
|
79
|
+
emiSurcharge?: FeeDerivation | null;
|
|
80
|
+
codHandlingFee?: FeeDerivation | null;
|
|
81
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
2
|
+
/** `2` → `"2%"`, `2.5` → `"2.5%"` — no trailing `.0`. */
|
|
3
|
+
function pct(value) {
|
|
4
|
+
return `${Number(value.toFixed(2))}%`;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The platform commission.
|
|
8
|
+
*
|
|
9
|
+
* The cap is the interesting half and the reason this needed explaining at
|
|
10
|
+
* all: on a ₹39,500 order the uncapped commission is large and the charge is
|
|
11
|
+
* ₹10, so the number on screen bears no visible relation to the subtotal.
|
|
12
|
+
*/
|
|
13
|
+
export function explainPlatformFee(subtotal, charged, fees, currency = "INR") {
|
|
14
|
+
if (!fees || fees.platformFeePercent === undefined)
|
|
15
|
+
return null;
|
|
16
|
+
const money = (v) => formatCurrency(v, currency);
|
|
17
|
+
const uncapped = (subtotal * fees.platformFeePercent) / 100;
|
|
18
|
+
const steps = [
|
|
19
|
+
`${pct(fees.platformFeePercent)} of ${money(subtotal)} = ${money(uncapped)}`,
|
|
20
|
+
];
|
|
21
|
+
if (fees.platformFeeMax !== undefined && uncapped > fees.platformFeeMax) {
|
|
22
|
+
steps.push(`Capped at ${money(fees.platformFeeMax)} — you pay the cap, not the percentage`);
|
|
23
|
+
}
|
|
24
|
+
/*
|
|
25
|
+
* The floor is defined on `platformFee + gstOnFee` COMBINED and is applied
|
|
26
|
+
* once, inside `allocateCheckoutFees`, as extra platform fee with no GST of
|
|
27
|
+
* its own. So it is mentioned here and never on the GST line — see the D41
|
|
28
|
+
* note in `calculator.ts`.
|
|
29
|
+
*/
|
|
30
|
+
if (fees.minimumTransactionFee !== undefined && charged > Math.min(uncapped, fees.platformFeeMax ?? uncapped)) {
|
|
31
|
+
steps.push(`Raised to meet the ${money(fees.minimumTransactionFee)} minimum transaction fee, which covers the commission and its GST together`);
|
|
32
|
+
}
|
|
33
|
+
steps.push(`You pay ${money(charged)}`);
|
|
34
|
+
return {
|
|
35
|
+
summary: fees.platformFeeMax !== undefined && uncapped > fees.platformFeeMax
|
|
36
|
+
? `Our commission is ${pct(fees.platformFeePercent)}, capped at ${money(fees.platformFeeMax)} per order.`
|
|
37
|
+
: `Our commission is ${pct(fees.platformFeePercent)} of the order.`,
|
|
38
|
+
steps,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* GST on the commission.
|
|
43
|
+
*
|
|
44
|
+
* 🛑 Levied on the commission ACTUALLY CHARGED, never on the uncapped figure —
|
|
45
|
+
* which is the one thing a buyer checking ₹1.80 against a ₹39,500 subtotal
|
|
46
|
+
* would get wrong.
|
|
47
|
+
*/
|
|
48
|
+
export function explainGstOnPlatformFee(platformFee, charged, fees, currency = "INR") {
|
|
49
|
+
if (!fees || fees.gstPercent === undefined)
|
|
50
|
+
return null;
|
|
51
|
+
const money = (v) => formatCurrency(v, currency);
|
|
52
|
+
return {
|
|
53
|
+
summary: `GST at ${pct(fees.gstPercent)}, charged on our commission — not on the price of the items.`,
|
|
54
|
+
steps: [
|
|
55
|
+
`${pct(fees.gstPercent)} of the ${money(platformFee)} commission = ${money(charged)}`,
|
|
56
|
+
"Item prices already include their own GST, shown below the total",
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The EMI surcharge — the cost of credit, stated as a cost of credit.
|
|
62
|
+
*
|
|
63
|
+
* 🛑 Takes a `BuyerEmiQuote`, which is `computeBuyerEmiQuote`'s return type and
|
|
64
|
+
* carries no surcharge split. `computeEmiSchedule`'s richer result is
|
|
65
|
+
* assignable to it, so the type does not stop a server caller passing the
|
|
66
|
+
* wrong one — but nothing here reads a field that only exists on the wider
|
|
67
|
+
* shape, so no split can be printed either.
|
|
68
|
+
*/
|
|
69
|
+
export function explainEmiSurcharge(sellerSubtotal, tenureMonths, quote, emi, currency = "INR") {
|
|
70
|
+
if (!emi)
|
|
71
|
+
return null;
|
|
72
|
+
const money = (v) => formatCurrency(v, currency);
|
|
73
|
+
const principal = sellerSubtotal - quote.tokenAmount;
|
|
74
|
+
const perMonth = quote.installments[0]?.amount ?? 0;
|
|
75
|
+
return {
|
|
76
|
+
summary: `Spreading ${money(sellerSubtotal)} over ${tenureMonths} months costs ${money(quote.surchargeAmount)} — that is the total cost of credit.`,
|
|
77
|
+
steps: [
|
|
78
|
+
`Token paid today: ${pct(emi.tokenPercent)} of ${money(sellerSubtotal)} = ${money(quote.tokenAmount)}`,
|
|
79
|
+
`Financed amount: ${money(sellerSubtotal)} − ${money(quote.tokenAmount)} = ${money(principal)}`,
|
|
80
|
+
`Surcharge: ${pct(emi.surchargePercentPerMonth)} of ${money(principal)} per month × ${tenureMonths} months = ${money(quote.surchargeAmount)}`,
|
|
81
|
+
`Repaid over ${tenureMonths} instalments of about ${money(perMonth)}, due on day ${emi.billingDay} of each month`,
|
|
82
|
+
`Total you will pay: ${money(quote.tokenAmount)} + ${money(quote.remainingBalance)} = ${money(quote.tokenAmount + quote.remainingBalance)}`,
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** COD handling — a cap that bites on most carts, same shape as the commission. */
|
|
87
|
+
export function explainCodHandlingFee(subtotal, charged, fees, currency = "INR") {
|
|
88
|
+
if (!fees || fees.codHandlingFeePercent === undefined)
|
|
89
|
+
return null;
|
|
90
|
+
const money = (v) => formatCurrency(v, currency);
|
|
91
|
+
const uncapped = (subtotal * fees.codHandlingFeePercent) / 100;
|
|
92
|
+
const steps = [`${pct(fees.codHandlingFeePercent)} of ${money(subtotal)} = ${money(uncapped)}`];
|
|
93
|
+
if (fees.codHandlingFeeCap !== undefined && uncapped > fees.codHandlingFeeCap) {
|
|
94
|
+
steps.push(`Capped at ${money(fees.codHandlingFeeCap)}`);
|
|
95
|
+
}
|
|
96
|
+
steps.push(`You pay ${money(charged)}`);
|
|
97
|
+
return {
|
|
98
|
+
summary: "What it costs us to collect cash at your door and bank it.",
|
|
99
|
+
steps,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export type { HistoryActorRole, HistoryActor, FieldChange, StatusChangeEntry, AppendHistoryResult, SerialisedStatusChangeEntry, } from "./types";
|
|
2
|
-
export { STATUS_HISTORY_MAX } from "./types";
|
|
2
|
+
export { STATUS_HISTORY_MAX, HISTORY_ACTOR_ROLE } from "./types";
|
|
3
3
|
export type { BuildHistoryEntryInput } from "./history";
|
|
4
4
|
export { diffTrackedFields, buildHistoryEntry, appendHistoryEntry, withHistory, } from "./history";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { STATUS_HISTORY_MAX } from "./types.js";
|
|
1
|
+
export { STATUS_HISTORY_MAX, HISTORY_ACTOR_ROLE } from "./types.js";
|
|
2
2
|
export { diffTrackedFields, buildHistoryEntry, appendHistoryEntry, withHistory, } from "./history.js";
|
|
@@ -9,7 +9,13 @@ import type { FirestoreValue } from "../../../schemas/types";
|
|
|
9
9
|
* entities had any history at all — `ads`, built inline in a route handler
|
|
10
10
|
* with no type and no reuse.
|
|
11
11
|
*/
|
|
12
|
-
export
|
|
12
|
+
export declare const HISTORY_ACTOR_ROLE: {
|
|
13
|
+
readonly BUYER: "buyer";
|
|
14
|
+
readonly SELLER: "seller";
|
|
15
|
+
readonly ADMIN: "admin";
|
|
16
|
+
readonly SYSTEM: "system";
|
|
17
|
+
};
|
|
18
|
+
export type HistoryActorRole = (typeof HISTORY_ACTOR_ROLE)[keyof typeof HISTORY_ACTOR_ROLE];
|
|
13
19
|
export interface HistoryActor {
|
|
14
20
|
/** Firebase Auth uid. Absent for `system` (cron jobs, Firestore triggers). */
|
|
15
21
|
uid?: string;
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic entity status history — the shape shared by every entity that
|
|
3
|
+
* records how it got to its current state.
|
|
4
|
+
*
|
|
5
|
+
* Built for orders first (W2) and adopted by stores, payouts, offers, bids,
|
|
6
|
+
* support tickets, scammer profiles, catalogue approvals, shipments and
|
|
7
|
+
* returns in W15. Before this existed, exactly ONE of ~40 status-bearing
|
|
8
|
+
* entities had any history at all — `ads`, built inline in a route handler
|
|
9
|
+
* with no type and no reuse.
|
|
10
|
+
*/
|
|
11
|
+
/*
|
|
12
|
+
* 🛑 This is an ACTOR KIND, not a `UserDocument.role`. No user has the role
|
|
13
|
+
* "buyer" — a buyer's stored role is "user" — so the two namespaces must never
|
|
14
|
+
* be compared, and `audit-phantom-buyer-role` exists to catch exactly that
|
|
15
|
+
* mistake in a runtime auth gate.
|
|
16
|
+
*
|
|
17
|
+
* The constant below is what a gate should compare against: it names the
|
|
18
|
+
* namespace at the call site, where a bare `"buyer"` literal cannot.
|
|
19
|
+
*/
|
|
20
|
+
export const HISTORY_ACTOR_ROLE = {
|
|
21
|
+
BUYER: "buyer",
|
|
22
|
+
SELLER: "seller",
|
|
23
|
+
ADMIN: "admin",
|
|
24
|
+
SYSTEM: "system",
|
|
25
|
+
};
|
|
1
26
|
/**
|
|
2
27
|
* Cap for any history array. Matches `HISTORY_MAX`'s reasoning: an unbounded
|
|
3
28
|
* array on a hot document inflates every read of that document (Rule #6), and
|
package/dist/client.d.ts
CHANGED
|
@@ -402,6 +402,9 @@ export { OrderCompletionPanel } from "./features/orders/components/OrderCompleti
|
|
|
402
402
|
export type { OrderCompletionPanelProps, RateableLine } from "./features/orders/components/OrderCompletionPanel";
|
|
403
403
|
export { EmiInstallmentsPanel } from "./features/orders/components/EmiInstallmentsPanel";
|
|
404
404
|
export type { EmiInstallmentsPanelProps } from "./features/orders/components/EmiInstallmentsPanel";
|
|
405
|
+
export { BalancePaymentPanel } from "./features/orders/components/BalancePaymentPanel";
|
|
406
|
+
export type { BalancePaymentPanelProps } from "./features/orders/components/BalancePaymentPanel";
|
|
407
|
+
export { balanceProofSchema, balanceReviewSchema, type BalanceProofInput, type BalanceReviewInput, } from "./features/orders/schemas/balance";
|
|
405
408
|
export { CountdownDisplay } from "./ui/components/CountdownDisplay";
|
|
406
409
|
export type { CountdownDisplayProps } from "./ui/components/CountdownDisplay";
|
|
407
410
|
export { OrderAddonBadges } from "./features/orders/components/OrderAddonBadges";
|
|
@@ -513,6 +516,7 @@ export { CommandPalette, useCommandPaletteHotkey } from "./features/shell/index"
|
|
|
513
516
|
export type { CommandPaletteGroup } from "./features/shell/index";
|
|
514
517
|
export { AdminBlogEditorView, AdminBundleEditorView, AdminCarouselEditorView, AdminCategoryEditorView, AdminCouponEditorView, AdminFaqEditorView, AdminFeatureEditorView, AdminOrderEditorView, AdminPayoutMarkPaidModal, AdminAuditLogView, AdminMoneyCasesView, AdminGatewayEventsView, ViewAuditLogEntryModal, AdminProductEditorView, AdminScammerEditorView, AdminEmployeeEditorView, AdminNavEditorView, AdminUserEditorView, AdminStoreEditorView, AdminSublistingCategoryEditorView, AdminSupportTicketDetailView, } from "./features/admin/index";
|
|
515
518
|
export type { AdminStoreEditorViewProps, AdminPayoutMarkPaidModalProps, AdminAuditLogViewProps, AdminMoneyCasesViewProps, AuditLogEntryDetail, ViewAuditLogEntryModalProps } from "./features/admin/index";
|
|
519
|
+
export type { AdminOrderEditorViewProps } from "./features/admin/components/AdminOrderEditorView";
|
|
516
520
|
export type { NavItemData } from "./features/admin/index";
|
|
517
521
|
export { PageLoader } from "./ui/index";
|
|
518
522
|
export { ADMIN_CHECKOUT_BYPASS_FLAG_KEY } from "./features/admin/schemas/firestore";
|
|
@@ -534,6 +538,10 @@ export { BUNDLE_COPY } from "./_internal/shared/features/categories/bundle-copy"
|
|
|
534
538
|
export { useSiteSettings } from "./core/index";
|
|
535
539
|
export { checkEmiEligibility, computeEmiSchedule, computeBuyerEmiQuote, } from "./_internal/shared/features/emi/schedule";
|
|
536
540
|
export type { EmiSettings, BuyerEmiQuote } from "./_internal/shared/features/emi/schedule";
|
|
541
|
+
export { emiTenderBlockReason, isTenderCompatibleWithEmi } from "./_internal/shared/features/emi/choice";
|
|
542
|
+
export type { EmiTender } from "./_internal/shared/features/emi/choice";
|
|
543
|
+
export { explainCodHandlingFee, explainEmiSurcharge, explainGstOnPlatformFee, explainPlatformFee, } from "./_internal/shared/fees/explain";
|
|
544
|
+
export type { FeeDerivation, FeeDerivations } from "./_internal/shared/fees/explain";
|
|
537
545
|
export type { BuyerFacingFees, BuyerEmiSettings, } from "./_internal/shared/features/site-settings/fees";
|
|
538
546
|
export type { PublicSiteSettings } from "./_internal/shared/features/site-settings/types";
|
|
539
547
|
export { computeCodHandlingFee } from "./_internal/shared/fees/calculator";
|
|
@@ -558,6 +566,8 @@ export { raffleEntrySchema } from "./features/contact/schemas/inquiry-forms";
|
|
|
558
566
|
export { orderCancelSchema, paymentProofSchema, } from "./features/orders/schemas/buyer-forms";
|
|
559
567
|
export { scamReportFormSchema } from "./features/scams/schemas/report-form";
|
|
560
568
|
export { sublistingCategoryFormSchema } from "./features/store-extensions/schemas/sublisting-category-form";
|
|
569
|
+
export { adCreativeFormSchema } from "./features/ads/schemas/creative-form";
|
|
570
|
+
export type { AdCreativeFormValues } from "./features/ads/schemas/creative-form";
|
|
561
571
|
export { adminStoreUpdateSchema, announcementBarSchema, } from "./features/admin/schemas/admin-editor-forms";
|
|
562
572
|
export { adminOrderUpdateSchema, employeeInviteSchema, hardBanReasonSchema, featureFlagsUpdateSchema, adminAdFormSchema, } from "./features/admin/schemas/admin-ops-forms";
|
|
563
573
|
export { changeEmailSchema } from "./features/account/schemas/index";
|
|
@@ -596,7 +606,7 @@ export type { ReturnReason } from "./_internal/shared/features/orders/return-rea
|
|
|
596
606
|
export { DISPUTE_KIND, DISPUTE_KIND_LABEL, DISPUTE_GATE, DISPUTE_TRANSIT_WINDOW_DAYS, DISPUTE_NOT_REACHED_GRACE_DAYS, isDisputeKind, checkDisputeGate, resolveDisputeEtd, disputeNotReachedFilableFrom, } from "./_internal/shared/features/orders/dispute-reasons";
|
|
597
607
|
export type { DisputeKind, DisputeGateOrder, DisputeGateResult, } from "./_internal/shared/features/orders/dispute-reasons";
|
|
598
608
|
export { INTEGRATIONS, INTEGRATION_ORDER, readIntegrationStates, integrationPatch, allIntegrationsPatch, } from "./_internal/shared/features/admin/integrations";
|
|
599
|
-
export type { IntegrationId, IntegrationDescriptor, IntegrationState, } from "./_internal/shared/features/admin/integrations";
|
|
609
|
+
export type { IntegrationId, IntegrationDescriptor, IntegrationPatch, IntegrationState, } from "./_internal/shared/features/admin/integrations";
|
|
600
610
|
export { RefundRequestView } from "./features/orders/components/RefundRequestView";
|
|
601
611
|
export type { RefundRequestViewProps, RefundRequestSubmission, } from "./features/orders/components/RefundRequestView";
|
|
602
612
|
export { ORDER_RETURN_WINDOW_DAYS, ORDER_RETURN_WINDOW_MS, ORDER_CANCELLABLE_STATUSES, } from "./_internal/shared/features/orders/config";
|