@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,87 @@
1
+ import { acquisitionQuoteBinding } from "../pricing/index.js";
2
+ /** The five sections the token hashes separately so a mismatch can name itself. */
3
+ export const PROMOTION_QUOTE_MONEY_SECTIONS = [
4
+ "lines",
5
+ "discounts",
6
+ "shipping",
7
+ "totals",
8
+ "currency",
9
+ ];
10
+ /** `other` is the honest answer for a token minted before section bindings existed. */
11
+ export const PROMOTION_QUOTE_MISMATCH_FIELDS = [
12
+ ...PROMOTION_QUOTE_MONEY_SECTIONS,
13
+ "other",
14
+ ];
15
+ /**
16
+ * Discount order is preserved: the promotion engine emits its lanes
17
+ * deterministically, so a reordering is a real evaluation difference.
18
+ */
19
+ export function projectPromotionQuoteMoney(quote, acquisitionTerms, shippingTerms) {
20
+ return {
21
+ ...acquisitionQuoteBinding(acquisitionTerms),
22
+ currency: quote.currency,
23
+ totalGross: quote.totalGross,
24
+ netTotal: quote.netTotal,
25
+ taxTotal: quote.taxTotal,
26
+ subtotalGross: quote.subtotalGross,
27
+ discountTotalGross: quote.discountTotalGross,
28
+ shippingGross: quote.shippingGross ?? null,
29
+ shippingDiscountGross: quote.shippingDiscountGross ?? null,
30
+ ...(shippingTerms ? { shippingTerms } : {}),
31
+ lines: quote.lines.map((line) => ({
32
+ sku: line.sku,
33
+ quantity: line.quantity,
34
+ unitPriceGross: line.unitPriceGross,
35
+ lineSubtotalGross: line.lineSubtotalGross,
36
+ tax: {
37
+ vatRateBps: line.tax.vatRateBps,
38
+ netAmount: line.tax.netAmount,
39
+ vatAmount: line.tax.vatAmount,
40
+ grossAmount: line.tax.grossAmount,
41
+ },
42
+ })),
43
+ discounts: quote.discounts.map((discount) => ({
44
+ promotionId: discount.promotionId,
45
+ appliesTo: discount.appliesTo,
46
+ amountOffMinor: discount.amountOffMinor,
47
+ promotionCodeId: discount.promotionCodeId ?? null,
48
+ promotionDefinitionFingerprint: discount.promotionDefinitionFingerprint ?? null,
49
+ promotionBenefitKind: discount.promotionBenefitKind ?? null,
50
+ promotionBenefitValueBps: discount.promotionBenefitValueBps ?? null,
51
+ promotionBenefitValueMinor: discount.promotionBenefitValueMinor ?? null,
52
+ })),
53
+ };
54
+ }
55
+ /** Same projection, partitioned. The union of these five sections is the whole projection. */
56
+ export function promotionQuoteMoneySections(quote, acquisitionTerms, shippingTerms) {
57
+ const money = projectPromotionQuoteMoney(quote, acquisitionTerms, shippingTerms);
58
+ return {
59
+ lines: money.lines,
60
+ discounts: money.discounts,
61
+ shipping: {
62
+ shippingGross: money.shippingGross,
63
+ shippingDiscountGross: money.shippingDiscountGross,
64
+ ...(money.shippingTerms ? { shippingTerms: money.shippingTerms } : {}),
65
+ },
66
+ totals: {
67
+ ...acquisitionQuoteBinding(money.acquisitionTerms),
68
+ totalGross: money.totalGross,
69
+ netTotal: money.netTotal,
70
+ taxTotal: money.taxTotal,
71
+ subtotalGross: money.subtotalGross,
72
+ discountTotalGross: money.discountTotalGross,
73
+ },
74
+ currency: money.currency,
75
+ };
76
+ }
77
+ /** Key-sorted, `undefined`-dropping JSON. Every acceptance binding hashes this shape. */
78
+ export function stableJson(value) {
79
+ if (Array.isArray(value))
80
+ return `[${value.map(stableJson).join(",")}]`;
81
+ if (value && typeof value === "object") {
82
+ return `{${Object.entries(value).filter(([, item]) => item !== undefined)
83
+ .sort(([left], [right]) => left.localeCompare(right))
84
+ .map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
85
+ }
86
+ return JSON.stringify(value);
87
+ }
@@ -0,0 +1,6 @@
1
+ import type { PromotionRow } from "@openlup/core/promo";
2
+ import type { AcquisitionFinancialPolicy } from "../pricing/index.js";
3
+ export interface QuoteAcquisitionBinding<Request = unknown> {
4
+ financialPolicy: AcquisitionFinancialPolicy<PromotionRow>;
5
+ capability(request: Request): unknown;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import type { QuoteAcquisitionBinding } from "./quoteAcquisitionBinding.js";
2
+ import type { CreateQuoteRequest } from "../pricing/index.js";
3
+ import type { CreateQuoteOptions } from "../pricing/index.js";
4
+ import type { CommercePromoDataPort } from "./promoDataPort.js";
5
+ import type { createQuotePromoDiscounts, QuoteDiscountResult } from "./quotePromoDiscounts.js";
6
+ import type { createQuotePromotionV2 } from "./quotePromotionV2.js";
7
+ export declare function createQuoteAutomaticPromotions<Request extends CreateQuoteRequest & {
8
+ visitorId?: string;
9
+ }>({ resolveEmailEligibility, evaluateOrderTotalDiscounts, evaluateAutomaticPromotionV2, legacyPromotionDataPort, acquisition }: {
10
+ acquisition?: QuoteAcquisitionBinding<Request>;
11
+ resolveEmailEligibility(request: Request): boolean;
12
+ evaluateOrderTotalDiscounts: ReturnType<typeof createQuotePromoDiscounts>["evaluateOrderTotalDiscounts"];
13
+ evaluateAutomaticPromotionV2: ReturnType<typeof createQuotePromotionV2>["evaluateAutomaticPromotionV2"];
14
+ legacyPromotionDataPort: ReturnType<typeof createQuotePromotionV2>["legacyPromotionDataPort"];
15
+ }): {
16
+ resolveAutomaticQuotePromotions: (input: {
17
+ promoDataPort?: CommercePromoDataPort;
18
+ request: Request;
19
+ options?: CreateQuoteOptions;
20
+ regionCode: string;
21
+ referenceProductMinor: number;
22
+ subtotalGrossMinor: number;
23
+ shippingGrossMinor: number;
24
+ atTime: string;
25
+ }) => Promise<QuoteDiscountResult>;
26
+ };
@@ -0,0 +1,60 @@
1
+ import { selectAcquisitionTerms, assertAcquisitionTermsCapability, ACQUISITION_TERMS_CAPABILITY } from "../pricing/index.js";
2
+ import { PROMOTION_ENGINE_V2 } from "../pricing/index.js";
3
+ export function createQuoteAutomaticPromotions({ resolveEmailEligibility, evaluateOrderTotalDiscounts, evaluateAutomaticPromotionV2, legacyPromotionDataPort, acquisition }) {
4
+ async function resolveAutomaticQuotePromotions(input) {
5
+ if (!input.promoDataPort)
6
+ return { discounts: [], codeRejections: [] };
7
+ let promoDataPort = input.promoDataPort;
8
+ let selection = null;
9
+ let acquisitionCapability;
10
+ if (acquisition) {
11
+ const clientId = input.options?.clientId ?? null;
12
+ const [paid, allRows] = await Promise.all([
13
+ clientId ? promoDataPort.countPaidOrdersByMode(clientId) : Promise.resolve({ oneTime: 0, subscription: 0 }),
14
+ promoDataPort.listActivePromotions(),
15
+ ]);
16
+ const existingSubscriber = input.request.mode === "subscription" && paid.subscription > 0;
17
+ const rows = existingSubscriber ? allRows.filter((row) => !acquisition.financialPolicy.facts(row).metadataPresent && row.eligibility.first_subscription_purchase !== true) : allRows;
18
+ promoDataPort = { ...promoDataPort, listActivePromotions: async () => rows, countPaidOrdersByMode: async () => paid };
19
+ selection = input.request.mode === "subscription" && !existingSubscriber
20
+ ? selectAcquisitionTerms(rows, input.atTime, input.regionCode, acquisition.financialPolicy) : null;
21
+ acquisitionCapability = acquisition.capability(input.request);
22
+ assertAcquisitionTermsCapability(selection, acquisitionCapability, input.options?.pricingPolicy?.promotionEngineVersion);
23
+ }
24
+ const emailEligibilityConfirmed = resolveEmailEligibility(input.request);
25
+ if (input.options?.pricingPolicy?.promotionEngineVersion === PROMOTION_ENGINE_V2) {
26
+ return evaluateAutomaticPromotionV2({
27
+ promoDataPort,
28
+ clientId: input.options.clientId ?? null,
29
+ visitorId: input.request.visitorId ?? null,
30
+ mode: input.request.mode,
31
+ promoCodes: input.request.promoCodes,
32
+ regionCode: input.regionCode,
33
+ referenceProductMinor: input.referenceProductMinor,
34
+ currentProductMinor: input.subtotalGrossMinor,
35
+ shippingMinor: input.shippingGrossMinor,
36
+ atTime: input.atTime,
37
+ emailEligibilityConfirmed,
38
+ ...(acquisition ? { acquisitionCapability } : {}),
39
+ });
40
+ }
41
+ const pending = evaluateOrderTotalDiscounts({
42
+ promoDataPort: legacyPromotionDataPort(promoDataPort),
43
+ clientId: input.options?.clientId ?? null,
44
+ visitorId: input.request.visitorId ?? null,
45
+ mode: input.request.mode,
46
+ promoCodes: input.request.promoCodes,
47
+ regionCode: input.regionCode,
48
+ subtotalGrossMinor: input.subtotalGrossMinor,
49
+ shippingGrossMinor: input.shippingGrossMinor,
50
+ atTime: input.atTime,
51
+ emailEligibilityConfirmed,
52
+ });
53
+ if (!acquisition)
54
+ return pending;
55
+ const result = await pending;
56
+ return selection && acquisitionCapability === ACQUISITION_TERMS_CAPABILITY
57
+ ? { ...result, acquisitionTerms: selection.terms } : result;
58
+ }
59
+ return { resolveAutomaticQuotePromotions };
60
+ }
@@ -0,0 +1,36 @@
1
+ import type { ResolvedAcquisitionTerms } from "../pricing/index.js";
2
+ import type { QuoteAcquisitionBinding } from "./quoteAcquisitionBinding.js";
3
+ import type { CreateQuoteRequest } from "../pricing/index.js";
4
+ import type { CommerceCodeRejection, CommerceQuoteDiscount } from "../pricing/index.js";
5
+ import type { PromotionRow } from "@openlup/core/promo";
6
+ import type { CommercePromoDataPort } from "./promoDataPort.js";
7
+ export interface QuoteDiscountResult {
8
+ acquisitionTerms?: ResolvedAcquisitionTerms;
9
+ /** Applied discounts: order_total (clamped to subtotal) + at most one shipping discount. */
10
+ discounts: CommerceQuoteDiscount[];
11
+ /** Submitted coupon codes that produced no discount, with the reason. */
12
+ codeRejections: CommerceCodeRejection[];
13
+ }
14
+ export declare function createQuotePromoDiscounts({ promotionCustomerSemantic, acquisition }: {
15
+ acquisition?: Pick<QuoteAcquisitionBinding, "financialPolicy">;
16
+ promotionCustomerSemantic: (row: PromotionRow) => CommerceQuoteDiscount["customerSemantic"];
17
+ }): {
18
+ evaluateOrderTotalDiscounts: (input: {
19
+ promoDataPort: CommercePromoDataPort;
20
+ clientId: string | null;
21
+ visitorId?: string | null;
22
+ mode: CreateQuoteRequest["mode"];
23
+ promoCodes: string[];
24
+ regionCode: string;
25
+ subtotalGrossMinor: number;
26
+ shippingGrossMinor?: number;
27
+ atTime: string;
28
+ /**
29
+ * True when a customer email drove eligibility resolution (the request carried
30
+ * `customerEligibilityContext.email`). Demotes the device-redemption guard to
31
+ * advisory so an email-eligible customer on a used device still gets the
32
+ * first-order price. See {@link PromoEvaluationCart.email_eligibility_confirmed}.
33
+ */
34
+ emailEligibilityConfirmed?: boolean;
35
+ }) => Promise<QuoteDiscountResult>;
36
+ };
@@ -0,0 +1,146 @@
1
+ import { filterAcquisitionPromotions } from "../pricing/index.js";
2
+ import { evaluatePromos, promoEligibilityFailure } from "@openlup/core/promo";
3
+ export function createQuotePromoDiscounts({ promotionCustomerSemantic, acquisition }) {
4
+ /**
5
+ * Evaluate active promotions for this cart and map them to quote discounts. Two lanes:
6
+ * `order_total` promos are clamped to the subtotal (so `discountTotal <= subtotal`), and
7
+ * a single `free_shipping` promo is emitted as an `appliesTo:"shipping"` discount clamped
8
+ * to `shippingGrossMinor` — it is NOT suppressed by an exclusive order-total promo and is
9
+ * NOT counted against the subtotal. Per-mode paid-order counts drive `first_subscription_purchase`
10
+ * and `first_onetime_purchase` independently; all counts are server-authoritative (resolved
11
+ * from the clientId, never client input), so eligibility cannot be forged.
12
+ */
13
+ async function evaluateOrderTotalDiscounts(input) {
14
+ if (input.subtotalGrossMinor <= 0)
15
+ return { discounts: [], codeRejections: [] };
16
+ const byMode = input.clientId
17
+ ? await input.promoDataPort.countPaidOrdersByMode(input.clientId)
18
+ : { oneTime: 0, subscription: 0 };
19
+ // Cap pre-filter: drop a promo that has reached its global or per-customer redemption
20
+ // limit BEFORE evaluation, so caps are enforced at quote time and a paid order is never
21
+ // failed on a cap. A per-customer cap can only be checked for an authenticated client.
22
+ const [allCandidates, redemptionCounts, deviceFirstOrderRedeemed] = await Promise.all([
23
+ input.promoDataPort.listActivePromotions(),
24
+ input.promoDataPort.redemptionCounts(input.clientId),
25
+ input.promoDataPort.deviceFirstOrderRedeemed(input.visitorId ?? null),
26
+ ]);
27
+ const customerCandidates = acquisition && input.mode === "subscription" && byMode.subscription > 0
28
+ ? allCandidates.filter((row) => !acquisition.financialPolicy.facts(row).metadataPresent && row.eligibility.first_subscription_purchase !== true)
29
+ : allCandidates;
30
+ const effectiveCandidates = acquisition && input.mode === "subscription"
31
+ ? filterAcquisitionPromotions(customerCandidates, input.atTime, input.regionCode, acquisition.financialPolicy) : customerCandidates;
32
+ const candidates = effectiveCandidates.filter((promo) => !capReached(promo, redemptionCounts.get(promo.id), input.clientId));
33
+ const cart = {
34
+ region_code: input.regionCode,
35
+ cart_mode: input.mode === "subscription" ? "subscription" : "one_time",
36
+ cart_subtotal_minor: input.subtotalGrossMinor,
37
+ shipping_amount_minor: input.shippingGrossMinor ?? 0,
38
+ client_orders_count: byMode.oneTime + byMode.subscription,
39
+ client_subscription_orders_count: byMode.subscription,
40
+ client_onetime_orders_count: byMode.oneTime,
41
+ device_first_order_redeemed: deviceFirstOrderRedeemed,
42
+ email_eligibility_confirmed: input.emailEligibilityConfirmed === true,
43
+ // The engine compares code literals. Preserve the customer's submitted spelling
44
+ // for the public rejection, but feed known legacy codes in their canonical
45
+ // spelling so eligibility and applied-discount matching are case-insensitive.
46
+ applied_codes: canonicalSubmittedCodes(input.promoCodes, allCandidates),
47
+ };
48
+ const applied = evaluatePromos({ cart, candidates, now: input.atTime });
49
+ const discounts = [];
50
+ let running = 0;
51
+ for (const promo of applied) {
52
+ const source = candidates.find((candidate) => candidate.id === promo.promotion_id);
53
+ const customerSemantic = source ? promotionCustomerSemantic(source) : undefined;
54
+ const isShipping = promo.discount_type === "free_shipping";
55
+ const cap = isShipping ? cart.shipping_amount_minor : input.subtotalGrossMinor - running;
56
+ if (cap <= 0) {
57
+ if (isShipping)
58
+ continue;
59
+ break;
60
+ }
61
+ const amountOffMinor = Math.min(promo.amount_off_minor, cap);
62
+ if (amountOffMinor <= 0)
63
+ continue;
64
+ if (!isShipping)
65
+ running += amountOffMinor;
66
+ discounts.push({
67
+ promotionId: promo.promotion_id,
68
+ ...(promo.code ? { code: promo.code } : {}),
69
+ // Human-facing label preserves the promotion name instead of its reason code.
70
+ ...(promo.name ? { label: promo.name } : {}),
71
+ appliesTo: isShipping ? "shipping" : "order_total",
72
+ amountOffMinor,
73
+ reasonCode: promo.reason_code,
74
+ ...(customerSemantic ? { customerSemantic } : {}),
75
+ });
76
+ }
77
+ return {
78
+ discounts,
79
+ codeRejections: classifyRejections(input, cart, allCandidates, redemptionCounts, discounts),
80
+ };
81
+ }
82
+ /**
83
+ * For each submitted coupon code that produced no discount, classify why. Uses the
84
+ * pre-cap candidate set so an exhausted per-customer cap reads as `already_used` rather
85
+ * than `not_recognized`. An eligible code that was merely outranked by a larger promo
86
+ * is a neutral `better_price_exists`: it was not applied because the customer already
87
+ * has an equally good or better automatic price. Applied shipping-code discounts count
88
+ * as applied too.
89
+ */
90
+ function classifyRejections(input, cart, allCandidates, redemptionCounts, discounts) {
91
+ const rejections = [];
92
+ for (const code of input.promoCodes) {
93
+ const promo = resolveSubmittedCoupon(code, allCandidates);
94
+ if (!promo) {
95
+ rejections.push({ code, reason: "not_recognized" });
96
+ continue;
97
+ }
98
+ // A legacy literal may be bound to more than one promotion/lane. A shipping
99
+ // benefit can legitimately apply even when the first matching product row
100
+ // fails scope, so the visible applied discount always wins over rejection.
101
+ if (discounts.some((discount) => sameCode(discount.code, code) || sameCode(discount.code, promo.code))) {
102
+ continue;
103
+ }
104
+ if (capReached(promo, redemptionCounts.get(promo.id), input.clientId)) {
105
+ rejections.push({ code, reason: "already_used" });
106
+ continue;
107
+ }
108
+ const failure = promoEligibilityFailure(promo, cart, input.atTime);
109
+ if (failure)
110
+ rejections.push({ code, reason: failure });
111
+ else if (!discounts.some((discount) => discount.code === promo.code)) {
112
+ rejections.push({ code, reason: "better_price_exists" });
113
+ }
114
+ }
115
+ return rejections;
116
+ }
117
+ function canonicalSubmittedCodes(codes, candidates) {
118
+ return codes.map((code) => resolveSubmittedCoupon(code, candidates)?.code ?? code);
119
+ }
120
+ /**
121
+ * Legacy permits normalized-code collisions. Preserve its exact-literal
122
+ * matching first; case-insensitive recovery is safe only for one identity.
123
+ */
124
+ function resolveSubmittedCoupon(code, candidates) {
125
+ const couponCandidates = candidates.filter((promo) => promo.trigger_type === "coupon_code" && promo.code !== null);
126
+ const exact = couponCandidates.find((promo) => promo.code === code);
127
+ if (exact)
128
+ return exact;
129
+ const normalized = couponCandidates.filter((promo) => sameCode(promo.code, code));
130
+ return normalized.length === 1 ? normalized[0] : undefined;
131
+ }
132
+ function sameCode(left, right) {
133
+ return typeof left === "string" && typeof right === "string" &&
134
+ left.trim().toUpperCase() === right.trim().toUpperCase();
135
+ }
136
+ function capReached(promo, counts, clientId) {
137
+ const { global, perCustomer } = counts ?? { global: 0, perCustomer: 0 };
138
+ if (promo.redemption_limit_global != null && global >= promo.redemption_limit_global)
139
+ return true;
140
+ if (clientId && promo.redemption_limit_per_customer != null && perCustomer >= promo.redemption_limit_per_customer) {
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+ return { evaluateOrderTotalDiscounts };
146
+ }
@@ -0,0 +1,27 @@
1
+ import type { PromotionCodeRejectionReason } from "@openlup/core/promo";
2
+ import type { CommerceCodeRejection, CommerceCodeRejectionDetail, CommerceCodeRejectionReason, CommerceQuoteDiscount } from "../pricing/index.js";
3
+ import type { PromotionCodeQuotePort } from "./promotionCodeQuotePort.js";
4
+ export interface ApplyPromotionCodesV2Input {
5
+ port?: PromotionCodeQuotePort;
6
+ preserveLegacyDiscountsWhenNoCandidates?: boolean;
7
+ clientId: string | null;
8
+ mode: "one_time" | "subscription";
9
+ promoCodes: readonly string[];
10
+ referenceProductMinor: number;
11
+ subtotalGrossMinor: number;
12
+ shippingGrossMinor: number;
13
+ discounts: CommerceQuoteDiscount[];
14
+ codeRejections: CommerceCodeRejection[];
15
+ codeRejectionDetails?: CommerceCodeRejectionDetail[];
16
+ atTime: string;
17
+ }
18
+ export declare function createQuotePromotionCodeAdjustments({ minimumProductPayableMinor: MINIMUM_PRODUCT_PAYABLE_MINOR }: {
19
+ minimumProductPayableMinor: number;
20
+ }): {
21
+ applyPromotionCodesV2: (input: ApplyPromotionCodesV2Input) => Promise<{
22
+ discounts: CommerceQuoteDiscount[];
23
+ codeRejections: CommerceCodeRejection[];
24
+ codeRejectionDetails: CommerceCodeRejectionDetail[];
25
+ }>;
26
+ mapEngineRejectionReason: (reason: PromotionCodeRejectionReason) => CommerceCodeRejectionReason;
27
+ };
@@ -0,0 +1,223 @@
1
+ import { evaluatePromotionAdjustmentsV2 } from "@openlup/core/promo";
2
+ export function createQuotePromotionCodeAdjustments({ minimumProductPayableMinor: MINIMUM_PRODUCT_PAYABLE_MINOR }) {
3
+ async function applyPromotionCodesV2(input) {
4
+ const submittedV2Codes = firstSubmittedCodes(input.promoCodes);
5
+ if (!input.port || submittedV2Codes.length === 0) {
6
+ return {
7
+ discounts: input.discounts,
8
+ codeRejections: input.codeRejections,
9
+ codeRejectionDetails: input.codeRejectionDetails ?? [],
10
+ };
11
+ }
12
+ const firstSubmittedByIdentity = new Map(submittedV2Codes.map((code) => [normalize(code), code]));
13
+ const resolution = await input.port.resolve({
14
+ codes: submittedV2Codes,
15
+ clientId: input.clientId,
16
+ purchaseScope: input.mode === "subscription" ? "subscription_initial" : "one_time",
17
+ referenceProductMinor: input.referenceProductMinor,
18
+ atTime: input.atTime,
19
+ });
20
+ if (resolution.candidates.length === 0) {
21
+ const discounts = input.preserveLegacyDiscountsWhenNoCandidates
22
+ ? input.discounts
23
+ : reduceProvisionalLegacyDiscounts(input.discounts, firstSubmittedByIdentity);
24
+ const appliedLegacyCodes = new Set(discounts.flatMap((discount) => discount.code ? [normalize(discount.code)] : []));
25
+ const codeRejections = dedupeRejections([
26
+ ...input.codeRejections,
27
+ ...resolution.codeRejections,
28
+ ], firstSubmittedByIdentity, input.codeRejections.length).filter((rejection) => !appliedLegacyCodes.has(normalize(rejection.code)));
29
+ return {
30
+ discounts: input.preserveLegacyDiscountsWhenNoCandidates
31
+ ? discounts
32
+ : orderSubmittedCodeSlots(projectSubmittedCodeSpelling(discounts, firstSubmittedByIdentity), firstSubmittedByIdentity),
33
+ codeRejections,
34
+ codeRejectionDetails: dedupeRejectionDetails([
35
+ ...selectedRejectionDetails(input.codeRejectionDetails ?? [], input.codeRejections, codeRejections),
36
+ ...selectedRejectionDetails(resolution.codeRejectionDetails, resolution.codeRejections, codeRejections),
37
+ ], firstSubmittedByIdentity),
38
+ };
39
+ }
40
+ const provisionalLegacyDiscounts = reduceProvisionalLegacyDiscounts(input.discounts, firstSubmittedByIdentity);
41
+ const appliedLegacyCodes = new Set(provisionalLegacyDiscounts.flatMap((discount) => discount.code ? [normalize(discount.code)] : []));
42
+ const result = evaluatePromotionAdjustmentsV2(adjustmentContext(input, provisionalLegacyDiscounts), resolution.candidates);
43
+ const byAdjustmentIdentity = new Map(resolution.candidates.map((candidate) => [
44
+ `${candidate.promotionId}:${candidate.codeId}`,
45
+ candidate,
46
+ ]));
47
+ const replacedLegacyLanes = new Set();
48
+ const effectiveAdjustments = result.adjustments.flatMap((adjustment) => {
49
+ const candidate = adjustment.codeId
50
+ ? byAdjustmentIdentity.get(`${adjustment.promotionId}:${adjustment.codeId}`)
51
+ : undefined;
52
+ if (!candidate || candidate.source !== "code" || !candidate.code) {
53
+ throw new Error("promotion_code_quote_candidate_identity_missing");
54
+ }
55
+ const appliesTo = adjustment.lane === "shipping" ? "shipping" : "order_total";
56
+ const identity = normalize(candidate.code);
57
+ const matchingLegacy = provisionalLegacyDiscounts.filter((discount) => discount.appliesTo === appliesTo && discount.code && normalize(discount.code) === identity);
58
+ if (matchingLegacy.length === 0)
59
+ return [{ adjustment, candidate }];
60
+ // Re-score without the matching provisional legacy identity and retain the
61
+ // single adjustment that gives the customer the lower lane payable.
62
+ const withoutMatchingLegacy = provisionalLegacyDiscounts.filter((discount) => !(discount.appliesTo === appliesTo && discount.code && normalize(discount.code) === identity));
63
+ const replacement = evaluatePromotionAdjustmentsV2(adjustmentContext(input, withoutMatchingLegacy), [candidate]).adjustments[0];
64
+ const legacyAmount = matchingLegacy.reduce((sum, discount) => sum + discount.amountOffMinor, 0);
65
+ if (!replacement || replacement.amountOffMinor < legacyAmount)
66
+ return [];
67
+ replacedLegacyLanes.add(`${identity}:${appliesTo}`);
68
+ return [{ adjustment: replacement, candidate }];
69
+ });
70
+ const retainedDiscounts = projectSubmittedCodeSpelling(provisionalLegacyDiscounts.filter((discount) => !(discount.code && replacedLegacyLanes.has(`${normalize(discount.code)}:${discount.appliesTo}`))), firstSubmittedByIdentity);
71
+ const v2Discounts = effectiveAdjustments.map(({ adjustment, candidate }) => ({
72
+ promotionId: adjustment.promotionId,
73
+ code: firstSubmittedByIdentity.get(normalize(candidate.code)) ?? candidate.code,
74
+ label: adjustment.name,
75
+ appliesTo: adjustment.lane === "shipping" ? "shipping" : "order_total",
76
+ amountOffMinor: adjustment.amountOffMinor,
77
+ reasonCode: "promotion_code_v2",
78
+ promotionEngineVersion: "promotion-engine.v2",
79
+ promotionCodeId: candidate.codeId,
80
+ promotionCodeRevision: candidate.codeRevision,
81
+ promotionDefinitionFingerprint: candidate.definitionFingerprint,
82
+ promotionCodeScopes: [...candidate.scopes],
83
+ promotionMinimumReferenceMinor: candidate.minimumReferenceMinor,
84
+ promotionCodeValidTo: candidate.validTo,
85
+ promotionBenefitKind: candidate.kind,
86
+ promotionBenefitValueBps: candidate.valueBps,
87
+ promotionBenefitValueMinor: candidate.valueMinor,
88
+ floorApplied: adjustment.floorApplied,
89
+ }));
90
+ const appliedV2Codes = new Set(v2Discounts.flatMap((discount) => discount.code ? [normalize(discount.code)] : []));
91
+ const engineRejections = result.rejectedCodes.flatMap((rejection) => rejection.code && !appliedLegacyCodes.has(normalize(rejection.code))
92
+ ? [{
93
+ code: firstSubmittedByIdentity.get(normalize(rejection.code)) ?? rejection.code,
94
+ reason: mapEngineRejectionReason(rejection.reason),
95
+ }]
96
+ : []);
97
+ const appliedCodes = new Set([...retainedDiscounts.flatMap((discount) => discount.code ? [normalize(discount.code)] : []), ...appliedV2Codes]);
98
+ const mergedRejections = dedupeRejections([
99
+ ...input.codeRejections,
100
+ ...resolution.codeRejections,
101
+ ...engineRejections,
102
+ ], firstSubmittedByIdentity, input.codeRejections.length).filter((rejection) => !appliedCodes.has(normalize(rejection.code)));
103
+ const codeRejectionDetails = dedupeRejectionDetails([
104
+ ...selectedRejectionDetails(input.codeRejectionDetails ?? [], input.codeRejections, mergedRejections),
105
+ ...selectedRejectionDetails(resolution.codeRejectionDetails, resolution.codeRejections, mergedRejections),
106
+ ], firstSubmittedByIdentity);
107
+ return {
108
+ discounts: orderSubmittedCodeSlots([...retainedDiscounts, ...v2Discounts], firstSubmittedByIdentity),
109
+ codeRejections: mergedRejections,
110
+ codeRejectionDetails,
111
+ };
112
+ }
113
+ function adjustmentContext(input, discounts) {
114
+ return {
115
+ purchaseScope: input.mode === "subscription" ? "subscription_initial" : "one_time",
116
+ referenceProductMinor: input.referenceProductMinor,
117
+ currentProductMinor: input.subtotalGrossMinor - sumLane(discounts, false),
118
+ shippingMinor: input.shippingGrossMinor - sumLane(discounts, true),
119
+ minimumProductPayableMinor: MINIMUM_PRODUCT_PAYABLE_MINOR,
120
+ };
121
+ }
122
+ function sumLane(discounts, shipping) {
123
+ return discounts
124
+ .filter((discount) => (discount.appliesTo === "shipping") === shipping)
125
+ .reduce((sum, discount) => sum + discount.amountOffMinor, 0);
126
+ }
127
+ function dedupeRejections(rejections, firstSubmittedByIdentity = new Map(), authoritativeFrom = rejections.length) {
128
+ const byCode = new Map();
129
+ for (const [index, rejection] of rejections.entries()) {
130
+ const identity = normalize(rejection.code);
131
+ const current = byCode.get(identity);
132
+ if (!current || (index >= authoritativeFrom && current.reason === "not_recognized")) {
133
+ byCode.set(identity, { ...rejection, code: firstSubmittedByIdentity.get(identity) ?? rejection.code });
134
+ }
135
+ }
136
+ return orderSubmittedCodeSlots([...byCode.values()], firstSubmittedByIdentity);
137
+ }
138
+ function dedupeRejectionDetails(details, firstSubmittedByIdentity = new Map()) {
139
+ const byCode = new Map();
140
+ for (const detail of details) {
141
+ const identity = normalize(detail.code);
142
+ if (!byCode.has(identity)) {
143
+ byCode.set(identity, { ...detail, code: firstSubmittedByIdentity.get(identity) ?? detail.code });
144
+ }
145
+ }
146
+ return orderSubmittedCodeSlots([...byCode.values()], firstSubmittedByIdentity);
147
+ }
148
+ function selectedRejectionDetails(details, sourceRejections, selectedRejections) {
149
+ const selected = new Map(selectedRejections.map((rejection) => [normalize(rejection.code), rejection.reason]));
150
+ const matchingSources = new Set(sourceRejections.flatMap((rejection) => selected.get(normalize(rejection.code)) === rejection.reason ? [normalize(rejection.code)] : []));
151
+ return details.filter((detail) => matchingSources.has(normalize(detail.code)));
152
+ }
153
+ function firstSubmittedCodes(codes) {
154
+ const seen = new Set();
155
+ const first = [];
156
+ for (const code of codes) {
157
+ const identity = normalize(code);
158
+ if (!identity || seen.has(identity))
159
+ continue;
160
+ seen.add(identity);
161
+ first.push(code);
162
+ }
163
+ return first;
164
+ }
165
+ /** Collapse legacy exact-literal collisions to one submitted identity per lane. */
166
+ function reduceProvisionalLegacyDiscounts(discounts, firstSubmittedByIdentity) {
167
+ const chosen = new Map();
168
+ for (const [index, discount] of discounts.entries()) {
169
+ if (!discount.code)
170
+ continue;
171
+ const identity = normalize(discount.code);
172
+ const firstSubmitted = firstSubmittedByIdentity.get(identity);
173
+ if (!firstSubmitted)
174
+ continue;
175
+ const key = `${identity}:${discount.appliesTo}`;
176
+ const exactFirstSpelling = discount.code === firstSubmitted;
177
+ const current = chosen.get(key);
178
+ if (!current || (exactFirstSpelling && !current.exactFirstSpelling)) {
179
+ chosen.set(key, { index, exactFirstSpelling });
180
+ }
181
+ }
182
+ return discounts.filter((discount, index) => {
183
+ if (!discount.code)
184
+ return true;
185
+ const identity = normalize(discount.code);
186
+ if (!firstSubmittedByIdentity.has(identity))
187
+ return true;
188
+ return chosen.get(`${identity}:${discount.appliesTo}`)?.index === index;
189
+ });
190
+ }
191
+ function projectSubmittedCodeSpelling(discounts, firstSubmittedByIdentity) {
192
+ return discounts.map((discount) => {
193
+ if (!discount.code)
194
+ return discount;
195
+ const code = firstSubmittedByIdentity.get(normalize(discount.code));
196
+ return code ? { ...discount, code } : discount;
197
+ });
198
+ }
199
+ function submittedRank(code, firstSubmittedByIdentity) {
200
+ const rank = [...firstSubmittedByIdentity.keys()].indexOf(normalize(code));
201
+ return rank < 0 ? Number.MAX_SAFE_INTEGER : rank;
202
+ }
203
+ function orderSubmittedCodeSlots(values, firstSubmittedByIdentity) {
204
+ const ordered = values.map((value, index) => ({ value, index })).filter(({ value }) => value.code && submittedRank(value.code, firstSubmittedByIdentity) < Number.MAX_SAFE_INTEGER)
205
+ .sort((left, right) => submittedRank(left.value.code, firstSubmittedByIdentity) - submittedRank(right.value.code, firstSubmittedByIdentity) || left.index - right.index).map(({ value }) => value);
206
+ let index = 0;
207
+ return values.map((value) => value.code && submittedRank(value.code, firstSubmittedByIdentity) < Number.MAX_SAFE_INTEGER ? ordered[index++] : value);
208
+ }
209
+ function normalize(code) {
210
+ return code.trim().toUpperCase();
211
+ }
212
+ function mapEngineRejectionReason(reason) {
213
+ switch (reason) {
214
+ case "better_price_exists":
215
+ return "better_price_exists";
216
+ case "scope_not_applicable":
217
+ return "scope_not_applicable";
218
+ default:
219
+ return "not_eligible";
220
+ }
221
+ }
222
+ return { applyPromotionCodesV2, mapEngineRejectionReason };
223
+ }
@@ -0,0 +1,30 @@
1
+ import type { QuoteAcquisitionBinding } from "./quoteAcquisitionBinding.js";
2
+ import type { QuoteDiscountResult } from "./quotePromoDiscounts.js";
3
+ import type { PromotionRow } from "@openlup/core/promo";
4
+ import type { CommerceQuoteDiscount } from "../pricing/index.js";
5
+ import type { CreateQuoteRequest } from "../pricing/index.js";
6
+ import type { CommercePromoDataPort } from "./promoDataPort.js";
7
+ import type { createQuotePromoDiscounts } from "./quotePromoDiscounts.js";
8
+ export declare function createQuotePromotionV2({ promotionCustomerSemantic, minimumProductPayableMinor: MINIMUM_PRODUCT_PAYABLE_MINOR, evaluateOrderTotalDiscounts, acquisition }: {
9
+ acquisition?: QuoteAcquisitionBinding<CreateQuoteRequest>;
10
+ promotionCustomerSemantic: (row: PromotionRow) => CommerceQuoteDiscount["customerSemantic"];
11
+ minimumProductPayableMinor: number;
12
+ evaluateOrderTotalDiscounts: ReturnType<typeof createQuotePromoDiscounts>["evaluateOrderTotalDiscounts"];
13
+ }): {
14
+ evaluateAutomaticPromotionV2: (input: {
15
+ promoDataPort: CommercePromoDataPort;
16
+ clientId: string | null;
17
+ visitorId?: string | null;
18
+ mode: CreateQuoteRequest["mode"];
19
+ /** Raw request spellings stay intact for legacy exact-literal evaluation. */
20
+ promoCodes?: readonly string[];
21
+ regionCode: string;
22
+ referenceProductMinor: number;
23
+ currentProductMinor: number;
24
+ shippingMinor: number;
25
+ atTime: string;
26
+ emailEligibilityConfirmed?: boolean;
27
+ acquisitionCapability?: unknown;
28
+ }) => Promise<QuoteDiscountResult>;
29
+ legacyPromotionDataPort: (port: CommercePromoDataPort) => CommercePromoDataPort;
30
+ };