@openlup/platform 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +284 -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 +20 -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 +166 -0
  22. package/dist/catalog/bundleCatalogReadPort.js +36 -0
  23. package/dist/catalog/bundleCatalogStore.d.ts +3 -0
  24. package/dist/catalog/bundleCatalogStore.js +152 -0
  25. package/dist/catalog/bundleRuleCodes.d.ts +41 -0
  26. package/dist/catalog/bundleRuleCodes.js +110 -0
  27. package/dist/catalog/bundleRules.d.ts +69 -0
  28. package/dist/catalog/bundleRules.js +102 -0
  29. package/dist/catalog/catalogDraftStore.d.ts +4 -0
  30. package/dist/catalog/catalogDraftStore.js +63 -0
  31. package/dist/catalog/catalogSkuEnvelope.d.ts +7 -0
  32. package/dist/catalog/catalogSkuEnvelope.js +142 -0
  33. package/dist/catalog/catalogSkuEnvelopeMapper.d.ts +38 -0
  34. package/dist/catalog/catalogSkuEnvelopeMapper.js +88 -0
  35. package/dist/catalog/contracts/bundleAdmin.d.ts +143 -0
  36. package/dist/catalog/contracts/bundleAdmin.js +1 -0
  37. package/dist/catalog/contracts/bundleAvailability.d.ts +61 -0
  38. package/dist/catalog/contracts/bundleAvailability.js +1 -0
  39. package/dist/catalog/contracts/bundleRead.d.ts +223 -0
  40. package/dist/catalog/contracts/bundleRead.js +1 -0
  41. package/dist/catalog/contracts/draft.d.ts +79 -0
  42. package/dist/catalog/contracts/draft.js +47 -0
  43. package/dist/catalog/contracts/foundation.d.ts +78 -0
  44. package/dist/catalog/contracts/foundation.js +53 -0
  45. package/dist/catalog/contracts/index.d.ts +14 -0
  46. package/dist/catalog/contracts/index.js +6 -0
  47. package/dist/catalog/contracts/lifecycle.d.ts +77 -0
  48. package/dist/catalog/contracts/lifecycle.js +1 -0
  49. package/dist/catalog/contracts/productMediaContracts.d.ts +77 -0
  50. package/dist/catalog/contracts/productMediaContracts.js +149 -0
  51. package/dist/catalog/contracts/productTypes.d.ts +54 -0
  52. package/dist/catalog/contracts/productTypes.js +16 -0
  53. package/dist/catalog/contracts/resolveProductMedia.d.ts +3 -0
  54. package/dist/catalog/contracts/resolveProductMedia.js +78 -0
  55. package/dist/catalog/contracts/sellable.d.ts +23 -0
  56. package/dist/catalog/contracts/sellable.js +1 -0
  57. package/dist/catalog/draftHandler.d.ts +25 -0
  58. package/dist/catalog/draftHandler.js +118 -0
  59. package/dist/catalog/draftPorts.d.ts +18 -0
  60. package/dist/catalog/draftPorts.js +8 -0
  61. package/dist/catalog/draftSchemas.d.ts +423 -0
  62. package/dist/catalog/draftSchemas.js +89 -0
  63. package/dist/catalog/draftValidation.d.ts +21 -0
  64. package/dist/catalog/draftValidation.js +125 -0
  65. package/dist/catalog/foundationSchemas.d.ts +98 -0
  66. package/dist/catalog/foundationSchemas.js +110 -0
  67. package/dist/catalog/index.d.ts +43 -0
  68. package/dist/catalog/index.js +25 -0
  69. package/dist/catalog/mediaSourcePack.d.ts +51 -0
  70. package/dist/catalog/mediaSourcePack.js +247 -0
  71. package/dist/catalog/productTypeSchemas.d.ts +46 -0
  72. package/dist/catalog/productTypeSchemas.js +72 -0
  73. package/dist/catalog/publicationPorts.d.ts +42 -0
  74. package/dist/catalog/publicationPorts.js +1 -0
  75. package/dist/catalog/readPorts.d.ts +51 -0
  76. package/dist/catalog/readPorts.js +1 -0
  77. package/dist/catalog/referenceCatalogHandler.d.ts +7 -0
  78. package/dist/catalog/referenceCatalogHandler.js +27 -0
  79. package/dist/catalog/resolvedSkuProjection.d.ts +88 -0
  80. package/dist/catalog/resolvedSkuProjection.js +60 -0
  81. package/dist/catalog/sellableBundleHandler.d.ts +46 -0
  82. package/dist/catalog/sellableBundleHandler.js +84 -0
  83. package/dist/catalog/sellableSchemas.d.ts +46 -0
  84. package/dist/catalog/sellableSchemas.js +27 -0
  85. package/dist/catalog/sql.d.ts +46 -0
  86. package/dist/catalog/sql.js +13 -0
  87. package/dist/catalog/validation.d.ts +2 -0
  88. package/dist/catalog/validation.js +3 -0
  89. package/dist/communications/contracts/index.d.ts +1 -0
  90. package/dist/communications/contracts/marketingSync.d.ts +194 -0
  91. package/dist/communications/contracts/marketingSync.js +1 -0
  92. package/dist/communications/index.d.ts +1 -0
  93. package/dist/communications/klaviyo/consentPush.d.ts +51 -0
  94. package/dist/communications/klaviyo/consentPush.js +19 -0
  95. package/dist/communications/klaviyo/index.d.ts +110 -0
  96. package/dist/communications/klaviyo/index.js +860 -0
  97. package/dist/communications/newsletterIntegrationContracts.d.ts +3 -3
  98. package/dist/communications/resend/index.d.ts +40 -0
  99. package/dist/communications/resend/index.js +310 -0
  100. package/dist/composition/catalogContribution.d.ts +18 -0
  101. package/dist/composition/catalogContribution.js +21 -0
  102. package/dist/composition/index.d.ts +1 -0
  103. package/dist/composition/index.js +1 -0
  104. package/dist/identity/customerContracts.d.ts +2 -2
  105. package/dist/inventory/adminInventory.d.ts +13 -0
  106. package/dist/inventory/adminInventory.js +214 -0
  107. package/dist/inventory/contracts/forecast.d.ts +86 -0
  108. package/dist/inventory/contracts/forecast.js +1 -0
  109. package/dist/inventory/contracts/index.d.ts +4 -0
  110. package/dist/inventory/contracts/index.js +1 -0
  111. package/dist/inventory/contracts/inventory.d.ts +157 -0
  112. package/dist/inventory/contracts/inventory.js +1 -0
  113. package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
  114. package/dist/inventory/contracts/newPurchaseStock.js +1 -0
  115. package/dist/inventory/contracts/reservation.d.ts +18 -0
  116. package/dist/inventory/contracts/reservation.js +1 -0
  117. package/dist/inventory/forecast.d.ts +24 -0
  118. package/dist/inventory/forecast.js +200 -0
  119. package/dist/inventory/index.d.ts +13 -0
  120. package/dist/inventory/index.js +10 -0
  121. package/dist/inventory/inventoryHandlers.d.ts +53 -0
  122. package/dist/inventory/inventoryHandlers.js +151 -0
  123. package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
  124. package/dist/inventory/newPurchaseStockAvailability.js +75 -0
  125. package/dist/inventory/ports.d.ts +21 -0
  126. package/dist/inventory/ports.js +16 -0
  127. package/dist/inventory/renewalReservation.d.ts +27 -0
  128. package/dist/inventory/renewalReservation.js +61 -0
  129. package/dist/inventory/reservationPolicy.d.ts +6 -0
  130. package/dist/inventory/reservationPolicy.js +19 -0
  131. package/dist/inventory/schemas.d.ts +137 -0
  132. package/dist/inventory/schemas.js +112 -0
  133. package/dist/inventory/sql.d.ts +14 -0
  134. package/dist/inventory/sql.js +5 -0
  135. package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
  136. package/dist/inventory/subscriptionForecastCalculation.js +133 -0
  137. package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
  138. package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
  139. package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
  140. package/dist/pricing/acquisitionOfferPricing.js +25 -0
  141. package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
  142. package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
  143. package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
  144. package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
  145. package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
  146. package/dist/pricing/acquisitionQuoteBinding.js +12 -0
  147. package/dist/pricing/acquisitionReadiness.d.ts +31 -0
  148. package/dist/pricing/acquisitionReadiness.js +117 -0
  149. package/dist/pricing/acquisitionTerms.d.ts +19 -0
  150. package/dist/pricing/acquisitionTerms.js +126 -0
  151. package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
  152. package/dist/pricing/acquisitionTermsSchemas.js +42 -0
  153. package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
  154. package/dist/pricing/catalogFactsProvenance.js +64 -0
  155. package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
  156. package/dist/pricing/catalogFactsSchemas.js +58 -0
  157. package/dist/pricing/commerceOfferPricing.d.ts +25 -0
  158. package/dist/pricing/commerceOfferPricing.js +139 -0
  159. package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
  160. package/dist/pricing/commerceOfferPricingCache.js +44 -0
  161. package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
  162. package/dist/pricing/commerceOfferPricingHandler.js +78 -0
  163. package/dist/pricing/commercePriceAuthority.d.ts +26 -0
  164. package/dist/pricing/commercePriceAuthority.js +79 -0
  165. package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
  166. package/dist/pricing/commercePricingPolicyErrors.js +67 -0
  167. package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
  168. package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
  169. package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
  170. package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
  171. package/dist/pricing/commerceQuoteHandler.d.ts +31 -0
  172. package/dist/pricing/commerceQuoteHandler.js +110 -0
  173. package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
  174. package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
  175. package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
  176. package/dist/pricing/contracts/acquisitionTerms.js +2 -0
  177. package/dist/pricing/contracts/authority.d.ts +27 -0
  178. package/dist/pricing/contracts/authority.js +1 -0
  179. package/dist/pricing/contracts/evidence.d.ts +33 -0
  180. package/dist/pricing/contracts/evidence.js +1 -0
  181. package/dist/pricing/contracts/index.d.ts +17 -0
  182. package/dist/pricing/contracts/index.js +7 -0
  183. package/dist/pricing/contracts/money.d.ts +27 -0
  184. package/dist/pricing/contracts/money.js +1 -0
  185. package/dist/pricing/contracts/offer.d.ts +52 -0
  186. package/dist/pricing/contracts/offer.js +4 -0
  187. package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
  188. package/dist/pricing/contracts/offerPolicy.js +5 -0
  189. package/dist/pricing/contracts/policy.d.ts +22 -0
  190. package/dist/pricing/contracts/policy.js +3 -0
  191. package/dist/pricing/contracts/provenance.d.ts +24 -0
  192. package/dist/pricing/contracts/provenance.js +2 -0
  193. package/dist/pricing/contracts/quote.d.ts +94 -0
  194. package/dist/pricing/contracts/quote.js +1 -0
  195. package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
  196. package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
  197. package/dist/pricing/dbBackedCommerceQuotePort.d.ts +66 -0
  198. package/dist/pricing/dbBackedCommerceQuotePort.js +206 -0
  199. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
  200. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
  201. package/dist/pricing/index.d.ts +51 -0
  202. package/dist/pricing/index.js +36 -0
  203. package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
  204. package/dist/pricing/offerPolicyReadiness.js +79 -0
  205. package/dist/pricing/offerPolicySchemas.d.ts +25 -0
  206. package/dist/pricing/offerPolicySchemas.js +15 -0
  207. package/dist/pricing/offerPricingSchemas.d.ts +315 -0
  208. package/dist/pricing/offerPricingSchemas.js +65 -0
  209. package/dist/pricing/ports.d.ts +13 -0
  210. package/dist/pricing/ports.js +9 -0
  211. package/dist/pricing/pricingPolicyToken.d.ts +39 -0
  212. package/dist/pricing/pricingPolicyToken.js +84 -0
  213. package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
  214. package/dist/pricing/quoteAcquisitionTerms.js +4 -0
  215. package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
  216. package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
  217. package/dist/pricing/quotePorts.d.ts +30 -0
  218. package/dist/pricing/quotePorts.js +20 -0
  219. package/dist/pricing/quotePrimitives.d.ts +13 -0
  220. package/dist/pricing/quotePrimitives.js +18 -0
  221. package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
  222. package/dist/pricing/quotePromotionEvidence.js +73 -0
  223. package/dist/pricing/quoteSchemas.d.ts +1176 -0
  224. package/dist/pricing/quoteSchemas.js +196 -0
  225. package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
  226. package/dist/pricing/subscriptionPricePolicy.js +96 -0
  227. package/dist/pricing/taxProfileGuard.d.ts +2 -0
  228. package/dist/pricing/taxProfileGuard.js +24 -0
  229. package/dist/pricing/validation.d.ts +2 -0
  230. package/dist/pricing/validation.js +3 -0
  231. package/dist/promotions/adminBindings.d.ts +72 -0
  232. package/dist/promotions/adminBindings.js +1 -0
  233. package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
  234. package/dist/promotions/adminPromotionCodesContracts.js +211 -0
  235. package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
  236. package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
  237. package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
  238. package/dist/promotions/adminPromotionCodesHandler.js +194 -0
  239. package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
  240. package/dist/promotions/adminPromotionsContracts.js +99 -0
  241. package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
  242. package/dist/promotions/adminPromotionsDataPort.js +1 -0
  243. package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
  244. package/dist/promotions/adminPromotionsHandler.js +47 -0
  245. package/dist/promotions/contracts/index.d.ts +387 -0
  246. package/dist/promotions/contracts/index.js +1 -0
  247. package/dist/promotions/index.d.ts +22 -0
  248. package/dist/promotions/index.js +21 -0
  249. package/dist/promotions/promoDataPort.d.ts +47 -0
  250. package/dist/promotions/promoDataPort.js +14 -0
  251. package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
  252. package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
  253. package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
  254. package/dist/promotions/promotionBenefitDefinition.js +33 -0
  255. package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
  256. package/dist/promotions/promotionClaimSweepPort.js +8 -0
  257. package/dist/promotions/promotionClaims.d.ts +5 -0
  258. package/dist/promotions/promotionClaims.js +57 -0
  259. package/dist/promotions/promotionCodePreview.d.ts +11 -0
  260. package/dist/promotions/promotionCodePreview.js +154 -0
  261. package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
  262. package/dist/promotions/promotionCodeQuotePort.js +1 -0
  263. package/dist/promotions/promotionQuoteAcceptance.d.ts +65 -0
  264. package/dist/promotions/promotionQuoteAcceptance.js +202 -0
  265. package/dist/promotions/promotionQuoteBinding.d.ts +71 -0
  266. package/dist/promotions/promotionQuoteBinding.js +85 -0
  267. package/dist/promotions/quoteAutomaticPromotions.d.ts +24 -0
  268. package/dist/promotions/quoteAutomaticPromotions.js +36 -0
  269. package/dist/promotions/quotePromoDiscounts.d.ts +32 -0
  270. package/dist/promotions/quotePromoDiscounts.js +140 -0
  271. package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
  272. package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
  273. package/dist/promotions/quotePromotionV2.d.ts +29 -0
  274. package/dist/promotions/quotePromotionV2.js +174 -0
  275. package/dist/promotions/sql.d.ts +14 -0
  276. package/dist/promotions/sql.js +5 -0
  277. package/dist/promotions/validation.d.ts +2 -0
  278. package/dist/promotions/validation.js +3 -0
  279. package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
  280. package/dist/shipping/addressCanonLookupHandler.js +105 -0
  281. package/dist/shipping/addressCanonical.d.ts +2 -0
  282. package/dist/shipping/addressCanonical.js +26 -0
  283. package/dist/shipping/addressPorts.d.ts +39 -0
  284. package/dist/shipping/addressPorts.js +1 -0
  285. package/dist/shipping/addressSchemas.d.ts +30 -0
  286. package/dist/shipping/addressSchemas.js +1 -0
  287. package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
  288. package/dist/shipping/adminShippingRateHandler.js +64 -0
  289. package/dist/shipping/contracts/address.d.ts +144 -0
  290. package/dist/shipping/contracts/address.js +1 -0
  291. package/dist/shipping/contracts/delivery.d.ts +110 -0
  292. package/dist/shipping/contracts/delivery.js +1 -0
  293. package/dist/shipping/contracts/index.d.ts +3 -0
  294. package/dist/shipping/contracts/index.js +1 -0
  295. package/dist/shipping/contracts/shippingRate.d.ts +11 -0
  296. package/dist/shipping/contracts/shippingRate.js +1 -0
  297. package/dist/shipping/customerAddressesHandler.d.ts +5 -0
  298. package/dist/shipping/customerAddressesHandler.js +80 -0
  299. package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
  300. package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
  301. package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
  302. package/dist/shipping/deliverySelectionHandler.js +77 -0
  303. package/dist/shipping/deliverySelectionPort.d.ts +8 -0
  304. package/dist/shipping/deliverySelectionPort.js +1 -0
  305. package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
  306. package/dist/shipping/deliverySelectionSummary.js +53 -0
  307. package/dist/shipping/handlerSchemas.d.ts +21 -0
  308. package/dist/shipping/handlerSchemas.js +1 -0
  309. package/dist/shipping/index.d.ts +17 -0
  310. package/dist/shipping/index.js +8 -0
  311. package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
  312. package/dist/shipping/pickupPointSearchPort.js +1 -0
  313. package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
  314. package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
  315. package/dist/shipping/shippingRatePorts.d.ts +52 -0
  316. package/dist/shipping/shippingRatePorts.js +1 -0
  317. package/package.json +62 -2
  318. package/release-gates.json +218 -10
  319. package/sql/20260807140000_read_parity_catalog.sql +94 -0
  320. package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
  321. package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
  322. package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
  323. package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
  324. package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
  325. package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
  326. package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
  327. package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
  328. package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
  329. package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
  330. package/src/catalog/activeSkuSelection.ts +20 -0
  331. package/src/catalog/adminBindings.ts +59 -0
  332. package/src/catalog/adminBundleHandler.ts +165 -0
  333. package/src/catalog/adminBundleReadHandler.ts +261 -0
  334. package/src/catalog/adminBundleWritePort.ts +102 -0
  335. package/src/catalog/bundleAvailability.ts +129 -0
  336. package/src/catalog/bundleAvailabilityService.ts +171 -0
  337. package/src/catalog/bundleBindings.ts +29 -0
  338. package/src/catalog/bundleCatalogQueries.ts +98 -0
  339. package/src/catalog/bundleCatalogReadPort.ts +177 -0
  340. package/src/catalog/bundleCatalogStore.ts +269 -0
  341. package/src/catalog/bundleRuleCodes.ts +134 -0
  342. package/src/catalog/bundleRules.ts +153 -0
  343. package/src/catalog/catalogDraftStore.ts +71 -0
  344. package/src/catalog/catalogSkuEnvelope.ts +214 -0
  345. package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
  346. package/src/catalog/contracts/bundleAdmin.ts +20 -0
  347. package/src/catalog/contracts/bundleAvailability.ts +5 -0
  348. package/src/catalog/contracts/bundleRead.ts +13 -0
  349. package/src/catalog/contracts/draft.ts +69 -0
  350. package/src/catalog/contracts/foundation.ts +104 -0
  351. package/src/catalog/contracts/index.ts +14 -0
  352. package/src/catalog/contracts/lifecycle.ts +74 -0
  353. package/src/catalog/contracts/productMediaContracts.ts +170 -0
  354. package/src/catalog/contracts/productTypes.ts +35 -0
  355. package/src/catalog/contracts/resolveProductMedia.ts +85 -0
  356. package/src/catalog/contracts/sellable.ts +12 -0
  357. package/src/catalog/draftHandler.ts +105 -0
  358. package/src/catalog/draftPorts.ts +18 -0
  359. package/src/catalog/draftSchemas.ts +88 -0
  360. package/src/catalog/draftValidation.ts +100 -0
  361. package/src/catalog/foundationSchemas.ts +123 -0
  362. package/src/catalog/index.ts +43 -0
  363. package/src/catalog/mediaSourcePack.ts +219 -0
  364. package/src/catalog/productTypeSchemas.ts +70 -0
  365. package/src/catalog/publicationPorts.ts +44 -0
  366. package/src/catalog/readPorts.ts +61 -0
  367. package/src/catalog/referenceCatalogHandler.ts +39 -0
  368. package/src/catalog/resolvedSkuProjection.ts +76 -0
  369. package/src/catalog/sellableBundleHandler.ts +133 -0
  370. package/src/catalog/sellableSchemas.ts +33 -0
  371. package/src/catalog/sql.ts +14 -0
  372. package/src/catalog/validation.ts +3 -0
  373. package/src/communications/contracts/index.ts +1 -0
  374. package/src/communications/contracts/marketingSync.ts +109 -0
  375. package/src/communications/index.ts +1 -0
  376. package/src/communications/klaviyo/consentPush.ts +19 -0
  377. package/src/communications/klaviyo/index.ts +649 -0
  378. package/src/communications/resend/index.ts +350 -0
  379. package/src/composition/catalogContribution.ts +32 -0
  380. package/src/composition/index.ts +1 -0
  381. package/src/inventory/adminInventory.ts +251 -0
  382. package/src/inventory/contracts/forecast.ts +96 -0
  383. package/src/inventory/contracts/index.ts +18 -0
  384. package/src/inventory/contracts/inventory.ts +14 -0
  385. package/src/inventory/contracts/newPurchaseStock.ts +23 -0
  386. package/src/inventory/contracts/reservation.ts +20 -0
  387. package/src/inventory/forecast.ts +239 -0
  388. package/src/inventory/index.ts +21 -0
  389. package/src/inventory/inventoryHandlers.ts +215 -0
  390. package/src/inventory/newPurchaseStockAvailability.ts +120 -0
  391. package/src/inventory/ports.ts +46 -0
  392. package/src/inventory/renewalReservation.ts +88 -0
  393. package/src/inventory/reservationPolicy.ts +27 -0
  394. package/src/inventory/schemas.ts +128 -0
  395. package/src/inventory/sql.ts +5 -0
  396. package/src/inventory/subscriptionForecastCalculation.ts +157 -0
  397. package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
  398. package/src/pricing/acquisitionOfferPricing.ts +38 -0
  399. package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
  400. package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
  401. package/src/pricing/acquisitionQuoteBinding.ts +12 -0
  402. package/src/pricing/acquisitionReadiness.ts +148 -0
  403. package/src/pricing/acquisitionTerms.ts +125 -0
  404. package/src/pricing/acquisitionTermsSchemas.ts +52 -0
  405. package/src/pricing/catalogFactsProvenance.ts +102 -0
  406. package/src/pricing/catalogFactsSchemas.ts +69 -0
  407. package/src/pricing/commerceOfferPricing.ts +224 -0
  408. package/src/pricing/commerceOfferPricingCache.ts +53 -0
  409. package/src/pricing/commerceOfferPricingHandler.ts +137 -0
  410. package/src/pricing/commercePriceAuthority.ts +104 -0
  411. package/src/pricing/commercePricingPolicyErrors.ts +88 -0
  412. package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
  413. package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
  414. package/src/pricing/commerceQuoteHandler.ts +184 -0
  415. package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
  416. package/src/pricing/contracts/acquisitionTerms.ts +26 -0
  417. package/src/pricing/contracts/authority.ts +44 -0
  418. package/src/pricing/contracts/evidence.ts +25 -0
  419. package/src/pricing/contracts/index.ts +17 -0
  420. package/src/pricing/contracts/money.ts +32 -0
  421. package/src/pricing/contracts/offer.ts +42 -0
  422. package/src/pricing/contracts/offerPolicy.ts +17 -0
  423. package/src/pricing/contracts/policy.ts +26 -0
  424. package/src/pricing/contracts/provenance.ts +19 -0
  425. package/src/pricing/contracts/quote.ts +106 -0
  426. package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
  427. package/src/pricing/dbBackedCommerceQuotePort.ts +260 -0
  428. package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
  429. package/src/pricing/index.ts +52 -0
  430. package/src/pricing/offerPolicyReadiness.ts +110 -0
  431. package/src/pricing/offerPolicySchemas.ts +23 -0
  432. package/src/pricing/offerPricingSchemas.ts +92 -0
  433. package/src/pricing/ports.ts +17 -0
  434. package/src/pricing/pricingPolicyToken.ts +119 -0
  435. package/src/pricing/quoteAcquisitionTerms.ts +10 -0
  436. package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
  437. package/src/pricing/quotePorts.ts +58 -0
  438. package/src/pricing/quotePrimitives.ts +21 -0
  439. package/src/pricing/quotePromotionEvidence.ts +82 -0
  440. package/src/pricing/quoteSchemas.ts +230 -0
  441. package/src/pricing/subscriptionPricePolicy.ts +113 -0
  442. package/src/pricing/taxProfileGuard.ts +33 -0
  443. package/src/pricing/validation.ts +3 -0
  444. package/src/promotions/adminBindings.ts +53 -0
  445. package/src/promotions/adminPromotionCodesContracts.ts +244 -0
  446. package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
  447. package/src/promotions/adminPromotionCodesHandler.ts +254 -0
  448. package/src/promotions/adminPromotionsContracts.ts +121 -0
  449. package/src/promotions/adminPromotionsDataPort.ts +48 -0
  450. package/src/promotions/adminPromotionsHandler.ts +72 -0
  451. package/src/promotions/contracts/index.ts +387 -0
  452. package/src/promotions/index.ts +22 -0
  453. package/src/promotions/promoDataPort.ts +65 -0
  454. package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
  455. package/src/promotions/promotionBenefitDefinition.ts +48 -0
  456. package/src/promotions/promotionClaimSweepPort.ts +23 -0
  457. package/src/promotions/promotionClaims.ts +70 -0
  458. package/src/promotions/promotionCodePreview.ts +183 -0
  459. package/src/promotions/promotionCodeQuotePort.ts +35 -0
  460. package/src/promotions/promotionQuoteAcceptance.ts +284 -0
  461. package/src/promotions/promotionQuoteBinding.ts +158 -0
  462. package/src/promotions/quoteAutomaticPromotions.ts +58 -0
  463. package/src/promotions/quotePromoDiscounts.ts +185 -0
  464. package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
  465. package/src/promotions/quotePromotionV2.ts +223 -0
  466. package/src/promotions/sql.ts +5 -0
  467. package/src/promotions/validation.ts +3 -0
  468. package/src/shipping/addressCanonLookupHandler.ts +114 -0
  469. package/src/shipping/addressCanonical.ts +31 -0
  470. package/src/shipping/addressPorts.ts +48 -0
  471. package/src/shipping/addressSchemas.ts +28 -0
  472. package/src/shipping/adminShippingRateHandler.ts +72 -0
  473. package/src/shipping/contracts/address.ts +71 -0
  474. package/src/shipping/contracts/delivery.ts +114 -0
  475. package/src/shipping/contracts/index.ts +18 -0
  476. package/src/shipping/contracts/shippingRate.ts +7 -0
  477. package/src/shipping/customerAddressesHandler.ts +100 -0
  478. package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
  479. package/src/shipping/deliverySelectionHandler.ts +94 -0
  480. package/src/shipping/deliverySelectionPort.ts +10 -0
  481. package/src/shipping/deliverySelectionSummary.ts +56 -0
  482. package/src/shipping/handlerSchemas.ts +19 -0
  483. package/src/shipping/index.ts +23 -0
  484. package/src/shipping/pickupPointSearchPort.ts +8 -0
  485. package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
  486. package/src/shipping/shippingRatePorts.ts +36 -0
