@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,230 @@
1
+ import { z } from "zod";
2
+ import type { CommerceQuote } from "./contracts/quote.js";
3
+ import { COMMERCE_CONTRACT_VERSION } from "./contracts/quote.js";
4
+ import { createCommerceQuotePrimitives } from "./quotePrimitives.js";
5
+ import { catalogFactsProvenanceSchema, rejectCatalogFactsFromPublicQuote } from "./catalogFactsSchemas.js";
6
+ import { quoteDiscountSchema } from "./quotePromotionEvidence.js";
7
+ import { quoteCodeRejectionDetailSchema } from "./quoteCodeRejectionDetails.js";
8
+
9
+ /** Split gross tax-included money into net/VAT using an explicit tax rate. */
10
+ export function splitIncludedVat(grossMinor: number, vatRateBps: number): { netMinor: number; vatMinor: number } {
11
+ const netMinor = Math.round((grossMinor * 10_000) / (10_000 + vatRateBps));
12
+ const vatMinor = grossMinor - netMinor;
13
+ return { netMinor, vatMinor };
14
+ }
15
+
16
+ /** Backstop: every Money in a quote must use the quote's own currency. */
17
+ export function quoteMoneyCurrenciesMatch(quote: CommerceQuote): boolean {
18
+ const monies = [
19
+ quote.subtotalGross,
20
+ quote.discountTotalGross,
21
+ ...(quote.shippingGross ? [quote.shippingGross] : []),
22
+ ...(quote.shippingDiscountGross ? [quote.shippingDiscountGross] : []),
23
+ quote.totalGross,
24
+ quote.netTotal,
25
+ quote.taxTotal,
26
+ ...quote.lines.flatMap((line) => [
27
+ line.unitPriceGross,
28
+ line.lineSubtotalGross,
29
+ line.tax.netAmount,
30
+ line.tax.vatAmount,
31
+ line.tax.grossAmount,
32
+ ]),
33
+ ];
34
+ return monies.every((money) => money.currency === quote.currency);
35
+ }
36
+
37
+ /** Preserve quote validation while hosts bind currency, product identifiers and context. */
38
+ export function createCommerceQuoteSchemas<Context extends z.ZodType>(input: {
39
+ currencySchema: z.ZodType<string>;
40
+ productSlugSchema: z.ZodType<string>;
41
+ contextSchema: Context;
42
+ }) {
43
+ const { commerceCurrencySchema, commerceMoneySchema, commerceSkuSchema, commerceTaxCategorySchema,
44
+ commerceTaxCountrySchema, commerceTaxLegalBasisSchema } = createCommerceQuotePrimitives(input.currencySchema);
45
+ const commerceProductSlugSchema = input.productSlugSchema;
46
+ const quoteContextSchema = input.contextSchema;
47
+ const quotePricingComponentSchema = z
48
+ .object({
49
+ scope: z.enum(["line", "order"]),
50
+ componentType: z.enum(["base_unit", "mode_discount", "qty_tier", "promo", "loyalty", "shipping", "bundle"]),
51
+ amountMinor: z.number().int(),
52
+ reasonCode: z.string().trim().min(1).max(120),
53
+ reasonPayload: z.record(z.string(), z.unknown()).default({}),
54
+ })
55
+ .strict();
56
+ const cartLineInputSchema = z.object({
57
+ sku: commerceSkuSchema,
58
+ quantity: z.number().int().min(1).max(99),
59
+ variantId: z.string().trim().min(1).max(120).optional(),
60
+ modeAtLine: z.enum(["one_time", "subscription"]).optional(),
61
+ isAddon: z.boolean().optional(),
62
+ });
63
+ const commerceTaxBreakdownSchema = z
64
+ .object({
65
+ included: z.literal(true),
66
+ country: commerceTaxCountrySchema,
67
+ category: commerceTaxCategorySchema,
68
+ vatRateBps: z.number().int().min(0).max(10_000),
69
+ legalBasis: commerceTaxLegalBasisSchema,
70
+ netAmount: commerceMoneySchema,
71
+ vatAmount: commerceMoneySchema,
72
+ grossAmount: commerceMoneySchema,
73
+ })
74
+ .refine(
75
+ (tax) =>
76
+ tax.netAmount.currency === tax.vatAmount.currency &&
77
+ tax.netAmount.currency === tax.grossAmount.currency &&
78
+ tax.netAmount.amountMinor + tax.vatAmount.amountMinor === tax.grossAmount.amountMinor,
79
+ {
80
+ message: "tax net and VAT amounts must add up to gross amount",
81
+ path: ["grossAmount"],
82
+ },
83
+ );
84
+
85
+ const quoteLineSchema = z
86
+ .object({
87
+ sku: commerceSkuSchema,
88
+ productSlug: commerceProductSlugSchema,
89
+ quantity: z.number().int().min(1).max(99),
90
+ unitPriceGross: commerceMoneySchema,
91
+ lineSubtotalGross: commerceMoneySchema,
92
+ tax: commerceTaxBreakdownSchema,
93
+ pricingComponents: z.array(quotePricingComponentSchema).optional(),
94
+ catalogFacts: catalogFactsProvenanceSchema.optional(),
95
+ })
96
+ .refine(
97
+ (line) =>
98
+ line.unitPriceGross.currency === line.lineSubtotalGross.currency &&
99
+ line.lineSubtotalGross.currency === line.tax.grossAmount.currency &&
100
+ line.unitPriceGross.amountMinor * line.quantity === line.lineSubtotalGross.amountMinor,
101
+ {
102
+ message: "quote line subtotal must match unit price and quantity",
103
+ path: ["lineSubtotalGross"],
104
+ },
105
+ );
106
+
107
+ const quoteCodeRejectionSchema = z
108
+ .object({
109
+ code: z.string().trim().min(1).max(80),
110
+ reason: z.enum([
111
+ "not_recognized",
112
+ "already_used",
113
+ "not_eligible",
114
+ "expired",
115
+ "better_price_exists",
116
+ "scope_not_applicable",
117
+ ]),
118
+ })
119
+ .strict();
120
+
121
+ const commerceQuoteSchema = z
122
+ .object({
123
+ currency: commerceCurrencySchema,
124
+ taxIncluded: z.literal(true),
125
+ lines: z.array(quoteLineSchema).min(1).max(50),
126
+ discounts: z.array(quoteDiscountSchema).max(20),
127
+ codeRejections: z.array(quoteCodeRejectionSchema).max(20).optional(),
128
+ codeRejectionDetails: z.array(quoteCodeRejectionDetailSchema).max(20).optional(),
129
+ pricingComponents: z.array(quotePricingComponentSchema).optional(),
130
+ context: quoteContextSchema.optional(),
131
+ subtotalGross: commerceMoneySchema,
132
+ discountTotalGross: commerceMoneySchema,
133
+ // Shipping/discount tax split uses one quote rate; mixed-tax allocation is out of scope.
134
+ shippingGross: commerceMoneySchema.optional(),
135
+ shippingDiscountGross: commerceMoneySchema.optional(),
136
+ totalGross: commerceMoneySchema,
137
+ netTotal: commerceMoneySchema,
138
+ taxTotal: commerceMoneySchema,
139
+ })
140
+ .refine((quote) => quote.lines.every((line) => line.tax.vatRateBps === quote.lines[0]?.tax.vatRateBps), {
141
+ message: "mixed VAT rates are not supported by quote discount and shipping tax allocation",
142
+ path: ["lines"],
143
+ })
144
+ .refine(
145
+ (quote) => {
146
+ const subtotal = sumAmounts(quote.lines.map((line) => line.lineSubtotalGross));
147
+ const lineNet = sumAmounts(quote.lines.map((line) => line.tax.netAmount));
148
+ const lineVat = sumAmounts(quote.lines.map((line) => line.tax.vatAmount));
149
+ const vatRateBps = quote.lines[0]?.tax.vatRateBps ?? 0;
150
+ const singleTaxRate = quote.lines.every((line) => line.tax.vatRateBps === vatRateBps);
151
+ const discountTotal = quote.discountTotalGross.amountMinor;
152
+ const orderDiscountSum = quote.discounts
153
+ .filter((discount) => discount.appliesTo !== "shipping")
154
+ .reduce((sum, discount) => sum + discount.amountOffMinor, 0);
155
+ const shippingDiscountSum = quote.discounts
156
+ .filter((discount) => discount.appliesTo === "shipping")
157
+ .reduce((sum, discount) => sum + discount.amountOffMinor, 0);
158
+ const shippingGross = quote.shippingGross?.amountMinor ?? 0;
159
+ const shippingDiscount = quote.shippingDiscountGross?.amountMinor ?? 0;
160
+ const { netMinor: discountNet, vatMinor: discountVat } = splitIncludedVat(discountTotal, vatRateBps);
161
+ const { netMinor: shipNet, vatMinor: shipVat } = splitIncludedVat(shippingGross, vatRateBps);
162
+ const { netMinor: shipDiscNet, vatMinor: shipDiscVat } = splitIncludedVat(shippingDiscount, vatRateBps);
163
+
164
+ return (
165
+ singleTaxRate &&
166
+ quote.subtotalGross.amountMinor === subtotal &&
167
+ discountTotal >= 0 &&
168
+ discountTotal <= subtotal &&
169
+ orderDiscountSum === discountTotal &&
170
+ shippingGross >= 0 &&
171
+ shippingDiscount >= 0 &&
172
+ shippingDiscount <= shippingGross &&
173
+ shippingDiscountSum === shippingDiscount &&
174
+ quote.totalGross.amountMinor === subtotal - discountTotal + shippingGross - shippingDiscount &&
175
+ quote.netTotal.amountMinor === lineNet - discountNet + shipNet - shipDiscNet &&
176
+ quote.taxTotal.amountMinor === lineVat - discountVat + shipVat - shipDiscVat &&
177
+ quote.netTotal.amountMinor + quote.taxTotal.amountMinor === quote.totalGross.amountMinor
178
+ );
179
+ },
180
+ {
181
+ message: "quote totals must match quote lines, discounts and shipping",
182
+ path: ["totalGross"],
183
+ },
184
+ )
185
+ // The currency backstop reads the validated money fields.
186
+ .refine((quote) => quoteMoneyCurrenciesMatch(quote as CommerceQuote), {
187
+ message: "all quote money amounts must use the quote currency",
188
+ path: ["currency"],
189
+ });
190
+
191
+ const createQuoteRequestSchema = z.object({
192
+ mode: z.enum(["one_time", "subscription"]).default("one_time"),
193
+ lines: z.array(cartLineInputSchema).min(1).max(50),
194
+ cadenceDays: z.number().int().positive().max(120).nullable().optional(),
195
+ promoCodes: z.array(z.string().trim().min(1).max(80)).max(20).default([]),
196
+ }).superRefine(validateQuoteRequest);
197
+ const createQuoteResponseSchema = z.object({
198
+ contractVersion: z.literal(COMMERCE_CONTRACT_VERSION), quote: commerceQuoteSchema,
199
+ }).strict();
200
+ const publicCreateQuoteResponseSchema = createQuoteResponseSchema.extend({
201
+ promotionAcceptanceToken: z.string().min(1).max(8_300).optional(),
202
+ }).superRefine(rejectCatalogFactsFromPublicQuote);
203
+ const createQuoteBatchResponseSchema = z.object({
204
+ contractVersion: z.literal(COMMERCE_CONTRACT_VERSION), quotes: z.array(commerceQuoteSchema),
205
+ });
206
+ const publicCreateQuoteBatchResponseSchema = createQuoteBatchResponseSchema
207
+ .superRefine((response, ctx) => response.quotes.forEach((quote, index) =>
208
+ rejectCatalogFactsFromPublicQuote({ quote }, ctx, ["quotes", index])));
209
+ return { createQuoteRequestSchema, quotePricingComponentSchema, cartLineInputSchema, commerceTaxBreakdownSchema, quoteLineSchema,
210
+ quoteCodeRejectionSchema, commerceQuoteSchema, createQuoteResponseSchema, publicCreateQuoteResponseSchema,
211
+ createQuoteBatchResponseSchema, publicCreateQuoteBatchResponseSchema };
212
+ }
213
+
214
+ function sumAmounts(amounts: Array<{ amountMinor?: number }>): number {
215
+ return amounts.reduce((sum, amount) => sum + (amount.amountMinor ?? 0), 0);
216
+ }
217
+
218
+ function validateQuoteRequest(
219
+ request: { mode: "one_time" | "subscription"; cadenceDays?: number | null },
220
+ ctx: z.RefinementCtx,
221
+ ): void {
222
+ if (request.mode === "subscription" && !request.cadenceDays) {
223
+ ctx.addIssue({
224
+ code: z.ZodIssueCode.custom,
225
+ message: "subscription quote requires cadenceDays",
226
+ path: ["cadenceDays"],
227
+ });
228
+ }
229
+ }
230
+
@@ -0,0 +1,113 @@
1
+ import { SUBSCRIPTION_PRICE_POLICY_SCHEMA_VERSION, type SubscriptionPricePolicySemantics, type SubscriptionPricePolicyCanonicalInput, type SubscriptionPricePolicyRevision, type SubscriptionPricePolicyDigestRefusal } from "./contracts/index.js";
2
+
3
+ import { z } from "./validation.js";
4
+
5
+ const uuidSchema = z.guid();
6
+ const sha256DigestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
7
+ const currencySchema = z.string().regex(/^[A-Z]{3}$/u);
8
+ const channelSchema = z.string().trim().min(1).max(64).regex(/^[A-Za-z0-9._:-]+$/);
9
+
10
+ export const subscriptionPricePolicySemanticsSchema = z.object({
11
+ discountBps: z.number().int().min(0).max(10_000),
12
+ roundingQuantumMinor: z.number().int().positive().refine(Number.isSafeInteger, "rounding quantum must be a safe integer"),
13
+ roundingRule: z.enum(["FLOOR_TO_QUANTUM"]),
14
+ }).strict();
15
+
16
+ const subscriptionPricePolicyRevisionFields = {
17
+ id: uuidSchema,
18
+ revisionNo: z.number().int().positive(),
19
+ priceListId: uuidSchema,
20
+ regionCode: z.string().regex(/^[A-Z]{2,3}$/u),
21
+ currency: currencySchema,
22
+ channel: channelSchema,
23
+ effectiveFrom: z.string().datetime({ offset: true }),
24
+ effectiveTo: z.string().datetime({ offset: true }).nullable(),
25
+ ...subscriptionPricePolicySemanticsSchema.shape,
26
+ };
27
+
28
+ export const subscriptionPricePolicyCanonicalInputSchema = z.object(subscriptionPricePolicyRevisionFields).strict().superRefine((policy, context) => {
29
+ if (policy.effectiveTo && policy.effectiveTo <= policy.effectiveFrom) {
30
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["effectiveTo"], message: "effectiveTo must be later than effectiveFrom" });
31
+ }
32
+ });
33
+
34
+ export const subscriptionPricePolicyRevisionSchema = subscriptionPricePolicyCanonicalInputSchema.extend({
35
+ digest: sha256DigestSchema,
36
+ }).strict();
37
+
38
+
39
+ export class SubscriptionPricePolicyDigestError extends Error {
40
+ constructor(readonly code: SubscriptionPricePolicyDigestRefusal) {
41
+ super(code);
42
+ this.name = "SubscriptionPricePolicyDigestError";
43
+ }
44
+ }
45
+
46
+ /** The exact tuple whose canonical digest is persisted with a policy revision. */
47
+ export function canonicalSubscriptionPricePolicyTuple(
48
+ policy: SubscriptionPricePolicyCanonicalInput | SubscriptionPricePolicyRevision,
49
+ ): string {
50
+ const value = parseCanonicalPolicyInput(policy);
51
+ return JSON.stringify({
52
+ schemaVersion: SUBSCRIPTION_PRICE_POLICY_SCHEMA_VERSION,
53
+ id: value.id,
54
+ revisionNo: value.revisionNo,
55
+ priceListId: value.priceListId,
56
+ regionCode: value.regionCode,
57
+ currency: value.currency,
58
+ channel: value.channel,
59
+ effectiveFrom: value.effectiveFrom,
60
+ effectiveTo: value.effectiveTo,
61
+ discountBps: value.discountBps,
62
+ roundingQuantumMinor: value.roundingQuantumMinor,
63
+ roundingRule: value.roundingRule,
64
+ });
65
+ }
66
+
67
+ /** Hashes application-owned canonical UTF-8 bytes; never a database JSON rendering. */
68
+ export async function digestSubscriptionPricePolicy(
69
+ policy: SubscriptionPricePolicyCanonicalInput | SubscriptionPricePolicyRevision,
70
+ ): Promise<string> {
71
+ const bytes = new TextEncoder().encode(canonicalSubscriptionPricePolicyTuple(policy));
72
+ const crypto = globalThis.crypto;
73
+ if (!crypto?.subtle) throw new Error("Web Crypto SHA-256 is unavailable");
74
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
75
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
76
+ }
77
+
78
+ /** Parses a persisted revision and refuses a digest outside its canonical UTF-8 tuple. */
79
+ export async function assertSubscriptionPricePolicyDigest(value: unknown): Promise<SubscriptionPricePolicyRevision> {
80
+ const revision = subscriptionPricePolicyRevisionSchema.parse(value);
81
+ const actual = await digestSubscriptionPricePolicy(revision);
82
+ if (actual !== revision.digest) {
83
+ throw new SubscriptionPricePolicyDigestError("subscription_price_policy_digest_mismatch");
84
+ }
85
+ return revision;
86
+ }
87
+
88
+ /** Resolves a subscription unit price from a base unit price and a policy revision. */
89
+ export function deriveSubscriptionUnitPrice(
90
+ baseUnitAmountMinor: number,
91
+ semantics: SubscriptionPricePolicySemantics | SubscriptionPricePolicyCanonicalInput | SubscriptionPricePolicyRevision,
92
+ ): number {
93
+ if (!Number.isSafeInteger(baseUnitAmountMinor) || baseUnitAmountMinor < 0) {
94
+ throw new RangeError("baseUnitAmountMinor must be a non-negative safe integer");
95
+ }
96
+ const policy = subscriptionPricePolicySemanticsSchema.parse({
97
+ discountBps: semantics.discountBps,
98
+ roundingQuantumMinor: semantics.roundingQuantumMinor,
99
+ roundingRule: semantics.roundingRule,
100
+ });
101
+ const discounted = (BigInt(baseUnitAmountMinor) * BigInt(10_000 - policy.discountBps)) / 10_000n;
102
+ const quantum = BigInt(policy.roundingQuantumMinor);
103
+ const resolved = (discounted / quantum) * quantum;
104
+ if (resolved > BigInt(Number.MAX_SAFE_INTEGER)) {
105
+ throw new RangeError("resolved subscription price exceeds Number.MAX_SAFE_INTEGER");
106
+ }
107
+ return Number(resolved);
108
+ }
109
+
110
+ function parseCanonicalPolicyInput(value: unknown): SubscriptionPricePolicyCanonicalInput {
111
+ const persisted = subscriptionPricePolicyRevisionSchema.safeParse(value);
112
+ return persisted.success ? persisted.data : subscriptionPricePolicyCanonicalInputSchema.parse(value);
113
+ }
@@ -0,0 +1,33 @@
1
+ import type { CommerceTaxProfile } from "./contracts/money.js";
2
+ import { CommerceQuoteError } from "./quotePorts.js";
3
+
4
+ export function assertSupportedCommerceTaxProfile(
5
+ taxProfile: CommerceTaxProfile,
6
+ supportedTaxProfiles: readonly CommerceTaxProfile[],
7
+ ): void {
8
+ if (
9
+ !Number.isInteger(taxProfile.vatRateBps) ||
10
+ taxProfile.vatRateBps < 0 ||
11
+ taxProfile.vatRateBps > 10_000
12
+ ) {
13
+ throw new CommerceQuoteError("UNSUPPORTED_TAX_PROFILE", "Unsupported commerce tax profile", {
14
+ reason: "vatRateBps must be an integer between 0 and 10000",
15
+ vatRateBps: taxProfile.vatRateBps,
16
+ });
17
+ }
18
+ const supported = supportedTaxProfiles.some((candidate) =>
19
+ candidate.included === taxProfile.included &&
20
+ candidate.country === taxProfile.country &&
21
+ candidate.category === taxProfile.category &&
22
+ candidate.vatRateBps === taxProfile.vatRateBps &&
23
+ candidate.legalBasis === taxProfile.legalBasis,
24
+ );
25
+ if (!supported) {
26
+ throw new CommerceQuoteError("UNSUPPORTED_TAX_PROFILE", "Unsupported commerce tax profile", {
27
+ country: taxProfile.country,
28
+ category: taxProfile.category,
29
+ vatRateBps: taxProfile.vatRateBps,
30
+ reason: "taxProfile must be declared in supportedTaxProfiles",
31
+ });
32
+ }
33
+ }
@@ -0,0 +1,3 @@
1
+ import { z } from "zod";
2
+ z.config({ jitless: true });
3
+ export { z };
@@ -0,0 +1,53 @@
1
+ import type { z } from "zod";
2
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
3
+ import type { BffResponder } from "../bff/index.js";
4
+ import type { createPromotionCodePreview } from "./promotionCodePreview.js";
5
+
6
+ /** Structural inputs to the existing host admin kit; no kit implementation lives here. */
7
+ export type AdminAuthorization =
8
+ | { ok: true; userId: string; role: "admin" | "distributor"; isMachineActor: boolean }
9
+ | { ok: false; code: "UNAUTHORIZED" | "FORBIDDEN"; message: string; identityCode?: string };
10
+ export type AuthorizeAdmin = () => Promise<AdminAuthorization>;
11
+ export interface AgentDomainQuerySpec<T> {
12
+ readonly key: string;
13
+ readonly requestSchema: z.ZodType<T, unknown>;
14
+ readonly gate: "read";
15
+ readonly allowedActorKinds: readonly ("human" | "machine")[];
16
+ }
17
+ export interface AgentDomainMutationSpec<T> extends Omit<AgentDomainQuerySpec<T>, "gate"> {
18
+ readonly gate: "mutation";
19
+ readonly lifecycleMarkers: readonly never[];
20
+ }
21
+ type Handler = (req: HttpRequest, res: HttpResponse) => Promise<void>;
22
+ type ErrorMapper = (res: HttpResponse, error: unknown, failureMessage: string) => void;
23
+ interface ReadOptions<T> {
24
+ readonly authorizeAdmin: AuthorizeAdmin;
25
+ readonly toResponse: (value: T) => Record<string, unknown>;
26
+ readonly responseSchema: { safeParse(value: unknown): { success: boolean; data?: unknown } };
27
+ readonly invalidResponseMessage: string;
28
+ readonly failureMessage: string;
29
+ }
30
+ interface MutationOptions<Request, Result> extends Omit<ReadOptions<Result>, "responseSchema" | "invalidResponseMessage"> {
31
+ readonly mutation: AgentDomainMutationSpec<Request>;
32
+ readonly invoke: (actorId: string, request: Request) => Promise<Result>;
33
+ readonly responseSchema?: ReadOptions<Result>["responseSchema"];
34
+ readonly invalidResponseMessage?: string;
35
+ readonly invalidRequestMessage: string;
36
+ readonly mapError?: ErrorMapper;
37
+ }
38
+ interface QueryOptions<Request, Result> extends ReadOptions<Result> {
39
+ readonly query: AgentDomainQuerySpec<Request>;
40
+ readonly load: (actorId: string, request: Request) => Promise<Result>;
41
+ readonly invalidRequestMessage: string;
42
+ readonly mapError?: ErrorMapper;
43
+ }
44
+ export interface PromotionAdminBindings extends BffResponder, Pick<ReturnType<typeof createPromotionCodePreview>,
45
+ "generatePromotionCode" | "normalizePromotionCode" | "previewPromotionCode" | "verifyPromotionPreviewProof"> {
46
+ createAdminMutationHandler<Request, Result>(options: MutationOptions<Request, Result>): Handler;
47
+ createAdminReadHandler<Result>(options: ReadOptions<Result> & { load(): Promise<Result> }): Handler;
48
+ createAdminParameterizedReadHandler<Request, Result>(options: QueryOptions<Request, Result>): Handler;
49
+ mapRpcError: ErrorMapper;
50
+ resolveAdmin(authorize: AuthorizeAdmin, res: HttpResponse): Promise<Omit<Extract<AdminAuthorization, { ok: true }>, "ok"> | null>;
51
+ /** The host constructor is passed by identity, preserving instanceof and SQLSTATE mapping. */
52
+ DomainRpcError: new (sqlstate: string | undefined, pgMessage: string) => Error & { sqlstate: string | undefined; pgMessage: string };
53
+ }
@@ -0,0 +1,244 @@
1
+ import { z } from "./validation.js";
2
+
3
+ import { COMMERCE_CONTRACT_VERSION } from "../pricing/index.js";
4
+
5
+ export const PROMOTION_CODE_STATUSES = ["draft", "active", "paused", "archived"] as const;
6
+ export const PROMOTION_CODE_EFFECTIVE_STATUSES = [
7
+ "active",
8
+ "draft",
9
+ "paused",
10
+ "scheduled",
11
+ "expired",
12
+ "archived",
13
+ ] as const;
14
+ export const PROMOTION_CODE_STATUS_FILTERS = [
15
+ ...PROMOTION_CODE_EFFECTIVE_STATUSES,
16
+ "expired_or_archived",
17
+ ] as const;
18
+ export const PROMOTION_CODE_SCOPES = ["one_time", "subscription_initial"] as const;
19
+
20
+ const nullablePositiveInteger = z.number().int().positive().nullable();
21
+ const maximumPromotionFixedMinor = 2_147_483_647;
22
+ const optionalDateTime = z.string().datetime({ offset: true }).nullable();
23
+ const scopesSchema = z
24
+ .array(z.enum(PROMOTION_CODE_SCOPES))
25
+ .min(1)
26
+ .max(2)
27
+ .refine((scopes) => new Set(scopes).size === scopes.length, {
28
+ message: "Purchase scopes must be unique",
29
+ });
30
+
31
+ const productTargetBenefitSchema = z.union([
32
+ z.object({ lane: z.literal("product"), kind: z.literal("target_percentage"), valueBps: z.number().int().min(1).max(9_999) }).strict(),
33
+ z.object({ lane: z.literal("product"), kind: z.literal("fixed_amount"), valueMinor: z.number().int().positive().max(maximumPromotionFixedMinor) }).strict(),
34
+ ]);
35
+ const shippingBenefitSchema = z.union([
36
+ z.object({ lane: z.literal("shipping"), kind: z.literal("percentage"), valueBps: z.number().int().min(1).max(9_999) }).strict(),
37
+ z.object({ lane: z.literal("shipping"), kind: z.literal("fixed_amount"), valueMinor: z.number().int().positive().max(maximumPromotionFixedMinor) }).strict(),
38
+ z.object({ lane: z.literal("shipping"), kind: z.literal("free_shipping") }).strict(),
39
+ ]);
40
+ export const promotionCodeBenefitSchema = z.union([
41
+ productTargetBenefitSchema,
42
+ shippingBenefitSchema,
43
+ z.object({ lane: z.literal("product"), kind: z.literal("percentage"), valueBps: z.number().int().min(0) }).strict(),
44
+ z.object({ lane: z.literal("shipping"), kind: z.literal("percentage"), valueBps: z.number().int().min(0) }).strict(),
45
+ z.object({ lane: z.literal("product"), kind: z.literal("fixed_amount"), valueMinor: z.number().int().nonnegative() }).strict(),
46
+ z.object({ lane: z.literal("shipping"), kind: z.literal("fixed_amount"), valueMinor: z.number().int().nonnegative() }).strict(),
47
+ z.object({
48
+ lane: z.enum(["product", "shipping"]),
49
+ kind: z.enum(["percentage", "fixed_amount"]),
50
+ validationState: z.literal("unsupported_legacy_value"),
51
+ }).strict(),
52
+ ]);
53
+ const createBenefitSchema = z.union([productTargetBenefitSchema, shippingBenefitSchema]);
54
+
55
+ const benefitsSchema = z
56
+ .array(promotionCodeBenefitSchema)
57
+ .min(1)
58
+ .max(2)
59
+ .refine((benefits) => new Set(benefits.map((benefit) => benefit.lane)).size === benefits.length, {
60
+ message: "Only one benefit per adjustment lane is allowed",
61
+ });
62
+ const createBenefitsSchema = z
63
+ .array(createBenefitSchema)
64
+ .min(1)
65
+ .max(2)
66
+ .refine((benefits) => new Set(benefits.map((benefit) => benefit.lane)).size === benefits.length, {
67
+ message: "Only one benefit per adjustment lane is allowed",
68
+ });
69
+
70
+ export const promotionCodePreviewContextSchema = z
71
+ .object({
72
+ referenceProductMinor: z.number().int().positive(),
73
+ oneTimeProductMinor: z.number().int().positive(),
74
+ subscriptionProductMinor: z.number().int().positive(),
75
+ shippingMinor: z.number().int().nonnegative(),
76
+ })
77
+ .strict()
78
+ .refine((value) => value.oneTimeProductMinor <= value.referenceProductMinor, {
79
+ message: "One-time product price cannot exceed the reference price",
80
+ })
81
+ .refine((value) => value.subscriptionProductMinor <= value.referenceProductMinor, {
82
+ message: "Subscription product price cannot exceed the reference price",
83
+ });
84
+
85
+ export const promotionCodePreviewRequestSchema = z
86
+ .object({
87
+ benefits: benefitsSchema,
88
+ scopes: scopesSchema,
89
+ promotionEngineVersion: z.enum(["promotion-engine.v1", "promotion-engine.v2"]).default("promotion-engine.v2"),
90
+ minimumReferenceMinor: z.number().int().nonnegative().default(0),
91
+ context: promotionCodePreviewContextSchema,
92
+ })
93
+ .strict();
94
+
95
+ export const promotionCodePreviewResultSchema = z.object({
96
+ scope: z.enum(PROMOTION_CODE_SCOPES),
97
+ productPayableMinor: z.number().int().nonnegative(),
98
+ shippingPayableMinor: z.number().int().nonnegative(),
99
+ effectiveProductDiscountBps: z.number().int().min(0).max(10_000),
100
+ winner: z.enum(["code", "automatic"]),
101
+ floorApplied: z.boolean(),
102
+ adjustments: z.array(
103
+ z.object({
104
+ lane: z.enum(["product", "shipping"]),
105
+ kind: z.enum(["target_percentage", "percentage", "fixed_amount", "free_shipping"]),
106
+ amountOffMinor: z.number().int().nonnegative(),
107
+ beforeMinor: z.number().int().nonnegative(),
108
+ afterMinor: z.number().int().nonnegative(),
109
+ }),
110
+ ),
111
+ });
112
+
113
+ export const promotionCodePreviewResponseSchema = z.object({
114
+ contractVersion: z.literal(COMMERCE_CONTRACT_VERSION),
115
+ promotionEngineVersion: z.enum(["promotion-engine.v1", "promotion-engine.v2"]),
116
+ previewProof: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
117
+ results: z.array(promotionCodePreviewResultSchema).length(2),
118
+ });
119
+
120
+ export const promotionCodeCreateRequestSchema = z
121
+ .object({
122
+ name: z.string().trim().min(1).max(120),
123
+ description: z.string().trim().max(500).nullable().default(null),
124
+ code: z.discriminatedUnion("kind", [
125
+ z.object({ kind: z.literal("automatic") }).strict(),
126
+ z.object({ kind: z.literal("manual"), value: z.string().trim().min(3).max(64).regex(/^[A-Za-z0-9_-]+$/) }).strict(),
127
+ ]),
128
+ benefits: createBenefitsSchema,
129
+ scopes: scopesSchema,
130
+ validFrom: z.string().datetime({ offset: true }),
131
+ validTo: optionalDateTime,
132
+ minimumReferenceMinor: z.number().int().nonnegative().default(0),
133
+ redemptionLimitGlobal: nullablePositiveInteger.default(null),
134
+ redemptionLimitPerCustomer: nullablePositiveInteger.default(null),
135
+ status: z.enum(["draft", "active"]).default("draft"),
136
+ previewContext: promotionCodePreviewContextSchema.optional(),
137
+ previewProof: z.string().regex(/^[A-Za-z0-9_-]{43}$/).optional(),
138
+ idempotencyKey: z.string().uuid(),
139
+ })
140
+ .strict()
141
+ .refine((value) => value.validTo === null || Date.parse(value.validTo) > Date.parse(value.validFrom), {
142
+ message: "validTo must be after validFrom",
143
+ })
144
+ .refine(
145
+ (value) => value.status !== "active" || Boolean(value.previewContext && value.previewProof),
146
+ { message: "Active codes require a successful backend preview" },
147
+ );
148
+
149
+ export const promotionCodeCreateResponseSchema = z.object({
150
+ contractVersion: z.literal(COMMERCE_CONTRACT_VERSION),
151
+ id: z.string().uuid(),
152
+ code: z.string().min(3).max(64),
153
+ status: z.enum(PROMOTION_CODE_STATUSES),
154
+ revision: z.number().int().positive(),
155
+ idempotent: z.boolean(),
156
+ });
157
+
158
+ export const promotionCodeUpdateRequestSchema = z
159
+ .object({
160
+ id: z.string().uuid(),
161
+ expectedRevision: z.number().int().positive(),
162
+ updates: z
163
+ .object({
164
+ status: z.enum(PROMOTION_CODE_STATUSES).optional(),
165
+ validFrom: z.string().datetime({ offset: true }).optional(),
166
+ validTo: optionalDateTime.optional(),
167
+ redemptionLimitGlobal: nullablePositiveInteger.optional(),
168
+ redemptionLimitPerCustomer: nullablePositiveInteger.optional(),
169
+ })
170
+ .strict()
171
+ .refine((updates) => Object.values(updates).some((value) => value !== undefined), {
172
+ message: "At least one field is required",
173
+ }),
174
+ previewContext: promotionCodePreviewContextSchema.optional(),
175
+ previewProof: z.string().regex(/^[A-Za-z0-9_-]{43}$/).optional(),
176
+ idempotencyKey: z.string().uuid(),
177
+ })
178
+ .strict()
179
+ ;
180
+
181
+ export const promotionCodeUpdateResponseSchema = z.object({
182
+ contractVersion: z.literal(COMMERCE_CONTRACT_VERSION),
183
+ id: z.string().uuid(),
184
+ status: z.enum(PROMOTION_CODE_STATUSES),
185
+ revision: z.number().int().positive(),
186
+ idempotent: z.boolean(),
187
+ });
188
+
189
+ export const promotionCodesListRequestSchema = z
190
+ .object({
191
+ q: z.string().trim().min(1).max(80).optional(),
192
+ status: z.enum([...PROMOTION_CODE_STATUS_FILTERS, "all"]).default("active"),
193
+ scope: z.enum([...PROMOTION_CODE_SCOPES, "both", "shipping", "all"]).default("all"),
194
+ cursor: z.string().max(500).optional(),
195
+ limit: z.coerce.number().int().min(1).max(100).default(25),
196
+ })
197
+ .strict();
198
+
199
+ export const promotionCodeSummarySchema = z.object({
200
+ id: z.string().uuid(),
201
+ code: z.string(),
202
+ name: z.string(),
203
+ description: z.string().nullable(),
204
+ benefits: benefitsSchema,
205
+ promotionEngineVersion: z.enum(["promotion-engine.v1", "promotion-engine.v2"]),
206
+ scopes: scopesSchema,
207
+ status: z.enum(PROMOTION_CODE_STATUSES),
208
+ effectiveStatus: z.enum(PROMOTION_CODE_EFFECTIVE_STATUSES),
209
+ validFrom: z.string(),
210
+ validTo: z.string().nullable(),
211
+ minimumReferenceMinor: z.number().int().nonnegative(),
212
+ redemptionLimitGlobal: z.number().int().positive().nullable(),
213
+ redemptionLimitPerCustomer: z.number().int().positive().nullable(),
214
+ redeemedCount: z.number().int().nonnegative(),
215
+ reservedCount: z.number().int().nonnegative(),
216
+ remainingCount: z.number().int().nonnegative().nullable(),
217
+ createdAt: z.string(),
218
+ revision: z.number().int().positive(),
219
+ });
220
+
221
+ export const promotionCodesListResponseSchema = z.object({
222
+ contractVersion: z.literal(COMMERCE_CONTRACT_VERSION),
223
+ codes: z.array(promotionCodeSummarySchema),
224
+ nextCursor: z.string().nullable(),
225
+ capabilities: z.object({
226
+ mutationsEnabled: z.boolean(),
227
+ }).strict(),
228
+ legacyCompatibility: z.object({
229
+ ready: z.boolean(),
230
+ unprojectedCount: z.number().int().nonnegative(),
231
+ collisionGroupCount: z.number().int().nonnegative(),
232
+ }).strict(),
233
+ });
234
+
235
+ export type PromotionCodeBenefit = z.infer<typeof promotionCodeBenefitSchema>;
236
+ export type PromotionCodePreviewRequest = z.infer<typeof promotionCodePreviewRequestSchema>;
237
+ export type PromotionCodePreviewResponse = z.infer<typeof promotionCodePreviewResponseSchema>;
238
+ export type PromotionCodeCreateRequest = z.infer<typeof promotionCodeCreateRequestSchema>;
239
+ export type PromotionCodeCreateResponse = z.infer<typeof promotionCodeCreateResponseSchema>;
240
+ export type PromotionCodeUpdateRequest = z.infer<typeof promotionCodeUpdateRequestSchema>;
241
+ export type PromotionCodeUpdateResponse = z.infer<typeof promotionCodeUpdateResponseSchema>;
242
+ export type PromotionCodesListRequest = z.infer<typeof promotionCodesListRequestSchema>;
243
+ export type PromotionCodeSummary = z.infer<typeof promotionCodeSummarySchema>;
244
+ export type PromotionCodesListResponse = z.infer<typeof promotionCodesListResponseSchema>;