@openlup/platform 0.17.0 → 0.19.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 +143 -0
- package/README.md +299 -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 +21 -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 +173 -0
- package/dist/catalog/bundleCatalogReadPort.js +36 -0
- package/dist/catalog/bundleCatalogStore.d.ts +5 -0
- package/dist/catalog/bundleCatalogStore.js +156 -0
- package/dist/catalog/bundlePriceSelection.d.ts +13 -0
- package/dist/catalog/bundlePriceSelection.js +26 -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 +241 -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/effectiveBundleCatalogQueries.d.ts +8 -0
- package/dist/catalog/effectiveBundleCatalogQueries.js +79 -0
- package/dist/catalog/effectiveBundleCatalogStore.d.ts +4 -0
- package/dist/catalog/effectiveBundleCatalogStore.js +156 -0
- package/dist/catalog/foundationSchemas.d.ts +98 -0
- package/dist/catalog/foundationSchemas.js +110 -0
- package/dist/catalog/index.d.ts +44 -0
- package/dist/catalog/index.js +26 -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 +3 -0
- package/dist/composition/index.js +3 -0
- package/dist/composition/inventoryContribution.d.ts +16 -0
- package/dist/composition/inventoryContribution.js +17 -0
- package/dist/composition/shippingContribution.d.ts +16 -0
- package/dist/composition/shippingContribution.js +17 -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/bindings.d.ts +35 -0
- package/dist/inventory/bindings.js +45 -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 +14 -0
- package/dist/inventory/index.js +11 -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 +28 -0
- package/dist/pricing/commercePriceAuthority.js +140 -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 +35 -0
- package/dist/pricing/commerceQuoteHandler.js +122 -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 +74 -0
- package/dist/pricing/dbBackedCommerceQuotePort.js +223 -0
- package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
- package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
- package/dist/pricing/index.d.ts +52 -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/quoteShippingPromotions.d.ts +20 -0
- package/dist/pricing/quoteShippingPromotions.js +1 -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/commerceQuoteReadPreparation.d.ts +3 -0
- package/dist/promotions/commerceQuoteReadPreparation.js +27 -0
- package/dist/promotions/contracts/index.d.ts +387 -0
- package/dist/promotions/contracts/index.js +1 -0
- package/dist/promotions/index.d.ts +25 -0
- package/dist/promotions/index.js +22 -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 +70 -0
- package/dist/promotions/promotionQuoteAcceptance.js +202 -0
- package/dist/promotions/promotionQuoteBinding.d.ts +73 -0
- package/dist/promotions/promotionQuoteBinding.js +87 -0
- package/dist/promotions/quoteAcquisitionBinding.d.ts +6 -0
- package/dist/promotions/quoteAcquisitionBinding.js +1 -0
- package/dist/promotions/quoteAutomaticPromotions.d.ts +26 -0
- package/dist/promotions/quoteAutomaticPromotions.js +60 -0
- package/dist/promotions/quotePromoDiscounts.d.ts +36 -0
- package/dist/promotions/quotePromoDiscounts.js +146 -0
- package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
- package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
- package/dist/promotions/quotePromotionV2.d.ts +30 -0
- package/dist/promotions/quotePromotionV2.js +186 -0
- package/dist/promotions/quoteShippingPolicy.d.ts +23 -0
- package/dist/promotions/quoteShippingPolicy.js +47 -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/bindings.d.ts +57 -0
- package/dist/shipping/bindings.js +46 -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 +4 -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/contracts/threshold.d.ts +22 -0
- package/dist/shipping/contracts/threshold.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 +20 -0
- package/dist/shipping/index.js +10 -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/dist/shipping/thresholdPolicy.d.ts +42 -0
- package/dist/shipping/thresholdPolicy.js +60 -0
- package/package.json +62 -2
- package/release-gates.json +263 -53
- 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 +263 -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 +181 -0
- package/src/catalog/bundleCatalogStore.ts +274 -0
- package/src/catalog/bundlePriceSelection.ts +25 -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/effectiveBundleCatalogQueries.ts +80 -0
- package/src/catalog/effectiveBundleCatalogStore.ts +208 -0
- package/src/catalog/foundationSchemas.ts +123 -0
- package/src/catalog/index.ts +44 -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 +3 -0
- package/src/composition/inventoryContribution.ts +24 -0
- package/src/composition/shippingContribution.ts +24 -0
- package/src/inventory/adminInventory.ts +251 -0
- package/src/inventory/bindings.ts +61 -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 +22 -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 +169 -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 +199 -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 +280 -0
- package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
- package/src/pricing/index.ts +53 -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/quoteShippingPromotions.ts +21 -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/commerceQuoteReadPreparation.ts +29 -0
- package/src/promotions/contracts/index.ts +387 -0
- package/src/promotions/index.ts +25 -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 +291 -0
- package/src/promotions/promotionQuoteBinding.ts +163 -0
- package/src/promotions/quoteAcquisitionBinding.ts +7 -0
- package/src/promotions/quoteAutomaticPromotions.ts +84 -0
- package/src/promotions/quotePromoDiscounts.ts +194 -0
- package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
- package/src/promotions/quotePromotionV2.ts +239 -0
- package/src/promotions/quoteShippingPolicy.ts +61 -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/bindings.ts +74 -0
- package/src/shipping/contracts/address.ts +71 -0
- package/src/shipping/contracts/delivery.ts +114 -0
- package/src/shipping/contracts/index.ts +19 -0
- package/src/shipping/contracts/shippingRate.ts +7 -0
- package/src/shipping/contracts/threshold.ts +22 -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 +26 -0
- package/src/shipping/pickupPointSearchPort.ts +8 -0
- package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
- package/src/shipping/shippingRatePorts.ts +36 -0
- package/src/shipping/thresholdPolicy.ts +65 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const CATALOG_FACTS_PROVENANCE_VERSION = "catalog_facts_v1" as const;
|
|
2
|
+
export const CATALOG_FACTS_POLICY_PROVENANCE_VERSION = "catalog_facts_v2" as const;
|
|
3
|
+
|
|
4
|
+
interface CatalogFactsFields {
|
|
5
|
+
skuId: string;
|
|
6
|
+
documentRevisionId: string;
|
|
7
|
+
documentDigest: string;
|
|
8
|
+
basePriceEntryId: string;
|
|
9
|
+
atTime: string;
|
|
10
|
+
currency: string;
|
|
11
|
+
resolvedUnitAmountMinor: number;
|
|
12
|
+
resolvedLineAmountMinor: number;
|
|
13
|
+
baseUnitAmountMinor: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type CatalogFactsProvenance = CatalogFactsFields & (
|
|
17
|
+
| { version: typeof CATALOG_FACTS_PROVENANCE_VERSION; resolvedPriceEntryId: string; mode: "one_time" | "subscription" }
|
|
18
|
+
| { version: typeof CATALOG_FACTS_POLICY_PROVENANCE_VERSION; policyRevisionId: string; policyDigest: string; mode: "subscription" }
|
|
19
|
+
);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { CommerceCurrency, CommerceMoney, CommerceTaxBreakdown } from "./money.js";
|
|
2
|
+
import type { CommerceCodeRejectionDetail } from "./evidence.js";
|
|
3
|
+
|
|
4
|
+
export const COMMERCE_CONTRACT_VERSION = "commerce.v0";
|
|
5
|
+
|
|
6
|
+
export interface CommerceQuotePricingComponent {
|
|
7
|
+
scope: "line" | "order";
|
|
8
|
+
componentType: "base_unit" | "mode_discount" | "qty_tier" | "promo" | "loyalty" | "shipping" | "bundle";
|
|
9
|
+
amountMinor: number;
|
|
10
|
+
reasonCode: string;
|
|
11
|
+
reasonPayload: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Hosts may extend this context with their own validated policy. */
|
|
15
|
+
export interface CommerceQuoteContext {
|
|
16
|
+
mode: "one_time" | "subscription";
|
|
17
|
+
cadenceDays?: number | null;
|
|
18
|
+
promoCodes: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CommerceQuoteLine {
|
|
22
|
+
sku: string;
|
|
23
|
+
productSlug: string;
|
|
24
|
+
quantity: number;
|
|
25
|
+
unitPriceGross: CommerceMoney;
|
|
26
|
+
lineSubtotalGross: CommerceMoney;
|
|
27
|
+
tax: CommerceTaxBreakdown;
|
|
28
|
+
pricingComponents?: CommerceQuotePricingComponent[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type CommercePromotionCustomerSemantic =
|
|
32
|
+
| "first_purchase_10"
|
|
33
|
+
| "first_subscription_50"
|
|
34
|
+
| "bundle_5";
|
|
35
|
+
|
|
36
|
+
export interface CommerceQuoteDiscount {
|
|
37
|
+
promotionId: string;
|
|
38
|
+
code?: string | null;
|
|
39
|
+
label?: string;
|
|
40
|
+
appliesTo: "order_total" | "line" | "shipping";
|
|
41
|
+
amountOffMinor: number;
|
|
42
|
+
reasonCode: string;
|
|
43
|
+
/** Stable presentation identity for canonical offers; never inferred from an editable name. */
|
|
44
|
+
customerSemantic?: CommercePromotionCustomerSemantic;
|
|
45
|
+
promotionEngineVersion?: "promotion-engine.v2";
|
|
46
|
+
promotionCodeId?: string;
|
|
47
|
+
promotionCodeRevision?: number;
|
|
48
|
+
promotionDefinitionFingerprint?: string;
|
|
49
|
+
promotionCodeScopes?: Array<"one_time" | "subscription_initial">;
|
|
50
|
+
promotionMinimumReferenceMinor?: number;
|
|
51
|
+
promotionCodeValidTo?: string | null;
|
|
52
|
+
promotionBenefitKind?: "target_percentage" | "percentage" | "fixed_amount" | "free_shipping";
|
|
53
|
+
promotionBenefitValueBps?: number;
|
|
54
|
+
promotionBenefitValueMinor?: number;
|
|
55
|
+
floorApplied?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Why a customer-submitted promo code did not produce a discount, surfaced so the FE
|
|
60
|
+
* can show a specific message instead of a generic "invalid". `better_price_exists` is
|
|
61
|
+
* the one non-error reason (eligible but unused because an automatic offer is equally
|
|
62
|
+
* good or better; the FE renders it neutrally); `scope_not_applicable` means the code
|
|
63
|
+
* is real but does not apply to this order type. The last two are v2-engine-only reasons.
|
|
64
|
+
*/
|
|
65
|
+
export type CommerceCodeRejectionReason =
|
|
66
|
+
| "not_recognized" | "already_used" | "not_eligible" | "expired"
|
|
67
|
+
| "better_price_exists" | "scope_not_applicable";
|
|
68
|
+
|
|
69
|
+
export interface CommerceCodeRejection {
|
|
70
|
+
code: string;
|
|
71
|
+
reason: CommerceCodeRejectionReason;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CommerceQuote {
|
|
75
|
+
currency: CommerceCurrency;
|
|
76
|
+
taxIncluded: true;
|
|
77
|
+
lines: CommerceQuoteLine[];
|
|
78
|
+
discounts: CommerceQuoteDiscount[];
|
|
79
|
+
pricingComponents?: CommerceQuotePricingComponent[];
|
|
80
|
+
context?: CommerceQuoteContext;
|
|
81
|
+
/** Submitted coupon codes that produced no discount, with the reason. Omitted when empty. */
|
|
82
|
+
codeRejections?: CommerceCodeRejection[];
|
|
83
|
+
/** UI-safe specifics for selected rejected codes. Omitted when no detail is available. */
|
|
84
|
+
codeRejectionDetails?: CommerceCodeRejectionDetail[];
|
|
85
|
+
subtotalGross: CommerceMoney;
|
|
86
|
+
discountTotalGross: CommerceMoney;
|
|
87
|
+
/** Gross shipping before any free-shipping promo. Optional; absent ⇒ shipping not priced (legacy/0). */
|
|
88
|
+
shippingGross?: CommerceMoney;
|
|
89
|
+
/** Gross shipping discount from a free-shipping promo (≤ shippingGross). Optional; absent ⇒ 0. */
|
|
90
|
+
shippingDiscountGross?: CommerceMoney;
|
|
91
|
+
totalGross: CommerceMoney;
|
|
92
|
+
netTotal: CommerceMoney;
|
|
93
|
+
taxTotal: CommerceMoney;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface CreateQuoteRequest {
|
|
97
|
+
mode: "one_time" | "subscription";
|
|
98
|
+
lines: import("./money.js").CartLineInput[];
|
|
99
|
+
cadenceDays?: number | null;
|
|
100
|
+
promoCodes: string[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface CreateQuoteResponse {
|
|
104
|
+
contractVersion: typeof COMMERCE_CONTRACT_VERSION;
|
|
105
|
+
quote: Omit<CommerceQuote, "lines"> & { lines: Array<CommerceQuoteLine & { catalogFacts?: import("./provenance.js").CatalogFactsProvenance }> };
|
|
106
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { CommercePriceAuthorityResult } from "./contracts/authority.js";
|
|
2
|
+
import type { CreateQuoteRequest } from "./contracts/quote.js";
|
|
3
|
+
import type {
|
|
4
|
+
CommerceMoney,
|
|
5
|
+
CommerceQuotePricingComponent,
|
|
6
|
+
} from "./contracts/index.js";
|
|
7
|
+
import type { PricingComponent } from "@openlup/core/pricing";
|
|
8
|
+
import { CommerceQuoteError } from "./quotePorts.js";
|
|
9
|
+
import { splitIncludedVat } from "./quoteSchemas.js";
|
|
10
|
+
import {
|
|
11
|
+
CommercePriceAuthorityError,
|
|
12
|
+
type CommercePriceAuthorityPort,
|
|
13
|
+
} from "./ports.js";
|
|
14
|
+
import type { PricingResolverPort, PricingMode, ResolvedPrice } from "@openlup/core/pricing";
|
|
15
|
+
import type {
|
|
16
|
+
CommerceQuoteCatalogItem,
|
|
17
|
+
} from "./commerceQuoteCatalogReadPort.js";
|
|
18
|
+
|
|
19
|
+
/** Pure helpers for the DB-backed commerce quote port (extracted to keep the port under the LOC cap). */
|
|
20
|
+
|
|
21
|
+
export interface SkuIndexEntry<Item = CommerceQuoteCatalogItem> {
|
|
22
|
+
item: Item;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function buildSkuIndex<Item extends CommerceQuoteCatalogItem>(
|
|
26
|
+
quoteCatalogReadPort: { listQuoteCatalogItems(): Promise<Item[]> },
|
|
27
|
+
): Promise<Map<string, SkuIndexEntry<Item>>> {
|
|
28
|
+
const items = await quoteCatalogReadPort.listQuoteCatalogItems();
|
|
29
|
+
return new Map(
|
|
30
|
+
items.map((item) => [item.skuCode, { item }] as const),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function eligibleQtyForMode(request: CreateQuoteRequest, mode: Exclude<PricingMode, "any">): number {
|
|
35
|
+
return request.lines
|
|
36
|
+
.filter((line) => !line.isAddon)
|
|
37
|
+
.filter((line) => (line.modeAtLine ?? request.mode) === mode)
|
|
38
|
+
.reduce((sum, line) => sum + line.quantity, 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function resolveOneTimeComparison(input: {
|
|
42
|
+
pricingResolverPort: PricingResolverPort;
|
|
43
|
+
variantId: string;
|
|
44
|
+
lineQty: number;
|
|
45
|
+
eligibleCartQty: number;
|
|
46
|
+
regionCode: string;
|
|
47
|
+
currency: string;
|
|
48
|
+
atTime: string;
|
|
49
|
+
}): Promise<ResolvedPrice | null> {
|
|
50
|
+
return input.pricingResolverPort.resolvePrice({
|
|
51
|
+
variantId: input.variantId,
|
|
52
|
+
mode: "one_time",
|
|
53
|
+
lineQty: input.lineQty,
|
|
54
|
+
eligibleCartQty: input.eligibleCartQty,
|
|
55
|
+
regionCode: input.regionCode,
|
|
56
|
+
currency: input.currency,
|
|
57
|
+
atTime: input.atTime,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface StrictQuotePriceResolution {
|
|
62
|
+
base: ResolvedPrice;
|
|
63
|
+
resolved: ResolvedPrice;
|
|
64
|
+
policy?: {
|
|
65
|
+
policyRevisionId: string;
|
|
66
|
+
policyDigest: string;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Strict authority quotes require one exact one-time base anchor. Subscription money is either
|
|
72
|
+
* derived by a validated policy or taken from the authority's explicit legacy
|
|
73
|
+
* subscription observation; this layer never calls the generic resolver.
|
|
74
|
+
*/
|
|
75
|
+
export async function resolveStrictQuotePrice(input: {
|
|
76
|
+
commercePriceAuthorityPort: CommercePriceAuthorityPort;
|
|
77
|
+
variantId: string;
|
|
78
|
+
mode: Exclude<PricingMode, "any">;
|
|
79
|
+
regionCode: string;
|
|
80
|
+
currency: string;
|
|
81
|
+
channel: string;
|
|
82
|
+
atTime: string;
|
|
83
|
+
}): Promise<StrictQuotePriceResolution> {
|
|
84
|
+
try {
|
|
85
|
+
const authority = await input.commercePriceAuthorityPort.resolvePrice({
|
|
86
|
+
variantId: input.variantId,
|
|
87
|
+
mode: input.mode,
|
|
88
|
+
regionCode: input.regionCode,
|
|
89
|
+
currency: input.currency,
|
|
90
|
+
channel: input.channel,
|
|
91
|
+
atTime: input.atTime,
|
|
92
|
+
});
|
|
93
|
+
return strictQuotePriceResolution(authority, input.mode);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof CommercePriceAuthorityError) {
|
|
96
|
+
throw new CommerceQuoteError(
|
|
97
|
+
"PRICE_NOT_CONFIGURED",
|
|
98
|
+
"Commerce price is not configured",
|
|
99
|
+
{ reason: error.code },
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function strictQuotePriceResolution(
|
|
107
|
+
authority: CommercePriceAuthorityResult,
|
|
108
|
+
requestedMode: Exclude<PricingMode, "any">,
|
|
109
|
+
): StrictQuotePriceResolution {
|
|
110
|
+
const base = authority.base;
|
|
111
|
+
if (base.mode !== "one_time" || base.matchedMinQty !== 1) {
|
|
112
|
+
throw new CommerceQuoteError(
|
|
113
|
+
"PRICING_INVARIANT_VIOLATION",
|
|
114
|
+
"Strict money authority did not return an exact one-time base anchor",
|
|
115
|
+
{ basePriceEntryId: base.priceEntryId, mode: base.mode, matchedMinQty: base.matchedMinQty },
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (requestedMode === "one_time") {
|
|
120
|
+
if (authority.kind !== "one_time" || authority.unitPriceMinor !== base.unitPriceMinor) {
|
|
121
|
+
throw new CommerceQuoteError(
|
|
122
|
+
"PRICING_INVARIANT_VIOLATION",
|
|
123
|
+
"Strict money authority returned an invalid one-time result",
|
|
124
|
+
{ authorityKind: authority.kind, basePriceEntryId: base.priceEntryId },
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return { base, resolved: base };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (authority.kind === "subscription_policy") {
|
|
131
|
+
return {
|
|
132
|
+
base,
|
|
133
|
+
resolved: { ...base, mode: "subscription", unitPriceMinor: authority.unitPriceMinor },
|
|
134
|
+
policy: {
|
|
135
|
+
policyRevisionId: authority.policy.id,
|
|
136
|
+
policyDigest: authority.policy.digest,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (authority.kind === "subscription_legacy") {
|
|
141
|
+
if (authority.legacyResolved.mode !== "subscription" || authority.legacyResolved.unitPriceMinor !== authority.unitPriceMinor) {
|
|
142
|
+
throw new CommerceQuoteError(
|
|
143
|
+
"PRICING_INVARIANT_VIOLATION",
|
|
144
|
+
"Strict money authority returned an invalid legacy subscription result",
|
|
145
|
+
{ legacyPriceEntryId: authority.legacyResolved.priceEntryId, mode: authority.legacyResolved.mode },
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return { base, resolved: authority.legacyResolved };
|
|
149
|
+
}
|
|
150
|
+
throw new CommerceQuoteError(
|
|
151
|
+
"PRICING_INVARIANT_VIOLATION",
|
|
152
|
+
"Strict money authority returned a one-time result for a subscription quote",
|
|
153
|
+
{ authorityKind: authority.kind, basePriceEntryId: base.priceEntryId },
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function assertSubscriptionCheapest(input: {
|
|
158
|
+
requestSku: string;
|
|
159
|
+
modeAtLine: Exclude<PricingMode, "any">;
|
|
160
|
+
resolved: ResolvedPrice;
|
|
161
|
+
oneTime: ResolvedPrice | null;
|
|
162
|
+
vatRateBps: number;
|
|
163
|
+
}): void {
|
|
164
|
+
if (
|
|
165
|
+
input.modeAtLine === "subscription" &&
|
|
166
|
+
input.oneTime &&
|
|
167
|
+
toGrossMinor(input.resolved, input.vatRateBps) > toGrossMinor(input.oneTime, input.vatRateBps)
|
|
168
|
+
) {
|
|
169
|
+
throw new CommerceQuoteError(
|
|
170
|
+
"PRICING_INVARIANT_VIOLATION",
|
|
171
|
+
"Subscription price must not exceed active one-time price",
|
|
172
|
+
{
|
|
173
|
+
sku: input.requestSku,
|
|
174
|
+
subscriptionPriceEntryId: input.resolved.priceEntryId,
|
|
175
|
+
oneTimePriceEntryId: input.oneTime.priceEntryId,
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function toGrossMinor(price: ResolvedPrice, vatRateBps: number): number {
|
|
182
|
+
if (price.amountKind === "gross") return price.unitPriceMinor;
|
|
183
|
+
return Math.round((price.unitPriceMinor * (10_000 + vatRateBps)) / 10_000);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function calculateIncludedVat(
|
|
187
|
+
grossMinor: number,
|
|
188
|
+
vatRateBps: number,
|
|
189
|
+
): { netMinor: number; vatMinor: number } {
|
|
190
|
+
return splitIncludedVat(grossMinor, vatRateBps);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Exhaustive transport vocabulary; widening requires the matching durable contract. */
|
|
194
|
+
const TRANSPORT_COMPONENT_TYPES: Record<CommerceQuotePricingComponent["componentType"], true> = {
|
|
195
|
+
base_unit: true,
|
|
196
|
+
mode_discount: true,
|
|
197
|
+
qty_tier: true,
|
|
198
|
+
promo: true,
|
|
199
|
+
loyalty: true,
|
|
200
|
+
shipping: true,
|
|
201
|
+
bundle: true,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
function isTransportComponentType(
|
|
205
|
+
componentType: PricingComponent["component_type"],
|
|
206
|
+
): componentType is CommerceQuotePricingComponent["componentType"] {
|
|
207
|
+
return Object.prototype.hasOwnProperty.call(TRANSPORT_COMPONENT_TYPES, componentType);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Map one kernel pricing component onto the quote transport shape.
|
|
212
|
+
*
|
|
213
|
+
* A component type outside the transport union stops the quote loudly. It is never
|
|
214
|
+
* cast through and never silently dropped: a dropped component breaks the breakdown
|
|
215
|
+
* invariant (components must sum to the line total), so a quote would go out priced
|
|
216
|
+
* from an incomplete explanation of its own total.
|
|
217
|
+
*/
|
|
218
|
+
export function mapPricingComponent(
|
|
219
|
+
component: PricingComponent,
|
|
220
|
+
scope: "line" | "order",
|
|
221
|
+
): CommerceQuotePricingComponent {
|
|
222
|
+
const componentType = component.component_type;
|
|
223
|
+
if (!isTransportComponentType(componentType)) {
|
|
224
|
+
throw new CommerceQuoteError(
|
|
225
|
+
"PRICING_INVARIANT_VIOLATION",
|
|
226
|
+
"Pricing component type is not carried by the quote transport",
|
|
227
|
+
{ componentType, carriedTypes: Object.keys(TRANSPORT_COMPONENT_TYPES) },
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
scope,
|
|
232
|
+
componentType,
|
|
233
|
+
amountMinor: component.amount_minor,
|
|
234
|
+
reasonCode: component.reason_code,
|
|
235
|
+
reasonPayload: component.reason_payload,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* A money builder bound to one currency.
|
|
241
|
+
*
|
|
242
|
+
* This used to be a plain `money(amountMinor)` that stamped the settlement
|
|
243
|
+
* currency as a literal, which meant the quote's declared `currency` and the
|
|
244
|
+
* currency on every amount inside it came from two different places. They
|
|
245
|
+
* agreed only because the port's `currency` option was typed as that same
|
|
246
|
+
* literal. Binding the builder once per port makes the agreement structural:
|
|
247
|
+
* there is no second place left for the two to disagree.
|
|
248
|
+
*/
|
|
249
|
+
export function moneyIn(currency: string): (amountMinor: number) => CommerceMoney {
|
|
250
|
+
return (amountMinor) => ({ amountMinor, currency });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function sum(values: number[]): number {
|
|
254
|
+
return values.reduce((total, value) => total + value, 0);
|
|
255
|
+
}
|
|
256
|
+
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type { ShippingTerms } from "../shipping/index.js";
|
|
2
|
+
import type { ResolvedAcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
3
|
+
import type { QuoteShippingPromotionInput, QuoteShippingPromotionResult } from "./quoteShippingPromotions.js";
|
|
4
|
+
import type { PricingResolverPort } from "@openlup/core/pricing";
|
|
5
|
+
import { buildLineBreakdown } from "@openlup/core/pricing";
|
|
6
|
+
import type { CreateQuoteResponse, PricingPolicySnapshot, CreateQuoteRequest, CommerceQuoteContext, CommerceQuoteLine, CommerceQuotePricingComponent, CommerceTaxProfile, CommerceQuoteDiscount, CommerceCodeRejection, CommerceCodeRejectionDetail, CartLineInput } from "./contracts/index.js";
|
|
7
|
+
import { COMMERCE_CONTRACT_VERSION, PROMOTION_ENGINE_V2 } from "./contracts/index.js";
|
|
8
|
+
import { CommerceQuoteError, type CreateQuoteOptions } from "./quotePorts.js";
|
|
9
|
+
import { splitIncludedVat } from "./quoteSchemas.js";
|
|
10
|
+
import type { CommercePriceAuthorityPort } from "./ports.js";
|
|
11
|
+
import { assertSubscriptionCheapest, buildSkuIndex, calculateIncludedVat, eligibleQtyForMode, mapPricingComponent, moneyIn, resolveOneTimeComparison, resolveStrictQuotePrice, sum, toGrossMinor } from "./dbBackedCommerceQuoteHelpers.js";
|
|
12
|
+
import { assertSupportedCommerceTaxProfile } from "./taxProfileGuard.js";
|
|
13
|
+
import type { CommerceQuoteCatalogItem } from "./commerceQuoteCatalogReadPort.js";
|
|
14
|
+
import { catalogFactsForQuoteLine, projectPublicQuoteSnapshot } from "./catalogFactsProvenance.js";
|
|
15
|
+
|
|
16
|
+
export interface QuotePromotionResult {
|
|
17
|
+
discounts: CommerceQuoteDiscount[];
|
|
18
|
+
codeRejections: CommerceCodeRejection[];
|
|
19
|
+
codeRejectionDetails: CommerceCodeRejectionDetail[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DbBackedCommerceQuotePortDeps<Request extends CreateQuoteRequest, Item extends CommerceQuoteCatalogItem, LineContext extends object, Context extends CommerceQuoteContext> {
|
|
23
|
+
quoteCatalogReadPort: { listQuoteCatalogItems(): Promise<Item[]> };
|
|
24
|
+
isStrictD1Quote: boolean;
|
|
25
|
+
commercePriceAuthorityPort?: CommercePriceAuthorityPort;
|
|
26
|
+
pricingResolverPort?: PricingResolverPort;
|
|
27
|
+
taxProfile: CommerceTaxProfile;
|
|
28
|
+
supportedTaxProfiles: readonly CommerceTaxProfile[];
|
|
29
|
+
regionCode: string;
|
|
30
|
+
currency: string;
|
|
31
|
+
now?: () => string;
|
|
32
|
+
shippingFlatMinor?: () => Promise<number>;
|
|
33
|
+
resolveQuoteShippingPromotions?: (input: QuoteShippingPromotionInput<Request>) => Promise<QuoteShippingPromotionResult>;
|
|
34
|
+
hasPromotionCodePort: boolean;
|
|
35
|
+
lineContext(line: CartLineInput, item: Item): LineContext;
|
|
36
|
+
quoteContext(request: Request, lines: Array<LineContext & { line: CommerceQuoteLine }>): () => Context;
|
|
37
|
+
resolveAutomaticQuotePromotions(input: {
|
|
38
|
+
request: Request; options?: CreateQuoteOptions; regionCode: string;
|
|
39
|
+
referenceProductMinor: number; subtotalGrossMinor: number; shippingGrossMinor: number; atTime: string;
|
|
40
|
+
}): Promise<Pick<QuotePromotionResult, "discounts" | "codeRejections">>;
|
|
41
|
+
applyPromotionCodesV2(input: {
|
|
42
|
+
preserveLegacyDiscountsWhenNoCandidates: boolean; clientId: string | null;
|
|
43
|
+
mode: Request["mode"]; promoCodes: string[]; referenceProductMinor: number;
|
|
44
|
+
subtotalGrossMinor: number; shippingGrossMinor: number;
|
|
45
|
+
discounts: CommerceQuoteDiscount[]; codeRejections: CommerceCodeRejection[]; atTime: string;
|
|
46
|
+
}): Promise<QuotePromotionResult>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const STRICT_D1_COMMERCE_CHANNEL = "D2C";
|
|
50
|
+
export function createDbBackedCommerceQuotePort<
|
|
51
|
+
Request extends CreateQuoteRequest, Item extends CommerceQuoteCatalogItem,
|
|
52
|
+
LineContext extends object, Context extends CommerceQuoteContext,
|
|
53
|
+
>(deps: DbBackedCommerceQuotePortDeps<Request, Item, LineContext, Context>) {
|
|
54
|
+
const {
|
|
55
|
+
taxProfile, supportedTaxProfiles, regionCode, currency,
|
|
56
|
+
now = () => new Date().toISOString(),
|
|
57
|
+
quoteCatalogReadPort, isStrictD1Quote, pricingResolverPort,
|
|
58
|
+
commercePriceAuthorityPort, shippingFlatMinor, resolveAutomaticQuotePromotions,
|
|
59
|
+
applyPromotionCodesV2, hasPromotionCodePort, lineContext, quoteContext, resolveQuoteShippingPromotions,
|
|
60
|
+
} = deps;
|
|
61
|
+
assertSupportedCommerceTaxProfile(taxProfile, supportedTaxProfiles);
|
|
62
|
+
const money = moneyIn(currency);
|
|
63
|
+
async function createServerAuthoritativeQuote(
|
|
64
|
+
request: Request,
|
|
65
|
+
options?: CreateQuoteOptions,
|
|
66
|
+
): Promise<Omit<CreateQuoteResponse, "quote"> & { quote: Omit<CreateQuoteResponse["quote"], "context"> & { context: Context & { pricingPolicy?: PricingPolicySnapshot; shippingTerms?: ShippingTerms; acquisitionTerms?: ResolvedAcquisitionTerms } } }> {
|
|
67
|
+
const atTime = now();
|
|
68
|
+
const skuIndex = await buildSkuIndex(quoteCatalogReadPort);
|
|
69
|
+
const pricedLines = await Promise.all(request.lines.map(async (requestLine) => {
|
|
70
|
+
const entry = skuIndex.get(requestLine.sku);
|
|
71
|
+
if (!entry || (requestLine.variantId && requestLine.variantId !== entry.item.variantId)) {
|
|
72
|
+
throw new CommerceQuoteError("UNKNOWN_SKU", "Unknown commerce SKU", {
|
|
73
|
+
sku: requestLine.sku,
|
|
74
|
+
variantId: requestLine.variantId ?? null,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const modeAtLine = requestLine.modeAtLine ?? request.mode;
|
|
78
|
+
if (!entry.item.sellability[modeAtLine === "one_time" ? "oneTime" : "subscription"]) {
|
|
79
|
+
throw new CommerceQuoteError("UNKNOWN_SKU", "Commerce SKU is not sellable in this purchase mode", {
|
|
80
|
+
sku: requestLine.sku,
|
|
81
|
+
variantId: entry.item.variantId,
|
|
82
|
+
mode: modeAtLine,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const eligibleCartQty = eligibleQtyForMode(request, modeAtLine);
|
|
86
|
+
const strictResolution = isStrictD1Quote
|
|
87
|
+
? await resolveStrictQuotePrice({
|
|
88
|
+
commercePriceAuthorityPort: commercePriceAuthorityPort!,
|
|
89
|
+
variantId: entry.item.variantId,
|
|
90
|
+
mode: modeAtLine,
|
|
91
|
+
regionCode,
|
|
92
|
+
currency,
|
|
93
|
+
channel: STRICT_D1_COMMERCE_CHANNEL,
|
|
94
|
+
atTime,
|
|
95
|
+
})
|
|
96
|
+
: undefined;
|
|
97
|
+
const resolved = strictResolution?.resolved ?? await pricingResolverPort!.resolvePrice({
|
|
98
|
+
variantId: entry.item.variantId,
|
|
99
|
+
mode: modeAtLine,
|
|
100
|
+
lineQty: requestLine.quantity,
|
|
101
|
+
eligibleCartQty,
|
|
102
|
+
regionCode,
|
|
103
|
+
currency,
|
|
104
|
+
atTime,
|
|
105
|
+
});
|
|
106
|
+
if (!resolved) {
|
|
107
|
+
throw new CommerceQuoteError("PRICE_NOT_CONFIGURED", "Commerce price is not configured", {
|
|
108
|
+
sku: requestLine.sku,
|
|
109
|
+
variantId: entry.item.variantId,
|
|
110
|
+
mode: modeAtLine,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const oneTime = strictResolution?.base ?? await resolveOneTimeComparison({
|
|
114
|
+
pricingResolverPort: pricingResolverPort!,
|
|
115
|
+
variantId: entry.item.variantId,
|
|
116
|
+
lineQty: requestLine.quantity,
|
|
117
|
+
eligibleCartQty,
|
|
118
|
+
regionCode,
|
|
119
|
+
currency,
|
|
120
|
+
atTime,
|
|
121
|
+
});
|
|
122
|
+
const promotionReference = hasPromotionCodePort && request.promoCodes.length > 0
|
|
123
|
+
? strictResolution?.base ?? await resolveOneTimeComparison({
|
|
124
|
+
pricingResolverPort: pricingResolverPort!,
|
|
125
|
+
variantId: entry.item.variantId,
|
|
126
|
+
lineQty: requestLine.quantity,
|
|
127
|
+
eligibleCartQty: 1,
|
|
128
|
+
regionCode,
|
|
129
|
+
currency,
|
|
130
|
+
atTime,
|
|
131
|
+
})
|
|
132
|
+
: oneTime;
|
|
133
|
+
assertSubscriptionCheapest({
|
|
134
|
+
requestSku: requestLine.sku,
|
|
135
|
+
modeAtLine,
|
|
136
|
+
resolved,
|
|
137
|
+
oneTime,
|
|
138
|
+
vatRateBps: taxProfile.vatRateBps,
|
|
139
|
+
});
|
|
140
|
+
const unitPriceGrossMinor = toGrossMinor(resolved, taxProfile.vatRateBps);
|
|
141
|
+
const baseUnitGrossMinor = oneTime ? toGrossMinor(oneTime, taxProfile.vatRateBps) : unitPriceGrossMinor;
|
|
142
|
+
const grossMinor = unitPriceGrossMinor * requestLine.quantity;
|
|
143
|
+
const tax = calculateIncludedVat(grossMinor, taxProfile.vatRateBps);
|
|
144
|
+
const pricingComponents = buildLineBreakdown({
|
|
145
|
+
variant_id: entry.item.variantId,
|
|
146
|
+
line_qty: requestLine.quantity,
|
|
147
|
+
base_unit_price_minor: baseUnitGrossMinor,
|
|
148
|
+
resolved_unit_price_minor: unitPriceGrossMinor,
|
|
149
|
+
matched_tier_min_qty: resolved.matchedMinQty,
|
|
150
|
+
mode_at_line: modeAtLine,
|
|
151
|
+
mode_resolved_via_fallback: resolved.mode === "any",
|
|
152
|
+
}).map((component) => mapPricingComponent(component, "line"));
|
|
153
|
+
const catalogFacts = isStrictD1Quote
|
|
154
|
+
? catalogFactsForQuoteLine(true, entry.item, resolved, oneTime!, modeAtLine, atTime, currency, unitPriceGrossMinor, grossMinor, toGrossMinor(oneTime!, taxProfile.vatRateBps), strictResolution?.policy)
|
|
155
|
+
: undefined;
|
|
156
|
+
return {
|
|
157
|
+
line: {
|
|
158
|
+
sku: entry.item.skuCode,
|
|
159
|
+
productSlug: entry.item.productSlug,
|
|
160
|
+
quantity: requestLine.quantity,
|
|
161
|
+
unitPriceGross: money(unitPriceGrossMinor),
|
|
162
|
+
lineSubtotalGross: money(grossMinor),
|
|
163
|
+
tax: {
|
|
164
|
+
...taxProfile,
|
|
165
|
+
netAmount: money(tax.netMinor),
|
|
166
|
+
vatAmount: money(tax.vatMinor),
|
|
167
|
+
grossAmount: money(grossMinor),
|
|
168
|
+
},
|
|
169
|
+
pricingComponents,
|
|
170
|
+
...(catalogFacts ? { catalogFacts } : {}),
|
|
171
|
+
},
|
|
172
|
+
pricingComponents,
|
|
173
|
+
...lineContext(requestLine, entry.item),
|
|
174
|
+
referenceGrossMinor: toGrossMinor(promotionReference ?? oneTime ?? resolved, taxProfile.vatRateBps)
|
|
175
|
+
* requestLine.quantity,
|
|
176
|
+
};
|
|
177
|
+
}));
|
|
178
|
+
const lines: CommerceQuoteLine[] = pricedLines.map((entry) => entry.line);
|
|
179
|
+
const quoteComponents: CommerceQuotePricingComponent[] = pricedLines.flatMap((entry) => entry.pricingComponents);
|
|
180
|
+
const context = quoteContext(request, pricedLines);
|
|
181
|
+
const subtotalGrossMinor = sum(lines.map((line) => line.lineSubtotalGross.amountMinor));
|
|
182
|
+
const referenceProductMinor = sum(pricedLines.map((entry) => entry.referenceGrossMinor));
|
|
183
|
+
const netTotalMinor = sum(lines.map((line) => line.tax.netAmount.amountMinor));
|
|
184
|
+
const taxTotalMinor = sum(lines.map((line) => line.tax.vatAmount.amountMinor));
|
|
185
|
+
let shippingGrossMinor: number;
|
|
186
|
+
let promotionResult: QuotePromotionResult;
|
|
187
|
+
let hasShipping: boolean;
|
|
188
|
+
let shippingTerms: ShippingTerms | undefined;
|
|
189
|
+
let acquisitionTerms: ResolvedAcquisitionTerms | undefined;
|
|
190
|
+
if (resolveQuoteShippingPromotions) {
|
|
191
|
+
const resolved = await resolveQuoteShippingPromotions({ request, options, regionCode, currency, referenceProductMinor, subtotalGrossMinor, atTime });
|
|
192
|
+
shippingGrossMinor = resolved.shippingGrossMinor;
|
|
193
|
+
promotionResult = resolved;
|
|
194
|
+
hasShipping = resolved.hasShipping;
|
|
195
|
+
shippingTerms = resolved.shippingTerms;
|
|
196
|
+
acquisitionTerms = resolved.acquisitionTerms;
|
|
197
|
+
} else {
|
|
198
|
+
// Shipping is a flat per-order gross from settings; absent settings port ⇒ no
|
|
199
|
+
// shipping (byte-identical to pre-shipping quotes). Resolved before promo eval so
|
|
200
|
+
// the free-shipping lane has a real amount to zero.
|
|
201
|
+
shippingGrossMinor = shippingFlatMinor
|
|
202
|
+
? await shippingFlatMinor()
|
|
203
|
+
: 0;
|
|
204
|
+
const automaticPromotionResult = await resolveAutomaticQuotePromotions({
|
|
205
|
+
request,
|
|
206
|
+
options,
|
|
207
|
+
regionCode,
|
|
208
|
+
referenceProductMinor,
|
|
209
|
+
subtotalGrossMinor,
|
|
210
|
+
shippingGrossMinor,
|
|
211
|
+
atTime,
|
|
212
|
+
});
|
|
213
|
+
promotionResult = await applyPromotionCodesV2({
|
|
214
|
+
preserveLegacyDiscountsWhenNoCandidates: options?.pricingPolicy?.promotionEngineVersion !== PROMOTION_ENGINE_V2,
|
|
215
|
+
clientId: options?.clientId ?? null,
|
|
216
|
+
mode: request.mode,
|
|
217
|
+
promoCodes: request.promoCodes,
|
|
218
|
+
referenceProductMinor: sum(pricedLines.map((entry) => entry.referenceGrossMinor)),
|
|
219
|
+
subtotalGrossMinor,
|
|
220
|
+
shippingGrossMinor,
|
|
221
|
+
discounts: automaticPromotionResult.discounts,
|
|
222
|
+
codeRejections: automaticPromotionResult.codeRejections,
|
|
223
|
+
atTime,
|
|
224
|
+
});
|
|
225
|
+
hasShipping = shippingFlatMinor != null;
|
|
226
|
+
}
|
|
227
|
+
const { discounts, codeRejections, codeRejectionDetails } = promotionResult;
|
|
228
|
+
// Two lanes: order_total/line discounts reduce the subtotal; a shipping discount
|
|
229
|
+
// (free-shipping promo) reduces the shipping gross. They are summed and validated
|
|
230
|
+
// independently by the quote schema.
|
|
231
|
+
const orderDiscountMinor = discounts
|
|
232
|
+
.filter((d) => d.appliesTo !== "shipping")
|
|
233
|
+
.reduce((total, d) => total + d.amountOffMinor, 0);
|
|
234
|
+
const shippingDiscountMinor = discounts
|
|
235
|
+
.filter((d) => d.appliesTo === "shipping")
|
|
236
|
+
.reduce((total, d) => total + d.amountOffMinor, 0);
|
|
237
|
+
|
|
238
|
+
const { netMinor: discountNetMinor, vatMinor: discountVatMinor } = splitIncludedVat(
|
|
239
|
+
orderDiscountMinor,
|
|
240
|
+
taxProfile.vatRateBps,
|
|
241
|
+
);
|
|
242
|
+
const ship = splitIncludedVat(shippingGrossMinor, taxProfile.vatRateBps);
|
|
243
|
+
const shipDiscount = splitIncludedVat(shippingDiscountMinor, taxProfile.vatRateBps);
|
|
244
|
+
const shippingFields = hasShipping
|
|
245
|
+
? {
|
|
246
|
+
shippingGross: money(shippingGrossMinor),
|
|
247
|
+
shippingDiscountGross: money(shippingDiscountMinor),
|
|
248
|
+
}
|
|
249
|
+
: {};
|
|
250
|
+
return {
|
|
251
|
+
contractVersion: COMMERCE_CONTRACT_VERSION,
|
|
252
|
+
quote: {
|
|
253
|
+
currency,
|
|
254
|
+
taxIncluded: true,
|
|
255
|
+
lines,
|
|
256
|
+
discounts,
|
|
257
|
+
...(codeRejections.length ? { codeRejections } : {}),
|
|
258
|
+
...(codeRejectionDetails.length ? { codeRejectionDetails } : {}),
|
|
259
|
+
pricingComponents: quoteComponents,
|
|
260
|
+
context: {
|
|
261
|
+
...context(),
|
|
262
|
+
...(resolveQuoteShippingPromotions && acquisitionTerms ? { acquisitionTerms } : {}),
|
|
263
|
+
...(resolveQuoteShippingPromotions && shippingTerms ? { shippingTerms } : {}),
|
|
264
|
+
...(options?.pricingPolicy ? { pricingPolicy: options.pricingPolicy } : {}),
|
|
265
|
+
},
|
|
266
|
+
subtotalGross: money(subtotalGrossMinor),
|
|
267
|
+
discountTotalGross: money(orderDiscountMinor),
|
|
268
|
+
...shippingFields,
|
|
269
|
+
totalGross: money(subtotalGrossMinor - orderDiscountMinor + shippingGrossMinor - shippingDiscountMinor),
|
|
270
|
+
netTotal: money(netTotalMinor - discountNetMinor + ship.netMinor - shipDiscount.netMinor),
|
|
271
|
+
taxTotal: money(taxTotalMinor - discountVatMinor + ship.vatMinor - shipDiscount.vatMinor),
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
createQuote: async (request: Request, options?: CreateQuoteOptions) => projectPublicQuoteSnapshot(await createServerAuthoritativeQuote(request, options)),
|
|
278
|
+
createServerAuthoritativeQuote,
|
|
279
|
+
};
|
|
280
|
+
}
|