@@ -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,260 @@
1
+ import type { PricingResolverPort } from "@openlup/core/pricing";
2
+ import { buildLineBreakdown } from "@openlup/core/pricing";
3
+ import type { CreateQuoteResponse, PricingPolicySnapshot, CreateQuoteRequest, CommerceQuoteContext, CommerceQuoteLine, CommerceQuotePricingComponent, CommerceTaxProfile, CommerceQuoteDiscount, CommerceCodeRejection, CommerceCodeRejectionDetail, CartLineInput } from "./contracts/index.js";
4
+ import { COMMERCE_CONTRACT_VERSION, PROMOTION_ENGINE_V2 } from "./contracts/index.js";
5
+ import { CommerceQuoteError, type CreateQuoteOptions } from "./quotePorts.js";
6
+ import { splitIncludedVat } from "./quoteSchemas.js";
7
+ import type { CommercePriceAuthorityPort } from "./ports.js";
8
+ import { assertSubscriptionCheapest, buildSkuIndex, calculateIncludedVat, eligibleQtyForMode, mapPricingComponent, moneyIn, resolveOneTimeComparison, resolveStrictQuotePrice, sum, toGrossMinor } from "./dbBackedCommerceQuoteHelpers.js";
9
+ import { assertSupportedCommerceTaxProfile } from "./taxProfileGuard.js";
10
+ import type { CommerceQuoteCatalogItem } from "./commerceQuoteCatalogReadPort.js";
11
+ import { catalogFactsForQuoteLine, projectPublicQuoteSnapshot } from "./catalogFactsProvenance.js";
12
+
13
+ export interface QuotePromotionResult {
14
+ discounts: CommerceQuoteDiscount[];
15
+ codeRejections: CommerceCodeRejection[];
16
+ codeRejectionDetails: CommerceCodeRejectionDetail[];
17
+ }
18
+
19
+ export interface DbBackedCommerceQuotePortDeps<Request extends CreateQuoteRequest, Item extends CommerceQuoteCatalogItem, LineContext extends object, Context extends CommerceQuoteContext> {
20
+ quoteCatalogReadPort: { listQuoteCatalogItems(): Promise<Item[]> };
21
+ isStrictD1Quote: boolean;
22
+ commercePriceAuthorityPort?: CommercePriceAuthorityPort;
23
+ pricingResolverPort?: PricingResolverPort;
24
+ taxProfile: CommerceTaxProfile;
25
+ supportedTaxProfiles: readonly CommerceTaxProfile[];
26
+ regionCode: string;
27
+ currency: string;
28
+ now?: () => string;
29
+ shippingFlatMinor?: () => Promise<number>;
30
+ hasPromotionCodePort: boolean;
31
+ lineContext(line: CartLineInput, item: Item): LineContext;
32
+ quoteContext(request: Request, lines: Array<LineContext & { line: CommerceQuoteLine }>): () => Context;
33
+ resolveAutomaticQuotePromotions(input: {
34
+ request: Request; options?: CreateQuoteOptions; regionCode: string;
35
+ referenceProductMinor: number; subtotalGrossMinor: number; shippingGrossMinor: number; atTime: string;
36
+ }): Promise<Pick<QuotePromotionResult, "discounts" | "codeRejections">>;
37
+ applyPromotionCodesV2(input: {
38
+ preserveLegacyDiscountsWhenNoCandidates: boolean; clientId: string | null;
39
+ mode: Request["mode"]; promoCodes: string[]; referenceProductMinor: number;
40
+ subtotalGrossMinor: number; shippingGrossMinor: number;
41
+ discounts: CommerceQuoteDiscount[]; codeRejections: CommerceCodeRejection[]; atTime: string;
42
+ }): Promise<QuotePromotionResult>;
43
+ }
44
+
45
+ const STRICT_D1_COMMERCE_CHANNEL = "D2C";
46
+ export function createDbBackedCommerceQuotePort<
47
+ Request extends CreateQuoteRequest, Item extends CommerceQuoteCatalogItem,
48
+ LineContext extends object, Context extends CommerceQuoteContext,
49
+ >(deps: DbBackedCommerceQuotePortDeps<Request, Item, LineContext, Context>) {
50
+ const {
51
+ taxProfile, supportedTaxProfiles, regionCode, currency,
52
+ now = () => new Date().toISOString(),
53
+ quoteCatalogReadPort, isStrictD1Quote, pricingResolverPort,
54
+ commercePriceAuthorityPort, shippingFlatMinor, resolveAutomaticQuotePromotions,
55
+ applyPromotionCodesV2, hasPromotionCodePort, lineContext, quoteContext,
56
+ } = deps;
57
+ assertSupportedCommerceTaxProfile(taxProfile, supportedTaxProfiles);
58
+ const money = moneyIn(currency);
59
+ async function createServerAuthoritativeQuote(
60
+ request: Request,
61
+ options?: CreateQuoteOptions,
62
+ ): Promise<Omit<CreateQuoteResponse, "quote"> & { quote: Omit<CreateQuoteResponse["quote"], "context"> & { context: Context & { pricingPolicy?: PricingPolicySnapshot } } }> {
63
+ const atTime = now();
64
+ const skuIndex = await buildSkuIndex(quoteCatalogReadPort);
65
+ const pricedLines = await Promise.all(request.lines.map(async (requestLine) => {
66
+ const entry = skuIndex.get(requestLine.sku);
67
+ if (!entry || (requestLine.variantId && requestLine.variantId !== entry.item.variantId)) {
68
+ throw new CommerceQuoteError("UNKNOWN_SKU", "Unknown commerce SKU", {
69
+ sku: requestLine.sku,
70
+ variantId: requestLine.variantId ?? null,
71
+ });
72
+ }
73
+ const modeAtLine = requestLine.modeAtLine ?? request.mode;
74
+ if (!entry.item.sellability[modeAtLine === "one_time" ? "oneTime" : "subscription"]) {
75
+ throw new CommerceQuoteError("UNKNOWN_SKU", "Commerce SKU is not sellable in this purchase mode", {
76
+ sku: requestLine.sku,
77
+ variantId: entry.item.variantId,
78
+ mode: modeAtLine,
79
+ });
80
+ }
81
+ const eligibleCartQty = eligibleQtyForMode(request, modeAtLine);
82
+ const strictResolution = isStrictD1Quote
83
+ ? await resolveStrictQuotePrice({
84
+ commercePriceAuthorityPort: commercePriceAuthorityPort!,
85
+ variantId: entry.item.variantId,
86
+ mode: modeAtLine,
87
+ regionCode,
88
+ currency,
89
+ channel: STRICT_D1_COMMERCE_CHANNEL,
90
+ atTime,
91
+ })
92
+ : undefined;
93
+ const resolved = strictResolution?.resolved ?? await pricingResolverPort!.resolvePrice({
94
+ variantId: entry.item.variantId,
95
+ mode: modeAtLine,
96
+ lineQty: requestLine.quantity,
97
+ eligibleCartQty,
98
+ regionCode,
99
+ currency,
100
+ atTime,
101
+ });
102
+ if (!resolved) {
103
+ throw new CommerceQuoteError("PRICE_NOT_CONFIGURED", "Commerce price is not configured", {
104
+ sku: requestLine.sku,
105
+ variantId: entry.item.variantId,
106
+ mode: modeAtLine,
107
+ });
108
+ }
109
+ const oneTime = strictResolution?.base ?? await resolveOneTimeComparison({
110
+ pricingResolverPort: pricingResolverPort!,
111
+ variantId: entry.item.variantId,
112
+ lineQty: requestLine.quantity,
113
+ eligibleCartQty,
114
+ regionCode,
115
+ currency,
116
+ atTime,
117
+ });
118
+ const promotionReference = hasPromotionCodePort && request.promoCodes.length > 0
119
+ ? strictResolution?.base ?? await resolveOneTimeComparison({
120
+ pricingResolverPort: pricingResolverPort!,
121
+ variantId: entry.item.variantId,
122
+ lineQty: requestLine.quantity,
123
+ eligibleCartQty: 1,
124
+ regionCode,
125
+ currency,
126
+ atTime,
127
+ })
128
+ : oneTime;
129
+ assertSubscriptionCheapest({
130
+ requestSku: requestLine.sku,
131
+ modeAtLine,
132
+ resolved,
133
+ oneTime,
134
+ vatRateBps: taxProfile.vatRateBps,
135
+ });
136
+ const unitPriceGrossMinor = toGrossMinor(resolved, taxProfile.vatRateBps);
137
+ const baseUnitGrossMinor = oneTime ? toGrossMinor(oneTime, taxProfile.vatRateBps) : unitPriceGrossMinor;
138
+ const grossMinor = unitPriceGrossMinor * requestLine.quantity;
139
+ const tax = calculateIncludedVat(grossMinor, taxProfile.vatRateBps);
140
+ const pricingComponents = buildLineBreakdown({
141
+ variant_id: entry.item.variantId,
142
+ line_qty: requestLine.quantity,
143
+ base_unit_price_minor: baseUnitGrossMinor,
144
+ resolved_unit_price_minor: unitPriceGrossMinor,
145
+ matched_tier_min_qty: resolved.matchedMinQty,
146
+ mode_at_line: modeAtLine,
147
+ mode_resolved_via_fallback: resolved.mode === "any",
148
+ }).map((component) => mapPricingComponent(component, "line"));
149
+ const catalogFacts = isStrictD1Quote
150
+ ? catalogFactsForQuoteLine(true, entry.item, resolved, oneTime!, modeAtLine, atTime, currency, unitPriceGrossMinor, grossMinor, toGrossMinor(oneTime!, taxProfile.vatRateBps), strictResolution?.policy)
151
+ : undefined;
152
+ return {
153
+ line: {
154
+ sku: entry.item.skuCode,
155
+ productSlug: entry.item.productSlug,
156
+ quantity: requestLine.quantity,
157
+ unitPriceGross: money(unitPriceGrossMinor),
158
+ lineSubtotalGross: money(grossMinor),
159
+ tax: {
160
+ ...taxProfile,
161
+ netAmount: money(tax.netMinor),
162
+ vatAmount: money(tax.vatMinor),
163
+ grossAmount: money(grossMinor),
164
+ },
165
+ pricingComponents,
166
+ ...(catalogFacts ? { catalogFacts } : {}),
167
+ },
168
+ pricingComponents,
169
+ ...lineContext(requestLine, entry.item),
170
+ referenceGrossMinor: toGrossMinor(promotionReference ?? oneTime ?? resolved, taxProfile.vatRateBps)
171
+ * requestLine.quantity,
172
+ };
173
+ }));
174
+ const lines: CommerceQuoteLine[] = pricedLines.map((entry) => entry.line);
175
+ const quoteComponents: CommerceQuotePricingComponent[] = pricedLines.flatMap((entry) => entry.pricingComponents);
176
+ const context = quoteContext(request, pricedLines);
177
+ const subtotalGrossMinor = sum(lines.map((line) => line.lineSubtotalGross.amountMinor));
178
+ const referenceProductMinor = sum(pricedLines.map((entry) => entry.referenceGrossMinor));
179
+ const netTotalMinor = sum(lines.map((line) => line.tax.netAmount.amountMinor));
180
+ const taxTotalMinor = sum(lines.map((line) => line.tax.vatAmount.amountMinor));
181
+ // Shipping is a flat per-order gross from settings; absent settings port ⇒ no
182
+ // shipping (byte-identical to pre-shipping quotes). Resolved before promo eval so
183
+ // the free-shipping lane has a real amount to zero.
184
+ const shippingGrossMinor = shippingFlatMinor
185
+ ? await shippingFlatMinor()
186
+ : 0;
187
+ const automaticPromotionResult = await resolveAutomaticQuotePromotions({
188
+ request,
189
+ options,
190
+ regionCode,
191
+ referenceProductMinor,
192
+ subtotalGrossMinor,
193
+ shippingGrossMinor,
194
+ atTime,
195
+ });
196
+ const promotionResult = await applyPromotionCodesV2({
197
+ preserveLegacyDiscountsWhenNoCandidates: options?.pricingPolicy?.promotionEngineVersion !== PROMOTION_ENGINE_V2,
198
+ clientId: options?.clientId ?? null,
199
+ mode: request.mode,
200
+ promoCodes: request.promoCodes,
201
+ referenceProductMinor: sum(pricedLines.map((entry) => entry.referenceGrossMinor)),
202
+ subtotalGrossMinor,
203
+ shippingGrossMinor,
204
+ discounts: automaticPromotionResult.discounts,
205
+ codeRejections: automaticPromotionResult.codeRejections,
206
+ atTime,
207
+ });
208
+ const { discounts, codeRejections, codeRejectionDetails } = promotionResult;
209
+ // Two lanes: order_total/line discounts reduce the subtotal; a shipping discount
210
+ // (free-shipping promo) reduces the shipping gross. They are summed and validated
211
+ // independently by the quote schema.
212
+ const orderDiscountMinor = discounts
213
+ .filter((d) => d.appliesTo !== "shipping")
214
+ .reduce((total, d) => total + d.amountOffMinor, 0);
215
+ const shippingDiscountMinor = discounts
216
+ .filter((d) => d.appliesTo === "shipping")
217
+ .reduce((total, d) => total + d.amountOffMinor, 0);
218
+
219
+ const { netMinor: discountNetMinor, vatMinor: discountVatMinor } = splitIncludedVat(
220
+ orderDiscountMinor,
221
+ taxProfile.vatRateBps,
222
+ );
223
+ const ship = splitIncludedVat(shippingGrossMinor, taxProfile.vatRateBps);
224
+ const shipDiscount = splitIncludedVat(shippingDiscountMinor, taxProfile.vatRateBps);
225
+ const hasShipping = shippingFlatMinor != null;
226
+ const shippingFields = hasShipping
227
+ ? {
228
+ shippingGross: money(shippingGrossMinor),
229
+ shippingDiscountGross: money(shippingDiscountMinor),
230
+ }
231
+ : {};
232
+ return {
233
+ contractVersion: COMMERCE_CONTRACT_VERSION,
234
+ quote: {
235
+ currency,
236
+ taxIncluded: true,
237
+ lines,
238
+ discounts,
239
+ ...(codeRejections.length ? { codeRejections } : {}),
240
+ ...(codeRejectionDetails.length ? { codeRejectionDetails } : {}),
241
+ pricingComponents: quoteComponents,
242
+ context: {
243
+ ...context(),
244
+ ...(options?.pricingPolicy ? { pricingPolicy: options.pricingPolicy } : {}),
245
+ },
246
+ subtotalGross: money(subtotalGrossMinor),
247
+ discountTotalGross: money(orderDiscountMinor),
248
+ ...shippingFields,
249
+ totalGross: money(subtotalGrossMinor - orderDiscountMinor + shippingGrossMinor - shippingDiscountMinor),
250
+ netTotal: money(netTotalMinor - discountNetMinor + ship.netMinor - shipDiscount.netMinor),
251
+ taxTotal: money(taxTotalMinor - discountVatMinor + ship.vatMinor - shipDiscount.vatMinor),
252
+ },
253
+ };
254
+ }
255
+
256
+ return {
257
+ createQuote: async (request: Request, options?: CreateQuoteOptions) => projectPublicQuoteSnapshot(await createServerAuthoritativeQuote(request, options)),
258
+ createServerAuthoritativeQuote,
259
+ };
260
+ }
@@ -0,0 +1,49 @@
1
+ import type { z } from "zod";
2
+ import { isDeepStrictEqual } from "node:util";
3
+ import {
4
+ type CreateQuoteResponse,
5
+ } from "./contracts/quote.js";
6
+ import {
7
+ CommerceQuoteSnapshotError,
8
+ type CommerceQuotePort,
9
+ type CommerceQuoteSnapshotVerifierPort,
10
+ } from "./quotePorts.js";
11
+ import { projectPublicQuoteSnapshot } from "./catalogFactsProvenance.js";
12
+ import { createExpectedQuote } from "./commerceQuoteSnapshotExpect.js";
13
+
14
+ export function createDbBackedCommerceQuoteSnapshotVerifier<Request, Response extends CreateQuoteResponse>(
15
+ quotePort: CommerceQuotePort<Request, Response>,
16
+ quoteRequestFromSnapshot: (snapshot: Response) => Request,
17
+ createQuoteResponseSchema: z.ZodType<Response>,
18
+ ): CommerceQuoteSnapshotVerifierPort<Response> {
19
+ async function verifyAndEnrich(snapshot: Response): Promise<Response> {
20
+ const publicSnapshot = projectPublicQuoteSnapshot(snapshot);
21
+ const expected = await createExpectedQuote(
22
+ quotePort.createServerAuthoritativeQuote
23
+ ? { createQuote: quotePort.createServerAuthoritativeQuote.bind(quotePort) }
24
+ : quotePort,
25
+ quoteRequestFromSnapshot(publicSnapshot),
26
+ {
27
+ invalidResponseMessage: "Commerce DB-backed quote verifier returned invalid response",
28
+ catalogMismatchMessage:
29
+ "Commerce quote snapshot does not match current DB-backed catalog pricing",
30
+ },
31
+ createQuoteResponseSchema,
32
+ );
33
+ if (!isDeepStrictEqual(publicSnapshot, projectPublicQuoteSnapshot(expected))) {
34
+ throw new CommerceQuoteSnapshotError(
35
+ "QUOTE_SNAPSHOT_MISMATCH",
36
+ "Commerce quote snapshot does not match current DB-backed catalog pricing",
37
+ {
38
+ reason: "snapshot_does_not_match_db_backed_quote",
39
+ skus: publicSnapshot.quote.lines.map((line) => line.sku),
40
+ },
41
+ );
42
+ }
43
+ return expected;
44
+ }
45
+
46
+ return {
47
+ verifyQuoteSnapshot: verifyAndEnrich,
48
+ };
49
+ }