@openlup/platform 0.16.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 (773) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +564 -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/accountingDocumentDelivery.d.ts +35 -0
  90. package/dist/communications/accountingDocumentDelivery.js +33 -0
  91. package/dist/communications/authEmailError.d.ts +5 -0
  92. package/dist/communications/authEmailError.js +8 -0
  93. package/dist/communications/authEmailPorts.d.ts +2 -0
  94. package/dist/communications/authEmailPorts.js +1 -0
  95. package/dist/communications/authSendEmailHook.d.ts +35 -0
  96. package/dist/communications/authSendEmailHook.js +250 -0
  97. package/dist/communications/capturedTransactionalDelivery.d.ts +13 -0
  98. package/dist/communications/capturedTransactionalDelivery.js +14 -0
  99. package/dist/communications/communicationSync.d.ts +11 -0
  100. package/dist/communications/communicationSync.js +79 -0
  101. package/dist/communications/communicationSyncWorker.d.ts +68 -0
  102. package/dist/communications/communicationSyncWorker.js +218 -0
  103. package/dist/communications/consentPolicy.d.ts +19 -0
  104. package/dist/communications/consentPolicy.js +43 -0
  105. package/dist/communications/consentPurposeRegistry.d.ts +4 -0
  106. package/dist/communications/consentPurposeRegistry.js +24 -0
  107. package/dist/communications/contracts/abandonedCart.d.ts +27 -0
  108. package/dist/communications/contracts/abandonedCart.js +25 -0
  109. package/dist/communications/contracts/authEmail.d.ts +62 -0
  110. package/dist/communications/contracts/authEmail.js +1 -0
  111. package/dist/communications/contracts/backInStock.d.ts +28 -0
  112. package/dist/communications/contracts/backInStock.js +28 -0
  113. package/dist/communications/contracts/blocks.d.ts +24 -0
  114. package/dist/communications/contracts/blocks.js +23 -0
  115. package/dist/communications/contracts/checkoutExpired.d.ts +22 -0
  116. package/dist/communications/contracts/checkoutExpired.js +25 -0
  117. package/dist/communications/contracts/checkoutRecovery.d.ts +26 -0
  118. package/dist/communications/contracts/checkoutRecovery.js +37 -0
  119. package/dist/communications/contracts/commerceEmailContent.d.ts +176 -0
  120. package/dist/communications/contracts/commerceEmailContent.js +1 -0
  121. package/dist/communications/contracts/commerceEngagementEmailContent.d.ts +95 -0
  122. package/dist/communications/contracts/commerceEngagementEmailContent.js +1 -0
  123. package/dist/communications/contracts/consent.d.ts +12 -0
  124. package/dist/communications/contracts/consent.js +1 -0
  125. package/dist/communications/contracts/email.d.ts +182 -0
  126. package/dist/communications/contracts/email.js +1 -0
  127. package/dist/communications/contracts/guideCard.d.ts +3 -0
  128. package/dist/communications/contracts/guideCard.js +26 -0
  129. package/dist/communications/contracts/index.d.ts +52 -0
  130. package/dist/communications/contracts/index.js +42 -0
  131. package/dist/communications/contracts/inquiryContent.d.ts +32 -0
  132. package/dist/communications/contracts/inquiryContent.js +33 -0
  133. package/dist/communications/contracts/invoiceDocumentContent.d.ts +26 -0
  134. package/dist/communications/contracts/invoiceDocumentContent.js +36 -0
  135. package/dist/communications/contracts/marketingAutomation.d.ts +52 -0
  136. package/dist/communications/contracts/marketingAutomation.js +1 -0
  137. package/dist/communications/contracts/marketingSync.d.ts +194 -0
  138. package/dist/communications/contracts/marketingSync.js +1 -0
  139. package/dist/communications/contracts/newsletter.d.ts +63 -0
  140. package/dist/communications/contracts/newsletter.js +1 -0
  141. package/dist/communications/contracts/newsletterWebhook.d.ts +26 -0
  142. package/dist/communications/contracts/newsletterWebhook.js +1 -0
  143. package/dist/communications/contracts/optOutContent.d.ts +11 -0
  144. package/dist/communications/contracts/optOutContent.js +17 -0
  145. package/dist/communications/contracts/orderCanceled.d.ts +25 -0
  146. package/dist/communications/contracts/orderCanceled.js +27 -0
  147. package/dist/communications/contracts/orderDraft.d.ts +46 -0
  148. package/dist/communications/contracts/orderDraft.js +46 -0
  149. package/dist/communications/contracts/orderPaid.d.ts +53 -0
  150. package/dist/communications/contracts/orderPaid.js +71 -0
  151. package/dist/communications/contracts/orderRefunded.d.ts +24 -0
  152. package/dist/communications/contracts/orderRefunded.js +27 -0
  153. package/dist/communications/contracts/paymentFailed.d.ts +24 -0
  154. package/dist/communications/contracts/paymentFailed.js +24 -0
  155. package/dist/communications/contracts/publicIntake.d.ts +49 -0
  156. package/dist/communications/contracts/publicIntake.js +1 -0
  157. package/dist/communications/contracts/render.d.ts +2 -0
  158. package/dist/communications/contracts/render.js +229 -0
  159. package/dist/communications/contracts/reorderReminder.d.ts +24 -0
  160. package/dist/communications/contracts/reorderReminder.js +24 -0
  161. package/dist/communications/contracts/returnEmails.d.ts +25 -0
  162. package/dist/communications/contracts/returnEmails.js +62 -0
  163. package/dist/communications/contracts/reviewEffects.d.ts +26 -0
  164. package/dist/communications/contracts/reviewEffects.js +25 -0
  165. package/dist/communications/contracts/reviewRequest.d.ts +26 -0
  166. package/dist/communications/contracts/reviewRequest.js +24 -0
  167. package/dist/communications/contracts/roleNotificationContent.d.ts +25 -0
  168. package/dist/communications/contracts/roleNotificationContent.js +46 -0
  169. package/dist/communications/contracts/shipmentDelivered.d.ts +23 -0
  170. package/dist/communications/contracts/shipmentDelivered.js +24 -0
  171. package/dist/communications/contracts/shipmentDispatched.d.ts +27 -0
  172. package/dist/communications/contracts/shipmentDispatched.js +35 -0
  173. package/dist/communications/contracts/shipmentException.d.ts +21 -0
  174. package/dist/communications/contracts/shipmentException.js +26 -0
  175. package/dist/communications/contracts/subscriptionActivationActionRequired.d.ts +17 -0
  176. package/dist/communications/contracts/subscriptionActivationActionRequired.js +22 -0
  177. package/dist/communications/contracts/subscriptionAddressChanged.d.ts +19 -0
  178. package/dist/communications/contracts/subscriptionAddressChanged.js +25 -0
  179. package/dist/communications/contracts/subscriptionCancelled.d.ts +19 -0
  180. package/dist/communications/contracts/subscriptionCancelled.js +25 -0
  181. package/dist/communications/contracts/subscriptionCycleSkipped.d.ts +25 -0
  182. package/dist/communications/contracts/subscriptionCycleSkipped.js +40 -0
  183. package/dist/communications/contracts/subscriptionDeliveryRescheduled.d.ts +25 -0
  184. package/dist/communications/contracts/subscriptionDeliveryRescheduled.js +39 -0
  185. package/dist/communications/contracts/subscriptionEmailContent.d.ts +145 -0
  186. package/dist/communications/contracts/subscriptionEmailContent.js +1 -0
  187. package/dist/communications/contracts/subscriptionPackageChanged.d.ts +20 -0
  188. package/dist/communications/contracts/subscriptionPackageChanged.js +26 -0
  189. package/dist/communications/contracts/subscriptionPauseReminder.d.ts +23 -0
  190. package/dist/communications/contracts/subscriptionPauseReminder.js +29 -0
  191. package/dist/communications/contracts/subscriptionPaused.d.ts +21 -0
  192. package/dist/communications/contracts/subscriptionPaused.js +25 -0
  193. package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +34 -0
  194. package/dist/communications/contracts/subscriptionPaymentExpired.js +37 -0
  195. package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +49 -0
  196. package/dist/communications/contracts/subscriptionPaymentFailed.js +56 -0
  197. package/dist/communications/contracts/subscriptionPaymentRecovered.d.ts +19 -0
  198. package/dist/communications/contracts/subscriptionPaymentRecovered.js +22 -0
  199. package/dist/communications/contracts/subscriptionRenewalAtRisk.d.ts +36 -0
  200. package/dist/communications/contracts/subscriptionRenewalAtRisk.js +36 -0
  201. package/dist/communications/contracts/subscriptionRenewalUpcoming.d.ts +49 -0
  202. package/dist/communications/contracts/subscriptionRenewalUpcoming.js +43 -0
  203. package/dist/communications/contracts/subscriptionResumed.d.ts +23 -0
  204. package/dist/communications/contracts/subscriptionResumed.js +25 -0
  205. package/dist/communications/contracts/subscriptionWelcome.d.ts +44 -0
  206. package/dist/communications/contracts/subscriptionWelcome.js +45 -0
  207. package/dist/communications/contracts/subscriptionWinback.d.ts +21 -0
  208. package/dist/communications/contracts/subscriptionWinback.js +28 -0
  209. package/dist/communications/contracts/transactionalDelivery.d.ts +42 -0
  210. package/dist/communications/contracts/transactionalDelivery.js +1 -0
  211. package/dist/communications/contracts/types.d.ts +46 -0
  212. package/dist/communications/contracts/types.js +1 -0
  213. package/dist/communications/contribution.d.ts +10 -0
  214. package/dist/communications/contribution.js +24 -0
  215. package/dist/communications/deliveryReconciliation.d.ts +64 -0
  216. package/dist/communications/deliveryReconciliation.js +60 -0
  217. package/dist/communications/emailEgress.d.ts +38 -0
  218. package/dist/communications/emailEgress.js +100 -0
  219. package/dist/communications/emailHealthAssessment.d.ts +39 -0
  220. package/dist/communications/emailHealthAssessment.js +59 -0
  221. package/dist/communications/emailHealthMetricsPort.d.ts +17 -0
  222. package/dist/communications/emailHealthMetricsPort.js +1 -0
  223. package/dist/communications/emailNotificationControl.d.ts +27 -0
  224. package/dist/communications/emailNotificationControl.js +10 -0
  225. package/dist/communications/emailObservation.d.ts +28 -0
  226. package/dist/communications/emailObservation.js +23 -0
  227. package/dist/communications/emailTransport.d.ts +82 -0
  228. package/dist/communications/emailTransport.js +95 -0
  229. package/dist/communications/errors.d.ts +15 -0
  230. package/dist/communications/errors.js +30 -0
  231. package/dist/communications/index.d.ts +47 -0
  232. package/dist/communications/index.js +38 -0
  233. package/dist/communications/integrationEventSignature.d.ts +15 -0
  234. package/dist/communications/integrationEventSignature.js +37 -0
  235. package/dist/communications/klaviyo/consentPush.d.ts +51 -0
  236. package/dist/communications/klaviyo/consentPush.js +19 -0
  237. package/dist/communications/klaviyo/index.d.ts +110 -0
  238. package/dist/communications/klaviyo/index.js +860 -0
  239. package/dist/communications/managedTransactionalDeliveryStore.d.ts +12 -0
  240. package/dist/communications/managedTransactionalDeliveryStore.js +49 -0
  241. package/dist/communications/nativeOperations.d.ts +79 -0
  242. package/dist/communications/nativeOperations.js +3 -0
  243. package/dist/communications/newsletterIntegrationContracts.d.ts +130 -0
  244. package/dist/communications/newsletterIntegrationContracts.js +128 -0
  245. package/dist/communications/newsletterWebhookPort.d.ts +11 -0
  246. package/dist/communications/newsletterWebhookPort.js +84 -0
  247. package/dist/communications/newsletterWebhookProcessing.d.ts +17 -0
  248. package/dist/communications/newsletterWebhookProcessing.js +71 -0
  249. package/dist/communications/originPolicy.d.ts +40 -0
  250. package/dist/communications/originPolicy.js +100 -0
  251. package/dist/communications/postgresCommunicationSync.d.ts +79 -0
  252. package/dist/communications/postgresCommunicationSync.js +111 -0
  253. package/dist/communications/postgresConsentEvaluator.d.ts +19 -0
  254. package/dist/communications/postgresConsentEvaluator.js +43 -0
  255. package/dist/communications/postgresDeliveryReconciliation.d.ts +3 -0
  256. package/dist/communications/postgresDeliveryReconciliation.js +113 -0
  257. package/dist/communications/postgresNativeOperations.d.ts +13 -0
  258. package/dist/communications/postgresNativeOperations.js +63 -0
  259. package/dist/communications/postgresNotificationRecipients.d.ts +37 -0
  260. package/dist/communications/postgresNotificationRecipients.js +60 -0
  261. package/dist/communications/postgresTransactionalDeliveryStore.d.ts +7 -0
  262. package/dist/communications/postgresTransactionalDeliveryStore.js +55 -0
  263. package/dist/communications/preferencesToken.d.ts +10 -0
  264. package/dist/communications/preferencesToken.js +40 -0
  265. package/dist/communications/publicPreferenceToken.d.ts +24 -0
  266. package/dist/communications/publicPreferenceToken.js +53 -0
  267. package/dist/communications/resend/index.d.ts +40 -0
  268. package/dist/communications/resend/index.js +310 -0
  269. package/dist/communications/shipmentNotifications.d.ts +34 -0
  270. package/dist/communications/shipmentNotifications.js +61 -0
  271. package/dist/communications/sql.d.ts +26 -0
  272. package/dist/communications/sql.js +7 -0
  273. package/dist/communications/standardWebhooks.d.ts +14 -0
  274. package/dist/communications/standardWebhooks.js +73 -0
  275. package/dist/communications/status.d.ts +11 -0
  276. package/dist/communications/status.js +36 -0
  277. package/dist/communications/transactionalDelivery.d.ts +19 -0
  278. package/dist/communications/transactionalDelivery.js +96 -0
  279. package/dist/communications/transactionalDeliveryIdentifier.d.ts +3 -0
  280. package/dist/communications/transactionalDeliveryIdentifier.js +6 -0
  281. package/dist/communications/unsubscribeToken.d.ts +77 -0
  282. package/dist/communications/unsubscribeToken.js +150 -0
  283. package/dist/communications/validation.d.ts +2 -0
  284. package/dist/communications/validation.js +3 -0
  285. package/dist/composition/catalogContribution.d.ts +18 -0
  286. package/dist/composition/catalogContribution.js +21 -0
  287. package/dist/composition/index.d.ts +1 -0
  288. package/dist/composition/index.js +1 -0
  289. package/dist/http/index.d.ts +1 -0
  290. package/dist/http/index.js +1 -0
  291. package/dist/http/webhookSignature.d.ts +42 -0
  292. package/dist/http/webhookSignature.js +86 -0
  293. package/dist/identity/customerContracts.d.ts +2 -2
  294. package/dist/inventory/adminInventory.d.ts +13 -0
  295. package/dist/inventory/adminInventory.js +214 -0
  296. package/dist/inventory/contracts/forecast.d.ts +86 -0
  297. package/dist/inventory/contracts/forecast.js +1 -0
  298. package/dist/inventory/contracts/index.d.ts +4 -0
  299. package/dist/inventory/contracts/index.js +1 -0
  300. package/dist/inventory/contracts/inventory.d.ts +157 -0
  301. package/dist/inventory/contracts/inventory.js +1 -0
  302. package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
  303. package/dist/inventory/contracts/newPurchaseStock.js +1 -0
  304. package/dist/inventory/contracts/reservation.d.ts +18 -0
  305. package/dist/inventory/contracts/reservation.js +1 -0
  306. package/dist/inventory/forecast.d.ts +24 -0
  307. package/dist/inventory/forecast.js +200 -0
  308. package/dist/inventory/index.d.ts +13 -0
  309. package/dist/inventory/index.js +10 -0
  310. package/dist/inventory/inventoryHandlers.d.ts +53 -0
  311. package/dist/inventory/inventoryHandlers.js +151 -0
  312. package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
  313. package/dist/inventory/newPurchaseStockAvailability.js +75 -0
  314. package/dist/inventory/ports.d.ts +21 -0
  315. package/dist/inventory/ports.js +16 -0
  316. package/dist/inventory/renewalReservation.d.ts +27 -0
  317. package/dist/inventory/renewalReservation.js +61 -0
  318. package/dist/inventory/reservationPolicy.d.ts +6 -0
  319. package/dist/inventory/reservationPolicy.js +19 -0
  320. package/dist/inventory/schemas.d.ts +137 -0
  321. package/dist/inventory/schemas.js +112 -0
  322. package/dist/inventory/sql.d.ts +14 -0
  323. package/dist/inventory/sql.js +5 -0
  324. package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
  325. package/dist/inventory/subscriptionForecastCalculation.js +133 -0
  326. package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
  327. package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
  328. package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
  329. package/dist/pricing/acquisitionOfferPricing.js +25 -0
  330. package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
  331. package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
  332. package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
  333. package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
  334. package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
  335. package/dist/pricing/acquisitionQuoteBinding.js +12 -0
  336. package/dist/pricing/acquisitionReadiness.d.ts +31 -0
  337. package/dist/pricing/acquisitionReadiness.js +117 -0
  338. package/dist/pricing/acquisitionTerms.d.ts +19 -0
  339. package/dist/pricing/acquisitionTerms.js +126 -0
  340. package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
  341. package/dist/pricing/acquisitionTermsSchemas.js +42 -0
  342. package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
  343. package/dist/pricing/catalogFactsProvenance.js +64 -0
  344. package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
  345. package/dist/pricing/catalogFactsSchemas.js +58 -0
  346. package/dist/pricing/commerceOfferPricing.d.ts +25 -0
  347. package/dist/pricing/commerceOfferPricing.js +139 -0
  348. package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
  349. package/dist/pricing/commerceOfferPricingCache.js +44 -0
  350. package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
  351. package/dist/pricing/commerceOfferPricingHandler.js +78 -0
  352. package/dist/pricing/commercePriceAuthority.d.ts +26 -0
  353. package/dist/pricing/commercePriceAuthority.js +79 -0
  354. package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
  355. package/dist/pricing/commercePricingPolicyErrors.js +67 -0
  356. package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
  357. package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
  358. package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
  359. package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
  360. package/dist/pricing/commerceQuoteHandler.d.ts +31 -0
  361. package/dist/pricing/commerceQuoteHandler.js +110 -0
  362. package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
  363. package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
  364. package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
  365. package/dist/pricing/contracts/acquisitionTerms.js +2 -0
  366. package/dist/pricing/contracts/authority.d.ts +27 -0
  367. package/dist/pricing/contracts/authority.js +1 -0
  368. package/dist/pricing/contracts/evidence.d.ts +33 -0
  369. package/dist/pricing/contracts/evidence.js +1 -0
  370. package/dist/pricing/contracts/index.d.ts +17 -0
  371. package/dist/pricing/contracts/index.js +7 -0
  372. package/dist/pricing/contracts/money.d.ts +27 -0
  373. package/dist/pricing/contracts/money.js +1 -0
  374. package/dist/pricing/contracts/offer.d.ts +52 -0
  375. package/dist/pricing/contracts/offer.js +4 -0
  376. package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
  377. package/dist/pricing/contracts/offerPolicy.js +5 -0
  378. package/dist/pricing/contracts/policy.d.ts +22 -0
  379. package/dist/pricing/contracts/policy.js +3 -0
  380. package/dist/pricing/contracts/provenance.d.ts +24 -0
  381. package/dist/pricing/contracts/provenance.js +2 -0
  382. package/dist/pricing/contracts/quote.d.ts +94 -0
  383. package/dist/pricing/contracts/quote.js +1 -0
  384. package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
  385. package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
  386. package/dist/pricing/dbBackedCommerceQuotePort.d.ts +66 -0
  387. package/dist/pricing/dbBackedCommerceQuotePort.js +206 -0
  388. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
  389. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
  390. package/dist/pricing/index.d.ts +51 -0
  391. package/dist/pricing/index.js +36 -0
  392. package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
  393. package/dist/pricing/offerPolicyReadiness.js +79 -0
  394. package/dist/pricing/offerPolicySchemas.d.ts +25 -0
  395. package/dist/pricing/offerPolicySchemas.js +15 -0
  396. package/dist/pricing/offerPricingSchemas.d.ts +315 -0
  397. package/dist/pricing/offerPricingSchemas.js +65 -0
  398. package/dist/pricing/ports.d.ts +13 -0
  399. package/dist/pricing/ports.js +9 -0
  400. package/dist/pricing/pricingPolicyToken.d.ts +39 -0
  401. package/dist/pricing/pricingPolicyToken.js +84 -0
  402. package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
  403. package/dist/pricing/quoteAcquisitionTerms.js +4 -0
  404. package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
  405. package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
  406. package/dist/pricing/quotePorts.d.ts +30 -0
  407. package/dist/pricing/quotePorts.js +20 -0
  408. package/dist/pricing/quotePrimitives.d.ts +13 -0
  409. package/dist/pricing/quotePrimitives.js +18 -0
  410. package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
  411. package/dist/pricing/quotePromotionEvidence.js +73 -0
  412. package/dist/pricing/quoteSchemas.d.ts +1176 -0
  413. package/dist/pricing/quoteSchemas.js +196 -0
  414. package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
  415. package/dist/pricing/subscriptionPricePolicy.js +96 -0
  416. package/dist/pricing/taxProfileGuard.d.ts +2 -0
  417. package/dist/pricing/taxProfileGuard.js +24 -0
  418. package/dist/pricing/validation.d.ts +2 -0
  419. package/dist/pricing/validation.js +3 -0
  420. package/dist/promotions/adminBindings.d.ts +72 -0
  421. package/dist/promotions/adminBindings.js +1 -0
  422. package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
  423. package/dist/promotions/adminPromotionCodesContracts.js +211 -0
  424. package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
  425. package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
  426. package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
  427. package/dist/promotions/adminPromotionCodesHandler.js +194 -0
  428. package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
  429. package/dist/promotions/adminPromotionsContracts.js +99 -0
  430. package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
  431. package/dist/promotions/adminPromotionsDataPort.js +1 -0
  432. package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
  433. package/dist/promotions/adminPromotionsHandler.js +47 -0
  434. package/dist/promotions/contracts/index.d.ts +387 -0
  435. package/dist/promotions/contracts/index.js +1 -0
  436. package/dist/promotions/index.d.ts +22 -0
  437. package/dist/promotions/index.js +21 -0
  438. package/dist/promotions/promoDataPort.d.ts +47 -0
  439. package/dist/promotions/promoDataPort.js +14 -0
  440. package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
  441. package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
  442. package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
  443. package/dist/promotions/promotionBenefitDefinition.js +33 -0
  444. package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
  445. package/dist/promotions/promotionClaimSweepPort.js +8 -0
  446. package/dist/promotions/promotionClaims.d.ts +5 -0
  447. package/dist/promotions/promotionClaims.js +57 -0
  448. package/dist/promotions/promotionCodePreview.d.ts +11 -0
  449. package/dist/promotions/promotionCodePreview.js +154 -0
  450. package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
  451. package/dist/promotions/promotionCodeQuotePort.js +1 -0
  452. package/dist/promotions/promotionQuoteAcceptance.d.ts +65 -0
  453. package/dist/promotions/promotionQuoteAcceptance.js +202 -0
  454. package/dist/promotions/promotionQuoteBinding.d.ts +71 -0
  455. package/dist/promotions/promotionQuoteBinding.js +85 -0
  456. package/dist/promotions/quoteAutomaticPromotions.d.ts +24 -0
  457. package/dist/promotions/quoteAutomaticPromotions.js +36 -0
  458. package/dist/promotions/quotePromoDiscounts.d.ts +32 -0
  459. package/dist/promotions/quotePromoDiscounts.js +140 -0
  460. package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
  461. package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
  462. package/dist/promotions/quotePromotionV2.d.ts +29 -0
  463. package/dist/promotions/quotePromotionV2.js +174 -0
  464. package/dist/promotions/sql.d.ts +14 -0
  465. package/dist/promotions/sql.js +5 -0
  466. package/dist/promotions/validation.d.ts +2 -0
  467. package/dist/promotions/validation.js +3 -0
  468. package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
  469. package/dist/shipping/addressCanonLookupHandler.js +105 -0
  470. package/dist/shipping/addressCanonical.d.ts +2 -0
  471. package/dist/shipping/addressCanonical.js +26 -0
  472. package/dist/shipping/addressPorts.d.ts +39 -0
  473. package/dist/shipping/addressPorts.js +1 -0
  474. package/dist/shipping/addressSchemas.d.ts +30 -0
  475. package/dist/shipping/addressSchemas.js +1 -0
  476. package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
  477. package/dist/shipping/adminShippingRateHandler.js +64 -0
  478. package/dist/shipping/contracts/address.d.ts +144 -0
  479. package/dist/shipping/contracts/address.js +1 -0
  480. package/dist/shipping/contracts/delivery.d.ts +110 -0
  481. package/dist/shipping/contracts/delivery.js +1 -0
  482. package/dist/shipping/contracts/index.d.ts +3 -0
  483. package/dist/shipping/contracts/index.js +1 -0
  484. package/dist/shipping/contracts/shippingRate.d.ts +11 -0
  485. package/dist/shipping/contracts/shippingRate.js +1 -0
  486. package/dist/shipping/customerAddressesHandler.d.ts +5 -0
  487. package/dist/shipping/customerAddressesHandler.js +80 -0
  488. package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
  489. package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
  490. package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
  491. package/dist/shipping/deliverySelectionHandler.js +77 -0
  492. package/dist/shipping/deliverySelectionPort.d.ts +8 -0
  493. package/dist/shipping/deliverySelectionPort.js +1 -0
  494. package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
  495. package/dist/shipping/deliverySelectionSummary.js +53 -0
  496. package/dist/shipping/handlerSchemas.d.ts +21 -0
  497. package/dist/shipping/handlerSchemas.js +1 -0
  498. package/dist/shipping/index.d.ts +17 -0
  499. package/dist/shipping/index.js +8 -0
  500. package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
  501. package/dist/shipping/pickupPointSearchPort.js +1 -0
  502. package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
  503. package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
  504. package/dist/shipping/shippingRatePorts.d.ts +52 -0
  505. package/dist/shipping/shippingRatePorts.js +1 -0
  506. package/package.json +72 -2
  507. package/release-gates.json +249 -8
  508. package/sql/20260807140000_read_parity_catalog.sql +94 -0
  509. package/sql/20260807190000_comms_outbox_rail.sql +329 -0
  510. package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
  511. package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
  512. package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
  513. package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
  514. package/sql/20260813120000_communications_control_plane.sql +568 -0
  515. package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
  516. package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
  517. package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
  518. package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
  519. package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
  520. package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
  521. package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
  522. package/sql/20261019200000_communications_native_operations.sql +68 -0
  523. package/src/catalog/activeSkuSelection.ts +20 -0
  524. package/src/catalog/adminBindings.ts +59 -0
  525. package/src/catalog/adminBundleHandler.ts +165 -0
  526. package/src/catalog/adminBundleReadHandler.ts +261 -0
  527. package/src/catalog/adminBundleWritePort.ts +102 -0
  528. package/src/catalog/bundleAvailability.ts +129 -0
  529. package/src/catalog/bundleAvailabilityService.ts +171 -0
  530. package/src/catalog/bundleBindings.ts +29 -0
  531. package/src/catalog/bundleCatalogQueries.ts +98 -0
  532. package/src/catalog/bundleCatalogReadPort.ts +177 -0
  533. package/src/catalog/bundleCatalogStore.ts +269 -0
  534. package/src/catalog/bundleRuleCodes.ts +134 -0
  535. package/src/catalog/bundleRules.ts +153 -0
  536. package/src/catalog/catalogDraftStore.ts +71 -0
  537. package/src/catalog/catalogSkuEnvelope.ts +214 -0
  538. package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
  539. package/src/catalog/contracts/bundleAdmin.ts +20 -0
  540. package/src/catalog/contracts/bundleAvailability.ts +5 -0
  541. package/src/catalog/contracts/bundleRead.ts +13 -0
  542. package/src/catalog/contracts/draft.ts +69 -0
  543. package/src/catalog/contracts/foundation.ts +104 -0
  544. package/src/catalog/contracts/index.ts +14 -0
  545. package/src/catalog/contracts/lifecycle.ts +74 -0
  546. package/src/catalog/contracts/productMediaContracts.ts +170 -0
  547. package/src/catalog/contracts/productTypes.ts +35 -0
  548. package/src/catalog/contracts/resolveProductMedia.ts +85 -0
  549. package/src/catalog/contracts/sellable.ts +12 -0
  550. package/src/catalog/draftHandler.ts +105 -0
  551. package/src/catalog/draftPorts.ts +18 -0
  552. package/src/catalog/draftSchemas.ts +88 -0
  553. package/src/catalog/draftValidation.ts +100 -0
  554. package/src/catalog/foundationSchemas.ts +123 -0
  555. package/src/catalog/index.ts +43 -0
  556. package/src/catalog/mediaSourcePack.ts +219 -0
  557. package/src/catalog/productTypeSchemas.ts +70 -0
  558. package/src/catalog/publicationPorts.ts +44 -0
  559. package/src/catalog/readPorts.ts +61 -0
  560. package/src/catalog/referenceCatalogHandler.ts +39 -0
  561. package/src/catalog/resolvedSkuProjection.ts +76 -0
  562. package/src/catalog/sellableBundleHandler.ts +133 -0
  563. package/src/catalog/sellableSchemas.ts +33 -0
  564. package/src/catalog/sql.ts +14 -0
  565. package/src/catalog/validation.ts +3 -0
  566. package/src/communications/accountingDocumentDelivery.ts +63 -0
  567. package/src/communications/authEmailError.ts +10 -0
  568. package/src/communications/authEmailPorts.ts +2 -0
  569. package/src/communications/authSendEmailHook.ts +310 -0
  570. package/src/communications/capturedTransactionalDelivery.ts +29 -0
  571. package/src/communications/communicationSync.ts +96 -0
  572. package/src/communications/communicationSyncWorker.ts +302 -0
  573. package/src/communications/consentPolicy.ts +82 -0
  574. package/src/communications/consentPurposeRegistry.ts +31 -0
  575. package/src/communications/contracts/abandonedCart.ts +64 -0
  576. package/src/communications/contracts/authEmail.ts +73 -0
  577. package/src/communications/contracts/backInStock.ts +69 -0
  578. package/src/communications/contracts/blocks.ts +47 -0
  579. package/src/communications/contracts/checkoutExpired.ts +62 -0
  580. package/src/communications/contracts/checkoutRecovery.ts +68 -0
  581. package/src/communications/contracts/commerceEmailContent.ts +189 -0
  582. package/src/communications/contracts/commerceEngagementEmailContent.ts +106 -0
  583. package/src/communications/contracts/consent.ts +13 -0
  584. package/src/communications/contracts/email.ts +210 -0
  585. package/src/communications/contracts/guideCard.ts +34 -0
  586. package/src/communications/contracts/index.ts +52 -0
  587. package/src/communications/contracts/inquiryContent.ts +53 -0
  588. package/src/communications/contracts/invoiceDocumentContent.ts +57 -0
  589. package/src/communications/contracts/marketingAutomation.ts +45 -0
  590. package/src/communications/contracts/marketingSync.ts +109 -0
  591. package/src/communications/contracts/newsletter.ts +78 -0
  592. package/src/communications/contracts/newsletterWebhook.ts +23 -0
  593. package/src/communications/contracts/optOutContent.ts +23 -0
  594. package/src/communications/contracts/orderCanceled.ts +64 -0
  595. package/src/communications/contracts/orderDraft.ts +112 -0
  596. package/src/communications/contracts/orderPaid.ts +147 -0
  597. package/src/communications/contracts/orderRefunded.ts +63 -0
  598. package/src/communications/contracts/paymentFailed.ts +59 -0
  599. package/src/communications/contracts/publicIntake.ts +53 -0
  600. package/src/communications/contracts/render.ts +271 -0
  601. package/src/communications/contracts/reorderReminder.ts +59 -0
  602. package/src/communications/contracts/returnEmails.ts +113 -0
  603. package/src/communications/contracts/reviewEffects.ts +60 -0
  604. package/src/communications/contracts/reviewRequest.ts +59 -0
  605. package/src/communications/contracts/roleNotificationContent.ts +65 -0
  606. package/src/communications/contracts/shipmentDelivered.ts +59 -0
  607. package/src/communications/contracts/shipmentDispatched.ts +77 -0
  608. package/src/communications/contracts/shipmentException.ts +62 -0
  609. package/src/communications/contracts/subscriptionActivationActionRequired.ts +37 -0
  610. package/src/communications/contracts/subscriptionAddressChanged.ts +56 -0
  611. package/src/communications/contracts/subscriptionCancelled.ts +57 -0
  612. package/src/communications/contracts/subscriptionCycleSkipped.ts +80 -0
  613. package/src/communications/contracts/subscriptionDeliveryRescheduled.ts +79 -0
  614. package/src/communications/contracts/subscriptionEmailContent.ts +163 -0
  615. package/src/communications/contracts/subscriptionPackageChanged.ts +59 -0
  616. package/src/communications/contracts/subscriptionPauseReminder.ts +63 -0
  617. package/src/communications/contracts/subscriptionPaused.ts +58 -0
  618. package/src/communications/contracts/subscriptionPaymentExpired.ts +83 -0
  619. package/src/communications/contracts/subscriptionPaymentFailed.ts +117 -0
  620. package/src/communications/contracts/subscriptionPaymentRecovered.ts +50 -0
  621. package/src/communications/contracts/subscriptionRenewalAtRisk.ts +85 -0
  622. package/src/communications/contracts/subscriptionRenewalUpcoming.ts +103 -0
  623. package/src/communications/contracts/subscriptionResumed.ts +60 -0
  624. package/src/communications/contracts/subscriptionWelcome.ts +102 -0
  625. package/src/communications/contracts/subscriptionWinback.ts +62 -0
  626. package/src/communications/contracts/transactionalDelivery.ts +43 -0
  627. package/src/communications/contracts/types.ts +58 -0
  628. package/src/communications/contribution.ts +33 -0
  629. package/src/communications/deliveryReconciliation.ts +108 -0
  630. package/src/communications/emailEgress.ts +111 -0
  631. package/src/communications/emailHealthAssessment.ts +106 -0
  632. package/src/communications/emailHealthMetricsPort.ts +17 -0
  633. package/src/communications/emailNotificationControl.ts +28 -0
  634. package/src/communications/emailObservation.ts +43 -0
  635. package/src/communications/emailTransport.ts +167 -0
  636. package/src/communications/errors.ts +34 -0
  637. package/src/communications/index.ts +47 -0
  638. package/src/communications/integrationEventSignature.ts +49 -0
  639. package/src/communications/klaviyo/consentPush.ts +19 -0
  640. package/src/communications/klaviyo/index.ts +649 -0
  641. package/src/communications/managedTransactionalDeliveryStore.ts +59 -0
  642. package/src/communications/nativeOperations.ts +40 -0
  643. package/src/communications/newsletterIntegrationContracts.ts +154 -0
  644. package/src/communications/newsletterWebhookPort.ts +104 -0
  645. package/src/communications/newsletterWebhookProcessing.ts +102 -0
  646. package/src/communications/originPolicy.ts +151 -0
  647. package/src/communications/postgresCommunicationSync.ts +127 -0
  648. package/src/communications/postgresConsentEvaluator.ts +49 -0
  649. package/src/communications/postgresDeliveryReconciliation.ts +101 -0
  650. package/src/communications/postgresNativeOperations.ts +55 -0
  651. package/src/communications/postgresNotificationRecipients.ts +57 -0
  652. package/src/communications/postgresTransactionalDeliveryStore.ts +79 -0
  653. package/src/communications/preferencesToken.ts +62 -0
  654. package/src/communications/publicPreferenceToken.ts +68 -0
  655. package/src/communications/resend/index.ts +350 -0
  656. package/src/communications/shipmentNotifications.ts +78 -0
  657. package/src/communications/sql.ts +7 -0
  658. package/src/communications/standardWebhooks.ts +84 -0
  659. package/src/communications/status.ts +51 -0
  660. package/src/communications/transactionalDelivery.ts +114 -0
  661. package/src/communications/transactionalDeliveryIdentifier.ts +9 -0
  662. package/src/communications/unsubscribeToken.ts +186 -0
  663. package/src/communications/validation.ts +3 -0
  664. package/src/composition/catalogContribution.ts +32 -0
  665. package/src/composition/index.ts +1 -0
  666. package/src/http/index.ts +1 -0
  667. package/src/http/webhookSignature.ts +96 -0
  668. package/src/inventory/adminInventory.ts +251 -0
  669. package/src/inventory/contracts/forecast.ts +96 -0
  670. package/src/inventory/contracts/index.ts +18 -0
  671. package/src/inventory/contracts/inventory.ts +14 -0
  672. package/src/inventory/contracts/newPurchaseStock.ts +23 -0
  673. package/src/inventory/contracts/reservation.ts +20 -0
  674. package/src/inventory/forecast.ts +239 -0
  675. package/src/inventory/index.ts +21 -0
  676. package/src/inventory/inventoryHandlers.ts +215 -0
  677. package/src/inventory/newPurchaseStockAvailability.ts +120 -0
  678. package/src/inventory/ports.ts +46 -0
  679. package/src/inventory/renewalReservation.ts +88 -0
  680. package/src/inventory/reservationPolicy.ts +27 -0
  681. package/src/inventory/schemas.ts +128 -0
  682. package/src/inventory/sql.ts +5 -0
  683. package/src/inventory/subscriptionForecastCalculation.ts +157 -0
  684. package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
  685. package/src/pricing/acquisitionOfferPricing.ts +38 -0
  686. package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
  687. package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
  688. package/src/pricing/acquisitionQuoteBinding.ts +12 -0
  689. package/src/pricing/acquisitionReadiness.ts +148 -0
  690. package/src/pricing/acquisitionTerms.ts +125 -0
  691. package/src/pricing/acquisitionTermsSchemas.ts +52 -0
  692. package/src/pricing/catalogFactsProvenance.ts +102 -0
  693. package/src/pricing/catalogFactsSchemas.ts +69 -0
  694. package/src/pricing/commerceOfferPricing.ts +224 -0
  695. package/src/pricing/commerceOfferPricingCache.ts +53 -0
  696. package/src/pricing/commerceOfferPricingHandler.ts +137 -0
  697. package/src/pricing/commercePriceAuthority.ts +104 -0
  698. package/src/pricing/commercePricingPolicyErrors.ts +88 -0
  699. package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
  700. package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
  701. package/src/pricing/commerceQuoteHandler.ts +184 -0
  702. package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
  703. package/src/pricing/contracts/acquisitionTerms.ts +26 -0
  704. package/src/pricing/contracts/authority.ts +44 -0
  705. package/src/pricing/contracts/evidence.ts +25 -0
  706. package/src/pricing/contracts/index.ts +17 -0
  707. package/src/pricing/contracts/money.ts +32 -0
  708. package/src/pricing/contracts/offer.ts +42 -0
  709. package/src/pricing/contracts/offerPolicy.ts +17 -0
  710. package/src/pricing/contracts/policy.ts +26 -0
  711. package/src/pricing/contracts/provenance.ts +19 -0
  712. package/src/pricing/contracts/quote.ts +106 -0
  713. package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
  714. package/src/pricing/dbBackedCommerceQuotePort.ts +260 -0
  715. package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
  716. package/src/pricing/index.ts +52 -0
  717. package/src/pricing/offerPolicyReadiness.ts +110 -0
  718. package/src/pricing/offerPolicySchemas.ts +23 -0
  719. package/src/pricing/offerPricingSchemas.ts +92 -0
  720. package/src/pricing/ports.ts +17 -0
  721. package/src/pricing/pricingPolicyToken.ts +119 -0
  722. package/src/pricing/quoteAcquisitionTerms.ts +10 -0
  723. package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
  724. package/src/pricing/quotePorts.ts +58 -0
  725. package/src/pricing/quotePrimitives.ts +21 -0
  726. package/src/pricing/quotePromotionEvidence.ts +82 -0
  727. package/src/pricing/quoteSchemas.ts +230 -0
  728. package/src/pricing/subscriptionPricePolicy.ts +113 -0
  729. package/src/pricing/taxProfileGuard.ts +33 -0
  730. package/src/pricing/validation.ts +3 -0
  731. package/src/promotions/adminBindings.ts +53 -0
  732. package/src/promotions/adminPromotionCodesContracts.ts +244 -0
  733. package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
  734. package/src/promotions/adminPromotionCodesHandler.ts +254 -0
  735. package/src/promotions/adminPromotionsContracts.ts +121 -0
  736. package/src/promotions/adminPromotionsDataPort.ts +48 -0
  737. package/src/promotions/adminPromotionsHandler.ts +72 -0
  738. package/src/promotions/contracts/index.ts +387 -0
  739. package/src/promotions/index.ts +22 -0
  740. package/src/promotions/promoDataPort.ts +65 -0
  741. package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
  742. package/src/promotions/promotionBenefitDefinition.ts +48 -0
  743. package/src/promotions/promotionClaimSweepPort.ts +23 -0
  744. package/src/promotions/promotionClaims.ts +70 -0
  745. package/src/promotions/promotionCodePreview.ts +183 -0
  746. package/src/promotions/promotionCodeQuotePort.ts +35 -0
  747. package/src/promotions/promotionQuoteAcceptance.ts +284 -0
  748. package/src/promotions/promotionQuoteBinding.ts +158 -0
  749. package/src/promotions/quoteAutomaticPromotions.ts +58 -0
  750. package/src/promotions/quotePromoDiscounts.ts +185 -0
  751. package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
  752. package/src/promotions/quotePromotionV2.ts +223 -0
  753. package/src/promotions/sql.ts +5 -0
  754. package/src/promotions/validation.ts +3 -0
  755. package/src/shipping/addressCanonLookupHandler.ts +114 -0
  756. package/src/shipping/addressCanonical.ts +31 -0
  757. package/src/shipping/addressPorts.ts +48 -0
  758. package/src/shipping/addressSchemas.ts +28 -0
  759. package/src/shipping/adminShippingRateHandler.ts +72 -0
  760. package/src/shipping/contracts/address.ts +71 -0
  761. package/src/shipping/contracts/delivery.ts +114 -0
  762. package/src/shipping/contracts/index.ts +18 -0
  763. package/src/shipping/contracts/shippingRate.ts +7 -0
  764. package/src/shipping/customerAddressesHandler.ts +100 -0
  765. package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
  766. package/src/shipping/deliverySelectionHandler.ts +94 -0
  767. package/src/shipping/deliverySelectionPort.ts +10 -0
  768. package/src/shipping/deliverySelectionSummary.ts +56 -0
  769. package/src/shipping/handlerSchemas.ts +19 -0
  770. package/src/shipping/index.ts +23 -0
  771. package/src/shipping/pickupPointSearchPort.ts +8 -0
  772. package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
  773. package/src/shipping/shippingRatePorts.ts +36 -0
