@mohasinac/appkit 4.62.0 → 4.62.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/server/features/checkout/actions.d.ts +80 -0
- package/dist/_internal/server/features/checkout/actions.js +542 -26
- package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
- package/dist/_internal/server/features/invoicing/allocate.js +162 -0
- package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
- package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
- package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
- package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
- package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
- package/dist/_internal/server/features/invoicing/issue.js +202 -0
- package/dist/_internal/server/features/orders/actions.d.ts +8 -1
- package/dist/_internal/server/features/orders/actions.js +81 -8
- package/dist/_internal/server/features/orders/adapters.js +33 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
- package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
- package/dist/_internal/server/features/points/award.js +46 -4
- package/dist/_internal/server/features/refunds/actions.js +26 -0
- package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
- package/dist/_internal/server/features/shipping/courier-block.js +239 -0
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +15 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
- package/dist/_internal/server/features/stores/adapters.js +51 -0
- package/dist/_internal/server/functions/scheduled.js +38 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +9 -2
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
- package/dist/_internal/server/jobs/core/index.d.ts +2 -1
- package/dist/_internal/server/jobs/core/index.js +2 -1
- package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
- package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +62 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
- package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
- package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +10 -2
- package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
- package/dist/_internal/shared/actions/action-registry.js +28 -0
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
- package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
- package/dist/_internal/shared/checkout/order-math.d.ts +145 -0
- package/dist/_internal/shared/checkout/order-math.js +173 -0
- package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
- package/dist/_internal/shared/checkout/refundable-base.js +13 -1
- package/dist/_internal/shared/checkout/split.d.ts +87 -0
- package/dist/_internal/shared/checkout/split.js +100 -0
- package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
- package/dist/_internal/shared/content/disclaimers.js +217 -0
- package/dist/_internal/shared/content/published-values.d.ts +88 -0
- package/dist/_internal/shared/content/published-values.js +198 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +109 -0
- package/dist/_internal/shared/features/admin/integrations.js +253 -0
- package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
- package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
- package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
- package/dist/_internal/shared/features/points/accepts.d.ts +36 -0
- package/dist/_internal/shared/features/points/accepts.js +43 -0
- package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
- package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
- package/dist/_internal/shared/fees/calculator.d.ts +33 -1
- package/dist/_internal/shared/fees/calculator.js +32 -2
- package/dist/client.d.ts +11 -0
- package/dist/client.js +30 -0
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +5 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/shipping.d.ts +45 -0
- package/dist/contracts/shipping.js +15 -0
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
- package/dist/features/account/components/UserOrdersView.js +19 -33
- package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
- package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
- package/dist/features/admin/components/AdminInvoicingView.js +65 -0
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +18 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +11 -2
- package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
- package/dist/features/admin/components/AdminOrdersView.js +67 -5
- package/dist/features/admin/components/OwnerQuickActions.js +24 -2
- package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
- package/dist/features/admin/constants/filter-tabs.js +6 -0
- package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
- package/dist/features/admin/hooks/useAdminListingData.js +16 -0
- package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
- package/dist/features/admin/schemas/firestore.d.ts +30 -0
- package/dist/features/admin/schemas/firestore.js +5 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +30 -0
- package/dist/features/audit-log/schemas/firestore.js +15 -0
- package/dist/features/auth/permissions/constants.js +13 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +13 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +6 -2
- package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
- package/dist/features/cart/components/PointsTenderPicker.js +15 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
- package/dist/features/cart/components/index.d.ts +2 -0
- package/dist/features/cart/components/index.js +1 -0
- package/dist/features/cart/repository/cart.repository.d.ts +19 -0
- package/dist/features/cart/repository/cart.repository.js +27 -0
- package/dist/features/cart/schemas/firestore.d.ts +92 -0
- package/dist/features/contact/email.d.ts +27 -10
- package/dist/features/contact/email.js +20 -3
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +38 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +80 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +64 -0
- package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
- package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
- package/dist/features/faq/components/FAQAccordion.js +29 -4
- package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
- package/dist/features/invoicing/schemas/firestore.js +45 -0
- package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
- package/dist/features/invoicing/utils/financial-year.js +53 -0
- package/dist/features/jobs/actions/enqueue-job.js +18 -1
- package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
- package/dist/features/jobs/repository/jobs.repository.js +84 -1
- package/dist/features/jobs/schemas/firestore.d.ts +80 -0
- package/dist/features/jobs/schemas/firestore.js +37 -0
- package/dist/features/lottery/schemas/zod.d.ts +6 -6
- package/dist/features/media/MediaImage.js +22 -1
- package/dist/features/media/upload/ImageUpload.js +1 -1
- package/dist/features/obligations/constants/policies.js +24 -1
- package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
- package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
- package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
- package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
- package/dist/features/orders/components/RaiseDisputeForm.js +111 -0
- package/dist/features/orders/components/RefundRequestView.js +19 -1
- package/dist/features/orders/components/index.d.ts +4 -0
- package/dist/features/orders/components/index.js +2 -0
- package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
- package/dist/features/orders/constants/order-lane-tabs.js +88 -0
- package/dist/features/orders/repository/orders.repository.d.ts +57 -0
- package/dist/features/orders/repository/orders.repository.js +145 -0
- package/dist/features/orders/schemas/firestore.d.ts +196 -1
- package/dist/features/orders/schemas/index.d.ts +54 -6
- package/dist/features/orders/schemas/index.js +12 -0
- package/dist/features/orders/types/index.d.ts +26 -0
- package/dist/features/orders/utils/courier-block.d.ts +25 -0
- package/dist/features/orders/utils/courier-block.js +23 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
- package/dist/features/payment-cases/schemas/firestore.js +1 -0
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
- package/dist/features/products/components/ProductDetailPageView.js +2 -2
- package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
- package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
- package/dist/features/promotions/actions/coupon-stacking.js +21 -2
- package/dist/features/promotions/components/CouponCard.js +13 -0
- package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
- package/dist/features/promotions/constants/coupon-help.js +9 -1
- package/dist/features/promotions/schemas/firestore.d.ts +24 -0
- package/dist/features/promotions/schemas/firestore.js +4 -0
- package/dist/features/promotions/types/index.d.ts +19 -1
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/search/action-index/tab-entries.js +1 -1
- package/dist/features/seller/actions/seller-actions.js +33 -3
- package/dist/features/seller/components/SellerOrdersView.js +129 -27
- package/dist/features/seller/hooks/useSellerListingData.js +25 -0
- package/dist/features/shipments/schemas/validation.d.ts +4 -4
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +43 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +90 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +82 -0
- package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
- package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
- package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
- package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
- package/dist/features/stores/schemas/firestore.d.ts +71 -0
- package/dist/features/stores/types/index.d.ts +19 -0
- package/dist/features/support/schemas/index.d.ts +6 -6
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +14 -1
- package/dist/next/routing/route-map.d.ts +20 -0
- package/dist/next/routing/route-map.js +10 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
- package/dist/providers/shipping-shiprocket/index.js +84 -1
- package/dist/schemas/registry.d.ts +47 -18
- package/dist/schemas/sieve.d.ts +2 -2
- package/dist/server-entry.d.ts +2 -2
- package/dist/server-entry.js +8 -1
- package/dist/server.d.ts +10 -1
- package/dist/server.js +39 -1
- package/dist/styles.css +64 -80
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +1 -0
- package/dist/tokens/themes/default-light.js +1 -0
- package/dist/tokens/themes/required.d.ts +1 -1
- package/dist/tokens/themes/required.js +1 -0
- package/dist/tokens/tokens.css +15 -0
- package/dist/ui/components/Disclaimer.d.ts +19 -0
- package/dist/ui/components/Disclaimer.js +74 -0
- package/dist/ui/components/tabs-overflow.js +64 -11
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/rich-text/RichText.style.css +48 -79
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +1831 -1822
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispute KINDS and the gate each one must pass.
|
|
3
|
+
*
|
|
4
|
+
* ## Why the kind is closed and the reason list is not
|
|
5
|
+
*
|
|
6
|
+
* The owner's two rules are policy:
|
|
7
|
+
*
|
|
8
|
+
* > An item dispute can only be raised after the order was received. A
|
|
9
|
+
* > "not reached" dispute is allowed once the ETD is more than 3 days past.
|
|
10
|
+
*
|
|
11
|
+
* A gate cannot branch on a user-created string. `return-reasons.ts` already
|
|
12
|
+
* makes this argument in its own header — *"'No returns, unless the buyer
|
|
13
|
+
* didn't get what they paid for' is not a policy that can be enforced against
|
|
14
|
+
* a free-text box: it needs a machine-readable statement of what went
|
|
15
|
+
* wrong"* — and it applies with more force here, because these two gates are
|
|
16
|
+
* MUTUALLY EXCLUSIVE.
|
|
17
|
+
*
|
|
18
|
+
* So there are two levels:
|
|
19
|
+
*
|
|
20
|
+
* 1. **`DisputeKind` — closed, here, two values.** It picks the gate.
|
|
21
|
+
* 2. **`disputeReasons` — a staff-creatable Firestore collection.** Every row
|
|
22
|
+
* carries a `kind`, so a new reason inherits an existing gate and cannot
|
|
23
|
+
* invent one. That list is for triage and analytics, never for policy.
|
|
24
|
+
*
|
|
25
|
+
* ## What this replaces
|
|
26
|
+
*
|
|
27
|
+
* `raiseOrderDisputeAction` took `reason: string` and checked only that it was
|
|
28
|
+
* non-empty. So today a buyer can file "it never arrived" on an order that was
|
|
29
|
+
* delivered last week, and "it arrived damaged" on one still in transit — both
|
|
30
|
+
* nonsense, both accepted, and both raising a `severity: "error"` fraud-category
|
|
31
|
+
* admin alert. The gate is the point of this file.
|
|
32
|
+
*/
|
|
33
|
+
export const DISPUTE_KIND = ["item", "not_reached"];
|
|
34
|
+
/**
|
|
35
|
+
* 🛑 `Record<DisputeKind, string>`, so a third kind is a COMPILE error here
|
|
36
|
+
* rather than a dispute that renders `undefined` and passes no gate.
|
|
37
|
+
*/
|
|
38
|
+
export const DISPUTE_KIND_LABEL = {
|
|
39
|
+
item: "There's a problem with what I received",
|
|
40
|
+
not_reached: "My order hasn't arrived",
|
|
41
|
+
};
|
|
42
|
+
export function isDisputeKind(value) {
|
|
43
|
+
return typeof value === "string" && DISPUTE_KIND.includes(value);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* How long after dispatch a parcel is ASSUMED to have been deliverable, when
|
|
47
|
+
* the courier gave us no estimate.
|
|
48
|
+
*
|
|
49
|
+
* 🛑 This exists so the `not_reached` gate is reachable AT ALL. There is no
|
|
50
|
+
* courier ETD on an order today: nothing platform-books (`createShipment` has
|
|
51
|
+
* zero callers), and a self-shipping seller types a tracking number with no
|
|
52
|
+
* estimate attached. Gate solely on a stored ETD and no buyer could ever file
|
|
53
|
+
* a non-delivery dispute — a gate nobody can pass is the same as no feature.
|
|
54
|
+
*
|
|
55
|
+
* Published through `PublishedValueKey` so the policy page and this gate
|
|
56
|
+
* cannot disagree (Phase 9).
|
|
57
|
+
*/
|
|
58
|
+
export const DISPUTE_TRANSIT_WINDOW_DAYS = 7;
|
|
59
|
+
/**
|
|
60
|
+
* The owner's "more than 3 days" grace, measured from the ETD.
|
|
61
|
+
*
|
|
62
|
+
* Separate from the transit window on purpose: one is "when should it have
|
|
63
|
+
* arrived", the other is "how long after that before we believe it is lost".
|
|
64
|
+
* Folding them into a single 10 would make neither number explicable on a
|
|
65
|
+
* policy page.
|
|
66
|
+
*/
|
|
67
|
+
export const DISPUTE_NOT_REACHED_GRACE_DAYS = 3;
|
|
68
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
69
|
+
function asDate(v) {
|
|
70
|
+
if (!v)
|
|
71
|
+
return null;
|
|
72
|
+
const d = v instanceof Date ? v : new Date(v);
|
|
73
|
+
return Number.isFinite(d.getTime()) ? d : null;
|
|
74
|
+
}
|
|
75
|
+
function fmt(d) {
|
|
76
|
+
return d.toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The effective ETD, and WHY we think so.
|
|
80
|
+
*
|
|
81
|
+
* Exported because the buyer-facing form needs to show the date they can file
|
|
82
|
+
* from — telling someone "not yet" without saying when is a dead end.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveDisputeEtd(order, transitWindowDays = DISPUTE_TRANSIT_WINDOW_DAYS) {
|
|
85
|
+
const courier = asDate(order.estimatedDeliveryDate);
|
|
86
|
+
if (courier)
|
|
87
|
+
return { etd: courier, source: "courier" };
|
|
88
|
+
const shipped = asDate(order.shippingDate);
|
|
89
|
+
if (shipped) {
|
|
90
|
+
return { etd: new Date(shipped.getTime() + transitWindowDays * DAY_MS), source: "assumed" };
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/** The date from which a `not_reached` dispute becomes filable. */
|
|
95
|
+
export function disputeNotReachedFilableFrom(order, graceDays = DISPUTE_NOT_REACHED_GRACE_DAYS) {
|
|
96
|
+
const resolved = resolveDisputeEtd(order);
|
|
97
|
+
if (!resolved)
|
|
98
|
+
return null;
|
|
99
|
+
return new Date(resolved.etd.getTime() + graceDays * DAY_MS);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 🛑 `Record<DisputeKind, …>`. A third kind cannot be added without deciding
|
|
103
|
+
* what gates it — which is the whole reason the kind is closed.
|
|
104
|
+
*/
|
|
105
|
+
export const DISPUTE_GATE = {
|
|
106
|
+
/**
|
|
107
|
+
* You cannot judge an item you have not seen.
|
|
108
|
+
*
|
|
109
|
+
* `completedAt` counts as well as `deliveryDate`: a buyer who pressed "Mark
|
|
110
|
+
* complete" has told us they received it, and refusing them because no
|
|
111
|
+
* courier scan landed would be refusing the more reliable signal of the two.
|
|
112
|
+
*/
|
|
113
|
+
item: (order) => {
|
|
114
|
+
if (asDate(order.deliveryDate) || asDate(order.completedAt))
|
|
115
|
+
return { allowed: true };
|
|
116
|
+
return {
|
|
117
|
+
allowed: false,
|
|
118
|
+
message: "You can report a problem with an item once it arrives. If it hasn't arrived, raise a delivery issue instead.",
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* Only valid BEFORE receipt, and only once the parcel is genuinely late.
|
|
123
|
+
*
|
|
124
|
+
* Three refusals, each pointing somewhere:
|
|
125
|
+
* - delivered → the wrong kind; offer the item route or a return.
|
|
126
|
+
* - never dispatched → not a courier problem at all. That is the
|
|
127
|
+
* `seller-dispatch` obligation's job, and it is already running.
|
|
128
|
+
* - too early → say the date, not just "not yet".
|
|
129
|
+
*/
|
|
130
|
+
not_reached: (order, now = new Date()) => {
|
|
131
|
+
const delivered = asDate(order.deliveryDate);
|
|
132
|
+
if (delivered) {
|
|
133
|
+
return {
|
|
134
|
+
allowed: false,
|
|
135
|
+
message: `This order was marked delivered on ${fmt(delivered)}. If something is wrong with what arrived, report a problem with the item instead.`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (!asDate(order.shippingDate) && !asDate(order.estimatedDeliveryDate)) {
|
|
139
|
+
return {
|
|
140
|
+
allowed: false,
|
|
141
|
+
message: "This order hasn't been dispatched yet, so there's nothing in transit to chase. We already track late dispatch and will act on it without you needing to do anything.",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const filableFrom = disputeNotReachedFilableFrom(order);
|
|
145
|
+
if (!filableFrom) {
|
|
146
|
+
// Unreachable given the check above; kept rather than asserted, because
|
|
147
|
+
// a thrown non-null assertion here would be a 500 on a buyer action.
|
|
148
|
+
return {
|
|
149
|
+
allowed: false,
|
|
150
|
+
message: "We can't work out an expected delivery date for this order yet. Contact support.",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (now.getTime() < filableFrom.getTime()) {
|
|
154
|
+
return {
|
|
155
|
+
allowed: false,
|
|
156
|
+
message: `It's still within the expected delivery window. If it hasn't arrived by ${fmt(filableFrom)}, you can raise a delivery issue then.`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return { allowed: true };
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Convenience wrapper so callers do not index the record by hand.
|
|
164
|
+
*
|
|
165
|
+
* 🛑 Takes the NARROW `DisputeGateOrder`, and an `OrderDocument` satisfies it
|
|
166
|
+
* structurally — so no adapter is needed and none should be added.
|
|
167
|
+
*
|
|
168
|
+
* A first draft had a `toDisputeGateOrder(order: OrderDocument)` helper, which
|
|
169
|
+
* meant this module imported the order schema while the order schema imports
|
|
170
|
+
* `DisputeKind` from here. That cycle is type-only and therefore erased, so it
|
|
171
|
+
* would have compiled and run — but it is also entirely avoidable, and a
|
|
172
|
+
* module that defines a policy should not depend on the document the policy is
|
|
173
|
+
* applied to. `return-reasons.ts` beside it has no imports at all for the same
|
|
174
|
+
* reason.
|
|
175
|
+
*
|
|
176
|
+
* Nothing leaks by passing the whole order in: what crosses back out is a
|
|
177
|
+
* `DisputeGateResult`, never the document (Root Cause #70).
|
|
178
|
+
*/
|
|
179
|
+
export function checkDisputeGate(kind, order, now) {
|
|
180
|
+
return DISPUTE_GATE[kind](order, now);
|
|
181
|
+
}
|
|
@@ -35,7 +35,7 @@ export declare const REFUND_COPY: {
|
|
|
35
35
|
readonly reasonSelectPlaceholder: "Choose a reason";
|
|
36
36
|
readonly reasonNoteLabel: "Anything else we should know? (optional)";
|
|
37
37
|
readonly alreadyRefundedMessage: "A refund has already been processed on this order. No further disputes or refund requests can be filed.";
|
|
38
|
-
readonly acknowledgments: readonly ["I understand this refund request, once approved, permanently removes my ability to file any dispute or RMA claim on this order.", "I confirm the item has not been used, worn, or damaged by me, and I am returning it in original condition (if a return is required).", "I understand the refund amount may take
|
|
38
|
+
readonly acknowledgments: readonly ["I understand this refund request, once approved, permanently removes my ability to file any dispute or RMA claim on this order.", "I confirm the item has not been used, worn, or damaged by me, and I am returning it in original condition (if a return is required).", "I understand the refund amount may take {{refundBusinessDaysMin}}–{{refundBusinessDaysMax}} business days to reflect and that platform fees may be non-recoverable."];
|
|
39
39
|
};
|
|
40
40
|
readonly siblingPayments: {
|
|
41
41
|
readonly heading: (count: number) => string;
|
|
@@ -38,7 +38,24 @@ export const REFUND_COPY = {
|
|
|
38
38
|
acknowledgments: [
|
|
39
39
|
"I understand this refund request, once approved, permanently removes my ability to file any dispute or RMA claim on this order.",
|
|
40
40
|
"I confirm the item has not been used, worn, or damaged by me, and I am returning it in original condition (if a return is required).",
|
|
41
|
-
|
|
41
|
+
/*
|
|
42
|
+
* 🛑 Phase 9 — TOKENS, resolved by `resolvePublishedCopy` at render.
|
|
43
|
+
*
|
|
44
|
+
* This literal was one of the three contradicting owners of the refund
|
|
45
|
+
* timeline: "5–7 business days" here, "5–7 business days via Razorpay"
|
|
46
|
+
* on the policy page (naming a gateway that was not even the one in
|
|
47
|
+
* use), and "3–5 business days" in the FAQ. Three statements of one
|
|
48
|
+
* fact, no reconciliation, and the FAQ one admin-editable so it could
|
|
49
|
+
* drift further after deployment.
|
|
50
|
+
*
|
|
51
|
+
* The caller renders this through `resolvePublishedCopy`, so the number
|
|
52
|
+
* now has exactly one definition. Note this string is an
|
|
53
|
+
* ACKNOWLEDGEMENT the buyer ticks — a token left unresolved here would
|
|
54
|
+
* appear in something they are agreeing to, which is the strongest
|
|
55
|
+
* possible argument for the unknown-token rule leaving `{{…}}` visible
|
|
56
|
+
* rather than blanking it.
|
|
57
|
+
*/
|
|
58
|
+
"I understand the refund amount may take {{refundBusinessDaysMin}}–{{refundBusinessDaysMax}} business days to reflect and that platform fees may be non-recoverable.",
|
|
42
59
|
],
|
|
43
60
|
},
|
|
44
61
|
siblingPayments: {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does this store accept buyer points as part-payment?
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is a function and not a bare field read
|
|
5
|
+
*
|
|
6
|
+
* Decision 3 has two halves: *"the main store always accepts, others opt in"*.
|
|
7
|
+
* The opt-in half is `StoreDocument.acceptsPoints`; the always half is the
|
|
8
|
+
* platform's own storefront, which cannot decline to fund its own programme.
|
|
9
|
+
*
|
|
10
|
+
* 🛑 The platform store is resolved in CODE rather than seeded `true`, because a
|
|
11
|
+
* seeded flag can be switched off by an edit, a reseed, or an admin clicking the
|
|
12
|
+
* wrong toggle — and the one store that must never stop accepting is the one the
|
|
13
|
+
* whole programme is advertised on. `UserPointsView` tells buyers points are
|
|
14
|
+
* spendable; if the only store could opt out, that promise would be a lie that
|
|
15
|
+
* nothing detected.
|
|
16
|
+
*
|
|
17
|
+
* ## 🛑 Absent means NOT accepted
|
|
18
|
+
*
|
|
19
|
+
* Matching `supportsSaleProtection`'s rule on the same document, and for the
|
|
20
|
+
* same reason: the accepting store ABSORBS the redemption against its payout,
|
|
21
|
+
* so defaulting absent to `true` would enrol every existing seller in funding
|
|
22
|
+
* discounts they never agreed to. A tri-state would be worse still — a buyer
|
|
23
|
+
* cannot tell a seller who declined from one who has not been asked.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* The platform's own storefront.
|
|
27
|
+
*
|
|
28
|
+
* Hard-coded deliberately: this is an identity, not a setting. It is the same
|
|
29
|
+
* id `realListingsOnly` and the seed's `REAL_STORE_IDS` already treat as the
|
|
30
|
+
* platform's own.
|
|
31
|
+
*/
|
|
32
|
+
export declare const PLATFORM_STORE_ID = "store-letitrip-official";
|
|
33
|
+
export declare function storeAcceptsPoints(store: {
|
|
34
|
+
id?: string;
|
|
35
|
+
acceptsPoints?: boolean;
|
|
36
|
+
} | null | undefined, storeId?: string): boolean;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does this store accept buyer points as part-payment?
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is a function and not a bare field read
|
|
5
|
+
*
|
|
6
|
+
* Decision 3 has two halves: *"the main store always accepts, others opt in"*.
|
|
7
|
+
* The opt-in half is `StoreDocument.acceptsPoints`; the always half is the
|
|
8
|
+
* platform's own storefront, which cannot decline to fund its own programme.
|
|
9
|
+
*
|
|
10
|
+
* 🛑 The platform store is resolved in CODE rather than seeded `true`, because a
|
|
11
|
+
* seeded flag can be switched off by an edit, a reseed, or an admin clicking the
|
|
12
|
+
* wrong toggle — and the one store that must never stop accepting is the one the
|
|
13
|
+
* whole programme is advertised on. `UserPointsView` tells buyers points are
|
|
14
|
+
* spendable; if the only store could opt out, that promise would be a lie that
|
|
15
|
+
* nothing detected.
|
|
16
|
+
*
|
|
17
|
+
* ## 🛑 Absent means NOT accepted
|
|
18
|
+
*
|
|
19
|
+
* Matching `supportsSaleProtection`'s rule on the same document, and for the
|
|
20
|
+
* same reason: the accepting store ABSORBS the redemption against its payout,
|
|
21
|
+
* so defaulting absent to `true` would enrol every existing seller in funding
|
|
22
|
+
* discounts they never agreed to. A tri-state would be worse still — a buyer
|
|
23
|
+
* cannot tell a seller who declined from one who has not been asked.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* The platform's own storefront.
|
|
27
|
+
*
|
|
28
|
+
* Hard-coded deliberately: this is an identity, not a setting. It is the same
|
|
29
|
+
* id `realListingsOnly` and the seed's `REAL_STORE_IDS` already treat as the
|
|
30
|
+
* platform's own.
|
|
31
|
+
*/
|
|
32
|
+
export const PLATFORM_STORE_ID = "store-letitrip-official";
|
|
33
|
+
export function storeAcceptsPoints(store, storeId) {
|
|
34
|
+
/*
|
|
35
|
+
* The id is taken from either source so a caller holding only an id (the
|
|
36
|
+
* order-group loop) and one holding the document (an admin surface) get the
|
|
37
|
+
* same answer. Mismatched sources would be two rules again.
|
|
38
|
+
*/
|
|
39
|
+
const id = store?.id ?? storeId;
|
|
40
|
+
if (id === PLATFORM_STORE_ID)
|
|
41
|
+
return true;
|
|
42
|
+
return store?.acceptsPoints === true;
|
|
43
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared purchase, S5a — one payer, N destinations.
|
|
3
|
+
*
|
|
4
|
+
* ## What this is, and what it deliberately is not
|
|
5
|
+
*
|
|
6
|
+
* The coordinator buys everything and assigns a destination per item, so one
|
|
7
|
+
* paid order becomes N parcels. That is **~20% of the shared-purchase work for
|
|
8
|
+
* most of its value** (the plan's own assessment), because it needs none of
|
|
9
|
+
* the hard parts: no multi-party payment, no coordinator deposit, no approval
|
|
10
|
+
* cascade, no per-buyer invoicing, no per-buyer GST, and no stock-reservation
|
|
11
|
+
* primitive.
|
|
12
|
+
*
|
|
13
|
+
* 🛑 It is NOT S5b. There is exactly ONE payer and ONE place of supply, which
|
|
14
|
+
* is what keeps the tax, the invoice and the refund path unchanged. The moment
|
|
15
|
+
* a second person pays, every one of those becomes per-buyer and the design is
|
|
16
|
+
* a different design.
|
|
17
|
+
*
|
|
18
|
+
* ## 🛑 Why the fees are SNAPSHOTTED, and the precedent that proves it
|
|
19
|
+
*
|
|
20
|
+
* A coordinator commits to a quoted total. If the handling fee or the
|
|
21
|
+
* per-destination shipping is re-read at settle, a seller can raise it after
|
|
22
|
+
* the quote and the coordinator absorbs the difference.
|
|
23
|
+
*
|
|
24
|
+
* The codebase already has this exact hole and this exact fix: the refund path
|
|
25
|
+
* reads `finalSale` from the ORDER snapshot rather than the live product,
|
|
26
|
+
* because — in its own words — "re-reading the live product would let a seller
|
|
27
|
+
* flip the switch afterwards". D61 generalises it: *any agreement with a gap
|
|
28
|
+
* between "terms agreed" and "money taken" must snapshot its terms at
|
|
29
|
+
* agreement.*
|
|
30
|
+
*
|
|
31
|
+
* So `quoteSharedPurchase` takes the rates as ARGUMENTS and the caller
|
|
32
|
+
* snapshots the result onto the order. There is no settings read in here.
|
|
33
|
+
*/
|
|
34
|
+
/** The two rates, read from the store document at quote time. */
|
|
35
|
+
export interface SharedPurchaseRates {
|
|
36
|
+
allowSharedPurchase?: boolean;
|
|
37
|
+
sharedPurchaseHandlingFee?: number;
|
|
38
|
+
sharedPurchaseExtraShippingPerDestination?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface SharedPurchaseQuoteInput {
|
|
41
|
+
/**
|
|
42
|
+
* Distinct destinations in this order group, INCLUDING the first.
|
|
43
|
+
*
|
|
44
|
+
* 🛑 A count of DISTINCT addresses, not of items. Three items to one address
|
|
45
|
+
* is one parcel and no extra leg; two items to two addresses is two. Passing
|
|
46
|
+
* an item count would charge a coordinator for splitting an order they did
|
|
47
|
+
* not split.
|
|
48
|
+
*/
|
|
49
|
+
destinationCount: number;
|
|
50
|
+
/** The store's snapshotted rates. */
|
|
51
|
+
rates: SharedPurchaseRates;
|
|
52
|
+
}
|
|
53
|
+
export interface SharedPurchaseQuote {
|
|
54
|
+
/** True when this is actually a multi-destination order. */
|
|
55
|
+
isShared: boolean;
|
|
56
|
+
/** Flat handling fee, or 0. */
|
|
57
|
+
handlingFee: number;
|
|
58
|
+
/** Extra shipping for destinations beyond the first, or 0. */
|
|
59
|
+
extraShippingFee: number;
|
|
60
|
+
/** `handlingFee + extraShippingFee`, the figure added to the order total. */
|
|
61
|
+
total: number;
|
|
62
|
+
/**
|
|
63
|
+
* Set when the arrangement cannot be priced — the seller allows it but has
|
|
64
|
+
* not configured a rate.
|
|
65
|
+
*
|
|
66
|
+
* 🛑 The caller MUST act on this rather than treating the fee as ₹0. That is
|
|
67
|
+
* D44 exactly: `shippingUnavailable` existed with its own docstring saying
|
|
68
|
+
* "the caller MUST act on this rather than trusting `shippingFee: 0`", had
|
|
69
|
+
* ZERO consumers, and an `estimate`-mode store shipped free on every path as
|
|
70
|
+
* a result. An unpriced arrangement must be refused, not given away.
|
|
71
|
+
*/
|
|
72
|
+
unpriced?: "handling" | "extra-shipping";
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Price a shared-purchase arrangement for ONE order group.
|
|
76
|
+
*
|
|
77
|
+
* Pure: no settings read, no Firestore, no clock. The rates come in, the
|
|
78
|
+
* figure comes out, and the caller snapshots it.
|
|
79
|
+
*/
|
|
80
|
+
export declare function quoteSharedPurchase(input: SharedPurchaseQuoteInput): SharedPurchaseQuote;
|
|
81
|
+
/**
|
|
82
|
+
* Distinct destinations across a set of lines.
|
|
83
|
+
*
|
|
84
|
+
* 🛑 Lines with NO `destinationAddressId` collapse into one bucket — the
|
|
85
|
+
* order's own address — rather than counting as a destination each. Otherwise
|
|
86
|
+
* an ordinary three-item order would look like three destinations and be
|
|
87
|
+
* charged two extra legs.
|
|
88
|
+
*/
|
|
89
|
+
export declare function countDestinations(lines: readonly {
|
|
90
|
+
destinationAddressId?: string;
|
|
91
|
+
}[]): number;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared purchase, S5a — one payer, N destinations.
|
|
3
|
+
*
|
|
4
|
+
* ## What this is, and what it deliberately is not
|
|
5
|
+
*
|
|
6
|
+
* The coordinator buys everything and assigns a destination per item, so one
|
|
7
|
+
* paid order becomes N parcels. That is **~20% of the shared-purchase work for
|
|
8
|
+
* most of its value** (the plan's own assessment), because it needs none of
|
|
9
|
+
* the hard parts: no multi-party payment, no coordinator deposit, no approval
|
|
10
|
+
* cascade, no per-buyer invoicing, no per-buyer GST, and no stock-reservation
|
|
11
|
+
* primitive.
|
|
12
|
+
*
|
|
13
|
+
* 🛑 It is NOT S5b. There is exactly ONE payer and ONE place of supply, which
|
|
14
|
+
* is what keeps the tax, the invoice and the refund path unchanged. The moment
|
|
15
|
+
* a second person pays, every one of those becomes per-buyer and the design is
|
|
16
|
+
* a different design.
|
|
17
|
+
*
|
|
18
|
+
* ## 🛑 Why the fees are SNAPSHOTTED, and the precedent that proves it
|
|
19
|
+
*
|
|
20
|
+
* A coordinator commits to a quoted total. If the handling fee or the
|
|
21
|
+
* per-destination shipping is re-read at settle, a seller can raise it after
|
|
22
|
+
* the quote and the coordinator absorbs the difference.
|
|
23
|
+
*
|
|
24
|
+
* The codebase already has this exact hole and this exact fix: the refund path
|
|
25
|
+
* reads `finalSale` from the ORDER snapshot rather than the live product,
|
|
26
|
+
* because — in its own words — "re-reading the live product would let a seller
|
|
27
|
+
* flip the switch afterwards". D61 generalises it: *any agreement with a gap
|
|
28
|
+
* between "terms agreed" and "money taken" must snapshot its terms at
|
|
29
|
+
* agreement.*
|
|
30
|
+
*
|
|
31
|
+
* So `quoteSharedPurchase` takes the rates as ARGUMENTS and the caller
|
|
32
|
+
* snapshots the result onto the order. There is no settings read in here.
|
|
33
|
+
*/
|
|
34
|
+
import { roundRupees } from "../../../../utils/number.formatter.js";
|
|
35
|
+
/**
|
|
36
|
+
* Price a shared-purchase arrangement for ONE order group.
|
|
37
|
+
*
|
|
38
|
+
* Pure: no settings read, no Firestore, no clock. The rates come in, the
|
|
39
|
+
* figure comes out, and the caller snapshots it.
|
|
40
|
+
*/
|
|
41
|
+
export function quoteSharedPurchase(input) {
|
|
42
|
+
const none = {
|
|
43
|
+
isShared: false,
|
|
44
|
+
handlingFee: 0,
|
|
45
|
+
extraShippingFee: 0,
|
|
46
|
+
total: 0,
|
|
47
|
+
};
|
|
48
|
+
/*
|
|
49
|
+
* One destination is an ordinary order. Checked BEFORE the allow flag, so a
|
|
50
|
+
* seller who has not opted in is unaffected by this code path entirely —
|
|
51
|
+
* rather than being refused an order they were always able to take.
|
|
52
|
+
*/
|
|
53
|
+
const destinations = Math.max(1, Math.floor(input.destinationCount));
|
|
54
|
+
if (destinations <= 1)
|
|
55
|
+
return none;
|
|
56
|
+
if (input.rates.allowSharedPurchase !== true) {
|
|
57
|
+
/*
|
|
58
|
+
* 🛑 Returns `isShared: false` rather than throwing, and the CALLER
|
|
59
|
+
* refuses. This function prices; it does not authorise. A thrown error
|
|
60
|
+
* here would be caught by a preview path and rendered as a broken page,
|
|
61
|
+
* where the correct behaviour is to tell the buyer this seller does not
|
|
62
|
+
* split orders.
|
|
63
|
+
*/
|
|
64
|
+
return none;
|
|
65
|
+
}
|
|
66
|
+
const handling = input.rates.sharedPurchaseHandlingFee;
|
|
67
|
+
const perExtra = input.rates.sharedPurchaseExtraShippingPerDestination;
|
|
68
|
+
/*
|
|
69
|
+
* 🛑 `undefined` is UNPRICED, not zero — see the `unpriced` field. A seller
|
|
70
|
+
* who opted in without setting a rate has not agreed to do it for free, and
|
|
71
|
+
* charging ₹0 would hand them the extra labour at their own cost silently.
|
|
72
|
+
*/
|
|
73
|
+
if (typeof handling !== "number" || !Number.isFinite(handling)) {
|
|
74
|
+
return { ...none, isShared: true, unpriced: "handling" };
|
|
75
|
+
}
|
|
76
|
+
if (typeof perExtra !== "number" || !Number.isFinite(perExtra)) {
|
|
77
|
+
return { ...none, isShared: true, unpriced: "extra-shipping" };
|
|
78
|
+
}
|
|
79
|
+
const handlingFee = roundRupees(Math.max(0, handling));
|
|
80
|
+
// BEYOND THE FIRST: the first destination's shipping is what the order would
|
|
81
|
+
// have paid anyway, so only the marginal legs are charged here.
|
|
82
|
+
const extraShippingFee = roundRupees(Math.max(0, perExtra) * (destinations - 1));
|
|
83
|
+
return {
|
|
84
|
+
isShared: true,
|
|
85
|
+
handlingFee,
|
|
86
|
+
extraShippingFee,
|
|
87
|
+
total: roundRupees(handlingFee + extraShippingFee),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Distinct destinations across a set of lines.
|
|
92
|
+
*
|
|
93
|
+
* 🛑 Lines with NO `destinationAddressId` collapse into one bucket — the
|
|
94
|
+
* order's own address — rather than counting as a destination each. Otherwise
|
|
95
|
+
* an ordinary three-item order would look like three destinations and be
|
|
96
|
+
* charged two extra legs.
|
|
97
|
+
*/
|
|
98
|
+
export function countDestinations(lines) {
|
|
99
|
+
const ids = new Set();
|
|
100
|
+
let usesOrderAddress = false;
|
|
101
|
+
for (const line of lines) {
|
|
102
|
+
if (line.destinationAddressId)
|
|
103
|
+
ids.add(line.destinationAddressId);
|
|
104
|
+
else
|
|
105
|
+
usesOrderAddress = true;
|
|
106
|
+
}
|
|
107
|
+
return ids.size + (usesOrderAddress ? 1 : 0);
|
|
108
|
+
}
|
|
@@ -42,6 +42,17 @@ export interface PublicSiteSettings {
|
|
|
42
42
|
chat?: {
|
|
43
43
|
enabled: boolean;
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Is the support desk reachable — read by `UserLayoutClient`, a client
|
|
47
|
+
* component. Narrowed to the one boolean rather than passed by reference,
|
|
48
|
+
* so a field added to the document's `support` group later is not published
|
|
49
|
+
* by accident; `PUBLIC_SITE_SETTINGS_FIELDS` carries the same caveat beside
|
|
50
|
+
* its entry. Optional here because the group is absent on a settings
|
|
51
|
+
* document written before it existed.
|
|
52
|
+
*/
|
|
53
|
+
support?: {
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
};
|
|
45
56
|
/**
|
|
46
57
|
* The browser-side search index kill switch. **ON by default** — the
|
|
47
58
|
* projection emits `indexEnabled: doc.search?.indexEnabled !== false`, so
|
|
@@ -79,6 +79,17 @@ export interface PayoutDeduction {
|
|
|
79
79
|
* plus ₹80 postage" is a sentence; one net figure is not.
|
|
80
80
|
*/
|
|
81
81
|
shippingReimbursement: number;
|
|
82
|
+
/**
|
|
83
|
+
* Rupee value of buyer points this order was settled with, deducted from
|
|
84
|
+
* `netAmount` (D46/D47, decisions 3 and 17 — the accepting store funds the
|
|
85
|
+
* redemption).
|
|
86
|
+
*
|
|
87
|
+
* Returned as its own figure rather than folded into `totalDeduction` for the
|
|
88
|
+
* same reason `shippingReimbursement` is: *"'₹900 less ₹30 commission plus
|
|
89
|
+
* ₹80 postage' is a sentence; one net figure is not."* A seller looking at a
|
|
90
|
+
* short payout needs to see which of the four terms shortened it.
|
|
91
|
+
*/
|
|
92
|
+
pointsFunded: number;
|
|
82
93
|
}
|
|
83
94
|
/**
|
|
84
95
|
* What a seller is actually paid.
|
|
@@ -101,8 +112,29 @@ export interface PayoutDeduction {
|
|
|
101
112
|
*
|
|
102
113
|
* Defaulted to 0, so the three call sites that do not yet pass it keep their
|
|
103
114
|
* current behaviour exactly rather than silently gaining a term.
|
|
115
|
+
*
|
|
116
|
+
* ## 🛑 `pointsFunded` is the mirror of that, and it is a DEDUCTION
|
|
117
|
+
*
|
|
118
|
+
* Decisions 3 and 17: the programme is seller-funded at both ends — the
|
|
119
|
+
* accepting store absorbs the redemption, the originating store bears the
|
|
120
|
+
* earn — and the platform funds neither. So points spent on an order reduce
|
|
121
|
+
* that order's payout by the rupee value spent.
|
|
122
|
+
*
|
|
123
|
+
* It sits in the SAME position as postage and for the inverse reason: outside
|
|
124
|
+
* the commission and gateway bases. The seller agreed to fund a discount, not
|
|
125
|
+
* to pay us a commission on the discount they funded, and a gateway percentage
|
|
126
|
+
* on money no gateway touched is not a card fee at all.
|
|
127
|
+
*
|
|
128
|
+
* 🛑 So `grossAmount` is untouched and the fee figures are IDENTICAL whether
|
|
129
|
+
* points were used or not. That is what keeps the commission the seller is
|
|
130
|
+
* charged a function of the sale rather than of how the buyer chose to settle
|
|
131
|
+
* it — and it is why this cannot be implemented by passing a smaller
|
|
132
|
+
* `grossAmount`, which is the obvious shortcut and silently reduces our own
|
|
133
|
+
* commission by 2% of every redemption.
|
|
134
|
+
*
|
|
135
|
+
* Also defaulted to 0, for the same reason.
|
|
104
136
|
*/
|
|
105
|
-
export declare function computePayoutDeduction(grossAmount: number, commissions: FeeCommissionRates, shippingReimbursement?: number): PayoutDeduction;
|
|
137
|
+
export declare function computePayoutDeduction(grossAmount: number, commissions: FeeCommissionRates, shippingReimbursement?: number, pointsFunded?: number): PayoutDeduction;
|
|
106
138
|
export interface CodHandlingFeeRates {
|
|
107
139
|
/**
|
|
108
140
|
* Rupee CEILING on the COD handling fee. Optional so a `siteSettings`
|
|
@@ -114,8 +114,29 @@ export function allocateCheckoutFees(groupSubtotals, fees) {
|
|
|
114
114
|
*
|
|
115
115
|
* Defaulted to 0, so the three call sites that do not yet pass it keep their
|
|
116
116
|
* current behaviour exactly rather than silently gaining a term.
|
|
117
|
+
*
|
|
118
|
+
* ## 🛑 `pointsFunded` is the mirror of that, and it is a DEDUCTION
|
|
119
|
+
*
|
|
120
|
+
* Decisions 3 and 17: the programme is seller-funded at both ends — the
|
|
121
|
+
* accepting store absorbs the redemption, the originating store bears the
|
|
122
|
+
* earn — and the platform funds neither. So points spent on an order reduce
|
|
123
|
+
* that order's payout by the rupee value spent.
|
|
124
|
+
*
|
|
125
|
+
* It sits in the SAME position as postage and for the inverse reason: outside
|
|
126
|
+
* the commission and gateway bases. The seller agreed to fund a discount, not
|
|
127
|
+
* to pay us a commission on the discount they funded, and a gateway percentage
|
|
128
|
+
* on money no gateway touched is not a card fee at all.
|
|
129
|
+
*
|
|
130
|
+
* 🛑 So `grossAmount` is untouched and the fee figures are IDENTICAL whether
|
|
131
|
+
* points were used or not. That is what keeps the commission the seller is
|
|
132
|
+
* charged a function of the sale rather than of how the buyer chose to settle
|
|
133
|
+
* it — and it is why this cannot be implemented by passing a smaller
|
|
134
|
+
* `grossAmount`, which is the obvious shortcut and silently reduces our own
|
|
135
|
+
* commission by 2% of every redemption.
|
|
136
|
+
*
|
|
137
|
+
* Also defaulted to 0, for the same reason.
|
|
117
138
|
*/
|
|
118
|
-
export function computePayoutDeduction(grossAmount, commissions, shippingReimbursement = 0) {
|
|
139
|
+
export function computePayoutDeduction(grossAmount, commissions, shippingReimbursement = 0, pointsFunded = 0) {
|
|
119
140
|
const platformFee = roundRupees(grossAmount * (commissions.platformFeePercent / 100));
|
|
120
141
|
const gatewayFee = roundRupees(grossAmount * ((commissions.gatewayFeePercent ?? 0) / 100));
|
|
121
142
|
const gstOnFee = roundRupees(platformFee * (commissions.gstPercent / 100));
|
|
@@ -123,7 +144,15 @@ export function computePayoutDeduction(grossAmount, commissions, shippingReimbur
|
|
|
123
144
|
const postage = Number.isFinite(shippingReimbursement)
|
|
124
145
|
? Math.max(0, shippingReimbursement)
|
|
125
146
|
: 0;
|
|
126
|
-
const
|
|
147
|
+
const points = Number.isFinite(pointsFunded) ? Math.max(0, pointsFunded) : 0;
|
|
148
|
+
/*
|
|
149
|
+
* Clamped at zero, as it already was. A redemption cannot exceed 50% of the
|
|
150
|
+
* order total, so it cannot plausibly exceed the net — but a floor here means
|
|
151
|
+
* no caller can ever hand a seller a negative payout, which would be written
|
|
152
|
+
* to a `PayoutDocument.amount` that every downstream sum then treats as a
|
|
153
|
+
* credit against the platform.
|
|
154
|
+
*/
|
|
155
|
+
const netAmount = Math.max(0, roundRupees(grossAmount - totalDeduction + postage - points));
|
|
127
156
|
return {
|
|
128
157
|
platformFee,
|
|
129
158
|
gatewayFee,
|
|
@@ -131,6 +160,7 @@ export function computePayoutDeduction(grossAmount, commissions, shippingReimbur
|
|
|
131
160
|
totalDeduction,
|
|
132
161
|
netAmount,
|
|
133
162
|
shippingReimbursement: roundRupees(postage),
|
|
163
|
+
pointsFunded: roundRupees(points),
|
|
134
164
|
};
|
|
135
165
|
}
|
|
136
166
|
/** Buyer-facing COD handling ceiling, in decimal rupees. */
|
package/dist/client.d.ts
CHANGED
|
@@ -283,6 +283,8 @@ export { useLogout, useLogin, useGoogleLogin, useLinkGoogleAccount, useRegister,
|
|
|
283
283
|
export type { LoginCredentials, RegisterData, ForgotPasswordData, ResetPasswordData, VerifyEmailData, ChangeEmailData } from "./features/auth/index";
|
|
284
284
|
export { CartView, CartItemRow, CartGroupLineRow, CartSummary, CartPriceBreakdown, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
|
|
285
285
|
export { StoreAddonsPicker, hasAnyStoreAddon } from "./features/cart/index";
|
|
286
|
+
export { PointsTenderPicker } from "./features/cart/index";
|
|
287
|
+
export type { PointsTenderPickerProps } from "./features/cart/index";
|
|
286
288
|
export { clientLineTotal } from "./features/cart/utils/line-total";
|
|
287
289
|
export type { PricedCartLine } from "./features/cart/utils/line-total";
|
|
288
290
|
export type { CartPriceBreakdownData, CartPriceBreakdownStore, StoreAddonsValue, StoreAddonsRates } from "./features/cart/index";
|
|
@@ -389,6 +391,11 @@ export type { UserAccountHubViewProps, UserAccountHubViewLabels, UserOrdersViewP
|
|
|
389
391
|
export { useOrders, useOrder, OrdersList } from "./features/orders/index";
|
|
390
392
|
export { OrderStatusTimeline } from "./features/orders/components/OrderStatusTimeline";
|
|
391
393
|
export type { OrderStatusTimelineProps } from "./features/orders/components/OrderStatusTimeline";
|
|
394
|
+
export { OrderProvenanceCard } from "./features/orders/components/OrderProvenanceCard";
|
|
395
|
+
export type { OrderProvenanceCardProps } from "./features/orders/components/OrderProvenanceCard";
|
|
396
|
+
export { RaiseDisputeForm } from "./features/orders/components/RaiseDisputeForm";
|
|
397
|
+
export type { RaiseDisputeFormProps } from "./features/orders/components/RaiseDisputeForm";
|
|
398
|
+
export type { OrderSourceContext } from "./features/orders/schemas/firestore";
|
|
392
399
|
export { PROTECTION_CLAIM_REASON_LABEL } from "./features/protection-claims/schemas/firestore";
|
|
393
400
|
export type { ProtectionClaimReason } from "./features/protection-claims/schemas/firestore";
|
|
394
401
|
export { OrderCompletionPanel } from "./features/orders/components/OrderCompletionPanel";
|
|
@@ -586,6 +593,10 @@ export type { SellerSupportViewProps } from "./features/seller/components/Seller
|
|
|
586
593
|
export { REFUND_COPY } from "./_internal/shared/features/orders/refund-copy";
|
|
587
594
|
export { RETURN_REASON, FINAL_SALE_EXEMPT_REASONS, RETURN_REASON_LABEL, RETURN_REASON_GROUP_LABEL, isFinalSaleExempt, isReturnReason, selectableReturnReasons, } from "./_internal/shared/features/orders/return-reasons";
|
|
588
595
|
export type { ReturnReason } from "./_internal/shared/features/orders/return-reasons";
|
|
596
|
+
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
|
+
export type { DisputeKind, DisputeGateOrder, DisputeGateResult, } from "./_internal/shared/features/orders/dispute-reasons";
|
|
598
|
+
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";
|
|
589
600
|
export { RefundRequestView } from "./features/orders/components/RefundRequestView";
|
|
590
601
|
export type { RefundRequestViewProps, RefundRequestSubmission, } from "./features/orders/components/RefundRequestView";
|
|
591
602
|
export { ORDER_RETURN_WINDOW_DAYS, ORDER_RETURN_WINDOW_MS, ORDER_CANCELLABLE_STATUSES, } from "./_internal/shared/features/orders/config";
|