@mohasinac/appkit 4.53.0 → 4.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/scaffolds/AppShell.js +1 -1
- package/dist/_internal/server/features/auctions/og.d.ts +3 -1
- package/dist/_internal/server/features/auctions/og.js +3 -1
- package/dist/_internal/server/features/bundles/actions.js +1 -1
- package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
- package/dist/_internal/server/features/business-documents/download.js +91 -0
- package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
- package/dist/_internal/server/features/categories/adapters.js +10 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
- package/dist/_internal/server/features/checkout/actions.js +106 -12
- package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
- package/dist/_internal/server/features/checkout/data.d.ts +22 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
- package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
- package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
- package/dist/_internal/server/features/classified/metadata.js +1 -1
- package/dist/_internal/server/features/classified/og.d.ts +3 -1
- package/dist/_internal/server/features/classified/og.js +3 -1
- package/dist/_internal/server/features/digital-code/metadata.js +1 -1
- package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
- package/dist/_internal/server/features/digital-code/og.js +3 -1
- package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
- package/dist/_internal/server/features/digital-code/pool.js +19 -4
- package/dist/_internal/server/features/live/metadata.js +1 -1
- package/dist/_internal/server/features/live/og.d.ts +3 -1
- package/dist/_internal/server/features/live/og.js +3 -1
- package/dist/_internal/server/features/messages/actions.js +1 -1
- package/dist/_internal/server/features/parts/og.js +1 -1
- package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
- package/dist/_internal/server/features/pre-orders/og.js +3 -1
- package/dist/_internal/server/features/price-index/search.d.ts +18 -0
- package/dist/_internal/server/features/price-index/search.js +47 -7
- package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
- package/dist/_internal/server/features/prize-draws/og.js +3 -1
- package/dist/_internal/server/features/products/group-members.d.ts +26 -0
- package/dist/_internal/server/features/products/group-members.js +7 -1
- package/dist/_internal/server/features/products/og.d.ts +3 -1
- package/dist/_internal/server/features/products/og.js +3 -1
- package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
- package/dist/_internal/server/features/search/scoped-sources.js +252 -0
- package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
- package/dist/_internal/server/features/search/search-everything.js +70 -20
- package/dist/_internal/server/features/seo/sitemap.js +6 -0
- package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
- package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
- package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
- package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
- package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
- package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +16 -0
- package/dist/_internal/server/features/stores/index.d.ts +1 -0
- package/dist/_internal/server/features/stores/index.js +1 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +25 -0
- package/dist/_internal/server/features/stores/tabs.js +122 -0
- package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
- package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
- package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
- package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
- package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
- package/dist/_internal/shared/checkout/rules/index.js +9 -0
- package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
- package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
- package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
- package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
- package/dist/_internal/shared/config/schema.d.ts +122 -0
- package/dist/_internal/shared/features/cart/schema.js +3 -2
- package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
- package/dist/_internal/shared/features/products/group-rules.js +142 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
- package/dist/_internal/shared/listing-types/_registry.js +39 -1
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +2 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +2 -0
- package/dist/_internal/shared/listing-types/availability.js +48 -0
- package/dist/_internal/shared/listing-types/capabilities.js +19 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +2 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +2 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
- package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
- package/dist/_internal/shared/listing-types/spares/config.js +60 -0
- package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
- package/dist/_internal/shared/listing-types/spares/og.js +1 -0
- package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
- package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +2 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
- package/dist/_internal/shared/search/match.d.ts +11 -0
- package/dist/_internal/shared/search/match.js +2 -1
- package/dist/_internal/shared/search/to-groups.d.ts +52 -0
- package/dist/_internal/shared/search/to-groups.js +141 -0
- package/dist/client.d.ts +8 -2
- package/dist/client.js +4 -1
- package/dist/constants/api-endpoints.d.ts +39 -15
- package/dist/constants/api-endpoints.js +28 -5
- package/dist/constants/cache-invalidation.js +3 -1
- package/dist/constants/field-names.d.ts +6 -0
- package/dist/constants/field-names.js +6 -0
- package/dist/features/about/components/PolicyPageView.d.ts +22 -2
- package/dist/features/about/components/PolicyPageView.js +14 -2
- package/dist/features/about/components/PublicProfileView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +42 -2
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/hooks/useProfileStats.js +5 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
- package/dist/features/addresses/repository/addresses.repository.js +21 -0
- package/dist/features/admin/actions/admin-actions.js +16 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
- package/dist/features/admin/components/AdminNavigationView.js +1 -1
- package/dist/features/admin/components/AdminPageHeader.js +1 -1
- package/dist/features/admin/components/AdminProductEditorView.js +17 -2
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
- package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
- package/dist/features/admin/components/AdminSparesView.js +30 -0
- package/dist/features/admin/components/AdminUsersView.js +9 -7
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
- package/dist/features/admin/constants/filter-tabs.js +1 -0
- package/dist/features/admin/schemas/firestore.d.ts +26 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +4 -11
- package/dist/features/auth/repository/user.repository.d.ts +13 -0
- package/dist/features/auth/repository/user.repository.js +28 -0
- package/dist/features/auth/schemas/firestore.d.ts +2 -0
- package/dist/features/auth/schemas/firestore.js +2 -0
- package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
- package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
- package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
- package/dist/features/business-documents/schemas/firestore.js +75 -0
- package/dist/features/cart/actions/cart-actions.js +83 -6
- package/dist/features/cart/repository/cart.repository.js +32 -4
- package/dist/features/cart/schemas/firestore.d.ts +20 -0
- package/dist/features/cart/schemas/index.d.ts +2 -2
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
- package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
- package/dist/features/categories/api/[id]/price-guidance/route.js +113 -0
- package/dist/features/categories/components/BrandDetailPageView.js +19 -3
- package/dist/features/categories/components/BrandDetailTabs.js +10 -0
- package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
- package/dist/features/categories/components/CategoryDetailPageView.js +141 -14
- package/dist/features/categories/components/CategoryDetailTabs.js +12 -0
- package/dist/features/categories/components/CategoryGrid.js +10 -1
- package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
- package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
- package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
- package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
- package/dist/features/categories/constants/category-tabs.d.ts +20 -25
- package/dist/features/categories/constants/category-tabs.js +22 -39
- package/dist/features/categories/repository/categories.repository.d.ts +12 -0
- package/dist/features/categories/repository/categories.repository.js +20 -0
- package/dist/features/categories/schemas/firestore.d.ts +22 -1
- package/dist/features/categories/schemas/firestore.js +10 -0
- package/dist/features/categories/schemas/index.d.ts +6 -0
- package/dist/features/categories/schemas/index.js +2 -0
- package/dist/features/categories/types/index.d.ts +8 -0
- package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
- package/dist/features/categories/utils/taxonomy-visual.js +26 -10
- package/dist/features/classified/components/ClassifiedDetailPageView.js +4 -9
- package/dist/features/contact/components/ContactPageView.d.ts +33 -1
- package/dist/features/contact/components/ContactPageView.js +58 -28
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +4 -9
- package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
- package/dist/features/grouped/schemas/firestore.d.ts +5 -0
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
- package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
- package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
- package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
- package/dist/features/homepage/components/HowItWorksSection.js +4 -4
- package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
- package/dist/features/homepage/components/PromoGrid.js +12 -3
- package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
- package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
- package/dist/features/homepage/components/index.d.ts +0 -20
- package/dist/features/homepage/components/index.js +0 -11
- package/dist/features/homepage/index.d.ts +0 -2
- package/dist/features/homepage/index.js +0 -2
- package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
- package/dist/features/homepage/lib/section-renderer.js +202 -58
- package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
- package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
- package/dist/features/homepage/schemas/firestore.d.ts +106 -3
- package/dist/features/homepage/schemas/firestore.js +32 -2
- package/dist/features/homepage/schemas/index.d.ts +36 -7
- package/dist/features/homepage/schemas/index.js +39 -27
- package/dist/features/homepage/server.d.ts +0 -3
- package/dist/features/homepage/server.js +0 -3
- package/dist/features/homepage/types/index.d.ts +0 -47
- package/dist/features/homepage/types/index.js +49 -0
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/BottomNavItem.js +1 -1
- package/dist/features/layout/BottomSheet.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +4 -9
- package/dist/features/media/hooks/useMedia.js +17 -0
- package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
- package/dist/features/media/repository/media-assets.repository.js +31 -0
- package/dist/features/media/trust-signals.d.ts +85 -0
- package/dist/features/media/trust-signals.js +126 -0
- package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
- package/dist/features/parts/components/PartDetailPageView.js +2 -2
- package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
- package/dist/features/parts/components/PartsIndexPageView.js +2 -2
- package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
- package/dist/features/parts/components/PartsSearchBox.js +57 -0
- package/dist/features/parts/schemas/firestore.d.ts +149 -3
- package/dist/features/parts/schemas/firestore.js +184 -0
- package/dist/features/payments/repository/payout.repository.d.ts +15 -0
- package/dist/features/payments/repository/payout.repository.js +21 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -11
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +3 -1
- package/dist/features/price-index/components/PriceIndexToolView.js +92 -13
- package/dist/features/products/components/CompareOverlay.d.ts +18 -1
- package/dist/features/products/components/CompareOverlay.js +21 -2
- package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
- package/dist/features/products/components/CustomSectionTabContent.js +29 -3
- package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
- package/dist/features/products/components/GroupMemberPicker.js +46 -8
- package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
- package/dist/features/products/components/GroupSettingsPanel.js +91 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
- package/dist/features/products/components/ProductDetailPageView.js +74 -15
- package/dist/features/products/components/ProductForm.d.ts +13 -1
- package/dist/features/products/components/ProductForm.js +16 -2
- package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
- package/dist/features/products/components/ProductsIndexListing.js +3 -3
- package/dist/features/products/components/ShowGroupSection.js +15 -2
- package/dist/features/products/components/SparesListView.d.ts +7 -0
- package/dist/features/products/components/SparesListView.js +39 -0
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/repository/products.repository.d.ts +98 -0
- package/dist/features/products/repository/products.repository.js +235 -17
- package/dist/features/products/schemas/firestore.d.ts +97 -4
- package/dist/features/products/schemas/firestore.js +104 -0
- package/dist/features/products/schemas/index.d.ts +10 -19
- package/dist/features/products/schemas/index.js +23 -6
- package/dist/features/products/types/index.d.ts +45 -1
- package/dist/features/products/utils/group-member-image.d.ts +39 -0
- package/dist/features/products/utils/group-member-image.js +3 -0
- package/dist/features/products/utils/listing-badge-variant.js +4 -0
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/scams/components/ScamProfileView.js +1 -1
- package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
- package/dist/features/search/components/EntityTypeahead.js +92 -0
- package/dist/features/search/components/Search.d.ts +12 -1
- package/dist/features/search/components/Search.js +47 -3
- package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
- package/dist/features/search/components/SearchFacetRail.js +92 -0
- package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
- package/dist/features/search/components/SearchResultsClient.js +103 -0
- package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/search/components/index.d.ts +0 -6
- package/dist/features/search/components/index.js +0 -3
- package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
- package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
- package/dist/features/search/hooks/useScopedSearch.js +114 -0
- package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
- package/dist/features/search/hooks/useSiteSearch.js +149 -0
- package/dist/features/search/index.d.ts +0 -4
- package/dist/features/search/index.js +0 -4
- package/dist/features/search/manifest.d.ts +12 -0
- package/dist/features/search/manifest.js +17 -2
- package/dist/features/search/server.d.ts +0 -3
- package/dist/features/search/server.js +0 -3
- package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
- package/dist/features/seller/components/SellerListingTypeView.js +20 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +2 -1
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
- package/dist/features/seller/components/SellerSparesView.js +6 -0
- package/dist/features/seller/components/index.d.ts +2 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/shell/CommandPalette.js +1 -1
- package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
- package/dist/features/stores/components/StoreHeader.js +1 -2
- package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
- package/dist/features/stores/components/StoreSparesPageView.js +48 -0
- package/dist/features/stores/schemas/firestore.d.ts +19 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +280 -45
- package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
- package/dist/features/tester/seed-data/authored/index.js +16 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +372 -18
- package/dist/index.d.ts +17 -57
- package/dist/index.js +35 -96
- package/dist/next/routing/route-map.d.ts +42 -1
- package/dist/next/routing/route-map.js +45 -0
- package/dist/providers/db-firebase/base.repository.d.ts +22 -0
- package/dist/providers/db-firebase/base.repository.js +36 -0
- package/dist/schemas/registry.d.ts +15 -6
- package/dist/security/blind-index-tokens.d.ts +54 -0
- package/dist/security/blind-index-tokens.js +152 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/settings-encryption.d.ts +35 -0
- package/dist/security/settings-encryption.js +91 -0
- package/dist/seed/_helpers/brand-rows.js +38 -14
- package/dist/seed/_helpers/category-forest.js +73 -7
- package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
- package/dist/seed/_helpers/category-images-generated.js +81 -6
- package/dist/seed/_helpers/category-models.d.ts +1 -1
- package/dist/seed/_helpers/category-models.js +7 -1
- package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +50 -2
- package/dist/seed/_helpers/character-features.d.ts +1 -1
- package/dist/seed/_helpers/character-features.js +2 -1
- package/dist/seed/_helpers/franchise-rows.js +10 -2
- package/dist/seed/_helpers/media.d.ts +47 -0
- package/dist/seed/_helpers/media.js +34 -0
- package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
- package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
- package/dist/seed/categories-seed-data.js +124 -20
- package/dist/seed/grouped-listings-seed-data.js +16 -0
- package/dist/seed/homepage-sections-seed-data.js +94 -5
- package/dist/seed/index.d.ts +2 -3
- package/dist/seed/index.js +2 -2
- package/dist/seed/manifest.js +3 -0
- package/dist/seed/media-assets-seed-data.d.ts +1 -1
- package/dist/seed/media-assets-seed-data.js +746 -196
- package/dist/seed/model-composition-seed-data.js +122 -6
- package/dist/seed/part-glossary-seed-data.d.ts +1 -1
- package/dist/seed/part-glossary-seed-data.js +37219 -463
- package/dist/seed/products-art-seed-data.js +7 -18
- package/dist/seed/products-auctions-seed-data.js +56 -47
- package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
- package/dist/seed/products-beyblade-x-seed-data.js +441 -0
- package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
- package/dist/seed/products-catalogue-seed-data.js +1500 -1240
- package/dist/seed/products-classifieds-seed-data.js +9 -27
- package/dist/seed/products-digital-codes-seed-data.js +9 -18
- package/dist/seed/products-live-items-seed-data.js +5 -13
- package/dist/seed/products-preorders-seed-data.js +67 -33
- package/dist/seed/products-prize-draws-seed-data.js +8 -17
- package/dist/seed/products-spares-seed-data.d.ts +2 -0
- package/dist/seed/products-spares-seed-data.js +368 -0
- package/dist/seed/products-standard-seed-data.js +39 -126
- package/dist/seed/products-stickers-seed-data.js +59 -15
- package/dist/seed/site-settings-seed-data.js +8 -0
- package/dist/seo/json-ld.d.ts +25 -0
- package/dist/seo/json-ld.js +28 -0
- package/dist/server-entry.d.ts +3 -1
- package/dist/server-entry.js +2 -2
- package/dist/server.d.ts +9 -9
- package/dist/server.js +27 -25
- package/dist/styles.css +202 -1
- package/dist/tailwind-input.css +31 -17
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Accordion.js +1 -1
- package/dist/ui/components/Breadcrumb.js +1 -1
- package/dist/ui/components/DetailPageGallery.js +0 -2
- package/dist/ui/components/DetailPageHero.js +0 -1
- package/dist/ui/components/DetailPageTabs.js +1 -1
- package/dist/ui/components/Divider.js +3 -3
- package/dist/ui/components/GatedPrice.js +5 -1
- package/dist/ui/components/HorizontalScroller.js +1 -1
- package/dist/ui/components/ListingLayout.js +3 -3
- package/dist/ui/components/MediaTabs.style.css +81 -0
- package/dist/ui/components/Pagination.js +1 -1
- package/dist/ui/components/PendingValue.d.ts +45 -0
- package/dist/ui/components/PendingValue.js +7 -0
- package/dist/ui/components/Radio.style.css +119 -0
- package/dist/ui/components/RatingDisplay.js +2 -2
- package/dist/ui/components/SettingsSection.js +0 -2
- package/dist/ui/components/SiteLogo.js +3 -0
- package/dist/ui/components/SiteMark.js +3 -0
- package/dist/ui/components/SortDropdown.js +1 -1
- package/dist/ui/components/SpecTable.d.ts +58 -0
- package/dist/ui/components/SpecTable.js +8 -0
- package/dist/ui/components/StarRating.js +1 -3
- package/dist/ui/components/SummaryCard.js +1 -1
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +9 -1
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +2 -0
- package/dist/utils/search-txt-query.d.ts +12 -0
- package/dist/utils/search-txt-query.js +12 -0
- package/package.json +1 -1
- package/scripts/dedupe-peer-deps.mjs +18 -0
- package/scripts/seed-cli.mjs +135 -26
- package/scripts/verify-css-build.mjs +118 -37
- package/dist/_internal/server/features/search/actions.d.ts +0 -7
- package/dist/_internal/server/features/search/actions.js +0 -21
- package/dist/_internal/server/features/search/data.d.ts +0 -15
- package/dist/_internal/server/features/search/data.js +0 -18
- package/dist/_internal/server/features/search/index.d.ts +0 -3
- package/dist/_internal/server/features/search/index.js +0 -2
- package/dist/features/homepage/api/[id]/route.d.ts +0 -23
- package/dist/features/homepage/api/[id]/route.js +0 -103
- package/dist/features/homepage/api/route.d.ts +0 -14
- package/dist/features/homepage/api/route.js +0 -140
- package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
- package/dist/features/homepage/api/sections/[id]/route.js +0 -95
- package/dist/features/homepage/api/sections/route.d.ts +0 -13
- package/dist/features/homepage/api/sections/route.js +0 -82
- package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
- package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
- package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
- package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
- package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
- package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
- package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
- package/dist/features/homepage/components/HeroBanner.js +0 -43
- package/dist/features/homepage/components/HeroSection.d.ts +0 -7
- package/dist/features/homepage/components/HeroSection.js +0 -6
- package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
- package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
- package/dist/features/homepage/components/HomepageView.d.ts +0 -25
- package/dist/features/homepage/components/HomepageView.js +0 -5
- package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
- package/dist/features/homepage/components/NewsletterBanner.js +0 -37
- package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
- package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
- package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
- package/dist/features/homepage/components/TrustBadges.js +0 -46
- package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
- package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
- package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
- package/dist/features/homepage/hooks/useHomepage.js +0 -17
- package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
- package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
- package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
- package/dist/features/homepage/repository/homepage.repository.js +0 -25
- package/dist/features/search/actions/index.d.ts +0 -1
- package/dist/features/search/actions/index.js +0 -1
- package/dist/features/search/actions/search-actions.d.ts +0 -18
- package/dist/features/search/actions/search-actions.js +0 -43
- package/dist/features/search/api/route.d.ts +0 -22
- package/dist/features/search/api/route.js +0 -128
- package/dist/features/search/columns/index.d.ts +0 -17
- package/dist/features/search/columns/index.js +0 -49
- package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
- package/dist/features/search/components/SearchFiltersRow.js +0 -21
- package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
- package/dist/features/search/components/SearchResultsSection.js +0 -35
- package/dist/features/search/components/SearchView.d.ts +0 -27
- package/dist/features/search/components/SearchView.js +0 -8
- package/dist/features/search/hooks/useSearch.d.ts +0 -21
- package/dist/features/search/hooks/useSearch.js +0 -33
- package/dist/features/search/repository/search.repository.d.ts +0 -14
- package/dist/features/search/repository/search.repository.js +0 -59
- package/dist/features/search/schemas/index.d.ts +0 -70
- package/dist/features/search/schemas/index.js +0 -36
- package/dist/features/search/types/index.d.ts +0 -48
- package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
- package/dist/features/stores/components/StoreScopedSearch.js +0 -27
- package/dist/seed/defaults/homepage-sections.d.ts +0 -2
- package/dist/seed/defaults/homepage-sections.js +0 -60
- package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
- package/dist/seed/factories/homepage-section.factory.js +0 -13
- /package/dist/{features/search/types/index.js → _internal/shared/listing-types/spares/og.d.ts} +0 -0
package/dist/.build-stamp.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"stampVersion": 1,
|
|
3
|
-
"version": "4.53.
|
|
4
|
-
"head": "
|
|
3
|
+
"version": "4.53.1",
|
|
4
|
+
"head": "d45b9f65d4b73c4c4a079a8ee5553600ddd0c251",
|
|
5
5
|
"branch": "main",
|
|
6
6
|
"dirty": false,
|
|
7
|
-
"builtAt": "2026-10-
|
|
7
|
+
"builtAt": "2026-10-19T21:36:59.723Z"
|
|
8
8
|
}
|
|
@@ -6,5 +6,5 @@ export function AppShell({ children, slotHeader, slotFooter, slotSidebar, render
|
|
|
6
6
|
const header = renderHeader ? renderHeader(ctx) : slotHeader;
|
|
7
7
|
const footer = renderFooter ? renderFooter(ctx) : slotFooter;
|
|
8
8
|
const sidebar = renderSidebar ? renderSidebar(ctx) : slotSidebar;
|
|
9
|
-
return (_jsxs(Stack, { className: `
|
|
9
|
+
return (_jsxs(Stack, { className: `min-h-screen ${className ?? ""}`, children: [header ? (_jsx(BlockHeader, { className: "sticky top-0 z-30 w-full", "data-appshell-slot": "header", children: header })) : null, _jsxs(Div, { layout: "flex", className: "flex-1", children: [sidebar ? (_jsx(Aside, { className: "hidden lg:block lg:w-64 lg:shrink-0", "data-appshell-slot": "sidebar", children: sidebar })) : null, _jsx(Main, { className: "min-w-0 flex-1", "data-appshell-slot": "main", children: children })] }), footer ? (_jsx(BlockFooter, { className: "w-full", "data-appshell-slot": "footer", children: footer })) : null] }));
|
|
10
10
|
}
|
|
@@ -8,7 +8,9 @@ interface AuctionDocLike {
|
|
|
8
8
|
title?: string | null;
|
|
9
9
|
auctionEndDate?: Date | string | null;
|
|
10
10
|
mainImage?: string | null;
|
|
11
|
-
images?: (
|
|
11
|
+
images?: ({
|
|
12
|
+
url: string;
|
|
13
|
+
} | string | null | undefined)[] | null;
|
|
12
14
|
}
|
|
13
15
|
/** High-level OG renderer — accepts the raw auction document from `getAuctionForDetail`. */
|
|
14
16
|
export declare function renderAuctionOg(doc: AuctionDocLike | null | undefined, opts: {
|
|
@@ -14,7 +14,9 @@ export function renderAuctionOg(doc, opts) {
|
|
|
14
14
|
return renderAuctionOgImage({
|
|
15
15
|
title: doc?.title ?? "Live Auction",
|
|
16
16
|
endsLabel,
|
|
17
|
-
imageUrl: resolveOgImageUrl(doc?.mainImage ||
|
|
17
|
+
imageUrl: resolveOgImageUrl(doc?.mainImage ||
|
|
18
|
+
(typeof doc?.images?.[0] === "string" ? doc.images[0] : doc?.images?.[0]?.url) ||
|
|
19
|
+
null, opts.baseUrl),
|
|
18
20
|
}, opts.siteName);
|
|
19
21
|
}
|
|
20
22
|
export function renderAuctionOgImage(data, siteName) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a statutory business document — GST certificate, Udyam registration,
|
|
3
|
+
* bank passbook — and decrypts it for an admin.
|
|
4
|
+
*
|
|
5
|
+
* 🛑 WHY THIS EXISTS rather than a `/media/{slug}` URL. Same two facts that
|
|
6
|
+
* forced `digital-code/download.ts` into the same shape, and either alone is
|
|
7
|
+
* enough:
|
|
8
|
+
*
|
|
9
|
+
* 1. `storage.rules` grants `allow read: if true` on EVERY object in the
|
|
10
|
+
* bucket. All real access goes through the Admin SDK, so that is an
|
|
11
|
+
* acceptable safety net — but it means Storage itself protects nothing.
|
|
12
|
+
* 2. `GET /api/media/[...slug]` performs NO authentication. It is a bare
|
|
13
|
+
* handler with no session read, deliberately, because its job is public
|
|
14
|
+
* product photography.
|
|
15
|
+
*
|
|
16
|
+
* So the bytes live under a private prefix that mints no `mediaAssets` row —
|
|
17
|
+
* and the row IS the public slug, so no row means no public URL — and are read
|
|
18
|
+
* only from here, after the route has checked the caller is an admin.
|
|
19
|
+
*
|
|
20
|
+
* On top of that, and unlike the digital-code case, these objects are stored
|
|
21
|
+
* ENCRYPTED: a bank passbook sitting in plaintext in a bucket whose rules say
|
|
22
|
+
* `read: if true` would depend entirely on nobody ever minting a slug for it.
|
|
23
|
+
*/
|
|
24
|
+
export interface BusinessDocumentResolution {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
status?: number;
|
|
27
|
+
message?: string;
|
|
28
|
+
buffer?: Buffer;
|
|
29
|
+
contentType?: string;
|
|
30
|
+
fileName?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve, download and decrypt the document with id `documentId`.
|
|
34
|
+
*
|
|
35
|
+
* The caller is responsible for the authorization check — the route wraps this
|
|
36
|
+
* in `createRouteHandler({ roles: ROLES_ADMIN_ONLY, permission: "settings:write" })`.
|
|
37
|
+
* This function deliberately takes no viewer: there is no per-row ownership to
|
|
38
|
+
* evaluate, so inventing a `viewerUid` parameter it never reads would imply a
|
|
39
|
+
* check that is not happening here.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveBusinessDocument(documentId: string): Promise<BusinessDocumentResolution>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a statutory business document — GST certificate, Udyam registration,
|
|
3
|
+
* bank passbook — and decrypts it for an admin.
|
|
4
|
+
*
|
|
5
|
+
* 🛑 WHY THIS EXISTS rather than a `/media/{slug}` URL. Same two facts that
|
|
6
|
+
* forced `digital-code/download.ts` into the same shape, and either alone is
|
|
7
|
+
* enough:
|
|
8
|
+
*
|
|
9
|
+
* 1. `storage.rules` grants `allow read: if true` on EVERY object in the
|
|
10
|
+
* bucket. All real access goes through the Admin SDK, so that is an
|
|
11
|
+
* acceptable safety net — but it means Storage itself protects nothing.
|
|
12
|
+
* 2. `GET /api/media/[...slug]` performs NO authentication. It is a bare
|
|
13
|
+
* handler with no session read, deliberately, because its job is public
|
|
14
|
+
* product photography.
|
|
15
|
+
*
|
|
16
|
+
* So the bytes live under a private prefix that mints no `mediaAssets` row —
|
|
17
|
+
* and the row IS the public slug, so no row means no public URL — and are read
|
|
18
|
+
* only from here, after the route has checked the caller is an admin.
|
|
19
|
+
*
|
|
20
|
+
* On top of that, and unlike the digital-code case, these objects are stored
|
|
21
|
+
* ENCRYPTED: a bank passbook sitting in plaintext in a bucket whose rules say
|
|
22
|
+
* `read: if true` would depend entirely on nobody ever minting a slug for it.
|
|
23
|
+
*/
|
|
24
|
+
import { getAdminStorage } from "../../../../providers/db-firebase/index.js";
|
|
25
|
+
import { normalizeError } from "../../../../errors/normalize.js";
|
|
26
|
+
import { decryptBuffer, isBufferEncrypted } from "../../../../security/settings-encryption.js";
|
|
27
|
+
import { businessDocumentsRepository } from "../../../../features/business-documents/repository/business-documents.repository.js";
|
|
28
|
+
/**
|
|
29
|
+
* Resolve, download and decrypt the document with id `documentId`.
|
|
30
|
+
*
|
|
31
|
+
* The caller is responsible for the authorization check — the route wraps this
|
|
32
|
+
* in `createRouteHandler({ roles: ROLES_ADMIN_ONLY, permission: "settings:write" })`.
|
|
33
|
+
* This function deliberately takes no viewer: there is no per-row ownership to
|
|
34
|
+
* evaluate, so inventing a `viewerUid` parameter it never reads would imply a
|
|
35
|
+
* check that is not happening here.
|
|
36
|
+
*/
|
|
37
|
+
export async function resolveBusinessDocument(documentId) {
|
|
38
|
+
const record = await businessDocumentsRepository.findById(documentId);
|
|
39
|
+
if (!record) {
|
|
40
|
+
return { ok: false, status: 404, message: "Document not found" };
|
|
41
|
+
}
|
|
42
|
+
let stored;
|
|
43
|
+
try {
|
|
44
|
+
const file = getAdminStorage().bucket().file(record.storagePath);
|
|
45
|
+
const [buffer] = await file.download();
|
|
46
|
+
stored = buffer;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
void normalizeError(err);
|
|
50
|
+
return { ok: false, status: 404, message: "Document is no longer available" };
|
|
51
|
+
}
|
|
52
|
+
/*
|
|
53
|
+
* Branch on the RECORDED flag, never on sniffing the buffer.
|
|
54
|
+
*
|
|
55
|
+
* `isBufferEncrypted` is used only to catch disagreement: a row that claims
|
|
56
|
+
* encryption over an object that has none means the upload half-failed or
|
|
57
|
+
* the object was replaced by hand, and handing those bytes back as though
|
|
58
|
+
* nothing were wrong is how a plaintext passbook gets served while every log
|
|
59
|
+
* line says it was encrypted. Refuse and say which way the mismatch runs.
|
|
60
|
+
*/
|
|
61
|
+
if (record.encrypted !== isBufferEncrypted(stored)) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
status: 500,
|
|
65
|
+
message: record.encrypted
|
|
66
|
+
? "Stored document is not encrypted but its record says it is — refusing to serve it."
|
|
67
|
+
: "Stored document is encrypted but its record says it is not — refusing to guess.",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
let plaintext;
|
|
71
|
+
if (record.encrypted) {
|
|
72
|
+
try {
|
|
73
|
+
plaintext = decryptBuffer(stored);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
// A GCM auth failure means the wrong key or a tampered object. Both are
|
|
77
|
+
// worth a 500 and a log; neither is worth telling the caller which.
|
|
78
|
+
void normalizeError(err);
|
|
79
|
+
return { ok: false, status: 500, message: "Document could not be decrypted" };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
plaintext = stored;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
buffer: plaintext,
|
|
88
|
+
contentType: record.contentType || "application/octet-stream",
|
|
89
|
+
fileName: record.fileName || "document",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -31,7 +31,7 @@ import type { CategoryItem } from "../../../../features/categories/types/index";
|
|
|
31
31
|
* the object literal in `toCategoryListItem` — the audit asserts both
|
|
32
32
|
* directions.
|
|
33
33
|
*/
|
|
34
|
-
export declare const PUBLIC_CATEGORY_FIELDS: readonly ["id", "name", "slug", "description", "rootId", "parentIds", "childrenIds", "tier", "path", "order", "isLeaf", "ancestors", "categoryType", "itemCode", "isFeatured", "featuredPriority", "seo", "display", "isActive", "highlights", "faqs", "brandWebsite", "brandCountry", "brandFounded", "metrics", "createdAt", "updatedAt"];
|
|
34
|
+
export declare const PUBLIC_CATEGORY_FIELDS: readonly ["id", "name", "slug", "description", "rootId", "parentIds", "childrenIds", "tier", "path", "order", "isLeaf", "ancestors", "categoryType", "itemCode", "isFeatured", "featuredPriority", "seo", "display", "isActive", "highlights", "faqs", "brandWebsite", "brandCountry", "brandFounded", "hasbroName", "hasbroCoverImage", "metrics", "createdAt", "updatedAt"];
|
|
35
35
|
/**
|
|
36
36
|
* Fields withheld, each with the reason. Never remove one without replacing
|
|
37
37
|
* the reason — "it seemed unused" is how a field ends up public by accident.
|
|
@@ -37,6 +37,14 @@ export const PUBLIC_CATEGORY_FIELDS = [
|
|
|
37
37
|
"brandWebsite",
|
|
38
38
|
"brandCountry",
|
|
39
39
|
"brandFounded",
|
|
40
|
+
/*
|
|
41
|
+
* The Hasbro alternate name and its cover, rendered as "Also known as" by
|
|
42
|
+
* `CategoryDetailPageView`. Public because the whole point of the field is
|
|
43
|
+
* that a buyer who only knows the Hasbro season name can SEE the
|
|
44
|
+
* correspondence — a name on a toy box is not sensitive.
|
|
45
|
+
*/
|
|
46
|
+
"hasbroName",
|
|
47
|
+
"hasbroCoverImage",
|
|
40
48
|
// Rollup counts only — see `metrics` in the private list.
|
|
41
49
|
"metrics",
|
|
42
50
|
"createdAt",
|
|
@@ -217,6 +225,8 @@ export function toCategoryListItem(doc) {
|
|
|
217
225
|
brandWebsite: doc.brandWebsite,
|
|
218
226
|
brandCountry: doc.brandCountry,
|
|
219
227
|
brandFounded: doc.brandFounded,
|
|
228
|
+
hasbroName: doc.hasbroName,
|
|
229
|
+
hasbroCoverImage: doc.hasbroCoverImage,
|
|
220
230
|
metrics: toPublicMetrics(doc.metrics),
|
|
221
231
|
// 🛑 `CategoryDocument` stores Dates; `CategoryItem` declares strings —
|
|
222
232
|
// this projection is also the serialisation boundary. Converting here
|
|
@@ -39,6 +39,20 @@ export declare function resolveShippingCost(storeId: string | undefined): Promis
|
|
|
39
39
|
storeState: string | undefined;
|
|
40
40
|
/** C5b — the resolved policy, so a caller can quote with the cart in hand. */
|
|
41
41
|
shippingPolicy: StoreShippingPolicy;
|
|
42
|
+
/**
|
|
43
|
+
* The seller's own tax registration, from the store read this function
|
|
44
|
+
* already performs.
|
|
45
|
+
*
|
|
46
|
+
* Returned here rather than re-fetched in `createOrderForGroup` so the
|
|
47
|
+
* checkout path pays no extra Firestore read for data already in scope
|
|
48
|
+
* (Rule #6). Raw fields, not a resolved identity — the seller-vs-platform
|
|
49
|
+
* decision belongs to `resolveSupplierIdentity` alone.
|
|
50
|
+
*/
|
|
51
|
+
storeTaxIdentity: {
|
|
52
|
+
gstin?: string;
|
|
53
|
+
legalName?: string;
|
|
54
|
+
registeredAddress?: string;
|
|
55
|
+
} | undefined;
|
|
42
56
|
/**
|
|
43
57
|
* 🛑 True when the mode is `estimate` and no carrier rate was supplied.
|
|
44
58
|
*
|
|
@@ -38,11 +38,13 @@ import { CHECKOUT_DEFAULT_COMMISSIONS, CHECKOUT_DEFAULT_EMI_SETTINGS } from "../
|
|
|
38
38
|
import { checkEmiEligibility, computeEmiSchedule } from "../../../shared/features/emi/schedule.js";
|
|
39
39
|
import { formatShippingAddress } from "./data.js";
|
|
40
40
|
import { enforceMaxPerUserForCart } from "./prize-bundle-gates.js";
|
|
41
|
-
import { getListingRule, runSyncPreflight, } from "../../../shared/checkout/rules/index.js";
|
|
41
|
+
import { getListingRule, runSyncPreflight, assertPaymentMethodAllowed, } from "../../../shared/checkout/rules/index.js";
|
|
42
|
+
import { revalidateGroupLines } from "./group-rule-revalidation.js";
|
|
42
43
|
import { cartIsDigitalOnly } from "../../../shared/listing-types/cart-shipping.js";
|
|
43
44
|
import { processRefundAction } from "../refunds/actions.js";
|
|
44
45
|
import { sendNotification } from "../../../../features/admin/actions/notification-actions.js";
|
|
45
46
|
import { resolveDisplayedUpiId } from "./upi-resolution.js";
|
|
47
|
+
import { resolveSupplierIdentity } from "./supplier-identity.js";
|
|
46
48
|
import { PAYMENT_WINDOW_MS } from "../../../../features/orders/constants/payment-window.js";
|
|
47
49
|
import { isCheckoutValueOtpVerified } from "../../../../features/checkout/actions/checkout-value-otp-actions.js";
|
|
48
50
|
const AUDIT_LOG_FAIL_MSG = "checkout: audit log failed (non-critical)";
|
|
@@ -193,6 +195,9 @@ export async function resolveShippingCost(storeId) {
|
|
|
193
195
|
shippingPolicy: DEFAULT_STORE_SHIPPING_POLICY,
|
|
194
196
|
shippingUnavailable: false,
|
|
195
197
|
storeProtections: { shipping: false, sale: false },
|
|
198
|
+
// No store, so no seller registration — the caller falls back to the
|
|
199
|
+
// platform's via resolveSupplierIdentity.
|
|
200
|
+
storeTaxIdentity: undefined,
|
|
196
201
|
};
|
|
197
202
|
}
|
|
198
203
|
const store = await storeRepository.findById(storeId);
|
|
@@ -246,6 +251,25 @@ export async function resolveShippingCost(storeId) {
|
|
|
246
251
|
shipping: store?.supportsShippingProtection === true,
|
|
247
252
|
sale: store?.supportsSaleProtection === true,
|
|
248
253
|
},
|
|
254
|
+
/*
|
|
255
|
+
* The seller's own tax registration, returned from the store read this
|
|
256
|
+
* function ALREADY does.
|
|
257
|
+
*
|
|
258
|
+
* Deliberately not a second `storeRepository.findById` in
|
|
259
|
+
* `createOrderForGroup`: that would add one Firestore read per order group
|
|
260
|
+
* to the checkout path for data already sitting in this scope, against
|
|
261
|
+
* Rule #6's round-trip budget. Returning the three raw fields rather than
|
|
262
|
+
* the resolved identity keeps the decision in one place —
|
|
263
|
+
* `resolveSupplierIdentity` — instead of splitting it across a shipping
|
|
264
|
+
* helper and a checkout action.
|
|
265
|
+
*/
|
|
266
|
+
storeTaxIdentity: store
|
|
267
|
+
? {
|
|
268
|
+
gstin: store.gstin,
|
|
269
|
+
legalName: store.legalName,
|
|
270
|
+
registeredAddress: store.registeredAddress,
|
|
271
|
+
}
|
|
272
|
+
: undefined,
|
|
249
273
|
};
|
|
250
274
|
}
|
|
251
275
|
/**
|
|
@@ -475,7 +499,17 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
475
499
|
return expandCartLineToOrderRows(item, product, ctx.productById).map((baseLine) => itemRule.decorateOrderItem(baseLine, product));
|
|
476
500
|
});
|
|
477
501
|
const totalQuantity = group.reduce((sum, { item }) => sum + item.quantity, 0);
|
|
478
|
-
const { shippingFee, storeOwnerId, storeEmiEnabled, storeState, storeProtections } = await resolveShippingCost(firstItem.storeId);
|
|
502
|
+
const { shippingFee, storeOwnerId, storeEmiEnabled, storeState, storeProtections, storeTaxIdentity } = await resolveShippingCost(firstItem.storeId);
|
|
503
|
+
/*
|
|
504
|
+
* Who issues this invoice — the seller if they have their own GSTIN, the
|
|
505
|
+
* platform otherwise. ONE function decides, and it is the only place that
|
|
506
|
+
* knows the order (Root Cause #75: a money rule reachable by copy-paste
|
|
507
|
+
* gets copied, and the copies miss branches added later).
|
|
508
|
+
*
|
|
509
|
+
* Snapshot below and never recomputed: `OrderDocument`'s own comment is
|
|
510
|
+
* that these are the registration "as it stood at the moment of supply".
|
|
511
|
+
*/
|
|
512
|
+
const supplier = resolveSupplierIdentity(storeTaxIdentity, gstSettings ? { gst: gstSettings } : undefined);
|
|
479
513
|
// P-8 GST — sum per-line-item tax (each product may carry its own gstRate),
|
|
480
514
|
// then split the group total into a single cgst/sgst/igst breakdown for the
|
|
481
515
|
// order document. Skipped entirely when GST is off or the buyer's state is
|
|
@@ -671,12 +705,17 @@ async function createOrderForGroup(group, orderType, ctx) {
|
|
|
671
705
|
* `taxableAmount` so the taxed base stays the base the tax was charged on,
|
|
672
706
|
* and recorded here so the invoice's columns can reconcile. */
|
|
673
707
|
exemptAmount: gstBreakdown?.exemptAmount || undefined,
|
|
674
|
-
/*
|
|
675
|
-
* GSTIN is not a tax invoice, and the invoice page cannot read
|
|
676
|
-
* site settings — `gst` is in PRIVATE_SITE_FIELDS. See the schema.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
708
|
+
/* The supplier of record AS IT STANDS NOW. A tax invoice without the
|
|
709
|
+
* supplier's GSTIN is not a tax invoice, and the invoice page cannot read
|
|
710
|
+
* it from site settings — `gst` is in PRIVATE_SITE_FIELDS. See the schema.
|
|
711
|
+
*
|
|
712
|
+
* 🛑 This was `gstSettings.gstin` unconditionally — the PLATFORM's
|
|
713
|
+
* registration on every order ever placed, with no way for a seller to
|
|
714
|
+
* invoice under their own, while the seeded FAQ told buyers otherwise.
|
|
715
|
+
* It now resolves seller-first via `resolveSupplierIdentity`. */
|
|
716
|
+
supplierGstin: supplier.gstin,
|
|
717
|
+
supplierLegalName: supplier.legalName,
|
|
718
|
+
supplierAddress: supplier.address,
|
|
680
719
|
/* Per-rate, for Rule 46. Computed by `sumGroupGst` rather than re-derived
|
|
681
720
|
* downstream — a mixed 5%+18% order cannot produce a compliant invoice
|
|
682
721
|
* from the aggregate triple alone. */
|
|
@@ -873,6 +912,23 @@ export async function createCheckoutOrderAction(input) {
|
|
|
873
912
|
preTxProductById.set(preTxLookup.productIds[i], snap.data());
|
|
874
913
|
}
|
|
875
914
|
}
|
|
915
|
+
/*
|
|
916
|
+
* Payment-method gate — before `enforceMaxPerUserForCart`, which runs count
|
|
917
|
+
* queries, because this answer needs no reads at all.
|
|
918
|
+
*
|
|
919
|
+
* 🛑 `preTxProductById` is the right map and `preTxPairs` below is NOT: that
|
|
920
|
+
* pairing keeps only `getCartItemMemberIds(item)[0]`, so a bundle or grouped
|
|
921
|
+
* line contributes one member and the rest are invisible. The gate walks
|
|
922
|
+
* every member of every line against this map.
|
|
923
|
+
*
|
|
924
|
+
* 🛑 This ONE call site is complete coverage, and that is worth stating so
|
|
925
|
+
* nobody adds a second. Only `cod`/`cash`/`emi` are ever blocked (auction,
|
|
926
|
+
* bundle, prize-draw) and all three arrive here; the Razorpay path is always
|
|
927
|
+
* `"online"`, which no rule blocks — and a throw there would land AFTER
|
|
928
|
+
* capture, taking the buyer's money and then refusing the order. Pre-capture
|
|
929
|
+
* is the only safe place for this, and the method is decided here.
|
|
930
|
+
*/
|
|
931
|
+
assertPaymentMethodAllowed(cartItems, preTxProductById, paymentMethod);
|
|
876
932
|
const preTxPairs = cartItems
|
|
877
933
|
.map((item) => {
|
|
878
934
|
const [firstMember] = getCartItemMemberIds(item);
|
|
@@ -982,6 +1038,28 @@ export async function createCheckoutOrderAction(input) {
|
|
|
982
1038
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.INSUFFICIENT_STOCK);
|
|
983
1039
|
}
|
|
984
1040
|
const { coupons: appliedCoupons, dropped: droppedCoupons } = await revalidateAppliedCoupons(cart.appliedCoupons ?? [], available, uid);
|
|
1041
|
+
/*
|
|
1042
|
+
* The THIRD group-rule enforcement point: the picker mirrors the rules,
|
|
1043
|
+
* `addGroupLineToCart` enforces them at add time, and this re-checks them at
|
|
1044
|
+
* placement. Without it a rule edited after a line was already in a cart is
|
|
1045
|
+
* never re-checked at all, and a quoted discount percentage outlives the rule
|
|
1046
|
+
* it was copied from.
|
|
1047
|
+
*
|
|
1048
|
+
* 🛑 Runs AFTER the stock bucketing, over `available` rather than the whole
|
|
1049
|
+
* cart — a line already dropped for being out of stock does not need its
|
|
1050
|
+
* group rules adjudicated too, and reporting both about one line would be two
|
|
1051
|
+
* explanations for one outcome.
|
|
1052
|
+
*
|
|
1053
|
+
* A blocked line THROWS: its selection is no longer valid and no price is
|
|
1054
|
+
* right for it. A repriced one is surfaced, which is `droppedCoupons`'
|
|
1055
|
+
* discipline — the buyer is on the payment step and cannot fix a seller's
|
|
1056
|
+
* rule change from there.
|
|
1057
|
+
*/
|
|
1058
|
+
const groupRecheck = await revalidateGroupLines(available.map(({ item }) => item));
|
|
1059
|
+
if (groupRecheck.blocked.length > 0) {
|
|
1060
|
+
const first = groupRecheck.blocked[0];
|
|
1061
|
+
throw Object.assign(new ValidationError(`"${first.groupTitle}" can no longer be bought as selected. ${first.reason} Update that line in your cart and try again.`), { code: "GROUP_RULES_CHANGED" });
|
|
1062
|
+
}
|
|
985
1063
|
const orderGroups = splitCartIntoOrderGroups(available);
|
|
986
1064
|
// Admin-bypass orders are immediately paid and processing; generate a shared
|
|
987
1065
|
// transaction reference for all orders in this checkout batch.
|
|
@@ -1039,6 +1117,15 @@ export async function createCheckoutOrderAction(input) {
|
|
|
1039
1117
|
itemCount: orderIds.length,
|
|
1040
1118
|
...(unavailable.length > 0 ? { unavailableItems: unavailable } : {}),
|
|
1041
1119
|
...(droppedCoupons.length > 0 ? { droppedCoupons } : {}),
|
|
1120
|
+
...(groupRecheck.repriced.length > 0
|
|
1121
|
+
? {
|
|
1122
|
+
repricedGroupLines: groupRecheck.repriced.map((r) => ({
|
|
1123
|
+
groupTitle: r.groupTitle,
|
|
1124
|
+
previousPercent: r.previousPercent,
|
|
1125
|
+
currentPercent: r.currentPercent,
|
|
1126
|
+
})),
|
|
1127
|
+
}
|
|
1128
|
+
: {}),
|
|
1042
1129
|
};
|
|
1043
1130
|
}
|
|
1044
1131
|
const EMPTY_PRICING_PREVIEW = {
|
|
@@ -1387,7 +1474,14 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
|
1387
1474
|
// Reuses the same store/seller lookup as the COD/UPI path above instead
|
|
1388
1475
|
// of re-implementing it inline — was two sequential findById calls per
|
|
1389
1476
|
// seller group here, duplicated from resolveShippingCost.
|
|
1390
|
-
const { shippingFee, storeOwnerId, storeState } = await resolveShippingCost(firstItem.storeId);
|
|
1477
|
+
const { shippingFee, storeOwnerId, storeState, storeTaxIdentity } = await resolveShippingCost(firstItem.storeId);
|
|
1478
|
+
/*
|
|
1479
|
+
* Same supplier-of-record rule as `createOrderForGroup`, through the same
|
|
1480
|
+
* function. This path (PhonePe verify / webhook) is the SECOND place an
|
|
1481
|
+
* order is created, and the two have drifted before — add-on fees, and the
|
|
1482
|
+
* whole tax breakdown, were both missing here until C0c defect 1.
|
|
1483
|
+
*/
|
|
1484
|
+
const supplier = resolveSupplierIdentity(storeTaxIdentity, siteSettings ? { gst: siteSettings.gst } : undefined);
|
|
1391
1485
|
const groupLines = group.map(({ item, product }) => ({ itemId: item.itemId, lineTotal: lineTotalFor(item, product) }));
|
|
1392
1486
|
const { couponDiscount, appliedDiscounts } = computeGroupCouponDiscount(appliedCoupons, groupLines, groupTotal, cartSubtotal, firstItem.storeId);
|
|
1393
1487
|
// Mirrors createOrderForGroup — without this the online-payment path wrote
|
|
@@ -1543,9 +1637,9 @@ async function createPhonePeGroupOrder(group, orderType, ctx) {
|
|
|
1543
1637
|
exemptAmount: phonepeGst?.exemptAmount || undefined,
|
|
1544
1638
|
// Our registration at the moment of supply — see the schema. Mirrors the
|
|
1545
1639
|
// COD/UPI path so both produce an invoiceable order.
|
|
1546
|
-
supplierGstin:
|
|
1547
|
-
supplierLegalName:
|
|
1548
|
-
supplierAddress:
|
|
1640
|
+
supplierGstin: supplier.gstin,
|
|
1641
|
+
supplierLegalName: supplier.legalName,
|
|
1642
|
+
supplierAddress: supplier.address,
|
|
1549
1643
|
gstByRate: phonepeGst?.byRate.length ? phonepeGst.byRate : undefined,
|
|
1550
1644
|
gstAmount: (phonepeGst?.gstAmount ?? 0) + platformFeeGst || undefined,
|
|
1551
1645
|
cgst: phonepeGst?.cgst || undefined,
|
|
@@ -60,6 +60,23 @@ export function validateCartItemStock(item, productById, decrements) {
|
|
|
60
60
|
if (!product || product.status !== ProductStatusValues.PUBLISHED) {
|
|
61
61
|
return { productId: member.productId, availableQty: product?.availableQuantity ?? 0 };
|
|
62
62
|
}
|
|
63
|
+
/*
|
|
64
|
+
* 🛑 Made to order: skip the stock CHECK for this member.
|
|
65
|
+
*
|
|
66
|
+
* There is nothing to be short of — the thing is printed or built when the
|
|
67
|
+
* order arrives. `availableQuantity` on such a row goes negative by design
|
|
68
|
+
* and that negative IS the production queue, which is why the check is
|
|
69
|
+
* bypassed here and `getExpandedDecrements` above is deliberately left
|
|
70
|
+
* alone: it still counts, the decrement still lands, and
|
|
71
|
+
* `stock-restore.ts` can still mirror it exactly on a cancellation. Skip
|
|
72
|
+
* the decrement instead and a cancelled order restores stock that was
|
|
73
|
+
* never taken.
|
|
74
|
+
*
|
|
75
|
+
* Placed AFTER the published-status check on purpose: a draft or archived
|
|
76
|
+
* listing is unbuyable whatever its production model.
|
|
77
|
+
*/
|
|
78
|
+
if (product.madeToOrder === true)
|
|
79
|
+
continue;
|
|
63
80
|
// The fallback must be member-weighted too. Left as a bare `item.quantity`
|
|
64
81
|
// it would validate a line needing 3× of a member against demand for 1
|
|
65
82
|
// whenever the cumulative map didn't happen to carry that product.
|
|
@@ -35,4 +35,26 @@ export interface CheckoutOrderResult {
|
|
|
35
35
|
code: string;
|
|
36
36
|
reason: string;
|
|
37
37
|
}[];
|
|
38
|
+
/**
|
|
39
|
+
* Grouped lines whose group DISCOUNT had changed since they were added. The
|
|
40
|
+
* order is priced at the group's current rule and the change surfaced here,
|
|
41
|
+
* for the same reason `droppedCoupons` exists: honouring the stale percent
|
|
42
|
+
* would charge less than the seller now offers, and changing a total the
|
|
43
|
+
* buyer has read without telling them is worse than either.
|
|
44
|
+
*/
|
|
45
|
+
repricedGroupLines?: {
|
|
46
|
+
groupTitle: string;
|
|
47
|
+
previousPercent: number;
|
|
48
|
+
currentPercent: number;
|
|
49
|
+
}[];
|
|
50
|
+
/**
|
|
51
|
+
* Grouped lines whose selection no longer satisfies the group's rules — the
|
|
52
|
+
* seller tightened them after the line was added. REPORTED rather than
|
|
53
|
+
* repriced: "this group now requires item X" cannot be fixed by adjusting a
|
|
54
|
+
* number, so the line is not silently bought.
|
|
55
|
+
*/
|
|
56
|
+
blockedGroupLines?: {
|
|
57
|
+
groupTitle: string;
|
|
58
|
+
reason: string;
|
|
59
|
+
}[];
|
|
38
60
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CartItemDocument } from "../../../../features/cart/schemas/firestore";
|
|
2
|
+
export interface GroupLineRepriced {
|
|
3
|
+
itemId: string;
|
|
4
|
+
groupTitle: string;
|
|
5
|
+
/** What the line was quoted at. */
|
|
6
|
+
previousPercent: number;
|
|
7
|
+
/** What the group's rule says now. */
|
|
8
|
+
currentPercent: number;
|
|
9
|
+
}
|
|
10
|
+
export interface GroupLineBlocked {
|
|
11
|
+
itemId: string;
|
|
12
|
+
groupTitle: string;
|
|
13
|
+
/** Buyer-facing, from `checkGroupRules`. */
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
export interface GroupRuleRevalidation {
|
|
17
|
+
repriced: GroupLineRepriced[];
|
|
18
|
+
blocked: GroupLineBlocked[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Re-validate every multi-member line in the checkout batch.
|
|
22
|
+
*
|
|
23
|
+
* 🛑 A null `rules` — the group is gone, or the read failed — is treated as
|
|
24
|
+
* UNRESTRICTED and the line is left exactly as it is. Failing open is right in
|
|
25
|
+
* both cases: a transient Firestore error must not block a payment, and a
|
|
26
|
+
* deleted group is not a reason to refuse a selection the buyer already made
|
|
27
|
+
* and already had priced.
|
|
28
|
+
*
|
|
29
|
+
* 🛑 Bundle lines are deliberately skipped. A bundle is all-or-nothing with its
|
|
30
|
+
* own locked price — `unitPriceFor`'s bundle branch runs BEFORE its members
|
|
31
|
+
* branch precisely so the discount that IS the bundle is not recomputed from
|
|
32
|
+
* list prices — so self-assembly rules have nothing to say about one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function revalidateGroupLines(items: readonly CartItemDocument[]): Promise<GroupRuleRevalidation>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: A group's rules and its discount are seller-editable, and a cart can sit
|
|
3
|
+
* for days. `addGroupLineToCart` checked both at add time and nothing
|
|
4
|
+
* re-checked them — so a rule tightened afterwards was never applied, and
|
|
5
|
+
* a line kept quoting a discount percentage the seller had since changed.
|
|
6
|
+
* WHAT: The third enforcement point — re-validate every multi-member line
|
|
7
|
+
* against its group's CURRENT rules, immediately before pricing.
|
|
8
|
+
*
|
|
9
|
+
* 🛑 IT REPORTS, IT DOES NOT SILENTLY RESOLVE. A changed discount is the
|
|
10
|
+
* delicate case and it has two wrong answers: honouring the stale percent
|
|
11
|
+
* charges less than the seller now offers, and re-deriving silently changes a
|
|
12
|
+
* total the buyer has already read and agreed to. So the line is repriced to
|
|
13
|
+
* the current rule AND the change is handed back to the caller to surface —
|
|
14
|
+
* exactly what `revalidateAppliedCoupons` does with a lapsed coupon, for the
|
|
15
|
+
* same reason: the buyer is on the payment step and cannot fix a seller's rule
|
|
16
|
+
* change from there, so failing the whole checkout is hostile.
|
|
17
|
+
*
|
|
18
|
+
* 🛑 A rule VIOLATION is different from a repriced discount and is treated
|
|
19
|
+
* differently. "This group now requires item X" cannot be fixed by adjusting a
|
|
20
|
+
* number — the selection itself is no longer valid — so that line is reported
|
|
21
|
+
* as blocked rather than quietly bought.
|
|
22
|
+
*
|
|
23
|
+
* EXPORTS: GroupLineRepriced, GroupLineBlocked, GroupRuleRevalidation,
|
|
24
|
+
* revalidateGroupLines
|
|
25
|
+
*
|
|
26
|
+
* @tag domain:checkout
|
|
27
|
+
* @tag layer:server
|
|
28
|
+
* @tag pattern:none
|
|
29
|
+
* @tag access:server
|
|
30
|
+
* @tag consumers:checkout/actions
|
|
31
|
+
* @tag sideEffects:firestore
|
|
32
|
+
*/
|
|
33
|
+
import { safeRead } from "../../../../errors/safe-read.js";
|
|
34
|
+
import { serverLogger } from "../../../../monitoring/server-logger.js";
|
|
35
|
+
import { productRepository } from "../../../../repositories/index.js";
|
|
36
|
+
import { groupedListingsRepository } from "../../../../features/grouped/repository/grouped-listings.repository.js";
|
|
37
|
+
import { checkGroupRules, readGroupRules, } from "../../../shared/features/products/group-rules.js";
|
|
38
|
+
import { isMultiMemberLine } from "../../../shared/checkout/line-members.js";
|
|
39
|
+
/** One group's rules, from whichever of the two homes owns them. */
|
|
40
|
+
async function rulesFor(item) {
|
|
41
|
+
const groupId = item.groupId;
|
|
42
|
+
if (!groupId)
|
|
43
|
+
return null;
|
|
44
|
+
if (item.groupSource === "grouped-listing") {
|
|
45
|
+
const doc = await safeRead(() => groupedListingsRepository.findById(groupId), {
|
|
46
|
+
route: "/checkout",
|
|
47
|
+
key: "groupedListings.rules",
|
|
48
|
+
fallback: null,
|
|
49
|
+
});
|
|
50
|
+
return doc ? readGroupRules(doc) : null;
|
|
51
|
+
}
|
|
52
|
+
/*
|
|
53
|
+
* A product group's rules live on the PARENT product, and `groupId` IS the
|
|
54
|
+
* parent's slug (`startGroup` sets `groupId = slug`, and products are pure
|
|
55
|
+
* slugs), so this resolves directly.
|
|
56
|
+
*/
|
|
57
|
+
const parent = await safeRead(() => productRepository.findById(groupId), {
|
|
58
|
+
route: "/checkout",
|
|
59
|
+
key: "products.groupParentRules",
|
|
60
|
+
fallback: null,
|
|
61
|
+
});
|
|
62
|
+
return parent ? readGroupRules(parent) : null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Re-validate every multi-member line in the checkout batch.
|
|
66
|
+
*
|
|
67
|
+
* 🛑 A null `rules` — the group is gone, or the read failed — is treated as
|
|
68
|
+
* UNRESTRICTED and the line is left exactly as it is. Failing open is right in
|
|
69
|
+
* both cases: a transient Firestore error must not block a payment, and a
|
|
70
|
+
* deleted group is not a reason to refuse a selection the buyer already made
|
|
71
|
+
* and already had priced.
|
|
72
|
+
*
|
|
73
|
+
* 🛑 Bundle lines are deliberately skipped. A bundle is all-or-nothing with its
|
|
74
|
+
* own locked price — `unitPriceFor`'s bundle branch runs BEFORE its members
|
|
75
|
+
* branch precisely so the discount that IS the bundle is not recomputed from
|
|
76
|
+
* list prices — so self-assembly rules have nothing to say about one.
|
|
77
|
+
*/
|
|
78
|
+
export async function revalidateGroupLines(items) {
|
|
79
|
+
const repriced = [];
|
|
80
|
+
const blocked = [];
|
|
81
|
+
const lines = items.filter((i) => isMultiMemberLine(i) && !i.bundleCategorySlug && i.lineKind === "group");
|
|
82
|
+
if (lines.length === 0)
|
|
83
|
+
return { repriced, blocked };
|
|
84
|
+
for (const item of lines) {
|
|
85
|
+
const rules = await rulesFor(item);
|
|
86
|
+
if (!rules)
|
|
87
|
+
continue;
|
|
88
|
+
const members = item.groupMembers ?? [];
|
|
89
|
+
const violations = checkGroupRules(rules, members.map((m) => ({
|
|
90
|
+
productId: m.productId,
|
|
91
|
+
quantity: m.quantity,
|
|
92
|
+
title: m.title,
|
|
93
|
+
})));
|
|
94
|
+
if (violations.length > 0) {
|
|
95
|
+
blocked.push({
|
|
96
|
+
itemId: item.itemId,
|
|
97
|
+
groupTitle: item.groupTitle ?? item.productTitle,
|
|
98
|
+
reason: violations[0].message,
|
|
99
|
+
});
|
|
100
|
+
/*
|
|
101
|
+
* Reported and NOT repriced. A blocked line's price is irrelevant — the
|
|
102
|
+
* caller is about to refuse it — and repricing it first would leave a
|
|
103
|
+
* stored price for a line that never sold.
|
|
104
|
+
*/
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const quoted = item.groupDiscountPercent ?? 0;
|
|
108
|
+
const current = Math.min(100, Math.max(0, rules.groupDiscountPercent ?? 0));
|
|
109
|
+
if (quoted !== current) {
|
|
110
|
+
repriced.push({
|
|
111
|
+
itemId: item.itemId,
|
|
112
|
+
groupTitle: item.groupTitle ?? item.productTitle,
|
|
113
|
+
previousPercent: quoted,
|
|
114
|
+
currentPercent: current,
|
|
115
|
+
});
|
|
116
|
+
serverLogger.info("checkout: group discount changed since add-to-cart", {
|
|
117
|
+
itemId: item.itemId,
|
|
118
|
+
quoted,
|
|
119
|
+
current,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { repriced, blocked };
|
|
124
|
+
}
|