@@ -0,0 +1,1083 @@
1
+ -- Public automatic-subscription-renewal rail.
2
+ --
3
+ -- CHANGE. This forward adds the neutral, durable operation spine required to move one due
4
+ -- subscription through a fenced worker claim, stock admission and reservation, cycle/order/
5
+ -- settlement preparation, and a terminal acknowledgement. One immutable operation fingerprint
6
+ -- is copied onto every business relation the operation owns. The same `(subscription,
7
+ -- scheduled_at)` can therefore never become two cycles, orders, reservations or settlement
8
+ -- intents, even when leases expire or workers race.
9
+ --
10
+ -- WHY. The public catalogue already owns subscriptions, observed stock, orders and settlement
11
+ -- truth, but no runtime could compose them into an unattended renewal. In particular, observed
12
+ -- stock was explicitly not ATP, and the settlement rail deliberately had no reservation. This
13
+ -- forward supplies those missing semantics without recreating a managed-provider schema.
14
+ --
15
+ -- SAFETY AND DEPARTURES. The canonical operation key is
16
+ -- `subscription:<uuid>:cycle:<ISO instant>`. A SHA-256 fingerprint over the neutral immutable
17
+ -- snapshot is supplied by the application, shape-checked here, persisted unchanged and compared
18
+ -- on every replay. PostgreSQL does not pretend it contacted a payment provider: a successful
19
+ -- terminal acknowledgement requires a non-empty opaque external reference and delegates money
20
+ -- truth and the single subscription-schedule advance to `commerce_record_settlement`; this
21
+ -- forward never advances `next_cycle_at` a second time. An indeterminate provider call remains
22
+ -- `attempt_prepared`; it does not release stock, open dunning or authorize another attempt.
23
+ -- Inventory refusal and pre-payment expiry are durable neutral refusals and never customer
24
+ -- dunning. There are no provider enums, gateway payloads, webhook tables, retry ladders, roles,
25
+ -- RLS policies, grants or SECURITY DEFINER routines below. All functions are invoker-rights with
26
+ -- fixed search paths; the host application remains the security boundary.
27
+ --
28
+ -- ROLLBACK. Disable the direct renewal adopter first. The new relations/functions and nullable
29
+ -- linkage columns are capability-local. Existing lifecycle, stock and settlement rows remain
30
+ -- valid after those links are removed; no historical row is rewritten by this migration.
31
+
32
+ CREATE TABLE public.subscription_renewal_terms (
33
+ subscription_id uuid PRIMARY KEY REFERENCES public.subscriptions(id) ON DELETE CASCADE,
34
+ next_cycle_number integer NOT NULL DEFAULT 1,
35
+ currency_code text NOT NULL,
36
+ stock_source_key text NOT NULL,
37
+ settlement_channel_key text NOT NULL,
38
+ unattended_charge_authorized_at timestamptz NOT NULL,
39
+ authorization_ref text NOT NULL,
40
+ payer_account_ref text,
41
+ delivery_admission text NOT NULL DEFAULT 'allowed',
42
+ delivery_block_reason text,
43
+ created_at timestamptz NOT NULL DEFAULT now(),
44
+ updated_at timestamptz NOT NULL DEFAULT now(),
45
+ CONSTRAINT subscription_renewal_terms_cycle_number_check CHECK (next_cycle_number > 0),
46
+ CONSTRAINT subscription_renewal_terms_currency_check CHECK (currency_code ~ '^[A-Z]{3}$'),
47
+ CONSTRAINT subscription_renewal_terms_stock_source_check
48
+ CHECK (stock_source_key ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),
49
+ CONSTRAINT subscription_renewal_terms_channel_check
50
+ CHECK (btrim(settlement_channel_key) <> '' AND char_length(settlement_channel_key) <= 64),
51
+ CONSTRAINT subscription_renewal_terms_authorization_ref_check
52
+ CHECK (btrim(authorization_ref) <> '' AND char_length(authorization_ref) <= 180),
53
+ CONSTRAINT subscription_renewal_terms_payer_account_ref_check
54
+ CHECK (payer_account_ref IS NULL OR (
55
+ btrim(payer_account_ref) <> '' AND char_length(payer_account_ref) <= 180)),
56
+ CONSTRAINT subscription_renewal_terms_delivery_admission_check
57
+ CHECK (delivery_admission IN ('allowed', 'blocked')),
58
+ CONSTRAINT subscription_renewal_terms_delivery_reason_check CHECK (
59
+ (delivery_admission = 'allowed' AND delivery_block_reason IS NULL)
60
+ OR (delivery_admission = 'blocked' AND btrim(COALESCE(delivery_block_reason, '')) <> '')
61
+ )
62
+ );
63
+
64
+ COMMENT ON COLUMN public.subscription_renewal_terms.authorization_ref IS
65
+ 'Opaque host evidence that unattended charging was authorized; stored and compared, never parsed.';
66
+ COMMENT ON COLUMN public.subscription_renewal_terms.settlement_channel_key IS
67
+ 'Opaque settlement-channel selection. This rail contains no provider vocabulary.';
68
+
69
+ CREATE TABLE public.subscription_renewal_term_lines (
70
+ subscription_id uuid NOT NULL REFERENCES public.subscription_renewal_terms(subscription_id)
71
+ ON DELETE CASCADE,
72
+ line_ordinal integer NOT NULL,
73
+ sku text NOT NULL,
74
+ quantity integer NOT NULL,
75
+ unit_amount_minor bigint NOT NULL,
76
+ created_at timestamptz NOT NULL DEFAULT now(),
77
+ updated_at timestamptz NOT NULL DEFAULT now(),
78
+ PRIMARY KEY (subscription_id, line_ordinal),
79
+ CONSTRAINT subscription_renewal_term_lines_sku_key UNIQUE (subscription_id, sku),
80
+ CONSTRAINT subscription_renewal_term_lines_ordinal_check CHECK (line_ordinal > 0),
81
+ CONSTRAINT subscription_renewal_term_lines_sku_check CHECK (btrim(sku) <> ''),
82
+ CONSTRAINT subscription_renewal_term_lines_quantity_check CHECK (quantity > 0),
83
+ CONSTRAINT subscription_renewal_term_lines_amount_check CHECK (unit_amount_minor >= 0)
84
+ );
85
+
86
+ CREATE TABLE public.subscription_renewal_operations (
87
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
88
+ subscription_id uuid NOT NULL REFERENCES public.subscriptions(id) ON DELETE RESTRICT,
89
+ scheduled_at timestamptz NOT NULL,
90
+ cycle_number integer NOT NULL,
91
+ identity_key text NOT NULL,
92
+ operation_fingerprint text NOT NULL,
93
+ cycle_snapshot jsonb NOT NULL,
94
+ state text NOT NULL DEFAULT 'claimed',
95
+ claim_token uuid NOT NULL,
96
+ claim_generation bigint NOT NULL DEFAULT 1,
97
+ claimed_by text NOT NULL,
98
+ claimed_at timestamptz NOT NULL,
99
+ lease_expires_at timestamptz NOT NULL,
100
+ attempt_prepared_at timestamptz,
101
+ external_attempt_ref text,
102
+ external_attempt_acknowledged_at timestamptz,
103
+ terminal_outcome text,
104
+ terminal_reason text,
105
+ terminal_at timestamptz,
106
+ created_at timestamptz NOT NULL DEFAULT now(),
107
+ updated_at timestamptz NOT NULL DEFAULT now(),
108
+ CONSTRAINT subscription_renewal_operations_identity_key_key UNIQUE (identity_key),
109
+ CONSTRAINT subscription_renewal_operations_cycle_key UNIQUE (subscription_id, scheduled_at),
110
+ CONSTRAINT subscription_renewal_operations_cycle_number_check CHECK (cycle_number > 0),
111
+ CONSTRAINT subscription_renewal_operations_identity_check
112
+ CHECK (char_length(btrim(identity_key)) BETWEEN 48 AND 180),
113
+ CONSTRAINT subscription_renewal_operations_fingerprint_check
114
+ CHECK (operation_fingerprint ~ '^[0-9a-f]{64}$'),
115
+ CONSTRAINT subscription_renewal_operations_snapshot_check
116
+ CHECK (jsonb_typeof(cycle_snapshot) = 'object'),
117
+ CONSTRAINT subscription_renewal_operations_state_check
118
+ CHECK (state IN ('claimed', 'reserved', 'attempt_prepared', 'succeeded', 'refused')),
119
+ CONSTRAINT subscription_renewal_operations_claim_generation_check CHECK (claim_generation > 0),
120
+ CONSTRAINT subscription_renewal_operations_claimed_by_check CHECK (btrim(claimed_by) <> ''),
121
+ CONSTRAINT subscription_renewal_operations_lease_check CHECK (lease_expires_at > claimed_at),
122
+ CONSTRAINT subscription_renewal_operations_external_attempt_check CHECK (
123
+ (external_attempt_ref IS NULL AND external_attempt_acknowledged_at IS NULL)
124
+ OR (btrim(COALESCE(external_attempt_ref, '')) <> ''
125
+ AND char_length(external_attempt_ref) <= 240
126
+ AND external_attempt_acknowledged_at IS NOT NULL)
127
+ ),
128
+ CONSTRAINT subscription_renewal_operations_terminal_check CHECK (
129
+ (state IN ('succeeded', 'refused')
130
+ AND terminal_outcome = CASE state WHEN 'succeeded' THEN 'succeeded' ELSE 'refused' END
131
+ AND terminal_at IS NOT NULL)
132
+ OR (state NOT IN ('succeeded', 'refused')
133
+ AND terminal_outcome IS NULL AND terminal_reason IS NULL AND terminal_at IS NULL)
134
+ )
135
+ );
136
+
137
+ -- squawk-ignore require-concurrent-index-creation
138
+ CREATE INDEX subscription_renewal_operations_due_recovery_idx
139
+ ON public.subscription_renewal_operations (state, lease_expires_at, scheduled_at);
140
+
141
+ CREATE TABLE public.subscription_renewal_outcomes (
142
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
143
+ renewal_operation_id uuid NOT NULL REFERENCES public.subscription_renewal_operations(id)
144
+ ON DELETE CASCADE,
145
+ operation_fingerprint text NOT NULL,
146
+ event_key text NOT NULL,
147
+ outcome text NOT NULL,
148
+ reason text,
149
+ external_ref text,
150
+ occurred_at timestamptz NOT NULL,
151
+ created_at timestamptz NOT NULL DEFAULT now(),
152
+ CONSTRAINT subscription_renewal_outcomes_event_key_key UNIQUE (renewal_operation_id, event_key),
153
+ CONSTRAINT subscription_renewal_outcomes_fingerprint_check
154
+ CHECK (operation_fingerprint ~ '^[0-9a-f]{64}$'),
155
+ CONSTRAINT subscription_renewal_outcomes_event_key_check CHECK (btrim(event_key) <> ''),
156
+ CONSTRAINT subscription_renewal_outcomes_outcome_check CHECK (outcome IN ('succeeded', 'refused')),
157
+ CONSTRAINT subscription_renewal_outcomes_external_ref_check CHECK (
158
+ external_ref IS NULL OR (btrim(external_ref) <> '' AND char_length(external_ref) <= 240)
159
+ )
160
+ );
161
+
162
+ CREATE TABLE public.fulfillment_inventory_reservations (
163
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
164
+ renewal_operation_id uuid NOT NULL REFERENCES public.subscription_renewal_operations(id)
165
+ ON DELETE RESTRICT,
166
+ operation_fingerprint text NOT NULL,
167
+ source_key text NOT NULL,
168
+ status text NOT NULL DEFAULT 'held',
169
+ expires_at timestamptz,
170
+ terminal_reason text,
171
+ created_at timestamptz NOT NULL DEFAULT now(),
172
+ updated_at timestamptz NOT NULL DEFAULT now(),
173
+ CONSTRAINT fulfillment_inventory_reservations_operation_key UNIQUE (renewal_operation_id),
174
+ CONSTRAINT fulfillment_inventory_reservations_fingerprint_check
175
+ CHECK (operation_fingerprint ~ '^[0-9a-f]{64}$'),
176
+ CONSTRAINT fulfillment_inventory_reservations_source_check
177
+ CHECK (source_key ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),
178
+ CONSTRAINT fulfillment_inventory_reservations_status_check
179
+ CHECK (status IN ('held', 'committed', 'consumed', 'released', 'expired')),
180
+ CONSTRAINT fulfillment_inventory_reservations_expiry_check CHECK (
181
+ (status = 'held' AND expires_at IS NOT NULL AND expires_at > created_at)
182
+ OR (status <> 'held')
183
+ ),
184
+ CONSTRAINT fulfillment_inventory_reservations_terminal_reason_check CHECK (
185
+ (status IN ('released', 'expired') AND btrim(COALESCE(terminal_reason, '')) <> '')
186
+ OR (status NOT IN ('released', 'expired') AND terminal_reason IS NULL)
187
+ )
188
+ );
189
+
190
+ CREATE TABLE public.fulfillment_inventory_reservation_lines (
191
+ reservation_id uuid NOT NULL REFERENCES public.fulfillment_inventory_reservations(id)
192
+ ON DELETE CASCADE,
193
+ operation_fingerprint text NOT NULL,
194
+ sku text NOT NULL,
195
+ quantity integer NOT NULL,
196
+ PRIMARY KEY (reservation_id, sku),
197
+ CONSTRAINT fulfillment_inventory_reservation_lines_fingerprint_check
198
+ CHECK (operation_fingerprint ~ '^[0-9a-f]{64}$'),
199
+ CONSTRAINT fulfillment_inventory_reservation_lines_sku_check CHECK (btrim(sku) <> ''),
200
+ CONSTRAINT fulfillment_inventory_reservation_lines_quantity_check CHECK (quantity > 0)
201
+ );
202
+
203
+ -- Nullable links preserve every row created by earlier forwards. New renewal rows always populate
204
+ -- both columns, and the functions below compare the fingerprint to the root before mutating.
205
+ ALTER TABLE public.subscription_cycles
206
+ ADD COLUMN renewal_operation_id uuid,
207
+ ADD COLUMN renewal_operation_fingerprint text;
208
+ ALTER TABLE public.subscription_cycles
209
+ ADD CONSTRAINT subscription_cycles_renewal_operation_fkey
210
+ FOREIGN KEY (renewal_operation_id) REFERENCES public.subscription_renewal_operations(id)
211
+ ON DELETE RESTRICT NOT VALID,
212
+ ADD CONSTRAINT subscription_cycles_renewal_fingerprint_check CHECK (
213
+ (renewal_operation_id IS NULL AND renewal_operation_fingerprint IS NULL)
214
+ OR (renewal_operation_id IS NOT NULL
215
+ AND renewal_operation_fingerprint ~ '^[0-9a-f]{64}$')
216
+ ) NOT VALID;
217
+
218
+ -- openlup:allow-unique-index: this partial index covers only renewal-owned rows. The column is
219
+ -- introduced immediately above, so no pre-existing row can enter the index; non-concurrent form
220
+ -- keeps the manifest forward transaction-safe while avoiding an index entry for every legacy row.
221
+ -- squawk-ignore require-concurrent-index-creation
222
+ CREATE UNIQUE INDEX subscription_cycles_renewal_operation_key
223
+ ON public.subscription_cycles (renewal_operation_id)
224
+ WHERE renewal_operation_id IS NOT NULL;
225
+
226
+ ALTER TABLE public.commerce_orders
227
+ ADD COLUMN renewal_operation_id uuid,
228
+ ADD COLUMN renewal_operation_fingerprint text;
229
+ ALTER TABLE public.commerce_orders
230
+ ADD CONSTRAINT commerce_orders_renewal_operation_fkey
231
+ FOREIGN KEY (renewal_operation_id) REFERENCES public.subscription_renewal_operations(id)
232
+ ON DELETE RESTRICT NOT VALID,
233
+ ADD CONSTRAINT commerce_orders_renewal_fingerprint_check CHECK (
234
+ (renewal_operation_id IS NULL AND renewal_operation_fingerprint IS NULL)
235
+ OR (renewal_operation_id IS NOT NULL
236
+ AND renewal_operation_fingerprint ~ '^[0-9a-f]{64}$')
237
+ ) NOT VALID;
238
+
239
+ -- openlup:allow-unique-index: the new nullable link has no pre-existing indexed rows; the partial
240
+ -- predicate preserves exact-one renewal ownership without indexing legacy orders.
241
+ -- squawk-ignore require-concurrent-index-creation
242
+ CREATE UNIQUE INDEX commerce_orders_renewal_operation_key
243
+ ON public.commerce_orders (renewal_operation_id)
244
+ WHERE renewal_operation_id IS NOT NULL;
245
+
246
+ ALTER TABLE public.commerce_settlement_intents
247
+ ADD COLUMN renewal_operation_id uuid,
248
+ ADD COLUMN renewal_operation_fingerprint text;
249
+ ALTER TABLE public.commerce_settlement_intents
250
+ ADD CONSTRAINT commerce_settlement_intents_renewal_operation_fkey
251
+ FOREIGN KEY (renewal_operation_id) REFERENCES public.subscription_renewal_operations(id)
252
+ ON DELETE RESTRICT NOT VALID,
253
+ ADD CONSTRAINT commerce_settlement_intents_renewal_fingerprint_check CHECK (
254
+ (renewal_operation_id IS NULL AND renewal_operation_fingerprint IS NULL)
255
+ OR (renewal_operation_id IS NOT NULL
256
+ AND renewal_operation_fingerprint ~ '^[0-9a-f]{64}$')
257
+ ) NOT VALID;
258
+
259
+ -- openlup:allow-unique-index: the new nullable link has no pre-existing indexed rows; the partial
260
+ -- predicate preserves exact-one renewal ownership without indexing legacy settlement intents.
261
+ -- squawk-ignore require-concurrent-index-creation
262
+ CREATE UNIQUE INDEX commerce_settlement_intents_renewal_operation_key
263
+ ON public.commerce_settlement_intents (renewal_operation_id)
264
+ WHERE renewal_operation_id IS NOT NULL;
265
+
266
+ CREATE FUNCTION public.subscription_renewal_identity(
267
+ p_subscription_id uuid,
268
+ p_scheduled_at timestamptz
269
+ )
270
+ RETURNS text
271
+ LANGUAGE sql
272
+ IMMUTABLE
273
+ SET search_path = pg_catalog
274
+ AS $$
275
+ SELECT 'subscription:' || p_subscription_id::text || ':cycle:'
276
+ || to_char(p_scheduled_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"');
277
+ $$;
278
+
279
+ CREATE FUNCTION public.subscription_renewal_operation_response(
280
+ p_operation public.subscription_renewal_operations,
281
+ p_acquired boolean,
282
+ p_replayed boolean,
283
+ p_reason text
284
+ )
285
+ RETURNS jsonb
286
+ LANGUAGE sql
287
+ STABLE
288
+ SET search_path = pg_catalog
289
+ AS $$
290
+ SELECT jsonb_build_object(
291
+ 'contractVersion', 'platform.subscription.renewal.v1',
292
+ 'acquired', p_acquired,
293
+ 'replayed', p_replayed,
294
+ 'reason', p_reason,
295
+ 'operationId', p_operation.id,
296
+ 'identityKey', p_operation.identity_key,
297
+ 'operationFingerprint', p_operation.operation_fingerprint,
298
+ 'subscriptionId', p_operation.subscription_id,
299
+ 'scheduledAt', p_operation.scheduled_at,
300
+ 'cycleNumber', p_operation.cycle_number,
301
+ 'state', p_operation.state,
302
+ 'claimToken', p_operation.claim_token,
303
+ 'claimGeneration', p_operation.claim_generation,
304
+ 'leaseExpiresAt', p_operation.lease_expires_at,
305
+ 'cycleSnapshot', p_operation.cycle_snapshot,
306
+ 'externalAttemptRef', p_operation.external_attempt_ref,
307
+ 'externalAttemptAcknowledgedAt', p_operation.external_attempt_acknowledged_at,
308
+ 'terminalOutcome', p_operation.terminal_outcome,
309
+ 'terminalReason', p_operation.terminal_reason,
310
+ 'terminalAt', p_operation.terminal_at,
311
+ 'reservationId', (
312
+ SELECT reservation.id FROM public.fulfillment_inventory_reservations reservation
313
+ WHERE reservation.renewal_operation_id = p_operation.id),
314
+ 'cycleId', (
315
+ SELECT cycle.id FROM public.subscription_cycles cycle
316
+ WHERE cycle.renewal_operation_id = p_operation.id),
317
+ 'orderId', (
318
+ SELECT order_row.id FROM public.commerce_orders order_row
319
+ WHERE order_row.renewal_operation_id = p_operation.id),
320
+ 'settlementIntentId', (
321
+ SELECT intent.id FROM public.commerce_settlement_intents intent
322
+ WHERE intent.renewal_operation_id = p_operation.id));
323
+ $$;
324
+
325
+ CREATE FUNCTION public.subscription_list_due_renewals(
326
+ p_as_of timestamptz DEFAULT now(),
327
+ p_limit integer DEFAULT 50
328
+ )
329
+ RETURNS TABLE (
330
+ subscription_id uuid,
331
+ client_id uuid,
332
+ scheduled_at timestamptz,
333
+ cadence_days integer,
334
+ cycle_number integer,
335
+ currency_code text,
336
+ stock_source_key text,
337
+ settlement_channel_key text,
338
+ authorization_ref text,
339
+ payer_account_ref text,
340
+ unattended_charge_authorized_at timestamptz,
341
+ delivery_admission text,
342
+ delivery_block_reason text
343
+ )
344
+ LANGUAGE sql
345
+ STABLE
346
+ SET search_path = pg_catalog
347
+ AS $$
348
+ SELECT subscription.id, subscription.client_id, subscription.next_cycle_at,
349
+ subscription.cadence_days, terms.next_cycle_number, terms.currency_code,
350
+ terms.stock_source_key, terms.settlement_channel_key, terms.authorization_ref,
351
+ terms.payer_account_ref,
352
+ terms.unattended_charge_authorized_at, terms.delivery_admission,
353
+ terms.delivery_block_reason
354
+ FROM public.subscriptions subscription
355
+ JOIN public.subscription_renewal_terms terms ON terms.subscription_id = subscription.id
356
+ WHERE subscription.status = 'active'
357
+ AND subscription.next_cycle_at IS NOT NULL
358
+ AND subscription.next_cycle_at <= COALESCE(p_as_of, now())
359
+ AND NOT EXISTS (
360
+ SELECT 1 FROM public.subscription_renewal_operations operation
361
+ WHERE operation.subscription_id = subscription.id
362
+ AND operation.scheduled_at = subscription.next_cycle_at
363
+ AND (
364
+ operation.state IN ('succeeded', 'refused')
365
+ OR operation.lease_expires_at > COALESCE(p_as_of, now())
366
+ ))
367
+ ORDER BY subscription.next_cycle_at, subscription.id
368
+ LIMIT LEAST(GREATEST(COALESCE(p_limit, 50), 1), 500);
369
+ $$;
370
+
371
+ CREATE FUNCTION public.subscription_claim_due_renewal(
372
+ p_identity_key text,
373
+ p_operation_fingerprint text,
374
+ p_cycle_snapshot jsonb,
375
+ p_worker_id text,
376
+ p_lease_seconds integer DEFAULT 300,
377
+ p_now timestamptz DEFAULT now()
378
+ )
379
+ RETURNS jsonb
380
+ LANGUAGE plpgsql
381
+ SET search_path = pg_catalog
382
+ AS $$
383
+ DECLARE
384
+ v_subscription_id uuid;
385
+ v_scheduled_at timestamptz;
386
+ v_cycle_number integer;
387
+ v_subscription public.subscriptions%ROWTYPE;
388
+ v_operation public.subscription_renewal_operations%ROWTYPE;
389
+ BEGIN
390
+ IF p_identity_key IS NULL OR p_operation_fingerprint !~ '^[0-9a-f]{64}$'
391
+ OR p_cycle_snapshot IS NULL OR jsonb_typeof(p_cycle_snapshot) <> 'object'
392
+ OR p_cycle_snapshot->>'contractVersion' <> 'platform.subscription.renewal.v1'
393
+ OR btrim(COALESCE(p_worker_id, '')) = ''
394
+ OR p_lease_seconds IS NULL OR p_lease_seconds NOT BETWEEN 30 AND 3600
395
+ THEN
396
+ RAISE EXCEPTION 'subscription_renewal_claim_invalid_input' USING ERRCODE = '22023';
397
+ END IF;
398
+
399
+ BEGIN
400
+ v_subscription_id := (p_cycle_snapshot->>'subscriptionId')::uuid;
401
+ v_scheduled_at := (p_cycle_snapshot->>'scheduledAt')::timestamptz;
402
+ v_cycle_number := (p_cycle_snapshot->>'cycleNumber')::integer;
403
+ EXCEPTION WHEN invalid_text_representation OR numeric_value_out_of_range THEN
404
+ RAISE EXCEPTION 'subscription_renewal_claim_invalid_snapshot' USING ERRCODE = '22023';
405
+ END;
406
+
407
+ IF v_cycle_number < 1
408
+ OR p_identity_key <> public.subscription_renewal_identity(v_subscription_id, v_scheduled_at)
409
+ OR jsonb_typeof(p_cycle_snapshot->'lines') <> 'array'
410
+ OR jsonb_array_length(p_cycle_snapshot->'lines') = 0
411
+ OR p_cycle_snapshot->>'currency' !~ '^[A-Z]{3}$'
412
+ OR (p_cycle_snapshot->>'totalAmountMinor')::bigint < 1
413
+ THEN
414
+ RAISE EXCEPTION 'subscription_renewal_claim_invalid_snapshot' USING ERRCODE = '22023';
415
+ END IF;
416
+
417
+ SELECT * INTO v_subscription
418
+ FROM public.subscriptions
419
+ WHERE id = v_subscription_id
420
+ FOR UPDATE;
421
+ IF NOT FOUND OR v_subscription.status <> 'active'
422
+ OR v_subscription.next_cycle_at IS DISTINCT FROM v_scheduled_at
423
+ OR v_subscription.next_cycle_at > COALESCE(p_now, now())
424
+ THEN
425
+ RAISE EXCEPTION 'subscription_renewal_claim_not_due' USING ERRCODE = '22023';
426
+ END IF;
427
+
428
+ SELECT * INTO v_operation
429
+ FROM public.subscription_renewal_operations
430
+ WHERE subscription_id = v_subscription_id AND scheduled_at = v_scheduled_at
431
+ FOR UPDATE;
432
+
433
+ IF FOUND THEN
434
+ IF v_operation.identity_key <> p_identity_key
435
+ OR v_operation.operation_fingerprint <> p_operation_fingerprint
436
+ OR v_operation.cycle_snapshot <> p_cycle_snapshot
437
+ THEN
438
+ RAISE EXCEPTION 'subscription_renewal_claim_idempotency_conflict' USING ERRCODE = '23505';
439
+ END IF;
440
+ IF v_operation.state IN ('succeeded', 'refused') THEN
441
+ RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'terminal');
442
+ END IF;
443
+ IF v_operation.lease_expires_at > COALESCE(p_now, now()) THEN
444
+ RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'lease_active');
445
+ END IF;
446
+ UPDATE public.subscription_renewal_operations
447
+ SET claim_token = gen_random_uuid(),
448
+ claim_generation = claim_generation + 1,
449
+ claimed_by = p_worker_id,
450
+ claimed_at = COALESCE(p_now, now()),
451
+ lease_expires_at = COALESCE(p_now, now()) + make_interval(secs => p_lease_seconds),
452
+ updated_at = COALESCE(p_now, now())
453
+ WHERE id = v_operation.id
454
+ RETURNING * INTO v_operation;
455
+ RETURN public.subscription_renewal_operation_response(v_operation, true, true, 'lease_taken_over');
456
+ END IF;
457
+
458
+ INSERT INTO public.subscription_renewal_operations (
459
+ subscription_id, scheduled_at, cycle_number, identity_key, operation_fingerprint,
460
+ cycle_snapshot, claim_token, claimed_by, claimed_at, lease_expires_at, updated_at
461
+ ) VALUES (
462
+ v_subscription_id, v_scheduled_at, v_cycle_number, p_identity_key,
463
+ p_operation_fingerprint, p_cycle_snapshot, gen_random_uuid(), p_worker_id,
464
+ COALESCE(p_now, now()), COALESCE(p_now, now()) + make_interval(secs => p_lease_seconds),
465
+ COALESCE(p_now, now())
466
+ ) RETURNING * INTO v_operation;
467
+ RETURN public.subscription_renewal_operation_response(v_operation, true, false, 'claimed');
468
+ END;
469
+ $$;
470
+
471
+ CREATE FUNCTION public.subscription_read_renewal(p_identity_key text)
472
+ RETURNS jsonb
473
+ LANGUAGE plpgsql
474
+ STABLE
475
+ SET search_path = pg_catalog
476
+ AS $$
477
+ DECLARE v_operation public.subscription_renewal_operations%ROWTYPE;
478
+ BEGIN
479
+ SELECT * INTO v_operation FROM public.subscription_renewal_operations
480
+ WHERE identity_key = p_identity_key;
481
+ IF NOT FOUND THEN RETURN NULL; END IF;
482
+ RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'readback');
483
+ END;
484
+ $$;
485
+
486
+ CREATE FUNCTION public.subscription_refuse_renewal_preflight(
487
+ p_operation_id uuid,
488
+ p_claim_token uuid,
489
+ p_reason text,
490
+ p_occurred_at timestamptz DEFAULT now()
491
+ )
492
+ RETURNS jsonb
493
+ LANGUAGE plpgsql
494
+ SET search_path = pg_catalog
495
+ AS $$
496
+ DECLARE v_operation public.subscription_renewal_operations%ROWTYPE;
497
+ BEGIN
498
+ IF p_operation_id IS NULL OR p_claim_token IS NULL
499
+ OR btrim(COALESCE(p_reason, '')) = '' OR char_length(p_reason) > 180
500
+ THEN
501
+ RAISE EXCEPTION 'subscription_renewal_preflight_refusal_invalid_input' USING ERRCODE = '22023';
502
+ END IF;
503
+ SELECT * INTO v_operation FROM public.subscription_renewal_operations
504
+ WHERE id = p_operation_id FOR UPDATE;
505
+ IF NOT FOUND THEN
506
+ RAISE EXCEPTION 'subscription_renewal_preflight_refusal_not_found' USING ERRCODE = '22023';
507
+ END IF;
508
+ IF v_operation.operation_fingerprint IS NULL THEN
509
+ RAISE EXCEPTION 'subscription_renewal_preflight_refusal_fingerprint_missing' USING ERRCODE = '23505';
510
+ END IF;
511
+ IF v_operation.state = 'refused' THEN
512
+ RETURN public.subscription_renewal_operation_response(
513
+ v_operation, false, true, 'preflight_refusal_replay');
514
+ END IF;
515
+ IF v_operation.claim_token <> p_claim_token
516
+ OR v_operation.lease_expires_at <= COALESCE(p_occurred_at, now())
517
+ OR v_operation.state NOT IN ('claimed', 'reserved')
518
+ THEN
519
+ RAISE EXCEPTION 'subscription_renewal_preflight_refusal_stale_claim' USING ERRCODE = '40001';
520
+ END IF;
521
+ UPDATE public.fulfillment_inventory_reservations
522
+ SET status = 'released', terminal_reason = p_reason,
523
+ expires_at = NULL, updated_at = COALESCE(p_occurred_at, now())
524
+ WHERE renewal_operation_id = p_operation_id AND status = 'held';
525
+ INSERT INTO public.subscription_renewal_outcomes (
526
+ renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
527
+ ) VALUES (
528
+ v_operation.id, v_operation.operation_fingerprint,
529
+ v_operation.identity_key || ':preflight-refusal', 'refused', p_reason,
530
+ COALESCE(p_occurred_at, now()));
531
+ UPDATE public.subscription_renewal_operations
532
+ SET state = 'refused', terminal_outcome = 'refused', terminal_reason = p_reason,
533
+ terminal_at = COALESCE(p_occurred_at, now()), updated_at = COALESCE(p_occurred_at, now())
534
+ WHERE id = p_operation_id RETURNING * INTO v_operation;
535
+ RETURN public.subscription_renewal_operation_response(
536
+ v_operation, false, false, 'preflight_refused');
537
+ END;
538
+ $$;
539
+
540
+ CREATE FUNCTION public.fulfillment_reserve_subscription_renewal(
541
+ p_operation_id uuid,
542
+ p_claim_token uuid,
543
+ p_hold_seconds integer DEFAULT 900,
544
+ p_now timestamptz DEFAULT now()
545
+ )
546
+ RETURNS jsonb
547
+ LANGUAGE plpgsql
548
+ SET search_path = pg_catalog
549
+ AS $$
550
+ DECLARE
551
+ v_operation public.subscription_renewal_operations%ROWTYPE;
552
+ v_reservation public.fulfillment_inventory_reservations%ROWTYPE;
553
+ v_line jsonb;
554
+ v_source text;
555
+ v_sku text;
556
+ v_quantity integer;
557
+ v_available integer;
558
+ v_reserved integer;
559
+ BEGIN
560
+ IF p_operation_id IS NULL OR p_claim_token IS NULL
561
+ OR p_hold_seconds IS NULL OR p_hold_seconds NOT BETWEEN 60 AND 86400
562
+ THEN
563
+ RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_input' USING ERRCODE = '22023';
564
+ END IF;
565
+
566
+ SELECT * INTO v_operation FROM public.subscription_renewal_operations
567
+ WHERE id = p_operation_id FOR UPDATE;
568
+ IF NOT FOUND THEN
569
+ RAISE EXCEPTION 'fulfillment_renewal_reservation_operation_not_found' USING ERRCODE = '22023';
570
+ END IF;
571
+ IF v_operation.claim_token <> p_claim_token
572
+ OR v_operation.lease_expires_at <= COALESCE(p_now, now())
573
+ THEN
574
+ RAISE EXCEPTION 'fulfillment_renewal_reservation_stale_claim' USING ERRCODE = '40001';
575
+ END IF;
576
+
577
+ SELECT * INTO v_reservation FROM public.fulfillment_inventory_reservations
578
+ WHERE renewal_operation_id = p_operation_id FOR UPDATE;
579
+ IF FOUND THEN
580
+ IF v_reservation.operation_fingerprint <> v_operation.operation_fingerprint THEN
581
+ RAISE EXCEPTION 'fulfillment_renewal_reservation_fingerprint_conflict' USING ERRCODE = '23505';
582
+ END IF;
583
+ IF v_reservation.status = 'held'
584
+ AND v_reservation.expires_at <= COALESCE(p_now, now())
585
+ AND v_operation.state = 'reserved'
586
+ THEN
587
+ UPDATE public.fulfillment_inventory_reservations
588
+ SET status = 'expired', expires_at = NULL,
589
+ terminal_reason = 'reservation_expired', updated_at = COALESCE(p_now, now())
590
+ WHERE id = v_reservation.id
591
+ RETURNING * INTO v_reservation;
592
+ UPDATE public.subscription_renewal_operations
593
+ SET state = 'refused', terminal_outcome = 'refused',
594
+ terminal_reason = 'reservation_expired', terminal_at = COALESCE(p_now, now()),
595
+ updated_at = COALESCE(p_now, now())
596
+ WHERE id = p_operation_id RETURNING * INTO v_operation;
597
+ INSERT INTO public.subscription_renewal_outcomes (
598
+ renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
599
+ ) VALUES (
600
+ p_operation_id, v_operation.operation_fingerprint,
601
+ v_operation.identity_key || ':reservation-expired', 'refused',
602
+ 'reservation_expired', COALESCE(p_now, now()));
603
+ RETURN jsonb_build_object(
604
+ 'reserved', false, 'replayed', true, 'reservationId', v_reservation.id,
605
+ 'status', 'expired', 'reason', 'reservation_expired');
606
+ END IF;
607
+ RETURN jsonb_build_object(
608
+ 'reserved', v_reservation.status IN ('held', 'committed', 'consumed'),
609
+ 'replayed', true, 'reservationId', v_reservation.id,
610
+ 'status', v_reservation.status, 'reason', v_reservation.terminal_reason);
611
+ END IF;
612
+
613
+ IF v_operation.state <> 'claimed' THEN
614
+ RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_state' USING ERRCODE = '22023';
615
+ END IF;
616
+ v_source := v_operation.cycle_snapshot->>'stockSourceKey';
617
+ IF v_source IS NULL OR v_source !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$' THEN
618
+ RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_snapshot' USING ERRCODE = '22023';
619
+ END IF;
620
+
621
+ -- Lock all stock rows in a deterministic order before computing ATP, so two different
622
+ -- operations containing the same SKUs cannot both spend the same observed availability.
623
+ PERFORM stock.sku
624
+ FROM public.fulfillment_stock_current stock
625
+ JOIN jsonb_array_elements(v_operation.cycle_snapshot->'lines') line
626
+ ON stock.source_key = v_source AND stock.sku = line->>'sku'
627
+ ORDER BY stock.sku
628
+ FOR UPDATE OF stock;
629
+
630
+ FOR v_line IN SELECT value FROM jsonb_array_elements(v_operation.cycle_snapshot->'lines')
631
+ LOOP
632
+ v_sku := v_line->>'sku';
633
+ BEGIN v_quantity := (v_line->>'quantity')::integer;
634
+ EXCEPTION WHEN invalid_text_representation OR numeric_value_out_of_range THEN
635
+ RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_snapshot' USING ERRCODE = '22023';
636
+ END;
637
+ IF btrim(COALESCE(v_sku, '')) = '' OR v_quantity < 1 THEN
638
+ RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_snapshot' USING ERRCODE = '22023';
639
+ END IF;
640
+
641
+ SELECT stock.for_sale_quantity INTO v_available
642
+ FROM public.fulfillment_stock_current stock
643
+ WHERE stock.source_key = v_source AND stock.sku = v_sku
644
+ AND stock.inventory_class = 'sellable'
645
+ AND stock.stale_after > COALESCE(p_now, now());
646
+ IF NOT FOUND THEN v_available := 0; END IF;
647
+
648
+ SELECT COALESCE(sum(reservation_line.quantity), 0)::integer INTO v_reserved
649
+ FROM public.fulfillment_inventory_reservation_lines reservation_line
650
+ JOIN public.fulfillment_inventory_reservations reservation
651
+ ON reservation.id = reservation_line.reservation_id
652
+ WHERE reservation.source_key = v_source AND reservation_line.sku = v_sku
653
+ AND reservation.status IN ('held', 'committed')
654
+ AND (reservation.status = 'committed' OR reservation.expires_at > COALESCE(p_now, now()));
655
+ IF v_available - v_reserved < v_quantity THEN
656
+ UPDATE public.subscription_renewal_operations
657
+ SET state = 'refused', terminal_outcome = 'refused',
658
+ terminal_reason = 'inventory_unavailable', terminal_at = COALESCE(p_now, now()),
659
+ updated_at = COALESCE(p_now, now())
660
+ WHERE id = p_operation_id RETURNING * INTO v_operation;
661
+ INSERT INTO public.subscription_renewal_outcomes (
662
+ renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
663
+ ) VALUES (
664
+ p_operation_id, v_operation.operation_fingerprint,
665
+ v_operation.identity_key || ':inventory', 'refused', 'inventory_unavailable',
666
+ COALESCE(p_now, now()));
667
+ RETURN jsonb_build_object(
668
+ 'reserved', false, 'replayed', false, 'reservationId', NULL,
669
+ 'status', 'refused', 'reason', 'inventory_unavailable');
670
+ END IF;
671
+ END LOOP;
672
+
673
+ INSERT INTO public.fulfillment_inventory_reservations (
674
+ renewal_operation_id, operation_fingerprint, source_key, expires_at, created_at, updated_at
675
+ ) VALUES (
676
+ p_operation_id, v_operation.operation_fingerprint, v_source,
677
+ COALESCE(p_now, now()) + make_interval(secs => p_hold_seconds),
678
+ COALESCE(p_now, now()), COALESCE(p_now, now())
679
+ ) RETURNING * INTO v_reservation;
680
+
681
+ FOR v_line IN SELECT value FROM jsonb_array_elements(v_operation.cycle_snapshot->'lines')
682
+ LOOP
683
+ INSERT INTO public.fulfillment_inventory_reservation_lines (
684
+ reservation_id, operation_fingerprint, sku, quantity
685
+ ) VALUES (
686
+ v_reservation.id, v_operation.operation_fingerprint,
687
+ v_line->>'sku', (v_line->>'quantity')::integer);
688
+ END LOOP;
689
+
690
+ UPDATE public.subscription_renewal_operations
691
+ SET state = 'reserved', updated_at = COALESCE(p_now, now())
692
+ WHERE id = p_operation_id;
693
+ RETURN jsonb_build_object(
694
+ 'reserved', true, 'replayed', false, 'reservationId', v_reservation.id,
695
+ 'status', 'held', 'reason', NULL);
696
+ END;
697
+ $$;
698
+
699
+ CREATE FUNCTION public.fulfillment_release_subscription_renewal(
700
+ p_operation_id uuid,
701
+ p_claim_token uuid,
702
+ p_reason text,
703
+ p_now timestamptz DEFAULT now()
704
+ )
705
+ RETURNS jsonb
706
+ LANGUAGE plpgsql
707
+ SET search_path = pg_catalog
708
+ AS $$
709
+ DECLARE
710
+ v_operation public.subscription_renewal_operations%ROWTYPE;
711
+ v_reservation public.fulfillment_inventory_reservations%ROWTYPE;
712
+ BEGIN
713
+ IF btrim(COALESCE(p_reason, '')) = '' THEN
714
+ RAISE EXCEPTION 'fulfillment_renewal_release_reason_required' USING ERRCODE = '22023';
715
+ END IF;
716
+ SELECT * INTO v_operation FROM public.subscription_renewal_operations
717
+ WHERE id = p_operation_id FOR UPDATE;
718
+ IF NOT FOUND OR v_operation.claim_token <> p_claim_token
719
+ OR v_operation.lease_expires_at <= COALESCE(p_now, now())
720
+ THEN
721
+ RAISE EXCEPTION 'fulfillment_renewal_release_stale_claim' USING ERRCODE = '40001';
722
+ END IF;
723
+ IF v_operation.state = 'attempt_prepared' THEN
724
+ RAISE EXCEPTION 'fulfillment_renewal_release_attempt_indeterminate' USING ERRCODE = '22023';
725
+ END IF;
726
+ IF v_operation.state IN ('succeeded', 'refused') THEN
727
+ RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'terminal');
728
+ END IF;
729
+
730
+ SELECT * INTO v_reservation FROM public.fulfillment_inventory_reservations
731
+ WHERE renewal_operation_id = p_operation_id FOR UPDATE;
732
+ IF FOUND AND v_reservation.status = 'held' THEN
733
+ UPDATE public.fulfillment_inventory_reservations
734
+ SET status = 'released', expires_at = NULL, terminal_reason = p_reason,
735
+ updated_at = COALESCE(p_now, now())
736
+ WHERE id = v_reservation.id;
737
+ END IF;
738
+ UPDATE public.subscription_renewal_operations
739
+ SET state = 'refused', terminal_outcome = 'refused', terminal_reason = p_reason,
740
+ terminal_at = COALESCE(p_now, now()), updated_at = COALESCE(p_now, now())
741
+ WHERE id = p_operation_id RETURNING * INTO v_operation;
742
+ INSERT INTO public.subscription_renewal_outcomes (
743
+ renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
744
+ ) VALUES (
745
+ p_operation_id, v_operation.operation_fingerprint,
746
+ v_operation.identity_key || ':release', 'refused', p_reason, COALESCE(p_now, now()));
747
+ RETURN public.subscription_renewal_operation_response(v_operation, false, false, 'released');
748
+ END;
749
+ $$;
750
+
751
+ CREATE FUNCTION public.fulfillment_expire_subscription_renewals(
752
+ p_now timestamptz DEFAULT now(),
753
+ p_limit integer DEFAULT 100
754
+ )
755
+ RETURNS integer
756
+ LANGUAGE plpgsql
757
+ SET search_path = pg_catalog
758
+ AS $$
759
+ DECLARE v_row record; v_count integer := 0;
760
+ BEGIN
761
+ FOR v_row IN
762
+ SELECT reservation.id, reservation.renewal_operation_id
763
+ FROM public.fulfillment_inventory_reservations reservation
764
+ JOIN public.subscription_renewal_operations operation
765
+ ON operation.id = reservation.renewal_operation_id
766
+ WHERE reservation.status = 'held'
767
+ AND reservation.expires_at <= COALESCE(p_now, now())
768
+ AND operation.state = 'reserved'
769
+ ORDER BY reservation.expires_at, reservation.id
770
+ FOR UPDATE OF reservation, operation SKIP LOCKED
771
+ LIMIT LEAST(GREATEST(COALESCE(p_limit, 100), 1), 1000)
772
+ LOOP
773
+ UPDATE public.fulfillment_inventory_reservations
774
+ SET status = 'expired', expires_at = NULL, terminal_reason = 'reservation_expired',
775
+ updated_at = COALESCE(p_now, now())
776
+ WHERE id = v_row.id;
777
+ UPDATE public.subscription_renewal_operations
778
+ SET state = 'refused', terminal_outcome = 'refused',
779
+ terminal_reason = 'reservation_expired', terminal_at = COALESCE(p_now, now()),
780
+ updated_at = COALESCE(p_now, now())
781
+ WHERE id = v_row.renewal_operation_id;
782
+ INSERT INTO public.subscription_renewal_outcomes (
783
+ renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
784
+ ) SELECT operation.id, operation.operation_fingerprint,
785
+ operation.identity_key || ':reservation-expired', 'refused',
786
+ 'reservation_expired', COALESCE(p_now, now())
787
+ FROM public.subscription_renewal_operations operation
788
+ WHERE operation.id = v_row.renewal_operation_id;
789
+ v_count := v_count + 1;
790
+ END LOOP;
791
+ RETURN v_count;
792
+ END;
793
+ $$;
794
+
795
+ CREATE FUNCTION public.commerce_prepare_subscription_renewal(
796
+ p_operation_id uuid,
797
+ p_claim_token uuid,
798
+ p_now timestamptz DEFAULT now()
799
+ )
800
+ RETURNS jsonb
801
+ LANGUAGE plpgsql
802
+ SET search_path = pg_catalog
803
+ AS $$
804
+ DECLARE
805
+ v_operation public.subscription_renewal_operations%ROWTYPE;
806
+ v_reservation public.fulfillment_inventory_reservations%ROWTYPE;
807
+ v_cycle_id uuid;
808
+ v_order_id uuid;
809
+ v_intent_id uuid;
810
+ v_line jsonb;
811
+ v_line_ordinal integer := 0;
812
+ v_sku_id uuid;
813
+ v_quantity integer;
814
+ v_unit_amount bigint;
815
+ v_total bigint;
816
+ v_currency text;
817
+ v_channel text;
818
+ BEGIN
819
+ SELECT * INTO v_operation FROM public.subscription_renewal_operations
820
+ WHERE id = p_operation_id FOR UPDATE;
821
+ IF NOT FOUND THEN
822
+ RAISE EXCEPTION 'commerce_renewal_operation_not_found' USING ERRCODE = '22023';
823
+ END IF;
824
+ IF v_operation.claim_token <> p_claim_token
825
+ OR v_operation.lease_expires_at <= COALESCE(p_now, now())
826
+ THEN
827
+ RAISE EXCEPTION 'commerce_renewal_stale_claim' USING ERRCODE = '40001';
828
+ END IF;
829
+
830
+ SELECT cycle.id, order_row.id, intent.id
831
+ INTO v_cycle_id, v_order_id, v_intent_id
832
+ FROM public.subscription_cycles cycle
833
+ JOIN public.commerce_orders order_row ON order_row.subscription_cycle_id = cycle.id
834
+ JOIN public.commerce_settlement_intents intent ON intent.order_id = order_row.id
835
+ WHERE cycle.renewal_operation_id = p_operation_id;
836
+ IF FOUND THEN
837
+ IF v_operation.state <> 'attempt_prepared' THEN
838
+ RAISE EXCEPTION 'commerce_renewal_artifact_state_conflict' USING ERRCODE = '23505';
839
+ END IF;
840
+ RETURN jsonb_build_object(
841
+ 'contractVersion', 'platform.subscription.renewal.v1', 'replayed', true,
842
+ 'operationId', p_operation_id, 'operationFingerprint', v_operation.operation_fingerprint,
843
+ 'cycleId', v_cycle_id, 'orderId', v_order_id, 'settlementIntentId', v_intent_id,
844
+ 'state', v_operation.state);
845
+ END IF;
846
+ IF v_operation.state <> 'reserved' THEN
847
+ RAISE EXCEPTION 'commerce_renewal_reservation_required' USING ERRCODE = '22023';
848
+ END IF;
849
+
850
+ SELECT * INTO v_reservation FROM public.fulfillment_inventory_reservations
851
+ WHERE renewal_operation_id = p_operation_id FOR UPDATE;
852
+ IF NOT FOUND OR v_reservation.status <> 'held'
853
+ OR v_reservation.expires_at <= COALESCE(p_now, now())
854
+ OR v_reservation.operation_fingerprint <> v_operation.operation_fingerprint
855
+ THEN
856
+ RAISE EXCEPTION 'commerce_renewal_active_reservation_required' USING ERRCODE = '22023';
857
+ END IF;
858
+
859
+ v_currency := v_operation.cycle_snapshot->>'currency';
860
+ v_channel := v_operation.cycle_snapshot->>'settlementChannelKey';
861
+ v_total := (v_operation.cycle_snapshot->>'totalAmountMinor')::bigint;
862
+ IF v_currency !~ '^[A-Z]{3}$' OR btrim(COALESCE(v_channel, '')) = '' OR v_total < 1 THEN
863
+ RAISE EXCEPTION 'commerce_renewal_invalid_snapshot' USING ERRCODE = '22023';
864
+ END IF;
865
+
866
+ INSERT INTO public.subscription_cycles (
867
+ subscription_id, scheduled_at, status, renewal_operation_id,
868
+ renewal_operation_fingerprint, updated_at
869
+ ) VALUES (
870
+ v_operation.subscription_id, v_operation.scheduled_at, 'payment_pending', p_operation_id,
871
+ v_operation.operation_fingerprint, COALESCE(p_now, now())
872
+ ) RETURNING id INTO v_cycle_id;
873
+
874
+ INSERT INTO public.commerce_orders (
875
+ client_id, subscription_cycle_id, status, currency_code, total_amount_minor,
876
+ source_kind, metadata, renewal_operation_id, renewal_operation_fingerprint, updated_at
877
+ ) SELECT subscription.client_id, v_cycle_id, 'pending_payment', v_currency, v_total,
878
+ 'storefront', jsonb_build_object(
879
+ 'source', 'subscription.renewal',
880
+ 'renewalIdentityKey', v_operation.identity_key,
881
+ 'renewalOperationFingerprint', v_operation.operation_fingerprint,
882
+ 'cycleSnapshot', v_operation.cycle_snapshot),
883
+ p_operation_id, v_operation.operation_fingerprint, COALESCE(p_now, now())
884
+ FROM public.subscriptions subscription WHERE subscription.id = v_operation.subscription_id
885
+ RETURNING id INTO v_order_id;
886
+
887
+ FOR v_line IN SELECT value FROM jsonb_array_elements(v_operation.cycle_snapshot->'lines')
888
+ LOOP
889
+ v_line_ordinal := v_line_ordinal + 1;
890
+ v_quantity := (v_line->>'quantity')::integer;
891
+ v_unit_amount := (v_line->>'unitAmountMinor')::bigint;
892
+ SELECT id INTO v_sku_id FROM public.catalog_skus WHERE sku = v_line->>'sku';
893
+ IF NOT FOUND OR v_quantity < 1 OR v_unit_amount < 0 THEN
894
+ RAISE EXCEPTION 'commerce_renewal_invalid_line' USING ERRCODE = '22023';
895
+ END IF;
896
+ INSERT INTO public.commerce_order_items (
897
+ order_id, sku_id, line_ordinal, quantity, unit_amount_minor, line_amount_minor
898
+ ) VALUES (
899
+ v_order_id, v_sku_id, v_line_ordinal, v_quantity, v_unit_amount,
900
+ v_quantity * v_unit_amount);
901
+ END LOOP;
902
+
903
+ PERFORM public.commerce_open_settlement_intent(
904
+ v_operation.identity_key || ':settlement', v_order_id, v_channel, v_total, v_currency,
905
+ jsonb_build_object(
906
+ 'renewalOperationId', p_operation_id,
907
+ 'renewalOperationFingerprint', v_operation.operation_fingerprint),
908
+ COALESCE(p_now, now()));
909
+ SELECT id INTO v_intent_id FROM public.commerce_settlement_intents WHERE order_id = v_order_id;
910
+ UPDATE public.commerce_settlement_intents
911
+ SET renewal_operation_id = p_operation_id,
912
+ renewal_operation_fingerprint = v_operation.operation_fingerprint,
913
+ updated_at = COALESCE(p_now, now())
914
+ WHERE id = v_intent_id;
915
+ UPDATE public.fulfillment_inventory_reservations
916
+ SET status = 'committed', expires_at = NULL, updated_at = COALESCE(p_now, now())
917
+ WHERE id = v_reservation.id;
918
+ UPDATE public.subscription_renewal_operations
919
+ SET state = 'attempt_prepared', attempt_prepared_at = COALESCE(p_now, now()),
920
+ updated_at = COALESCE(p_now, now())
921
+ WHERE id = p_operation_id RETURNING * INTO v_operation;
922
+
923
+ RETURN jsonb_build_object(
924
+ 'contractVersion', 'platform.subscription.renewal.v1', 'replayed', false,
925
+ 'operationId', p_operation_id, 'operationFingerprint', v_operation.operation_fingerprint,
926
+ 'cycleId', v_cycle_id, 'orderId', v_order_id, 'settlementIntentId', v_intent_id,
927
+ 'state', v_operation.state);
928
+ END;
929
+ $$;
930
+
931
+ CREATE FUNCTION public.commerce_acknowledge_subscription_renewal_attempt(
932
+ p_operation_id uuid,
933
+ p_operation_fingerprint text,
934
+ p_external_attempt_ref text,
935
+ p_acknowledged_at timestamptz DEFAULT now()
936
+ )
937
+ RETURNS jsonb
938
+ LANGUAGE plpgsql
939
+ SET search_path = pg_catalog
940
+ AS $$
941
+ DECLARE v_operation public.subscription_renewal_operations%ROWTYPE;
942
+ BEGIN
943
+ IF p_operation_id IS NULL
944
+ OR p_operation_fingerprint !~ '^[0-9a-f]{64}$'
945
+ OR btrim(COALESCE(p_external_attempt_ref, '')) = ''
946
+ OR char_length(p_external_attempt_ref) > 240
947
+ THEN
948
+ RAISE EXCEPTION 'commerce_renewal_attempt_ack_invalid_input' USING ERRCODE = '22023';
949
+ END IF;
950
+ SELECT * INTO v_operation FROM public.subscription_renewal_operations
951
+ WHERE id = p_operation_id FOR UPDATE;
952
+ IF NOT FOUND OR v_operation.state <> 'attempt_prepared' THEN
953
+ RAISE EXCEPTION 'commerce_renewal_attempt_ack_not_prepared' USING ERRCODE = '22023';
954
+ END IF;
955
+ IF v_operation.operation_fingerprint <> p_operation_fingerprint THEN
956
+ RAISE EXCEPTION 'commerce_renewal_attempt_ack_fingerprint_conflict' USING ERRCODE = '23505';
957
+ END IF;
958
+ IF v_operation.external_attempt_ref IS NOT NULL THEN
959
+ IF v_operation.external_attempt_ref <> p_external_attempt_ref THEN
960
+ RAISE EXCEPTION 'commerce_renewal_attempt_ack_idempotency_conflict' USING ERRCODE = '23505';
961
+ END IF;
962
+ RETURN public.subscription_renewal_operation_response(
963
+ v_operation, false, true, 'external_attempt_ack_replay');
964
+ END IF;
965
+ UPDATE public.subscription_renewal_operations
966
+ SET external_attempt_ref = p_external_attempt_ref,
967
+ external_attempt_acknowledged_at = COALESCE(p_acknowledged_at, now()),
968
+ updated_at = COALESCE(p_acknowledged_at, now())
969
+ WHERE id = p_operation_id RETURNING * INTO v_operation;
970
+ RETURN public.subscription_renewal_operation_response(
971
+ v_operation, false, false, 'external_attempt_ack_recorded');
972
+ END;
973
+ $$;
974
+
975
+ CREATE FUNCTION public.commerce_record_subscription_renewal_outcome(
976
+ p_operation_id uuid,
977
+ p_event_key text,
978
+ p_operation_fingerprint text,
979
+ p_outcome text,
980
+ p_external_ref text DEFAULT NULL,
981
+ p_reason text DEFAULT NULL,
982
+ p_occurred_at timestamptz DEFAULT now()
983
+ )
984
+ RETURNS jsonb
985
+ LANGUAGE plpgsql
986
+ SET search_path = pg_catalog
987
+ AS $$
988
+ DECLARE
989
+ v_operation public.subscription_renewal_operations%ROWTYPE;
990
+ v_existing public.subscription_renewal_outcomes%ROWTYPE;
991
+ v_intent public.commerce_settlement_intents%ROWTYPE;
992
+ BEGIN
993
+ IF btrim(COALESCE(p_event_key, '')) = ''
994
+ OR p_operation_fingerprint !~ '^[0-9a-f]{64}$'
995
+ OR p_outcome NOT IN ('succeeded', 'refused')
996
+ OR (p_outcome = 'succeeded' AND btrim(COALESCE(p_external_ref, '')) = '')
997
+ THEN
998
+ RAISE EXCEPTION 'commerce_renewal_outcome_invalid_input' USING ERRCODE = '22023';
999
+ END IF;
1000
+
1001
+ SELECT * INTO v_operation FROM public.subscription_renewal_operations
1002
+ WHERE id = p_operation_id FOR UPDATE;
1003
+ IF NOT FOUND THEN
1004
+ RAISE EXCEPTION 'commerce_renewal_outcome_operation_not_found' USING ERRCODE = '22023';
1005
+ END IF;
1006
+ IF v_operation.operation_fingerprint <> p_operation_fingerprint THEN
1007
+ RAISE EXCEPTION 'commerce_renewal_outcome_fingerprint_conflict' USING ERRCODE = '23505';
1008
+ END IF;
1009
+ IF p_external_ref IS NOT NULL
1010
+ AND v_operation.external_attempt_ref IS DISTINCT FROM p_external_ref
1011
+ THEN
1012
+ RAISE EXCEPTION 'commerce_renewal_outcome_external_ack_mismatch' USING ERRCODE = '23505';
1013
+ END IF;
1014
+
1015
+ SELECT * INTO v_existing FROM public.subscription_renewal_outcomes
1016
+ WHERE renewal_operation_id = p_operation_id AND event_key = p_event_key;
1017
+ IF FOUND THEN
1018
+ IF v_existing.operation_fingerprint <> p_operation_fingerprint
1019
+ OR v_existing.outcome <> p_outcome
1020
+ OR v_existing.external_ref IS DISTINCT FROM p_external_ref
1021
+ THEN
1022
+ RAISE EXCEPTION 'commerce_renewal_outcome_idempotency_conflict' USING ERRCODE = '23505';
1023
+ END IF;
1024
+ RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'terminal_replay');
1025
+ END IF;
1026
+ IF v_operation.state IN ('succeeded', 'refused') THEN
1027
+ RAISE EXCEPTION 'commerce_renewal_outcome_already_terminal' USING ERRCODE = '22023';
1028
+ END IF;
1029
+ IF v_operation.state <> 'attempt_prepared' THEN
1030
+ RAISE EXCEPTION 'commerce_renewal_outcome_attempt_not_prepared' USING ERRCODE = '22023';
1031
+ END IF;
1032
+
1033
+ SELECT * INTO v_intent FROM public.commerce_settlement_intents
1034
+ WHERE renewal_operation_id = p_operation_id;
1035
+ IF NOT FOUND OR v_intent.renewal_operation_fingerprint <> p_operation_fingerprint THEN
1036
+ RAISE EXCEPTION 'commerce_renewal_outcome_intent_not_found' USING ERRCODE = '22023';
1037
+ END IF;
1038
+
1039
+ PERFORM public.commerce_record_settlement(
1040
+ p_event_key || ':settlement', v_intent.id,
1041
+ CASE p_outcome WHEN 'succeeded' THEN 'succeeded' ELSE 'failed' END,
1042
+ p_external_ref,
1043
+ jsonb_build_object(
1044
+ 'reason', COALESCE(NULLIF(btrim(COALESCE(p_reason, '')), ''),
1045
+ CASE p_outcome WHEN 'refused' THEN 'declined' ELSE NULL END),
1046
+ 'renewalOperationId', p_operation_id,
1047
+ 'renewalOperationFingerprint', p_operation_fingerprint),
1048
+ COALESCE(p_occurred_at, now()));
1049
+
1050
+ INSERT INTO public.subscription_renewal_outcomes (
1051
+ renewal_operation_id, operation_fingerprint, event_key, outcome, reason,
1052
+ external_ref, occurred_at
1053
+ ) VALUES (
1054
+ p_operation_id, p_operation_fingerprint, p_event_key, p_outcome,
1055
+ NULLIF(btrim(COALESCE(p_reason, '')), ''), p_external_ref, COALESCE(p_occurred_at, now()));
1056
+
1057
+ IF p_outcome = 'succeeded' THEN
1058
+ UPDATE public.fulfillment_inventory_reservations
1059
+ SET status = 'consumed', updated_at = COALESCE(p_occurred_at, now())
1060
+ WHERE renewal_operation_id = p_operation_id AND status = 'committed';
1061
+ UPDATE public.subscription_renewal_terms
1062
+ SET next_cycle_number = GREATEST(next_cycle_number, v_operation.cycle_number + 1),
1063
+ updated_at = COALESCE(p_occurred_at, now())
1064
+ WHERE subscription_id = v_operation.subscription_id;
1065
+ ELSE
1066
+ UPDATE public.fulfillment_inventory_reservations
1067
+ SET status = 'released', terminal_reason = COALESCE(
1068
+ NULLIF(btrim(COALESCE(p_reason, '')), ''), 'settlement_refused'),
1069
+ updated_at = COALESCE(p_occurred_at, now())
1070
+ WHERE renewal_operation_id = p_operation_id AND status = 'committed';
1071
+ END IF;
1072
+
1073
+ UPDATE public.subscription_renewal_operations
1074
+ SET state = p_outcome,
1075
+ terminal_outcome = p_outcome,
1076
+ terminal_reason = CASE WHEN p_outcome = 'refused' THEN COALESCE(
1077
+ NULLIF(btrim(COALESCE(p_reason, '')), ''), 'settlement_refused') ELSE NULL END,
1078
+ terminal_at = COALESCE(p_occurred_at, now()),
1079
+ updated_at = COALESCE(p_occurred_at, now())
1080
+ WHERE id = p_operation_id RETURNING * INTO v_operation;
1081
+ RETURN public.subscription_renewal_operation_response(v_operation, false, false, 'terminal_recorded');
1082
+ END;
1083
+ $$;