@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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Order } from "../types";
|
|
2
|
+
export interface BalancePaymentPanelProps {
|
|
3
|
+
order: Pick<Order, "id" | "balance" | "currency">;
|
|
4
|
+
/** Uploads one file and resolves to the stored `/media/…` slug. */
|
|
5
|
+
onUpload: (file: File) => Promise<string>;
|
|
6
|
+
/** POSTs to `/api/orders/{id}/balance-proof`. Throws on failure. */
|
|
7
|
+
onSubmit: (input: {
|
|
8
|
+
proofUrl: string;
|
|
9
|
+
transactionId: string;
|
|
10
|
+
}) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function BalancePaymentPanel({ order, onUpload, onSubmit }: BalancePaymentPanelProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* D55 — the buyer's panel for paying a pre-order balance.
|
|
5
|
+
*
|
|
6
|
+
* ## 🛑 This exists because the figure was already on screen
|
|
7
|
+
*
|
|
8
|
+
* `OrderProvenanceCard` has printed "Balance due ₹X" on every pre-order since
|
|
9
|
+
* Part 2, and nothing in the product could accept that money. A buyer who
|
|
10
|
+
* reads an amount owed and finds no control does not conclude "the feature is
|
|
11
|
+
* coming" — they conclude the site is broken, and the seller waiting on the
|
|
12
|
+
* money has no way to chase it.
|
|
13
|
+
*
|
|
14
|
+
* ## Why it is NOT a second page
|
|
15
|
+
*
|
|
16
|
+
* The deposit's proof upload is its own route (`/user/orders/[id]/payment`)
|
|
17
|
+
* because it runs against a 15-minute window with a countdown, a UPI address
|
|
18
|
+
* to copy and a fraud declaration — a task with its own screen. The balance
|
|
19
|
+
* has a 7-day window and no countdown, so sending the buyer somewhere else to
|
|
20
|
+
* fill in two fields would add a navigation and a back button for nothing.
|
|
21
|
+
*
|
|
22
|
+
* ## The four states
|
|
23
|
+
*
|
|
24
|
+
* | `balance.status` | What the buyer sees |
|
|
25
|
+
* |---|---|
|
|
26
|
+
* | absent | NOTHING — the seller has not opened collection; the goods do not exist yet |
|
|
27
|
+
* | `awaiting` / `rejected` | the form, with the rejection note when there is one |
|
|
28
|
+
* | `submitted` | "under review", no form |
|
|
29
|
+
* | `paid` | a confirmation line and the reference |
|
|
30
|
+
*
|
|
31
|
+
* 🛑 The absent case rendering nothing is the load-bearing one. A pre-order's
|
|
32
|
+
* premise is that the item has not been made yet, so a payable-looking balance
|
|
33
|
+
* before the seller opens it would be asking the buyer to fund inventory.
|
|
34
|
+
*/
|
|
35
|
+
import { useState } from "react";
|
|
36
|
+
import { Button } from "../../../ui/components/Button.js";
|
|
37
|
+
import { Row, Stack } from "../../../ui/components/Layout.js";
|
|
38
|
+
import { Div } from "../../../ui/components/Div.js";
|
|
39
|
+
import { Text, Span } from "../../../ui/components/Typography.js";
|
|
40
|
+
import { Form } from "../../../ui/components/Form.js";
|
|
41
|
+
import { FieldInput } from "../../../ui/forms/FieldInput.js";
|
|
42
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary.js";
|
|
43
|
+
import { MediaUploadField } from "../../media/upload/MediaUploadField.js";
|
|
44
|
+
import { balanceProofSchema } from "../schemas/balance.js";
|
|
45
|
+
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
46
|
+
function dueText(dueBy) {
|
|
47
|
+
if (!dueBy)
|
|
48
|
+
return "";
|
|
49
|
+
const d = new Date(dueBy);
|
|
50
|
+
if (Number.isNaN(d.getTime()))
|
|
51
|
+
return "";
|
|
52
|
+
return ` Due by ${d.toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" })}.`;
|
|
53
|
+
}
|
|
54
|
+
export function BalancePaymentPanel({ order, onUpload, onSubmit }) {
|
|
55
|
+
const [proofUrl, setProofUrl] = useState("");
|
|
56
|
+
const [transactionId, setTransactionId] = useState("");
|
|
57
|
+
const [isPending, setIsPending] = useState(false);
|
|
58
|
+
const balance = order.balance;
|
|
59
|
+
// Nothing to collect yet — see the header. Not an empty card, no card.
|
|
60
|
+
if (!balance)
|
|
61
|
+
return null;
|
|
62
|
+
const amount = formatCurrency(balance.amount, order.currency);
|
|
63
|
+
if (balance.status === "paid") {
|
|
64
|
+
return (_jsxs(Div, { surface: "card", padding: "md", className: "mt-4", children: [_jsx(Span, { layout: "inline-flex", color: "success", surface: "success-surface", weight: "semibold", className: "text-[10px]", rounded: "full", padding: "pill-xs", children: "Balance paid" }), _jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [amount, " received. This order is ready to ship.", balance.transactionId ? ` Reference: ${balance.transactionId}` : ""] })] }));
|
|
65
|
+
}
|
|
66
|
+
if (balance.status === "submitted") {
|
|
67
|
+
return (_jsxs(Div, { surface: "card", padding: "md", className: "mt-4", children: [_jsx(Span, { layout: "inline-flex", color: "info", surface: "info-surface", weight: "semibold", className: "text-[10px]", rounded: "full", padding: "pill-xs", children: "Balance under review" }), _jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: ["We have your proof for ", amount, ". Our team checks it against the reference you gave, and the order ships once it clears."] })] }));
|
|
68
|
+
}
|
|
69
|
+
const rejected = balance.status === "rejected";
|
|
70
|
+
const handleSubmit = async () => {
|
|
71
|
+
setIsPending(true);
|
|
72
|
+
try {
|
|
73
|
+
await onSubmit({ proofUrl, transactionId: transactionId.trim() });
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
/*
|
|
77
|
+
* Cleared even on failure. The caller surfaces the error; leaving the
|
|
78
|
+
* button spinning forever is how a recoverable 400 reads as a hang.
|
|
79
|
+
*/
|
|
80
|
+
setIsPending(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return (_jsx(Div, { surface: "card", padding: "md", className: "mt-4", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsx(Span, { layout: "inline-flex", color: rejected ? "error" : "warning", surface: rejected ? "danger-surface" : "warning-surface", weight: "semibold", className: "text-[10px]", rounded: "full", padding: "pill-xs", children: rejected ? "Balance proof needs correction" : "Balance due" }), _jsx(Text, { className: "mt-1", color: "muted", size: "xs", children: rejected
|
|
84
|
+
? /*
|
|
85
|
+
* The note is REQUIRED on a rejection server-side, so the
|
|
86
|
+
* fallback here should effectively never render — it exists
|
|
87
|
+
* because a row written before that rule would otherwise say
|
|
88
|
+
* nothing at all, which is the one thing a rejection must not do.
|
|
89
|
+
*/
|
|
90
|
+
balance.reviewNote
|
|
91
|
+
? `Our team could not accept the last proof: ${balance.reviewNote}`
|
|
92
|
+
: "Our team could not accept the last proof. Please send a clearer screenshot."
|
|
93
|
+
: `Your item is ready. Pay the remaining ${amount} and upload the receipt.${dueText(balance.dueBy)}` })] }), _jsx(Form, { schema: balanceProofSchema, onSubmit: (e) => e.preventDefault(), children: () => (_jsxs(Stack, { gap: "md", children: [_jsx(FormErrorSummary, {}), _jsx(MediaUploadField, { label: "Payment screenshot (JPG/PNG/PDF)", value: proofUrl, onChange: setProofUrl, onUpload: onUpload, kind: "image", accept: "image/*,application/pdf", showYoutube: false, showExternal: false, helperText: "The confirmation screen from your bank or UPI app." }), _jsx(FieldInput, { name: "transactionId", label: "UTR / Transaction reference", required: true, value: transactionId, onChange: (value) => setTransactionId(value), placeholder: "e.g. 123456789012", hint: "Required \u2014 it is the only thing our team can check the payment against. A screenshot on its own is a picture." }), _jsx(Row, { gap: "3", children: _jsx(Button, { type: "button", variant: "primary", disabled: isPending || !proofUrl || transactionId.trim().length < 4, onClick: handleSubmit, paddingX: "md", paddingY: "sm", textSize: "sm", weight: "semibold", children: isPending ? "Sending…" : rejected ? "Re-send proof" : "Submit payment proof" }) })] })) })] }) }));
|
|
94
|
+
}
|
|
@@ -28,6 +28,12 @@ import { Alert, Button, Div, FieldTextarea, Form, Stack, Text, useToast } from "
|
|
|
28
28
|
import { PaginatedSelect } from "../../../ui/components/PaginatedSelect.js";
|
|
29
29
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
30
30
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
31
|
+
/*
|
|
32
|
+
* 🛑 From the DEFINING module, not the `@mohasinac/appkit/client` barrel. This
|
|
33
|
+
* is a `"use client"` component so either resolves, but a barrel import drags
|
|
34
|
+
* the whole client entry's graph in for one symbol (Root Cause #18).
|
|
35
|
+
*/
|
|
36
|
+
import { apiClient } from "../../../http/ApiClient.js";
|
|
31
37
|
export function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, }) {
|
|
32
38
|
const { showToast } = useToast();
|
|
33
39
|
const [kind, setKind] = useState(null);
|
|
@@ -55,17 +61,19 @@ export function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, })
|
|
|
55
61
|
const loadReasons = useCallback(async (query, page) => {
|
|
56
62
|
if (!kind)
|
|
57
63
|
return { items: [], hasMore: false };
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
/*
|
|
65
|
+
* `apiClient`, not a bare `fetch` — it carries the credentials and the
|
|
66
|
+
* error shape every other call in this feature uses, and the bare form
|
|
67
|
+
* is what `audit-form-mutation-hook` blocks inside a <Form>-rendering
|
|
68
|
+
* file. Same shape as `YourPurchaseStrip`'s read, one folder over.
|
|
69
|
+
*
|
|
70
|
+
* It THROWS on a non-2xx, which is the behaviour this call site already
|
|
71
|
+
* wanted: surfaced, not swallowed into an empty list. An empty picker is
|
|
72
|
+
* indistinguishable from "there are no reasons", and the buyer would sit
|
|
73
|
+
* on a required field with nothing in it (Root Cause #59).
|
|
74
|
+
*/
|
|
75
|
+
const json = await apiClient.get(`${reasonsEndpoint}?kind=${encodeURIComponent(kind)}`);
|
|
76
|
+
const rows = json?.data?.items ?? [];
|
|
69
77
|
const q = query.trim().toLowerCase();
|
|
70
78
|
const filtered = q ? rows.filter((r) => r.label.toLowerCase().includes(q)) : rows;
|
|
71
79
|
return {
|
|
@@ -5,6 +5,7 @@ import { BaseRepository, parseSieveDateValue } from "../../../providers/db-fireb
|
|
|
5
5
|
import { type OrderCreateInput, type OrderDocument, type OrderPlain, type OrderRefundEvent } from "../schemas";
|
|
6
6
|
import { type HistoryActor, type FieldChange } from "../../../_internal/shared/history/index";
|
|
7
7
|
import type { OrderStatus, PaymentStatus } from "../types";
|
|
8
|
+
import type { OrderType } from "../utils/order-splitter";
|
|
8
9
|
import { type PaymentReviewQueueMode } from "../constants/payment-window";
|
|
9
10
|
/**
|
|
10
11
|
* Who/why for a write that lands in `statusHistory`.
|
|
@@ -64,7 +65,7 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
64
65
|
* encryption lived in a private method only three call sites remembered to
|
|
65
66
|
* call.
|
|
66
67
|
*/
|
|
67
|
-
protected piiFields: readonly ["userName", "userEmail", "sellerEmail"];
|
|
68
|
+
protected piiFields: readonly ["userName", "userEmail", "sellerEmail", "deliveryFullName", "deliveryPhone", "deliveryAddressLine1", "deliveryAddressLine2", "deliveryLandmark", "deliveryAmendments[].fullName", "deliveryAmendments[].phone", "deliveryAmendments[].addressLine1", "deliveryAmendments[].addressLine2", "deliveryAmendments[].landmark"];
|
|
68
69
|
/** Plaintext PII. **Opt-in and named** — shows up in review and in a grep. */
|
|
69
70
|
revealOrder(doc: OrderDocument): OrderPlain;
|
|
70
71
|
/** As `revealOrder`, for a list read. */
|
|
@@ -144,6 +145,20 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
144
145
|
* `order.bundleId` at creation time (SB3 / SB6-C).
|
|
145
146
|
*/
|
|
146
147
|
countByUserAndBundle(userId: string, bundleId: string): Promise<number>;
|
|
148
|
+
/**
|
|
149
|
+
* How many orders this buyer has placed, optionally scoped to one lane.
|
|
150
|
+
*
|
|
151
|
+
* A Firestore aggregation — billed at one read per 1000 index entries
|
|
152
|
+
* matched, not one per order — so it stays flat as an account's history
|
|
153
|
+
* grows. That matters because the only caller is the session profile
|
|
154
|
+
* endpoint, which every page load hits.
|
|
155
|
+
*
|
|
156
|
+
* 🛑 `orderType` may only name a NON-standard lane. The field was added
|
|
157
|
+
* after orders already existed, so an equality on `"standard"` would return
|
|
158
|
+
* zero for every legacy order while looking perfectly correct — see the
|
|
159
|
+
* constant's own note.
|
|
160
|
+
*/
|
|
161
|
+
countForUser(userId: string, orderType?: Exclude<OrderType, "standard">): Promise<number>;
|
|
147
162
|
deleteByUser(userId: string): Promise<number>;
|
|
148
163
|
static readonly SELLER_SIEVE_FIELDS: {
|
|
149
164
|
id: {
|
|
@@ -564,6 +579,18 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
564
579
|
*/
|
|
565
580
|
getEligibleAutomatic(cutoff: Date, opts?: {
|
|
566
581
|
onWarn?: (msg: string, meta?: Record<string, JsonValue>) => void;
|
|
582
|
+
/**
|
|
583
|
+
* Stores paid at SHIPPING rather than at delivery.
|
|
584
|
+
*
|
|
585
|
+
* 🛑 A faster path through the SAME query, not a second one. A `shipped`
|
|
586
|
+
* row is admitted only for a store in this set, and then skips the hold
|
|
587
|
+
* anchor — being paid before delivery is the entire grant, so applying a
|
|
588
|
+
* post-delivery hold to it would make the flag do nothing.
|
|
589
|
+
*
|
|
590
|
+
* Omitted or empty means nobody is trusted, which is the correct default
|
|
591
|
+
* and reproduces the pre-existing behaviour exactly.
|
|
592
|
+
*/
|
|
593
|
+
trustedStoreIds?: ReadonlySet<string>;
|
|
567
594
|
}): Promise<Array<{
|
|
568
595
|
id: string;
|
|
569
596
|
ref: DocumentReference;
|
|
@@ -9,7 +9,7 @@ import { normalizeError } from "../../../errors/normalize.js";
|
|
|
9
9
|
* that mix-up swallowing ~100 call sites' validation messages.
|
|
10
10
|
*/
|
|
11
11
|
import { DatabaseError, NotFoundError, ValidationError } from "../../../errors/index.js";
|
|
12
|
-
import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase/index.js";
|
|
12
|
+
import { BaseRepository, prepareForFirestore, parseSieveDateValue, getFirestoreCount, } from "../../../providers/db-firebase/index.js";
|
|
13
13
|
import { maskPii, revealPii, ORDER_PII_FIELDS, } from "../../../security/index.js";
|
|
14
14
|
import { serverTimestamp, arrayUnion } from "../../../contracts/field-ops.js";
|
|
15
15
|
import { createOrderId, ORDER_COLLECTION, ORDER_TRACKED_FIELDS, OrderStatusValues, } from "../schemas/index.js";
|
|
@@ -304,10 +304,11 @@ class OrderRepository extends BaseRepository {
|
|
|
304
304
|
reason: ctx?.reason,
|
|
305
305
|
note: ctx?.note,
|
|
306
306
|
extraChanges: ctx?.extraChanges,
|
|
307
|
-
// `encryptPiiFields`
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
307
|
+
// `encryptPiiFields` covers a field only where it is NAMED, so a
|
|
308
|
+
// PII-named value reaching `changes` is plaintext unless somebody
|
|
309
|
+
// declared `statusHistory[].changes.<that key>` — which nobody will.
|
|
310
|
+
// The scrub removes the guesswork. None of ORDER_TRACKED_FIELDS is PII
|
|
311
|
+
// today; this is defence for the next field somebody adds.
|
|
311
312
|
piiFields: ORDER_PII_FIELDS,
|
|
312
313
|
historyField: "statusHistory",
|
|
313
314
|
truncatedField: "statusHistoryTruncated",
|
|
@@ -516,6 +517,26 @@ class OrderRepository extends BaseRepository {
|
|
|
516
517
|
.get();
|
|
517
518
|
return snapshot.size;
|
|
518
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* How many orders this buyer has placed, optionally scoped to one lane.
|
|
522
|
+
*
|
|
523
|
+
* A Firestore aggregation — billed at one read per 1000 index entries
|
|
524
|
+
* matched, not one per order — so it stays flat as an account's history
|
|
525
|
+
* grows. That matters because the only caller is the session profile
|
|
526
|
+
* endpoint, which every page load hits.
|
|
527
|
+
*
|
|
528
|
+
* 🛑 `orderType` may only name a NON-standard lane. The field was added
|
|
529
|
+
* after orders already existed, so an equality on `"standard"` would return
|
|
530
|
+
* zero for every legacy order while looking perfectly correct — see the
|
|
531
|
+
* constant's own note.
|
|
532
|
+
*/
|
|
533
|
+
async countForUser(userId, orderType) {
|
|
534
|
+
let query = this.getCollection().where(ORDER_FIELDS.USER_ID, "==", userId);
|
|
535
|
+
if (orderType) {
|
|
536
|
+
query = query.where(ORDER_FIELDS.ORDER_TYPE, "==", orderType);
|
|
537
|
+
}
|
|
538
|
+
return getFirestoreCount(query);
|
|
539
|
+
}
|
|
519
540
|
async deleteByUser(userId) {
|
|
520
541
|
try {
|
|
521
542
|
const snapshot = await this.getCollection()
|
|
@@ -1139,10 +1160,23 @@ class OrderRepository extends BaseRepository {
|
|
|
1139
1160
|
* and the anchor test is a per-row predicate below. One query, no new
|
|
1140
1161
|
* composite index, and no way to silently return nothing.
|
|
1141
1162
|
*/
|
|
1163
|
+
const trusted = opts?.trustedStoreIds ?? new Set();
|
|
1142
1164
|
const snap = await this.db
|
|
1143
1165
|
.collection(this.collection)
|
|
1144
1166
|
.where(ORDER_FIELDS.PAYOUT_STATUS, "==", "eligible")
|
|
1145
|
-
|
|
1167
|
+
/*
|
|
1168
|
+
* 🛑 Widened to an `in` ONLY when some store is trusted.
|
|
1169
|
+
*
|
|
1170
|
+
* Firestore's `in` is an OR of equalities, so the existing
|
|
1171
|
+
* `(payoutStatus, status)` composite serves both forms and no new index
|
|
1172
|
+
* is needed. Keeping the plain equality when the set is empty matters
|
|
1173
|
+
* anyway: a deployment with no trusted sellers then runs byte-identically
|
|
1174
|
+
* to before, so the flag cannot change behaviour for anyone who has not
|
|
1175
|
+
* been granted it.
|
|
1176
|
+
*/
|
|
1177
|
+
.where(ORDER_FIELDS.STATUS, ...(trusted.size > 0
|
|
1178
|
+
? ["in", [OrderStatusValues.DELIVERED, OrderStatusValues.SHIPPED]]
|
|
1179
|
+
: ["==", OrderStatusValues.DELIVERED]))
|
|
1146
1180
|
.limit(500)
|
|
1147
1181
|
.get();
|
|
1148
1182
|
let missingAnchor = 0;
|
|
@@ -1153,6 +1187,26 @@ class OrderRepository extends BaseRepository {
|
|
|
1153
1187
|
data: { id: d.id, ...d.data() },
|
|
1154
1188
|
}))
|
|
1155
1189
|
.filter((o) => !o.data.emiEnabled || o.data.emiComplete)
|
|
1190
|
+
.filter((o) => {
|
|
1191
|
+
/*
|
|
1192
|
+
* 🛑 A SHIPPED row is admitted only for a TRUSTED store, and then
|
|
1193
|
+
* bypasses the hold entirely — paying at shipping is the grant.
|
|
1194
|
+
*
|
|
1195
|
+
* Tested before the anchor rather than inside it, because a shipped
|
|
1196
|
+
* order has no `deliveryDate` and would fall through to the
|
|
1197
|
+
* `updatedAt` fallback — which is always LATER than delivery would
|
|
1198
|
+
* have been. A trusted seller would be paid more SLOWLY than an
|
|
1199
|
+
* untrusted one: the flag would read as working and do the opposite.
|
|
1200
|
+
*
|
|
1201
|
+
* The `in` above cannot express "shipped, but only for these stores",
|
|
1202
|
+
* so the store test has to live here — otherwise one trusted seller
|
|
1203
|
+
* anywhere would widen the sweep for every seller.
|
|
1204
|
+
*/
|
|
1205
|
+
if (o.data.status === OrderStatusValues.SHIPPED) {
|
|
1206
|
+
return typeof o.data.storeId === "string" && trusted.has(o.data.storeId);
|
|
1207
|
+
}
|
|
1208
|
+
return true;
|
|
1209
|
+
})
|
|
1156
1210
|
.filter((o) => {
|
|
1157
1211
|
/*
|
|
1158
1212
|
* The anchor, with a conservative fallback. `deliveryDate` is the
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D55 — write shapes for the pre-order balance collector.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 Both are `.strict()`, and that is the Root Cause #76 lesson rather than
|
|
5
|
+
* tidiness: the balance block carries `status`, `reviewedBy`, `reviewedAt`
|
|
6
|
+
* and `amount`, and a write shape that can NAME a field is a write shape that
|
|
7
|
+
* can destroy it. A buyer must not be able to send `status: "paid"` or
|
|
8
|
+
* `amount: 1`, and `z.object()` merely STRIPS unknown keys — which is the
|
|
9
|
+
* same silent-success failure as the grouped-listings PATCH. `.strict()`
|
|
10
|
+
* makes it a 400 instead.
|
|
11
|
+
*
|
|
12
|
+
* Neither schema is where `amount` comes from. It is snapshotted from
|
|
13
|
+
* `sourceContext.balanceDue` when the seller opens collection, months before
|
|
14
|
+
* this, and is never re-read from the live product (D61).
|
|
15
|
+
*/
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
/** The buyer's proof of payment. */
|
|
18
|
+
export declare const balanceProofSchema: z.ZodObject<{
|
|
19
|
+
proofUrl: z.ZodString;
|
|
20
|
+
transactionId: z.ZodString;
|
|
21
|
+
}, "strict", z.ZodTypeAny, {
|
|
22
|
+
transactionId: string;
|
|
23
|
+
proofUrl: string;
|
|
24
|
+
}, {
|
|
25
|
+
transactionId: string;
|
|
26
|
+
proofUrl: string;
|
|
27
|
+
}>;
|
|
28
|
+
export type BalanceProofInput = z.infer<typeof balanceProofSchema>;
|
|
29
|
+
/** The admin's verdict on a submitted proof. */
|
|
30
|
+
export declare const balanceReviewSchema: z.ZodObject<{
|
|
31
|
+
approve: z.ZodBoolean;
|
|
32
|
+
note: z.ZodOptional<z.ZodString>;
|
|
33
|
+
}, "strict", z.ZodTypeAny, {
|
|
34
|
+
approve: boolean;
|
|
35
|
+
note?: string | undefined;
|
|
36
|
+
}, {
|
|
37
|
+
approve: boolean;
|
|
38
|
+
note?: string | undefined;
|
|
39
|
+
}>;
|
|
40
|
+
export type BalanceReviewInput = z.infer<typeof balanceReviewSchema>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D55 — write shapes for the pre-order balance collector.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 Both are `.strict()`, and that is the Root Cause #76 lesson rather than
|
|
5
|
+
* tidiness: the balance block carries `status`, `reviewedBy`, `reviewedAt`
|
|
6
|
+
* and `amount`, and a write shape that can NAME a field is a write shape that
|
|
7
|
+
* can destroy it. A buyer must not be able to send `status: "paid"` or
|
|
8
|
+
* `amount: 1`, and `z.object()` merely STRIPS unknown keys — which is the
|
|
9
|
+
* same silent-success failure as the grouped-listings PATCH. `.strict()`
|
|
10
|
+
* makes it a 400 instead.
|
|
11
|
+
*
|
|
12
|
+
* Neither schema is where `amount` comes from. It is snapshotted from
|
|
13
|
+
* `sourceContext.balanceDue` when the seller opens collection, months before
|
|
14
|
+
* this, and is never re-read from the live product (D61).
|
|
15
|
+
*/
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
/** The buyer's proof of payment. */
|
|
18
|
+
export const balanceProofSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
/*
|
|
21
|
+
* A `/media/…` slug from the signed-URL upload flow — bytes never reach
|
|
22
|
+
* this route (Rule #6's 4.5 MB payload cap, and § Media Architecture).
|
|
23
|
+
*/
|
|
24
|
+
proofUrl: z.string().min(1, "Upload a screenshot of the payment."),
|
|
25
|
+
/*
|
|
26
|
+
* The UTR or UPI reference. Required, because it is the only thing an
|
|
27
|
+
* admin can check the payment against — a screenshot alone is a picture.
|
|
28
|
+
*/
|
|
29
|
+
transactionId: z
|
|
30
|
+
.string()
|
|
31
|
+
.min(4, "Enter the transaction reference from your bank or UPI app.")
|
|
32
|
+
.max(64),
|
|
33
|
+
})
|
|
34
|
+
.strict();
|
|
35
|
+
/** The admin's verdict on a submitted proof. */
|
|
36
|
+
export const balanceReviewSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
approve: z.boolean(),
|
|
39
|
+
/*
|
|
40
|
+
* Optional HERE and required in `reviewBalanceProof` when `approve` is
|
|
41
|
+
* false. Deliberately not a `.refine()`: the rule belongs with the
|
|
42
|
+
* transition it guards, and duplicating it would give two places to
|
|
43
|
+
* disagree. An approval genuinely needs no note.
|
|
44
|
+
*/
|
|
45
|
+
note: z.string().max(300).optional(),
|
|
46
|
+
})
|
|
47
|
+
.strict();
|
|
@@ -174,6 +174,68 @@ export declare const RefundStatusValues: {
|
|
|
174
174
|
readonly REJECTED: "rejected";
|
|
175
175
|
};
|
|
176
176
|
/** Firestore storage shape for an order line item â€" distinct from the API display model OrderItem */
|
|
177
|
+
/**
|
|
178
|
+
* The PII inside one amendment.
|
|
179
|
+
*
|
|
180
|
+
* 🛑 BARE keys, and `ORDER_PII_FIELDS` uses the `deliveryAmendments[].…`
|
|
181
|
+
* form for the same five fields. Two notations, two scopes, both correct:
|
|
182
|
+
* the `[]` form is for the whole ORDER document, where the hook has to walk
|
|
183
|
+
* into the array; these bare names are for ONE amendment object, where they
|
|
184
|
+
* are genuinely top-level.
|
|
185
|
+
*
|
|
186
|
+
* 🛑 This list was first justified as "`encryptPiiFields` CANNOT cover an
|
|
187
|
+
* array" — CLAUDE.md's wording, and FALSE. `encryptInArray` has always
|
|
188
|
+
* handled `a[].b`, in both directions. The real rule is that the encryptor
|
|
189
|
+
* does not infer: a field is covered only where it is NAMED, in the notation
|
|
190
|
+
* matching the object the list is applied to.
|
|
191
|
+
*/
|
|
192
|
+
export declare const AMENDMENT_PII_FIELDS: readonly ["fullName", "phone", "addressLine1", "addressLine2", "landmark"];
|
|
193
|
+
/**
|
|
194
|
+
* A correction to the delivery address after the order was placed.
|
|
195
|
+
*
|
|
196
|
+
* 🛑 Carries the FULL new address, not a diff. A diff would make the current
|
|
197
|
+
* address reconstructable only by replaying every entry in order, so one
|
|
198
|
+
* malformed entry in the middle makes everything after it unreadable — and
|
|
199
|
+
* the parcel label is produced from the current address.
|
|
200
|
+
*
|
|
201
|
+
* PII in here is the same problem as everywhere else: `encryptPiiFields` does
|
|
202
|
+
* not descend into arrays, so these values are NOT encrypted by that
|
|
203
|
+
* mechanism. `scrubAmendmentPii` is what handles them; see its header.
|
|
204
|
+
*/
|
|
205
|
+
export interface DeliveryAmendment {
|
|
206
|
+
at: Date;
|
|
207
|
+
/** Who asked. `actorRole` distinguishes a buyer correction from a support fix. */
|
|
208
|
+
actorUid?: string;
|
|
209
|
+
actorRole: "buyer" | "seller" | "admin" | "system";
|
|
210
|
+
reason?: string;
|
|
211
|
+
fullName: string;
|
|
212
|
+
phone: string;
|
|
213
|
+
addressLine1: string;
|
|
214
|
+
addressLine2?: string;
|
|
215
|
+
landmark?: string;
|
|
216
|
+
city: string;
|
|
217
|
+
state: string;
|
|
218
|
+
pincode: string;
|
|
219
|
+
country: string;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* How much seller prose a single order line carries.
|
|
223
|
+
*
|
|
224
|
+
* 🛑 A Firestore document caps at 1 MiB and `items[]` is inline on the order.
|
|
225
|
+
* 2,000 characters × 20 lines is ~40 KB of description — comfortable. The
|
|
226
|
+
* same field unbounded is a rejected write at checkout, and the checkout is
|
|
227
|
+
* the one operation in the system that must never fail for a reason the buyer
|
|
228
|
+
* cannot act on.
|
|
229
|
+
*/
|
|
230
|
+
export declare const ORDER_ITEM_DESCRIPTION_MAX = 2000;
|
|
231
|
+
/**
|
|
232
|
+
* How many spec rows one order line keeps.
|
|
233
|
+
*
|
|
234
|
+
* Capped by COUNT rather than characters: a spec table is rows, and half a
|
|
235
|
+
* row is worse than a missing one — "Weight: 4" with the unit cut off reads
|
|
236
|
+
* as a fact rather than as truncation.
|
|
237
|
+
*/
|
|
238
|
+
export declare const ORDER_ITEM_SPECIFICATIONS_MAX = 30;
|
|
177
239
|
export interface OrderDocumentItem {
|
|
178
240
|
productId: string;
|
|
179
241
|
productTitle: string;
|
|
@@ -261,6 +323,37 @@ export interface OrderDocumentItem {
|
|
|
261
323
|
/** Which kind of multi-member line produced these rows. */
|
|
262
324
|
groupSource?: "bundle" | "product-group" | "grouped-listing";
|
|
263
325
|
/** P-8 GST — snapshotted from the product at order time so the invoice stays accurate even if the product's HSN/rate later changes. */
|
|
326
|
+
/**
|
|
327
|
+
* The listing description as it read at purchase.
|
|
328
|
+
*
|
|
329
|
+
* 🛑 BOUNDED at `ORDER_ITEM_DESCRIPTION_MAX`, with `descriptionTruncated`
|
|
330
|
+
* recording that it was cut. A Firestore document caps at 1 MiB and this is
|
|
331
|
+
* free-form seller prose on every line of every order — an unbounded copy
|
|
332
|
+
* on a 20-line order is a rejected write AT CHECKOUT, which fails the one
|
|
333
|
+
* operation that must not fail. Truncation is recorded rather than silent
|
|
334
|
+
* for the same reason `statusHistoryTruncated` exists: a short description
|
|
335
|
+
* and a cut one must not read alike.
|
|
336
|
+
*/
|
|
337
|
+
description?: string;
|
|
338
|
+
descriptionTruncated?: boolean;
|
|
339
|
+
/** The spec table as it read at purchase. */
|
|
340
|
+
specifications?: Array<{
|
|
341
|
+
name: string;
|
|
342
|
+
value: string;
|
|
343
|
+
unit?: string;
|
|
344
|
+
}>;
|
|
345
|
+
/**
|
|
346
|
+
* For a composed listing, the parts it was sold as containing.
|
|
347
|
+
*
|
|
348
|
+
* Ids and names only — the part CATALOGUE is a live reference work and
|
|
349
|
+
* copying its entries onto every order line would duplicate a research
|
|
350
|
+
* corpus per purchase. What matters for a dispute is which parts were
|
|
351
|
+
* claimed, not their current catalogue entry.
|
|
352
|
+
*/
|
|
353
|
+
parts?: Array<{
|
|
354
|
+
code: string;
|
|
355
|
+
name: string;
|
|
356
|
+
}>;
|
|
264
357
|
hsnCode?: string;
|
|
265
358
|
gstRate?: 0 | 5 | 12 | 18 | 28;
|
|
266
359
|
/**
|
|
@@ -425,7 +518,64 @@ export interface OrderDocument extends BaseDocument {
|
|
|
425
518
|
paymentStatus: PaymentStatus;
|
|
426
519
|
paymentId?: string;
|
|
427
520
|
paymentMethod?: string;
|
|
521
|
+
/**
|
|
522
|
+
* 🛑 LEGACY. Never written for an order created after Part 14.
|
|
523
|
+
*
|
|
524
|
+
* Flat free text with no pincode, no phone and no address id — which is why
|
|
525
|
+
* `placeOfSupply` had to be written separately rather than derived, and why
|
|
526
|
+
* parcel booking could not be built at all (`CreateShipmentInput` needs a
|
|
527
|
+
* structured `{name, phone, addressLine1, city, state, pincode, country}`).
|
|
528
|
+
*
|
|
529
|
+
* It is ALSO in `ORDER_UPDATABLE_FIELDS`, so it is editable after the order
|
|
530
|
+
* — meaning it never recorded what the buyer actually chose. And it was
|
|
531
|
+
* never encrypted: `ORDER_PII_FIELDS` is `userName` / `userEmail` /
|
|
532
|
+
* `sellerEmail`, so every delivery address on every order up to now sits in
|
|
533
|
+
* Firestore in plaintext, despite the docs saying otherwise.
|
|
534
|
+
*
|
|
535
|
+
* Read it only as a fallback for orders that predate `deliveryAddress*`,
|
|
536
|
+
* and render it labelled as legacy. Forward-only; no backfill — parsing a
|
|
537
|
+
* state out of free text to file a tax return is the thing the
|
|
538
|
+
* `placeOfSupply` comment below already refuses.
|
|
539
|
+
*/
|
|
428
540
|
shippingAddress?: string;
|
|
541
|
+
/** PII — encrypted. See `ORDER_PII_FIELDS`. */
|
|
542
|
+
deliveryFullName?: Encrypted<string>;
|
|
543
|
+
/** PII — encrypted. */
|
|
544
|
+
deliveryPhone?: Encrypted<string>;
|
|
545
|
+
/** PII — encrypted. */
|
|
546
|
+
deliveryAddressLine1?: Encrypted<string>;
|
|
547
|
+
/** PII — encrypted. Optional second line / apartment. */
|
|
548
|
+
deliveryAddressLine2?: Encrypted<string>;
|
|
549
|
+
/** PII — encrypted. */
|
|
550
|
+
deliveryLandmark?: Encrypted<string>;
|
|
551
|
+
deliveryCity?: string;
|
|
552
|
+
deliveryState?: string;
|
|
553
|
+
deliveryPincode?: string;
|
|
554
|
+
deliveryCountry?: string;
|
|
555
|
+
/**
|
|
556
|
+
* The `addresses` row this was taken from, for provenance only.
|
|
557
|
+
*
|
|
558
|
+
* 🛑 Never resolved at read time. The row can be edited or deleted after the
|
|
559
|
+
* order, and following it would make the invoice disagree with what the
|
|
560
|
+
* buyer chose — which is the entire failure a snapshot exists to prevent.
|
|
561
|
+
*/
|
|
562
|
+
deliveryAddressId?: string;
|
|
563
|
+
/**
|
|
564
|
+
* Corrections after the fact, newest LAST. Append-only.
|
|
565
|
+
*
|
|
566
|
+
* 🛑 Its OWN array, not a passenger on `statusHistory`.
|
|
567
|
+
*
|
|
568
|
+
* `statusHistory` caps at 50 with FIFO eviction. Address edits riding that
|
|
569
|
+
* log would compete with status transitions for the same slots and evict
|
|
570
|
+
* the thing the timeline exists to show — and on a chatty order the
|
|
571
|
+
* creation entry goes first, which is precisely the value reconstruction
|
|
572
|
+
* depends on. The creation snapshot is a FIELD above for the same reason: a
|
|
573
|
+
* field cannot be truncated.
|
|
574
|
+
*
|
|
575
|
+
* Dispatch reads the latest amendment (or the snapshot when there is none).
|
|
576
|
+
* The invoice always reads the snapshot.
|
|
577
|
+
*/
|
|
578
|
+
deliveryAmendments?: DeliveryAmendment[];
|
|
429
579
|
trackingNumber?: string;
|
|
430
580
|
notes?: string;
|
|
431
581
|
orderDate: Date;
|
|
@@ -1130,6 +1280,58 @@ export interface OrderDocument extends BaseDocument {
|
|
|
1130
1280
|
/** Buyer's required "this payment is genuine, no fraudulent tricks/chargebacks" agreement — server-validated, not just a client-side disable. */
|
|
1131
1281
|
buyerFraudAgreementAccepted?: boolean;
|
|
1132
1282
|
buyerFraudAgreementAcceptedAt?: Date;
|
|
1283
|
+
/**
|
|
1284
|
+
* ── D55: the pre-order BALANCE, which is a second payment ──────────────
|
|
1285
|
+
*
|
|
1286
|
+
* 🛑 Its own block rather than reusing the fields above, and that is the
|
|
1287
|
+
* decision this whole feature turns on. Every field above describes ONE
|
|
1288
|
+
* payment; a pre-order has two, months apart. Reusing `paymentProofUrl`
|
|
1289
|
+
* for the balance overwrites the deposit's proof — the evidence for the
|
|
1290
|
+
* payment that created the order — and `paymentReviewOutcome` then means
|
|
1291
|
+
* two different things depending on when you read it.
|
|
1292
|
+
*
|
|
1293
|
+
* Worse, `paymentWindowTimeout` sweeps orders that are `pending`/`pending`
|
|
1294
|
+
* with no `paymentProofUrl`, so clearing that field to collect a balance
|
|
1295
|
+
* would make the sweep cancel and restock a PAID order.
|
|
1296
|
+
*
|
|
1297
|
+
* The balance is therefore its own small state machine beside the
|
|
1298
|
+
* deposit's, sharing the review vocabulary but none of the storage.
|
|
1299
|
+
*/
|
|
1300
|
+
balance?: {
|
|
1301
|
+
/**
|
|
1302
|
+
* 🛑 SNAPSHOTTED, not recomputed — D61, and the worst case in the
|
|
1303
|
+
* codebase for it: a deposit-to-balance gap is months. It is copied from
|
|
1304
|
+
* `sourceContext.balanceDue` at the moment the collection is opened, so
|
|
1305
|
+
* a price change, a tax-rate change or a re-quote in between cannot move
|
|
1306
|
+
* what the buyer was told they owe. Recomputing at payment time is how a
|
|
1307
|
+
* buyer who agreed to ₹4,000 is charged ₹4,400.
|
|
1308
|
+
*/
|
|
1309
|
+
amount: number;
|
|
1310
|
+
/**
|
|
1311
|
+
* `manual` only, today, and the field exists so the absence is explicit.
|
|
1312
|
+
*
|
|
1313
|
+
* 🛑 The AUTO path is not merely unbuilt — it is blocked on things
|
|
1314
|
+
* outside this repo. A months-long gap cannot use authorise-then-capture
|
|
1315
|
+
* (capture windows are days), so it needs a real RBI-compliant mandate:
|
|
1316
|
+
* AFA at creation, a pre-debit notice at least 24 HOURS before every
|
|
1317
|
+
* debit, and a max amount fixed at registration. Razorpay must also have
|
|
1318
|
+
* recurring enabled, and production runs TEST keys with
|
|
1319
|
+
* `razorpayEnabled` defaulting false. Declaring the union now means
|
|
1320
|
+
* adding `auto` later is a compile-checked change rather than a new
|
|
1321
|
+
* field nobody wires.
|
|
1322
|
+
*/
|
|
1323
|
+
method: "manual";
|
|
1324
|
+
status: "awaiting" | "submitted" | "paid" | "rejected";
|
|
1325
|
+
/** Set when the collection is opened, so the buyer has a stated window. */
|
|
1326
|
+
dueBy?: Date;
|
|
1327
|
+
proofUrl?: string;
|
|
1328
|
+
transactionId?: string;
|
|
1329
|
+
submittedAt?: Date;
|
|
1330
|
+
reviewedBy?: string;
|
|
1331
|
+
reviewedAt?: Date;
|
|
1332
|
+
/** Required on `rejected`, for the same reason a refund reason is. */
|
|
1333
|
+
reviewNote?: string;
|
|
1334
|
+
};
|
|
1133
1335
|
/** Which of the three admin review actions was taken on this order's payment proof. */
|
|
1134
1336
|
paymentReviewOutcome?: "approved" | "reupload_requested" | "rejected_fraud";
|
|
1135
1337
|
/** Admin's reason text — required for both `reupload_requested` and `rejected_fraud`. */
|