@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,157 @@
|
|
|
1
|
+
import type { HttpRequest, HttpResponse } from "../http/index.js";
|
|
2
|
+
import type { BffResponder } from "../bff/index.js";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
import { COMMERCE_OFFER_PRICING_V3_CONTRACT_VERSION, type AcquisitionOfferPricingResponse, type CommerceOfferPricingResponse } from "./contracts/offer.js";
|
|
5
|
+
import type { AcquisitionCapability } from "./contracts/acquisitionTerms.js";
|
|
6
|
+
import { acquisitionCapabilitySchema } from "./acquisitionTermsSchemas.js";
|
|
7
|
+
import { pricingPolicyRequestSchema } from "./offerPolicySchemas.js";
|
|
8
|
+
import type { PricingPolicyRequest, PricingPolicySnapshot } from "./contracts/offerPolicy.js";
|
|
9
|
+
import { mapPricingPolicyFailure } from "./commercePricingPolicyErrors.js";
|
|
10
|
+
import { COMMERCE_OFFER_PRICING_ASSIGNED_CACHE_CONTROL, COMMERCE_OFFER_PRICING_CACHE_CONTROL,
|
|
11
|
+
OFFER_POLICY_CAPABILITY_HEADER, OFFER_POLICY_TOKEN_HEADER, OFFER_POLICY_VISITOR_HEADER } from "./commerceOfferPricingHandler.js";
|
|
12
|
+
export const ACQUISITION_CAPABILITY_HEADER = "x-commerce-acquisition-capability";
|
|
13
|
+
const ROUTER_QUERY_KEYS = new Set(["path", "__bffPath"]);
|
|
14
|
+
|
|
15
|
+
export interface AcquisitionOfferPricingHandlerDeps {
|
|
16
|
+
responder: BffResponder;
|
|
17
|
+
commerceOfferPricingResponseSchema: z.ZodType<AcquisitionOfferPricingResponse | CommerceOfferPricingResponse>;
|
|
18
|
+
incumbentHandler(req: HttpRequest, res: HttpResponse): Promise<void>;
|
|
19
|
+
readOfferPricing(pricingPolicy?: PricingPolicySnapshot, acquisitionCapability?: AcquisitionCapability): Promise<AcquisitionOfferPricingResponse | CommerceOfferPricingResponse>;
|
|
20
|
+
requirePricingPolicy?: boolean;
|
|
21
|
+
resolvePricingPolicy?(request: {
|
|
22
|
+
visitorId: string;
|
|
23
|
+
pricingPolicy: PricingPolicyRequest;
|
|
24
|
+
}): PricingPolicySnapshot | undefined | Promise<PricingPolicySnapshot | undefined>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createAcquisitionOfferPricingHandler({
|
|
28
|
+
responder, commerceOfferPricingResponseSchema, incumbentHandler,
|
|
29
|
+
readOfferPricing,
|
|
30
|
+
resolvePricingPolicy,
|
|
31
|
+
requirePricingPolicy = false,
|
|
32
|
+
}: AcquisitionOfferPricingHandlerDeps) {
|
|
33
|
+
const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = responder;
|
|
34
|
+
return async function handler(
|
|
35
|
+
req: HttpRequest,
|
|
36
|
+
res: HttpResponse,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
if (req.headers?.[ACQUISITION_CAPABILITY_HEADER] === undefined) return incumbentHandler(req, res);
|
|
39
|
+
if (req.method !== "GET") {
|
|
40
|
+
sendMethodNotAllowed(res, ["GET"]);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const userQueryKeys = Object.keys(req.query ?? {}).filter(
|
|
44
|
+
(key) => !ROUTER_QUERY_KEYS.has(key),
|
|
45
|
+
);
|
|
46
|
+
if (userQueryKeys.length > 0) {
|
|
47
|
+
sendBffError(res, "BAD_REQUEST", "Commerce offer pricing does not accept query parameters");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let failureReason = "offer_pricing_policy_unavailable";
|
|
52
|
+
try {
|
|
53
|
+
const acquisitionCapability = readAcquisitionCapability(req);
|
|
54
|
+
if (acquisitionCapability === null) {
|
|
55
|
+
sendBffError(res, "BAD_REQUEST", "Commerce acquisition capability is invalid");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const policyRequest = readPolicyRequest(req);
|
|
59
|
+
if (policyRequest === null) {
|
|
60
|
+
sendBffError(res, "BAD_REQUEST", "Commerce offer pricing policy capability is invalid");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!policyRequest && requirePricingPolicy) {
|
|
64
|
+
// A caller that sent no capability headers is an incomplete request,
|
|
65
|
+
// not an unavailable origin — the same attribution the missing-visitor
|
|
66
|
+
// branch above already applies.
|
|
67
|
+
sendBffError(
|
|
68
|
+
res,
|
|
69
|
+
"BAD_REQUEST",
|
|
70
|
+
"Commerce offer pricing requires offer policy v2",
|
|
71
|
+
{
|
|
72
|
+
details: {
|
|
73
|
+
reason: "pricing_policy_request_invalid",
|
|
74
|
+
stage: "pricing_policy",
|
|
75
|
+
policyReason: "v2_capability_required",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const pricingPolicy = policyRequest && resolvePricingPolicy
|
|
82
|
+
? await resolvePricingPolicy(policyRequest)
|
|
83
|
+
: undefined;
|
|
84
|
+
if (acquisitionCapability && pricingPolicy?.offerPolicyVersion !== "commerce.offer-policy.v2") {
|
|
85
|
+
sendBffError(res, "BAD_REQUEST", "Commerce acquisition pricing requires offer policy v2");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
failureReason = "offer_pricing_projection_unavailable";
|
|
89
|
+
const parsed = commerceOfferPricingResponseSchema.safeParse(
|
|
90
|
+
await (acquisitionCapability ? readOfferPricing(pricingPolicy, acquisitionCapability) : readOfferPricing(pricingPolicy)),
|
|
91
|
+
);
|
|
92
|
+
if (!parsed.success || Boolean(acquisitionCapability) !==
|
|
93
|
+
(parsed.data.contractVersion === COMMERCE_OFFER_PRICING_V3_CONTRACT_VERSION)) {
|
|
94
|
+
sendBffError(
|
|
95
|
+
res,
|
|
96
|
+
"INVALID_RESPONSE",
|
|
97
|
+
"Commerce offer pricing returned invalid response",
|
|
98
|
+
);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
res.setHeader("Vary", [ACQUISITION_CAPABILITY_HEADER, OFFER_POLICY_CAPABILITY_HEADER,
|
|
102
|
+
OFFER_POLICY_TOKEN_HEADER, OFFER_POLICY_VISITOR_HEADER].join(", "));
|
|
103
|
+
sendBffSuccess(
|
|
104
|
+
res,
|
|
105
|
+
parsed.data,
|
|
106
|
+
{ contractVersion: parsed.data.contractVersion },
|
|
107
|
+
{
|
|
108
|
+
cacheControl: policyRequest
|
|
109
|
+
? COMMERCE_OFFER_PRICING_ASSIGNED_CACHE_CONTROL
|
|
110
|
+
: COMMERCE_OFFER_PRICING_CACHE_CONTROL,
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const policyFailure = failureReason === "offer_pricing_policy_unavailable"
|
|
115
|
+
? mapPricingPolicyFailure(error) : null;
|
|
116
|
+
sendBffError(
|
|
117
|
+
res,
|
|
118
|
+
"UPSTREAM_UNAVAILABLE",
|
|
119
|
+
"Commerce offer pricing is unavailable",
|
|
120
|
+
{
|
|
121
|
+
details: {
|
|
122
|
+
reason: failureReason,
|
|
123
|
+
...(policyFailure ? { policyReason: policyFailure.details.policyReason } : {}),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function readAcquisitionCapability(req: HttpRequest): AcquisitionCapability | undefined | null {
|
|
132
|
+
const raw = req.headers?.[ACQUISITION_CAPABILITY_HEADER];
|
|
133
|
+
if (raw === undefined) return undefined;
|
|
134
|
+
const parsed = acquisitionCapabilitySchema.safeParse(typeof raw === "string" ? raw.trim() : raw);
|
|
135
|
+
return parsed.success ? parsed.data : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readPolicyRequest(req: HttpRequest): {
|
|
139
|
+
visitorId: string;
|
|
140
|
+
pricingPolicy: PricingPolicyRequest;
|
|
141
|
+
} | undefined | null {
|
|
142
|
+
const capability = singleHeader(req, OFFER_POLICY_CAPABILITY_HEADER);
|
|
143
|
+
const token = singleHeader(req, OFFER_POLICY_TOKEN_HEADER);
|
|
144
|
+
if (!capability && !token) return undefined;
|
|
145
|
+
const visitorId = singleHeader(req, OFFER_POLICY_VISITOR_HEADER);
|
|
146
|
+
if (!visitorId || visitorId.length > 120) return null;
|
|
147
|
+
const parsed = pricingPolicyRequestSchema.safeParse({
|
|
148
|
+
...(capability ? { capability } : {}),
|
|
149
|
+
...(token ? { token } : {}),
|
|
150
|
+
});
|
|
151
|
+
return parsed.success ? { visitorId, pricingPolicy: parsed.data } : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function singleHeader(req: HttpRequest, name: string): string | undefined {
|
|
155
|
+
const value = req.headers?.[name];
|
|
156
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
157
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { acquisitionTermsSchema } from "./acquisitionTermsSchemas.js";
|
|
3
|
+
import { COMMERCE_OFFER_PRICING_V2_CONTRACT_VERSION, COMMERCE_OFFER_PRICING_V3_CONTRACT_VERSION, type AcquisitionOfferPricingResponse, type CommerceOfferPricingResponse } from "./contracts/offer.js";
|
|
4
|
+
export const ACQUISITION_PROJECTION_MAX_AGE_MS = 60_000;
|
|
5
|
+
export const acquisitionProjectionFreshnessShape = {
|
|
6
|
+
acquisitionTerms: acquisitionTermsSchema,
|
|
7
|
+
observedAt: z.string().datetime({ offset: true }),
|
|
8
|
+
validUntil: z.string().datetime({ offset: true }),
|
|
9
|
+
};
|
|
10
|
+
export const acquisitionProjectionFreshnessSchema = z.object(acquisitionProjectionFreshnessShape).strict().refine((value) => {
|
|
11
|
+
const observed = Date.parse(value.observedAt);
|
|
12
|
+
const until = Date.parse(value.validUntil);
|
|
13
|
+
return until > observed && until <= observed + ACQUISITION_PROJECTION_MAX_AGE_MS
|
|
14
|
+
&& (value.acquisitionTerms.effectiveFrom == null || Date.parse(value.acquisitionTerms.effectiveFrom) <= observed)
|
|
15
|
+
&& (value.acquisitionTerms.effectiveTo == null || until <= Date.parse(value.acquisitionTerms.effectiveTo));
|
|
16
|
+
}, "acquisition projection must be current and expire by its next boundary or 60 seconds");
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/** Validate against the host's existing v2 scope, product and settlement schemas. */
|
|
20
|
+
export function createAcquisitionOfferPricingSchema<ScopeV2 extends string>(
|
|
21
|
+
v2Schema: z.ZodType<Extract<CommerceOfferPricingResponse<string, ScopeV2>, { contractVersion: typeof COMMERCE_OFFER_PRICING_V2_CONTRACT_VERSION }>>,
|
|
22
|
+
): z.ZodType<AcquisitionOfferPricingResponse<ScopeV2>> {
|
|
23
|
+
return z.unknown().transform((value, ctx) => {
|
|
24
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
25
|
+
ctx.addIssue({ code: "custom", message: "Invalid acquisition offer" }); return z.NEVER;
|
|
26
|
+
}
|
|
27
|
+
const { contractVersion, acquisitionTerms, observedAt, validUntil, ...base } = value as Record<string, unknown>;
|
|
28
|
+
const pricing = v2Schema.safeParse({ ...base, contractVersion: COMMERCE_OFFER_PRICING_V2_CONTRACT_VERSION });
|
|
29
|
+
const freshness = acquisitionProjectionFreshnessSchema.safeParse({ acquisitionTerms, observedAt, validUntil });
|
|
30
|
+
if (contractVersion !== COMMERCE_OFFER_PRICING_V3_CONTRACT_VERSION || !pricing.success || !freshness.success) {
|
|
31
|
+
ctx.addIssue({ code: "custom", message: "Invalid acquisition offer" }); return z.NEVER;
|
|
32
|
+
}
|
|
33
|
+
return { ...pricing.data, contractVersion: COMMERCE_OFFER_PRICING_V3_CONTRACT_VERSION, ...freshness.data };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ResolvedAcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
2
|
+
import { AcquisitionTermsError } from "./acquisitionTerms.js";
|
|
3
|
+
/** Server-selected terms only. Absence contributes no property or token bytes. */
|
|
4
|
+
export function acquisitionQuoteBinding(acquisitionTerms?: ResolvedAcquisitionTerms): { acquisitionTerms?: ResolvedAcquisitionTerms } {
|
|
5
|
+
return acquisitionTerms ? { acquisitionTerms } : {};
|
|
6
|
+
}
|
|
7
|
+
export function mapAcquisitionTermsFailure(error: unknown) {
|
|
8
|
+
if (!(error instanceof AcquisitionTermsError)) return null;
|
|
9
|
+
const refreshRequired = error.reason === "acquisition_terms_capability_required" || error.reason === "acquisition_terms_v2_required";
|
|
10
|
+
return { code: refreshRequired ? "CONFLICT" as const : "UPSTREAM_UNAVAILABLE" as const,
|
|
11
|
+
details: { reason: error.reason, refreshRequired } };
|
|
12
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ACQUISITION_TERMS_CAPABILITY, type AcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
3
|
+
import { acquisitionTermsSchema } from "./acquisitionTermsSchemas.js";
|
|
4
|
+
import type { AcquisitionFinancialPolicy } from "./acquisitionFinancialPolicy.js";
|
|
5
|
+
import { AcquisitionTermsError } from "./acquisitionTerms.js";
|
|
6
|
+
import { OFFER_POLICY_V2_READINESS_CONTRACT } from "./offerPolicyReadiness.js";
|
|
7
|
+
export const ACQUISITION_READINESS_CONTRACT = "commerce-acquisition-readiness.v1";
|
|
8
|
+
|
|
9
|
+
export interface AcquisitionReadinessEvidence extends Omit<AcquisitionTerms, "schemaVersion"> {
|
|
10
|
+
contractVersion: typeof ACQUISITION_READINESS_CONTRACT;
|
|
11
|
+
kind: "legacy_pair" | "v2_standalone";
|
|
12
|
+
legacyPairCount: number;
|
|
13
|
+
standaloneCount: number;
|
|
14
|
+
nextBoundaryAt: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AcquisitionOfferPolicyReadiness {
|
|
18
|
+
contractVersion: typeof OFFER_POLICY_V2_READINESS_CONTRACT;
|
|
19
|
+
ready: boolean;
|
|
20
|
+
checkedAt: string;
|
|
21
|
+
reasons: string[];
|
|
22
|
+
evidence?: { acquisition: AcquisitionReadinessEvidence | null };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
import type { OfferPolicyReadinessPort } from "./offerPolicyReadiness.js";
|
|
26
|
+
|
|
27
|
+
interface CacheEntry {
|
|
28
|
+
expiresAtMs: number;
|
|
29
|
+
readiness: AcquisitionOfferPolicyReadiness;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AcquisitionReadinessCache {
|
|
33
|
+
read(port: OfferPolicyReadinessPort): Promise<AcquisitionOfferPolicyReadiness>;
|
|
34
|
+
clear(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createAcquisitionReadinessCache<Row>(input: {
|
|
38
|
+
policy: AcquisitionFinancialPolicy<Row>;
|
|
39
|
+
now?: () => number;
|
|
40
|
+
positiveTtlMs?: number;
|
|
41
|
+
negativeTtlMs?: number;
|
|
42
|
+
}): AcquisitionReadinessCache {
|
|
43
|
+
if (!input?.policy) throw new AcquisitionTermsError("acquisition_terms_policy_unconfigured");
|
|
44
|
+
const now = input.now ?? Date.now;
|
|
45
|
+
const positiveTtlMs = Math.min(60_000, Math.max(0, input.positiveTtlMs ?? 60_000));
|
|
46
|
+
const negativeTtlMs = Math.min(5_000, Math.max(0, input.negativeTtlMs ?? 5_000));
|
|
47
|
+
let cached: CacheEntry | null = null;
|
|
48
|
+
let inFlight: Promise<AcquisitionOfferPolicyReadiness> | null = null;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
read(port) {
|
|
52
|
+
const current = now();
|
|
53
|
+
if (cached && cached.expiresAtMs > current) return Promise.resolve(cached.readiness);
|
|
54
|
+
if (inFlight) return inFlight;
|
|
55
|
+
const startedAtMs = current;
|
|
56
|
+
inFlight = loadFailClosed(port)
|
|
57
|
+
.then(async (readiness) => {
|
|
58
|
+
// An RPC started before the boundary can finish after it. Re-read once;
|
|
59
|
+
// never lend the old green result a fresh positive cache window.
|
|
60
|
+
if (readiness.ready && boundaryMs(readiness) <= now()) readiness = await loadFailClosed(port);
|
|
61
|
+
if (readiness.ready && boundaryMs(readiness) <= now()) readiness = unavailable("readiness_acquisition_boundary_elapsed");
|
|
62
|
+
cached = {
|
|
63
|
+
readiness,
|
|
64
|
+
// Bound staleness from the start of the database read. A slow RPC
|
|
65
|
+
// must consume the TTL, not extend a green decision past 60s.
|
|
66
|
+
expiresAtMs: readiness.ready ? Math.min(startedAtMs + positiveTtlMs, boundaryMs(readiness))
|
|
67
|
+
: startedAtMs + negativeTtlMs,
|
|
68
|
+
};
|
|
69
|
+
return readiness;
|
|
70
|
+
})
|
|
71
|
+
.finally(() => {
|
|
72
|
+
inFlight = null;
|
|
73
|
+
});
|
|
74
|
+
return inFlight;
|
|
75
|
+
},
|
|
76
|
+
clear() {
|
|
77
|
+
cached = null;
|
|
78
|
+
inFlight = null;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function loadFailClosed(port: OfferPolicyReadinessPort): Promise<AcquisitionOfferPolicyReadiness> {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = parseReadiness(await port.readOfferPolicyV2Readiness());
|
|
86
|
+
return parsed ?? unavailable("readiness_contract_invalid");
|
|
87
|
+
} catch {
|
|
88
|
+
return unavailable("readiness_rpc_unavailable");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseReadiness(value: unknown): AcquisitionOfferPolicyReadiness | null {
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
94
|
+
const record = value as Record<string, unknown>;
|
|
95
|
+
if (
|
|
96
|
+
record.contractVersion !== OFFER_POLICY_V2_READINESS_CONTRACT ||
|
|
97
|
+
typeof record.ready !== "boolean" ||
|
|
98
|
+
typeof record.checkedAt !== "string" ||
|
|
99
|
+
!Number.isFinite(Date.parse(record.checkedAt)) ||
|
|
100
|
+
!Array.isArray(record.reasons) ||
|
|
101
|
+
!record.reasons.every((reason) => typeof reason === "string")
|
|
102
|
+
) return null;
|
|
103
|
+
const evidence = record.evidence;
|
|
104
|
+
let acquisition: AcquisitionReadinessEvidence | null | undefined;
|
|
105
|
+
if (evidence && typeof evidence === "object" && !Array.isArray(evidence) && "acquisition" in evidence) {
|
|
106
|
+
const raw = (evidence as Record<string, unknown>).acquisition;
|
|
107
|
+
acquisition = raw === null && !record.ready ? null : parseAcquisitionEvidence(raw);
|
|
108
|
+
if (acquisition === undefined) return null;
|
|
109
|
+
if (record.ready && acquisition && (Date.parse(acquisition.effectiveFrom) > Date.parse(record.checkedAt)
|
|
110
|
+
|| acquisition.effectiveTo !== null && Date.parse(acquisition.effectiveTo) <= Date.parse(record.checkedAt)
|
|
111
|
+
|| acquisition.nextBoundaryAt !== null && Date.parse(acquisition.nextBoundaryAt) <= Date.parse(record.checkedAt))) return null;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
contractVersion: OFFER_POLICY_V2_READINESS_CONTRACT,
|
|
115
|
+
ready: record.ready,
|
|
116
|
+
checkedAt: record.checkedAt,
|
|
117
|
+
reasons: record.reasons,
|
|
118
|
+
...(acquisition !== undefined ? { evidence: { acquisition } } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseAcquisitionEvidence(value: unknown): AcquisitionReadinessEvidence | undefined {
|
|
123
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
124
|
+
const { contractVersion, kind, legacyPairCount, standaloneCount, nextBoundaryAt, ...terms } = value as Record<string, unknown>;
|
|
125
|
+
const parsed = acquisitionTermsSchema.safeParse({ ...terms, schemaVersion: ACQUISITION_TERMS_CAPABILITY });
|
|
126
|
+
if (!parsed.success || contractVersion !== ACQUISITION_READINESS_CONTRACT
|
|
127
|
+
|| !(kind === "legacy_pair" && legacyPairCount === 1 && standaloneCount === 0
|
|
128
|
+
|| kind === "v2_standalone" && legacyPairCount === 0 && standaloneCount === 1)
|
|
129
|
+
|| !(nextBoundaryAt === null || z.string().datetime({ offset: true }).safeParse(nextBoundaryAt).success)
|
|
130
|
+
|| parsed.data.effectiveTo !== null && (nextBoundaryAt === null
|
|
131
|
+
|| Date.parse(nextBoundaryAt as string) > Date.parse(parsed.data.effectiveTo))) return undefined;
|
|
132
|
+
const { schemaVersion: _schema, ...publicTerms } = parsed.data;
|
|
133
|
+
return { ...publicTerms, contractVersion, kind, legacyPairCount, standaloneCount, nextBoundaryAt } as AcquisitionReadinessEvidence;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function boundaryMs(readiness: AcquisitionOfferPolicyReadiness): number {
|
|
137
|
+
const boundary = readiness.evidence?.acquisition?.nextBoundaryAt;
|
|
138
|
+
return boundary ? Date.parse(boundary) : Infinity;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function unavailable(reason: string): AcquisitionOfferPolicyReadiness {
|
|
142
|
+
return {
|
|
143
|
+
contractVersion: OFFER_POLICY_V2_READINESS_CONTRACT,
|
|
144
|
+
ready: false,
|
|
145
|
+
checkedAt: new Date(0).toISOString(),
|
|
146
|
+
reasons: [reason],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { ACQUISITION_TERMS_CAPABILITY, type AcquisitionTermsMetadata, type ResolvedAcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
2
|
+
import type { AcquisitionFinancialPolicy } from "./acquisitionFinancialPolicy.js";
|
|
3
|
+
import { acquisitionTermsMetadataSchema, resolvedAcquisitionTermsSchema } from "./acquisitionTermsSchemas.js";
|
|
4
|
+
export type AcquisitionTermsRefusal = "acquisition_terms_policy_unconfigured" | "acquisition_terms_invalid" | "acquisition_terms_ambiguous"
|
|
5
|
+
| "acquisition_terms_not_effective" | "acquisition_terms_capability_required" | "acquisition_terms_v2_required";
|
|
6
|
+
export class AcquisitionTermsError extends Error {
|
|
7
|
+
constructor(readonly reason: AcquisitionTermsRefusal) { super(reason); this.name = "AcquisitionTermsError"; }
|
|
8
|
+
}
|
|
9
|
+
export interface AcquisitionTermsSelection {
|
|
10
|
+
kind: "legacy_pair" | "v2_standalone" | "legacy_unconfigured";
|
|
11
|
+
terms: ResolvedAcquisitionTerms;
|
|
12
|
+
sourcePromotionId: string;
|
|
13
|
+
legacyPromotionId?: string;
|
|
14
|
+
nextBoundaryAt: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function requirePolicy<Row>(policy: AcquisitionFinancialPolicy<Row>): void {
|
|
18
|
+
if (!policy) throw new AcquisitionTermsError("acquisition_terms_policy_unconfigured");
|
|
19
|
+
}
|
|
20
|
+
function effective<Row>(row: Row, at: number, policy: AcquisitionFinancialPolicy<Row>): boolean {
|
|
21
|
+
const facts = policy.facts(row);
|
|
22
|
+
return Date.parse(facts.validFrom) <= at && (facts.validTo === null || Date.parse(facts.validTo) > at);
|
|
23
|
+
}
|
|
24
|
+
function metadata<Row>(row: Row, policy: AcquisitionFinancialPolicy<Row>): AcquisitionTermsMetadata {
|
|
25
|
+
const parsed = acquisitionTermsMetadataSchema.safeParse(policy.facts(row).metadata);
|
|
26
|
+
if (!parsed.success) throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
27
|
+
policy.validateMetadata(row, parsed.data);
|
|
28
|
+
return parsed.data;
|
|
29
|
+
}
|
|
30
|
+
function resolved<Row>(row: Row, terms: AcquisitionTermsMetadata, policy: AcquisitionFinancialPolicy<Row>,
|
|
31
|
+
purpose: "resolved_target" | "filter_validation" = "resolved_target"): ResolvedAcquisitionTerms {
|
|
32
|
+
const facts = policy.facts(row);
|
|
33
|
+
const parsed = resolvedAcquisitionTermsSchema.safeParse({ ...terms,
|
|
34
|
+
initialDiscountBps: policy.initialDiscountBps(row, purpose), effectiveFrom: facts.validFrom,
|
|
35
|
+
effectiveTo: facts.validTo, sourcePromotionId: facts.id });
|
|
36
|
+
if (!parsed.success) throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
37
|
+
return { ...parsed.data, effectiveFrom: new Date(parsed.data.effectiveFrom).toISOString(),
|
|
38
|
+
effectiveTo: parsed.data.effectiveTo === null ? null : new Date(parsed.data.effectiveTo).toISOString() };
|
|
39
|
+
}
|
|
40
|
+
/** Selection never falls back from a configured but malformed or incomplete schedule. */
|
|
41
|
+
export function selectAcquisitionTerms<Row>(
|
|
42
|
+
rows: readonly Row[], atTime: string, regionCode: string, policy: AcquisitionFinancialPolicy<Row>,
|
|
43
|
+
): AcquisitionTermsSelection | null {
|
|
44
|
+
requirePolicy(policy);
|
|
45
|
+
const at = Date.parse(atTime);
|
|
46
|
+
if (!Number.isFinite(at)) throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
47
|
+
const scoped = rows.filter((row) => policy.facts(row).active && policy.facts(row).regionCodes.includes(regionCode));
|
|
48
|
+
const tagged = scoped.filter((row) => policy.facts(row).metadataPresent);
|
|
49
|
+
if (tagged.length === 0) {
|
|
50
|
+
const legacyRows = scoped.filter((row) => policy.classify(row).unconfiguredLegacyEligible);
|
|
51
|
+
const knownPairs = legacyRows.flatMap((legacy) => scoped.filter((mirror) => policy.classify(mirror).target
|
|
52
|
+
&& policy.pairMatches(legacy, mirror)).map((mirror) => ({ legacy, mirror })));
|
|
53
|
+
const pairs = knownPairs.filter(({ mirror }) => effective(mirror, at, policy));
|
|
54
|
+
if (pairs.length > 1) throw new AcquisitionTermsError("acquisition_terms_ambiguous");
|
|
55
|
+
if (pairs.length === 0) {
|
|
56
|
+
if (knownPairs.length) throw new AcquisitionTermsError("acquisition_terms_not_effective");
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const { legacy, mirror } = pairs[0]!;
|
|
60
|
+
const terms = resolved(mirror, policy.unconfiguredTerms(legacy, mirror), policy);
|
|
61
|
+
return { kind: "legacy_unconfigured", terms, sourcePromotionId: policy.facts(mirror).id,
|
|
62
|
+
legacyPromotionId: policy.facts(legacy).id, nextBoundaryAt: terms.effectiveTo };
|
|
63
|
+
}
|
|
64
|
+
const grouped = new Map<string, Row[]>();
|
|
65
|
+
for (const row of tagged) {
|
|
66
|
+
const parsed = metadata(row, policy);
|
|
67
|
+
const group = grouped.get(parsed.revision) ?? [];
|
|
68
|
+
group.push(row); grouped.set(parsed.revision, group);
|
|
69
|
+
}
|
|
70
|
+
const selections: AcquisitionTermsSelection[] = [];
|
|
71
|
+
for (const group of grouped.values()) {
|
|
72
|
+
const mirrors = group.filter((row) => policy.classify(row).target);
|
|
73
|
+
const legacy = group.filter((row) => policy.classify(row).baselineLegacy);
|
|
74
|
+
if (mirrors.length !== 1 || legacy.length > 1 || group.length !== mirrors.length + legacy.length) {
|
|
75
|
+
throw new AcquisitionTermsError("acquisition_terms_ambiguous");
|
|
76
|
+
}
|
|
77
|
+
const mirror = mirrors[0]!;
|
|
78
|
+
if (legacy.length === 1 ? !policy.pairMatches(legacy[0]!, mirror) : policy.facts(mirror).mirrorOf != null) {
|
|
79
|
+
throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
80
|
+
}
|
|
81
|
+
const terms = resolved(mirror, metadata(mirror, policy), policy);
|
|
82
|
+
selections.push({ kind: legacy.length ? "legacy_pair" : "v2_standalone", terms,
|
|
83
|
+
sourcePromotionId: policy.facts(mirror).id, ...(legacy.length ? { legacyPromotionId: policy.facts(legacy[0]!).id } : {}), nextBoundaryAt: null });
|
|
84
|
+
}
|
|
85
|
+
selections.sort((a, b) => Date.parse(a.terms.effectiveFrom) - Date.parse(b.terms.effectiveFrom));
|
|
86
|
+
for (let i = 1; i < selections.length; i++) {
|
|
87
|
+
const previousEnd = selections[i - 1]!.terms.effectiveTo;
|
|
88
|
+
const start = Date.parse(selections[i]!.terms.effectiveFrom);
|
|
89
|
+
if (previousEnd === null || Date.parse(previousEnd) > start) throw new AcquisitionTermsError("acquisition_terms_ambiguous");
|
|
90
|
+
if (Date.parse(previousEnd) < start) throw new AcquisitionTermsError("acquisition_terms_not_effective");
|
|
91
|
+
}
|
|
92
|
+
const current = selections.filter(({ terms }) => Date.parse(terms.effectiveFrom) <= at
|
|
93
|
+
&& (terms.effectiveTo === null || Date.parse(terms.effectiveTo) > at));
|
|
94
|
+
if (current.length !== 1) throw new AcquisitionTermsError("acquisition_terms_not_effective");
|
|
95
|
+
if (scoped.some((row) => !policy.facts(row).metadataPresent && policy.classify(row).canonicalRow && effective(row, at, policy))) {
|
|
96
|
+
throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
97
|
+
}
|
|
98
|
+
const boundaries = selections.flatMap(({ terms }) => [terms.effectiveFrom, terms.effectiveTo])
|
|
99
|
+
.filter((instant): instant is string => instant !== null && Date.parse(instant) > at).sort();
|
|
100
|
+
return { ...current[0]!, nextBoundaryAt: boundaries[0] ?? null };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Ordinary coupon validity and inactive rows remain owned by their evaluator. */
|
|
104
|
+
export function filterAcquisitionPromotions<Row>(rows: readonly Row[], atTime: string, regionCode: string,
|
|
105
|
+
policy: AcquisitionFinancialPolicy<Row>): Row[] {
|
|
106
|
+
requirePolicy(policy);
|
|
107
|
+
const at = Date.parse(atTime);
|
|
108
|
+
if (!Number.isFinite(at)) throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
109
|
+
return rows.filter((row) => {
|
|
110
|
+
if (!policy.facts(row).regionCodes.includes(regionCode) || !policy.classify(row).canonicalRow) return true;
|
|
111
|
+
if (policy.facts(row).metadataPresent) {
|
|
112
|
+
const parsed = metadata(row, policy);
|
|
113
|
+
resolved(row, parsed, policy, "filter_validation");
|
|
114
|
+
}
|
|
115
|
+
return effective(row, at, policy);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function assertAcquisitionTermsCapability(
|
|
120
|
+
selection: AcquisitionTermsSelection | null, capability: unknown, promotionEngineVersion: unknown,
|
|
121
|
+
): void {
|
|
122
|
+
if (!selection || selection.kind !== "v2_standalone") return;
|
|
123
|
+
if (capability !== ACQUISITION_TERMS_CAPABILITY) throw new AcquisitionTermsError("acquisition_terms_capability_required");
|
|
124
|
+
if (promotionEngineVersion !== "promotion-engine.v2") throw new AcquisitionTermsError("acquisition_terms_v2_required");
|
|
125
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ACQUISITION_TERMS_CAPABILITY, type AcquisitionTerms, type ResolvedAcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
3
|
+
export const acquisitionCapabilitySchema = z.literal(ACQUISITION_TERMS_CAPABILITY);
|
|
4
|
+
const revisionSchema = z.string().min(1).max(80).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
5
|
+
const bpsSchema = z.number().int().min(0).max(10_000);
|
|
6
|
+
const instantSchema = z.string().datetime({ offset: true });
|
|
7
|
+
|
|
8
|
+
/** Only these extra terms are stored; promotion benefit and validity remain authoritative. */
|
|
9
|
+
export const acquisitionTermsMetadataSchema = z.object({
|
|
10
|
+
schemaVersion: acquisitionCapabilitySchema,
|
|
11
|
+
revision: revisionSchema,
|
|
12
|
+
delivery2DiscountBps: bpsSchema,
|
|
13
|
+
}).strict();
|
|
14
|
+
|
|
15
|
+
const termsShape = {
|
|
16
|
+
...acquisitionTermsMetadataSchema.shape,
|
|
17
|
+
initialDiscountBps: bpsSchema,
|
|
18
|
+
effectiveFrom: instantSchema,
|
|
19
|
+
effectiveTo: instantSchema.nullable(),
|
|
20
|
+
};
|
|
21
|
+
const validWindow = (terms: { effectiveFrom?: string; effectiveTo?: string | null }) =>
|
|
22
|
+
typeof terms.effectiveFrom === "string" && (terms.effectiveTo === null
|
|
23
|
+
|| typeof terms.effectiveTo === "string" && Date.parse(terms.effectiveTo) > Date.parse(terms.effectiveFrom));
|
|
24
|
+
|
|
25
|
+
export const acquisitionTermsSchema = z.object(termsShape).strict()
|
|
26
|
+
.refine(validWindow, { message: "Acquisition terms require a nonempty effective interval" });
|
|
27
|
+
export const resolvedAcquisitionTermsSchema = z.object({
|
|
28
|
+
...termsShape,
|
|
29
|
+
sourcePromotionId: z.string().min(1).max(120),
|
|
30
|
+
}).strict().refine(validWindow, { message: "Acquisition terms require a nonempty effective interval" });
|
|
31
|
+
|
|
32
|
+
export const acquisitionTermsExpectationSchema = z.object({
|
|
33
|
+
revision: revisionSchema,
|
|
34
|
+
delivery2DiscountBps: bpsSchema,
|
|
35
|
+
}).strict();
|
|
36
|
+
|
|
37
|
+
export function projectAcquisitionTerms(terms: ResolvedAcquisitionTerms): AcquisitionTerms {
|
|
38
|
+
const { sourcePromotionId: _source, ...publicTerms } = resolvedAcquisitionTermsSchema.parse(terms);
|
|
39
|
+
return publicTerms;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/** Compose only at an opted-in host boundary; preserve its existing refinements. */
|
|
44
|
+
export function createAcquisitionQuoteSchemas<Request extends z.ZodObject, Context extends z.ZodObject, Quote extends z.ZodObject>(input: {
|
|
45
|
+
request: Request; context: Context; quote: Quote;
|
|
46
|
+
}) {
|
|
47
|
+
return {
|
|
48
|
+
request: input.request.safeExtend({ acquisitionCapability: acquisitionCapabilitySchema.optional() }),
|
|
49
|
+
context: input.context.safeExtend({ acquisitionCapability: acquisitionCapabilitySchema.optional(), atTime: instantSchema.optional() }),
|
|
50
|
+
quote: input.quote.safeExtend({ acquisitionTerms: resolvedAcquisitionTermsSchema.optional() }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { CATALOG_FACTS_POLICY_PROVENANCE_VERSION, CATALOG_FACTS_PROVENANCE_VERSION } from "./contracts/provenance.js";
|
|
2
|
+
import type { CatalogFactsProvenance } from "./contracts/provenance.js";
|
|
3
|
+
import {
|
|
4
|
+
catalogFactsProvenanceSchema,
|
|
5
|
+
} from "./catalogFactsSchemas.js";
|
|
6
|
+
import type { CreateQuoteResponse } from "./contracts/quote.js";
|
|
7
|
+
import { CommerceQuoteError } from "./quotePorts.js";
|
|
8
|
+
import type { CommerceQuoteCatalogItem } from "./commerceQuoteCatalogReadPort.js";
|
|
9
|
+
|
|
10
|
+
type ResolvedPriceObservation = {
|
|
11
|
+
priceEntryId: string;
|
|
12
|
+
unitPriceMinor: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type StrictCatalogFactsItem = CommerceQuoteCatalogItem & {
|
|
16
|
+
documentRevision: { id: string; digest: string };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type PolicyDerivedSubscriptionFacts = {
|
|
20
|
+
policyRevisionId: string;
|
|
21
|
+
policyDigest: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function createCatalogFactsProvenance(input: {
|
|
25
|
+
item: StrictCatalogFactsItem;
|
|
26
|
+
resolved: ResolvedPriceObservation;
|
|
27
|
+
base: ResolvedPriceObservation;
|
|
28
|
+
mode: "one_time" | "subscription";
|
|
29
|
+
atTime: string;
|
|
30
|
+
currency: string;
|
|
31
|
+
resolvedUnitAmountMinor: number;
|
|
32
|
+
resolvedLineAmountMinor: number;
|
|
33
|
+
baseUnitAmountMinor: number;
|
|
34
|
+
policy?: PolicyDerivedSubscriptionFacts;
|
|
35
|
+
}): CatalogFactsProvenance {
|
|
36
|
+
if (input.policy) {
|
|
37
|
+
if (input.mode !== "subscription") {
|
|
38
|
+
throw new CommerceQuoteError("PRICING_INVARIANT_VIOLATION", "One-time quote facts cannot carry a subscription policy");
|
|
39
|
+
}
|
|
40
|
+
return catalogFactsProvenanceSchema.parse({
|
|
41
|
+
version: CATALOG_FACTS_POLICY_PROVENANCE_VERSION,
|
|
42
|
+
skuId: input.item.skuId,
|
|
43
|
+
documentRevisionId: input.item.documentRevision.id,
|
|
44
|
+
documentDigest: input.item.documentRevision.digest,
|
|
45
|
+
basePriceEntryId: input.base.priceEntryId,
|
|
46
|
+
policyRevisionId: input.policy.policyRevisionId,
|
|
47
|
+
policyDigest: input.policy.policyDigest,
|
|
48
|
+
mode: input.mode,
|
|
49
|
+
atTime: input.atTime,
|
|
50
|
+
currency: input.currency,
|
|
51
|
+
resolvedUnitAmountMinor: input.resolvedUnitAmountMinor,
|
|
52
|
+
resolvedLineAmountMinor: input.resolvedLineAmountMinor,
|
|
53
|
+
baseUnitAmountMinor: input.baseUnitAmountMinor,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return catalogFactsProvenanceSchema.parse({
|
|
57
|
+
version: CATALOG_FACTS_PROVENANCE_VERSION,
|
|
58
|
+
skuId: input.item.skuId,
|
|
59
|
+
documentRevisionId: input.item.documentRevision.id,
|
|
60
|
+
documentDigest: input.item.documentRevision.digest,
|
|
61
|
+
resolvedPriceEntryId: input.resolved.priceEntryId,
|
|
62
|
+
basePriceEntryId: input.base.priceEntryId,
|
|
63
|
+
mode: input.mode,
|
|
64
|
+
atTime: input.atTime,
|
|
65
|
+
currency: input.currency,
|
|
66
|
+
resolvedUnitAmountMinor: input.resolvedUnitAmountMinor,
|
|
67
|
+
resolvedLineAmountMinor: input.resolvedLineAmountMinor,
|
|
68
|
+
baseUnitAmountMinor: input.baseUnitAmountMinor,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function catalogFactsForQuoteLine(
|
|
73
|
+
enabled: boolean, item: CommerceQuoteCatalogItem, resolved: ResolvedPriceObservation,
|
|
74
|
+
base: ResolvedPriceObservation, mode: "one_time" | "subscription",
|
|
75
|
+
atTime: string, currency: string, resolvedUnitAmountMinor: number,
|
|
76
|
+
resolvedLineAmountMinor: number, baseUnitAmountMinor: number,
|
|
77
|
+
policy?: PolicyDerivedSubscriptionFacts,
|
|
78
|
+
): CatalogFactsProvenance | undefined {
|
|
79
|
+
if (!enabled) return undefined;
|
|
80
|
+
const { id, digest } = item.documentRevision;
|
|
81
|
+
if (id === null || digest === null) {
|
|
82
|
+
throw new CommerceQuoteError("PRICE_NOT_CONFIGURED", "Strict catalog facts are unavailable", {
|
|
83
|
+
sku: item.skuCode,
|
|
84
|
+
variantId: item.variantId,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return createCatalogFactsProvenance({
|
|
88
|
+
item: { ...item, documentRevision: { id, digest } },
|
|
89
|
+
resolved, base, mode, atTime, currency, resolvedUnitAmountMinor, resolvedLineAmountMinor, baseUnitAmountMinor, policy,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Public responses retain every money field but never server-only catalog identifiers. */
|
|
94
|
+
export function projectPublicQuoteSnapshot<Snapshot extends CreateQuoteResponse>(snapshot: Snapshot): Snapshot {
|
|
95
|
+
return {
|
|
96
|
+
...snapshot,
|
|
97
|
+
quote: {
|
|
98
|
+
...snapshot.quote,
|
|
99
|
+
lines: snapshot.quote.lines.map(({ catalogFacts: _catalogFacts, ...line }) => line),
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|