@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.
Files changed (525) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +299 -6
  3. package/dist/catalog/activeSkuSelection.d.ts +2 -0
  4. package/dist/catalog/activeSkuSelection.js +20 -0
  5. package/dist/catalog/adminBindings.d.ts +72 -0
  6. package/dist/catalog/adminBindings.js +1 -0
  7. package/dist/catalog/adminBundleHandler.d.ts +26 -0
  8. package/dist/catalog/adminBundleHandler.js +111 -0
  9. package/dist/catalog/adminBundleReadHandler.d.ts +21 -0
  10. package/dist/catalog/adminBundleReadHandler.js +185 -0
  11. package/dist/catalog/adminBundleWritePort.d.ts +85 -0
  12. package/dist/catalog/adminBundleWritePort.js +1 -0
  13. package/dist/catalog/bundleAvailability.d.ts +63 -0
  14. package/dist/catalog/bundleAvailability.js +78 -0
  15. package/dist/catalog/bundleAvailabilityService.d.ts +65 -0
  16. package/dist/catalog/bundleAvailabilityService.js +89 -0
  17. package/dist/catalog/bundleBindings.d.ts +33 -0
  18. package/dist/catalog/bundleBindings.js +1 -0
  19. package/dist/catalog/bundleCatalogQueries.d.ts +28 -0
  20. package/dist/catalog/bundleCatalogQueries.js +84 -0
  21. package/dist/catalog/bundleCatalogReadPort.d.ts +173 -0
  22. package/dist/catalog/bundleCatalogReadPort.js +36 -0
  23. package/dist/catalog/bundleCatalogStore.d.ts +5 -0
  24. package/dist/catalog/bundleCatalogStore.js +156 -0
  25. package/dist/catalog/bundlePriceSelection.d.ts +13 -0
  26. package/dist/catalog/bundlePriceSelection.js +26 -0
  27. package/dist/catalog/bundleRuleCodes.d.ts +41 -0
  28. package/dist/catalog/bundleRuleCodes.js +110 -0
  29. package/dist/catalog/bundleRules.d.ts +69 -0
  30. package/dist/catalog/bundleRules.js +102 -0
  31. package/dist/catalog/catalogDraftStore.d.ts +4 -0
  32. package/dist/catalog/catalogDraftStore.js +63 -0
  33. package/dist/catalog/catalogSkuEnvelope.d.ts +7 -0
  34. package/dist/catalog/catalogSkuEnvelope.js +142 -0
  35. package/dist/catalog/catalogSkuEnvelopeMapper.d.ts +38 -0
  36. package/dist/catalog/catalogSkuEnvelopeMapper.js +88 -0
  37. package/dist/catalog/contracts/bundleAdmin.d.ts +143 -0
  38. package/dist/catalog/contracts/bundleAdmin.js +1 -0
  39. package/dist/catalog/contracts/bundleAvailability.d.ts +61 -0
  40. package/dist/catalog/contracts/bundleAvailability.js +1 -0
  41. package/dist/catalog/contracts/bundleRead.d.ts +241 -0
  42. package/dist/catalog/contracts/bundleRead.js +1 -0
  43. package/dist/catalog/contracts/draft.d.ts +79 -0
  44. package/dist/catalog/contracts/draft.js +47 -0
  45. package/dist/catalog/contracts/foundation.d.ts +78 -0
  46. package/dist/catalog/contracts/foundation.js +53 -0
  47. package/dist/catalog/contracts/index.d.ts +14 -0
  48. package/dist/catalog/contracts/index.js +6 -0
  49. package/dist/catalog/contracts/lifecycle.d.ts +77 -0
  50. package/dist/catalog/contracts/lifecycle.js +1 -0
  51. package/dist/catalog/contracts/productMediaContracts.d.ts +77 -0
  52. package/dist/catalog/contracts/productMediaContracts.js +149 -0
  53. package/dist/catalog/contracts/productTypes.d.ts +54 -0
  54. package/dist/catalog/contracts/productTypes.js +16 -0
  55. package/dist/catalog/contracts/resolveProductMedia.d.ts +3 -0
  56. package/dist/catalog/contracts/resolveProductMedia.js +78 -0
  57. package/dist/catalog/contracts/sellable.d.ts +23 -0
  58. package/dist/catalog/contracts/sellable.js +1 -0
  59. package/dist/catalog/draftHandler.d.ts +25 -0
  60. package/dist/catalog/draftHandler.js +118 -0
  61. package/dist/catalog/draftPorts.d.ts +18 -0
  62. package/dist/catalog/draftPorts.js +8 -0
  63. package/dist/catalog/draftSchemas.d.ts +423 -0
  64. package/dist/catalog/draftSchemas.js +89 -0
  65. package/dist/catalog/draftValidation.d.ts +21 -0
  66. package/dist/catalog/draftValidation.js +125 -0
  67. package/dist/catalog/effectiveBundleCatalogQueries.d.ts +8 -0
  68. package/dist/catalog/effectiveBundleCatalogQueries.js +79 -0
  69. package/dist/catalog/effectiveBundleCatalogStore.d.ts +4 -0
  70. package/dist/catalog/effectiveBundleCatalogStore.js +156 -0
  71. package/dist/catalog/foundationSchemas.d.ts +98 -0
  72. package/dist/catalog/foundationSchemas.js +110 -0
  73. package/dist/catalog/index.d.ts +44 -0
  74. package/dist/catalog/index.js +26 -0
  75. package/dist/catalog/mediaSourcePack.d.ts +51 -0
  76. package/dist/catalog/mediaSourcePack.js +247 -0
  77. package/dist/catalog/productTypeSchemas.d.ts +46 -0
  78. package/dist/catalog/productTypeSchemas.js +72 -0
  79. package/dist/catalog/publicationPorts.d.ts +42 -0
  80. package/dist/catalog/publicationPorts.js +1 -0
  81. package/dist/catalog/readPorts.d.ts +51 -0
  82. package/dist/catalog/readPorts.js +1 -0
  83. package/dist/catalog/referenceCatalogHandler.d.ts +7 -0
  84. package/dist/catalog/referenceCatalogHandler.js +27 -0
  85. package/dist/catalog/resolvedSkuProjection.d.ts +88 -0
  86. package/dist/catalog/resolvedSkuProjection.js +60 -0
  87. package/dist/catalog/sellableBundleHandler.d.ts +46 -0
  88. package/dist/catalog/sellableBundleHandler.js +84 -0
  89. package/dist/catalog/sellableSchemas.d.ts +46 -0
  90. package/dist/catalog/sellableSchemas.js +27 -0
  91. package/dist/catalog/sql.d.ts +46 -0
  92. package/dist/catalog/sql.js +13 -0
  93. package/dist/catalog/validation.d.ts +2 -0
  94. package/dist/catalog/validation.js +3 -0
  95. package/dist/communications/contracts/index.d.ts +1 -0
  96. package/dist/communications/contracts/marketingSync.d.ts +194 -0
  97. package/dist/communications/contracts/marketingSync.js +1 -0
  98. package/dist/communications/index.d.ts +1 -0
  99. package/dist/communications/klaviyo/consentPush.d.ts +51 -0
  100. package/dist/communications/klaviyo/consentPush.js +19 -0
  101. package/dist/communications/klaviyo/index.d.ts +110 -0
  102. package/dist/communications/klaviyo/index.js +860 -0
  103. package/dist/communications/newsletterIntegrationContracts.d.ts +3 -3
  104. package/dist/communications/resend/index.d.ts +40 -0
  105. package/dist/communications/resend/index.js +310 -0
  106. package/dist/composition/catalogContribution.d.ts +18 -0
  107. package/dist/composition/catalogContribution.js +21 -0
  108. package/dist/composition/index.d.ts +3 -0
  109. package/dist/composition/index.js +3 -0
  110. package/dist/composition/inventoryContribution.d.ts +16 -0
  111. package/dist/composition/inventoryContribution.js +17 -0
  112. package/dist/composition/shippingContribution.d.ts +16 -0
  113. package/dist/composition/shippingContribution.js +17 -0
  114. package/dist/identity/customerContracts.d.ts +2 -2
  115. package/dist/inventory/adminInventory.d.ts +13 -0
  116. package/dist/inventory/adminInventory.js +214 -0
  117. package/dist/inventory/bindings.d.ts +35 -0
  118. package/dist/inventory/bindings.js +45 -0
  119. package/dist/inventory/contracts/forecast.d.ts +86 -0
  120. package/dist/inventory/contracts/forecast.js +1 -0
  121. package/dist/inventory/contracts/index.d.ts +4 -0
  122. package/dist/inventory/contracts/index.js +1 -0
  123. package/dist/inventory/contracts/inventory.d.ts +157 -0
  124. package/dist/inventory/contracts/inventory.js +1 -0
  125. package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
  126. package/dist/inventory/contracts/newPurchaseStock.js +1 -0
  127. package/dist/inventory/contracts/reservation.d.ts +18 -0
  128. package/dist/inventory/contracts/reservation.js +1 -0
  129. package/dist/inventory/forecast.d.ts +24 -0
  130. package/dist/inventory/forecast.js +200 -0
  131. package/dist/inventory/index.d.ts +14 -0
  132. package/dist/inventory/index.js +11 -0
  133. package/dist/inventory/inventoryHandlers.d.ts +53 -0
  134. package/dist/inventory/inventoryHandlers.js +151 -0
  135. package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
  136. package/dist/inventory/newPurchaseStockAvailability.js +75 -0
  137. package/dist/inventory/ports.d.ts +21 -0
  138. package/dist/inventory/ports.js +16 -0
  139. package/dist/inventory/renewalReservation.d.ts +27 -0
  140. package/dist/inventory/renewalReservation.js +61 -0
  141. package/dist/inventory/reservationPolicy.d.ts +6 -0
  142. package/dist/inventory/reservationPolicy.js +19 -0
  143. package/dist/inventory/schemas.d.ts +137 -0
  144. package/dist/inventory/schemas.js +112 -0
  145. package/dist/inventory/sql.d.ts +14 -0
  146. package/dist/inventory/sql.js +5 -0
  147. package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
  148. package/dist/inventory/subscriptionForecastCalculation.js +133 -0
  149. package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
  150. package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
  151. package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
  152. package/dist/pricing/acquisitionOfferPricing.js +25 -0
  153. package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
  154. package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
  155. package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
  156. package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
  157. package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
  158. package/dist/pricing/acquisitionQuoteBinding.js +12 -0
  159. package/dist/pricing/acquisitionReadiness.d.ts +31 -0
  160. package/dist/pricing/acquisitionReadiness.js +117 -0
  161. package/dist/pricing/acquisitionTerms.d.ts +19 -0
  162. package/dist/pricing/acquisitionTerms.js +126 -0
  163. package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
  164. package/dist/pricing/acquisitionTermsSchemas.js +42 -0
  165. package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
  166. package/dist/pricing/catalogFactsProvenance.js +64 -0
  167. package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
  168. package/dist/pricing/catalogFactsSchemas.js +58 -0
  169. package/dist/pricing/commerceOfferPricing.d.ts +25 -0
  170. package/dist/pricing/commerceOfferPricing.js +139 -0
  171. package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
  172. package/dist/pricing/commerceOfferPricingCache.js +44 -0
  173. package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
  174. package/dist/pricing/commerceOfferPricingHandler.js +78 -0
  175. package/dist/pricing/commercePriceAuthority.d.ts +28 -0
  176. package/dist/pricing/commercePriceAuthority.js +140 -0
  177. package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
  178. package/dist/pricing/commercePricingPolicyErrors.js +67 -0
  179. package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
  180. package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
  181. package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
  182. package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
  183. package/dist/pricing/commerceQuoteHandler.d.ts +35 -0
  184. package/dist/pricing/commerceQuoteHandler.js +122 -0
  185. package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
  186. package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
  187. package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
  188. package/dist/pricing/contracts/acquisitionTerms.js +2 -0
  189. package/dist/pricing/contracts/authority.d.ts +27 -0
  190. package/dist/pricing/contracts/authority.js +1 -0
  191. package/dist/pricing/contracts/evidence.d.ts +33 -0
  192. package/dist/pricing/contracts/evidence.js +1 -0
  193. package/dist/pricing/contracts/index.d.ts +17 -0
  194. package/dist/pricing/contracts/index.js +7 -0
  195. package/dist/pricing/contracts/money.d.ts +27 -0
  196. package/dist/pricing/contracts/money.js +1 -0
  197. package/dist/pricing/contracts/offer.d.ts +52 -0
  198. package/dist/pricing/contracts/offer.js +4 -0
  199. package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
  200. package/dist/pricing/contracts/offerPolicy.js +5 -0
  201. package/dist/pricing/contracts/policy.d.ts +22 -0
  202. package/dist/pricing/contracts/policy.js +3 -0
  203. package/dist/pricing/contracts/provenance.d.ts +24 -0
  204. package/dist/pricing/contracts/provenance.js +2 -0
  205. package/dist/pricing/contracts/quote.d.ts +94 -0
  206. package/dist/pricing/contracts/quote.js +1 -0
  207. package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
  208. package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
  209. package/dist/pricing/dbBackedCommerceQuotePort.d.ts +74 -0
  210. package/dist/pricing/dbBackedCommerceQuotePort.js +223 -0
  211. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
  212. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
  213. package/dist/pricing/index.d.ts +52 -0
  214. package/dist/pricing/index.js +36 -0
  215. package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
  216. package/dist/pricing/offerPolicyReadiness.js +79 -0
  217. package/dist/pricing/offerPolicySchemas.d.ts +25 -0
  218. package/dist/pricing/offerPolicySchemas.js +15 -0
  219. package/dist/pricing/offerPricingSchemas.d.ts +315 -0
  220. package/dist/pricing/offerPricingSchemas.js +65 -0
  221. package/dist/pricing/ports.d.ts +13 -0
  222. package/dist/pricing/ports.js +9 -0
  223. package/dist/pricing/pricingPolicyToken.d.ts +39 -0
  224. package/dist/pricing/pricingPolicyToken.js +84 -0
  225. package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
  226. package/dist/pricing/quoteAcquisitionTerms.js +4 -0
  227. package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
  228. package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
  229. package/dist/pricing/quotePorts.d.ts +30 -0
  230. package/dist/pricing/quotePorts.js +20 -0
  231. package/dist/pricing/quotePrimitives.d.ts +13 -0
  232. package/dist/pricing/quotePrimitives.js +18 -0
  233. package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
  234. package/dist/pricing/quotePromotionEvidence.js +73 -0
  235. package/dist/pricing/quoteSchemas.d.ts +1176 -0
  236. package/dist/pricing/quoteSchemas.js +196 -0
  237. package/dist/pricing/quoteShippingPromotions.d.ts +20 -0
  238. package/dist/pricing/quoteShippingPromotions.js +1 -0
  239. package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
  240. package/dist/pricing/subscriptionPricePolicy.js +96 -0
  241. package/dist/pricing/taxProfileGuard.d.ts +2 -0
  242. package/dist/pricing/taxProfileGuard.js +24 -0
  243. package/dist/pricing/validation.d.ts +2 -0
  244. package/dist/pricing/validation.js +3 -0
  245. package/dist/promotions/adminBindings.d.ts +72 -0
  246. package/dist/promotions/adminBindings.js +1 -0
  247. package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
  248. package/dist/promotions/adminPromotionCodesContracts.js +211 -0
  249. package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
  250. package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
  251. package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
  252. package/dist/promotions/adminPromotionCodesHandler.js +194 -0
  253. package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
  254. package/dist/promotions/adminPromotionsContracts.js +99 -0
  255. package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
  256. package/dist/promotions/adminPromotionsDataPort.js +1 -0
  257. package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
  258. package/dist/promotions/adminPromotionsHandler.js +47 -0
  259. package/dist/promotions/commerceQuoteReadPreparation.d.ts +3 -0
  260. package/dist/promotions/commerceQuoteReadPreparation.js +27 -0
  261. package/dist/promotions/contracts/index.d.ts +387 -0
  262. package/dist/promotions/contracts/index.js +1 -0
  263. package/dist/promotions/index.d.ts +25 -0
  264. package/dist/promotions/index.js +22 -0
  265. package/dist/promotions/promoDataPort.d.ts +47 -0
  266. package/dist/promotions/promoDataPort.js +14 -0
  267. package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
  268. package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
  269. package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
  270. package/dist/promotions/promotionBenefitDefinition.js +33 -0
  271. package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
  272. package/dist/promotions/promotionClaimSweepPort.js +8 -0
  273. package/dist/promotions/promotionClaims.d.ts +5 -0
  274. package/dist/promotions/promotionClaims.js +57 -0
  275. package/dist/promotions/promotionCodePreview.d.ts +11 -0
  276. package/dist/promotions/promotionCodePreview.js +154 -0
  277. package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
  278. package/dist/promotions/promotionCodeQuotePort.js +1 -0
  279. package/dist/promotions/promotionQuoteAcceptance.d.ts +70 -0
  280. package/dist/promotions/promotionQuoteAcceptance.js +202 -0
  281. package/dist/promotions/promotionQuoteBinding.d.ts +73 -0
  282. package/dist/promotions/promotionQuoteBinding.js +87 -0
  283. package/dist/promotions/quoteAcquisitionBinding.d.ts +6 -0
  284. package/dist/promotions/quoteAcquisitionBinding.js +1 -0
  285. package/dist/promotions/quoteAutomaticPromotions.d.ts +26 -0
  286. package/dist/promotions/quoteAutomaticPromotions.js +60 -0
  287. package/dist/promotions/quotePromoDiscounts.d.ts +36 -0
  288. package/dist/promotions/quotePromoDiscounts.js +146 -0
  289. package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
  290. package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
  291. package/dist/promotions/quotePromotionV2.d.ts +30 -0
  292. package/dist/promotions/quotePromotionV2.js +186 -0
  293. package/dist/promotions/quoteShippingPolicy.d.ts +23 -0
  294. package/dist/promotions/quoteShippingPolicy.js +47 -0
  295. package/dist/promotions/sql.d.ts +14 -0
  296. package/dist/promotions/sql.js +5 -0
  297. package/dist/promotions/validation.d.ts +2 -0
  298. package/dist/promotions/validation.js +3 -0
  299. package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
  300. package/dist/shipping/addressCanonLookupHandler.js +105 -0
  301. package/dist/shipping/addressCanonical.d.ts +2 -0
  302. package/dist/shipping/addressCanonical.js +26 -0
  303. package/dist/shipping/addressPorts.d.ts +39 -0
  304. package/dist/shipping/addressPorts.js +1 -0
  305. package/dist/shipping/addressSchemas.d.ts +30 -0
  306. package/dist/shipping/addressSchemas.js +1 -0
  307. package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
  308. package/dist/shipping/adminShippingRateHandler.js +64 -0
  309. package/dist/shipping/bindings.d.ts +57 -0
  310. package/dist/shipping/bindings.js +46 -0
  311. package/dist/shipping/contracts/address.d.ts +144 -0
  312. package/dist/shipping/contracts/address.js +1 -0
  313. package/dist/shipping/contracts/delivery.d.ts +110 -0
  314. package/dist/shipping/contracts/delivery.js +1 -0
  315. package/dist/shipping/contracts/index.d.ts +4 -0
  316. package/dist/shipping/contracts/index.js +1 -0
  317. package/dist/shipping/contracts/shippingRate.d.ts +11 -0
  318. package/dist/shipping/contracts/shippingRate.js +1 -0
  319. package/dist/shipping/contracts/threshold.d.ts +22 -0
  320. package/dist/shipping/contracts/threshold.js +1 -0
  321. package/dist/shipping/customerAddressesHandler.d.ts +5 -0
  322. package/dist/shipping/customerAddressesHandler.js +80 -0
  323. package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
  324. package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
  325. package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
  326. package/dist/shipping/deliverySelectionHandler.js +77 -0
  327. package/dist/shipping/deliverySelectionPort.d.ts +8 -0
  328. package/dist/shipping/deliverySelectionPort.js +1 -0
  329. package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
  330. package/dist/shipping/deliverySelectionSummary.js +53 -0
  331. package/dist/shipping/handlerSchemas.d.ts +21 -0
  332. package/dist/shipping/handlerSchemas.js +1 -0
  333. package/dist/shipping/index.d.ts +20 -0
  334. package/dist/shipping/index.js +10 -0
  335. package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
  336. package/dist/shipping/pickupPointSearchPort.js +1 -0
  337. package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
  338. package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
  339. package/dist/shipping/shippingRatePorts.d.ts +52 -0
  340. package/dist/shipping/shippingRatePorts.js +1 -0
  341. package/dist/shipping/thresholdPolicy.d.ts +42 -0
  342. package/dist/shipping/thresholdPolicy.js +60 -0
  343. package/package.json +62 -2
  344. package/release-gates.json +263 -53
  345. package/sql/20260807140000_read_parity_catalog.sql +94 -0
  346. package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
  347. package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
  348. package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
  349. package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
  350. package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
  351. package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
  352. package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
  353. package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
  354. package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
  355. package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
  356. package/src/catalog/activeSkuSelection.ts +20 -0
  357. package/src/catalog/adminBindings.ts +59 -0
  358. package/src/catalog/adminBundleHandler.ts +165 -0
  359. package/src/catalog/adminBundleReadHandler.ts +263 -0
  360. package/src/catalog/adminBundleWritePort.ts +102 -0
  361. package/src/catalog/bundleAvailability.ts +129 -0
  362. package/src/catalog/bundleAvailabilityService.ts +171 -0
  363. package/src/catalog/bundleBindings.ts +29 -0
  364. package/src/catalog/bundleCatalogQueries.ts +98 -0
  365. package/src/catalog/bundleCatalogReadPort.ts +181 -0
  366. package/src/catalog/bundleCatalogStore.ts +274 -0
  367. package/src/catalog/bundlePriceSelection.ts +25 -0
  368. package/src/catalog/bundleRuleCodes.ts +134 -0
  369. package/src/catalog/bundleRules.ts +153 -0
  370. package/src/catalog/catalogDraftStore.ts +71 -0
  371. package/src/catalog/catalogSkuEnvelope.ts +214 -0
  372. package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
  373. package/src/catalog/contracts/bundleAdmin.ts +20 -0
  374. package/src/catalog/contracts/bundleAvailability.ts +5 -0
  375. package/src/catalog/contracts/bundleRead.ts +13 -0
  376. package/src/catalog/contracts/draft.ts +69 -0
  377. package/src/catalog/contracts/foundation.ts +104 -0
  378. package/src/catalog/contracts/index.ts +14 -0
  379. package/src/catalog/contracts/lifecycle.ts +74 -0
  380. package/src/catalog/contracts/productMediaContracts.ts +170 -0
  381. package/src/catalog/contracts/productTypes.ts +35 -0
  382. package/src/catalog/contracts/resolveProductMedia.ts +85 -0
  383. package/src/catalog/contracts/sellable.ts +12 -0
  384. package/src/catalog/draftHandler.ts +105 -0
  385. package/src/catalog/draftPorts.ts +18 -0
  386. package/src/catalog/draftSchemas.ts +88 -0
  387. package/src/catalog/draftValidation.ts +100 -0
  388. package/src/catalog/effectiveBundleCatalogQueries.ts +80 -0
  389. package/src/catalog/effectiveBundleCatalogStore.ts +208 -0
  390. package/src/catalog/foundationSchemas.ts +123 -0
  391. package/src/catalog/index.ts +44 -0
  392. package/src/catalog/mediaSourcePack.ts +219 -0
  393. package/src/catalog/productTypeSchemas.ts +70 -0
  394. package/src/catalog/publicationPorts.ts +44 -0
  395. package/src/catalog/readPorts.ts +61 -0
  396. package/src/catalog/referenceCatalogHandler.ts +39 -0
  397. package/src/catalog/resolvedSkuProjection.ts +76 -0
  398. package/src/catalog/sellableBundleHandler.ts +133 -0
  399. package/src/catalog/sellableSchemas.ts +33 -0
  400. package/src/catalog/sql.ts +14 -0
  401. package/src/catalog/validation.ts +3 -0
  402. package/src/communications/contracts/index.ts +1 -0
  403. package/src/communications/contracts/marketingSync.ts +109 -0
  404. package/src/communications/index.ts +1 -0
  405. package/src/communications/klaviyo/consentPush.ts +19 -0
  406. package/src/communications/klaviyo/index.ts +649 -0
  407. package/src/communications/resend/index.ts +350 -0
  408. package/src/composition/catalogContribution.ts +32 -0
  409. package/src/composition/index.ts +3 -0
  410. package/src/composition/inventoryContribution.ts +24 -0
  411. package/src/composition/shippingContribution.ts +24 -0
  412. package/src/inventory/adminInventory.ts +251 -0
  413. package/src/inventory/bindings.ts +61 -0
  414. package/src/inventory/contracts/forecast.ts +96 -0
  415. package/src/inventory/contracts/index.ts +18 -0
  416. package/src/inventory/contracts/inventory.ts +14 -0
  417. package/src/inventory/contracts/newPurchaseStock.ts +23 -0
  418. package/src/inventory/contracts/reservation.ts +20 -0
  419. package/src/inventory/forecast.ts +239 -0
  420. package/src/inventory/index.ts +22 -0
  421. package/src/inventory/inventoryHandlers.ts +215 -0
  422. package/src/inventory/newPurchaseStockAvailability.ts +120 -0
  423. package/src/inventory/ports.ts +46 -0
  424. package/src/inventory/renewalReservation.ts +88 -0
  425. package/src/inventory/reservationPolicy.ts +27 -0
  426. package/src/inventory/schemas.ts +128 -0
  427. package/src/inventory/sql.ts +5 -0
  428. package/src/inventory/subscriptionForecastCalculation.ts +157 -0
  429. package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
  430. package/src/pricing/acquisitionOfferPricing.ts +38 -0
  431. package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
  432. package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
  433. package/src/pricing/acquisitionQuoteBinding.ts +12 -0
  434. package/src/pricing/acquisitionReadiness.ts +148 -0
  435. package/src/pricing/acquisitionTerms.ts +125 -0
  436. package/src/pricing/acquisitionTermsSchemas.ts +52 -0
  437. package/src/pricing/catalogFactsProvenance.ts +102 -0
  438. package/src/pricing/catalogFactsSchemas.ts +69 -0
  439. package/src/pricing/commerceOfferPricing.ts +224 -0
  440. package/src/pricing/commerceOfferPricingCache.ts +53 -0
  441. package/src/pricing/commerceOfferPricingHandler.ts +137 -0
  442. package/src/pricing/commercePriceAuthority.ts +169 -0
  443. package/src/pricing/commercePricingPolicyErrors.ts +88 -0
  444. package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
  445. package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
  446. package/src/pricing/commerceQuoteHandler.ts +199 -0
  447. package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
  448. package/src/pricing/contracts/acquisitionTerms.ts +26 -0
  449. package/src/pricing/contracts/authority.ts +44 -0
  450. package/src/pricing/contracts/evidence.ts +25 -0
  451. package/src/pricing/contracts/index.ts +17 -0
  452. package/src/pricing/contracts/money.ts +32 -0
  453. package/src/pricing/contracts/offer.ts +42 -0
  454. package/src/pricing/contracts/offerPolicy.ts +17 -0
  455. package/src/pricing/contracts/policy.ts +26 -0
  456. package/src/pricing/contracts/provenance.ts +19 -0
  457. package/src/pricing/contracts/quote.ts +106 -0
  458. package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
  459. package/src/pricing/dbBackedCommerceQuotePort.ts +280 -0
  460. package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
  461. package/src/pricing/index.ts +53 -0
  462. package/src/pricing/offerPolicyReadiness.ts +110 -0
  463. package/src/pricing/offerPolicySchemas.ts +23 -0
  464. package/src/pricing/offerPricingSchemas.ts +92 -0
  465. package/src/pricing/ports.ts +17 -0
  466. package/src/pricing/pricingPolicyToken.ts +119 -0
  467. package/src/pricing/quoteAcquisitionTerms.ts +10 -0
  468. package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
  469. package/src/pricing/quotePorts.ts +58 -0
  470. package/src/pricing/quotePrimitives.ts +21 -0
  471. package/src/pricing/quotePromotionEvidence.ts +82 -0
  472. package/src/pricing/quoteSchemas.ts +230 -0
  473. package/src/pricing/quoteShippingPromotions.ts +21 -0
  474. package/src/pricing/subscriptionPricePolicy.ts +113 -0
  475. package/src/pricing/taxProfileGuard.ts +33 -0
  476. package/src/pricing/validation.ts +3 -0
  477. package/src/promotions/adminBindings.ts +53 -0
  478. package/src/promotions/adminPromotionCodesContracts.ts +244 -0
  479. package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
  480. package/src/promotions/adminPromotionCodesHandler.ts +254 -0
  481. package/src/promotions/adminPromotionsContracts.ts +121 -0
  482. package/src/promotions/adminPromotionsDataPort.ts +48 -0
  483. package/src/promotions/adminPromotionsHandler.ts +72 -0
  484. package/src/promotions/commerceQuoteReadPreparation.ts +29 -0
  485. package/src/promotions/contracts/index.ts +387 -0
  486. package/src/promotions/index.ts +25 -0
  487. package/src/promotions/promoDataPort.ts +65 -0
  488. package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
  489. package/src/promotions/promotionBenefitDefinition.ts +48 -0
  490. package/src/promotions/promotionClaimSweepPort.ts +23 -0
  491. package/src/promotions/promotionClaims.ts +70 -0
  492. package/src/promotions/promotionCodePreview.ts +183 -0
  493. package/src/promotions/promotionCodeQuotePort.ts +35 -0
  494. package/src/promotions/promotionQuoteAcceptance.ts +291 -0
  495. package/src/promotions/promotionQuoteBinding.ts +163 -0
  496. package/src/promotions/quoteAcquisitionBinding.ts +7 -0
  497. package/src/promotions/quoteAutomaticPromotions.ts +84 -0
  498. package/src/promotions/quotePromoDiscounts.ts +194 -0
  499. package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
  500. package/src/promotions/quotePromotionV2.ts +239 -0
  501. package/src/promotions/quoteShippingPolicy.ts +61 -0
  502. package/src/promotions/sql.ts +5 -0
  503. package/src/promotions/validation.ts +3 -0
  504. package/src/shipping/addressCanonLookupHandler.ts +114 -0
  505. package/src/shipping/addressCanonical.ts +31 -0
  506. package/src/shipping/addressPorts.ts +48 -0
  507. package/src/shipping/addressSchemas.ts +28 -0
  508. package/src/shipping/adminShippingRateHandler.ts +72 -0
  509. package/src/shipping/bindings.ts +74 -0
  510. package/src/shipping/contracts/address.ts +71 -0
  511. package/src/shipping/contracts/delivery.ts +114 -0
  512. package/src/shipping/contracts/index.ts +19 -0
  513. package/src/shipping/contracts/shippingRate.ts +7 -0
  514. package/src/shipping/contracts/threshold.ts +22 -0
  515. package/src/shipping/customerAddressesHandler.ts +100 -0
  516. package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
  517. package/src/shipping/deliverySelectionHandler.ts +94 -0
  518. package/src/shipping/deliverySelectionPort.ts +10 -0
  519. package/src/shipping/deliverySelectionSummary.ts +56 -0
  520. package/src/shipping/handlerSchemas.ts +19 -0
  521. package/src/shipping/index.ts +26 -0
  522. package/src/shipping/pickupPointSearchPort.ts +8 -0
  523. package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
  524. package/src/shipping/shippingRatePorts.ts +36 -0
  525. package/src/shipping/thresholdPolicy.ts +65 -0
