@openlup/platform 0.21.0 → 0.23.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 (349) hide show
  1. package/CHANGELOG.md +432 -146
  2. package/README.md +82 -589
  3. package/dist/composition/composeBundle.d.ts +7 -0
  4. package/dist/composition/composeBundle.js +2 -0
  5. package/dist/composition/fulfillmentContribution.d.ts +21 -0
  6. package/dist/composition/fulfillmentContribution.js +19 -0
  7. package/dist/composition/index.d.ts +1 -0
  8. package/dist/composition/index.js +1 -0
  9. package/dist/fulfillment/adapterConformance.d.ts +3 -0
  10. package/dist/fulfillment/adapterConformance.js +223 -0
  11. package/dist/fulfillment/bindings.d.ts +3 -0
  12. package/dist/fulfillment/bindings.js +29 -0
  13. package/dist/fulfillment/commerceFulfillmentHandlers.d.ts +29 -0
  14. package/dist/fulfillment/commerceFulfillmentHandlers.js +184 -0
  15. package/dist/fulfillment/commerceFulfillmentReadModel.d.ts +86 -0
  16. package/dist/fulfillment/commerceFulfillmentReadModel.js +131 -0
  17. package/dist/fulfillment/contracts/alignment.d.ts +25 -0
  18. package/dist/fulfillment/contracts/alignment.js +1 -0
  19. package/dist/fulfillment/contracts/bindings.d.ts +51 -0
  20. package/dist/fulfillment/contracts/bindings.js +1 -0
  21. package/dist/fulfillment/contracts/capabilities.d.ts +29 -0
  22. package/dist/fulfillment/contracts/capabilities.js +1 -0
  23. package/dist/fulfillment/contracts/conformance.d.ts +95 -0
  24. package/dist/fulfillment/contracts/conformance.js +1 -0
  25. package/dist/fulfillment/contracts/customerAlignment.d.ts +6 -0
  26. package/dist/fulfillment/contracts/customerAlignment.js +1 -0
  27. package/dist/fulfillment/contracts/dispatch.d.ts +143 -0
  28. package/dist/fulfillment/contracts/dispatch.js +1 -0
  29. package/dist/fulfillment/contracts/index.d.ts +18 -0
  30. package/dist/fulfillment/contracts/index.js +1 -0
  31. package/dist/fulfillment/contracts/label.d.ts +35 -0
  32. package/dist/fulfillment/contracts/label.js +1 -0
  33. package/dist/fulfillment/contracts/nativeUnsupported.d.ts +6 -0
  34. package/dist/fulfillment/contracts/nativeUnsupported.js +1 -0
  35. package/dist/fulfillment/contracts/observation.d.ts +72 -0
  36. package/dist/fulfillment/contracts/observation.js +1 -0
  37. package/dist/fulfillment/contracts/operator.d.ts +64 -0
  38. package/dist/fulfillment/contracts/operator.js +1 -0
  39. package/dist/fulfillment/contracts/read.d.ts +110 -0
  40. package/dist/fulfillment/contracts/read.js +1 -0
  41. package/dist/fulfillment/contracts/readback.d.ts +59 -0
  42. package/dist/fulfillment/contracts/readback.js +1 -0
  43. package/dist/fulfillment/contracts/replacement.d.ts +34 -0
  44. package/dist/fulfillment/contracts/replacement.js +1 -0
  45. package/dist/fulfillment/contracts/store.d.ts +89 -0
  46. package/dist/fulfillment/contracts/store.js +1 -0
  47. package/dist/fulfillment/contracts/sync.d.ts +226 -0
  48. package/dist/fulfillment/contracts/sync.js +1 -0
  49. package/dist/fulfillment/currentFulfillmentParcel.d.ts +2 -0
  50. package/dist/fulfillment/currentFulfillmentParcel.js +1 -0
  51. package/dist/fulfillment/delayReadModel.d.ts +11 -0
  52. package/dist/fulfillment/delayReadModel.js +45 -0
  53. package/dist/fulfillment/deliveryAlignmentReadModel.d.ts +4 -0
  54. package/dist/fulfillment/deliveryAlignmentReadModel.js +12 -0
  55. package/dist/fulfillment/dispatchProjection.d.ts +26 -0
  56. package/dist/fulfillment/dispatchProjection.js +107 -0
  57. package/dist/fulfillment/fulfillmentObservationEffects.d.ts +66 -0
  58. package/dist/fulfillment/fulfillmentObservationEffects.js +36 -0
  59. package/dist/fulfillment/handlerSchemas.d.ts +647 -0
  60. package/dist/fulfillment/handlerSchemas.js +213 -0
  61. package/dist/fulfillment/handlerSupport.d.ts +33 -0
  62. package/dist/fulfillment/handlerSupport.js +25 -0
  63. package/dist/fulfillment/index.d.ts +38 -0
  64. package/dist/fulfillment/index.js +37 -0
  65. package/dist/fulfillment/labelLifecycle.d.ts +24 -0
  66. package/dist/fulfillment/labelLifecycle.js +9 -0
  67. package/dist/fulfillment/lowStockEvidenceHandler.d.ts +36 -0
  68. package/dist/fulfillment/lowStockEvidenceHandler.js +46 -0
  69. package/dist/fulfillment/migrations.d.ts +32 -0
  70. package/dist/fulfillment/migrations.js +9 -0
  71. package/dist/fulfillment/nativeUnsupported.d.ts +10 -0
  72. package/dist/fulfillment/nativeUnsupported.js +18 -0
  73. package/dist/fulfillment/normalizedFulfillmentFacts.d.ts +2 -0
  74. package/dist/fulfillment/normalizedFulfillmentFacts.js +1 -0
  75. package/dist/fulfillment/observationProjection.d.ts +32 -0
  76. package/dist/fulfillment/observationProjection.js +90 -0
  77. package/dist/fulfillment/omnipack/capabilities.d.ts +3 -0
  78. package/dist/fulfillment/omnipack/capabilities.js +16 -0
  79. package/dist/fulfillment/omnipack/client.d.ts +39 -0
  80. package/dist/fulfillment/omnipack/client.js +172 -0
  81. package/dist/fulfillment/omnipack/index.d.ts +55 -0
  82. package/dist/fulfillment/omnipack/index.js +408 -0
  83. package/dist/fulfillment/omnipack/mappers.d.ts +79 -0
  84. package/dist/fulfillment/omnipack/mappers.js +199 -0
  85. package/dist/fulfillment/omnipack/observation.d.ts +28 -0
  86. package/dist/fulfillment/omnipack/observation.js +72 -0
  87. package/dist/fulfillment/omnipack/omnipackDispatchPayload.d.ts +36 -0
  88. package/dist/fulfillment/omnipack/omnipackDispatchPayload.js +63 -0
  89. package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.d.ts +83 -0
  90. package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.js +113 -0
  91. package/dist/fulfillment/omnipack/omnipackProductPackPayload.d.ts +11 -0
  92. package/dist/fulfillment/omnipack/omnipackProductPackPayload.js +38 -0
  93. package/dist/fulfillment/omnipack/omnipackProductPayload.d.ts +18 -0
  94. package/dist/fulfillment/omnipack/omnipackProductPayload.js +51 -0
  95. package/dist/fulfillment/omnipack/omnipackReconciliationPayload.d.ts +1 -0
  96. package/dist/fulfillment/omnipack/omnipackReconciliationPayload.js +3 -0
  97. package/dist/fulfillment/omnipack/omnipackStatusVocabulary.d.ts +4 -0
  98. package/dist/fulfillment/omnipack/omnipackStatusVocabulary.js +87 -0
  99. package/dist/fulfillment/omnipack/omnipackWebhookPayload.d.ts +9 -0
  100. package/dist/fulfillment/omnipack/omnipackWebhookPayload.js +76 -0
  101. package/dist/fulfillment/omnipack/productSyncMappers.d.ts +18 -0
  102. package/dist/fulfillment/omnipack/productSyncMappers.js +43 -0
  103. package/dist/fulfillment/omnipack/providerError.d.ts +25 -0
  104. package/dist/fulfillment/omnipack/providerError.js +47 -0
  105. package/dist/fulfillment/omnipack/reconciliation.d.ts +15 -0
  106. package/dist/fulfillment/omnipack/reconciliation.js +55 -0
  107. package/dist/fulfillment/omnipack/trackingReferences.d.ts +16 -0
  108. package/dist/fulfillment/omnipack/trackingReferences.js +52 -0
  109. package/dist/fulfillment/paidFulfillmentRecovery.d.ts +67 -0
  110. package/dist/fulfillment/paidFulfillmentRecovery.js +154 -0
  111. package/dist/fulfillment/paidHandoff.d.ts +27 -0
  112. package/dist/fulfillment/paidHandoff.js +86 -0
  113. package/dist/fulfillment/paidHandoffPorts.d.ts +24 -0
  114. package/dist/fulfillment/paidHandoffPorts.js +3 -0
  115. package/dist/fulfillment/postgresDeliveryAlignment.d.ts +4 -0
  116. package/dist/fulfillment/postgresDeliveryAlignment.js +12 -0
  117. package/dist/fulfillment/postgresDispatchStore.d.ts +16 -0
  118. package/dist/fulfillment/postgresDispatchStore.js +44 -0
  119. package/dist/fulfillment/postgresEvidenceReadPort.d.ts +25 -0
  120. package/dist/fulfillment/postgresEvidenceReadPort.js +45 -0
  121. package/dist/fulfillment/postgresPaidFulfillmentRecovery.d.ts +25 -0
  122. package/dist/fulfillment/postgresPaidFulfillmentRecovery.js +26 -0
  123. package/dist/fulfillment/postgresPaidHandoffStore.d.ts +4 -0
  124. package/dist/fulfillment/postgresPaidHandoffStore.js +10 -0
  125. package/dist/fulfillment/postgresShipmentSpine.d.ts +70 -0
  126. package/dist/fulfillment/postgresShipmentSpine.js +246 -0
  127. package/dist/fulfillment/postgresStockSyncPort.d.ts +8 -0
  128. package/dist/fulfillment/postgresStockSyncPort.js +66 -0
  129. package/dist/fulfillment/productReconciliationEvidenceHandler.d.ts +36 -0
  130. package/dist/fulfillment/productReconciliationEvidenceHandler.js +50 -0
  131. package/dist/fulfillment/providerCapabilities.d.ts +9 -0
  132. package/dist/fulfillment/providerCapabilities.js +17 -0
  133. package/dist/fulfillment/providerCommandLedger.d.ts +143 -0
  134. package/dist/fulfillment/providerCommandLedger.js +14 -0
  135. package/dist/fulfillment/providerDispatchAcceptance.d.ts +27 -0
  136. package/dist/fulfillment/providerDispatchAcceptance.js +29 -0
  137. package/dist/fulfillment/providerDispatchError.d.ts +6 -0
  138. package/dist/fulfillment/providerDispatchError.js +5 -0
  139. package/dist/fulfillment/providerDispatchWorker.d.ts +44 -0
  140. package/dist/fulfillment/providerDispatchWorker.js +199 -0
  141. package/dist/fulfillment/providerProductSyncWorker.d.ts +28 -0
  142. package/dist/fulfillment/providerProductSyncWorker.js +122 -0
  143. package/dist/fulfillment/providerReconciliationError.d.ts +9 -0
  144. package/dist/fulfillment/providerReconciliationError.js +17 -0
  145. package/dist/fulfillment/providerReconciliationTerminalSkip.d.ts +2 -0
  146. package/dist/fulfillment/providerReconciliationTerminalSkip.js +6 -0
  147. package/dist/fulfillment/providerReconciliationTimestamp.d.ts +1 -0
  148. package/dist/fulfillment/providerReconciliationTimestamp.js +27 -0
  149. package/dist/fulfillment/providerReconciliationWorker.d.ts +90 -0
  150. package/dist/fulfillment/providerReconciliationWorker.js +207 -0
  151. package/dist/fulfillment/providerStockAvailability.d.ts +13 -0
  152. package/dist/fulfillment/providerStockAvailability.js +51 -0
  153. package/dist/fulfillment/providerStockSyncWorker.d.ts +41 -0
  154. package/dist/fulfillment/providerStockSyncWorker.js +295 -0
  155. package/dist/fulfillment/providerTrackingReferences.d.ts +16 -0
  156. package/dist/fulfillment/providerTrackingReferences.js +23 -0
  157. package/dist/fulfillment/replacementRefusals.d.ts +5 -0
  158. package/dist/fulfillment/replacementRefusals.js +38 -0
  159. package/dist/fulfillment/replacementShipmentHandler.d.ts +5 -0
  160. package/dist/fulfillment/replacementShipmentHandler.js +46 -0
  161. package/dist/fulfillment/schemaCapabilities.d.ts +13 -0
  162. package/dist/fulfillment/schemaCapabilities.js +7 -0
  163. package/dist/fulfillment/shipmentsOverviewHandler.d.ts +16 -0
  164. package/dist/fulfillment/shipmentsOverviewHandler.js +54 -0
  165. package/dist/fulfillment/statusMap.d.ts +2 -0
  166. package/dist/fulfillment/statusMap.js +1 -0
  167. package/dist/fulfillment/trackingProgress.d.ts +1 -0
  168. package/dist/fulfillment/trackingProgress.js +1 -0
  169. package/dist/fulfillment/verifiedIngress.d.ts +79 -0
  170. package/dist/fulfillment/verifiedIngress.js +94 -0
  171. package/dist/invoicing/capabilities.d.ts +11 -0
  172. package/dist/invoicing/capabilities.js +10 -0
  173. package/dist/invoicing/contracts/capabilities.d.ts +40 -0
  174. package/dist/invoicing/contracts/capabilities.js +1 -0
  175. package/dist/invoicing/contracts/companyLookup.d.ts +49 -0
  176. package/dist/invoicing/contracts/companyLookup.js +1 -0
  177. package/dist/invoicing/contracts/delivery.d.ts +74 -0
  178. package/dist/invoicing/contracts/delivery.js +1 -0
  179. package/dist/invoicing/contracts/facts.d.ts +100 -0
  180. package/dist/invoicing/contracts/facts.js +1 -0
  181. package/dist/invoicing/contracts/host.d.ts +246 -0
  182. package/dist/invoicing/contracts/host.js +1 -0
  183. package/dist/invoicing/contracts/index.d.ts +16 -0
  184. package/dist/invoicing/contracts/index.js +1 -0
  185. package/dist/invoicing/contracts/provider.d.ts +126 -0
  186. package/dist/invoicing/contracts/provider.js +1 -0
  187. package/dist/invoicing/contracts/settlement.d.ts +66 -0
  188. package/dist/invoicing/contracts/settlement.js +1 -0
  189. package/dist/invoicing/contracts/store.d.ts +267 -0
  190. package/dist/invoicing/contracts/store.js +1 -0
  191. package/dist/invoicing/index.d.ts +1 -0
  192. package/dist/invoicing/index.js +1 -0
  193. package/dist/payments/stripe/index.d.ts +25 -0
  194. package/dist/payments/stripe/index.js +25 -0
  195. package/dist/payments/tpay/index.d.ts +30 -0
  196. package/dist/payments/tpay/index.js +30 -0
  197. package/dist/subscriptions/automaticRenewal.d.ts +44 -0
  198. package/dist/subscriptions/automaticRenewal.js +81 -0
  199. package/dist/subscriptions/automaticRenewalExecution.d.ts +21 -0
  200. package/dist/subscriptions/automaticRenewalExecution.js +44 -0
  201. package/dist/subscriptions/contracts/commands.d.ts +115 -0
  202. package/dist/subscriptions/contracts/commands.js +1 -0
  203. package/dist/subscriptions/contracts/dunning.d.ts +133 -0
  204. package/dist/subscriptions/contracts/dunning.js +1 -0
  205. package/dist/subscriptions/contracts/index.d.ts +6 -0
  206. package/dist/subscriptions/contracts/index.js +1 -0
  207. package/dist/subscriptions/contracts/messages.d.ts +43 -0
  208. package/dist/subscriptions/contracts/messages.js +1 -0
  209. package/dist/subscriptions/contracts/renewal.d.ts +533 -0
  210. package/dist/subscriptions/contracts/renewal.js +1 -0
  211. package/dist/subscriptions/contracts/stockWait.d.ts +73 -0
  212. package/dist/subscriptions/contracts/stockWait.js +1 -0
  213. package/dist/subscriptions/index.d.ts +39 -0
  214. package/dist/subscriptions/index.js +17 -0
  215. package/dist/subscriptions/paymentOutcomes.d.ts +8 -0
  216. package/dist/subscriptions/paymentOutcomes.js +27 -0
  217. package/dist/subscriptions/postgresRenewalStore.d.ts +20 -0
  218. package/dist/subscriptions/postgresRenewalStore.js +61 -0
  219. package/dist/subscriptions/postgresRetryStore.d.ts +78 -0
  220. package/dist/subscriptions/postgresRetryStore.js +142 -0
  221. package/dist/subscriptions/renewalCycleFailureMechanism.d.ts +34 -0
  222. package/dist/subscriptions/renewalCycleFailureMechanism.js +79 -0
  223. package/dist/subscriptions/renewalCycleMechanism.d.ts +46 -0
  224. package/dist/subscriptions/renewalCycleMechanism.js +157 -0
  225. package/dist/subscriptions/renewalCycleRefusalMechanism.d.ts +41 -0
  226. package/dist/subscriptions/renewalCycleRefusalMechanism.js +160 -0
  227. package/dist/subscriptions/renewalIdentity.d.ts +3 -0
  228. package/dist/subscriptions/renewalIdentity.js +4 -0
  229. package/package.json +37 -2
  230. package/release-gates.json +97 -3
  231. package/sql/20260808210000_fulfillment_shipment_rail.sql +810 -0
  232. package/sql/20260819150000_fulfillment_shipment_sequence.sql +98 -0
  233. package/sql/20261022080000_fulfillment_command_ledger.sql +786 -0
  234. package/sql/20261022090000_fulfillment_delivery_alignment.sql +1106 -0
  235. package/sql/20261022160000_subscription_retry_lifecycle.sql +328 -0
  236. package/sql/20261022165000_fulfillment_stock_evidence_replay_reopen.sql +30 -0
  237. package/src/composition/composeBundle.ts +4 -0
  238. package/src/composition/fulfillmentContribution.ts +28 -0
  239. package/src/composition/index.ts +1 -0
  240. package/src/fulfillment/adapterConformance.ts +195 -0
  241. package/src/fulfillment/bindings.ts +26 -0
  242. package/src/fulfillment/commerceFulfillmentHandlers.ts +263 -0
  243. package/src/fulfillment/commerceFulfillmentReadModel.ts +257 -0
  244. package/src/fulfillment/contracts/alignment.ts +13 -0
  245. package/src/fulfillment/contracts/bindings.ts +30 -0
  246. package/src/fulfillment/contracts/capabilities.ts +15 -0
  247. package/src/fulfillment/contracts/conformance.ts +63 -0
  248. package/src/fulfillment/contracts/customerAlignment.ts +4 -0
  249. package/src/fulfillment/contracts/dispatch.ts +73 -0
  250. package/src/fulfillment/contracts/index.ts +18 -0
  251. package/src/fulfillment/contracts/label.ts +9 -0
  252. package/src/fulfillment/contracts/nativeUnsupported.ts +6 -0
  253. package/src/fulfillment/contracts/observation.ts +26 -0
  254. package/src/fulfillment/contracts/operator.ts +31 -0
  255. package/src/fulfillment/contracts/read.ts +42 -0
  256. package/src/fulfillment/contracts/readback.ts +24 -0
  257. package/src/fulfillment/contracts/replacement.ts +15 -0
  258. package/src/fulfillment/contracts/store.ts +27 -0
  259. package/src/fulfillment/contracts/sync.ts +72 -0
  260. package/src/fulfillment/currentFulfillmentParcel.ts +2 -0
  261. package/src/fulfillment/delayReadModel.ts +60 -0
  262. package/src/fulfillment/deliveryAlignmentReadModel.ts +15 -0
  263. package/src/fulfillment/dispatchProjection.ts +105 -0
  264. package/src/fulfillment/fulfillmentObservationEffects.ts +86 -0
  265. package/src/fulfillment/handlerSchemas.ts +255 -0
  266. package/src/fulfillment/handlerSupport.ts +25 -0
  267. package/src/fulfillment/index.ts +39 -0
  268. package/src/fulfillment/labelLifecycle.ts +11 -0
  269. package/src/fulfillment/lowStockEvidenceHandler.ts +60 -0
  270. package/src/fulfillment/migrations.ts +9 -0
  271. package/src/fulfillment/nativeUnsupported.ts +23 -0
  272. package/src/fulfillment/normalizedFulfillmentFacts.ts +2 -0
  273. package/src/fulfillment/observationProjection.ts +92 -0
  274. package/src/fulfillment/omnipack/capabilities.ts +18 -0
  275. package/src/fulfillment/omnipack/client.ts +270 -0
  276. package/src/fulfillment/omnipack/index.ts +369 -0
  277. package/src/fulfillment/omnipack/mappers.ts +282 -0
  278. package/src/fulfillment/omnipack/observation.ts +79 -0
  279. package/src/fulfillment/omnipack/omnipackDispatchPayload.ts +93 -0
  280. package/src/fulfillment/omnipack/omnipackOutboundOrderPayload.ts +194 -0
  281. package/src/fulfillment/omnipack/omnipackProductPackPayload.ts +47 -0
  282. package/src/fulfillment/omnipack/omnipackProductPayload.ts +67 -0
  283. package/src/fulfillment/omnipack/omnipackReconciliationPayload.ts +7 -0
  284. package/src/fulfillment/omnipack/omnipackStatusVocabulary.ts +103 -0
  285. package/src/fulfillment/omnipack/omnipackWebhookPayload.ts +73 -0
  286. package/src/fulfillment/omnipack/productSyncMappers.ts +74 -0
  287. package/src/fulfillment/omnipack/providerError.ts +64 -0
  288. package/src/fulfillment/omnipack/reconciliation.ts +56 -0
  289. package/src/fulfillment/omnipack/trackingReferences.ts +77 -0
  290. package/src/fulfillment/paidFulfillmentRecovery.ts +252 -0
  291. package/src/fulfillment/paidHandoff.ts +122 -0
  292. package/src/fulfillment/paidHandoffPorts.ts +21 -0
  293. package/src/fulfillment/postgresDeliveryAlignment.ts +14 -0
  294. package/src/fulfillment/postgresDispatchStore.ts +55 -0
  295. package/src/fulfillment/postgresEvidenceReadPort.ts +59 -0
  296. package/src/fulfillment/postgresPaidFulfillmentRecovery.ts +29 -0
  297. package/src/fulfillment/postgresPaidHandoffStore.ts +11 -0
  298. package/src/fulfillment/postgresShipmentSpine.ts +338 -0
  299. package/src/fulfillment/postgresStockSyncPort.ts +68 -0
  300. package/src/fulfillment/productReconciliationEvidenceHandler.ts +64 -0
  301. package/src/fulfillment/providerCapabilities.ts +18 -0
  302. package/src/fulfillment/providerCommandLedger.ts +53 -0
  303. package/src/fulfillment/providerDispatchAcceptance.ts +37 -0
  304. package/src/fulfillment/providerDispatchError.ts +7 -0
  305. package/src/fulfillment/providerDispatchWorker.ts +159 -0
  306. package/src/fulfillment/providerProductSyncWorker.ts +154 -0
  307. package/src/fulfillment/providerReconciliationError.ts +20 -0
  308. package/src/fulfillment/providerReconciliationTerminalSkip.ts +8 -0
  309. package/src/fulfillment/providerReconciliationTimestamp.ts +34 -0
  310. package/src/fulfillment/providerReconciliationWorker.ts +206 -0
  311. package/src/fulfillment/providerStockAvailability.ts +60 -0
  312. package/src/fulfillment/providerStockSyncWorker.ts +318 -0
  313. package/src/fulfillment/providerTrackingReferences.ts +32 -0
  314. package/src/fulfillment/replacementRefusals.ts +43 -0
  315. package/src/fulfillment/replacementShipmentHandler.ts +47 -0
  316. package/src/fulfillment/schemaCapabilities.ts +7 -0
  317. package/src/fulfillment/shipmentsOverviewHandler.ts +73 -0
  318. package/src/fulfillment/statusMap.ts +2 -0
  319. package/src/fulfillment/trackingProgress.ts +1 -0
  320. package/src/fulfillment/verifiedIngress.ts +113 -0
  321. package/src/invoicing/capabilities.ts +9 -0
  322. package/src/invoicing/contracts/capabilities.ts +15 -0
  323. package/src/invoicing/contracts/companyLookup.ts +13 -0
  324. package/src/invoicing/contracts/delivery.ts +24 -0
  325. package/src/invoicing/contracts/facts.ts +60 -0
  326. package/src/invoicing/contracts/host.ts +85 -0
  327. package/src/invoicing/contracts/index.ts +16 -0
  328. package/src/invoicing/contracts/provider.ts +78 -0
  329. package/src/invoicing/contracts/settlement.ts +39 -0
  330. package/src/invoicing/contracts/store.ts +98 -0
  331. package/src/invoicing/index.ts +1 -0
  332. package/src/payments/stripe/index.ts +30 -0
  333. package/src/payments/tpay/index.ts +36 -0
  334. package/src/subscriptions/automaticRenewal.ts +87 -0
  335. package/src/subscriptions/automaticRenewalExecution.ts +39 -0
  336. package/src/subscriptions/contracts/commands.ts +46 -0
  337. package/src/subscriptions/contracts/dunning.ts +48 -0
  338. package/src/subscriptions/contracts/index.ts +6 -0
  339. package/src/subscriptions/contracts/messages.ts +18 -0
  340. package/src/subscriptions/contracts/renewal.ts +325 -0
  341. package/src/subscriptions/contracts/stockWait.ts +34 -0
  342. package/src/subscriptions/index.ts +24 -0
  343. package/src/subscriptions/paymentOutcomes.ts +23 -0
  344. package/src/subscriptions/postgresRenewalStore.ts +76 -0
  345. package/src/subscriptions/postgresRetryStore.ts +174 -0
  346. package/src/subscriptions/renewalCycleFailureMechanism.ts +119 -0
  347. package/src/subscriptions/renewalCycleMechanism.ts +225 -0
  348. package/src/subscriptions/renewalCycleRefusalMechanism.ts +215 -0
  349. package/src/subscriptions/renewalIdentity.ts +5 -0
