@mohasinac/appkit 4.62.0 → 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 +129 -9
- package/dist/_internal/server/features/checkout/actions.js +850 -79
- 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/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/offers/destinations.d.ts +40 -0
- package/dist/_internal/server/features/offers/destinations.js +65 -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 +54 -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/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/pricing/actions.d.ts +58 -0
- package/dist/_internal/server/features/pricing/actions.js +144 -0
- package/dist/_internal/server/features/refunds/actions.js +26 -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.d.ts +69 -0
- package/dist/_internal/server/features/shipping/courier-block.js +264 -0
- package/dist/_internal/server/features/site-settings/adapters.d.ts +2 -2
- package/dist/_internal/server/features/site-settings/adapters.js +29 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
- package/dist/_internal/server/features/stores/adapters.js +65 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +95 -2
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +11 -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/jobRunners.js +45 -0
- 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 +65 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
- 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/runJobOnce.d.ts +48 -0
- package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +25 -2
- 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/onOrderStatusChange.js +17 -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/server/notifications/channel-health.d.ts +26 -3
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +296 -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 +174 -0
- package/dist/_internal/shared/checkout/order-math.js +159 -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/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 +75 -0
- package/dist/_internal/shared/checkout/split.js +120 -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 +93 -0
- package/dist/_internal/shared/content/published-values.js +206 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +139 -0
- package/dist/_internal/shared/features/admin/integrations.js +263 -0
- 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/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/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 +29 -0
- package/dist/_internal/shared/features/points/accepts.js +49 -0
- 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/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/fees.d.ts +16 -8
- package/dist/_internal/shared/features/site-settings/fees.js +12 -8
- 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/_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 +21 -0
- package/dist/client.js +43 -0
- package/dist/constants/api-endpoints.d.ts +114 -0
- package/dist/constants/api-endpoints.js +55 -0
- package/dist/constants/field-names.d.ts +42 -0
- package/dist/constants/field-names.js +52 -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 +47 -35
- package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
- package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
- package/dist/features/admin/components/AdminIntegrationsView.js +74 -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 +33 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +81 -3
- package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
- package/dist/features/admin/components/AdminOrdersView.js +86 -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 +62 -0
- package/dist/features/admin/schemas/firestore.js +5 -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 +140 -0
- package/dist/features/audit-log/schemas/firestore.js +89 -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/permissions/constants.js +13 -0
- 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 +25 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +29 -6
- 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 +33 -0
- package/dist/features/cart/repository/cart.repository.js +47 -0
- package/dist/features/cart/schemas/firestore.d.ts +116 -0
- package/dist/features/contact/email.d.ts +27 -10
- package/dist/features/contact/email.js +20 -3
- 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 +62 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +108 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +62 -0
- package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
- package/dist/features/faq/components/FAQAccordion.js +29 -4
- 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/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/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/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/BalancePaymentPanel.d.ts +12 -0
- package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
- 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 +119 -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 +85 -1
- package/dist/features/orders/repository/orders.repository.js +205 -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 +398 -1
- package/dist/features/orders/schemas/firestore.js +43 -0
- package/dist/features/orders/schemas/index.d.ts +56 -8
- package/dist/features/orders/schemas/index.js +12 -0
- package/dist/features/orders/types/index.d.ts +47 -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/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/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/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/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/reviews/repository/reviews.repository.d.ts +14 -0
- package/dist/features/reviews/repository/reviews.repository.js +16 -0
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- 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/search/action-index/tab-entries.js +1 -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 +37 -3
- 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 +221 -27
- package/dist/features/seller/components/index.d.ts +1 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/seller/hooks/useSellerListingData.js +25 -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/shipments/schemas/validation.d.ts +4 -4
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +82 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +145 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +80 -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/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 +112 -0
- package/dist/features/stores/schemas/firestore.js +20 -0
- package/dist/features/stores/schemas/index.d.ts +6 -6
- package/dist/features/stores/types/index.d.ts +19 -0
- 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 +8 -8
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +33 -1
- package/dist/next/routing/route-map.d.ts +112 -0
- package/dist/next/routing/route-map.js +63 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
- package/dist/providers/shipping-shiprocket/index.js +84 -1
- package/dist/react/contexts/SessionContext.d.ts +17 -1
- package/dist/schemas/registry.d.ts +69 -34
- package/dist/schemas/sieve.d.ts +2 -2
- package/dist/security/pii-schemas.d.ts +1 -1
- package/dist/security/pii-schemas.js +45 -0
- package/dist/server-entry.d.ts +2 -2
- package/dist/server-entry.js +8 -1
- package/dist/server.d.ts +36 -1
- package/dist/server.js +183 -1
- package/dist/styles.css +412 -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/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.d.ts +19 -0
- package/dist/ui/components/Disclaimer.js +83 -0
- 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/tabs-overflow.js +64 -11
- 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 +8 -0
- package/dist/ui/index.js +9 -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 +83 -79
- 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 +1834 -1822
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issuing an invoice: allocate the number, render, store the bytes (Phase 7).
|
|
3
|
+
*
|
|
4
|
+
* ## 🛑 WHERE the PDF goes, and why it is not the media path
|
|
5
|
+
*
|
|
6
|
+
* `private/invoices/{orderId}/{uuid}.pdf`, with **no `mediaAssets` row** and a
|
|
7
|
+
* **random** object name. An invoice carries the buyer's full name, address and
|
|
8
|
+
* phone.
|
|
9
|
+
*
|
|
10
|
+
* Three facts, each of which alone would be enough:
|
|
11
|
+
*
|
|
12
|
+
* 1. `/api/media/[...slug]` applies **no authentication at all** — it is a
|
|
13
|
+
* deliberately bare handler whose job is watermarking public product
|
|
14
|
+
* photography.
|
|
15
|
+
* 2. The bucket's storage rule is `allow read: if true`.
|
|
16
|
+
* 3. Media filenames in this codebase are content-derived and therefore
|
|
17
|
+
* GUESSABLE — `generateInvoiceFilename` is `invoice-{orderId}-{YYYYMMDD}`.
|
|
18
|
+
*
|
|
19
|
+
* So the random name is against the SEO-slug convention everywhere else *on
|
|
20
|
+
* purpose*: guessable is the one property this must not have. And the `private/`
|
|
21
|
+
* prefix is refused by the proxy's raw-path fallback — a check, not a naming
|
|
22
|
+
* convention, which is precisely the distinction the digital-content comment
|
|
23
|
+
* got wrong for weeks.
|
|
24
|
+
*
|
|
25
|
+
* ## 🛑 Store the bytes; never re-render to serve or to email
|
|
26
|
+
*
|
|
27
|
+
* Two renders can disagree — a changed template, a different library version,
|
|
28
|
+
* a settings read that moved — and the buyer's copy must be byte-identical to
|
|
29
|
+
* ours. So the bytes are written once, at issue time, and every later consumer
|
|
30
|
+
* reads that object. On-demand rendering survives only as a fallback for orders
|
|
31
|
+
* that predate this phase and have no stored path.
|
|
32
|
+
*/
|
|
33
|
+
import type { OrderDocument } from "../../../../features/orders/schemas/firestore";
|
|
34
|
+
/** Storage prefix. Mirrors `private/digital-content/` — refused by the proxy. */
|
|
35
|
+
export declare const INVOICE_PREFIX_PATH = "private/invoices";
|
|
36
|
+
/**
|
|
37
|
+
* Renders the PDF for an order.
|
|
38
|
+
*
|
|
39
|
+
* Injected rather than imported, because the renderer lives in
|
|
40
|
+
* `functions/src/invoice-pdf.ts` — it pulls `pdfkit`, which is a Functions
|
|
41
|
+
* dependency and has no business in appkit's module graph (Rule #6's build
|
|
42
|
+
* weight, and Root Cause #18's import-chain trap).
|
|
43
|
+
*/
|
|
44
|
+
export type InvoiceRenderer = (order: OrderDocument) => Promise<Buffer>;
|
|
45
|
+
export interface IssueInvoiceResult {
|
|
46
|
+
invoiceNumber: string;
|
|
47
|
+
invoiceStoragePath: string;
|
|
48
|
+
/** False when the order already had an invoice — the normal re-entry case. */
|
|
49
|
+
issued: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Give a paid order its invoice number and its stored PDF.
|
|
53
|
+
*
|
|
54
|
+
* 🛑 Idempotent through `allocateSeriesNumber`'s transactional claim.
|
|
55
|
+
* `onOrderStatusChange` can fire more than once, and two numbers for one order
|
|
56
|
+
* is the single unrecoverable failure in this phase — a number cannot be
|
|
57
|
+
* withdrawn without leaving a gap.
|
|
58
|
+
*
|
|
59
|
+
* 🛑 Called on `paymentStatus → paid`, never at creation. COD and manual orders
|
|
60
|
+
* are created `pending`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function issueInvoiceForOrder(order: OrderDocument, render: InvoiceRenderer): Promise<IssueInvoiceResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Persist bytes rendered on demand, for an order that is already NUMBERED.
|
|
65
|
+
*
|
|
66
|
+
* ## Why the lazy path exists at all
|
|
67
|
+
*
|
|
68
|
+
* Numbering happens in `onOrderStatusChange`, which runs in appkit and cannot
|
|
69
|
+
* render — `pdfkit` is a Functions dependency and bringing it into appkit's
|
|
70
|
+
* module graph would add it to every consumer build (Rule #6's build weight,
|
|
71
|
+
* Root Cause #18's import chain). So the two halves are split: the number is
|
|
72
|
+
* allocated immediately and transactionally, the bytes are produced the first
|
|
73
|
+
* time anyone asks for them and then stored forever.
|
|
74
|
+
*
|
|
75
|
+
* That split is a deliberate trade and worth naming: the first download of a
|
|
76
|
+
* given invoice pays a render, and every later one does not. The alternative —
|
|
77
|
+
* a second Firestore trigger on `orders` living in `functions/` — is the shape
|
|
78
|
+
* that burned a million invocations a day in Root Cause #92.
|
|
79
|
+
*
|
|
80
|
+
* 🛑 Refuses to overwrite an existing path. A second numbered copy of one
|
|
81
|
+
* invoice is two documents claiming one number, and the one a buyer already
|
|
82
|
+
* downloaded is the one that counts.
|
|
83
|
+
*
|
|
84
|
+
* 🛑 Refuses an un-numbered order. Storing a PDF before the number exists
|
|
85
|
+
* would freeze a pre-payment snapshot as "the invoice", and the real issuance
|
|
86
|
+
* would then find a path already set.
|
|
87
|
+
*/
|
|
88
|
+
export declare function storeRenderedInvoice(orderId: string, bytes: Buffer): Promise<string | null>;
|
|
89
|
+
/**
|
|
90
|
+
* The stored bytes for an already-issued invoice, or null.
|
|
91
|
+
*
|
|
92
|
+
* Returns null rather than throwing on a missing object, because the caller's
|
|
93
|
+
* correct response is to fall back to an on-demand render for a pre-Phase-7
|
|
94
|
+
* order — and a throw would turn a legacy order's invoice page into an error.
|
|
95
|
+
*/
|
|
96
|
+
export declare function readStoredInvoice(storagePath: string | undefined): Promise<Buffer | null>;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issuing an invoice: allocate the number, render, store the bytes (Phase 7).
|
|
3
|
+
*
|
|
4
|
+
* ## 🛑 WHERE the PDF goes, and why it is not the media path
|
|
5
|
+
*
|
|
6
|
+
* `private/invoices/{orderId}/{uuid}.pdf`, with **no `mediaAssets` row** and a
|
|
7
|
+
* **random** object name. An invoice carries the buyer's full name, address and
|
|
8
|
+
* phone.
|
|
9
|
+
*
|
|
10
|
+
* Three facts, each of which alone would be enough:
|
|
11
|
+
*
|
|
12
|
+
* 1. `/api/media/[...slug]` applies **no authentication at all** — it is a
|
|
13
|
+
* deliberately bare handler whose job is watermarking public product
|
|
14
|
+
* photography.
|
|
15
|
+
* 2. The bucket's storage rule is `allow read: if true`.
|
|
16
|
+
* 3. Media filenames in this codebase are content-derived and therefore
|
|
17
|
+
* GUESSABLE — `generateInvoiceFilename` is `invoice-{orderId}-{YYYYMMDD}`.
|
|
18
|
+
*
|
|
19
|
+
* So the random name is against the SEO-slug convention everywhere else *on
|
|
20
|
+
* purpose*: guessable is the one property this must not have. And the `private/`
|
|
21
|
+
* prefix is refused by the proxy's raw-path fallback — a check, not a naming
|
|
22
|
+
* convention, which is precisely the distinction the digital-content comment
|
|
23
|
+
* got wrong for weeks.
|
|
24
|
+
*
|
|
25
|
+
* ## 🛑 Store the bytes; never re-render to serve or to email
|
|
26
|
+
*
|
|
27
|
+
* Two renders can disagree — a changed template, a different library version,
|
|
28
|
+
* a settings read that moved — and the buyer's copy must be byte-identical to
|
|
29
|
+
* ours. So the bytes are written once, at issue time, and every later consumer
|
|
30
|
+
* reads that object. On-demand rendering survives only as a fallback for orders
|
|
31
|
+
* that predate this phase and have no stored path.
|
|
32
|
+
*/
|
|
33
|
+
import { randomUUID } from "node:crypto";
|
|
34
|
+
import { getAdminStorage } from "../../../../providers/db-firebase/index.js";
|
|
35
|
+
import { orderRepository } from "../../../../features/orders/repository/orders.repository.js";
|
|
36
|
+
import { serverLogger } from "../../../../monitoring/index.js";
|
|
37
|
+
import { normalizeError } from "../../../../errors/normalize.js";
|
|
38
|
+
import { allocateSeriesNumber } from "./allocate.js";
|
|
39
|
+
/** Storage prefix. Mirrors `private/digital-content/` — refused by the proxy. */
|
|
40
|
+
export const INVOICE_PREFIX_PATH = "private/invoices";
|
|
41
|
+
/**
|
|
42
|
+
* Give a paid order its invoice number and its stored PDF.
|
|
43
|
+
*
|
|
44
|
+
* 🛑 Idempotent through `allocateSeriesNumber`'s transactional claim.
|
|
45
|
+
* `onOrderStatusChange` can fire more than once, and two numbers for one order
|
|
46
|
+
* is the single unrecoverable failure in this phase — a number cannot be
|
|
47
|
+
* withdrawn without leaving a gap.
|
|
48
|
+
*
|
|
49
|
+
* 🛑 Called on `paymentStatus → paid`, never at creation. COD and manual orders
|
|
50
|
+
* are created `pending`.
|
|
51
|
+
*/
|
|
52
|
+
export async function issueInvoiceForOrder(order, render) {
|
|
53
|
+
/*
|
|
54
|
+
* The fast path, and the common one: a second trigger on an order that is
|
|
55
|
+
* already invoiced does no work and touches no counter. Checked before the
|
|
56
|
+
* transaction purely to save the round trip — the transaction would reach
|
|
57
|
+
* the same answer, which is what makes this safe rather than a race.
|
|
58
|
+
*/
|
|
59
|
+
if (order.invoiceNumber && order.invoiceStoragePath) {
|
|
60
|
+
return {
|
|
61
|
+
invoiceNumber: order.invoiceNumber,
|
|
62
|
+
invoiceStoragePath: order.invoiceStoragePath,
|
|
63
|
+
issued: false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const issuedAt = new Date();
|
|
67
|
+
const allocation = await allocateSeriesNumber({
|
|
68
|
+
kind: "invoice",
|
|
69
|
+
subjectId: order.id,
|
|
70
|
+
orderId: order.id,
|
|
71
|
+
at: issuedAt,
|
|
72
|
+
});
|
|
73
|
+
/*
|
|
74
|
+
* 🛑 `randomUUID()`, and the ORDER ID is in the directory rather than the
|
|
75
|
+
* filename. The directory makes an operator's life possible (every document
|
|
76
|
+
* for one order is in one place); the random leaf is what makes the object
|
|
77
|
+
* unguessable. Putting the order id in the FILENAME would undo the second
|
|
78
|
+
* without improving the first.
|
|
79
|
+
*/
|
|
80
|
+
const storagePath = `${INVOICE_PREFIX_PATH}/${order.id}/${randomUUID()}.pdf`;
|
|
81
|
+
/*
|
|
82
|
+
* Rendered with the number already on the order object, so the PDF carries
|
|
83
|
+
* the number it was allocated — the renderer reads `order.invoiceNumber`,
|
|
84
|
+
* and handing it the un-numbered document would produce an invoice with no
|
|
85
|
+
* number printed on it while the database said otherwise.
|
|
86
|
+
*/
|
|
87
|
+
const buffer = await render({
|
|
88
|
+
...order,
|
|
89
|
+
invoiceNumber: allocation.number,
|
|
90
|
+
invoiceIssuedAt: issuedAt,
|
|
91
|
+
});
|
|
92
|
+
await getAdminStorage()
|
|
93
|
+
.bucket()
|
|
94
|
+
.file(storagePath)
|
|
95
|
+
.save(buffer, {
|
|
96
|
+
contentType: "application/pdf",
|
|
97
|
+
/*
|
|
98
|
+
* 🛑 `private, no-store` on the OBJECT as well as on every response that
|
|
99
|
+
* serves it. The object is not reachable without a signed URL or the
|
|
100
|
+
* Admin SDK, but an intermediate cache that ever saw one must not keep
|
|
101
|
+
* it: an invoice is one buyer's address, not a product photo.
|
|
102
|
+
*/
|
|
103
|
+
metadata: { cacheControl: "private, no-store" },
|
|
104
|
+
resumable: false,
|
|
105
|
+
});
|
|
106
|
+
/*
|
|
107
|
+
* 🛑 The order is updated AFTER the bytes exist. The reverse ordering would
|
|
108
|
+
* leave an order claiming an `invoiceStoragePath` that resolves to nothing,
|
|
109
|
+
* and the serve route would then 404 on a document the buyer is entitled to
|
|
110
|
+
* — while the number is already consumed, so retrying cannot re-issue it.
|
|
111
|
+
*
|
|
112
|
+
* A crash between the save and this update leaves an unreferenced object
|
|
113
|
+
* (a few KB) and an order with a number but no path, which the next trigger
|
|
114
|
+
* re-renders into a NEW object. Wasteful and harmless, which is the right
|
|
115
|
+
* side of this trade.
|
|
116
|
+
*/
|
|
117
|
+
await orderRepository.update(order.id, {
|
|
118
|
+
invoiceNumber: allocation.number,
|
|
119
|
+
invoiceIssuedAt: issuedAt,
|
|
120
|
+
invoiceStoragePath: storagePath,
|
|
121
|
+
});
|
|
122
|
+
serverLogger.info("[invoicing] invoice issued", {
|
|
123
|
+
orderId: order.id,
|
|
124
|
+
invoiceNumber: allocation.number,
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
invoiceNumber: allocation.number,
|
|
128
|
+
invoiceStoragePath: storagePath,
|
|
129
|
+
issued: allocation.allocated,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Persist bytes rendered on demand, for an order that is already NUMBERED.
|
|
134
|
+
*
|
|
135
|
+
* ## Why the lazy path exists at all
|
|
136
|
+
*
|
|
137
|
+
* Numbering happens in `onOrderStatusChange`, which runs in appkit and cannot
|
|
138
|
+
* render — `pdfkit` is a Functions dependency and bringing it into appkit's
|
|
139
|
+
* module graph would add it to every consumer build (Rule #6's build weight,
|
|
140
|
+
* Root Cause #18's import chain). So the two halves are split: the number is
|
|
141
|
+
* allocated immediately and transactionally, the bytes are produced the first
|
|
142
|
+
* time anyone asks for them and then stored forever.
|
|
143
|
+
*
|
|
144
|
+
* That split is a deliberate trade and worth naming: the first download of a
|
|
145
|
+
* given invoice pays a render, and every later one does not. The alternative —
|
|
146
|
+
* a second Firestore trigger on `orders` living in `functions/` — is the shape
|
|
147
|
+
* that burned a million invocations a day in Root Cause #92.
|
|
148
|
+
*
|
|
149
|
+
* 🛑 Refuses to overwrite an existing path. A second numbered copy of one
|
|
150
|
+
* invoice is two documents claiming one number, and the one a buyer already
|
|
151
|
+
* downloaded is the one that counts.
|
|
152
|
+
*
|
|
153
|
+
* 🛑 Refuses an un-numbered order. Storing a PDF before the number exists
|
|
154
|
+
* would freeze a pre-payment snapshot as "the invoice", and the real issuance
|
|
155
|
+
* would then find a path already set.
|
|
156
|
+
*/
|
|
157
|
+
export async function storeRenderedInvoice(orderId, bytes) {
|
|
158
|
+
const order = await orderRepository.findById(orderId);
|
|
159
|
+
if (!order?.invoiceNumber)
|
|
160
|
+
return null;
|
|
161
|
+
if (order.invoiceStoragePath)
|
|
162
|
+
return order.invoiceStoragePath;
|
|
163
|
+
const storagePath = `${INVOICE_PREFIX_PATH}/${orderId}/${randomUUID()}.pdf`;
|
|
164
|
+
await getAdminStorage()
|
|
165
|
+
.bucket()
|
|
166
|
+
.file(storagePath)
|
|
167
|
+
.save(bytes, {
|
|
168
|
+
contentType: "application/pdf",
|
|
169
|
+
metadata: { cacheControl: "private, no-store" },
|
|
170
|
+
resumable: false,
|
|
171
|
+
});
|
|
172
|
+
// Path written only after the bytes exist — the reverse would point at
|
|
173
|
+
// nothing and 404 a document the buyer is entitled to.
|
|
174
|
+
await orderRepository.update(orderId, { invoiceStoragePath: storagePath });
|
|
175
|
+
serverLogger.info("[invoicing] invoice bytes stored on first download", {
|
|
176
|
+
orderId,
|
|
177
|
+
invoiceNumber: order.invoiceNumber,
|
|
178
|
+
});
|
|
179
|
+
return storagePath;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The stored bytes for an already-issued invoice, or null.
|
|
183
|
+
*
|
|
184
|
+
* Returns null rather than throwing on a missing object, because the caller's
|
|
185
|
+
* correct response is to fall back to an on-demand render for a pre-Phase-7
|
|
186
|
+
* order — and a throw would turn a legacy order's invoice page into an error.
|
|
187
|
+
*/
|
|
188
|
+
export async function readStoredInvoice(storagePath) {
|
|
189
|
+
if (!storagePath)
|
|
190
|
+
return null;
|
|
191
|
+
try {
|
|
192
|
+
const [buffer] = await getAdminStorage().bucket().file(storagePath).download();
|
|
193
|
+
return buffer;
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
serverLogger.warn("[invoicing] stored invoice unreadable — falling back to render", {
|
|
197
|
+
storagePath,
|
|
198
|
+
error: normalizeError(err).message,
|
|
199
|
+
});
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side verification that a coordinated purchase's destinations are real
|
|
3
|
+
* and belong to the people naming them.
|
|
4
|
+
*
|
|
5
|
+
* ## 🛑 Why this file exists at all
|
|
6
|
+
*
|
|
7
|
+
* `CoordinationMember.destinationAddressId` is a REFERENCE, which is the right
|
|
8
|
+
* shape — copying the address inline would put a name, a phone number and a
|
|
9
|
+
* street into an offer document with no encryption story and no retention
|
|
10
|
+
* story. But a reference has a failure mode a copy does not:
|
|
11
|
+
*
|
|
12
|
+
* **An id is guessable in a way an address is not.** Without this check a
|
|
13
|
+
* member supplies any `addresses` document id and the parcel ships to a
|
|
14
|
+
* stranger's house, at the coordinator's expense, with every other part of
|
|
15
|
+
* the flow behaving correctly. Nothing errors; the order is valid; the goods
|
|
16
|
+
* simply go somewhere nobody chose.
|
|
17
|
+
*
|
|
18
|
+
* So the rule is: **an id that crosses the wire is never trusted, and is
|
|
19
|
+
* resolved against its claimed owner before anything is written.**
|
|
20
|
+
*/
|
|
21
|
+
import type { CoordinationMember } from "../../../shared/features/offers/coordination";
|
|
22
|
+
/**
|
|
23
|
+
* Every named destination must exist and belong to the member who named it.
|
|
24
|
+
*
|
|
25
|
+
* 🛑 **Owned by THAT MEMBER, not merely by someone.** Checking only that the
|
|
26
|
+
* address exists and has `ownerType: "user"` would let a member ship to any
|
|
27
|
+
* other user's saved address — the weaker check reads as a real one and
|
|
28
|
+
* closes nothing.
|
|
29
|
+
*
|
|
30
|
+
* 🛑 **Fails the whole request, naming the member.** Dropping a bad
|
|
31
|
+
* destination and shipping that member's parcel to the coordinator instead
|
|
32
|
+
* would be a silent substitution of somebody's delivery address, which is
|
|
33
|
+
* exactly the class of quiet wrongness this check exists to prevent. Grouped
|
|
34
|
+
* cart lines take the same position for the same reason.
|
|
35
|
+
*
|
|
36
|
+
* One `getAll` over the distinct ids, not one read per member: five members
|
|
37
|
+
* sharing two destinations is two reads, and Rule #6's budget is per request
|
|
38
|
+
* rather than per member.
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertOwnedAddresses(members: readonly CoordinationMember[]): Promise<void>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side verification that a coordinated purchase's destinations are real
|
|
3
|
+
* and belong to the people naming them.
|
|
4
|
+
*
|
|
5
|
+
* ## 🛑 Why this file exists at all
|
|
6
|
+
*
|
|
7
|
+
* `CoordinationMember.destinationAddressId` is a REFERENCE, which is the right
|
|
8
|
+
* shape — copying the address inline would put a name, a phone number and a
|
|
9
|
+
* street into an offer document with no encryption story and no retention
|
|
10
|
+
* story. But a reference has a failure mode a copy does not:
|
|
11
|
+
*
|
|
12
|
+
* **An id is guessable in a way an address is not.** Without this check a
|
|
13
|
+
* member supplies any `addresses` document id and the parcel ships to a
|
|
14
|
+
* stranger's house, at the coordinator's expense, with every other part of
|
|
15
|
+
* the flow behaving correctly. Nothing errors; the order is valid; the goods
|
|
16
|
+
* simply go somewhere nobody chose.
|
|
17
|
+
*
|
|
18
|
+
* So the rule is: **an id that crosses the wire is never trusted, and is
|
|
19
|
+
* resolved against its claimed owner before anything is written.**
|
|
20
|
+
*/
|
|
21
|
+
import { addressesRepository } from "../../../../features/addresses/repository/addresses.repository.js";
|
|
22
|
+
import { ValidationError } from "../../../../errors/validation-error.js";
|
|
23
|
+
/**
|
|
24
|
+
* Every named destination must exist and belong to the member who named it.
|
|
25
|
+
*
|
|
26
|
+
* 🛑 **Owned by THAT MEMBER, not merely by someone.** Checking only that the
|
|
27
|
+
* address exists and has `ownerType: "user"` would let a member ship to any
|
|
28
|
+
* other user's saved address — the weaker check reads as a real one and
|
|
29
|
+
* closes nothing.
|
|
30
|
+
*
|
|
31
|
+
* 🛑 **Fails the whole request, naming the member.** Dropping a bad
|
|
32
|
+
* destination and shipping that member's parcel to the coordinator instead
|
|
33
|
+
* would be a silent substitution of somebody's delivery address, which is
|
|
34
|
+
* exactly the class of quiet wrongness this check exists to prevent. Grouped
|
|
35
|
+
* cart lines take the same position for the same reason.
|
|
36
|
+
*
|
|
37
|
+
* One `getAll` over the distinct ids, not one read per member: five members
|
|
38
|
+
* sharing two destinations is two reads, and Rule #6's budget is per request
|
|
39
|
+
* rather than per member.
|
|
40
|
+
*/
|
|
41
|
+
export async function assertOwnedAddresses(members) {
|
|
42
|
+
const named = members.filter((m) => !!m.destinationAddressId);
|
|
43
|
+
if (named.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
const ids = [...new Set(named.map((m) => m.destinationAddressId))];
|
|
46
|
+
const docs = await Promise.all(ids.map((id) => addressesRepository.findById(id)));
|
|
47
|
+
const byId = new Map(ids.map((id, i) => [id, docs[i]]));
|
|
48
|
+
for (const member of named) {
|
|
49
|
+
const address = byId.get(member.destinationAddressId);
|
|
50
|
+
/*
|
|
51
|
+
* 🛑 ONE message for "does not exist" and "is not yours".
|
|
52
|
+
*
|
|
53
|
+
* Distinguishing them turns this endpoint into an oracle for which
|
|
54
|
+
* `addresses` ids are real — a caller probes ids and reads existence off
|
|
55
|
+
* the error text. The member who typed a genuinely stale id and the
|
|
56
|
+
* member probing somebody else's get the same answer, and only the first
|
|
57
|
+
* of those has a legitimate next step anyway (pick again).
|
|
58
|
+
*/
|
|
59
|
+
if (!address || address.ownerType !== "user" || address.ownerId !== member.uid) {
|
|
60
|
+
throw new ValidationError("A chosen delivery address is not available.", {
|
|
61
|
+
destinationAddressId: `${member.uid}: that address cannot be used.`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ActionResult } from "../../../shared/types/action-result";
|
|
2
|
+
import { type DisputeKind } from "../../../shared/features/orders/dispute-reasons";
|
|
2
3
|
export declare function createOrderAction(input: unknown): Promise<ActionResult<unknown>>;
|
|
3
4
|
/**
|
|
4
5
|
* Cancel an order, and give the money back if any was taken (D14).
|
|
@@ -58,7 +59,13 @@ export declare function adminRejectPaymentAsFraudAction(orderId: string, note: s
|
|
|
58
59
|
* the seller (store owner), or admin/moderator. Does not itself reverse
|
|
59
60
|
* payment/order status — surfaces to the admin queue for investigation.
|
|
60
61
|
*/
|
|
61
|
-
export declare function raiseOrderDisputeAction(orderId: string,
|
|
62
|
+
export declare function raiseOrderDisputeAction(orderId: string, input: {
|
|
63
|
+
kind: DisputeKind;
|
|
64
|
+
/** `disputeReasons/{id}` — must exist and must match `kind`. */
|
|
65
|
+
reasonId: string;
|
|
66
|
+
/** The buyer's own words. Optional; the reason is the required part. */
|
|
67
|
+
detail?: string;
|
|
68
|
+
}): Promise<ActionResult<void>>;
|
|
62
69
|
/**
|
|
63
70
|
* A buyer submits payment for ONE of their own EMI instalments.
|
|
64
71
|
*
|
|
@@ -15,6 +15,8 @@ import { adminNotificationsRepository, orderRepository, storeRepository } from "
|
|
|
15
15
|
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers.js";
|
|
16
16
|
import { createOrderSchema, updateOrderStatusSchema, cancelOrderSchema, returnRequestSchema, } from "../../../shared/features/orders/schema.js";
|
|
17
17
|
import { isFinalSaleExempt } from "../../../shared/features/orders/return-reasons.js";
|
|
18
|
+
import { DISPUTE_KIND_LABEL, checkDisputeGate, isDisputeKind, } from "../../../shared/features/orders/dispute-reasons.js";
|
|
19
|
+
import { disputeReasonsRepository } from "../../../../features/dispute-reasons/repository/dispute-reasons.repository.js";
|
|
18
20
|
import { REFUND_COPY } from "../../../shared/features/orders/refund-copy.js";
|
|
19
21
|
import { assertOrderCancellable, assertReturnWindowOpen } from "./service.js";
|
|
20
22
|
import { cancelOrderWithRefund } from "./cancellation-refund.js";
|
|
@@ -390,10 +392,12 @@ export async function adminRejectPaymentAsFraudAction(orderId, note) {
|
|
|
390
392
|
* the seller (store owner), or admin/moderator. Does not itself reverse
|
|
391
393
|
* payment/order status — surfaces to the admin queue for investigation.
|
|
392
394
|
*/
|
|
393
|
-
export async function raiseOrderDisputeAction(orderId,
|
|
395
|
+
export async function raiseOrderDisputeAction(orderId, input) {
|
|
394
396
|
return wrapAction(async () => {
|
|
395
397
|
const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
|
|
396
|
-
if (!
|
|
398
|
+
if (!isDisputeKind(input.kind))
|
|
399
|
+
throw new ValidationError("Pick what kind of problem this is.");
|
|
400
|
+
if (!input.reasonId?.trim())
|
|
397
401
|
throw new ValidationError(REASON_REQUIRED_MSG);
|
|
398
402
|
const order = await orderRepository.findById(orderId);
|
|
399
403
|
if (!order)
|
|
@@ -407,11 +411,50 @@ export async function raiseOrderDisputeAction(orderId, reason) {
|
|
|
407
411
|
if (!isBuyer && !isSeller && !isAdminUser(user) && !isModeratorUser(user)) {
|
|
408
412
|
throw new OrderOwnershipError(orderId);
|
|
409
413
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
414
|
+
/*
|
|
415
|
+
* 🛑 THE `autoApproved` RESTRICTION IS GONE, and that is the one
|
|
416
|
+
* behaviour change an existing path will notice.
|
|
417
|
+
*
|
|
418
|
+
* It used to refuse every order whose payment was not auto-approved,
|
|
419
|
+
* telling the caller to use the return/refund flow — which made this a
|
|
420
|
+
* narrow "the 2-hour auto-approval might have been wrong" escalation
|
|
421
|
+
* rather than a dispute mechanism. The owner's rules are about delivery
|
|
422
|
+
* and item problems on ANY order, so the scope had to widen.
|
|
423
|
+
*
|
|
424
|
+
* What replaces it is a REAL gate per kind, below: the old check refused
|
|
425
|
+
* 99% of orders for a reason unrelated to the complaint, while accepting
|
|
426
|
+
* "it never arrived" on an order delivered last week.
|
|
427
|
+
*/
|
|
413
428
|
if (order.disputeStatus === "open")
|
|
414
429
|
return; // idempotent
|
|
430
|
+
/*
|
|
431
|
+
* The reason must exist AND belong to the kind being claimed.
|
|
432
|
+
*
|
|
433
|
+
* 🛑 Both halves matter. Without the kind check a caller could pass an
|
|
434
|
+
* `item` reason with `kind: "not_reached"` and pass the wrong gate — the
|
|
435
|
+
* kind is what the gate keys on, so an unvalidated pairing is the gate
|
|
436
|
+
* being bypassed by the request body.
|
|
437
|
+
*/
|
|
438
|
+
const reasonDoc = await disputeReasonsRepository.findById(input.reasonId.trim());
|
|
439
|
+
if (!reasonDoc || !reasonDoc.isActive) {
|
|
440
|
+
throw new ValidationError("That reason is no longer available. Pick another.");
|
|
441
|
+
}
|
|
442
|
+
if (reasonDoc.kind !== input.kind) {
|
|
443
|
+
throw new ValidationError("That reason doesn't match the kind of problem selected.");
|
|
444
|
+
}
|
|
445
|
+
/*
|
|
446
|
+
* ── THE GATES ─────────────────────────────────────────────────────────
|
|
447
|
+
*
|
|
448
|
+
* Owner's rules: an item dispute only after the order was received; a
|
|
449
|
+
* "not reached" dispute only once the ETD is more than 3 days past.
|
|
450
|
+
*
|
|
451
|
+
* Enforced HERE, where the request arrives, for the same reason the
|
|
452
|
+
* 7-day cutoff below is: a refusal has to live where the refusing
|
|
453
|
+
* happens. The message is buyer-facing and names the next action.
|
|
454
|
+
*/
|
|
455
|
+
const gate = checkDisputeGate(input.kind, order);
|
|
456
|
+
if (!gate.allowed)
|
|
457
|
+
throw new ValidationError(gate.message);
|
|
415
458
|
/*
|
|
416
459
|
* ── THE DISPUTE CUTOFF ────────────────────────────────────────────────
|
|
417
460
|
*
|
|
@@ -436,15 +479,39 @@ export async function raiseOrderDisputeAction(orderId, reason) {
|
|
|
436
479
|
* `disputeWindowClosed` boolean would need a backfill and would drift from
|
|
437
480
|
* the policy the day the duration changed (Root Cause #42).
|
|
438
481
|
*/
|
|
482
|
+
/*
|
|
483
|
+
* 🛑 This correctly does NOT apply to a `not_reached` dispute, and that is
|
|
484
|
+
* not an oversight to "fix".
|
|
485
|
+
*
|
|
486
|
+
* `order-complete` is anchored on `deliveryDate`, so `deadlinesFor` yields
|
|
487
|
+
* no such deadline when nothing was delivered — and you cannot be past a
|
|
488
|
+
* delivery window that never happened. Making the cutoff refuse
|
|
489
|
+
* undelivered parcels would block exactly the buyers whose parcel is lost,
|
|
490
|
+
* which is the opposite of what a dispute window is for.
|
|
491
|
+
*/
|
|
439
492
|
const completion = deadlinesFor(order).find((d) => d.kind === "order-complete");
|
|
440
493
|
if (completion && completion.dueAt.getTime() <= Date.now()) {
|
|
441
494
|
throw new ValidationError(`The dispute window for this order closed on ${completion.dueAt.toLocaleDateString("en-IN")} — 7 days after delivery. Contact support if you believe this is wrong.`);
|
|
442
495
|
}
|
|
496
|
+
/*
|
|
497
|
+
* No `as any`. Every field below is declared on `OrderDocument`, and the
|
|
498
|
+
* cast that used to be here is precisely how a misspelled field reaches
|
|
499
|
+
* Firestore unnoticed — it silences the check on all six at once.
|
|
500
|
+
*
|
|
501
|
+
* 🛑 `disputeReason` stores the reason's LABEL, not its id. It is a
|
|
502
|
+
* SNAPSHOT: staff can retire or reword a `disputeReasons` row, and neither
|
|
503
|
+
* may rewrite what a buyer said months ago. `disputeReasonId` beside it is
|
|
504
|
+
* the join key for "what are buyers reporting"; the label is the record.
|
|
505
|
+
* Same discipline as the fee terms and the box presets.
|
|
506
|
+
*/
|
|
443
507
|
await orderRepository.update(orderId, {
|
|
444
508
|
disputeRaised: true,
|
|
445
509
|
disputeRaisedBy: user.uid,
|
|
446
510
|
disputeRaisedAt: new Date(),
|
|
447
|
-
|
|
511
|
+
disputeKind: input.kind,
|
|
512
|
+
disputeReasonId: reasonDoc.id,
|
|
513
|
+
disputeReason: reasonDoc.label,
|
|
514
|
+
...(input.detail?.trim() ? { disputeDetail: input.detail.trim() } : {}),
|
|
448
515
|
disputeStatus: "open",
|
|
449
516
|
});
|
|
450
517
|
/*
|
|
@@ -463,8 +530,14 @@ export async function raiseOrderDisputeAction(orderId, reason) {
|
|
|
463
530
|
await adminNotificationsRepository
|
|
464
531
|
.create({
|
|
465
532
|
category: "fraud",
|
|
466
|
-
|
|
467
|
-
|
|
533
|
+
/*
|
|
534
|
+
* 🛑 No longer "on auto-approved order". The title said that because
|
|
535
|
+
* the action refused everything else; now any order can be disputed,
|
|
536
|
+
* and a title naming a payment path the dispute has nothing to do with
|
|
537
|
+
* would send an operator looking in the wrong place.
|
|
538
|
+
*/
|
|
539
|
+
title: `Dispute raised: ${DISPUTE_KIND_LABEL[input.kind]}`,
|
|
540
|
+
body: `A dispute was raised on order "${order.productTitle}". Reason: ${reasonDoc.label}.${input.detail?.trim() ? ` Buyer said: "${input.detail.trim()}"` : ""}`,
|
|
468
541
|
severity: "error",
|
|
469
542
|
isRead: false,
|
|
470
543
|
entityType: "order",
|
|
@@ -203,6 +203,39 @@ export function orderDocumentToOrder(doc) {
|
|
|
203
203
|
saleProtectionAddon: doc.saleProtectionAddon,
|
|
204
204
|
saleProtectionFee: doc.saleProtectionFee,
|
|
205
205
|
platformFee: doc.platformFee,
|
|
206
|
+
/*
|
|
207
|
+
* D46/D47 — the points tender, mapped for the same reason as everything
|
|
208
|
+
* above it and stated once more because this is the field most likely to
|
|
209
|
+
* be dropped: it is optional, it is absent on every historical order, and a
|
|
210
|
+
* surface that never receives it renders as though no points were used.
|
|
211
|
+
*
|
|
212
|
+
* That is Root Cause #57 exactly, and on this field it is not cosmetic —
|
|
213
|
+
* the order page would show a buyer "Total ₹1,000" and a card statement
|
|
214
|
+
* reading ₹750 with nothing on the page explaining the difference, which
|
|
215
|
+
* reads as an overcharge rather than as their own discount.
|
|
216
|
+
*
|
|
217
|
+
* Readers must use `amountDue ?? total`, never a `?? 0`: absent means "no
|
|
218
|
+
* points, the whole total was collected", and zero means free.
|
|
219
|
+
*/
|
|
220
|
+
pointsApplied: doc.pointsApplied,
|
|
221
|
+
amountDue: doc.amountDue,
|
|
222
|
+
/*
|
|
223
|
+
* Phase 7 — the invoice number, mapped for the same Root Cause #57 reason
|
|
224
|
+
* as everything above. Unmapped, the buyer's invoice page would render a
|
|
225
|
+
* statutory document with no number on it while the database held one, and
|
|
226
|
+
* the only symptom would be a blank line where an auditor looks first.
|
|
227
|
+
*
|
|
228
|
+
* 🛑 `invoiceStoragePath` is deliberately NOT mapped. It is a raw Storage
|
|
229
|
+
* path, and the client has no use for one — the bytes are served by an
|
|
230
|
+
* authenticated route. Publishing the path into an RSC payload would hand
|
|
231
|
+
* every buyer a string pointing at a private object, which is the shape of
|
|
232
|
+
* Root Cause #70's leak even though the `private/` prefix is refused by
|
|
233
|
+
* the proxy.
|
|
234
|
+
*/
|
|
235
|
+
invoiceNumber: doc.invoiceNumber,
|
|
236
|
+
invoiceIssuedAt: doc.invoiceIssuedAt
|
|
237
|
+
? new Date(doc.invoiceIssuedAt).toISOString()
|
|
238
|
+
: undefined,
|
|
206
239
|
/*
|
|
207
240
|
* 🛑 `gstAmount` -> `tax`, and codHandlingFee alongside it.
|
|
208
241
|
*
|
|
@@ -313,6 +346,27 @@ export function orderDocumentToOrder(doc) {
|
|
|
313
346
|
timelineTruncated: doc.statusHistoryTruncated,
|
|
314
347
|
/** How the buyer acquired the right to buy. Written once, at creation. */
|
|
315
348
|
sourceContext: doc.sourceContext,
|
|
349
|
+
/*
|
|
350
|
+
* D55 — the pre-order balance, present only once a seller has opened it.
|
|
351
|
+
*
|
|
352
|
+
* 🛑 `reviewedBy` is deliberately NOT mapped. It is the reviewing staff
|
|
353
|
+
* member's uid and no buyer surface reads it, so it stays server-side by
|
|
354
|
+
* default (§ Public Data Projections — a field is private until someone
|
|
355
|
+
* names the client that reads it).
|
|
356
|
+
*/
|
|
357
|
+
balance: doc.balance
|
|
358
|
+
? {
|
|
359
|
+
amount: doc.balance.amount,
|
|
360
|
+
method: doc.balance.method,
|
|
361
|
+
status: doc.balance.status,
|
|
362
|
+
dueBy: toIsoOrUndefined(doc.balance.dueBy),
|
|
363
|
+
proofUrl: doc.balance.proofUrl,
|
|
364
|
+
transactionId: doc.balance.transactionId,
|
|
365
|
+
submittedAt: toIsoOrUndefined(doc.balance.submittedAt),
|
|
366
|
+
reviewedAt: toIsoOrUndefined(doc.balance.reviewedAt),
|
|
367
|
+
reviewNote: doc.balance.reviewNote,
|
|
368
|
+
}
|
|
369
|
+
: undefined,
|
|
316
370
|
createdAt: doc.createdAt instanceof Date
|
|
317
371
|
? doc.createdAt.toISOString()
|
|
318
372
|
: String(doc.createdAt),
|