@@ -0,0 +1,224 @@
1
+ import {
2
+ COMMERCE_OFFER_PRICING_CONTRACT_VERSION,
3
+ COMMERCE_OFFER_PRICING_V2_CONTRACT_VERSION,
4
+ type CommerceOfferPricingResponse,
5
+ type CommerceProductOfferPricing,
6
+ } from "./contracts/offer.js";
7
+ import {
8
+ OFFER_POLICY_V2,
9
+ PROMOTION_ENGINE_V2,
10
+ type PricingPolicySnapshot,
11
+ } from "./contracts/offerPolicy.js";
12
+ import type { CreateQuoteRequest } from "./contracts/quote.js";
13
+ import type { CommerceCurrency, CommerceQuote } from "./contracts/index.js";
14
+ import {
15
+ CommerceQuoteError,
16
+ type CommerceQuotePort,
17
+ } from "./quotePorts.js";
18
+ import type {
19
+ CommerceQuoteCatalogItem,
20
+ } from "./commerceQuoteCatalogReadPort.js";
21
+
22
+ /**
23
+ * Why one product was left out of the offer projection.
24
+ *
25
+ * The projection's `minimumBy` produces the customer-facing "from" figure, so a
26
+ * silently shrunken product set would advertise a price that is not the real
27
+ * minimum. A dropped product cannot pull that minimum DOWN - the reachable error
28
+ * is always a HIGHER advertised price, never an undersell - but it must still be
29
+ * legible, so every exclusion is recorded. The response contract deliberately
30
+ * does not move: this is the whole signal.
31
+ *
32
+ * Private, bounded operational fact; no request or customer data.
33
+ */
34
+ interface OfferPricingExclusionDiagnostic {
35
+ event: "commerce_offer_pricing_product_excluded";
36
+ product_slug: string;
37
+ refusal_code: string;
38
+ }
39
+
40
+ function recordOfferPricingExclusion(diagnostic: OfferPricingExclusionDiagnostic): void {
41
+ try {
42
+ console.info(JSON.stringify(diagnostic));
43
+ } catch {
44
+ // A private diagnostic sink must never alter the projection it observes.
45
+ }
46
+ }
47
+
48
+ export interface CommerceOfferPricingDeps<Item extends CommerceQuoteCatalogItem, ScopeV1 extends string, ScopeV2 extends string> {
49
+ quoteCatalogReadPort: { listQuoteCatalogItems(): Promise<Item[]> };
50
+ quoteWithPromotions: CommerceQuotePort;
51
+ quoteWithoutPromotions: CommerceQuotePort;
52
+ pricingPolicy?: PricingPolicySnapshot;
53
+ isOfferProduct(product: Item): boolean;
54
+ requestFor(product: Item, mode: "one_time" | "subscription"): CreateQuoteRequest;
55
+ unitCount: number;
56
+ minimumProductPayableMinor: number;
57
+ scopeV1: ScopeV1;
58
+ scopeV2: ScopeV2;
59
+ }
60
+
61
+ /**
62
+ * Product-only acquisition projection over the same quote ports used by
63
+ * configurator checkout. It never prices delivery and is never accepted as a
64
+ * checkout expectation; its only consumer is non-transactional "from" copy.
65
+ */
66
+ export async function projectCommerceOfferPricing<Item extends CommerceQuoteCatalogItem, ScopeV1 extends string, ScopeV2 extends string>({
67
+ quoteCatalogReadPort,
68
+ quoteWithPromotions,
69
+ quoteWithoutPromotions,
70
+ pricingPolicy, isOfferProduct, requestFor, unitCount: COMMERCE_MIN_AUTO_ORDER_UNITS,
71
+ minimumProductPayableMinor: MINIMUM_PRODUCT_PAYABLE_MINOR, scopeV1, scopeV2,
72
+ }: CommerceOfferPricingDeps<Item, ScopeV1, ScopeV2>): Promise<CommerceOfferPricingResponse<ScopeV1, ScopeV2>> {
73
+ const products = (await quoteCatalogReadPort.listQuoteCatalogItems()).filter(isOfferProduct);
74
+ if (products.length === 0) throw new Error("commerce_offer_pricing_unavailable");
75
+
76
+ const settled = await Promise.all(
77
+ products.map(async (product) => {
78
+ try {
79
+ return {
80
+ product,
81
+ entry: await projectProduct(
82
+ product, quoteWithPromotions, quoteWithoutPromotions, requestFor, COMMERCE_MIN_AUTO_ORDER_UNITS, pricingPolicy,
83
+ ),
84
+ };
85
+ } catch (error) {
86
+ // One SKU the strict money authority cannot price must not remove every
87
+ // PDP price block. Only that refusal degrades; an infrastructure failure
88
+ // or a violated pricing invariant still refuses the whole projection.
89
+ if (error instanceof CommerceQuoteError && error.code === "PRICE_NOT_CONFIGURED") {
90
+ recordOfferPricingExclusion({
91
+ event: "commerce_offer_pricing_product_excluded",
92
+ product_slug: product.productSlug,
93
+ refusal_code: typeof error.details.reason === "string" ? error.details.reason : "unknown",
94
+ });
95
+ return null;
96
+ }
97
+ throw error;
98
+ }
99
+ }),
100
+ );
101
+ const priced = settled.flatMap((outcome) => (outcome ? [outcome] : []));
102
+ const projected = priced.map((outcome) => outcome.entry);
103
+ // The minimum below is the customer-facing "from" figure. Over an EMPTY set it
104
+ // would be meaningless, so a projection that priced nothing refuses exactly as
105
+ // an empty catalogue does rather than answering with an invented floor.
106
+ if (projected.length === 0) throw new Error("commerce_offer_pricing_unavailable");
107
+ const oneTime = minimumBy(projected, (entry) => entry.oneTime.unitGross.amountMinor);
108
+ const subscription = minimumBy(
109
+ projected,
110
+ (entry) => entry.subscriptionInitial.unitGross.amountMinor,
111
+ );
112
+
113
+ const legacy = {
114
+ contractVersion: COMMERCE_OFFER_PRICING_CONTRACT_VERSION,
115
+ scope: scopeV1,
116
+ products: projected,
117
+ minimum: { oneTime, subscription },
118
+ } as const;
119
+ if (pricingPolicy?.offerPolicyVersion !== OFFER_POLICY_V2 ||
120
+ pricingPolicy.promotionEngineVersion !== PROMOTION_ENGINE_V2) return legacy;
121
+
122
+ // The representative quote must come from a product that actually PRICED. Using
123
+ // the catalogue's first product would re-raise the very refusal just excluded
124
+ // and refuse the whole response despite another product having priced.
125
+ const representative = await quoteWithPromotions.createQuote(
126
+ requestFor(priced[0]!.product, "one_time"),
127
+ pricingPolicy ? { pricingPolicy } : undefined,
128
+ );
129
+ // An absent shipping figure is zero in the currency the quote came back in.
130
+ // It used to be zero in a named currency, which is a coercion rather than a
131
+ // default: a quote in one currency would have carried a shipping line in
132
+ // another, and the two would have been summed.
133
+ const quoteCurrency = representative.quote.currency;
134
+ const gross = representative.quote.shippingGross ?? { amountMinor: 0, currency: quoteCurrency };
135
+ const discountGross = representative.quote.shippingDiscountGross
136
+ ?? { amountMinor: 0, currency: quoteCurrency };
137
+ return {
138
+ contractVersion: COMMERCE_OFFER_PRICING_V2_CONTRACT_VERSION,
139
+ scope: scopeV2,
140
+ pricingPolicy,
141
+ products: projected,
142
+ minimum: { oneTime, subscription },
143
+ shipping: {
144
+ gross,
145
+ discountGross,
146
+ payable: { amountMinor: gross.amountMinor - discountGross.amountMinor, currency: gross.currency },
147
+ },
148
+ // Both halves come from the injected settlement policy, which supplies the
149
+ // denomination and left the amount, which was one major unit written out as
150
+ // a hundredths-assuming literal.
151
+ minimumProductPayable: {
152
+ amountMinor: MINIMUM_PRODUCT_PAYABLE_MINOR,
153
+ currency: quoteCurrency,
154
+ },
155
+ };
156
+ }
157
+
158
+ async function projectProduct<Item extends CommerceQuoteCatalogItem>(
159
+ product: Item,
160
+ quoteWithPromotions: CommerceQuotePort,
161
+ quoteWithoutPromotions: CommerceQuotePort,
162
+ requestFor: (product: Item, mode: "one_time" | "subscription") => CreateQuoteRequest,
163
+ COMMERCE_MIN_AUTO_ORDER_UNITS: number,
164
+ pricingPolicy?: PricingPolicySnapshot,
165
+ ): Promise<CommerceProductOfferPricing> {
166
+ const options = pricingPolicy ? { pricingPolicy } : undefined;
167
+ const [oneTime, subscriptionInitial, subscriptionRecurring] = await Promise.all([
168
+ quoteWithPromotions.createQuote(requestFor(product, "one_time"), options),
169
+ quoteWithPromotions.createQuote(requestFor(product, "subscription"), options),
170
+ quoteWithoutPromotions.createQuote(requestFor(product, "subscription"), options),
171
+ ]);
172
+ const oneTimeProductGross = productGross(oneTime.quote);
173
+ const initialProductGross = productGross(subscriptionInitial.quote);
174
+ const recurringProductGross = productGross(subscriptionRecurring.quote);
175
+ const anchorUnitGross = oneTime.quote.lines[0]?.unitPriceGross;
176
+ if (!anchorUnitGross) throw new Error("commerce_offer_pricing_line_missing");
177
+ const anchorPackageGross =
178
+ anchorUnitGross.amountMinor * COMMERCE_MIN_AUTO_ORDER_UNITS;
179
+
180
+ return {
181
+ productSlug: product.productSlug,
182
+ unitCount: COMMERCE_MIN_AUTO_ORDER_UNITS,
183
+ oneTime: price(oneTimeProductGross, oneTime.quote.currency, COMMERCE_MIN_AUTO_ORDER_UNITS),
184
+ subscriptionInitial: price(initialProductGross, subscriptionInitial.quote.currency, COMMERCE_MIN_AUTO_ORDER_UNITS),
185
+ subscriptionRecurring: price(
186
+ recurringProductGross,
187
+ subscriptionRecurring.quote.currency,
188
+ COMMERCE_MIN_AUTO_ORDER_UNITS,
189
+ ),
190
+ catalogAnchorUnitGross: anchorUnitGross,
191
+ initialDiscountPercent: discountPercent(anchorPackageGross, initialProductGross),
192
+ recurringDiscountPercent: discountPercent(anchorPackageGross, recurringProductGross),
193
+ };
194
+ }
195
+
196
+ function productGross(quote: CommerceQuote): number {
197
+ return quote.subtotalGross.amountMinor - quote.discountTotalGross.amountMinor;
198
+ }
199
+
200
+ // Carries whatever currency the quote came back in; it is not this helper's job
201
+ // to name one (src/lib/currency/platformCurrency.ts owns that question).
202
+ function price(amountMinor: number, currency: CommerceCurrency, COMMERCE_MIN_AUTO_ORDER_UNITS: number) {
203
+ return {
204
+ packageGross: { amountMinor, currency },
205
+ unitGross: {
206
+ amountMinor: Math.round(amountMinor / COMMERCE_MIN_AUTO_ORDER_UNITS),
207
+ currency,
208
+ },
209
+ };
210
+ }
211
+
212
+ function discountPercent(anchorMinor: number, effectiveMinor: number): number {
213
+ if (anchorMinor <= 0 || effectiveMinor >= anchorMinor) return 0;
214
+ return Math.round(((anchorMinor - effectiveMinor) / anchorMinor) * 100);
215
+ }
216
+
217
+ function minimumBy(
218
+ entries: CommerceProductOfferPricing[],
219
+ amount: (entry: CommerceProductOfferPricing) => number,
220
+ ): CommerceProductOfferPricing {
221
+ return entries.reduce((minimum, entry) =>
222
+ amount(entry) < amount(minimum) ? entry : minimum,
223
+ );
224
+ }
@@ -0,0 +1,53 @@
1
+ import type { CommerceOfferPricingResponse } from "./contracts/offer.js";
2
+
3
+ /** One bounded value and one in-flight load per runtime instance. */
4
+ export interface CommerceOfferPricingCache<Response extends CommerceOfferPricingResponse = CommerceOfferPricingResponse> {
5
+ read(key: string, load: () => Promise<Response>): Promise<Response>;
6
+ }
7
+
8
+ export function createCommerceOfferPricingCache<Response extends CommerceOfferPricingResponse = CommerceOfferPricingResponse>(input: {
9
+ ttlMs: number;
10
+ failureTtlMs?: number;
11
+ now?: () => number;
12
+ }): CommerceOfferPricingCache<Response> {
13
+ const now = input.now ?? Date.now;
14
+ const failureTtlMs = input.failureTtlMs ?? 0;
15
+ const cached = new Map<string, { value: Response; expiresAt: number }>();
16
+ const failed = new Map<string, { error: unknown; expiresAt: number }>();
17
+ const pending = new Map<string, Promise<Response>>();
18
+
19
+ return {
20
+ read(key, load) {
21
+ const cachedEntry = cached.get(key);
22
+ const failedEntry = failed.get(key);
23
+ if (cachedEntry && cachedEntry.expiresAt > now()) return Promise.resolve(cachedEntry.value);
24
+ if (failedEntry && failedEntry.expiresAt > now()) return Promise.reject(failedEntry.error);
25
+ const pendingEntry = pending.get(key);
26
+ if (pendingEntry) return pendingEntry;
27
+
28
+ const startedAt = now();
29
+ const current = load()
30
+ .then((value) => {
31
+ // Bound freshness from the pricing instant, not from completion of a
32
+ // potentially slow DB read.
33
+ cached.set(key, { value, expiresAt: startedAt + input.ttlMs });
34
+ failed.delete(key);
35
+ return value;
36
+ })
37
+ .catch((error: unknown) => {
38
+ // A public cold endpoint must not hammer a degraded database once per
39
+ // request. Keep only the failure object, briefly, and never turn it
40
+ // into stale pricing data; callers still receive the same rejection.
41
+ if (failureTtlMs > 0) {
42
+ failed.set(key, { error, expiresAt: now() + failureTtlMs });
43
+ }
44
+ throw error;
45
+ })
46
+ .finally(() => {
47
+ if (pending.get(key) === current) pending.delete(key);
48
+ });
49
+ pending.set(key, current);
50
+ return current;
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,137 @@
1
+ import type { PricingPolicyRequest, PricingPolicySnapshot } from "./contracts/offerPolicy.js";
2
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
3
+ import type { BffResponder } from "../bff/index.js";
4
+ import type { z } from "zod";
5
+ import {
6
+ type CommerceOfferPricingResponse,
7
+ } from "./contracts/offer.js";
8
+ import {
9
+ pricingPolicyRequestSchema,
10
+ } from "./offerPolicySchemas.js";
11
+
12
+ // Price/promotion validity can end at any instant. Keep the public cache very
13
+ // short and omit stale-while-revalidate; a future contract may expose the next
14
+ // validity boundary and clamp this exactly to it.
15
+ export const COMMERCE_OFFER_PRICING_CACHE_CONTROL =
16
+ "public, max-age=0, s-maxage=5, must-revalidate";
17
+ export const COMMERCE_OFFER_PRICING_ASSIGNED_CACHE_CONTROL = "private, no-store";
18
+
19
+ export const OFFER_POLICY_CAPABILITY_HEADER = "x-commerce-offer-policy-capability";
20
+ export const OFFER_POLICY_TOKEN_HEADER = "x-commerce-pricing-policy-token";
21
+ export const OFFER_POLICY_VISITOR_HEADER = "x-commerce-visitor-id";
22
+
23
+ const ROUTER_QUERY_KEYS = new Set(["path", "__bffPath"]);
24
+
25
+ export interface CommerceOfferPricingHandlerDeps<Response extends CommerceOfferPricingResponse = CommerceOfferPricingResponse> {
26
+ responder: BffResponder;
27
+ commerceOfferPricingResponseSchema: z.ZodType<Response>;
28
+ readOfferPricing(pricingPolicy?: PricingPolicySnapshot): Promise<Response>;
29
+ requirePricingPolicy?: boolean;
30
+ resolvePricingPolicy?(request: {
31
+ visitorId: string;
32
+ pricingPolicy: PricingPolicyRequest;
33
+ }): PricingPolicySnapshot | undefined | Promise<PricingPolicySnapshot | undefined>;
34
+ }
35
+
36
+ export function createCommerceOfferPricingHandler<Response extends CommerceOfferPricingResponse>({
37
+ responder, commerceOfferPricingResponseSchema,
38
+ readOfferPricing,
39
+ resolvePricingPolicy,
40
+ requirePricingPolicy = false,
41
+ }: CommerceOfferPricingHandlerDeps<Response>) {
42
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = responder;
43
+ return async function handler(
44
+ req: HttpRequest,
45
+ res: HttpResponse,
46
+ ): Promise<void> {
47
+ if (req.method !== "GET") {
48
+ sendMethodNotAllowed(res, ["GET"]);
49
+ return;
50
+ }
51
+ const userQueryKeys = Object.keys(req.query ?? {}).filter(
52
+ (key) => !ROUTER_QUERY_KEYS.has(key),
53
+ );
54
+ if (userQueryKeys.length > 0) {
55
+ sendBffError(res, "BAD_REQUEST", "Commerce offer pricing does not accept query parameters");
56
+ return;
57
+ }
58
+
59
+ try {
60
+ const policyRequest = readPolicyRequest(req);
61
+ if (policyRequest === null) {
62
+ sendBffError(res, "BAD_REQUEST", "Commerce offer pricing policy capability is invalid");
63
+ return;
64
+ }
65
+ if (!policyRequest && requirePricingPolicy) {
66
+ // A caller that sent no capability headers is an incomplete request,
67
+ // not an unavailable origin — the same attribution the missing-visitor
68
+ // branch above already applies.
69
+ sendBffError(
70
+ res,
71
+ "BAD_REQUEST",
72
+ "Commerce offer pricing requires offer policy v2",
73
+ {
74
+ details: {
75
+ reason: "pricing_policy_request_invalid",
76
+ stage: "pricing_policy",
77
+ policyReason: "v2_capability_required",
78
+ },
79
+ },
80
+ );
81
+ return;
82
+ }
83
+ const pricingPolicy = policyRequest && resolvePricingPolicy
84
+ ? await resolvePricingPolicy(policyRequest)
85
+ : undefined;
86
+ const parsed = commerceOfferPricingResponseSchema.safeParse(
87
+ await readOfferPricing(pricingPolicy),
88
+ );
89
+ if (!parsed.success) {
90
+ sendBffError(
91
+ res,
92
+ "INVALID_RESPONSE",
93
+ "Commerce offer pricing returned invalid response",
94
+ );
95
+ return;
96
+ }
97
+ sendBffSuccess(
98
+ res,
99
+ parsed.data,
100
+ { contractVersion: parsed.data.contractVersion },
101
+ {
102
+ cacheControl: policyRequest
103
+ ? COMMERCE_OFFER_PRICING_ASSIGNED_CACHE_CONTROL
104
+ : COMMERCE_OFFER_PRICING_CACHE_CONTROL,
105
+ },
106
+ );
107
+ } catch {
108
+ sendBffError(
109
+ res,
110
+ "UPSTREAM_UNAVAILABLE",
111
+ "Commerce offer pricing is unavailable",
112
+ );
113
+ }
114
+ };
115
+ }
116
+
117
+ function readPolicyRequest(req: HttpRequest): {
118
+ visitorId: string;
119
+ pricingPolicy: PricingPolicyRequest;
120
+ } | undefined | null {
121
+ const capability = singleHeader(req, OFFER_POLICY_CAPABILITY_HEADER);
122
+ const token = singleHeader(req, OFFER_POLICY_TOKEN_HEADER);
123
+ if (!capability && !token) return undefined;
124
+ const visitorId = singleHeader(req, OFFER_POLICY_VISITOR_HEADER);
125
+ if (!visitorId || visitorId.length > 120) return null;
126
+ const parsed = pricingPolicyRequestSchema.safeParse({
127
+ ...(capability ? { capability } : {}),
128
+ ...(token ? { token } : {}),
129
+ });
130
+ return parsed.success ? { visitorId, pricingPolicy: parsed.data } : null;
131
+ }
132
+
133
+ function singleHeader(req: HttpRequest, name: string): string | undefined {
134
+ const value = req.headers?.[name];
135
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
136
+ }
137
+
@@ -0,0 +1,169 @@
1
+ import type { CommercePriceAuthorityQuery, CommercePriceAuthorityResult } from "./contracts/index.js";
2
+ import {
3
+ CommercePriceAuthorityError,
4
+ type CommercePriceAuthorityPort,
5
+ } from "./ports.js";
6
+ import {
7
+ SubscriptionPricePolicyDigestError,
8
+ assertSubscriptionPricePolicyDigest,
9
+ deriveSubscriptionUnitPrice,
10
+ } from "./subscriptionPricePolicy.js";
11
+ import type { ResolvedPrice } from "@openlup/core/pricing";
12
+
13
+ export interface TimedAuthorityValue<T> {
14
+ value: T;
15
+ validFrom: string;
16
+ validTo: string | null;
17
+ }
18
+
19
+ export interface CommercePriceAuthorityReadPort {
20
+ listPriceLists(context: Pick<CommercePriceAuthorityQuery, "regionCode" | "currency">): Promise<ReadonlyArray<TimedAuthorityValue<string>>>;
21
+ listExactBaseEntries(input: { variantId: string; priceListId: string }): Promise<ReadonlyArray<TimedAuthorityValue<ResolvedPrice>>>;
22
+ listExactLegacySubscriptionEntries(input: { variantId: string; priceListId: string }): Promise<ReadonlyArray<TimedAuthorityValue<ResolvedPrice>>>;
23
+ listSubscriptionPolicyHistory(context: Pick<CommercePriceAuthorityQuery, "regionCode" | "currency" | "channel"> & { priceListId: string }): Promise<ReadonlyArray<TimedAuthorityValue<unknown>>>;
24
+ }
25
+
26
+ /** Provider-neutral strict money algorithm; adapters own only persistence reads and row mapping. */
27
+ export function createCommercePriceAuthorityPort(
28
+ reads: CommercePriceAuthorityReadPort,
29
+ ): CommercePriceAuthorityPort {
30
+ return {
31
+ async resolvePrice(query): Promise<CommercePriceAuthorityResult> {
32
+ const atTime = query.atTime ?? new Date().toISOString();
33
+ const priceListId = selectUniqueEffectivePriceList(await reads.listPriceLists(query), atTime);
34
+
35
+ const baseCandidates = effective(await reads.listExactBaseEntries({
36
+ variantId: query.variantId,
37
+ priceListId,
38
+ }), atTime);
39
+ if (baseCandidates.length === 0) throw new CommercePriceAuthorityError("base_price_not_found");
40
+ if (baseCandidates.length !== 1) throw new CommercePriceAuthorityError("base_price_ambiguous");
41
+ if (baseCandidates[0]!.value.amountKind !== "gross") {
42
+ throw new CommercePriceAuthorityError("base_price_amount_kind_invalid");
43
+ }
44
+
45
+ const base = { ...baseCandidates[0]!.value, resolvedAt: atTime };
46
+ if (query.mode === "one_time") return { kind: "one_time", base, unitPriceMinor: base.unitPriceMinor };
47
+
48
+ const policyHistory = await reads.listSubscriptionPolicyHistory({
49
+ priceListId: base.priceListId,
50
+ regionCode: query.regionCode,
51
+ currency: query.currency,
52
+ channel: query.channel,
53
+ });
54
+ if (policyHistory.length === 0) {
55
+ const legacyCandidates = effective(await reads.listExactLegacySubscriptionEntries({
56
+ variantId: query.variantId,
57
+ priceListId: base.priceListId,
58
+ }), atTime);
59
+ if (legacyCandidates.length === 0) throw new CommercePriceAuthorityError("legacy_subscription_price_not_found");
60
+ if (legacyCandidates.length !== 1) throw new CommercePriceAuthorityError("legacy_subscription_price_ambiguous");
61
+ const legacyResolved = { ...legacyCandidates[0]!.value, resolvedAt: atTime };
62
+ return { kind: "subscription_legacy", base, unitPriceMinor: legacyResolved.unitPriceMinor, legacyResolved };
63
+ }
64
+
65
+ const effectivePolicies = effective(policyHistory, atTime);
66
+ if (effectivePolicies.length === 0) throw new CommercePriceAuthorityError("subscription_price_policy_not_effective");
67
+ if (effectivePolicies.length !== 1) throw new CommercePriceAuthorityError("subscription_price_policy_ambiguous");
68
+ try {
69
+ const policy = await assertSubscriptionPricePolicyDigest(effectivePolicies[0]!.value);
70
+ return {
71
+ kind: "subscription_policy",
72
+ base,
73
+ unitPriceMinor: deriveSubscriptionUnitPrice(base.unitPriceMinor, policy),
74
+ policy,
75
+ };
76
+ } catch (error) {
77
+ throw new CommercePriceAuthorityError(
78
+ error instanceof SubscriptionPricePolicyDigestError
79
+ ? "subscription_price_policy_digest_mismatch"
80
+ : "subscription_price_policy_invalid",
81
+ );
82
+ }
83
+ },
84
+ };
85
+ }
86
+
87
+ /** Opt-in scheduling using the selected list for concurrent history reads. */
88
+ export function createConcurrentCommercePriceAuthorityPort(
89
+ reads: CommercePriceAuthorityReadPort,
90
+ ): CommercePriceAuthorityPort {
91
+ return {
92
+ async resolvePrice(query): Promise<CommercePriceAuthorityResult> {
93
+ const atTime = query.atTime ?? new Date().toISOString();
94
+ const priceListId = selectUniqueEffectivePriceList(await reads.listPriceLists(query), atTime);
95
+
96
+ // Schedule history alongside base reads, retaining base-refusal precedence.
97
+ const policyHistoryRead = query.mode === "subscription" ? Promise.resolve().then(() =>
98
+ reads.listSubscriptionPolicyHistory({ priceListId, regionCode: query.regionCode,
99
+ currency: query.currency, channel: query.channel })).then(
100
+ (value) => ({ value }), (error: unknown) => ({ error }),
101
+ ) : null;
102
+
103
+ const baseCandidates = effective(await reads.listExactBaseEntries({
104
+ variantId: query.variantId,
105
+ priceListId,
106
+ }), atTime);
107
+ if (baseCandidates.length === 0) throw new CommercePriceAuthorityError("base_price_not_found");
108
+ if (baseCandidates.length !== 1) throw new CommercePriceAuthorityError("base_price_ambiguous");
109
+ if (baseCandidates[0]!.value.amountKind !== "gross") {
110
+ throw new CommercePriceAuthorityError("base_price_amount_kind_invalid");
111
+ }
112
+
113
+ const base = { ...baseCandidates[0]!.value, resolvedAt: atTime };
114
+ if (query.mode === "one_time") return { kind: "one_time", base, unitPriceMinor: base.unitPriceMinor };
115
+
116
+ const policyRead = await policyHistoryRead!;
117
+ if ("error" in policyRead) throw policyRead.error;
118
+ const policyHistory = policyRead.value;
119
+ if (policyHistory.length === 0) {
120
+ const legacyCandidates = effective(await reads.listExactLegacySubscriptionEntries({
121
+ variantId: query.variantId,
122
+ priceListId: base.priceListId,
123
+ }), atTime);
124
+ if (legacyCandidates.length === 0) throw new CommercePriceAuthorityError("legacy_subscription_price_not_found");
125
+ if (legacyCandidates.length !== 1) throw new CommercePriceAuthorityError("legacy_subscription_price_ambiguous");
126
+ const legacyResolved = { ...legacyCandidates[0]!.value, resolvedAt: atTime };
127
+ return { kind: "subscription_legacy", base, unitPriceMinor: legacyResolved.unitPriceMinor, legacyResolved };
128
+ }
129
+
130
+ const effectivePolicies = effective(policyHistory, atTime);
131
+ if (effectivePolicies.length === 0) throw new CommercePriceAuthorityError("subscription_price_policy_not_effective");
132
+ if (effectivePolicies.length !== 1) throw new CommercePriceAuthorityError("subscription_price_policy_ambiguous");
133
+ try {
134
+ const policy = await assertSubscriptionPricePolicyDigest(effectivePolicies[0]!.value);
135
+ return {
136
+ kind: "subscription_policy",
137
+ base,
138
+ unitPriceMinor: deriveSubscriptionUnitPrice(base.unitPriceMinor, policy),
139
+ policy,
140
+ };
141
+ } catch (error) {
142
+ throw new CommercePriceAuthorityError(
143
+ error instanceof SubscriptionPricePolicyDigestError
144
+ ? "subscription_price_policy_digest_mismatch"
145
+ : "subscription_price_policy_invalid",
146
+ );
147
+ }
148
+ },
149
+ };
150
+ }
151
+
152
+ /** The current money context requires one list; creation order cannot resolve overlap. */
153
+ export function selectUniqueEffectivePriceList(
154
+ priceLists: readonly TimedAuthorityValue<string>[], atTime: string,
155
+ ): string {
156
+ const candidates = effective(priceLists, atTime);
157
+ if (candidates.length === 0) throw new CommercePriceAuthorityError("active_price_list_not_found");
158
+ if (candidates.length !== 1) throw new CommercePriceAuthorityError("active_price_list_ambiguous");
159
+ return candidates[0]!.value;
160
+ }
161
+
162
+ function effective<T>(values: readonly TimedAuthorityValue<T>[], atTime: string): TimedAuthorityValue<T>[] {
163
+ const atMs = Date.parse(atTime);
164
+ return values.filter(({ validFrom, validTo }) => {
165
+ const fromMs = Date.parse(validFrom);
166
+ const toMs = validTo === null ? null : Date.parse(validTo);
167
+ return Number.isFinite(atMs) && Number.isFinite(fromMs) && fromMs <= atMs && (toMs === null || toMs > atMs);
168
+ });
169
+ }
@@ -0,0 +1,88 @@
1
+ /** The server cannot assign a policy right now. Caller-independent: retrying an
2
+ * identical request only succeeds once production configuration changes. */
3
+ export class CommercePricingPolicyUnavailableError extends Error {
4
+ constructor(public readonly reason: string) {
5
+ super("commerce_pricing_policy_v2_unavailable");
6
+ this.name = "CommercePricingPolicyUnavailableError";
7
+ }
8
+ }
9
+
10
+ /**
11
+ * Every reason a caller-side refusal can carry. ⛔ Each one answers `400`, which
12
+ * puts it outside every 5xx monitor, so each one owes a dedicated monitor of its
13
+ * own; a host's observability check can read this tuple and fail when a reason
14
+ * has none. Adding a member here without adding a monitor is the regression this
15
+ * tuple exists to prevent: reclassifying `v2_capability_required` from `503` to
16
+ * `400` once silently removed the only alert on a site-wide unpriceable-quote
17
+ * outage.
18
+ */
19
+ export const PRICING_POLICY_REQUEST_REASONS = [
20
+ "v2_capability_required",
21
+ "v2_visitor_id_required",
22
+ ] as const;
23
+
24
+ export type PricingPolicyRequestReason = (typeof PRICING_POLICY_REQUEST_REASONS)[number];
25
+
26
+ /** The request itself lacks the context a v2 assignment needs. Caller-fixable,
27
+ * and never evidence that pricing is down. The reason is typed off
28
+ * PRICING_POLICY_REQUEST_REASONS so a new one cannot skip its monitor. */
29
+ export class CommercePricingPolicyRequestError extends Error {
30
+ constructor(public readonly reason: PricingPolicyRequestReason) {
31
+ super("commerce_pricing_policy_v2_request_invalid");
32
+ this.name = "CommercePricingPolicyRequestError";
33
+ }
34
+ }
35
+
36
+ export type SafePricingPolicyReason =
37
+ | "v2_capability_required"
38
+ | "v2_visitor_id_required"
39
+ | "signing_secret_unavailable"
40
+ | "v1_assignment_rejected"
41
+ | "v2_assignment_config_invalid"
42
+ | "v2_readiness_not_ready"
43
+ | "other";
44
+
45
+ const SAFE_PRICING_POLICY_REASONS = new Set<SafePricingPolicyReason>([
46
+ "v2_capability_required",
47
+ "v2_visitor_id_required",
48
+ "signing_secret_unavailable", // gitleaks:allow — closed-set diagnostic code, never a credential
49
+ "v1_assignment_rejected",
50
+ "v2_assignment_config_invalid",
51
+ "v2_readiness_not_ready",
52
+ "other",
53
+ ]);
54
+
55
+ export interface PricingPolicyFailureMapping {
56
+ code: "BAD_REQUEST" | "UPSTREAM_UNAVAILABLE";
57
+ details: {
58
+ reason: "pricing_policy_request_invalid" | "pricing_policy_unavailable";
59
+ stage: "pricing_policy";
60
+ policyReason: SafePricingPolicyReason;
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Single owner of the status a pricing-policy refusal earns, shared by every
66
+ * route that resolves one. A caller-attributable gap answers `400`; only a
67
+ * server that genuinely cannot price answers `503`, so a `503` on these routes
68
+ * stays a truthful outage signal.
69
+ */
70
+ export function mapPricingPolicyFailure(error: unknown): PricingPolicyFailureMapping | null {
71
+ const caller = error instanceof CommercePricingPolicyRequestError;
72
+ if (!caller && !(error instanceof CommercePricingPolicyUnavailableError)) return null;
73
+ return {
74
+ code: caller ? "BAD_REQUEST" : "UPSTREAM_UNAVAILABLE",
75
+ details: {
76
+ reason: caller ? "pricing_policy_request_invalid" : "pricing_policy_unavailable",
77
+ stage: "pricing_policy",
78
+ policyReason: safePricingPolicyReason((error as { reason: unknown }).reason),
79
+ },
80
+ };
81
+ }
82
+
83
+ function safePricingPolicyReason(value: unknown): SafePricingPolicyReason {
84
+ return SAFE_PRICING_POLICY_REASONS.has(value as SafePricingPolicyReason)
85
+ ? value as SafePricingPolicyReason
86
+ : "other";
87
+ }
88
+