@@ -0,0 +1,39 @@
1
+ import { nextRetryAttemptAt } from "@openlup/core/subscription";
2
+ import type { RenewalDue, RenewalPaymentRail, RenewalReadback, RenewalSettlementPort, RenewalStorePort } from "./contracts/renewal.js";
3
+ import type { AutomaticRenewalPolicy, AutomaticRenewalRunDeps, AutomaticRenewalRunResult, AutomaticRenewalRunStatus } from "./automaticRenewal.js";
4
+ export function automaticRenewalResult(operation: RenewalReadback, status: AutomaticRenewalRunStatus, reason: string | null): AutomaticRenewalRunResult {
5
+ return { subscriptionId: operation.subscriptionId, identityKey: operation.identityKey, operationId: operation.operationId, status, replayed: operation.replayed, reason };
6
+ }
7
+ export async function recoverPreparedExternalAttempt(input: { operation: RenewalReadback; rail: RenewalPaymentRail; settlement: RenewalSettlementPort; occurredAt: string }): Promise<string | null> {
8
+ if (input.operation.externalAttemptRef) return input.operation.externalAttemptRef;
9
+ if (!input.operation.settlementIntentId) return null;
10
+ const externalAttemptRef = await input.rail.findExternalAttempt({ settlementIntentId: input.operation.settlementIntentId });
11
+ if (!externalAttemptRef) return null;
12
+ await input.settlement.acknowledgeExternalAttempt({ operationId: input.operation.operationId, operationFingerprint: input.operation.operationFingerprint, externalAttemptRef, acknowledgedAt: input.occurredAt });
13
+ return externalAttemptRef;
14
+ }
15
+ export async function runRenewalBatchRows(due: RenewalDue[], run: (row: RenewalDue) => Promise<AutomaticRenewalRunResult>, policy: Pick<AutomaticRenewalPolicy, "identityKey">): Promise<AutomaticRenewalRunResult[]> {
16
+ const results: AutomaticRenewalRunResult[] = [];
17
+ for (const row of due) {
18
+ try { results.push(await run(row)); }
19
+ catch { results.push({ subscriptionId: row.subscriptionId, identityKey: policy.identityKey(row), operationId: "", status: "error", replayed: false, reason: "renewal_processing_failed" }); }
20
+ }
21
+ return results;
22
+ }
23
+ export async function refuseRenewalBeforePayment(input: { store: RenewalStorePort; operation: RenewalReadback; reason: string; occurredAt: string }): Promise<AutomaticRenewalRunResult> {
24
+ const refused = await input.store.refuseBeforePayment({ operationId: input.operation.operationId, claimToken: input.operation.claimToken, reason: input.reason, occurredAt: input.occurredAt });
25
+ return automaticRenewalResult(refused, "refused", input.reason);
26
+ }
27
+ const HANDOFF_OPENS_AT_RETRY_ATTEMPT = 1;
28
+ export async function handoffRefusal(deps: AutomaticRenewalRunDeps, operation: RenewalReadback, prepared: { cycleId: string; orderId: string; settlementIntentId: string }, externalAttemptRef: string | null, reason: string, failureClass: string, occurredAt: string): Promise<AutomaticRenewalRunResult> {
29
+ await deps.failureHandoff.handoffFailure({
30
+ idempotencyKey: `${operation.identityKey}:failure-handoff`, subscriptionId: operation.subscriptionId,
31
+ clientId: operation.cycleSnapshot.clientId, cycleId: prepared.cycleId, orderId: prepared.orderId,
32
+ paymentIntentId: prepared.settlementIntentId, retryAttempt: HANDOFF_OPENS_AT_RETRY_ATTEMPT,
33
+ failureClass, failureReason: reason,
34
+ nextRetryAt: nextRetryAttemptAt(occurredAt, HANDOFF_OPENS_AT_RETRY_ATTEMPT, undefined, failureClass),
35
+ occurredAt, amountMinor: operation.cycleSnapshot.pricingSnapshot.totalAmountMinor, currency: operation.cycleSnapshot.pricingSnapshot.currency,
36
+ });
37
+ await deps.settlement.recordTerminalOutcome({ operationId: operation.operationId, eventKey: `${operation.identityKey}:provider-refusal`, operationFingerprint: operation.operationFingerprint, outcome: "refused", externalRef: externalAttemptRef, reason, occurredAt });
38
+ return automaticRenewalResult(operation, "refused", reason);
39
+ }
@@ -0,0 +1,46 @@
1
+ export interface SubscriptionCommandContext {
2
+ actor: { kind: "customer" | "operator"; actorId: string; clientId: string };
3
+ subscriptionId: string; idempotencyKey: string; now: string;
4
+ }
5
+ export type SubscriptionCommand = SubscriptionCommandContext & (
6
+ | { action: "pause"; pausePreset: string; reason: string | null; survey: string | null; saveOffer: string | null }
7
+ | { action: "resume" | "skip" | "cancel"; reason: string | null; confirmedChargeTiming: boolean }
8
+ | { action: "slide"; newNextCycleAt: string; confirmedChargeTiming: boolean }
9
+ | { action: "edit_future_terms"; shippingAddressId: string; planDays: number; cadenceDays: number;
10
+ expectedTemplateVersion: number; acceptedQuoteHash: string; confirmedChargeTiming: boolean;
11
+ coreLines: readonly SubscriptionBundleLine[]; addonLines: readonly SubscriptionBundleLine[];
12
+ compositionConstraint: string }
13
+ );
14
+ export interface SubscriptionBundleLine { sku: string; quantity: number }
15
+ export interface SubscriptionCommandReceipt {
16
+ actorId: string; idempotencyKey: string; commandFingerprint: string;
17
+ outcome: "applied" | "replayed" | "noop"; subscriptionId: string;
18
+ originalState: string; nextCycleAt: string | null; revision: string; eventId: string | null;
19
+ }
20
+ export type SubscriptionUnsupportedCode = "multi_cycle_prepaid_unsupported" | "native_generic_bundle_commands_unsupported" | "native_command_bound_receipts_unsupported" | "native_prepared_attempt_store_unsupported";
21
+ export interface SubscriptionUnsupported { supported: false; refusalCode: SubscriptionUnsupportedCode }
22
+ /** Readiness must be selected explicitly; a port declaration does not install a store. */
23
+ export interface SubscriptionCapabilities {
24
+ graph: "managed" | "native";
25
+ multiCyclePrepaid: { supported: false; refusalCode: "multi_cycle_prepaid_unsupported" };
26
+ genericBundleCommands: { supported: true } | { supported: false; refusalCode: "native_generic_bundle_commands_unsupported" };
27
+ commandBoundReceipts: { supported: true } | { supported: false; refusalCode: "native_command_bound_receipts_unsupported" };
28
+ preparedAttemptStore: { supported: true } | { supported: false; refusalCode: "native_prepared_attempt_store_unsupported" };
29
+ retryRecovery: "unavailable" | "selected";
30
+ stockRecovery: "unavailable" | "selected";
31
+ deliveryAdmission: "required";
32
+ executorCount: 1;
33
+ defaultEnabled: false;
34
+ }
35
+ export interface SubscriptionCommandPort {
36
+ readonly capabilities: SubscriptionCapabilities;
37
+ /** Future term writes, actor/command receipt and events must commit atomically. */
38
+ apply(command: SubscriptionCommand): Promise<SubscriptionCommandReceipt | SubscriptionUnsupported>;
39
+ }
40
+ export interface SubscriptionActivationPort {
41
+ activate(input: { idempotencyKey: string; orderId: string; paymentIntentId: string;
42
+ paidEvidenceRef: string; paidAt: string; paymentMethodRef: string; paymentMethodKind: string;
43
+ checkoutKind: string }): Promise<
44
+ | { outcome: "noop" | "blocked"; reason: string }
45
+ | { outcome: "active"; subscriptionId: string; orderId: string; paymentIntentId: string; nextCycleAt: string; cadenceDays: number; replayed: boolean }>;
46
+ }
@@ -0,0 +1,48 @@
1
+ export interface DunningCaseOpenInput {
2
+ idempotencyKey: string; subscriptionId: string; cycleId: string | null; clientId: string;
3
+ retryAttempt: number; nextRetryAt: string | null; failureClass: string | null;
4
+ failureReason: string | null; occurredAt: string; amountMinor: number | null;
5
+ currency: string | null; templateSlug: string; recoveryUrlPath: string | null;
6
+ }
7
+ export interface DunningCaseOpened {
8
+ caseId: string; caseStatus: string; replayed: boolean; notificationId: string | null;
9
+ }
10
+ export interface DunningCaseAdvanced {
11
+ caseId: string; caseStatus: string; notificationId: string | null;
12
+ retryAttempt: number | null; paused: false; pauseWindowId: null; terminal: boolean;
13
+ }
14
+ export type DunningRefusalCode = "case_not_open" | "recoverable_order_unavailable";
15
+ export interface DunningCaseLifecyclePort {
16
+ openCase(input: DunningCaseOpenInput): Promise<DunningCaseOpened>;
17
+ recordFailedAttempt(input: { caseId: string; occurredAt: string; failureClass: string | null; failureReason: string | null; retryAttempt: number; nextRetryAt: string | null; templateSlug: string; recoveryUrlPath: string | null }): Promise<DunningCaseAdvanced>;
18
+ recordRecovery(input: { caseId: string; recoveredAt: string; templateSlug: string }): Promise<
19
+ | { outcome: "recovered"; caseId: string; caseStatus: string; replayed: boolean; notificationId: string | null }
20
+ | { outcome: "refused"; caseId: string; caseStatus: string; refusalCode: DunningRefusalCode }>;
21
+ issueRecoveryLink(input: { caseId: string; tokenHash: string; expiresAt: string }): Promise<
22
+ | { outcome: "issued"; orderId: string }
23
+ | { outcome: "refused"; refusalCode: DunningRefusalCode }>;
24
+ }
25
+ export interface RenewalRetryState {
26
+ rowPresent: boolean; retryAttempt: number | null; nextRetryAt: string | null;
27
+ }
28
+ export interface RenewalRetryObligation {
29
+ operationId: string; subscriptionId: string; cycleId: string; orderId: string;
30
+ settlementIntentId: string; logicalCycleNumber: number; nativeRenewalCounter: number;
31
+ retryAttempt: number; externalAttemptSequence: number; nextRetryAt: string | null;
32
+ failureClass: string | null; failureReason: string | null; occurredAt: string;
33
+ identityKey: string; requestFingerprint: string;
34
+ disposition: "retry_scheduled" | "payment_failed" | "paid";
35
+ }
36
+ export interface RenewalRecoveryAuthorization {
37
+ tokenId: string; tokenHash: string; caseId: string; clientId: string;
38
+ authUserId: string | null; subscriptionId: string; cycleId: string; orderId: string;
39
+ payerAccountRef: string | null; purpose: "repair_payment" | "resume_subscription";
40
+ retryAttempt: number; paymentMethodRef: string; paymentMethodKind: string;
41
+ expiresAt: string; usedAt: string | null; revokedAt: string | null; requestedAt: string;
42
+ }
43
+ export interface RenewalFailureHandoffPort {
44
+ handoffFailure(input: { idempotencyKey: string; subscriptionId: string; clientId: string;
45
+ cycleId: string; orderId: string; paymentIntentId: string; retryAttempt: number;
46
+ failureClass: string; failureReason: string; nextRetryAt: string | null;
47
+ occurredAt: string; amountMinor: number | null; currency: string | null }): Promise<{ caseId: string | null; replayed: boolean }>;
48
+ }
@@ -0,0 +1,6 @@
1
+ export type { RenewalSchedule, RenewalIdentity, RenewalAdmission, RenewalLine, RenewalCycleCorrelation, RenewalTemplateSnapshot, RenewalPricingSnapshot, RenewalOrderSnapshot, RenewalFrozenSnapshot, RenewalDue, RenewalOperationState, RenewalReadback, RenewalPaymentEvidence, RenewalTransactionUnits, RenewalReservationResult, RenewalPreparedSettlement, RenewalStorePort, RenewalSettlementPort, RenewalExecutionRequest, RenewalPaymentRail, RenewalResult } from "./renewal.js";
2
+ export type { DunningCaseOpenInput, DunningCaseOpened, DunningCaseAdvanced, DunningRefusalCode, DunningCaseLifecyclePort, RenewalRetryState, RenewalRetryObligation, RenewalRecoveryAuthorization, RenewalFailureHandoffPort } from "./dunning.js";
3
+ export type { StockWaitRecoveryBinding, StockWaitDeficit, StockWaitCase, StockWaitStorePort } from "./stockWait.js";
4
+ export type { SubscriptionCommandContext, SubscriptionCommand, SubscriptionBundleLine, SubscriptionCommandReceipt, SubscriptionUnsupportedCode, SubscriptionUnsupported, SubscriptionCapabilities, SubscriptionCommandPort, SubscriptionActivationPort } from "./commands.js";
5
+ export type { SubscriptionLifecycleFact, SubscriptionLifecycleMessageDescriptor, SubscriptionLifecycleMessagePort } from "./messages.js";
6
+ export type { SubscriptionRenewalOutcome, HostedRenewalDue, SubscriptionRenewalChargeResult, SubscriptionCycleSnapshotResult, SubscriptionCycleOrderInput, SubscriptionCycleOrderResult, SubscriptionReservationPreflightResult, SubscriptionRenewalPersistencePort, FailurePropagationKind, FailurePropagationContext, CycleRetryState, DunningCaseOpenOutcome, CycleChargeFailurePropagationPort, FailurePropagationResult, RenewalPrepareInput, RenewalAttemptResult, RenewalIntentResult, RenewalPaymentPort, RenewalExecutionPort, RenewalDeliveryAdmission, RenewalAllowedAdmission, RenewalDeliveryClient, RenewalFailureDeps, RenewalPreflightDeps, RenewalCycleDeps, RenewalExecutionContext, RenewalOutcomeContext, RenewalReplayContext, RenewalPreparedAttemptPrepareInput, RenewalPreparedAttemptPersistenceResult, RenewalPreparedAttemptFinalizeInput, RenewalPreparedAttemptPersistencePort } from "./renewal.js";
@@ -0,0 +1,18 @@
1
+ export type SubscriptionLifecycleFact = {
2
+ factId: string; idempotencyKey: string; subscriptionId: string; clientId: string;
3
+ cycleId: string | null; caseId: string | null; orderId: string | null;
4
+ occurredAt: string; firstRecordedAt: string;
5
+ } & (
6
+ | { kind: "payment_failed" | "payment_expired"; retryAttempt: number; nextRetryAt: string | null; failureClass: string | null; amountMinor: number | null; currency: string | null }
7
+ | { kind: "payment_recovered"; paymentEvidenceRef: string }
8
+ | { kind: "stock_wait_started" | "stock_wait_resumed"; frozenFingerprint: string }
9
+ | { kind: "winback_eligible"; cancelledAt: string; paymentMethodRef: string }
10
+ );
11
+ /** M04 receives a descriptor; host owns copy, recipient, consent and transport. */
12
+ export interface SubscriptionLifecycleMessageDescriptor {
13
+ fact: SubscriptionLifecycleFact; messageKey: string; templateSlug: string;
14
+ recipientRef: string; locale: string; recoveryUrlPath: string | null;
15
+ }
16
+ export interface SubscriptionLifecycleMessagePort {
17
+ enqueue(descriptor: SubscriptionLifecycleMessageDescriptor): Promise<{ notificationId: string; replayed: boolean }>;
18
+ }
@@ -0,0 +1,325 @@
1
+ import type {
2
+ PaymentAttemptStatus, PaymentExecutionBaseInput, PaymentExecutionBaseResult,
3
+ PaymentProviderCapabilityDescriptor, ProviderAttemptIdentity,
4
+ } from "@openlup/core/payment";
5
+
6
+ /** Structural hosted-cycle contracts; native vocabulary is bound by the caller. */
7
+ export type SubscriptionRenewalOutcome = "charged" | "requires_action" | "failed" | "skipped";
8
+ export interface HostedRenewalDue {
9
+ subscriptionId: string; nextCycleAt: string; providerKind: string;
10
+ providerMethodRef: string | null;
11
+ }
12
+ export interface SubscriptionRenewalChargeResult {
13
+ subscriptionId: string; outcome: SubscriptionRenewalOutcome; cycleId: string | null;
14
+ cycleNumber: number | null; orderId: string | null; paymentIntentId: string | null;
15
+ attemptStatus: PaymentAttemptStatus | null; replayed: boolean; dunningCaseId?: string | null;
16
+ retryAttempt?: number | null; applyReplayed?: boolean; dunningPropagationFailed?: boolean; reason?: string;
17
+ admissionDeferralReference?: string;
18
+ }
19
+ export interface SubscriptionCycleSnapshotResult {
20
+ cycleNumber: number; retryAttempt: number; providerAttemptSequence: number;
21
+ templateSnapshot: Record<string, unknown>; pricingSnapshot: Record<string, unknown>;
22
+ orderSnapshot: Record<string, unknown>;
23
+ }
24
+ export interface SubscriptionCycleOrderInput {
25
+ idempotencyKey: string; subscriptionId: string; cycleNumber: number; scheduledAt: string;
26
+ templateSnapshot: Record<string, unknown>; pricingSnapshot: Record<string, unknown>;
27
+ orderSnapshot: Record<string, unknown>;
28
+ }
29
+ export interface SubscriptionCycleOrderResult {
30
+ cycleId: string; orderRef: string; orderUuid: string; paymentRef: string; replayed: boolean;
31
+ }
32
+ export interface SubscriptionReservationPreflightResult {
33
+ ok: boolean; reason: string | null; itemsChecked: number; reacquired: number;
34
+ }
35
+ export interface SubscriptionRenewalPersistencePort<M extends string | null | undefined = string | null | undefined> {
36
+ buildCycleSnapshots(input: { subscriptionId: string; scheduledAt: string }): Promise<SubscriptionCycleSnapshotResult>;
37
+ createCycleOrder(input: SubscriptionCycleOrderInput): Promise<SubscriptionCycleOrderResult>;
38
+ preflightReservation(input: { orderId: string; now: string }): Promise<SubscriptionReservationPreflightResult>;
39
+ readMandateRecurringModel(providerKind: string, providerMethodRef: string | null): Promise<M>;
40
+ noteRowOutcome(input: { subscriptionId: string; scheduledAt: string; errorKey: string | null }): Promise<void>;
41
+ }
42
+
43
+ export type FailurePropagationKind =
44
+ | "off_session_requires_action"
45
+ | "provider_declined"
46
+ | "preflight_block";
47
+ export interface FailurePropagationContext {
48
+ kind: FailurePropagationKind;
49
+ executionIdempotencyKey: string;
50
+ providerIdempotencyKey: string;
51
+ providerRequestFingerprint: string;
52
+ paymentIntentId: string;
53
+ cycleId: string;
54
+ subscriptionId: string;
55
+ orderUuid: string;
56
+ providerKind: string;
57
+ failureReason: string;
58
+ failureClassification?: { failureClass: string; decidedBy: string } | null;
59
+ occurredAt: string;
60
+ attemptAlreadyPrepared?: boolean;
61
+ }
62
+ export interface CycleRetryState {
63
+ retryAttempt: number | null;
64
+ nextRetryAt: string | null;
65
+ rowPresent: boolean;
66
+ }
67
+ export type DunningCaseOpenOutcome =
68
+ | { status: "opened"; caseId: string | null }
69
+ | { status: "rejected"; reason: string; code?: string }
70
+ | { status: "unavailable"; reason: string };
71
+ export interface CycleChargeFailurePropagationPort {
72
+ recordBlockedPreflightAttempt(input: {
73
+ idempotencyKey: string;
74
+ paymentIntentId: string;
75
+ providerKind: string;
76
+ providerIdempotencyKey: string;
77
+ providerRequestFingerprint: string;
78
+ failureReason: string;
79
+ }): Promise<void>;
80
+ readDurableAttemptInstant(input: {
81
+ paymentIntentId: string;
82
+ attemptIdempotencyKey: string;
83
+ }): Promise<string | null>;
84
+ applyFailedResult(input: {
85
+ idempotencyKey: string;
86
+ paymentIntentId: string;
87
+ occurredAt: string;
88
+ failureReason: string;
89
+ failureClassification: { failureClass: string; decidedBy: string };
90
+ }): Promise<{ replayed: boolean }>;
91
+ readCycleRetryState(cycleId: string): Promise<CycleRetryState>;
92
+ openDunningCase(input: {
93
+ idempotencyKey: string;
94
+ cycleId: string;
95
+ subscriptionId: string;
96
+ orderUuid: string;
97
+ paymentIntentId: string;
98
+ retryAttempt: number;
99
+ nextRetryAt: string | null;
100
+ failureReason: string;
101
+ occurredAt: string;
102
+ failureClass: string;
103
+ }): Promise<DunningCaseOpenOutcome>;
104
+ }
105
+ export interface FailurePropagationResult {
106
+ dunningCaseId: string | null;
107
+ retryAttempt: number | null;
108
+ applyReplayed: boolean;
109
+ dunningPropagationFailed: boolean;
110
+ }
111
+
112
+ export type RenewalPrepareInput<P extends string> =
113
+ Parameters<RenewalPreparedAttemptPersistencePort<P, string>["prepareProviderAttempt"]>[0];
114
+ export type RenewalAttemptResult<N extends string> =
115
+ Awaited<ReturnType<RenewalPreparedAttemptPersistencePort<string, N>["finalizeProviderAttempt"]>>;
116
+ export interface RenewalIntentResult { paymentIntentId: string; replayed: boolean }
117
+ export interface RenewalPaymentPort<CI, IR extends RenewalIntentResult, P extends string, N extends string>
118
+ extends RenewalPreparedAttemptPersistencePort<P, N> {
119
+ createIntent(input: CI): Promise<IR>;
120
+ }
121
+ export interface RenewalExecutionPort<I extends PaymentExecutionBaseInput, R extends PaymentExecutionBaseResult> {
122
+ execute(input: I): Promise<R>;
123
+ }
124
+ export interface RenewalDeliveryAdmission { allowed: boolean; state: string; reason: string }
125
+ export type RenewalAllowedAdmission = RenewalDeliveryAdmission & { allowed: true };
126
+ export interface RenewalDeliveryClient {
127
+ admitDeliveryAlignment(input: { subscriptionId: string; scheduledAt: string; asOf: string }): Promise<RenewalDeliveryAdmission>;
128
+ }
129
+ export interface RenewalFailureDeps {
130
+ chargeFailurePropagation: CycleChargeFailurePropagationPort;
131
+ now?: () => string;
132
+ }
133
+ export interface RenewalPreflightDeps<CI, IR extends RenewalIntentResult, M extends string | null | undefined>
134
+ extends RenewalFailureDeps {
135
+ persistence: SubscriptionRenewalPersistencePort<M>;
136
+ paymentPort: { createIntent(input: CI): Promise<IR> };
137
+ }
138
+ export interface RenewalCycleDeps<CI, IR extends RenewalIntentResult, P extends string, N extends string,
139
+ I extends PaymentExecutionBaseInput, R extends PaymentExecutionBaseResult, M extends string | null | undefined>
140
+ extends RenewalPreflightDeps<CI, IR, M> {
141
+ paymentPort: RenewalPaymentPort<CI, IR, P, N>;
142
+ executionPort: RenewalExecutionPort<I, R>;
143
+ deliveryAlignment: RenewalDeliveryClient;
144
+ capability: PaymentProviderCapabilityDescriptor;
145
+ deliveryAlignmentAdmission?: RenewalAllowedAdmission;
146
+ }
147
+ /** Local references, never a snapshot of the original due/ports or their getters. */
148
+ export interface RenewalExecutionContext<IR extends RenewalIntentResult, M extends string | null | undefined, F extends string> {
149
+ snapshots: SubscriptionCycleSnapshotResult;
150
+ cycleOrder: SubscriptionCycleOrderResult;
151
+ intent: IR;
152
+ totalGrossMinor: number;
153
+ executionIdempotencyKey: string;
154
+ attemptIdentity: ProviderAttemptIdentity;
155
+ providerFlow: F;
156
+ paymentMethodRecurringModel: M | undefined;
157
+ }
158
+ export interface RenewalOutcomeContext {
159
+ executionIdempotencyKey: string;
160
+ attemptIdentity: ProviderAttemptIdentity;
161
+ intent: RenewalIntentResult;
162
+ cycleOrder: Pick<SubscriptionCycleOrderResult, "cycleId" | "orderUuid" | "replayed">;
163
+ snapshots: Pick<SubscriptionCycleSnapshotResult, "cycleNumber">;
164
+ finalizedAttempt: { status: PaymentAttemptStatus };
165
+ }
166
+ export interface RenewalReplayContext {
167
+ cycleId: string; cycleNumber: number; orderId: string; paymentIntentId: string;
168
+ executionIdempotencyKey: string; providerIdempotencyKey: string;
169
+ providerRequestFingerprint: string; providerKind: string;
170
+ }
171
+
172
+ /** Durable preparation facts; provider admission belongs to the caller. */
173
+ export interface RenewalPreparedAttemptPrepareInput<P extends string = string> {
174
+ idempotencyKey: string;
175
+ paymentIntentId: string;
176
+ provider: P;
177
+ providerIdempotencyKey: string;
178
+ providerRequestFingerprint: string;
179
+ providerFlow: string;
180
+ paymentMethodRef?: string | null;
181
+ requestPayload: Record<string, unknown>;
182
+ }
183
+
184
+ export interface RenewalPreparedAttemptPersistenceResult<N extends string = string> {
185
+ paymentAttemptId: string;
186
+ status: PaymentAttemptStatus;
187
+ replayed: boolean;
188
+ providerAttemptId: string | null;
189
+ providerSessionId: string | null;
190
+ nextActionKind: N | null;
191
+ }
192
+
193
+ export interface RenewalPreparedAttemptFinalizeInput<N extends string = string> extends Pick<
194
+ PaymentExecutionBaseResult,
195
+ "providerAttemptId" | "providerSessionId" | "attemptStatus" | "requestPayload" | "responsePayload"
196
+ > {
197
+ idempotencyKey: string;
198
+ paymentIntentId: string;
199
+ paymentAttemptId: string;
200
+ providerIdempotencyKey: string;
201
+ providerRequestFingerprint: string;
202
+ nextActionKind: N | null;
203
+ }
204
+
205
+ /** Only persistence capabilities needed by interactive attempt ordering. */
206
+ export interface RenewalPreparedAttemptPersistencePort<P extends string = string, N extends string = string> {
207
+ prepareProviderAttempt(input: RenewalPreparedAttemptPrepareInput<P>): Promise<RenewalPreparedAttemptPersistenceResult<N>>;
208
+ finalizeProviderAttempt(input: RenewalPreparedAttemptFinalizeInput<N>): Promise<RenewalPreparedAttemptPersistenceResult<N>>;
209
+ }
210
+
211
+
212
+ /** Original text is an identity input; instant is for comparisons only. */
213
+ export interface RenewalSchedule {
214
+ subscriptionId: string;
215
+ scheduledAt: string;
216
+ scheduledInstant: number;
217
+ }
218
+ export interface RenewalIdentity {
219
+ cycleIdempotencyKey: string;
220
+ executionIdempotencyKey: string;
221
+ externalIdempotencyKey: string;
222
+ requestFingerprint: string;
223
+ }
224
+ export type RenewalAdmission =
225
+ | { allowed: true; state: string; reason: string }
226
+ | { allowed: false; state: string; reason: string; deferred: true; caseRef?: string }
227
+ | { allowed: false; state: string; reason: string; deferred: false };
228
+ export interface RenewalLine {
229
+ lineOrdinal: number; sku: string; quantity: number; unitAmountMinor: number;
230
+ }
231
+ /** Logical numbering is evidence-bound, never a universal nativeCounter + 1. */
232
+ export type RenewalCycleCorrelation =
233
+ | { status: "correlated"; logicalCycleNumber: number; nativeRenewalCounter: number | null; activationCycleId: string | null; evidenceRef: string }
234
+ | { status: "ambiguous"; logicalCycleNumber: null; nativeRenewalCounter: number | null; activationCycleId: string | null; reason: string };
235
+ export interface RenewalTemplateSnapshot {
236
+ templateId: string; version: number; cadenceDays: number; lines: readonly RenewalLine[];
237
+ }
238
+ export interface RenewalPricingSnapshot {
239
+ currency: string; totalAmountMinor: number; shippingAmountMinor: number;
240
+ agreementPolicy: "lock_until_edit"; proration: "none";
241
+ }
242
+ export interface RenewalOrderSnapshot {
243
+ shippingAddressRef: string | null; shippingQuoteRef: string | null;
244
+ acquisitionRef: string | null; lines: readonly RenewalLine[];
245
+ }
246
+ export interface RenewalFrozenSnapshot extends RenewalSchedule {
247
+ contractVersion: "platform.subscription.renewal.v1";
248
+ clientId: string; frozenRevision: string; frozenFingerprint: string;
249
+ cycle: RenewalCycleCorrelation; retryAttempt: number; externalAttemptSequence: number;
250
+ templateSnapshot: RenewalTemplateSnapshot;
251
+ pricingSnapshot: RenewalPricingSnapshot;
252
+ orderSnapshot: RenewalOrderSnapshot;
253
+ stockSourceKey: string; settlementChannelKey: string; authorizationRef: string;
254
+ payerAccountRef: string | null; unattendedChargeAuthorizedAt: string;
255
+ }
256
+ export interface RenewalDue extends RenewalSchedule {
257
+ clientId: string; cadenceDays: number; cycle: RenewalCycleCorrelation; currency: string;
258
+ stockSourceKey: string; settlementChannelKey: string; authorizationRef: string;
259
+ payerAccountRef: string | null; unattendedChargeAuthorizedAt: string;
260
+ deliveryAdmission: "allowed" | "blocked"; deliveryBlockReason: string | null;
261
+ }
262
+ export type RenewalOperationState = "claimed" | "reserved" | "attempt_prepared" | "succeeded" | "refused";
263
+ export interface RenewalReadback extends RenewalSchedule {
264
+ contractVersion: "platform.subscription.renewal.v1";
265
+ acquired: boolean; replayed: boolean; reason: string;
266
+ operationId: string; identityKey: string; operationFingerprint: string;
267
+ cycle: RenewalCycleCorrelation; state: RenewalOperationState;
268
+ claimToken: string; claimGeneration: number; leaseExpiresAt: string;
269
+ cycleSnapshot: RenewalFrozenSnapshot;
270
+ externalAttemptRef: string | null; externalAttemptAcknowledgedAt: string | null;
271
+ terminalOutcome: "succeeded" | "refused" | null; terminalReason: string | null; terminalAt: string | null;
272
+ reservationId: string | null; cycleId: string | null; orderId: string | null; settlementIntentId: string | null;
273
+ }
274
+ /** M07 verified truth. Nullable money is not positive matching evidence. */
275
+ export interface RenewalPaymentEvidence {
276
+ status: "succeeded" | "failed" | "pending" | "unknown";
277
+ disposition: "observed" | "ambiguous" | "late_positive";
278
+ operationId: string; eventKey: string; requestFingerprint: string;
279
+ verifiedEvidenceRef: string; externalAttemptRef: string | null;
280
+ occurredAt: string | null; observedAt: string; firstRecordedAt: string;
281
+ amountMinor: number | null; currency: string | null; reason: string | null;
282
+ }
283
+ /** Payment execution is outside all SQL transactions. */
284
+ export type RenewalTransactionUnits =
285
+ | { graph: "native"; prepare: "cycle_order_items_settlement_reservation_attempt"; outcome: "settlement_outcome_schedule_counter_reservation"; recovery: "settlement_cycle_receipt_case_counter" }
286
+ | { graph: "managed"; prepare: "create_cycle_order_then_prepare_attempt"; failure: "record_attempt_then_apply_result_then_open_case"; recovery: "authorize_method_then_schedule" };
287
+ export interface RenewalReservationResult {
288
+ reserved: boolean; replayed: boolean; reservationId: string | null;
289
+ status: "held" | "committed" | "consumed" | "released" | "expired" | "refused";
290
+ reason: string | null;
291
+ }
292
+ export interface RenewalPreparedSettlement {
293
+ contractVersion: "platform.subscription.renewal.v1"; replayed: boolean;
294
+ operationId: string; operationFingerprint: string; cycleId: string;
295
+ orderId: string; settlementIntentId: string; state: "attempt_prepared";
296
+ }
297
+ export interface RenewalStorePort {
298
+ readonly transactions: RenewalTransactionUnits;
299
+ listDue(limit: number, asOf: string): Promise<RenewalDue[]>;
300
+ buildCycleSnapshots(input: RenewalSchedule): Promise<RenewalFrozenSnapshot>;
301
+ readMandateEvidence(input: RenewalSchedule): Promise<{ authorized: boolean; authorizationRef: string; authorizedAt: string | null }>;
302
+ admitDeliveryAlignment(input: RenewalSchedule & { occurredAt: string }): Promise<RenewalAdmission>;
303
+ claimDue(input: { snapshot: RenewalFrozenSnapshot; workerId: string; leaseSeconds?: number; now: string }): Promise<RenewalReadback>;
304
+ readRenewal(identityKey: string): Promise<RenewalReadback | null>;
305
+ refuseBeforePayment(input: { operationId: string; claimToken: string; reason: string; occurredAt: string }): Promise<RenewalReadback>;
306
+ }
307
+ export interface RenewalSettlementPort {
308
+ prepareRenewal(input: { operationId: string; claimToken: string; now: string }): Promise<RenewalPreparedSettlement>;
309
+ acknowledgeExternalAttempt(input: { operationId: string; operationFingerprint: string; externalAttemptRef: string; acknowledgedAt: string }): Promise<RenewalReadback>;
310
+ recordTerminalOutcome(input: { operationId: string; eventKey: string; operationFingerprint: string; outcome: "succeeded" | "refused"; externalRef?: string | null; reason?: string | null; occurredAt: string }): Promise<RenewalReadback>;
311
+ }
312
+ export interface RenewalExecutionRequest extends RenewalIdentity {
313
+ paymentIntentId: string; amountMinor: number; currency: string; mode: string;
314
+ orderRef: string; orderId: string; flow: string; customerRef: string | null;
315
+ paymentMethodRef: string; recurringModel: string | null; clientId: string;
316
+ payer: { email: string | null; name: string | null } | null;
317
+ }
318
+ export interface RenewalPaymentRail {
319
+ execute(input: RenewalExecutionRequest): Promise<{ status: "succeeded" | "failed" | "requires_action" | "pending" | "unknown"; externalAttemptRef: string | null; failure?: { reason: string; failureClass: string } }>;
320
+ findExternalAttempt(input: { settlementIntentId: string }): Promise<string | null>;
321
+ readOutcome(input: { externalAttemptRef: string; settlementIntentId: string; orderId: string; subscriptionId: string; amountMinor: number; currency: string }): Promise<RenewalPaymentEvidence>;
322
+ }
323
+ export type RenewalResult =
324
+ | { outcome: "deferred"; subscriptionId: string; scheduledAt: string; reason: string; caseRef?: string }
325
+ | { outcome: "charged" | "requires_action" | "failed" | "skipped"; subscriptionId: string; cycleId: string | null; cycleNumber: number | null; orderId: string | null; paymentIntentId: string | null; attemptStatus: string | null; replayed: boolean; dunningCaseId?: string | null; retryAttempt?: number | null; applyReplayed?: boolean; dunningPropagationFailed?: boolean; reason?: string };
@@ -0,0 +1,34 @@
1
+ import type { RenewalFrozenSnapshot, RenewalReadback, RenewalSchedule } from "./renewal.js";
2
+ export interface StockWaitRecoveryBinding {
3
+ caseId: string | null; cycleId: string | null; orderId: string | null;
4
+ paymentId: string | null; engineIdempotencyKey: string | null;
5
+ }
6
+ export interface StockWaitDeficit {
7
+ skuId: string; sku: string; displayName: string; size: string | null;
8
+ required: number; reserved: number; available: number; missing: number;
9
+ }
10
+ export interface StockWaitCase extends RenewalSchedule {
11
+ caseId: string; clientId: string; state: "waiting" | "stock_secured" | "resumed" | "closed";
12
+ frozenSnapshot: RenewalFrozenSnapshot; frozenFingerprint: string;
13
+ binding: StockWaitRecoveryBinding; deficits: readonly StockWaitDeficit[];
14
+ fulfillmentRef: string | null; blockReason: string | null;
15
+ openedAt: string; checkedAt: string | null; selectedAt: string | null;
16
+ securedAt: string | null; resumedAt: string | null; closedAt: string | null;
17
+ startedNoticeKey: string; resumedNoticeKey: string; noticeEligible: boolean;
18
+ }
19
+ /** All-line reserve/reacquire and the wait transition share a DB transaction.
20
+ * A shortage rolls back the subtransaction, retaining previous commitments;
21
+ * record the wait outside that failed subtransaction. Technical failure is unknown.
22
+ */
23
+ export interface StockWaitStorePort {
24
+ listCandidates(input: { limit: number; asOf: string }): Promise<StockWaitCase[]>;
25
+ readFrozenCase(input: StockWaitRecoveryBinding): Promise<StockWaitCase | null>;
26
+ reserveAndTransition(input: { caseId: string; frozenFingerprint: string; now: string }): Promise<StockWaitCase>;
27
+ /** Only elapsed, unprepared, unacknowledged, stock-only refusals may reset.
28
+ * Requires unchanged schedule/fingerprint and no payment artifacts or outcomes.
29
+ * Rotate fencing generation atomically with reacquisition. Never reset ambiguity.
30
+ */
31
+ recoverRefusedSchedule(input: { operationId: string; caseId: string; claimGeneration: number; frozenFingerprint: string; scheduledAt: string; now: string }): Promise<
32
+ | { outcome: "recovered"; operation: RenewalReadback; replayed: boolean }
33
+ | { outcome: "refused"; reason: "not_stock_only" | "external_ownership" | "stale_snapshot" | "lease_active" | "inventory_unavailable" }>;
34
+ }
@@ -0,0 +1,24 @@
1
+ export { renewalIdentity } from "./renewalIdentity.js";
2
+ export const NATIVE_SUBSCRIPTION_CAPABILITIES = Object.freeze({
3
+ graph: "native",
4
+ multiCyclePrepaid: Object.freeze({ supported: false, refusalCode: "multi_cycle_prepaid_unsupported" }),
5
+ genericBundleCommands: Object.freeze({ supported: false, refusalCode: "native_generic_bundle_commands_unsupported" }),
6
+ commandBoundReceipts: Object.freeze({ supported: false, refusalCode: "native_command_bound_receipts_unsupported" }),
7
+ preparedAttemptStore: Object.freeze({ supported: false, refusalCode: "native_prepared_attempt_store_unsupported" }),
8
+ retryRecovery: "selected", stockRecovery: "unavailable",
9
+ deliveryAdmission: "required", executorCount: 1, defaultEnabled: false,
10
+ } as const satisfies import("./contracts/commands.js").SubscriptionCapabilities);
11
+ export { executeRenewalCycle, prepareRenewalAttempt, checkRenewalDeliveryAdmission } from "./renewalCycleMechanism.js";
12
+ export type { RenewalCyclePolicy } from "./renewalCycleMechanism.js";
13
+ export { recordRenewalPreflightBlock, replayRenewalAttempt, declinedRenewalOutcome, requiresActionRenewalOutcome } from "./renewalCycleRefusalMechanism.js";
14
+ export type { RenewalPreflightPolicy, RenewalReplayPolicy, RenewalOutcomePolicy } from "./renewalCycleRefusalMechanism.js";
15
+ export { propagateRenewalFailure } from "./renewalCycleFailureMechanism.js";
16
+ export type { RenewalFailurePolicy } from "./renewalCycleFailureMechanism.js";
17
+ export { runAutomaticSubscriptionRenewals, runOneAutomaticSubscriptionRenewal, correlatedLogicalCycle } from "./automaticRenewal.js";
18
+ export type { AutomaticRenewalRunStatus, AutomaticRenewalRunResult, AutomaticRenewalRunDeps, AutomaticRenewalPolicy } from "./automaticRenewal.js";
19
+ export { createPostgresRenewalStore } from "./postgresRenewalStore.js";
20
+ export type { NativeRenewalCodec, SubscriptionQueryExecutor } from "./postgresRenewalStore.js";
21
+ export { createPostgresRetryStore } from "./postgresRetryStore.js";
22
+ export type { NativeRetryFailure, NativeRetryRequest, NativeRetryPositive, NativeRetryPreparation, NativeRetryRow } from "./postgresRetryStore.js";
23
+ export { createPostgresRetryLifecycleBinding } from "./postgresRetryStore.js";
24
+ export type { NativeRetryLifecycleBinding } from "./postgresRetryStore.js";
@@ -0,0 +1,23 @@
1
+ import type { RenewalPaymentRail, RenewalReadback } from "./contracts/renewal.js";
2
+ import type { AutomaticRenewalRunDeps, AutomaticRenewalRunResult } from "./automaticRenewal.js";
3
+ import { automaticRenewalResult, handoffRefusal } from "./automaticRenewalExecution.js";
4
+ /** H4: only explicit verified matching M07 evidence may become positive local truth. */
5
+ export async function readAndApplyProviderOutcome(deps: AutomaticRenewalRunDeps, operation: RenewalReadback, prepared: { cycleId: string; orderId: string; settlementIntentId: string }, rail: RenewalPaymentRail, externalAttemptRef: string, occurredAt: string): Promise<AutomaticRenewalRunResult> {
6
+ const money = operation.cycleSnapshot.pricingSnapshot;
7
+ const observed = await rail.readOutcome({ externalAttemptRef, settlementIntentId: prepared.settlementIntentId, orderId: prepared.orderId, subscriptionId: operation.subscriptionId, amountMinor: money.totalAmountMinor, currency: money.currency });
8
+ if (observed.amountMinor !== null && observed.amountMinor !== money.totalAmountMinor) throw new Error("subscription_renewal_provider_amount_mismatch");
9
+ if (observed.currency !== null && observed.currency.toUpperCase() !== money.currency.toUpperCase()) throw new Error("subscription_renewal_provider_currency_mismatch");
10
+ if (observed.status === "pending" || observed.status === "unknown") return automaticRenewalResult(operation, "awaiting_provider", `provider_${observed.status}`);
11
+ if (observed.operationId !== operation.operationId || observed.requestFingerprint !== operation.operationFingerprint
12
+ || observed.externalAttemptRef !== externalAttemptRef || !observed.verifiedEvidenceRef || !observed.eventKey
13
+ || !observed.firstRecordedAt || !observed.observedAt) throw new Error("subscription_renewal_verified_evidence_required");
14
+ if (observed.disposition === "ambiguous") return automaticRenewalResult(operation, "awaiting_provider", "provider_ambiguous");
15
+ const terminalAt = observed.occurredAt ?? occurredAt;
16
+ if (observed.status === "failed") {
17
+ const reason = observed.reason ?? "provider_failed";
18
+ return handoffRefusal(deps, operation, prepared, externalAttemptRef, reason, deps.policy.classifyFailure(reason), terminalAt);
19
+ }
20
+ if (observed.amountMinor === null || observed.currency === null || observed.occurredAt === null) return automaticRenewalResult(operation, "awaiting_provider", "provider_positive_evidence_incomplete");
21
+ await deps.settlement.recordTerminalOutcome({ operationId: operation.operationId, eventKey: `${operation.identityKey}:provider-success:${externalAttemptRef}`, operationFingerprint: operation.operationFingerprint, outcome: "succeeded", externalRef: externalAttemptRef, occurredAt: terminalAt });
22
+ return automaticRenewalResult(operation, "succeeded", null);
23
+ }