@mohasinac/appkit 4.59.0 → 4.60.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 +62 -5
- package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
- package/dist/_internal/server/features/brands/actions.js +78 -4
- package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
- package/dist/_internal/server/features/checkout/actions.js +668 -183
- package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
- package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
- package/dist/_internal/server/features/gateway-events/index.js +10 -0
- package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
- package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
- package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
- package/dist/_internal/server/features/gateway-events/redact.js +151 -0
- package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
- package/dist/_internal/server/features/grievance/actions.js +173 -0
- package/dist/_internal/server/features/grievance/index.d.ts +8 -0
- package/dist/_internal/server/features/grievance/index.js +8 -0
- package/dist/_internal/server/features/grouped/data.js +28 -3
- package/dist/_internal/server/features/lottery/actions.js +99 -4
- package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
- package/dist/_internal/server/features/order-completion/actions.js +336 -0
- package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
- package/dist/_internal/server/features/order-completion/index.js +7 -0
- package/dist/_internal/server/features/orders/actions.d.ts +13 -0
- package/dist/_internal/server/features/orders/actions.js +56 -2
- package/dist/_internal/server/features/orders/adapters.js +26 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
- package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
- package/dist/_internal/server/features/orders/index.d.ts +2 -1
- package/dist/_internal/server/features/orders/index.js +1 -1
- package/dist/_internal/server/features/orders/service.d.ts +60 -0
- package/dist/_internal/server/features/orders/service.js +74 -0
- package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
- package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
- package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
- package/dist/_internal/server/features/payment-cases/index.js +9 -0
- package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
- package/dist/_internal/server/features/payouts/actions.js +84 -10
- package/dist/_internal/server/features/points/adjust.d.ts +61 -0
- package/dist/_internal/server/features/points/adjust.js +121 -0
- package/dist/_internal/server/features/points/award.d.ts +65 -0
- package/dist/_internal/server/features/points/award.js +145 -0
- package/dist/_internal/server/features/price-index/market.d.ts +33 -0
- package/dist/_internal/server/features/price-index/market.js +54 -1
- package/dist/_internal/server/features/products/data.js +40 -22
- package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
- package/dist/_internal/server/features/protection-claims/actions.js +228 -0
- package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
- package/dist/_internal/server/features/protection-claims/index.js +8 -0
- package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
- package/dist/_internal/server/features/refunds/actions.js +259 -17
- package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
- package/dist/_internal/server/features/refunds/capacity.js +37 -0
- package/dist/_internal/server/features/reviews/actions.js +3 -2
- package/dist/_internal/server/features/search/index-build/sources.js +20 -1
- package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
- package/dist/_internal/server/features/shipping/reconcile.js +34 -3
- package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
- package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
- package/dist/_internal/server/features/stores/tabs.js +24 -4
- package/dist/_internal/server/functions/https.d.ts +31 -1
- package/dist/_internal/server/functions/https.js +52 -1
- package/dist/_internal/server/functions/scheduled.d.ts +72 -1
- package/dist/_internal/server/functions/scheduled.js +124 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
- package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
- package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
- package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
- package/dist/_internal/server/jobs/core/index.d.ts +2 -0
- package/dist/_internal/server/jobs/core/index.js +2 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
- package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
- package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
- package/dist/_internal/server/jobs/handlers/index.js +5 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
- package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
- package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +71 -0
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
- package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
- package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
- package/dist/_internal/shared/checkout/lanes.js +46 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
- package/dist/_internal/shared/checkout/order-math.js +251 -3
- package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
- package/dist/_internal/shared/checkout/refundable-base.js +122 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
- package/dist/_internal/shared/features/auctions/config.js +30 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
- package/dist/_internal/shared/features/brands/schema.js +18 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
- package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
- package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
- package/dist/_internal/shared/features/print/label-profiles.js +350 -0
- package/dist/_internal/shared/features/products/to-product-item.js +33 -5
- package/dist/_internal/shared/fees/calculator.d.ts +31 -2
- package/dist/_internal/shared/fees/calculator.js +103 -5
- package/dist/_internal/shared/fees/india-states.d.ts +195 -0
- package/dist/_internal/shared/fees/india-states.js +190 -0
- package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
- package/dist/_internal/shared/fees/payout-hold.js +74 -0
- package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
- package/dist/_internal/shared/obligations/deadlines.js +155 -0
- package/dist/_internal/shared/shipping/quote.d.ts +96 -0
- package/dist/_internal/shared/shipping/quote.js +67 -0
- package/dist/_internal/shared/time/business-days.d.ts +91 -0
- package/dist/_internal/shared/time/business-days.js +185 -0
- package/dist/cli/index.js +0 -2
- package/dist/client.d.ts +9 -4
- package/dist/client.js +26 -3
- package/dist/configs/tailwind.d.ts +0 -12
- package/dist/configs/tailwind.js +28 -15
- package/dist/constants/api-endpoints.d.ts +51 -9
- package/dist/constants/api-endpoints.js +23 -7
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/payment.d.ts +131 -0
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/about/components/DeveloperView.js +5 -2
- package/dist/features/about/components/FAQPageView.js +2 -1
- package/dist/features/about/components/FeesView.js +2 -1
- package/dist/features/about/components/HelpPageView.js +2 -1
- package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
- package/dist/features/about/components/HowEmiWorksView.js +2 -1
- package/dist/features/about/components/HowOffersWorkView.js +2 -1
- package/dist/features/about/components/HowOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
- package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowReviewsWorkView.js +2 -1
- package/dist/features/about/components/PolicyPageView.js +2 -1
- package/dist/features/about/components/ProseHero.d.ts +73 -0
- package/dist/features/about/components/ProseHero.js +18 -0
- package/dist/features/about/components/SecurityPrivacyView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +2 -1
- package/dist/features/about/components/TrackOrderView.js +2 -1
- package/dist/features/about/components/UnauthorizedView.js +1 -1
- package/dist/features/about/index.d.ts +2 -0
- package/dist/features/about/index.js +5 -0
- package/dist/features/account/actions/address-actions.d.ts +26 -2
- package/dist/features/account/actions/address-actions.js +25 -1
- package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
- package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
- package/dist/features/account/components/UserOrdersView.js +17 -0
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/hooks/useNotifications.js +29 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
- package/dist/features/admin/components/AdminBeysView.js +2 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCategoriesView.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
- package/dist/features/admin/components/AdminContactEditorView.js +1 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
- package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
- package/dist/features/admin/components/AdminFaqsView.js +2 -2
- package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
- package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
- package/dist/features/admin/components/AdminGuideHubView.js +1 -1
- package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
- package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
- package/dist/features/admin/components/AdminPartsView.js +2 -2
- package/dist/features/admin/components/AdminProductsView.js +75 -4
- package/dist/features/admin/components/AdminSidebar.js +2 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
- package/dist/features/admin/components/AdminUsersView.js +72 -1
- package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
- package/dist/features/admin/components/AdminViewCards.js +4 -4
- package/dist/features/admin/components/DataListingView.d.ts +28 -0
- package/dist/features/admin/components/DataListingView.js +175 -14
- package/dist/features/admin/components/DataTable.d.ts +12 -1
- package/dist/features/admin/components/DataTable.js +3 -3
- package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/OwnerQuickActions.js +60 -2
- package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
- package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
- package/dist/features/admin/components/index.d.ts +4 -0
- package/dist/features/admin/components/index.js +6 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
- package/dist/features/admin/constants/filter-tabs.js +106 -0
- package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
- package/dist/features/admin/repository/site-settings.repository.js +82 -1
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
- package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
- package/dist/features/admin/schemas/firestore.js +5 -1
- package/dist/features/admin/schemas/site-settings-form.js +30 -1
- package/dist/features/auctions/actions/bid-actions.js +13 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
- package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
- package/dist/features/audit-log/schemas/firestore.js +14 -0
- package/dist/features/auth/permissions/constants.d.ts +17 -1
- package/dist/features/auth/permissions/constants.js +18 -0
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
- package/dist/features/categories/api/[id]/description-template/route.js +107 -1
- package/dist/features/categories/components/BrandDetailPageView.js +2 -2
- package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
- package/dist/features/categories/components/BundleDetailView.js +1 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryForm.js +12 -1
- package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
- package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
- package/dist/features/categories/repository/market-price-index.repository.js +105 -0
- package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
- package/dist/features/categories/repository/price-observation.repository.js +132 -0
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/events/components/EventStatusBadge.js +1 -1
- package/dist/features/faq/components/FAQAccordion.js +2 -1
- package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
- package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
- package/dist/features/faq/components/RelatedFAQs.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/filters/SwitchFilter.js +1 -1
- package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
- package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
- package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
- package/dist/features/gateway-events/schemas/firestore.js +125 -0
- package/dist/features/gateway-events/schemas/index.d.ts +81 -0
- package/dist/features/gateway-events/schemas/index.js +79 -0
- package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.js +37 -1
- package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CTABannerSection.js +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
- package/dist/features/homepage/components/HeroCarousel.js +2 -2
- package/dist/features/homepage/components/SocialPostCard.js +1 -1
- package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
- package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
- package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
- package/dist/features/inventory/components/ReceiveStationView.js +413 -0
- package/dist/features/inventory/components/operator-message.d.ts +29 -0
- package/dist/features/inventory/components/operator-message.js +41 -0
- package/dist/features/inventory/index.d.ts +13 -0
- package/dist/features/inventory/index.js +28 -0
- package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
- package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
- package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
- package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
- package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
- package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
- package/dist/features/inventory/schemas/firestore.d.ts +197 -0
- package/dist/features/inventory/schemas/firestore.js +159 -0
- package/dist/features/layout/AppLayoutShell.d.ts +12 -1
- package/dist/features/layout/AppLayoutShell.js +25 -12
- package/dist/features/layout/AutoBreadcrumbs.js +162 -3
- package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
- package/dist/features/layout/BottomNavbar.d.ts +1 -1
- package/dist/features/layout/BottomNavbar.js +2 -2
- package/dist/features/media/MediaAvatar.js +1 -1
- package/dist/features/media/MediaImage.d.ts +24 -4
- package/dist/features/media/MediaImage.js +44 -4
- package/dist/features/media/MediaLightbox.js +1 -1
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +91 -3
- package/dist/features/obligations/constants/policies.d.ts +92 -0
- package/dist/features/obligations/constants/policies.js +378 -0
- package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
- package/dist/features/obligations/repository/obligations.repository.js +158 -0
- package/dist/features/obligations/schemas/firestore.d.ts +160 -0
- package/dist/features/obligations/schemas/firestore.js +87 -0
- package/dist/features/orders/actions/order-actions.d.ts +43 -4
- package/dist/features/orders/actions/order-actions.js +60 -3
- package/dist/features/orders/actions/refund-actions.js +70 -10
- package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
- package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
- package/dist/features/orders/components/OrdersList.js +2 -2
- package/dist/features/orders/components/RefundHistoryTable.js +3 -1
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +6 -0
- package/dist/features/orders/constants/transitions.d.ts +59 -0
- package/dist/features/orders/constants/transitions.js +104 -0
- package/dist/features/orders/repository/orders.repository.d.ts +109 -6
- package/dist/features/orders/repository/orders.repository.js +464 -21
- package/dist/features/orders/schemas/firestore.d.ts +270 -0
- package/dist/features/orders/schemas/firestore.js +24 -0
- package/dist/features/orders/schemas/index.d.ts +61 -0
- package/dist/features/orders/schemas/index.js +22 -0
- package/dist/features/orders/types/index.d.ts +79 -0
- package/dist/features/parts/components/PartsIndexPageView.js +8 -1
- package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
- package/dist/features/payment-cases/schemas/firestore.js +105 -0
- package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
- package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
- package/dist/features/payments/repository/payout.repository.d.ts +0 -1
- package/dist/features/payments/repository/payout.repository.js +0 -1
- package/dist/features/points/repository/points.repository.d.ts +122 -0
- package/dist/features/points/repository/points.repository.js +304 -1
- package/dist/features/points/schemas/adjust-form.d.ts +58 -0
- package/dist/features/points/schemas/adjust-form.js +63 -0
- package/dist/features/points/schemas/firestore.d.ts +108 -1
- package/dist/features/points/schemas/firestore.js +84 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
- package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
- package/dist/features/price-index/market-stats.d.ts +82 -0
- package/dist/features/price-index/market-stats.js +230 -0
- package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
- package/dist/features/print/components/BoxLabelPrintView.js +66 -0
- package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
- package/dist/features/print/components/LabelProfilePicker.js +38 -0
- package/dist/features/print/components/labels.d.ts +117 -0
- package/dist/features/print/components/labels.js +258 -0
- package/dist/features/print/components/labels.style.css +46 -0
- package/dist/features/print/index.d.ts +6 -0
- package/dist/features/print/index.js +16 -0
- package/dist/features/products/columns/productTableColumns.js +5 -4
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
- package/dist/features/products/components/PrizeDrawCollage.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/PrizeRevealModal.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.js +18 -4
- package/dist/features/products/components/ProductForm.js +34 -23
- package/dist/features/products/components/ProductGrid.js +86 -62
- package/dist/features/products/constants/card-badges.d.ts +97 -0
- package/dist/features/products/constants/card-badges.js +100 -0
- package/dist/features/products/repository/products.repository.js +34 -1
- package/dist/features/products/schemas/firestore.d.ts +46 -3
- package/dist/features/products/schemas/firestore.js +21 -0
- package/dist/features/promotions/components/PromotionsView.js +1 -1
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
- package/dist/features/promotions/schemas/firestore.js +30 -3
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
- package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
- package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
- package/dist/features/protection-claims/schemas/firestore.js +130 -0
- package/dist/features/reviews/actions/review-actions.js +3 -2
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/reviews/schemas/firestore.d.ts +20 -1
- package/dist/features/scams/constants/scam-types.d.ts +2 -0
- package/dist/features/scams/constants/scam-types.js +18 -0
- package/dist/features/scams/schemas/report-form.d.ts +3 -3
- package/dist/features/scams/schemas/report-form.js +26 -1
- package/dist/features/search/components/Search.js +9 -9
- package/dist/features/search/components/SearchResultsClient.js +18 -3
- package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/seller/actions/offer-actions.js +16 -1
- package/dist/features/seller/actions/seller-actions.js +14 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
- package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
- package/dist/features/seller/components/PrintCenterView.js +41 -30
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
- package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
- package/dist/features/seller/components/SellerProductShell.js +123 -13
- package/dist/features/seller/components/SellerReviewsView.js +4 -2
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
- package/dist/features/seller/schemas/coupon-form.js +27 -0
- package/dist/features/seller/schemas/product-form.d.ts +3 -0
- package/dist/features/seller/schemas/product-form.js +5 -0
- package/dist/features/shell/FormShell.js +2 -2
- package/dist/features/shell/QuickFormDrawer.js +14 -0
- package/dist/features/shell/SectionForm.d.ts +19 -0
- package/dist/features/shell/SectionForm.js +133 -9
- package/dist/features/shell/SectionForm.style.css +54 -0
- package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
- package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
- package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
- package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
- package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
- package/dist/features/stores/components/StoreGuideHubView.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/support/schemas/firestore.d.ts +73 -2
- package/dist/features/support/schemas/firestore.js +59 -0
- package/dist/index.d.ts +16 -24
- package/dist/index.js +38 -47
- package/dist/next/ErrorBoundary.js +1 -1
- package/dist/next/routing/route-map.d.ts +24 -0
- package/dist/next/routing/route-map.js +40 -0
- package/dist/providers/payment-manual/index.d.ts +15 -1
- package/dist/providers/payment-manual/index.js +37 -0
- package/dist/providers/payment-phonepe/index.d.ts +14 -1
- package/dist/providers/payment-phonepe/index.js +36 -0
- package/dist/providers/payment-razorpay/index.d.ts +44 -1
- package/dist/providers/payment-razorpay/index.js +99 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
- package/dist/providers/shipping-shiprocket/index.js +18 -0
- package/dist/schemas/registry.d.ts +44 -11
- package/dist/server-entry.d.ts +2 -0
- package/dist/server-entry.js +27 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +93 -0
- package/dist/styles.css +418 -149
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +15 -12
- package/dist/tokens/themes/default-light.js +14 -11
- package/dist/tokens/tokens.css +106 -23
- package/dist/ui/DataTable.style.css +4 -4
- package/dist/ui/columns/cell-renderers.d.ts +20 -0
- package/dist/ui/columns/cell-renderers.js +26 -1
- package/dist/ui/columns/index.d.ts +1 -1
- package/dist/ui/columns/index.js +4 -1
- package/dist/ui/components/Badge.style.css +1 -1
- package/dist/ui/components/Breadcrumb.js +19 -1
- package/dist/ui/components/BulkActionBar.style.css +4 -4
- package/dist/ui/components/Button.style.css +1 -1
- package/dist/ui/components/Checkbox.style.css +1 -1
- package/dist/ui/components/FallbackShell.js +1 -1
- package/dist/ui/components/Form.d.ts +14 -1
- package/dist/ui/components/Form.js +2 -2
- package/dist/ui/components/FormField.js +18 -4
- package/dist/ui/components/FormField.style.css +43 -3
- package/dist/ui/components/GatedPrice.style.css +1 -1
- package/dist/ui/components/IconButton.style.css +1 -1
- package/dist/ui/components/Input.js +22 -1
- package/dist/ui/components/Input.style.css +38 -22
- package/dist/ui/components/ListingFilterDrawer.js +1 -1
- package/dist/ui/components/ListingToolbar.js +1 -1
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/Radio.style.css +1 -1
- package/dist/ui/components/Repeater.d.ts +53 -0
- package/dist/ui/components/Repeater.js +7 -0
- package/dist/ui/components/RichTextEditor.d.ts +20 -1
- package/dist/ui/components/RichTextEditor.js +23 -2
- package/dist/ui/components/Select.style.css +7 -21
- package/dist/ui/components/Semantic.js +27 -3
- package/dist/ui/components/TagInput.style.css +4 -9
- package/dist/ui/components/Textarea.style.css +4 -18
- package/dist/ui/components/Toggle.style.css +1 -1
- package/dist/ui/components/Typography.d.ts +3 -3
- package/dist/ui/components/Typography.js +15 -15
- package/dist/ui/components/ViewToggle.style.css +4 -4
- package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
- package/dist/ui/forms/AddAnotherToggle.js +56 -0
- package/dist/ui/forms/ColorPickerField.js +15 -2
- package/dist/ui/forms/FieldCheckbox.js +15 -2
- package/dist/ui/forms/FieldInput.d.ts +22 -1
- package/dist/ui/forms/FieldInput.js +19 -5
- package/dist/ui/forms/FieldLabel.d.ts +35 -0
- package/dist/ui/forms/FieldLabel.js +70 -0
- package/dist/ui/forms/FieldSelect.d.ts +4 -1
- package/dist/ui/forms/FieldSelect.js +27 -5
- package/dist/ui/forms/FieldTextarea.d.ts +4 -1
- package/dist/ui/forms/FieldTextarea.js +22 -5
- package/dist/ui/forms/FormShell.d.ts +17 -1
- package/dist/ui/forms/FormShell.js +36 -2
- package/dist/ui/forms/FormShell.style.css +41 -33
- package/dist/ui/forms/index.d.ts +5 -1
- package/dist/ui/forms/index.js +2 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +17 -1
- package/dist/utils/id-generators.d.ts +80 -0
- package/dist/utils/id-generators.js +93 -0
- package/dist/utils/search-txt-builders.d.ts +12 -0
- package/dist/utils/search-txt-builders.js +17 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +106 -5
- package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
- package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
- package/dist/features/loyalty/api/balance/route.d.ts +0 -12
- package/dist/features/loyalty/api/balance/route.js +0 -41
- package/dist/features/loyalty/api/earn/route.d.ts +0 -12
- package/dist/features/loyalty/api/earn/route.js +0 -38
- package/dist/features/loyalty/api/history/route.d.ts +0 -12
- package/dist/features/loyalty/api/history/route.js +0 -58
- package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
- package/dist/features/loyalty/api/redeem/route.js +0 -38
- package/dist/features/loyalty/columns/index.d.ts +0 -17
- package/dist/features/loyalty/columns/index.js +0 -40
- package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
- package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
- package/dist/features/loyalty/components/index.d.ts +0 -1
- package/dist/features/loyalty/components/index.js +0 -1
- package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
- package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
- package/dist/features/loyalty/index.d.ts +0 -6
- package/dist/features/loyalty/index.js +0 -6
- package/dist/features/loyalty/manifest.d.ts +0 -2
- package/dist/features/loyalty/manifest.js +0 -16
- package/dist/features/loyalty/messages/en.json +0 -19
- package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
- package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
- package/dist/features/loyalty/schemas/index.d.ts +0 -85
- package/dist/features/loyalty/schemas/index.js +0 -40
- package/dist/features/loyalty/server.d.ts +0 -6
- package/dist/features/loyalty/server.js +0 -6
- package/dist/features/loyalty/types/index.d.ts +0 -31
- package/dist/features/loyalty/types/index.js +0 -1
- package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
- package/dist/features/loyalty/types/loyalty-config.js +0 -1
- package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
- package/dist/features/loyalty/types/loyalty-math.js +0 -27
- package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
- package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
|
@@ -53,7 +53,24 @@ export function resolveSupplierIdentity(store, settings) {
|
|
|
53
53
|
* A seller with a GSTIN and no legal name is a data gap worth seeing as a
|
|
54
54
|
* blank on the invoice, not one worth papering over with the wrong name.
|
|
55
55
|
*/
|
|
56
|
-
|
|
56
|
+
/*
|
|
57
|
+
* 🛑 G8 — `&& settings?.gst?.enabled`, added 2026-10-21.
|
|
58
|
+
*
|
|
59
|
+
* This branch returned on the GSTIN ALONE while the platform branch below
|
|
60
|
+
* was correctly gated on `gst.enabled`. So with GST disabled, a seller who
|
|
61
|
+
* happens to have a GSTIN still got it stamped onto the order — producing a
|
|
62
|
+
* document that LOOKS like a tax invoice, names a registration, and carries
|
|
63
|
+
* **no tax at all**, because the whole GST computation is skipped when the
|
|
64
|
+
* flag is off (`previewCheckoutPricing` and `createOrderForGroup` both gate
|
|
65
|
+
* their `sumGroupGst` call on it).
|
|
66
|
+
*
|
|
67
|
+
* An invoice asserting a GSTIN with zero tax on it is worse than a plain
|
|
68
|
+
* invoice: it implies a taxable supply was made and nil tax was due. The two
|
|
69
|
+
* branches now agree that `gst.enabled` is the switch for whether this is a
|
|
70
|
+
* tax document at all — which is what that flag already means everywhere
|
|
71
|
+
* else, and what the platform branch's own comment below says.
|
|
72
|
+
*/
|
|
73
|
+
if (sellerGstin && settings?.gst?.enabled) {
|
|
57
74
|
return {
|
|
58
75
|
gstin: sellerGstin,
|
|
59
76
|
legalName: trimmed(store?.legalName),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gateway-events — server surface.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 A barrel for ONE feature's public surface, not a convenience re-export
|
|
5
|
+
* hub (Root Cause #18). Three symbols, all consumed by the webhook route; the
|
|
6
|
+
* repository is imported directly by anything that queries, because a
|
|
7
|
+
* reconciler has no business pulling the redactor into its graph.
|
|
8
|
+
*/
|
|
9
|
+
export { ingestGatewayEvent, settleGatewayEvent, statusForDispatch, resolveDuplicateDelivery, type IngestGatewayEventInput, type SettleGatewayEventInput, type DuplicateAction, type DuplicateResolution, } from "./ingest";
|
|
10
|
+
export { redactGatewayPayload, type RedactResult } from "./redact";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gateway-events — server surface.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 A barrel for ONE feature's public surface, not a convenience re-export
|
|
5
|
+
* hub (Root Cause #18). Three symbols, all consumed by the webhook route; the
|
|
6
|
+
* repository is imported directly by anything that queries, because a
|
|
7
|
+
* reconciler has no business pulling the redactor into its graph.
|
|
8
|
+
*/
|
|
9
|
+
export { ingestGatewayEvent, settleGatewayEvent, statusForDispatch, resolveDuplicateDelivery, } from "./ingest.js";
|
|
10
|
+
export { redactGatewayPayload } from "./redact.js";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gateway-event inbox: persist, then act, then ack.
|
|
3
|
+
*
|
|
4
|
+
* ## The ordering, and why it is this way round
|
|
5
|
+
*
|
|
6
|
+
* ```
|
|
7
|
+
* verify signature ← already done by the route, see below
|
|
8
|
+
* parse + normalise
|
|
9
|
+
* ingestGatewayEvent() ← 1 write: persist AND claim, atomically
|
|
10
|
+
* dispatch ← place the order / signal RTDB / nothing
|
|
11
|
+
* settleGatewayEvent() ← 1 write: the outcome
|
|
12
|
+
* ack
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Total cost: 2 writes, 0 reads for a new event; 1 failed create for a
|
|
16
|
+
* duplicate. ~80 ms against the route's 60,000 ms budget.
|
|
17
|
+
*
|
|
18
|
+
* 🛑 **Ingest MUST sit below the signature check**, and the webhook route
|
|
19
|
+
* carries a comment saying so at the rejection branch. An unauthenticated
|
|
20
|
+
* caller who can create Firestore documents by POSTing garbage is a write-quota
|
|
21
|
+
* denial-of-service with no authentication at all — on the one route in the
|
|
22
|
+
* application that accepts requests with no session. Persist after
|
|
23
|
+
* authentication, never before.
|
|
24
|
+
*
|
|
25
|
+
* ## What a crash leaves behind
|
|
26
|
+
*
|
|
27
|
+
* A row with `status: "received"` and a lease that expires in 120 s. That is
|
|
28
|
+
* the entire point: the 2026-10-21 incident left *nothing*, so there was no
|
|
29
|
+
* artefact to reconcile against and the first signal was a customer asking
|
|
30
|
+
* where their order was. Phase 4's R1 sweep queries exactly that shape every
|
|
31
|
+
* 5 minutes.
|
|
32
|
+
*/
|
|
33
|
+
import { type GatewayEventClaimResult } from "../../../../features/gateway-events/repository/gateway-events.repository";
|
|
34
|
+
import { type GatewayEventKind, type GatewayEventStatus } from "../../../../features/gateway-events/schemas/firestore";
|
|
35
|
+
export interface IngestGatewayEventInput {
|
|
36
|
+
/** `payment.name` — the provider that signed this delivery. */
|
|
37
|
+
gateway: string;
|
|
38
|
+
/** The raw request body, verbatim — the same string the signature covered. */
|
|
39
|
+
rawBody: string;
|
|
40
|
+
/** The parsed body. Passed in rather than re-parsed, so there is one parse. */
|
|
41
|
+
body: unknown;
|
|
42
|
+
/** The gateway's own event string, e.g. `order.paid`. */
|
|
43
|
+
rawEvent: string;
|
|
44
|
+
kind: GatewayEventKind;
|
|
45
|
+
providerEventId?: string;
|
|
46
|
+
gatewayOrderId?: string;
|
|
47
|
+
gatewayPaymentId?: string;
|
|
48
|
+
/** Decimal rupees. Convert at the provider boundary, never here. */
|
|
49
|
+
amount?: number;
|
|
50
|
+
now?: Date;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Persist and claim.
|
|
54
|
+
*
|
|
55
|
+
* 🛑 **Throws** when the write fails for any reason other than "already
|
|
56
|
+
* exists". The caller must answer **503** in that case — per the route's ack
|
|
57
|
+
* contract, we hold no record, so the only honest response is "send it again".
|
|
58
|
+
* Catching this and acking 200 loses the event, which is the failure this
|
|
59
|
+
* module exists to prevent.
|
|
60
|
+
*/
|
|
61
|
+
export declare function ingestGatewayEvent(input: IngestGatewayEventInput): Promise<GatewayEventClaimResult>;
|
|
62
|
+
/**
|
|
63
|
+
* What to do about a delivery we have seen before.
|
|
64
|
+
*
|
|
65
|
+
* 🛑 THIS BRANCH EXISTS BECAUSE NAIVE DEDUPE WOULD HAVE BROKEN THE D4 FIX, and
|
|
66
|
+
* the interaction is not obvious until you trace the two mechanisms together.
|
|
67
|
+
*
|
|
68
|
+
* Phase 0 made a transient placement failure answer **500**, specifically so
|
|
69
|
+
* the gateway's own retry schedule becomes the safety net for a
|
|
70
|
+
* captured-but-unplaced payment. Razorpay then redelivers **the same event
|
|
71
|
+
* id**. So if "we already have a row for this id" meant "ack 200 and do
|
|
72
|
+
* nothing", the retry we deliberately asked for would be swallowed by the
|
|
73
|
+
* inbox — and a captured payment with no order would go back to being
|
|
74
|
+
* unrecoverable. The inbox would have made things *worse* than no inbox.
|
|
75
|
+
*
|
|
76
|
+
* So a duplicate is resolved by reading its row — the one read the write-
|
|
77
|
+
* amplification budget allows for this path — and the answer depends on where
|
|
78
|
+
* that row got to:
|
|
79
|
+
*
|
|
80
|
+
* | existing row | action | why |
|
|
81
|
+
* |---|---|---|
|
|
82
|
+
* | `processed` / `ignored` / `conflict` | **ack** | done; nothing a retry can add |
|
|
83
|
+
* | `failed` + `terminal: true` | **ack** | a retry cannot help; a human must look |
|
|
84
|
+
* | `failed` without `terminal` | **redispatch** | transient — this IS the retry we asked for |
|
|
85
|
+
* | `received`/`processing`, lease LIVE | **ack** | another worker has it; a 5xx would thundering-herd one order |
|
|
86
|
+
* | `received`/`processing`, lease LAPSED | **redispatch** | the previous worker died mid-flight |
|
|
87
|
+
* | row missing (raced its own deletion) | **redispatch** | treat as new rather than lose it |
|
|
88
|
+
*/
|
|
89
|
+
export type DuplicateAction = "ack" | "redispatch";
|
|
90
|
+
export interface DuplicateResolution {
|
|
91
|
+
action: DuplicateAction;
|
|
92
|
+
/** Attempts recorded so far — the caller renews the lease with this. */
|
|
93
|
+
attempts: number;
|
|
94
|
+
status?: GatewayEventStatus;
|
|
95
|
+
}
|
|
96
|
+
export declare function resolveDuplicateDelivery(id: string, now?: Date): Promise<DuplicateResolution>;
|
|
97
|
+
export interface SettleGatewayEventInput {
|
|
98
|
+
status: GatewayEventStatus;
|
|
99
|
+
orderIds?: string[];
|
|
100
|
+
lastError?: string;
|
|
101
|
+
terminal?: boolean;
|
|
102
|
+
now?: Date;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Record the outcome. Never throws — by this point the event is durable and the
|
|
106
|
+
* work is done, so a bookkeeping failure must not become a 5xx that makes the
|
|
107
|
+
* gateway redeliver an event we processed successfully. The row is left
|
|
108
|
+
* `received` with a lapsing lease, which the stuck-event sweep handles.
|
|
109
|
+
*/
|
|
110
|
+
export declare function settleGatewayEvent(id: string, input: SettleGatewayEventInput): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Translate a dispatch outcome into an inbox status.
|
|
113
|
+
*
|
|
114
|
+
* Kept here rather than inlined at the route so the mapping is stated once and
|
|
115
|
+
* both halves of the ack contract — the HTTP status the gateway sees and the
|
|
116
|
+
* row an operator reads — are derived from the same decision. Two hand-rolled
|
|
117
|
+
* copies of this would be Root Cause #75's shape on the observability axis:
|
|
118
|
+
* the row would say `processed` while the route answered 500.
|
|
119
|
+
*/
|
|
120
|
+
export declare function statusForDispatch(outcome: {
|
|
121
|
+
handled: boolean;
|
|
122
|
+
succeeded: boolean;
|
|
123
|
+
retryable: boolean;
|
|
124
|
+
}): {
|
|
125
|
+
status: GatewayEventStatus;
|
|
126
|
+
terminal?: boolean;
|
|
127
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gateway-event inbox: persist, then act, then ack.
|
|
3
|
+
*
|
|
4
|
+
* ## The ordering, and why it is this way round
|
|
5
|
+
*
|
|
6
|
+
* ```
|
|
7
|
+
* verify signature ← already done by the route, see below
|
|
8
|
+
* parse + normalise
|
|
9
|
+
* ingestGatewayEvent() ← 1 write: persist AND claim, atomically
|
|
10
|
+
* dispatch ← place the order / signal RTDB / nothing
|
|
11
|
+
* settleGatewayEvent() ← 1 write: the outcome
|
|
12
|
+
* ack
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Total cost: 2 writes, 0 reads for a new event; 1 failed create for a
|
|
16
|
+
* duplicate. ~80 ms against the route's 60,000 ms budget.
|
|
17
|
+
*
|
|
18
|
+
* 🛑 **Ingest MUST sit below the signature check**, and the webhook route
|
|
19
|
+
* carries a comment saying so at the rejection branch. An unauthenticated
|
|
20
|
+
* caller who can create Firestore documents by POSTing garbage is a write-quota
|
|
21
|
+
* denial-of-service with no authentication at all — on the one route in the
|
|
22
|
+
* application that accepts requests with no session. Persist after
|
|
23
|
+
* authentication, never before.
|
|
24
|
+
*
|
|
25
|
+
* ## What a crash leaves behind
|
|
26
|
+
*
|
|
27
|
+
* A row with `status: "received"` and a lease that expires in 120 s. That is
|
|
28
|
+
* the entire point: the 2026-10-21 incident left *nothing*, so there was no
|
|
29
|
+
* artefact to reconcile against and the first signal was a customer asking
|
|
30
|
+
* where their order was. Phase 4's R1 sweep queries exactly that shape every
|
|
31
|
+
* 5 minutes.
|
|
32
|
+
*/
|
|
33
|
+
/*
|
|
34
|
+
* 🛑 `"crypto"`, NOT `"node:crypto"` — Root Cause #24. A top-level
|
|
35
|
+
* `node:`-prefixed import in any file reachable from `index.ts` or `client.ts`
|
|
36
|
+
* through ANY re-export chain hard-fails the Turbopack production build with
|
|
37
|
+
* `the chunking context (unknown) does not support external modules`, on every
|
|
38
|
+
* page, in production only. `next dev` tolerates it, so the mistake ships.
|
|
39
|
+
*
|
|
40
|
+
* This file is server-only today and so would probably survive the prefixed
|
|
41
|
+
* form — but "probably, today" is how that bug arrived the first time, and the
|
|
42
|
+
* bare specifier costs nothing. `refunds/actions.ts` uses the same spelling for
|
|
43
|
+
* the same reason.
|
|
44
|
+
*/
|
|
45
|
+
import { createHash } from "crypto";
|
|
46
|
+
import { normalizeError } from "../../../../errors/normalize.js";
|
|
47
|
+
import { serverLogger } from "../../../../monitoring/index.js";
|
|
48
|
+
import { gatewayEventsRepository, } from "../../../../features/gateway-events/repository/gateway-events.repository.js";
|
|
49
|
+
import { GATEWAY_EVENT_MAX_ATTEMPTS, } from "../../../../features/gateway-events/schemas/firestore.js";
|
|
50
|
+
import { redactGatewayPayload } from "./redact.js";
|
|
51
|
+
/**
|
|
52
|
+
* Persist and claim.
|
|
53
|
+
*
|
|
54
|
+
* 🛑 **Throws** when the write fails for any reason other than "already
|
|
55
|
+
* exists". The caller must answer **503** in that case — per the route's ack
|
|
56
|
+
* contract, we hold no record, so the only honest response is "send it again".
|
|
57
|
+
* Catching this and acking 200 loses the event, which is the failure this
|
|
58
|
+
* module exists to prevent.
|
|
59
|
+
*/
|
|
60
|
+
export async function ingestGatewayEvent(input) {
|
|
61
|
+
const bodySha256 = createHash("sha256").update(input.rawBody).digest("hex");
|
|
62
|
+
const { payload, truncated } = redactGatewayPayload(input.body);
|
|
63
|
+
const result = await gatewayEventsRepository().claim({
|
|
64
|
+
gateway: input.gateway,
|
|
65
|
+
rawEvent: input.rawEvent,
|
|
66
|
+
kind: input.kind,
|
|
67
|
+
bodySha256,
|
|
68
|
+
...(input.providerEventId ? { providerEventId: input.providerEventId } : {}),
|
|
69
|
+
...(input.gatewayOrderId ? { gatewayOrderId: input.gatewayOrderId } : {}),
|
|
70
|
+
...(input.gatewayPaymentId ? { gatewayPaymentId: input.gatewayPaymentId } : {}),
|
|
71
|
+
...(typeof input.amount === "number" ? { amount: input.amount } : {}),
|
|
72
|
+
payload,
|
|
73
|
+
...(truncated ? { payloadTruncated: true } : {}),
|
|
74
|
+
...(input.now ? { now: input.now } : {}),
|
|
75
|
+
});
|
|
76
|
+
if (!result.claimed) {
|
|
77
|
+
/*
|
|
78
|
+
* A redelivery, or a second event carrying an identical body. Logged at
|
|
79
|
+
* `info` because it is NORMAL traffic and not a fault — Razorpay's own
|
|
80
|
+
* Resend button produces it, and so does any at-least-once delivery
|
|
81
|
+
* schedule. The absence of a second row IS the feature working.
|
|
82
|
+
*/
|
|
83
|
+
serverLogger.info("gatewayEvents: duplicate delivery — already have it", {
|
|
84
|
+
id: result.id,
|
|
85
|
+
rawEvent: input.rawEvent,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
export async function resolveDuplicateDelivery(id, now = new Date()) {
|
|
91
|
+
const existing = await gatewayEventsRepository().findById(id);
|
|
92
|
+
if (!existing) {
|
|
93
|
+
/*
|
|
94
|
+
* The id exists (the create failed) but the row does not — a race with the
|
|
95
|
+
* retention sweep, or a delete between the two calls. Fail toward DOING THE
|
|
96
|
+
* WORK: re-dispatching an already-processed event is idempotent downstream
|
|
97
|
+
* (the placement claim makes it a no-op), whereas acking an event we have
|
|
98
|
+
* no record of is the loss this collection exists to prevent.
|
|
99
|
+
*/
|
|
100
|
+
return { action: "redispatch", attempts: 1 };
|
|
101
|
+
}
|
|
102
|
+
if (existing.status === "processed" ||
|
|
103
|
+
existing.status === "ignored" ||
|
|
104
|
+
existing.status === "conflict") {
|
|
105
|
+
return { action: "ack", attempts: existing.attempts, status: existing.status };
|
|
106
|
+
}
|
|
107
|
+
if (existing.status === "failed") {
|
|
108
|
+
return {
|
|
109
|
+
action: existing.terminal === true ? "ack" : "redispatch",
|
|
110
|
+
attempts: existing.attempts,
|
|
111
|
+
status: existing.status,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// received | processing — decided by the lease, not by the status.
|
|
115
|
+
const leaseLive = existing.leaseExpiresAt.getTime() > now.getTime();
|
|
116
|
+
if (leaseLive) {
|
|
117
|
+
return { action: "ack", attempts: existing.attempts, status: existing.status };
|
|
118
|
+
}
|
|
119
|
+
if (existing.attempts >= GATEWAY_EVENT_MAX_ATTEMPTS) {
|
|
120
|
+
/*
|
|
121
|
+
* Budget exhausted. Ack rather than loop forever — the row is `received`
|
|
122
|
+
* with a lapsed lease and a maxed attempt count, which is exactly the shape
|
|
123
|
+
* Phase 4's R1 sweep escalates into a case.
|
|
124
|
+
*/
|
|
125
|
+
return { action: "ack", attempts: existing.attempts, status: existing.status };
|
|
126
|
+
}
|
|
127
|
+
/*
|
|
128
|
+
* Taking over a dead worker's claim, so renew the lease HERE rather than
|
|
129
|
+
* leaving it to the caller.
|
|
130
|
+
*
|
|
131
|
+
* 🛑 This is not a lock and does not pretend to be: `renewLease` is a plain
|
|
132
|
+
* update, so two redeliveries arriving in the same instant can both read a
|
|
133
|
+
* lapsed lease and both decide to dispatch. Narrowing that window is worth
|
|
134
|
+
* one write; closing it would need a transaction, and a transaction here
|
|
135
|
+
* would buy nothing — the thing that actually makes a double dispatch safe is
|
|
136
|
+
* the placement claim in `verifyAndPlaceGatewayOrderAction`, which is
|
|
137
|
+
* at-most-once on the gateway order id and turns the second attempt into a
|
|
138
|
+
* no-op. Two guards for one hazard, and the downstream one is the real one.
|
|
139
|
+
*/
|
|
140
|
+
try {
|
|
141
|
+
await gatewayEventsRepository().renewLease(id, existing.attempts, now);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
/*
|
|
145
|
+
* Could not renew. Dispatch anyway: the alternative is dropping a retry we
|
|
146
|
+
* explicitly asked the gateway for, and the row is already durable so the
|
|
147
|
+
* sweep can still find it. The lease is an optimisation, not a correctness
|
|
148
|
+
* boundary.
|
|
149
|
+
*/
|
|
150
|
+
void normalizeError(err);
|
|
151
|
+
serverLogger.warn("gatewayEvents: lease renewal failed — dispatching regardless", { id });
|
|
152
|
+
}
|
|
153
|
+
return { action: "redispatch", attempts: existing.attempts, status: existing.status };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Record the outcome. Never throws — by this point the event is durable and the
|
|
157
|
+
* work is done, so a bookkeeping failure must not become a 5xx that makes the
|
|
158
|
+
* gateway redeliver an event we processed successfully. The row is left
|
|
159
|
+
* `received` with a lapsing lease, which the stuck-event sweep handles.
|
|
160
|
+
*/
|
|
161
|
+
export async function settleGatewayEvent(id, input) {
|
|
162
|
+
try {
|
|
163
|
+
await gatewayEventsRepository().settle(id, input);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
void normalizeError(err);
|
|
167
|
+
serverLogger.warn("gatewayEvents: settle failed — the stuck-event sweep will retry", {
|
|
168
|
+
id,
|
|
169
|
+
err,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Translate a dispatch outcome into an inbox status.
|
|
175
|
+
*
|
|
176
|
+
* Kept here rather than inlined at the route so the mapping is stated once and
|
|
177
|
+
* both halves of the ack contract — the HTTP status the gateway sees and the
|
|
178
|
+
* row an operator reads — are derived from the same decision. Two hand-rolled
|
|
179
|
+
* copies of this would be Root Cause #75's shape on the observability axis:
|
|
180
|
+
* the row would say `processed` while the route answered 500.
|
|
181
|
+
*/
|
|
182
|
+
export function statusForDispatch(outcome) {
|
|
183
|
+
if (!outcome.handled)
|
|
184
|
+
return { status: "ignored" };
|
|
185
|
+
if (outcome.succeeded)
|
|
186
|
+
return { status: "processed" };
|
|
187
|
+
return { status: "failed", terminal: !outcome.retryable };
|
|
188
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redact a gateway webhook body down to something safe to keep for 90 days.
|
|
3
|
+
*
|
|
4
|
+
* ## Why the raw body is never stored
|
|
5
|
+
*
|
|
6
|
+
* A Razorpay payment entity carries the buyer's `email`, `contact`,
|
|
7
|
+
* `card.last4`, `vpa` and `bank` — so persisting the body verbatim would make
|
|
8
|
+
* `gatewayEvents` a **new PII store**, with a 90-day retention, no encryption,
|
|
9
|
+
* and an admin table rendering it. That is a worse outcome than the problem the
|
|
10
|
+
* inbox solves.
|
|
11
|
+
*
|
|
12
|
+
* The integrity guarantee does not depend on keeping the body: `bodySha256` is
|
|
13
|
+
* computed over the verbatim bytes before redaction, so "is this the same event
|
|
14
|
+
* the gateway signed" stays answerable regardless of what the projection drops.
|
|
15
|
+
*
|
|
16
|
+
* ## Deny-list, and why that is defensible HERE specifically
|
|
17
|
+
*
|
|
18
|
+
* 🛑 Root Cause #70 is the incident where a deny-list projection published
|
|
19
|
+
* every field nobody thought to delete, and § "Public Data Projections" draws
|
|
20
|
+
* the rule: **a document field is private until someone names it public.** This
|
|
21
|
+
* redactor is a deny-list, which looks like the same mistake. It is not, for
|
|
22
|
+
* two reasons worth stating rather than assuming:
|
|
23
|
+
*
|
|
24
|
+
* 1. **The shape is THEIRS, not ours.** An allow-list requires enumerating
|
|
25
|
+
* every field a gateway might send, across providers and across their
|
|
26
|
+
* future API versions. A field we failed to anticipate would be *dropped*,
|
|
27
|
+
* and the whole value of this row is being able to read a payload we did
|
|
28
|
+
* not expect — that is what makes an unhandled event diagnosable. An
|
|
29
|
+
* allow-list here optimises for the case that never needs diagnosing.
|
|
30
|
+
* 2. **The output is never published.** It reaches one admin maintenance
|
|
31
|
+
* table behind `admin:maintenance:view-server-errors`. The field that got
|
|
32
|
+
* away in #70 went to an *unauthenticated, edge-cached* public endpoint.
|
|
33
|
+
*
|
|
34
|
+
* The deny-list is therefore keyed on *categories of sensitive data* rather
|
|
35
|
+
* than on one provider's field names, and it matches **recursively on the key
|
|
36
|
+
* at every depth** — so `payload.payment.entity.card.last4` is caught by the
|
|
37
|
+
* `card` key without anyone having written that path down.
|
|
38
|
+
*/
|
|
39
|
+
import type { FirestoreDocument } from "../../../../schemas/types";
|
|
40
|
+
/**
|
|
41
|
+
* `vpa` appears as a bare key AND embedded in strings like
|
|
42
|
+
* `"someone@okhdfcbank"`. The key test catches the former; nothing here tries
|
|
43
|
+
* to catch the latter, and that is a deliberate limit rather than an oversight:
|
|
44
|
+
* a value-scanning redactor over arbitrary provider JSON produces false
|
|
45
|
+
* positives on ids that merely contain "@", and a redactor that mangles the
|
|
46
|
+
* diagnostic data is worse than one with a documented edge.
|
|
47
|
+
*
|
|
48
|
+
* What covers the gap is that the *keys* carrying an address — `email`,
|
|
49
|
+
* `contact`, `vpa` — are all denied, so a value only survives if the gateway
|
|
50
|
+
* put a contact detail under a key none of us anticipated.
|
|
51
|
+
*/
|
|
52
|
+
export interface RedactResult {
|
|
53
|
+
payload: FirestoreDocument;
|
|
54
|
+
truncated: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Redact, then cap.
|
|
58
|
+
*
|
|
59
|
+
* 🛑 The cap is applied by MEASURING the serialised result and, if it is over
|
|
60
|
+
* budget, replacing it with a marker rather than by slicing the JSON string.
|
|
61
|
+
* A sliced JSON string is not JSON — it would be stored as a truncated blob
|
|
62
|
+
* that no reader can parse, which is a field that looks present and is
|
|
63
|
+
* unusable. A marker plus `payloadTruncated: true` tells the reader exactly
|
|
64
|
+
* what happened, and `bodySha256` still proves what arrived.
|
|
65
|
+
*/
|
|
66
|
+
export declare function redactGatewayPayload(body: unknown): RedactResult;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redact a gateway webhook body down to something safe to keep for 90 days.
|
|
3
|
+
*
|
|
4
|
+
* ## Why the raw body is never stored
|
|
5
|
+
*
|
|
6
|
+
* A Razorpay payment entity carries the buyer's `email`, `contact`,
|
|
7
|
+
* `card.last4`, `vpa` and `bank` — so persisting the body verbatim would make
|
|
8
|
+
* `gatewayEvents` a **new PII store**, with a 90-day retention, no encryption,
|
|
9
|
+
* and an admin table rendering it. That is a worse outcome than the problem the
|
|
10
|
+
* inbox solves.
|
|
11
|
+
*
|
|
12
|
+
* The integrity guarantee does not depend on keeping the body: `bodySha256` is
|
|
13
|
+
* computed over the verbatim bytes before redaction, so "is this the same event
|
|
14
|
+
* the gateway signed" stays answerable regardless of what the projection drops.
|
|
15
|
+
*
|
|
16
|
+
* ## Deny-list, and why that is defensible HERE specifically
|
|
17
|
+
*
|
|
18
|
+
* 🛑 Root Cause #70 is the incident where a deny-list projection published
|
|
19
|
+
* every field nobody thought to delete, and § "Public Data Projections" draws
|
|
20
|
+
* the rule: **a document field is private until someone names it public.** This
|
|
21
|
+
* redactor is a deny-list, which looks like the same mistake. It is not, for
|
|
22
|
+
* two reasons worth stating rather than assuming:
|
|
23
|
+
*
|
|
24
|
+
* 1. **The shape is THEIRS, not ours.** An allow-list requires enumerating
|
|
25
|
+
* every field a gateway might send, across providers and across their
|
|
26
|
+
* future API versions. A field we failed to anticipate would be *dropped*,
|
|
27
|
+
* and the whole value of this row is being able to read a payload we did
|
|
28
|
+
* not expect — that is what makes an unhandled event diagnosable. An
|
|
29
|
+
* allow-list here optimises for the case that never needs diagnosing.
|
|
30
|
+
* 2. **The output is never published.** It reaches one admin maintenance
|
|
31
|
+
* table behind `admin:maintenance:view-server-errors`. The field that got
|
|
32
|
+
* away in #70 went to an *unauthenticated, edge-cached* public endpoint.
|
|
33
|
+
*
|
|
34
|
+
* The deny-list is therefore keyed on *categories of sensitive data* rather
|
|
35
|
+
* than on one provider's field names, and it matches **recursively on the key
|
|
36
|
+
* at every depth** — so `payload.payment.entity.card.last4` is caught by the
|
|
37
|
+
* `card` key without anyone having written that path down.
|
|
38
|
+
*/
|
|
39
|
+
import { GATEWAY_EVENT_PAYLOAD_MAX_BYTES } from "../../../../features/gateway-events/schemas/firestore.js";
|
|
40
|
+
/**
|
|
41
|
+
* Keys dropped wherever they appear, at any depth.
|
|
42
|
+
*
|
|
43
|
+
* Grouped by why, because "it looked sensitive" is not a maintainable reason
|
|
44
|
+
* and the next person needs to know whether to extend the list:
|
|
45
|
+
*
|
|
46
|
+
* - **direct identifiers** — reach a person without any other data
|
|
47
|
+
* - **instrument data** — card/UPI/bank details; `last4` is nested under `card`
|
|
48
|
+
* and so is covered by the parent key
|
|
49
|
+
* - **our own passthrough** — `notes` and `metaInfo` are where WE pack
|
|
50
|
+
* `uid`/`addressId`/`outOfStockPolicy` at create-order time, so they are the
|
|
51
|
+
* one category that is sensitive *because of what we put there*
|
|
52
|
+
* - **credentials** — anything token-shaped, which should never be in a webhook
|
|
53
|
+
* body but costs nothing to exclude
|
|
54
|
+
*/
|
|
55
|
+
const DENY_KEYS = new Set([
|
|
56
|
+
// direct identifiers
|
|
57
|
+
"email",
|
|
58
|
+
"contact",
|
|
59
|
+
"phone",
|
|
60
|
+
"phone_number",
|
|
61
|
+
"phoneNumber",
|
|
62
|
+
"mobile",
|
|
63
|
+
"name",
|
|
64
|
+
"customer_id",
|
|
65
|
+
"customerId",
|
|
66
|
+
"address",
|
|
67
|
+
"billing_address",
|
|
68
|
+
"shipping_address",
|
|
69
|
+
// instrument data
|
|
70
|
+
"card",
|
|
71
|
+
"card_id",
|
|
72
|
+
"vpa",
|
|
73
|
+
"bank_account",
|
|
74
|
+
"bankAccount",
|
|
75
|
+
"account_number",
|
|
76
|
+
"accountNumber",
|
|
77
|
+
"ifsc",
|
|
78
|
+
"upi",
|
|
79
|
+
"wallet",
|
|
80
|
+
// our own passthrough channel
|
|
81
|
+
"notes",
|
|
82
|
+
"metaInfo",
|
|
83
|
+
"udf1",
|
|
84
|
+
"udf2",
|
|
85
|
+
"udf3",
|
|
86
|
+
"udf4",
|
|
87
|
+
"udf5",
|
|
88
|
+
// credentials
|
|
89
|
+
"token",
|
|
90
|
+
"access_token",
|
|
91
|
+
"accessToken",
|
|
92
|
+
"refresh_token",
|
|
93
|
+
"secret",
|
|
94
|
+
"signature",
|
|
95
|
+
"password",
|
|
96
|
+
]);
|
|
97
|
+
function redactValue(value) {
|
|
98
|
+
if (value === null || value === undefined)
|
|
99
|
+
return value;
|
|
100
|
+
if (value instanceof Date)
|
|
101
|
+
return value;
|
|
102
|
+
if (typeof value !== "object")
|
|
103
|
+
return value;
|
|
104
|
+
if (Array.isArray(value))
|
|
105
|
+
return value.map(redactValue);
|
|
106
|
+
const out = {};
|
|
107
|
+
for (const [key, child] of Object.entries(value)) {
|
|
108
|
+
if (DENY_KEYS.has(key))
|
|
109
|
+
continue;
|
|
110
|
+
out[key] = redactValue(child);
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Redact, then cap.
|
|
116
|
+
*
|
|
117
|
+
* 🛑 The cap is applied by MEASURING the serialised result and, if it is over
|
|
118
|
+
* budget, replacing it with a marker rather than by slicing the JSON string.
|
|
119
|
+
* A sliced JSON string is not JSON — it would be stored as a truncated blob
|
|
120
|
+
* that no reader can parse, which is a field that looks present and is
|
|
121
|
+
* unusable. A marker plus `payloadTruncated: true` tells the reader exactly
|
|
122
|
+
* what happened, and `bodySha256` still proves what arrived.
|
|
123
|
+
*/
|
|
124
|
+
export function redactGatewayPayload(body) {
|
|
125
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
126
|
+
return { payload: {}, truncated: false };
|
|
127
|
+
}
|
|
128
|
+
const redacted = redactValue(body);
|
|
129
|
+
let serialisedLength;
|
|
130
|
+
try {
|
|
131
|
+
serialisedLength = JSON.stringify(redacted)?.length ?? 0;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
/*
|
|
135
|
+
* A circular structure cannot come from `JSON.parse`, which is the only
|
|
136
|
+
* producer here — but this function is exported and a future caller could
|
|
137
|
+
* hand it something else. Failing to a marker keeps the row writable.
|
|
138
|
+
*/
|
|
139
|
+
return { payload: { redactionFailed: true }, truncated: true };
|
|
140
|
+
}
|
|
141
|
+
if (serialisedLength > GATEWAY_EVENT_PAYLOAD_MAX_BYTES) {
|
|
142
|
+
return {
|
|
143
|
+
payload: {
|
|
144
|
+
omitted: "payload exceeded the size budget after redaction",
|
|
145
|
+
approximateBytes: serialisedLength,
|
|
146
|
+
},
|
|
147
|
+
truncated: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return { payload: redacted, truncated: false };
|
|
151
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grievance intake — a record, a clock, and a place in the queue.
|
|
3
|
+
*
|
|
4
|
+
* ## What was wrong (D18)
|
|
5
|
+
*
|
|
6
|
+
* There was **no intake at all**: no form, no route, no API endpoint. The
|
|
7
|
+
* public `/grievance` page's only instruction was a `mailto:` to the generic
|
|
8
|
+
* support address, so nothing recorded a grievance, timestamped it, or tracked
|
|
9
|
+
* any statutory clock. `/report` is for scam and entity reports, not money.
|
|
10
|
+
*
|
|
11
|
+
* ## What this builds, and what it deliberately does NOT
|
|
12
|
+
*
|
|
13
|
+
* Built: the record, the acknowledgement timestamp, the resolution deadline,
|
|
14
|
+
* the structured disputed amount, and a `paymentCases` row so the deadline sits
|
|
15
|
+
* in the one queue an operator already reads.
|
|
16
|
+
*
|
|
17
|
+
* 🛑 **NOT built: anything published.** The Consumer Protection (E-Commerce)
|
|
18
|
+
* Rules 2020 require a NAMED individual as grievance officer, and this project
|
|
19
|
+
* publishes the business name with designation "Support" and the same generic
|
|
20
|
+
* support email — so there is no escalation tier (D17). It also publishes no
|
|
21
|
+
* timeline anywhere.
|
|
22
|
+
*
|
|
23
|
+
* I am not asserting the statutory periods from memory, and the reason is
|
|
24
|
+
* asymmetric: **a published timeline we then miss is worse than the present
|
|
25
|
+
* silence.** So the clock runs against `GRIEVANCE_PERIODS`, whose defaults are
|
|
26
|
+
* deliberately shorter than any plausible statutory period — firing early is
|
|
27
|
+
* harmless, firing late is the thing the regime punishes — and the public page
|
|
28
|
+
* stays untouched until the owner supplies the officer and the real numbers.
|
|
29
|
+
*/
|
|
30
|
+
import { type ActionResult } from "../../../shared/types/action-result";
|
|
31
|
+
export interface FileGrievanceInput {
|
|
32
|
+
subject: string;
|
|
33
|
+
detail: string;
|
|
34
|
+
/** The order it concerns, when it concerns one. */
|
|
35
|
+
orderId?: string;
|
|
36
|
+
/** Decimal rupees in dispute. Structured so the queue can sort by it. */
|
|
37
|
+
disputedAmount?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* File a grievance.
|
|
41
|
+
*
|
|
42
|
+
* 🛑 The acknowledgement is stamped at intake, SYNCHRONOUSLY, and that is the
|
|
43
|
+
* one thing here that must not be best-effort. An acknowledgement timestamp is
|
|
44
|
+
* the evidence that we received the complaint on the day we say we did; writing
|
|
45
|
+
* it fire-and-forget would mean the clock's start could silently fail to
|
|
46
|
+
* record, and a clock whose start is unknown cannot be shown to have been met.
|
|
47
|
+
*
|
|
48
|
+
* The `paymentCases` row IS best-effort, because it is a convenience surface —
|
|
49
|
+
* losing it costs visibility, not evidence.
|
|
50
|
+
*/
|
|
51
|
+
export declare function fileGrievanceAction(input: FileGrievanceInput): Promise<ActionResult<{
|
|
52
|
+
ticketId: string;
|
|
53
|
+
dueAt: string;
|
|
54
|
+
}>>;
|
|
55
|
+
/**
|
|
56
|
+
* Stamp the first human response.
|
|
57
|
+
*
|
|
58
|
+
* 🛑 Separate from the ticket's ordinary reply path on purpose: it is
|
|
59
|
+
* write-once (`!existing.firstResponseAt`), so a second reply cannot move the
|
|
60
|
+
* metric forward and make a slow first response look fast. The same
|
|
61
|
+
* write-once discipline `resolvedAt` already uses.
|
|
62
|
+
*/
|
|
63
|
+
export declare function recordGrievanceFirstResponseAction(ticketId: string): Promise<ActionResult<void>>;
|