@openlup/platform 0.17.0 → 0.18.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/CHANGELOG.md +96 -0
- package/README.md +284 -6
- package/dist/catalog/activeSkuSelection.d.ts +2 -0
- package/dist/catalog/activeSkuSelection.js +20 -0
- package/dist/catalog/adminBindings.d.ts +72 -0
- package/dist/catalog/adminBindings.js +1 -0
- package/dist/catalog/adminBundleHandler.d.ts +26 -0
- package/dist/catalog/adminBundleHandler.js +111 -0
- package/dist/catalog/adminBundleReadHandler.d.ts +20 -0
- package/dist/catalog/adminBundleReadHandler.js +185 -0
- package/dist/catalog/adminBundleWritePort.d.ts +85 -0
- package/dist/catalog/adminBundleWritePort.js +1 -0
- package/dist/catalog/bundleAvailability.d.ts +63 -0
- package/dist/catalog/bundleAvailability.js +78 -0
- package/dist/catalog/bundleAvailabilityService.d.ts +65 -0
- package/dist/catalog/bundleAvailabilityService.js +89 -0
- package/dist/catalog/bundleBindings.d.ts +33 -0
- package/dist/catalog/bundleBindings.js +1 -0
- package/dist/catalog/bundleCatalogQueries.d.ts +28 -0
- package/dist/catalog/bundleCatalogQueries.js +84 -0
- package/dist/catalog/bundleCatalogReadPort.d.ts +166 -0
- package/dist/catalog/bundleCatalogReadPort.js +36 -0
- package/dist/catalog/bundleCatalogStore.d.ts +3 -0
- package/dist/catalog/bundleCatalogStore.js +152 -0
- package/dist/catalog/bundleRuleCodes.d.ts +41 -0
- package/dist/catalog/bundleRuleCodes.js +110 -0
- package/dist/catalog/bundleRules.d.ts +69 -0
- package/dist/catalog/bundleRules.js +102 -0
- package/dist/catalog/catalogDraftStore.d.ts +4 -0
- package/dist/catalog/catalogDraftStore.js +63 -0
- package/dist/catalog/catalogSkuEnvelope.d.ts +7 -0
- package/dist/catalog/catalogSkuEnvelope.js +142 -0
- package/dist/catalog/catalogSkuEnvelopeMapper.d.ts +38 -0
- package/dist/catalog/catalogSkuEnvelopeMapper.js +88 -0
- package/dist/catalog/contracts/bundleAdmin.d.ts +143 -0
- package/dist/catalog/contracts/bundleAdmin.js +1 -0
- package/dist/catalog/contracts/bundleAvailability.d.ts +61 -0
- package/dist/catalog/contracts/bundleAvailability.js +1 -0
- package/dist/catalog/contracts/bundleRead.d.ts +223 -0
- package/dist/catalog/contracts/bundleRead.js +1 -0
- package/dist/catalog/contracts/draft.d.ts +79 -0
- package/dist/catalog/contracts/draft.js +47 -0
- package/dist/catalog/contracts/foundation.d.ts +78 -0
- package/dist/catalog/contracts/foundation.js +53 -0
- package/dist/catalog/contracts/index.d.ts +14 -0
- package/dist/catalog/contracts/index.js +6 -0
- package/dist/catalog/contracts/lifecycle.d.ts +77 -0
- package/dist/catalog/contracts/lifecycle.js +1 -0
- package/dist/catalog/contracts/productMediaContracts.d.ts +77 -0
- package/dist/catalog/contracts/productMediaContracts.js +149 -0
- package/dist/catalog/contracts/productTypes.d.ts +54 -0
- package/dist/catalog/contracts/productTypes.js +16 -0
- package/dist/catalog/contracts/resolveProductMedia.d.ts +3 -0
- package/dist/catalog/contracts/resolveProductMedia.js +78 -0
- package/dist/catalog/contracts/sellable.d.ts +23 -0
- package/dist/catalog/contracts/sellable.js +1 -0
- package/dist/catalog/draftHandler.d.ts +25 -0
- package/dist/catalog/draftHandler.js +118 -0
- package/dist/catalog/draftPorts.d.ts +18 -0
- package/dist/catalog/draftPorts.js +8 -0
- package/dist/catalog/draftSchemas.d.ts +423 -0
- package/dist/catalog/draftSchemas.js +89 -0
- package/dist/catalog/draftValidation.d.ts +21 -0
- package/dist/catalog/draftValidation.js +125 -0
- package/dist/catalog/foundationSchemas.d.ts +98 -0
- package/dist/catalog/foundationSchemas.js +110 -0
- package/dist/catalog/index.d.ts +43 -0
- package/dist/catalog/index.js +25 -0
- package/dist/catalog/mediaSourcePack.d.ts +51 -0
- package/dist/catalog/mediaSourcePack.js +247 -0
- package/dist/catalog/productTypeSchemas.d.ts +46 -0
- package/dist/catalog/productTypeSchemas.js +72 -0
- package/dist/catalog/publicationPorts.d.ts +42 -0
- package/dist/catalog/publicationPorts.js +1 -0
- package/dist/catalog/readPorts.d.ts +51 -0
- package/dist/catalog/readPorts.js +1 -0
- package/dist/catalog/referenceCatalogHandler.d.ts +7 -0
- package/dist/catalog/referenceCatalogHandler.js +27 -0
- package/dist/catalog/resolvedSkuProjection.d.ts +88 -0
- package/dist/catalog/resolvedSkuProjection.js +60 -0
- package/dist/catalog/sellableBundleHandler.d.ts +46 -0
- package/dist/catalog/sellableBundleHandler.js +84 -0
- package/dist/catalog/sellableSchemas.d.ts +46 -0
- package/dist/catalog/sellableSchemas.js +27 -0
- package/dist/catalog/sql.d.ts +46 -0
- package/dist/catalog/sql.js +13 -0
- package/dist/catalog/validation.d.ts +2 -0
- package/dist/catalog/validation.js +3 -0
- package/dist/communications/contracts/index.d.ts +1 -0
- package/dist/communications/contracts/marketingSync.d.ts +194 -0
- package/dist/communications/contracts/marketingSync.js +1 -0
- package/dist/communications/index.d.ts +1 -0
- package/dist/communications/klaviyo/consentPush.d.ts +51 -0
- package/dist/communications/klaviyo/consentPush.js +19 -0
- package/dist/communications/klaviyo/index.d.ts +110 -0
- package/dist/communications/klaviyo/index.js +860 -0
- package/dist/communications/newsletterIntegrationContracts.d.ts +3 -3
- package/dist/communications/resend/index.d.ts +40 -0
- package/dist/communications/resend/index.js +310 -0
- package/dist/composition/catalogContribution.d.ts +18 -0
- package/dist/composition/catalogContribution.js +21 -0
- package/dist/composition/index.d.ts +1 -0
- package/dist/composition/index.js +1 -0
- package/dist/identity/customerContracts.d.ts +2 -2
- package/dist/inventory/adminInventory.d.ts +13 -0
- package/dist/inventory/adminInventory.js +214 -0
- package/dist/inventory/contracts/forecast.d.ts +86 -0
- package/dist/inventory/contracts/forecast.js +1 -0
- package/dist/inventory/contracts/index.d.ts +4 -0
- package/dist/inventory/contracts/index.js +1 -0
- package/dist/inventory/contracts/inventory.d.ts +157 -0
- package/dist/inventory/contracts/inventory.js +1 -0
- package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
- package/dist/inventory/contracts/newPurchaseStock.js +1 -0
- package/dist/inventory/contracts/reservation.d.ts +18 -0
- package/dist/inventory/contracts/reservation.js +1 -0
- package/dist/inventory/forecast.d.ts +24 -0
- package/dist/inventory/forecast.js +200 -0
- package/dist/inventory/index.d.ts +13 -0
- package/dist/inventory/index.js +10 -0
- package/dist/inventory/inventoryHandlers.d.ts +53 -0
- package/dist/inventory/inventoryHandlers.js +151 -0
- package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
- package/dist/inventory/newPurchaseStockAvailability.js +75 -0
- package/dist/inventory/ports.d.ts +21 -0
- package/dist/inventory/ports.js +16 -0
- package/dist/inventory/renewalReservation.d.ts +27 -0
- package/dist/inventory/renewalReservation.js +61 -0
- package/dist/inventory/reservationPolicy.d.ts +6 -0
- package/dist/inventory/reservationPolicy.js +19 -0
- package/dist/inventory/schemas.d.ts +137 -0
- package/dist/inventory/schemas.js +112 -0
- package/dist/inventory/sql.d.ts +14 -0
- package/dist/inventory/sql.js +5 -0
- package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
- package/dist/inventory/subscriptionForecastCalculation.js +133 -0
- package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
- package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
- package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
- package/dist/pricing/acquisitionOfferPricing.js +25 -0
- package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
- package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
- package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
- package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
- package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
- package/dist/pricing/acquisitionQuoteBinding.js +12 -0
- package/dist/pricing/acquisitionReadiness.d.ts +31 -0
- package/dist/pricing/acquisitionReadiness.js +117 -0
- package/dist/pricing/acquisitionTerms.d.ts +19 -0
- package/dist/pricing/acquisitionTerms.js +126 -0
- package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
- package/dist/pricing/acquisitionTermsSchemas.js +42 -0
- package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
- package/dist/pricing/catalogFactsProvenance.js +64 -0
- package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
- package/dist/pricing/catalogFactsSchemas.js +58 -0
- package/dist/pricing/commerceOfferPricing.d.ts +25 -0
- package/dist/pricing/commerceOfferPricing.js +139 -0
- package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
- package/dist/pricing/commerceOfferPricingCache.js +44 -0
- package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
- package/dist/pricing/commerceOfferPricingHandler.js +78 -0
- package/dist/pricing/commercePriceAuthority.d.ts +26 -0
- package/dist/pricing/commercePriceAuthority.js +79 -0
- package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
- package/dist/pricing/commercePricingPolicyErrors.js +67 -0
- package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
- package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
- package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
- package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
- package/dist/pricing/commerceQuoteHandler.d.ts +31 -0
- package/dist/pricing/commerceQuoteHandler.js +110 -0
- package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
- package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
- package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
- package/dist/pricing/contracts/acquisitionTerms.js +2 -0
- package/dist/pricing/contracts/authority.d.ts +27 -0
- package/dist/pricing/contracts/authority.js +1 -0
- package/dist/pricing/contracts/evidence.d.ts +33 -0
- package/dist/pricing/contracts/evidence.js +1 -0
- package/dist/pricing/contracts/index.d.ts +17 -0
- package/dist/pricing/contracts/index.js +7 -0
- package/dist/pricing/contracts/money.d.ts +27 -0
- package/dist/pricing/contracts/money.js +1 -0
- package/dist/pricing/contracts/offer.d.ts +52 -0
- package/dist/pricing/contracts/offer.js +4 -0
- package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
- package/dist/pricing/contracts/offerPolicy.js +5 -0
- package/dist/pricing/contracts/policy.d.ts +22 -0
- package/dist/pricing/contracts/policy.js +3 -0
- package/dist/pricing/contracts/provenance.d.ts +24 -0
- package/dist/pricing/contracts/provenance.js +2 -0
- package/dist/pricing/contracts/quote.d.ts +94 -0
- package/dist/pricing/contracts/quote.js +1 -0
- package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
- package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
- package/dist/pricing/dbBackedCommerceQuotePort.d.ts +66 -0
- package/dist/pricing/dbBackedCommerceQuotePort.js +206 -0
- package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
- package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
- package/dist/pricing/index.d.ts +51 -0
- package/dist/pricing/index.js +36 -0
- package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
- package/dist/pricing/offerPolicyReadiness.js +79 -0
- package/dist/pricing/offerPolicySchemas.d.ts +25 -0
- package/dist/pricing/offerPolicySchemas.js +15 -0
- package/dist/pricing/offerPricingSchemas.d.ts +315 -0
- package/dist/pricing/offerPricingSchemas.js +65 -0
- package/dist/pricing/ports.d.ts +13 -0
- package/dist/pricing/ports.js +9 -0
- package/dist/pricing/pricingPolicyToken.d.ts +39 -0
- package/dist/pricing/pricingPolicyToken.js +84 -0
- package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
- package/dist/pricing/quoteAcquisitionTerms.js +4 -0
- package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
- package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
- package/dist/pricing/quotePorts.d.ts +30 -0
- package/dist/pricing/quotePorts.js +20 -0
- package/dist/pricing/quotePrimitives.d.ts +13 -0
- package/dist/pricing/quotePrimitives.js +18 -0
- package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
- package/dist/pricing/quotePromotionEvidence.js +73 -0
- package/dist/pricing/quoteSchemas.d.ts +1176 -0
- package/dist/pricing/quoteSchemas.js +196 -0
- package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
- package/dist/pricing/subscriptionPricePolicy.js +96 -0
- package/dist/pricing/taxProfileGuard.d.ts +2 -0
- package/dist/pricing/taxProfileGuard.js +24 -0
- package/dist/pricing/validation.d.ts +2 -0
- package/dist/pricing/validation.js +3 -0
- package/dist/promotions/adminBindings.d.ts +72 -0
- package/dist/promotions/adminBindings.js +1 -0
- package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
- package/dist/promotions/adminPromotionCodesContracts.js +211 -0
- package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
- package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
- package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
- package/dist/promotions/adminPromotionCodesHandler.js +194 -0
- package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
- package/dist/promotions/adminPromotionsContracts.js +99 -0
- package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
- package/dist/promotions/adminPromotionsDataPort.js +1 -0
- package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
- package/dist/promotions/adminPromotionsHandler.js +47 -0
- package/dist/promotions/contracts/index.d.ts +387 -0
- package/dist/promotions/contracts/index.js +1 -0
- package/dist/promotions/index.d.ts +22 -0
- package/dist/promotions/index.js +21 -0
- package/dist/promotions/promoDataPort.d.ts +47 -0
- package/dist/promotions/promoDataPort.js +14 -0
- package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
- package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
- package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
- package/dist/promotions/promotionBenefitDefinition.js +33 -0
- package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
- package/dist/promotions/promotionClaimSweepPort.js +8 -0
- package/dist/promotions/promotionClaims.d.ts +5 -0
- package/dist/promotions/promotionClaims.js +57 -0
- package/dist/promotions/promotionCodePreview.d.ts +11 -0
- package/dist/promotions/promotionCodePreview.js +154 -0
- package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
- package/dist/promotions/promotionCodeQuotePort.js +1 -0
- package/dist/promotions/promotionQuoteAcceptance.d.ts +65 -0
- package/dist/promotions/promotionQuoteAcceptance.js +202 -0
- package/dist/promotions/promotionQuoteBinding.d.ts +71 -0
- package/dist/promotions/promotionQuoteBinding.js +85 -0
- package/dist/promotions/quoteAutomaticPromotions.d.ts +24 -0
- package/dist/promotions/quoteAutomaticPromotions.js +36 -0
- package/dist/promotions/quotePromoDiscounts.d.ts +32 -0
- package/dist/promotions/quotePromoDiscounts.js +140 -0
- package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
- package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
- package/dist/promotions/quotePromotionV2.d.ts +29 -0
- package/dist/promotions/quotePromotionV2.js +174 -0
- package/dist/promotions/sql.d.ts +14 -0
- package/dist/promotions/sql.js +5 -0
- package/dist/promotions/validation.d.ts +2 -0
- package/dist/promotions/validation.js +3 -0
- package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
- package/dist/shipping/addressCanonLookupHandler.js +105 -0
- package/dist/shipping/addressCanonical.d.ts +2 -0
- package/dist/shipping/addressCanonical.js +26 -0
- package/dist/shipping/addressPorts.d.ts +39 -0
- package/dist/shipping/addressPorts.js +1 -0
- package/dist/shipping/addressSchemas.d.ts +30 -0
- package/dist/shipping/addressSchemas.js +1 -0
- package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
- package/dist/shipping/adminShippingRateHandler.js +64 -0
- package/dist/shipping/contracts/address.d.ts +144 -0
- package/dist/shipping/contracts/address.js +1 -0
- package/dist/shipping/contracts/delivery.d.ts +110 -0
- package/dist/shipping/contracts/delivery.js +1 -0
- package/dist/shipping/contracts/index.d.ts +3 -0
- package/dist/shipping/contracts/index.js +1 -0
- package/dist/shipping/contracts/shippingRate.d.ts +11 -0
- package/dist/shipping/contracts/shippingRate.js +1 -0
- package/dist/shipping/customerAddressesHandler.d.ts +5 -0
- package/dist/shipping/customerAddressesHandler.js +80 -0
- package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
- package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
- package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
- package/dist/shipping/deliverySelectionHandler.js +77 -0
- package/dist/shipping/deliverySelectionPort.d.ts +8 -0
- package/dist/shipping/deliverySelectionPort.js +1 -0
- package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
- package/dist/shipping/deliverySelectionSummary.js +53 -0
- package/dist/shipping/handlerSchemas.d.ts +21 -0
- package/dist/shipping/handlerSchemas.js +1 -0
- package/dist/shipping/index.d.ts +17 -0
- package/dist/shipping/index.js +8 -0
- package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
- package/dist/shipping/pickupPointSearchPort.js +1 -0
- package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
- package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
- package/dist/shipping/shippingRatePorts.d.ts +52 -0
- package/dist/shipping/shippingRatePorts.js +1 -0
- package/package.json +62 -2
- package/release-gates.json +218 -10
- package/sql/20260807140000_read_parity_catalog.sql +94 -0
- package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
- package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
- package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
- package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
- package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
- package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
- package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
- package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
- package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
- package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
- package/src/catalog/activeSkuSelection.ts +20 -0
- package/src/catalog/adminBindings.ts +59 -0
- package/src/catalog/adminBundleHandler.ts +165 -0
- package/src/catalog/adminBundleReadHandler.ts +261 -0
- package/src/catalog/adminBundleWritePort.ts +102 -0
- package/src/catalog/bundleAvailability.ts +129 -0
- package/src/catalog/bundleAvailabilityService.ts +171 -0
- package/src/catalog/bundleBindings.ts +29 -0
- package/src/catalog/bundleCatalogQueries.ts +98 -0
- package/src/catalog/bundleCatalogReadPort.ts +177 -0
- package/src/catalog/bundleCatalogStore.ts +269 -0
- package/src/catalog/bundleRuleCodes.ts +134 -0
- package/src/catalog/bundleRules.ts +153 -0
- package/src/catalog/catalogDraftStore.ts +71 -0
- package/src/catalog/catalogSkuEnvelope.ts +214 -0
- package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
- package/src/catalog/contracts/bundleAdmin.ts +20 -0
- package/src/catalog/contracts/bundleAvailability.ts +5 -0
- package/src/catalog/contracts/bundleRead.ts +13 -0
- package/src/catalog/contracts/draft.ts +69 -0
- package/src/catalog/contracts/foundation.ts +104 -0
- package/src/catalog/contracts/index.ts +14 -0
- package/src/catalog/contracts/lifecycle.ts +74 -0
- package/src/catalog/contracts/productMediaContracts.ts +170 -0
- package/src/catalog/contracts/productTypes.ts +35 -0
- package/src/catalog/contracts/resolveProductMedia.ts +85 -0
- package/src/catalog/contracts/sellable.ts +12 -0
- package/src/catalog/draftHandler.ts +105 -0
- package/src/catalog/draftPorts.ts +18 -0
- package/src/catalog/draftSchemas.ts +88 -0
- package/src/catalog/draftValidation.ts +100 -0
- package/src/catalog/foundationSchemas.ts +123 -0
- package/src/catalog/index.ts +43 -0
- package/src/catalog/mediaSourcePack.ts +219 -0
- package/src/catalog/productTypeSchemas.ts +70 -0
- package/src/catalog/publicationPorts.ts +44 -0
- package/src/catalog/readPorts.ts +61 -0
- package/src/catalog/referenceCatalogHandler.ts +39 -0
- package/src/catalog/resolvedSkuProjection.ts +76 -0
- package/src/catalog/sellableBundleHandler.ts +133 -0
- package/src/catalog/sellableSchemas.ts +33 -0
- package/src/catalog/sql.ts +14 -0
- package/src/catalog/validation.ts +3 -0
- package/src/communications/contracts/index.ts +1 -0
- package/src/communications/contracts/marketingSync.ts +109 -0
- package/src/communications/index.ts +1 -0
- package/src/communications/klaviyo/consentPush.ts +19 -0
- package/src/communications/klaviyo/index.ts +649 -0
- package/src/communications/resend/index.ts +350 -0
- package/src/composition/catalogContribution.ts +32 -0
- package/src/composition/index.ts +1 -0
- package/src/inventory/adminInventory.ts +251 -0
- package/src/inventory/contracts/forecast.ts +96 -0
- package/src/inventory/contracts/index.ts +18 -0
- package/src/inventory/contracts/inventory.ts +14 -0
- package/src/inventory/contracts/newPurchaseStock.ts +23 -0
- package/src/inventory/contracts/reservation.ts +20 -0
- package/src/inventory/forecast.ts +239 -0
- package/src/inventory/index.ts +21 -0
- package/src/inventory/inventoryHandlers.ts +215 -0
- package/src/inventory/newPurchaseStockAvailability.ts +120 -0
- package/src/inventory/ports.ts +46 -0
- package/src/inventory/renewalReservation.ts +88 -0
- package/src/inventory/reservationPolicy.ts +27 -0
- package/src/inventory/schemas.ts +128 -0
- package/src/inventory/sql.ts +5 -0
- package/src/inventory/subscriptionForecastCalculation.ts +157 -0
- package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
- package/src/pricing/acquisitionOfferPricing.ts +38 -0
- package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
- package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
- package/src/pricing/acquisitionQuoteBinding.ts +12 -0
- package/src/pricing/acquisitionReadiness.ts +148 -0
- package/src/pricing/acquisitionTerms.ts +125 -0
- package/src/pricing/acquisitionTermsSchemas.ts +52 -0
- package/src/pricing/catalogFactsProvenance.ts +102 -0
- package/src/pricing/catalogFactsSchemas.ts +69 -0
- package/src/pricing/commerceOfferPricing.ts +224 -0
- package/src/pricing/commerceOfferPricingCache.ts +53 -0
- package/src/pricing/commerceOfferPricingHandler.ts +137 -0
- package/src/pricing/commercePriceAuthority.ts +104 -0
- package/src/pricing/commercePricingPolicyErrors.ts +88 -0
- package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
- package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
- package/src/pricing/commerceQuoteHandler.ts +184 -0
- package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
- package/src/pricing/contracts/acquisitionTerms.ts +26 -0
- package/src/pricing/contracts/authority.ts +44 -0
- package/src/pricing/contracts/evidence.ts +25 -0
- package/src/pricing/contracts/index.ts +17 -0
- package/src/pricing/contracts/money.ts +32 -0
- package/src/pricing/contracts/offer.ts +42 -0
- package/src/pricing/contracts/offerPolicy.ts +17 -0
- package/src/pricing/contracts/policy.ts +26 -0
- package/src/pricing/contracts/provenance.ts +19 -0
- package/src/pricing/contracts/quote.ts +106 -0
- package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
- package/src/pricing/dbBackedCommerceQuotePort.ts +260 -0
- package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
- package/src/pricing/index.ts +52 -0
- package/src/pricing/offerPolicyReadiness.ts +110 -0
- package/src/pricing/offerPolicySchemas.ts +23 -0
- package/src/pricing/offerPricingSchemas.ts +92 -0
- package/src/pricing/ports.ts +17 -0
- package/src/pricing/pricingPolicyToken.ts +119 -0
- package/src/pricing/quoteAcquisitionTerms.ts +10 -0
- package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
- package/src/pricing/quotePorts.ts +58 -0
- package/src/pricing/quotePrimitives.ts +21 -0
- package/src/pricing/quotePromotionEvidence.ts +82 -0
- package/src/pricing/quoteSchemas.ts +230 -0
- package/src/pricing/subscriptionPricePolicy.ts +113 -0
- package/src/pricing/taxProfileGuard.ts +33 -0
- package/src/pricing/validation.ts +3 -0
- package/src/promotions/adminBindings.ts +53 -0
- package/src/promotions/adminPromotionCodesContracts.ts +244 -0
- package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
- package/src/promotions/adminPromotionCodesHandler.ts +254 -0
- package/src/promotions/adminPromotionsContracts.ts +121 -0
- package/src/promotions/adminPromotionsDataPort.ts +48 -0
- package/src/promotions/adminPromotionsHandler.ts +72 -0
- package/src/promotions/contracts/index.ts +387 -0
- package/src/promotions/index.ts +22 -0
- package/src/promotions/promoDataPort.ts +65 -0
- package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
- package/src/promotions/promotionBenefitDefinition.ts +48 -0
- package/src/promotions/promotionClaimSweepPort.ts +23 -0
- package/src/promotions/promotionClaims.ts +70 -0
- package/src/promotions/promotionCodePreview.ts +183 -0
- package/src/promotions/promotionCodeQuotePort.ts +35 -0
- package/src/promotions/promotionQuoteAcceptance.ts +284 -0
- package/src/promotions/promotionQuoteBinding.ts +158 -0
- package/src/promotions/quoteAutomaticPromotions.ts +58 -0
- package/src/promotions/quotePromoDiscounts.ts +185 -0
- package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
- package/src/promotions/quotePromotionV2.ts +223 -0
- package/src/promotions/sql.ts +5 -0
- package/src/promotions/validation.ts +3 -0
- package/src/shipping/addressCanonLookupHandler.ts +114 -0
- package/src/shipping/addressCanonical.ts +31 -0
- package/src/shipping/addressPorts.ts +48 -0
- package/src/shipping/addressSchemas.ts +28 -0
- package/src/shipping/adminShippingRateHandler.ts +72 -0
- package/src/shipping/contracts/address.ts +71 -0
- package/src/shipping/contracts/delivery.ts +114 -0
- package/src/shipping/contracts/index.ts +18 -0
- package/src/shipping/contracts/shippingRate.ts +7 -0
- package/src/shipping/customerAddressesHandler.ts +100 -0
- package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
- package/src/shipping/deliverySelectionHandler.ts +94 -0
- package/src/shipping/deliverySelectionPort.ts +10 -0
- package/src/shipping/deliverySelectionSummary.ts +56 -0
- package/src/shipping/handlerSchemas.ts +19 -0
- package/src/shipping/index.ts +23 -0
- package/src/shipping/pickupPointSearchPort.ts +8 -0
- package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
- package/src/shipping/shippingRatePorts.ts +36 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CATALOG_FACTS_POLICY_PROVENANCE_VERSION, CATALOG_FACTS_PROVENANCE_VERSION } from "./contracts/provenance.js";
|
|
2
|
+
import { catalogFactsProvenanceSchema, } from "./catalogFactsSchemas.js";
|
|
3
|
+
import { CommerceQuoteError } from "./quotePorts.js";
|
|
4
|
+
export function createCatalogFactsProvenance(input) {
|
|
5
|
+
if (input.policy) {
|
|
6
|
+
if (input.mode !== "subscription") {
|
|
7
|
+
throw new CommerceQuoteError("PRICING_INVARIANT_VIOLATION", "One-time quote facts cannot carry a subscription policy");
|
|
8
|
+
}
|
|
9
|
+
return catalogFactsProvenanceSchema.parse({
|
|
10
|
+
version: CATALOG_FACTS_POLICY_PROVENANCE_VERSION,
|
|
11
|
+
skuId: input.item.skuId,
|
|
12
|
+
documentRevisionId: input.item.documentRevision.id,
|
|
13
|
+
documentDigest: input.item.documentRevision.digest,
|
|
14
|
+
basePriceEntryId: input.base.priceEntryId,
|
|
15
|
+
policyRevisionId: input.policy.policyRevisionId,
|
|
16
|
+
policyDigest: input.policy.policyDigest,
|
|
17
|
+
mode: input.mode,
|
|
18
|
+
atTime: input.atTime,
|
|
19
|
+
currency: input.currency,
|
|
20
|
+
resolvedUnitAmountMinor: input.resolvedUnitAmountMinor,
|
|
21
|
+
resolvedLineAmountMinor: input.resolvedLineAmountMinor,
|
|
22
|
+
baseUnitAmountMinor: input.baseUnitAmountMinor,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return catalogFactsProvenanceSchema.parse({
|
|
26
|
+
version: CATALOG_FACTS_PROVENANCE_VERSION,
|
|
27
|
+
skuId: input.item.skuId,
|
|
28
|
+
documentRevisionId: input.item.documentRevision.id,
|
|
29
|
+
documentDigest: input.item.documentRevision.digest,
|
|
30
|
+
resolvedPriceEntryId: input.resolved.priceEntryId,
|
|
31
|
+
basePriceEntryId: input.base.priceEntryId,
|
|
32
|
+
mode: input.mode,
|
|
33
|
+
atTime: input.atTime,
|
|
34
|
+
currency: input.currency,
|
|
35
|
+
resolvedUnitAmountMinor: input.resolvedUnitAmountMinor,
|
|
36
|
+
resolvedLineAmountMinor: input.resolvedLineAmountMinor,
|
|
37
|
+
baseUnitAmountMinor: input.baseUnitAmountMinor,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export function catalogFactsForQuoteLine(enabled, item, resolved, base, mode, atTime, currency, resolvedUnitAmountMinor, resolvedLineAmountMinor, baseUnitAmountMinor, policy) {
|
|
41
|
+
if (!enabled)
|
|
42
|
+
return undefined;
|
|
43
|
+
const { id, digest } = item.documentRevision;
|
|
44
|
+
if (id === null || digest === null) {
|
|
45
|
+
throw new CommerceQuoteError("PRICE_NOT_CONFIGURED", "Strict catalog facts are unavailable", {
|
|
46
|
+
sku: item.skuCode,
|
|
47
|
+
variantId: item.variantId,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return createCatalogFactsProvenance({
|
|
51
|
+
item: { ...item, documentRevision: { id, digest } },
|
|
52
|
+
resolved, base, mode, atTime, currency, resolvedUnitAmountMinor, resolvedLineAmountMinor, baseUnitAmountMinor, policy,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Public responses retain every money field but never server-only catalog identifiers. */
|
|
56
|
+
export function projectPublicQuoteSnapshot(snapshot) {
|
|
57
|
+
return {
|
|
58
|
+
...snapshot,
|
|
59
|
+
quote: {
|
|
60
|
+
...snapshot.quote,
|
|
61
|
+
lines: snapshot.quote.lines.map(({ catalogFacts: _catalogFacts, ...line }) => line),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Server-only evidence for a strict quote line. V1 remains readable for
|
|
4
|
+
* historical and explicitly pre-cutover legacy subscription observations; v2
|
|
5
|
+
* records the policy which derived a current subscription amount.
|
|
6
|
+
*/
|
|
7
|
+
export declare const catalogFactsProvenanceSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
8
|
+
skuId: z.ZodString;
|
|
9
|
+
documentRevisionId: z.ZodString;
|
|
10
|
+
documentDigest: z.ZodString;
|
|
11
|
+
basePriceEntryId: z.ZodString;
|
|
12
|
+
atTime: z.ZodString;
|
|
13
|
+
currency: z.ZodString;
|
|
14
|
+
resolvedUnitAmountMinor: z.ZodNumber;
|
|
15
|
+
resolvedLineAmountMinor: z.ZodNumber;
|
|
16
|
+
baseUnitAmountMinor: z.ZodNumber;
|
|
17
|
+
version: z.ZodLiteral<"catalog_facts_v1">;
|
|
18
|
+
resolvedPriceEntryId: z.ZodString;
|
|
19
|
+
mode: z.ZodEnum<{
|
|
20
|
+
subscription: "subscription";
|
|
21
|
+
one_time: "one_time";
|
|
22
|
+
}>;
|
|
23
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
24
|
+
skuId: z.ZodString;
|
|
25
|
+
documentRevisionId: z.ZodString;
|
|
26
|
+
documentDigest: z.ZodString;
|
|
27
|
+
basePriceEntryId: z.ZodString;
|
|
28
|
+
atTime: z.ZodString;
|
|
29
|
+
currency: z.ZodString;
|
|
30
|
+
resolvedUnitAmountMinor: z.ZodNumber;
|
|
31
|
+
resolvedLineAmountMinor: z.ZodNumber;
|
|
32
|
+
baseUnitAmountMinor: z.ZodNumber;
|
|
33
|
+
version: z.ZodLiteral<"catalog_facts_v2">;
|
|
34
|
+
policyRevisionId: z.ZodString;
|
|
35
|
+
policyDigest: z.ZodString;
|
|
36
|
+
mode: z.ZodLiteral<"subscription">;
|
|
37
|
+
}, z.core.$strict>]>;
|
|
38
|
+
/** Reject server-only provenance at every HTTP response schema boundary. */
|
|
39
|
+
export declare function rejectCatalogFactsFromPublicQuote(response: {
|
|
40
|
+
quote: {
|
|
41
|
+
lines: readonly {
|
|
42
|
+
catalogFacts?: unknown;
|
|
43
|
+
}[];
|
|
44
|
+
};
|
|
45
|
+
}, ctx: z.RefinementCtx, pathPrefix?: Array<string | number>): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CATALOG_FACTS_PROVENANCE_VERSION, CATALOG_FACTS_POLICY_PROVENANCE_VERSION } from "./contracts/provenance.js";
|
|
3
|
+
const catalogFactsFields = {
|
|
4
|
+
skuId: z.string().uuid(),
|
|
5
|
+
documentRevisionId: z.string().uuid(),
|
|
6
|
+
documentDigest: z.string().regex(/^[a-f0-9]{64}$/iu),
|
|
7
|
+
basePriceEntryId: z.string().trim().min(1).max(120),
|
|
8
|
+
atTime: z.string().datetime({ offset: true }),
|
|
9
|
+
currency: z.string().regex(/^[A-Z]{3}$/u),
|
|
10
|
+
resolvedUnitAmountMinor: z.number().int().min(0),
|
|
11
|
+
resolvedLineAmountMinor: z.number().int().min(0),
|
|
12
|
+
baseUnitAmountMinor: z.number().int().min(0),
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Server-only evidence for a quote line resolved from the strict current
|
|
16
|
+
* catalog facts port. V1 records authored price-entry observations; v2
|
|
17
|
+
* records policy-derived subscription money without inventing such an entry.
|
|
18
|
+
*/
|
|
19
|
+
const catalogFactsV1ProvenanceSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
version: z.literal(CATALOG_FACTS_PROVENANCE_VERSION),
|
|
22
|
+
resolvedPriceEntryId: z.string().trim().min(1).max(120),
|
|
23
|
+
mode: z.enum(["one_time", "subscription"]),
|
|
24
|
+
...catalogFactsFields,
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
const catalogFactsV2ProvenanceSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
version: z.literal(CATALOG_FACTS_POLICY_PROVENANCE_VERSION),
|
|
30
|
+
// A policy-derived subscription price has no authored subscription price
|
|
31
|
+
// entry. Its immutable evidence is the exact base anchor plus this policy.
|
|
32
|
+
policyRevisionId: z.string().uuid(),
|
|
33
|
+
policyDigest: z.string().regex(/^[a-f0-9]{64}$/iu),
|
|
34
|
+
mode: z.literal("subscription"),
|
|
35
|
+
...catalogFactsFields,
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
/**
|
|
39
|
+
* Server-only evidence for a strict quote line. V1 remains readable for
|
|
40
|
+
* historical and explicitly pre-cutover legacy subscription observations; v2
|
|
41
|
+
* records the policy which derived a current subscription amount.
|
|
42
|
+
*/
|
|
43
|
+
export const catalogFactsProvenanceSchema = z.union([
|
|
44
|
+
catalogFactsV1ProvenanceSchema,
|
|
45
|
+
catalogFactsV2ProvenanceSchema,
|
|
46
|
+
]);
|
|
47
|
+
/** Reject server-only provenance at every HTTP response schema boundary. */
|
|
48
|
+
export function rejectCatalogFactsFromPublicQuote(response, ctx, pathPrefix = ["quote"]) {
|
|
49
|
+
response.quote.lines.forEach((line, index) => {
|
|
50
|
+
if (line.catalogFacts !== undefined) {
|
|
51
|
+
ctx.addIssue({
|
|
52
|
+
code: z.ZodIssueCode.custom,
|
|
53
|
+
message: "catalog facts are server-only",
|
|
54
|
+
path: [...pathPrefix, "lines", index, "catalogFacts"],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CommerceOfferPricingResponse } from "./contracts/offer.js";
|
|
2
|
+
import { type PricingPolicySnapshot } from "./contracts/offerPolicy.js";
|
|
3
|
+
import type { CreateQuoteRequest } from "./contracts/quote.js";
|
|
4
|
+
import { type CommerceQuotePort } from "./quotePorts.js";
|
|
5
|
+
import type { CommerceQuoteCatalogItem } from "./commerceQuoteCatalogReadPort.js";
|
|
6
|
+
export interface CommerceOfferPricingDeps<Item extends CommerceQuoteCatalogItem, ScopeV1 extends string, ScopeV2 extends string> {
|
|
7
|
+
quoteCatalogReadPort: {
|
|
8
|
+
listQuoteCatalogItems(): Promise<Item[]>;
|
|
9
|
+
};
|
|
10
|
+
quoteWithPromotions: CommerceQuotePort;
|
|
11
|
+
quoteWithoutPromotions: CommerceQuotePort;
|
|
12
|
+
pricingPolicy?: PricingPolicySnapshot;
|
|
13
|
+
isOfferProduct(product: Item): boolean;
|
|
14
|
+
requestFor(product: Item, mode: "one_time" | "subscription"): CreateQuoteRequest;
|
|
15
|
+
unitCount: number;
|
|
16
|
+
minimumProductPayableMinor: number;
|
|
17
|
+
scopeV1: ScopeV1;
|
|
18
|
+
scopeV2: ScopeV2;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Product-only acquisition projection over the same quote ports used by
|
|
22
|
+
* configurator checkout. It never prices delivery and is never accepted as a
|
|
23
|
+
* checkout expectation; its only consumer is non-transactional "from" copy.
|
|
24
|
+
*/
|
|
25
|
+
export declare function projectCommerceOfferPricing<Item extends CommerceQuoteCatalogItem, ScopeV1 extends string, ScopeV2 extends string>({ quoteCatalogReadPort, quoteWithPromotions, quoteWithoutPromotions, pricingPolicy, isOfferProduct, requestFor, unitCount: COMMERCE_MIN_AUTO_ORDER_UNITS, minimumProductPayableMinor: MINIMUM_PRODUCT_PAYABLE_MINOR, scopeV1, scopeV2, }: CommerceOfferPricingDeps<Item, ScopeV1, ScopeV2>): Promise<CommerceOfferPricingResponse<ScopeV1, ScopeV2>>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { COMMERCE_OFFER_PRICING_CONTRACT_VERSION, COMMERCE_OFFER_PRICING_V2_CONTRACT_VERSION, } from "./contracts/offer.js";
|
|
2
|
+
import { OFFER_POLICY_V2, PROMOTION_ENGINE_V2, } from "./contracts/offerPolicy.js";
|
|
3
|
+
import { CommerceQuoteError, } from "./quotePorts.js";
|
|
4
|
+
function recordOfferPricingExclusion(diagnostic) {
|
|
5
|
+
try {
|
|
6
|
+
console.info(JSON.stringify(diagnostic));
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
// A private diagnostic sink must never alter the projection it observes.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Product-only acquisition projection over the same quote ports used by
|
|
14
|
+
* configurator checkout. It never prices delivery and is never accepted as a
|
|
15
|
+
* checkout expectation; its only consumer is non-transactional "from" copy.
|
|
16
|
+
*/
|
|
17
|
+
export async function projectCommerceOfferPricing({ quoteCatalogReadPort, quoteWithPromotions, quoteWithoutPromotions, pricingPolicy, isOfferProduct, requestFor, unitCount: COMMERCE_MIN_AUTO_ORDER_UNITS, minimumProductPayableMinor: MINIMUM_PRODUCT_PAYABLE_MINOR, scopeV1, scopeV2, }) {
|
|
18
|
+
const products = (await quoteCatalogReadPort.listQuoteCatalogItems()).filter(isOfferProduct);
|
|
19
|
+
if (products.length === 0)
|
|
20
|
+
throw new Error("commerce_offer_pricing_unavailable");
|
|
21
|
+
const settled = await Promise.all(products.map(async (product) => {
|
|
22
|
+
try {
|
|
23
|
+
return {
|
|
24
|
+
product,
|
|
25
|
+
entry: await projectProduct(product, quoteWithPromotions, quoteWithoutPromotions, requestFor, COMMERCE_MIN_AUTO_ORDER_UNITS, pricingPolicy),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// One SKU the strict money authority cannot price must not remove every
|
|
30
|
+
// PDP price block. Only that refusal degrades; an infrastructure failure
|
|
31
|
+
// or a violated pricing invariant still refuses the whole projection.
|
|
32
|
+
if (error instanceof CommerceQuoteError && error.code === "PRICE_NOT_CONFIGURED") {
|
|
33
|
+
recordOfferPricingExclusion({
|
|
34
|
+
event: "commerce_offer_pricing_product_excluded",
|
|
35
|
+
product_slug: product.productSlug,
|
|
36
|
+
refusal_code: typeof error.details.reason === "string" ? error.details.reason : "unknown",
|
|
37
|
+
});
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
const priced = settled.flatMap((outcome) => (outcome ? [outcome] : []));
|
|
44
|
+
const projected = priced.map((outcome) => outcome.entry);
|
|
45
|
+
// The minimum below is the customer-facing "from" figure. Over an EMPTY set it
|
|
46
|
+
// would be meaningless, so a projection that priced nothing refuses exactly as
|
|
47
|
+
// an empty catalogue does rather than answering with an invented floor.
|
|
48
|
+
if (projected.length === 0)
|
|
49
|
+
throw new Error("commerce_offer_pricing_unavailable");
|
|
50
|
+
const oneTime = minimumBy(projected, (entry) => entry.oneTime.unitGross.amountMinor);
|
|
51
|
+
const subscription = minimumBy(projected, (entry) => entry.subscriptionInitial.unitGross.amountMinor);
|
|
52
|
+
const legacy = {
|
|
53
|
+
contractVersion: COMMERCE_OFFER_PRICING_CONTRACT_VERSION,
|
|
54
|
+
scope: scopeV1,
|
|
55
|
+
products: projected,
|
|
56
|
+
minimum: { oneTime, subscription },
|
|
57
|
+
};
|
|
58
|
+
if (pricingPolicy?.offerPolicyVersion !== OFFER_POLICY_V2 ||
|
|
59
|
+
pricingPolicy.promotionEngineVersion !== PROMOTION_ENGINE_V2)
|
|
60
|
+
return legacy;
|
|
61
|
+
// The representative quote must come from a product that actually PRICED. Using
|
|
62
|
+
// the catalogue's first product would re-raise the very refusal just excluded
|
|
63
|
+
// and refuse the whole response despite another product having priced.
|
|
64
|
+
const representative = await quoteWithPromotions.createQuote(requestFor(priced[0].product, "one_time"), pricingPolicy ? { pricingPolicy } : undefined);
|
|
65
|
+
// An absent shipping figure is zero in the currency the quote came back in.
|
|
66
|
+
// It used to be zero in a named currency, which is a coercion rather than a
|
|
67
|
+
// default: a quote in one currency would have carried a shipping line in
|
|
68
|
+
// another, and the two would have been summed.
|
|
69
|
+
const quoteCurrency = representative.quote.currency;
|
|
70
|
+
const gross = representative.quote.shippingGross ?? { amountMinor: 0, currency: quoteCurrency };
|
|
71
|
+
const discountGross = representative.quote.shippingDiscountGross
|
|
72
|
+
?? { amountMinor: 0, currency: quoteCurrency };
|
|
73
|
+
return {
|
|
74
|
+
contractVersion: COMMERCE_OFFER_PRICING_V2_CONTRACT_VERSION,
|
|
75
|
+
scope: scopeV2,
|
|
76
|
+
pricingPolicy,
|
|
77
|
+
products: projected,
|
|
78
|
+
minimum: { oneTime, subscription },
|
|
79
|
+
shipping: {
|
|
80
|
+
gross,
|
|
81
|
+
discountGross,
|
|
82
|
+
payable: { amountMinor: gross.amountMinor - discountGross.amountMinor, currency: gross.currency },
|
|
83
|
+
},
|
|
84
|
+
// Both halves come from the injected settlement policy, which supplies the
|
|
85
|
+
// denomination and left the amount, which was one major unit written out as
|
|
86
|
+
// a hundredths-assuming literal.
|
|
87
|
+
minimumProductPayable: {
|
|
88
|
+
amountMinor: MINIMUM_PRODUCT_PAYABLE_MINOR,
|
|
89
|
+
currency: quoteCurrency,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function projectProduct(product, quoteWithPromotions, quoteWithoutPromotions, requestFor, COMMERCE_MIN_AUTO_ORDER_UNITS, pricingPolicy) {
|
|
94
|
+
const options = pricingPolicy ? { pricingPolicy } : undefined;
|
|
95
|
+
const [oneTime, subscriptionInitial, subscriptionRecurring] = await Promise.all([
|
|
96
|
+
quoteWithPromotions.createQuote(requestFor(product, "one_time"), options),
|
|
97
|
+
quoteWithPromotions.createQuote(requestFor(product, "subscription"), options),
|
|
98
|
+
quoteWithoutPromotions.createQuote(requestFor(product, "subscription"), options),
|
|
99
|
+
]);
|
|
100
|
+
const oneTimeProductGross = productGross(oneTime.quote);
|
|
101
|
+
const initialProductGross = productGross(subscriptionInitial.quote);
|
|
102
|
+
const recurringProductGross = productGross(subscriptionRecurring.quote);
|
|
103
|
+
const anchorUnitGross = oneTime.quote.lines[0]?.unitPriceGross;
|
|
104
|
+
if (!anchorUnitGross)
|
|
105
|
+
throw new Error("commerce_offer_pricing_line_missing");
|
|
106
|
+
const anchorPackageGross = anchorUnitGross.amountMinor * COMMERCE_MIN_AUTO_ORDER_UNITS;
|
|
107
|
+
return {
|
|
108
|
+
productSlug: product.productSlug,
|
|
109
|
+
unitCount: COMMERCE_MIN_AUTO_ORDER_UNITS,
|
|
110
|
+
oneTime: price(oneTimeProductGross, oneTime.quote.currency, COMMERCE_MIN_AUTO_ORDER_UNITS),
|
|
111
|
+
subscriptionInitial: price(initialProductGross, subscriptionInitial.quote.currency, COMMERCE_MIN_AUTO_ORDER_UNITS),
|
|
112
|
+
subscriptionRecurring: price(recurringProductGross, subscriptionRecurring.quote.currency, COMMERCE_MIN_AUTO_ORDER_UNITS),
|
|
113
|
+
catalogAnchorUnitGross: anchorUnitGross,
|
|
114
|
+
initialDiscountPercent: discountPercent(anchorPackageGross, initialProductGross),
|
|
115
|
+
recurringDiscountPercent: discountPercent(anchorPackageGross, recurringProductGross),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function productGross(quote) {
|
|
119
|
+
return quote.subtotalGross.amountMinor - quote.discountTotalGross.amountMinor;
|
|
120
|
+
}
|
|
121
|
+
// Carries whatever currency the quote came back in; it is not this helper's job
|
|
122
|
+
// to name one (src/lib/currency/platformCurrency.ts owns that question).
|
|
123
|
+
function price(amountMinor, currency, COMMERCE_MIN_AUTO_ORDER_UNITS) {
|
|
124
|
+
return {
|
|
125
|
+
packageGross: { amountMinor, currency },
|
|
126
|
+
unitGross: {
|
|
127
|
+
amountMinor: Math.round(amountMinor / COMMERCE_MIN_AUTO_ORDER_UNITS),
|
|
128
|
+
currency,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function discountPercent(anchorMinor, effectiveMinor) {
|
|
133
|
+
if (anchorMinor <= 0 || effectiveMinor >= anchorMinor)
|
|
134
|
+
return 0;
|
|
135
|
+
return Math.round(((anchorMinor - effectiveMinor) / anchorMinor) * 100);
|
|
136
|
+
}
|
|
137
|
+
function minimumBy(entries, amount) {
|
|
138
|
+
return entries.reduce((minimum, entry) => amount(entry) < amount(minimum) ? entry : minimum);
|
|
139
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CommerceOfferPricingResponse } from "./contracts/offer.js";
|
|
2
|
+
/** One bounded value and one in-flight load per runtime instance. */
|
|
3
|
+
export interface CommerceOfferPricingCache<Response extends CommerceOfferPricingResponse = CommerceOfferPricingResponse> {
|
|
4
|
+
read(key: string, load: () => Promise<Response>): Promise<Response>;
|
|
5
|
+
}
|
|
6
|
+
export declare function createCommerceOfferPricingCache<Response extends CommerceOfferPricingResponse = CommerceOfferPricingResponse>(input: {
|
|
7
|
+
ttlMs: number;
|
|
8
|
+
failureTtlMs?: number;
|
|
9
|
+
now?: () => number;
|
|
10
|
+
}): CommerceOfferPricingCache<Response>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function createCommerceOfferPricingCache(input) {
|
|
2
|
+
const now = input.now ?? Date.now;
|
|
3
|
+
const failureTtlMs = input.failureTtlMs ?? 0;
|
|
4
|
+
const cached = new Map();
|
|
5
|
+
const failed = new Map();
|
|
6
|
+
const pending = new Map();
|
|
7
|
+
return {
|
|
8
|
+
read(key, load) {
|
|
9
|
+
const cachedEntry = cached.get(key);
|
|
10
|
+
const failedEntry = failed.get(key);
|
|
11
|
+
if (cachedEntry && cachedEntry.expiresAt > now())
|
|
12
|
+
return Promise.resolve(cachedEntry.value);
|
|
13
|
+
if (failedEntry && failedEntry.expiresAt > now())
|
|
14
|
+
return Promise.reject(failedEntry.error);
|
|
15
|
+
const pendingEntry = pending.get(key);
|
|
16
|
+
if (pendingEntry)
|
|
17
|
+
return pendingEntry;
|
|
18
|
+
const startedAt = now();
|
|
19
|
+
const current = load()
|
|
20
|
+
.then((value) => {
|
|
21
|
+
// Bound freshness from the pricing instant, not from completion of a
|
|
22
|
+
// potentially slow DB read.
|
|
23
|
+
cached.set(key, { value, expiresAt: startedAt + input.ttlMs });
|
|
24
|
+
failed.delete(key);
|
|
25
|
+
return value;
|
|
26
|
+
})
|
|
27
|
+
.catch((error) => {
|
|
28
|
+
// A public cold endpoint must not hammer a degraded database once per
|
|
29
|
+
// request. Keep only the failure object, briefly, and never turn it
|
|
30
|
+
// into stale pricing data; callers still receive the same rejection.
|
|
31
|
+
if (failureTtlMs > 0) {
|
|
32
|
+
failed.set(key, { error, expiresAt: now() + failureTtlMs });
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
})
|
|
36
|
+
.finally(() => {
|
|
37
|
+
if (pending.get(key) === current)
|
|
38
|
+
pending.delete(key);
|
|
39
|
+
});
|
|
40
|
+
pending.set(key, current);
|
|
41
|
+
return current;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PricingPolicyRequest, PricingPolicySnapshot } from "./contracts/offerPolicy.js";
|
|
2
|
+
import type { HttpRequest, HttpResponse } from "../http/index.js";
|
|
3
|
+
import type { BffResponder } from "../bff/index.js";
|
|
4
|
+
import type { z } from "zod";
|
|
5
|
+
import { type CommerceOfferPricingResponse } from "./contracts/offer.js";
|
|
6
|
+
export declare const COMMERCE_OFFER_PRICING_CACHE_CONTROL = "public, max-age=0, s-maxage=5, must-revalidate";
|
|
7
|
+
export declare const COMMERCE_OFFER_PRICING_ASSIGNED_CACHE_CONTROL = "private, no-store";
|
|
8
|
+
export declare const OFFER_POLICY_CAPABILITY_HEADER = "x-commerce-offer-policy-capability";
|
|
9
|
+
export declare const OFFER_POLICY_TOKEN_HEADER = "x-commerce-pricing-policy-token";
|
|
10
|
+
export declare const OFFER_POLICY_VISITOR_HEADER = "x-commerce-visitor-id";
|
|
11
|
+
export interface CommerceOfferPricingHandlerDeps<Response extends CommerceOfferPricingResponse = CommerceOfferPricingResponse> {
|
|
12
|
+
responder: BffResponder;
|
|
13
|
+
commerceOfferPricingResponseSchema: z.ZodType<Response>;
|
|
14
|
+
readOfferPricing(pricingPolicy?: PricingPolicySnapshot): Promise<Response>;
|
|
15
|
+
requirePricingPolicy?: boolean;
|
|
16
|
+
resolvePricingPolicy?(request: {
|
|
17
|
+
visitorId: string;
|
|
18
|
+
pricingPolicy: PricingPolicyRequest;
|
|
19
|
+
}): PricingPolicySnapshot | undefined | Promise<PricingPolicySnapshot | undefined>;
|
|
20
|
+
}
|
|
21
|
+
export declare function createCommerceOfferPricingHandler<Response extends CommerceOfferPricingResponse>({ responder, commerceOfferPricingResponseSchema, readOfferPricing, resolvePricingPolicy, requirePricingPolicy, }: CommerceOfferPricingHandlerDeps<Response>): (req: HttpRequest, res: HttpResponse) => Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { pricingPolicyRequestSchema, } from "./offerPolicySchemas.js";
|
|
2
|
+
// Price/promotion validity can end at any instant. Keep the public cache very
|
|
3
|
+
// short and omit stale-while-revalidate; a future contract may expose the next
|
|
4
|
+
// validity boundary and clamp this exactly to it.
|
|
5
|
+
export const COMMERCE_OFFER_PRICING_CACHE_CONTROL = "public, max-age=0, s-maxage=5, must-revalidate";
|
|
6
|
+
export const COMMERCE_OFFER_PRICING_ASSIGNED_CACHE_CONTROL = "private, no-store";
|
|
7
|
+
export const OFFER_POLICY_CAPABILITY_HEADER = "x-commerce-offer-policy-capability";
|
|
8
|
+
export const OFFER_POLICY_TOKEN_HEADER = "x-commerce-pricing-policy-token";
|
|
9
|
+
export const OFFER_POLICY_VISITOR_HEADER = "x-commerce-visitor-id";
|
|
10
|
+
const ROUTER_QUERY_KEYS = new Set(["path", "__bffPath"]);
|
|
11
|
+
export function createCommerceOfferPricingHandler({ responder, commerceOfferPricingResponseSchema, readOfferPricing, resolvePricingPolicy, requirePricingPolicy = false, }) {
|
|
12
|
+
const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = responder;
|
|
13
|
+
return async function handler(req, res) {
|
|
14
|
+
if (req.method !== "GET") {
|
|
15
|
+
sendMethodNotAllowed(res, ["GET"]);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const userQueryKeys = Object.keys(req.query ?? {}).filter((key) => !ROUTER_QUERY_KEYS.has(key));
|
|
19
|
+
if (userQueryKeys.length > 0) {
|
|
20
|
+
sendBffError(res, "BAD_REQUEST", "Commerce offer pricing does not accept query parameters");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const policyRequest = readPolicyRequest(req);
|
|
25
|
+
if (policyRequest === null) {
|
|
26
|
+
sendBffError(res, "BAD_REQUEST", "Commerce offer pricing policy capability is invalid");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!policyRequest && requirePricingPolicy) {
|
|
30
|
+
// A caller that sent no capability headers is an incomplete request,
|
|
31
|
+
// not an unavailable origin — the same attribution the missing-visitor
|
|
32
|
+
// branch above already applies.
|
|
33
|
+
sendBffError(res, "BAD_REQUEST", "Commerce offer pricing requires offer policy v2", {
|
|
34
|
+
details: {
|
|
35
|
+
reason: "pricing_policy_request_invalid",
|
|
36
|
+
stage: "pricing_policy",
|
|
37
|
+
policyReason: "v2_capability_required",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const pricingPolicy = policyRequest && resolvePricingPolicy
|
|
43
|
+
? await resolvePricingPolicy(policyRequest)
|
|
44
|
+
: undefined;
|
|
45
|
+
const parsed = commerceOfferPricingResponseSchema.safeParse(await readOfferPricing(pricingPolicy));
|
|
46
|
+
if (!parsed.success) {
|
|
47
|
+
sendBffError(res, "INVALID_RESPONSE", "Commerce offer pricing returned invalid response");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
sendBffSuccess(res, parsed.data, { contractVersion: parsed.data.contractVersion }, {
|
|
51
|
+
cacheControl: policyRequest
|
|
52
|
+
? COMMERCE_OFFER_PRICING_ASSIGNED_CACHE_CONTROL
|
|
53
|
+
: COMMERCE_OFFER_PRICING_CACHE_CONTROL,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Commerce offer pricing is unavailable");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function readPolicyRequest(req) {
|
|
62
|
+
const capability = singleHeader(req, OFFER_POLICY_CAPABILITY_HEADER);
|
|
63
|
+
const token = singleHeader(req, OFFER_POLICY_TOKEN_HEADER);
|
|
64
|
+
if (!capability && !token)
|
|
65
|
+
return undefined;
|
|
66
|
+
const visitorId = singleHeader(req, OFFER_POLICY_VISITOR_HEADER);
|
|
67
|
+
if (!visitorId || visitorId.length > 120)
|
|
68
|
+
return null;
|
|
69
|
+
const parsed = pricingPolicyRequestSchema.safeParse({
|
|
70
|
+
...(capability ? { capability } : {}),
|
|
71
|
+
...(token ? { token } : {}),
|
|
72
|
+
});
|
|
73
|
+
return parsed.success ? { visitorId, pricingPolicy: parsed.data } : null;
|
|
74
|
+
}
|
|
75
|
+
function singleHeader(req, name) {
|
|
76
|
+
const value = req.headers?.[name];
|
|
77
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
78
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CommercePriceAuthorityQuery } from "./contracts/index.js";
|
|
2
|
+
import { type CommercePriceAuthorityPort } from "./ports.js";
|
|
3
|
+
import type { ResolvedPrice } from "@openlup/core/pricing";
|
|
4
|
+
export interface TimedAuthorityValue<T> {
|
|
5
|
+
value: T;
|
|
6
|
+
validFrom: string;
|
|
7
|
+
validTo: string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface CommercePriceAuthorityReadPort {
|
|
10
|
+
listPriceLists(context: Pick<CommercePriceAuthorityQuery, "regionCode" | "currency">): Promise<ReadonlyArray<TimedAuthorityValue<string>>>;
|
|
11
|
+
listExactBaseEntries(input: {
|
|
12
|
+
variantId: string;
|
|
13
|
+
priceListId: string;
|
|
14
|
+
}): Promise<ReadonlyArray<TimedAuthorityValue<ResolvedPrice>>>;
|
|
15
|
+
listExactLegacySubscriptionEntries(input: {
|
|
16
|
+
variantId: string;
|
|
17
|
+
priceListId: string;
|
|
18
|
+
}): Promise<ReadonlyArray<TimedAuthorityValue<ResolvedPrice>>>;
|
|
19
|
+
listSubscriptionPolicyHistory(context: Pick<CommercePriceAuthorityQuery, "regionCode" | "currency" | "channel"> & {
|
|
20
|
+
priceListId: string;
|
|
21
|
+
}): Promise<ReadonlyArray<TimedAuthorityValue<unknown>>>;
|
|
22
|
+
}
|
|
23
|
+
/** Provider-neutral strict money algorithm; adapters own only persistence reads and row mapping. */
|
|
24
|
+
export declare function createCommercePriceAuthorityPort(reads: CommercePriceAuthorityReadPort): CommercePriceAuthorityPort;
|
|
25
|
+
/** The current money context requires one list; creation order cannot resolve overlap. */
|
|
26
|
+
export declare function selectUniqueEffectivePriceList(priceLists: readonly TimedAuthorityValue<string>[], atTime: string): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CommercePriceAuthorityError, } from "./ports.js";
|
|
2
|
+
import { SubscriptionPricePolicyDigestError, assertSubscriptionPricePolicyDigest, deriveSubscriptionUnitPrice, } from "./subscriptionPricePolicy.js";
|
|
3
|
+
/** Provider-neutral strict money algorithm; adapters own only persistence reads and row mapping. */
|
|
4
|
+
export function createCommercePriceAuthorityPort(reads) {
|
|
5
|
+
return {
|
|
6
|
+
async resolvePrice(query) {
|
|
7
|
+
const atTime = query.atTime ?? new Date().toISOString();
|
|
8
|
+
const priceListId = selectUniqueEffectivePriceList(await reads.listPriceLists(query), atTime);
|
|
9
|
+
const baseCandidates = effective(await reads.listExactBaseEntries({
|
|
10
|
+
variantId: query.variantId,
|
|
11
|
+
priceListId,
|
|
12
|
+
}), atTime);
|
|
13
|
+
if (baseCandidates.length === 0)
|
|
14
|
+
throw new CommercePriceAuthorityError("base_price_not_found");
|
|
15
|
+
if (baseCandidates.length !== 1)
|
|
16
|
+
throw new CommercePriceAuthorityError("base_price_ambiguous");
|
|
17
|
+
if (baseCandidates[0].value.amountKind !== "gross") {
|
|
18
|
+
throw new CommercePriceAuthorityError("base_price_amount_kind_invalid");
|
|
19
|
+
}
|
|
20
|
+
const base = { ...baseCandidates[0].value, resolvedAt: atTime };
|
|
21
|
+
if (query.mode === "one_time")
|
|
22
|
+
return { kind: "one_time", base, unitPriceMinor: base.unitPriceMinor };
|
|
23
|
+
const policyHistory = await reads.listSubscriptionPolicyHistory({
|
|
24
|
+
priceListId: base.priceListId,
|
|
25
|
+
regionCode: query.regionCode,
|
|
26
|
+
currency: query.currency,
|
|
27
|
+
channel: query.channel,
|
|
28
|
+
});
|
|
29
|
+
if (policyHistory.length === 0) {
|
|
30
|
+
const legacyCandidates = effective(await reads.listExactLegacySubscriptionEntries({
|
|
31
|
+
variantId: query.variantId,
|
|
32
|
+
priceListId: base.priceListId,
|
|
33
|
+
}), atTime);
|
|
34
|
+
if (legacyCandidates.length === 0)
|
|
35
|
+
throw new CommercePriceAuthorityError("legacy_subscription_price_not_found");
|
|
36
|
+
if (legacyCandidates.length !== 1)
|
|
37
|
+
throw new CommercePriceAuthorityError("legacy_subscription_price_ambiguous");
|
|
38
|
+
const legacyResolved = { ...legacyCandidates[0].value, resolvedAt: atTime };
|
|
39
|
+
return { kind: "subscription_legacy", base, unitPriceMinor: legacyResolved.unitPriceMinor, legacyResolved };
|
|
40
|
+
}
|
|
41
|
+
const effectivePolicies = effective(policyHistory, atTime);
|
|
42
|
+
if (effectivePolicies.length === 0)
|
|
43
|
+
throw new CommercePriceAuthorityError("subscription_price_policy_not_effective");
|
|
44
|
+
if (effectivePolicies.length !== 1)
|
|
45
|
+
throw new CommercePriceAuthorityError("subscription_price_policy_ambiguous");
|
|
46
|
+
try {
|
|
47
|
+
const policy = await assertSubscriptionPricePolicyDigest(effectivePolicies[0].value);
|
|
48
|
+
return {
|
|
49
|
+
kind: "subscription_policy",
|
|
50
|
+
base,
|
|
51
|
+
unitPriceMinor: deriveSubscriptionUnitPrice(base.unitPriceMinor, policy),
|
|
52
|
+
policy,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new CommercePriceAuthorityError(error instanceof SubscriptionPricePolicyDigestError
|
|
57
|
+
? "subscription_price_policy_digest_mismatch"
|
|
58
|
+
: "subscription_price_policy_invalid");
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** The current money context requires one list; creation order cannot resolve overlap. */
|
|
64
|
+
export function selectUniqueEffectivePriceList(priceLists, atTime) {
|
|
65
|
+
const candidates = effective(priceLists, atTime);
|
|
66
|
+
if (candidates.length === 0)
|
|
67
|
+
throw new CommercePriceAuthorityError("active_price_list_not_found");
|
|
68
|
+
if (candidates.length !== 1)
|
|
69
|
+
throw new CommercePriceAuthorityError("active_price_list_ambiguous");
|
|
70
|
+
return candidates[0].value;
|
|
71
|
+
}
|
|
72
|
+
function effective(values, atTime) {
|
|
73
|
+
const atMs = Date.parse(atTime);
|
|
74
|
+
return values.filter(({ validFrom, validTo }) => {
|
|
75
|
+
const fromMs = Date.parse(validFrom);
|
|
76
|
+
const toMs = validTo === null ? null : Date.parse(validTo);
|
|
77
|
+
return Number.isFinite(atMs) && Number.isFinite(fromMs) && fromMs <= atMs && (toMs === null || toMs > atMs);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** The server cannot assign a policy right now. Caller-independent: retrying an
|
|
2
|
+
* identical request only succeeds once production configuration changes. */
|
|
3
|
+
export declare class CommercePricingPolicyUnavailableError extends Error {
|
|
4
|
+
readonly reason: string;
|
|
5
|
+
constructor(reason: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Every reason a caller-side refusal can carry. ⛔ Each one answers `400`, which
|
|
9
|
+
* puts it outside every 5xx monitor, so each one owes a dedicated monitor of its
|
|
10
|
+
* own; a host's observability check can read this tuple and fail when a reason
|
|
11
|
+
* has none. Adding a member here without adding a monitor is the regression this
|
|
12
|
+
* tuple exists to prevent: reclassifying `v2_capability_required` from `503` to
|
|
13
|
+
* `400` once silently removed the only alert on a site-wide unpriceable-quote
|
|
14
|
+
* outage.
|
|
15
|
+
*/
|
|
16
|
+
export declare const PRICING_POLICY_REQUEST_REASONS: readonly ["v2_capability_required", "v2_visitor_id_required"];
|
|
17
|
+
export type PricingPolicyRequestReason = (typeof PRICING_POLICY_REQUEST_REASONS)[number];
|
|
18
|
+
/** The request itself lacks the context a v2 assignment needs. Caller-fixable,
|
|
19
|
+
* and never evidence that pricing is down. The reason is typed off
|
|
20
|
+
* PRICING_POLICY_REQUEST_REASONS so a new one cannot skip its monitor. */
|
|
21
|
+
export declare class CommercePricingPolicyRequestError extends Error {
|
|
22
|
+
readonly reason: PricingPolicyRequestReason;
|
|
23
|
+
constructor(reason: PricingPolicyRequestReason);
|
|
24
|
+
}
|
|
25
|
+
export type SafePricingPolicyReason = "v2_capability_required" | "v2_visitor_id_required" | "signing_secret_unavailable" | "v1_assignment_rejected" | "v2_assignment_config_invalid" | "v2_readiness_not_ready" | "other";
|
|
26
|
+
export interface PricingPolicyFailureMapping {
|
|
27
|
+
code: "BAD_REQUEST" | "UPSTREAM_UNAVAILABLE";
|
|
28
|
+
details: {
|
|
29
|
+
reason: "pricing_policy_request_invalid" | "pricing_policy_unavailable";
|
|
30
|
+
stage: "pricing_policy";
|
|
31
|
+
policyReason: SafePricingPolicyReason;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Single owner of the status a pricing-policy refusal earns, shared by every
|
|
36
|
+
* route that resolves one. A caller-attributable gap answers `400`; only a
|
|
37
|
+
* server that genuinely cannot price answers `503`, so a `503` on these routes
|
|
38
|
+
* stays a truthful outage signal.
|
|
39
|
+
*/
|
|
40
|
+
export declare function mapPricingPolicyFailure(error: unknown): PricingPolicyFailureMapping | null;
|