@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,86 @@
1
+ import type { FulfillmentObservation } from "./contracts/index.js";
2
+ /** Ledger effects for an already accepted observation; acceptance and retry belong to the caller. */
3
+ interface FulfillmentObservationPort<TrackingStatus> {
4
+ markProviderStockConsumed(input: { idempotencyKey: string; fulfillmentOrderId: string }): Promise<{ replayed: boolean }>;
5
+ markHandedOver(input: { idempotencyKey: string; fulfillmentOrderId: string; suppressDispatched: boolean }): Promise<{ replayed: boolean }>;
6
+ recordTrackingReference(input: {
7
+ idempotencyKey: string;
8
+ orderId: string;
9
+ fulfillmentOrderId: string;
10
+ trackingNumber: string;
11
+ trackingUrl: string | null;
12
+ carrierKind: string | null;
13
+ service: string | null;
14
+ status: TrackingStatus;
15
+ observation: FulfillmentObservation;
16
+ }): Promise<{ replayed: boolean; readBack: boolean }>;
17
+ }
18
+
19
+ interface FulfillmentObservationTrackingReference {
20
+ trackingNumber: string;
21
+ trackingUrl?: string | null;
22
+ carrierKind?: string | null;
23
+ service?: string | null;
24
+ }
25
+
26
+
27
+ interface FulfillmentObservationInput<LocalStatus, TrackingStatus> {
28
+ port: FulfillmentObservationPort<TrackingStatus>;
29
+ localStatus: LocalStatus;
30
+ ids: { orderId: string; fulfillmentOrderId: string };
31
+ observation: FulfillmentObservation;
32
+ trackingRefs: readonly FulfillmentObservationTrackingReference[];
33
+ stockIdempotencyKey: string;
34
+ handoffIdempotencyKey: string;
35
+ trackingIdempotencyKey: (trackingNumber: string) => string;
36
+ issueAccountingInvoice?: () => Promise<void>;
37
+ onReplayed?: () => void;
38
+ onTrackingRecorded?: (result: { replayed: boolean; readBack: boolean }) => void;
39
+ }
40
+
41
+ /** Pure status decisions/projection; the mechanism owns every effect and await. */
42
+ interface FulfillmentObservationStatusPolicy<LocalStatus, TrackingStatus> {
43
+ consumesStock(status: LocalStatus): boolean;
44
+ handsOver(status: LocalStatus): boolean;
45
+ suppressDispatched(status: LocalStatus): boolean;
46
+ trackingStatus(status: LocalStatus): TrackingStatus;
47
+ }
48
+
49
+ /** Stock → durable handover → every tracking reference → optional invoice. No catches or retries. */
50
+ export async function applyFulfillmentObservationEffects<LocalStatus, TrackingStatus>(
51
+ input: FulfillmentObservationInput<LocalStatus, TrackingStatus>,
52
+ policy: FulfillmentObservationStatusPolicy<LocalStatus, TrackingStatus>,
53
+ ): Promise<void> {
54
+ const { fulfillmentOrderId, orderId } = input.ids;
55
+ if (policy.consumesStock(input.localStatus)) {
56
+ const consumed = await input.port.markProviderStockConsumed({
57
+ idempotencyKey: input.stockIdempotencyKey,
58
+ fulfillmentOrderId,
59
+ });
60
+ if (consumed.replayed) input.onReplayed?.();
61
+ }
62
+ if (!policy.handsOver(input.localStatus)) return;
63
+
64
+ const handoff = await input.port.markHandedOver({
65
+ idempotencyKey: input.handoffIdempotencyKey,
66
+ fulfillmentOrderId,
67
+ suppressDispatched: policy.suppressDispatched(input.localStatus),
68
+ });
69
+ if (handoff.replayed) input.onReplayed?.();
70
+
71
+ for (const trackingRef of input.trackingRefs) {
72
+ const tracking = await input.port.recordTrackingReference({
73
+ idempotencyKey: input.trackingIdempotencyKey(trackingRef.trackingNumber),
74
+ orderId,
75
+ fulfillmentOrderId,
76
+ trackingNumber: trackingRef.trackingNumber,
77
+ trackingUrl: trackingRef.trackingUrl ?? null,
78
+ carrierKind: trackingRef.carrierKind ?? null,
79
+ service: trackingRef.service ?? null,
80
+ status: policy.trackingStatus(input.localStatus),
81
+ observation: input.observation,
82
+ });
83
+ input.onTrackingRecorded?.(tracking);
84
+ }
85
+ await input.issueAccountingInvoice?.();
86
+ }
@@ -0,0 +1,255 @@
1
+ import { z } from 'zod';
2
+ import { PAYMENT_INTENT_STATUSES } from '@openlup/core/payment';
3
+ const OMS_FULFILLMENT_BLOCK_REASONS = ['order_not_paid','payment_not_succeeded','subscription_cycle_not_paid','active_hold','missing_shipping_address','inventory_review','risk_review'] as const;
4
+ const OMS_INVENTORY_STATUSES = ['not_checked','reserved','missing','released','expired','consumed','review_required'] as const;
5
+ export const COMMERCE_FULFILLMENT_CONTRACT_VERSION = "commerce.fulfillment.v0";
6
+
7
+ export const COMMERCE_FULFILLMENT_STATUSES = [
8
+ "created",
9
+ "packed",
10
+ "label_pending",
11
+ "label_created",
12
+ "handed_over",
13
+ "in_transit",
14
+ "delivered",
15
+ "exception",
16
+ "cancelled",
17
+ ] as const;
18
+
19
+ export const COMMERCE_FULFILLMENT_OPERATION_TYPES = [
20
+ "created",
21
+ "packed",
22
+ "provider_attempt_recorded",
23
+ "label_created",
24
+ "handed_over",
25
+ "tracking_event_recorded",
26
+ "cancelled",
27
+ ] as const;
28
+
29
+ export const COMMERCE_FULFILLMENT_PROVIDER_ATTEMPT_STATUSES = [
30
+ "recorded",
31
+ "succeeded",
32
+ "failed",
33
+ ] as const;
34
+
35
+ const uuidSchema = z.guid();
36
+ const datetimeSchema = z.string().datetime({ offset: true });
37
+
38
+ export const commerceFulfillmentStatusSchema = z.enum(COMMERCE_FULFILLMENT_STATUSES);
39
+ export const commerceFulfillmentOperationTypeSchema = z.enum(COMMERCE_FULFILLMENT_OPERATION_TYPES);
40
+ export const commerceFulfillmentProviderAttemptStatusSchema = z.enum(
41
+ COMMERCE_FULFILLMENT_PROVIDER_ATTEMPT_STATUSES,
42
+ );
43
+ export const commerceFulfillmentPaymentStatusSchema = z.enum(["not_started", ...PAYMENT_INTENT_STATUSES]);
44
+ export const commerceFulfillmentInventoryStatusSchema = z.enum(OMS_INVENTORY_STATUSES);
45
+ export const commerceFulfillmentBlockReasonSchema = z.enum(OMS_FULFILLMENT_BLOCK_REASONS);
46
+
47
+ export const commerceFulfillmentShippingAddressSnapshotSchema = z.object({
48
+ addressId: uuidSchema,
49
+ clientId: uuidSchema,
50
+ label: z.string().nullable(),
51
+ line1: z.string().trim().min(1),
52
+ line2: z.string().nullable(),
53
+ city: z.string().trim().min(1),
54
+ postalCode: z.string().trim().min(1),
55
+ country: z.string().trim().min(2).max(3),
56
+ }).strict();
57
+
58
+ export const commerceFulfillmentLineSchema = z.object({
59
+ id: uuidSchema.nullable(),
60
+ orderItemId: uuidSchema,
61
+ skuId: uuidSchema,
62
+ sku: z.string().trim().min(1),
63
+ title: z.string().trim().min(1).nullable(),
64
+ quantity: z.number().int().positive(),
65
+ inventoryReservationIds: z.array(uuidSchema),
66
+ productSnapshot: z.record(z.string(), z.unknown()),
67
+ }).strict();
68
+
69
+ export const commerceFulfillmentPaymentSummarySchema = z.object({
70
+ paymentIntentId: uuidSchema,
71
+ paymentStatus: commerceFulfillmentPaymentStatusSchema,
72
+ providerPaymentId: z.string().nullable(),
73
+ }).strict();
74
+
75
+ export const commerceFulfillmentInventorySummarySchema = z.object({
76
+ status: commerceFulfillmentInventoryStatusSchema,
77
+ reservationId: uuidSchema.nullable(),
78
+ reservationStatus: z.enum(["reserved", "released", "consumed", "expired"]).nullable(),
79
+ expiresAt: datetimeSchema.nullable(),
80
+ locationId: uuidSchema.nullable(),
81
+ locationCode: z.string().nullable(),
82
+ }).strict();
83
+
84
+ export const commerceFulfillmentOmsEligibilitySchema = z.object({
85
+ allowed: z.boolean(),
86
+ reason: commerceFulfillmentBlockReasonSchema.nullable(),
87
+ }).strict();
88
+
89
+ export const commerceFulfillmentOperationSchema = z.object({
90
+ id: uuidSchema,
91
+ type: commerceFulfillmentOperationTypeSchema,
92
+ occurredAt: datetimeSchema,
93
+ actorUserId: uuidSchema.nullable(),
94
+ payload: z.record(z.string(), z.unknown()),
95
+ }).strict();
96
+
97
+ export const commerceFulfillmentOrderSchema = z.object({
98
+ id: uuidSchema,
99
+ orderId: uuidSchema,
100
+ clientId: uuidSchema.nullable(),
101
+ status: commerceFulfillmentStatusSchema,
102
+ providerKind: z.string().trim().min(1).nullable(),
103
+ providerTrackingId: z.string().trim().min(1).nullable(),
104
+ shippingAddress: commerceFulfillmentShippingAddressSnapshotSchema,
105
+ lines: z.array(commerceFulfillmentLineSchema).min(1),
106
+ payment: commerceFulfillmentPaymentSummarySchema,
107
+ inventory: commerceFulfillmentInventorySummarySchema,
108
+ omsEligibility: commerceFulfillmentOmsEligibilitySchema,
109
+ latestOperation: commerceFulfillmentOperationSchema.nullable(),
110
+ createdAt: datetimeSchema,
111
+ updatedAt: datetimeSchema,
112
+ }).strict();
113
+
114
+ export const adminCommerceFulfillmentOrdersListRequestSchema = z.object({
115
+ page: z.coerce.number().int().min(1).default(1),
116
+ pageSize: z.coerce.number().int().min(1).max(100).default(25),
117
+ status: commerceFulfillmentStatusSchema.optional(),
118
+ }).strict();
119
+
120
+ export const adminCommerceFulfillmentOrderDetailRequestSchema = z
121
+ .object({
122
+ fulfillmentOrderId: uuidSchema.optional(),
123
+ orderId: uuidSchema.optional(),
124
+ })
125
+ .strict()
126
+ .refine((request) => request.fulfillmentOrderId || request.orderId, {
127
+ message: "fulfillmentOrderId or orderId is required",
128
+ });
129
+
130
+ export const adminCommerceFulfillmentCreateRequestSchema = z.object({
131
+ idempotencyKey: z.string().trim().min(8).max(120),
132
+ orderId: uuidSchema,
133
+ metadata: z.record(z.string(), z.unknown()).optional(),
134
+ }).strict();
135
+
136
+ export const adminCommerceFulfillmentRecordProviderAttemptRequestSchema = z.object({
137
+ idempotencyKey: z.string().trim().min(8).max(120),
138
+ fulfillmentOrderId: uuidSchema,
139
+ providerKind: z.string().trim().min(1),
140
+ status: commerceFulfillmentProviderAttemptStatusSchema,
141
+ requestPayload: z.record(z.string(), z.unknown()).default({}),
142
+ responsePayload: z.record(z.string(), z.unknown()).default({}),
143
+ error: z.string().trim().min(1).nullable().optional(),
144
+ metadata: z.record(z.string(), z.unknown()).optional(),
145
+ }).strict();
146
+
147
+ export const adminCommerceFulfillmentRecordLabelRequestSchema = z.object({
148
+ idempotencyKey: z.string().trim().min(8).max(120),
149
+ fulfillmentOrderId: uuidSchema,
150
+ providerKind: z.string().trim().min(1),
151
+ providerTrackingId: z.string().trim().min(1),
152
+ labelUrl: z.string().trim().min(1).nullable().optional(),
153
+ deliveryEstimateDays: z.number().int().nonnegative().nullable().optional(),
154
+ rawProviderPayload: z.record(z.string(), z.unknown()).default({}),
155
+ metadata: z.record(z.string(), z.unknown()).optional(),
156
+ }).strict();
157
+
158
+ export const adminCommerceFulfillmentHandOffRequestSchema = z.object({
159
+ idempotencyKey: z.string().trim().min(8).max(120),
160
+ fulfillmentOrderId: uuidSchema,
161
+ metadata: z.record(z.string(), z.unknown()).optional(),
162
+ }).strict();
163
+
164
+ export const adminCommerceFulfillmentTrackingEventRequestSchema = z.object({
165
+ idempotencyKey: z.string().trim().min(8).max(120),
166
+ fulfillmentOrderId: uuidSchema,
167
+ status: z.enum(["in_transit", "delivered", "exception"]),
168
+ providerTrackingId: z.string().trim().min(1).optional(),
169
+ rawEvent: z.record(z.string(), z.unknown()).default({}),
170
+ metadata: z.record(z.string(), z.unknown()).optional(),
171
+ }).strict();
172
+
173
+ export const adminCommerceFulfillmentCancelRequestSchema = z.object({
174
+ idempotencyKey: z.string().trim().min(8).max(120),
175
+ fulfillmentOrderId: uuidSchema,
176
+ reason: z.string().trim().min(1).max(240),
177
+ metadata: z.record(z.string(), z.unknown()).optional(),
178
+ }).strict();
179
+
180
+ export const adminCommerceFulfillmentOrdersListResponseSchema = z.object({
181
+ contractVersion: z.literal(COMMERCE_FULFILLMENT_CONTRACT_VERSION),
182
+ orders: z.array(commerceFulfillmentOrderSchema),
183
+ totalCount: z.number().int().min(0),
184
+ page: z.number().int().min(1),
185
+ pageSize: z.number().int().min(1),
186
+ }).strict();
187
+
188
+ export const adminCommerceFulfillmentOrderDetailResponseSchema = z.object({
189
+ contractVersion: z.literal(COMMERCE_FULFILLMENT_CONTRACT_VERSION),
190
+ order: commerceFulfillmentOrderSchema,
191
+ }).strict();
192
+
193
+ export const adminCommerceFulfillmentMutationResponseSchema = z.object({
194
+ contractVersion: z.literal(COMMERCE_FULFILLMENT_CONTRACT_VERSION),
195
+ fulfillmentOrderId: uuidSchema,
196
+ orderId: uuidSchema,
197
+ status: commerceFulfillmentStatusSchema,
198
+ replayed: z.boolean(),
199
+ }).strict();
200
+
201
+ export type AdminCommerceFulfillmentOrdersListRequest = z.infer<typeof adminCommerceFulfillmentOrdersListRequestSchema>;
202
+ export type AdminCommerceFulfillmentOrderDetailRequest = z.infer<typeof adminCommerceFulfillmentOrderDetailRequestSchema>;
203
+ export type AdminCommerceFulfillmentCreateRequest = z.infer<typeof adminCommerceFulfillmentCreateRequestSchema>;
204
+ export type AdminCommerceFulfillmentRecordProviderAttemptRequest = z.input<typeof adminCommerceFulfillmentRecordProviderAttemptRequestSchema>;
205
+ export type AdminCommerceFulfillmentRecordLabelRequest = z.input<typeof adminCommerceFulfillmentRecordLabelRequestSchema>;
206
+ export type AdminCommerceFulfillmentHandOffRequest = z.infer<typeof adminCommerceFulfillmentHandOffRequestSchema>;
207
+ export type AdminCommerceFulfillmentTrackingEventRequest = z.input<typeof adminCommerceFulfillmentTrackingEventRequestSchema>;
208
+ export type AdminCommerceFulfillmentCancelRequest = z.infer<typeof adminCommerceFulfillmentCancelRequestSchema>;
209
+ export type AdminCommerceFulfillmentOrdersListResponse = z.infer<typeof adminCommerceFulfillmentOrdersListResponseSchema>;
210
+ export type AdminCommerceFulfillmentOrderDetailResponse = z.infer<typeof adminCommerceFulfillmentOrderDetailResponseSchema>;
211
+ export type AdminCommerceFulfillmentMutationResponse = z.infer<typeof adminCommerceFulfillmentMutationResponseSchema>;
212
+ export type CommerceFulfillmentOrder = z.infer<typeof commerceFulfillmentOrderSchema>;
213
+ export type CommerceFulfillmentStatus = z.infer<typeof commerceFulfillmentStatusSchema>;
214
+ export type CommerceFulfillmentOperationType = z.infer<typeof commerceFulfillmentOperationTypeSchema>;
215
+
216
+ export const adminCommerceOrderRequestReplacementShipmentRequestSchema = z.object({
217
+ idempotencyKey: z.string().trim().min(8).max(120),
218
+ orderId: uuidSchema,
219
+ reason: z.enum(['damaged', 'lost', 'returned_undelivered', 'other']),
220
+ metadata: z.record(z.string(), z.unknown()).optional(),
221
+ }).strict();
222
+ export const omsOperationTypeSchema = z.enum([
223
+ "hold_created",
224
+ "hold_released",
225
+ "fulfillment_reviewed",
226
+ "support_note",
227
+ "shipping_address_updated",
228
+ "order_marked_refunded",
229
+ "order_cancelled_manual",
230
+ "replacement_shipment_requested",
231
+ ]);
232
+ export const omsOrderOperationSchema = z.object({
233
+ id: uuidSchema,
234
+ orderId: uuidSchema,
235
+ type: omsOperationTypeSchema,
236
+ holdId: uuidSchema.nullable(),
237
+ actorUserId: uuidSchema.nullable(),
238
+ occurredAt: datetimeSchema,
239
+ payload: z.record(z.string(), z.unknown()),
240
+ });
241
+
242
+ export const adminCommerceOrderRequestReplacementShipmentResponseSchema = z.object({
243
+ contractVersion: z.literal('commerce.v0'),
244
+ operation: omsOrderOperationSchema,
245
+ replacement: z.object({
246
+ fulfillmentOrderId: z.string().uuid(),
247
+ orderId: z.string().uuid(),
248
+ sequenceNo: z.number().int().min(1),
249
+ replacesFulfillmentOrderId: z.string().uuid(),
250
+ reason: z.enum(["damaged", "lost", "returned_undelivered", "other"]),
251
+ status: z.string(),
252
+ }),
253
+ releasedHoldIds: z.array(z.string().uuid()).default([]),
254
+ replayed: z.boolean(),
255
+ });
@@ -0,0 +1,25 @@
1
+ import type { FulfillmentNativeUnsupported } from './contracts/nativeUnsupported.js';
2
+ export interface FulfillmentHandlerRequest { method?: string; query: Record<string, string | string[] | undefined>; body?: unknown }
3
+ export interface FulfillmentHandlerResponder<Response> {
4
+ sendMethodNotAllowed(res: Response, methods: readonly string[]): void;
5
+ sendBffSuccess(res: Response, data: unknown): void;
6
+ sendBffError(res: Response, code: 'UNAUTHORIZED' | 'FORBIDDEN' | 'BAD_REQUEST' | 'INVALID_RESPONSE' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_UNAVAILABLE', message: string, options?: { details: unknown }): void;
7
+ }
8
+ export type AdminAuthResult = { ok: false; code: 'UNAUTHORIZED' | 'FORBIDDEN'; message: string } | { ok: true; userId: string };
9
+ export interface BaseDeps<Response> {
10
+ authorizeAdmin(req: FulfillmentHandlerRequest): Promise<AdminAuthResult>;
11
+ respond: FulfillmentHandlerResponder<Response>;
12
+ }
13
+ export function isNativeUnsupported(value: unknown): value is FulfillmentNativeUnsupported {
14
+ return typeof value === 'object' && value !== null && 'kind' in value && value.kind === 'native_unsupported';
15
+ }
16
+ export class CommerceFulfillmentConflictError extends Error {
17
+ constructor(message = 'Commerce fulfillment conflict', readonly details: Record<string, unknown> = {}) { super(message); this.name = 'CommerceFulfillmentConflictError'; }
18
+ }
19
+ export async function authorize<Response>(req: FulfillmentHandlerRequest, res: Response, authorizeAdmin: BaseDeps<Response>['authorizeAdmin'], respond: FulfillmentHandlerResponder<Response>) {
20
+ try {
21
+ const authorization = await authorizeAdmin(req);
22
+ if (authorization.ok === false) { respond.sendBffError(res, authorization.code, authorization.message); return null; }
23
+ return { userId: authorization.userId };
24
+ } catch { respond.sendBffError(res, 'UPSTREAM_UNAVAILABLE', 'Admin authorization failed'); return null; }
25
+ }
@@ -0,0 +1,39 @@
1
+ export { FULFILLMENT_STATUS_MAP, CANONICAL_LOCAL_FULFILLMENT_STATUSES, CUSTOMER_STEP_SEQUENCE, canonicalStatusToken, stepForToken, customerStepFromSignals, phaseIndexForStep, stepLabelKey, timelineLabelForStep, trackingPhaseKeyForStep, stepUsesTruckIcon } from "./statusMap.js";
2
+ export { parseFulfillmentFact, parseFulfillmentTransitionConflict } from "./normalizedFulfillmentFacts.js";
3
+ export { readFulfillmentCapabilities } from "./providerCapabilities.js";
4
+ export { createOutboxOrderPaidFulfillmentHandler } from "./paidHandoff.js";
5
+ export { collectPaidFulfillmentRecoveryCandidates } from "./paidFulfillmentRecovery.js";
6
+ export { runFulfillmentDispatchWorker } from "./providerDispatchWorker.js";
7
+ export { createFulfillmentCommandLedger } from "./providerCommandLedger.js";
8
+ export type { FulfillmentIngressCounts, FulfillmentIngressResult } from "./verifiedIngress.js";
9
+ export { verifyAndNormalizeFulfillmentObservation, fulfillmentIngressHttpResult } from "./verifiedIngress.js";
10
+ export { applyFulfillmentObservationEffects } from "./fulfillmentObservationEffects.js";
11
+ export { reconcileFulfillmentObservations, runFulfillmentReconciliationPage } from "./providerReconciliationWorker.js";
12
+ export { runFulfillmentStockSyncWorker } from "./providerStockSyncWorker.js";
13
+ export { runFulfillmentProductSyncWorker } from "./providerProductSyncWorker.js";
14
+ export { createFulfillmentLowStockEvidenceHandler } from "./lowStockEvidenceHandler.js";
15
+ export { createProductReconciliationEvidenceHandler } from "./productReconciliationEvidenceHandler.js";
16
+ export { runFulfillmentAdapterConformance } from "./adapterConformance.js";
17
+ export { createPostgresFulfillmentShipmentSpinePort } from './postgresShipmentSpine.js';
18
+ export { createPostgresFulfillmentLowStockEvidencePort } from './postgresEvidenceReadPort.js';
19
+ export { createPostgresFulfillmentStockSyncPort } from './postgresStockSyncPort.js';
20
+ export { createPostgresPaidFulfillmentRecoveryOpsPort } from './postgresPaidFulfillmentRecovery.js';
21
+ export { createPostgresFulfillmentDispatchStore } from './postgresDispatchStore.js';
22
+ export { createPostgresPaidFulfillmentHandoffStore } from './postgresPaidHandoffStore.js';
23
+ export { createPostgresDeliveryAlignmentPort } from './postgresDeliveryAlignment.js';
24
+ export { readDeliveryAlignment } from './deliveryAlignmentReadModel.js';
25
+ export { FULFILLMENT_NATIVE_CAPABILITIES } from './schemaCapabilities.js';
26
+ export { FULFILLMENT_MIGRATIONS } from './migrations.js';
27
+
28
+ export { createAdminCommerceFulfillmentOrdersListHandler, createAdminCommerceFulfillmentOrderDetailHandler, createAdminCommerceFulfillmentCreateOrderHandler, createAdminCommerceFulfillmentRecordProviderAttemptHandler, createAdminCommerceFulfillmentRecordLabelHandler, createAdminCommerceFulfillmentHandOffHandler, createAdminCommerceFulfillmentTrackingEventHandler, createAdminCommerceFulfillmentCancelOrderHandler } from './commerceFulfillmentHandlers.js';
29
+ export { createAdminCommerceOrderRequestReplacementShipmentHandler } from './replacementShipmentHandler.js';
30
+ export { CommerceFulfillmentConflictError } from './handlerSupport.js';
31
+ export { readFulfillmentDelay } from './delayReadModel.js';
32
+ export { readFulfillmentLabel, cleanupFulfillmentLabel } from './labelLifecycle.js';
33
+ export { currentParcel, currentParcelFor, currentParcelPerOrder, currentParcelFirst, belongsToParcel, rowsForParcel, parcelSequenceNo, compareParcelPrecedence } from './currentFulfillmentParcel.js';
34
+ export { FULFILLMENT_NATIVE_UNSUPPORTED, createNativeUnsupportedFulfillmentPorts } from './nativeUnsupported.js';
35
+ export { FULFILLMENT_REPLACEMENT_REFUSALS } from './replacementRefusals.js';
36
+ export { buildCommerceFulfillmentOrdersListResponse, buildCommerceFulfillmentOrderDetailResponse } from './commerceFulfillmentReadModel.js';
37
+ export { createFulfillmentShipmentsOverviewHandler } from './shipmentsOverviewHandler.js';
38
+ export { fulfillmentProgressPresentation } from './trackingProgress.js';
39
+ export { createFulfillmentServices } from './bindings.js';
@@ -0,0 +1,11 @@
1
+ import type { FulfillmentLabelPort, LabelDocument } from './contracts/label.js';
2
+ /** Storage-only read: a missing cached label never proves the parcel absent. Signed URLs are transient. */
3
+ export async function readFulfillmentLabel(port: Pick<FulfillmentLabelPort, 'read'>, document: LabelDocument | null) {
4
+ if (!document) return { kind: 'refused' as const, refusal: { reason: 'local_missing' as const, phase: 'readback' as const, retryable: false } };
5
+ return port.read(document);
6
+ }
7
+ /** Provider and storage deletion remain host-owned; canProceed never asserts either deletion. */
8
+ type LabelCleanupInput = Parameters<FulfillmentLabelPort['void']>[0] & { actorUserId: string; idempotencyKey: string };
9
+ export async function cleanupFulfillmentLabel(port: { void(input: LabelCleanupInput): ReturnType<FulfillmentLabelPort['void']> }, input: LabelCleanupInput) {
10
+ return port.void(input);
11
+ }
@@ -0,0 +1,60 @@
1
+ /** Read-only evidence boundary; parsing remains with the owning host schema. */
2
+ export function createFulfillmentLowStockEvidenceHandler<Request extends { method?: string; query: Record<string, string | string[] | undefined> }, Response, Query, Result>({ evidencePort, authorizeAdmin, requestSchema, responseSchema, respond }: {
3
+ evidencePort: { getLowStockEvidence(query: Query): Promise<unknown> };
4
+ authorizeAdmin(request: Request): Promise<boolean>;
5
+ requestSchema: { safeParse(input: unknown): { success: true; data: Query } | { success: false; error: { flatten(): unknown } } };
6
+ responseSchema: { safeParse(input: unknown): { success: true; data: Result } | { success: false } };
7
+ respond: {
8
+ sendBffError(res: Response, code: "UPSTREAM_UNAVAILABLE" | "UNAUTHORIZED" | "BAD_REQUEST" | "INVALID_RESPONSE", message: string, options?: { details: unknown }): void;
9
+ sendBffSuccess(res: Response, data: Result): void;
10
+ sendMethodNotAllowed(res: Response, methods: readonly string[]): void;
11
+ };
12
+ }) {
13
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = respond;
14
+ return async function handler(req: Request, res: Response): Promise<void> {
15
+ if (req.method !== "GET") {
16
+ sendMethodNotAllowed(res, ["GET"]);
17
+ return;
18
+ }
19
+
20
+ let authorized = false;
21
+ try {
22
+ authorized = await authorizeAdmin(req);
23
+ } catch {
24
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
25
+ return;
26
+ }
27
+
28
+ if (!authorized) {
29
+ sendBffError(res, "UNAUTHORIZED", "Admin session required");
30
+ return;
31
+ }
32
+
33
+ const request = requestSchema.safeParse({
34
+ statusFilter: scalarQuery(req.query.statusFilter),
35
+ });
36
+ if (!request.success) {
37
+ sendBffError(res, "BAD_REQUEST", "Invalid low-stock evidence request", {
38
+ details: request.error.flatten(),
39
+ });
40
+ return;
41
+ }
42
+
43
+ try {
44
+ const result = await evidencePort.getLowStockEvidence(request.data);
45
+ const response = responseSchema.safeParse(result);
46
+ if (!response.success) {
47
+ sendBffError(res, "INVALID_RESPONSE", "Low-stock evidence returned invalid response");
48
+ return;
49
+ }
50
+
51
+ sendBffSuccess(res, response.data);
52
+ } catch {
53
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Low-stock evidence read failed");
54
+ }
55
+ };
56
+ }
57
+
58
+ function scalarQuery(value: string | string[] | undefined): string | undefined {
59
+ return Array.isArray(value) ? value[0] : value;
60
+ }
@@ -0,0 +1,9 @@
1
+ /** Byte-identical sparse projections; install the complete canonical catalogue. */
2
+ export const FULFILLMENT_MIGRATIONS = [
3
+ { position: 5, catalogPath: "db/platform/migrations/20260808210000_fulfillment_shipment_rail.sql", sha256: "eb5824551903717f3868690909fcc2cb6a7f845abe5cb19bd4cff13103b1383d", url: new URL("../../sql/20260808210000_fulfillment_shipment_rail.sql", import.meta.url) },
4
+ { position: 27, catalogPath: "db/platform/migrations/20260814000000_fulfillment_stock_evidence.sql", sha256: "df20c2491cb0196b4e28d19e7578c95bfa5c87b8f89fb0b6284c0e0695a3cc1d", url: new URL("../../sql/20260814000000_fulfillment_stock_evidence.sql", import.meta.url) },
5
+ { position: 51, catalogPath: "db/platform/migrations/20260819150000_fulfillment_shipment_sequence.sql", sha256: "906706cf66a6a555aa1219b12eb674d937eaa099abde6da18766310af43aaa9f", url: new URL("../../sql/20260819150000_fulfillment_shipment_sequence.sql", import.meta.url) },
6
+ { position: 73, catalogPath: "db/platform/migrations/20261022080000_fulfillment_command_ledger.sql", sha256: "c6806906cdb0b6931b29f7a5c7c800016e98d04e6ea956f3c1e219a6cdb7e9d1", url: new URL("../../sql/20261022080000_fulfillment_command_ledger.sql", import.meta.url) },
7
+ { position: 74, catalogPath: "db/platform/migrations/20261022090000_fulfillment_delivery_alignment.sql", sha256: "d9966ae79fe77a0feae8317dc5b46ef2d8196dd0790b1cd0ee1b06ab58061499", url: new URL("../../sql/20261022090000_fulfillment_delivery_alignment.sql", import.meta.url) },
8
+ { position: 76, catalogPath: "db/platform/migrations/20261022165000_fulfillment_stock_evidence_replay_reopen.sql", sha256: "6d132dec2820047f4e337bcf752b3d884600ccc12de03887fe6d6aa5575bc1db", url: new URL("../../sql/20261022165000_fulfillment_stock_evidence_replay_reopen.sql", import.meta.url) },
9
+ ] as const;
@@ -0,0 +1,23 @@
1
+ import type { FulfillmentCommerceReadPort, FulfillmentShipmentsOverviewPort } from './contracts/read.js';
2
+ import type { FulfillmentNativeUnsupported } from './contracts/nativeUnsupported.js';
3
+ import type { FulfillmentReplacementPort } from './contracts/replacement.js';
4
+ import type { FulfillmentOperatorMutationPort } from './contracts/operator.js';
5
+ import type { FulfillmentCustomerAlignmentRowsReader } from './contracts/customerAlignment.js';
6
+ export const FULFILLMENT_NATIVE_UNSUPPORTED: FulfillmentNativeUnsupported = Object.freeze({ kind: 'native_unsupported', reason: 'host_binding_required', retryable: false });
7
+ /** Deliberately has no executor: unsupported native parity can never make a partial write. */
8
+ export function createNativeUnsupportedFulfillmentPorts(): FulfillmentReplacementPort & FulfillmentOperatorMutationPort & FulfillmentCommerceReadPort & FulfillmentShipmentsOverviewPort & { readCustomerAlignmentRows: FulfillmentCustomerAlignmentRowsReader } {
9
+ const unsupported = async () => FULFILLMENT_NATIVE_UNSUPPORTED;
10
+ return {
11
+ requestReplacementShipment: unsupported,
12
+ createCommerceFulfillmentOrder: unsupported,
13
+ recordCommerceFulfillmentProviderAttempt: unsupported,
14
+ recordCommerceFulfillmentLabel: unsupported,
15
+ handOffCommerceFulfillmentOrder: unsupported,
16
+ recordCommerceFulfillmentTrackingEvent: unsupported,
17
+ cancelCommerceFulfillmentOrder: unsupported,
18
+ readCustomerAlignmentRows: unsupported,
19
+ listCommerceFulfillmentOrders: unsupported,
20
+ getCommerceFulfillmentOrderDetail: unsupported,
21
+ getShipmentsOverview: unsupported,
22
+ };
23
+ }
@@ -0,0 +1,2 @@
1
+ export { isCanonicalLocalFulfillmentStatus, parseFulfillmentFact, parseFulfillmentTransitionConflict } from "@openlup/core/fulfillment";
2
+ export type { CanonicalNormalizedFulfillmentFact as NormalizedFulfillmentFact, CanonicalNormalizedFulfillmentFactV1 as NormalizedFulfillmentFactV1, CanonicalNormalizedFulfillmentFactV0 as NormalizedFulfillmentFactV0, CanonicalCompatibleNormalizedFulfillmentFact as CompatibleNormalizedFulfillmentFact, CanonicalFulfillmentTransitionSnapshot as FulfillmentTransitionSnapshot, CanonicalTransitionConflict as TransitionConflict } from "@openlup/core/fulfillment";
@@ -0,0 +1,92 @@
1
+ import { z } from "zod";
2
+ import type { CanonicalLocalFulfillmentStatus, FulfillmentObservation, FulfillmentObservationCodecs } from "./contracts/index.js";
3
+ import { parseFulfillmentFact } from "./normalizedFulfillmentFacts.js";
4
+ import { admittedReference, durableStrings, reference } from "./dispatchProjection.js";
5
+ import { FULFILLMENT_STATUS_MAP } from "./statusMap.js";
6
+
7
+ type Privacy = ReturnType<typeof durableStrings>;
8
+ const timestamp = z.iso.datetime({ offset: true });
9
+ export function projectCorrelation(value: FulfillmentObservation["correlation"], privacy = durableStrings(), codecs: FulfillmentObservationCodecs = {}): FulfillmentObservation["correlation"] {
10
+ const token = (value: string | null) => privacy.token(value, text => admittedReference(text, codecs.approveReference));
11
+ return { orderRef: value.orderRef, fulfillmentRef: value.fulfillmentRef, externalRef: token(value.externalRef),
12
+ orderNumber: token(value.orderNumber), providerFulfillmentRef: token(value.providerFulfillmentRef), parcelRef: value.parcelRef };
13
+ }
14
+
15
+ export function projectProviderStatus(value: NonNullable<FulfillmentObservation["providerStatus"]>, privacy = durableStrings()): NonNullable<FulfillmentObservation["providerStatus"]> {
16
+ return { primary: privacy.token(value.primary), subStatus: privacy.token(value.subStatus) };
17
+ }
18
+ export type PollEvidence = Pick<FulfillmentObservation, "correlation" | "eventKey" | "providerStatus" | "diagnostics">;
19
+ function pollEvidence(value: PollEvidence, privacy: Privacy, codecs: FulfillmentObservationCodecs = {}): PollEvidence {
20
+ // Host identities and keys composed from admitted parts are already authoritative.
21
+ return { eventKey: value.eventKey, correlation: projectCorrelation(value.correlation, privacy, codecs),
22
+ ...(value.providerStatus ? { providerStatus: projectProviderStatus(value.providerStatus, privacy) } : {}) };
23
+ }
24
+ /** Unknown-status quarantine/ACK evidence has the same privacy boundary as observations. */
25
+ export function projectPollEvidence(value: PollEvidence): PollEvidence {
26
+ const privacy = durableStrings(value.diagnostics?.redactedFieldCount);
27
+ const safe = pollEvidence(value, privacy);
28
+ return { ...safe, ...(privacy.redactedFieldCount ? { diagnostics: { redactedFieldCount: privacy.redactedFieldCount } } : {}) };
29
+ }
30
+
31
+ /** One URL policy: strict default, optional adopter codec, and approval at every sink. */
32
+ export function publicTrackingUrl(value: string, approve?: (url: string) => boolean, codec?: (url: string) => string | null): string | null {
33
+ try {
34
+ const candidate = codec ? codec(value) : value;
35
+ if (!candidate) return null;
36
+ const url = new URL(candidate);
37
+ return url.protocol === "https:" && !url.username && !url.password && (codec || (!url.search && !url.hash)) && approve?.(url.href) ? url.href : null;
38
+ } catch { return null; }
39
+ }
40
+ export function projectTrackingReference(value: FulfillmentObservation["trackingRefs"][number], privacy = durableStrings(), approveTrackingUrl?: (url: string) => boolean, codecs: FulfillmentObservationCodecs = {}) {
41
+ const token = (value: string | null) => privacy.token(value, text => admittedReference(text, codecs.approveReference));
42
+ const trackingNumber = token(value.trackingNumber);
43
+ if (trackingNumber === null) return null;
44
+ const serviceRef = token(value.serviceRef);
45
+ // Even an opaque-looking derived carrier is unsafe when its service was refused.
46
+ const carrierRef = privacy.token(value.carrierRef, text => value.serviceRef !== null && serviceRef === null ? null : reference(text));
47
+ return { trackingNumber, carrierRef, serviceRef,
48
+ publicUrl: privacy.token(value.publicUrl, url => publicTrackingUrl(url, approveTrackingUrl, codecs.trackingUrl)) };
49
+ }
50
+
51
+ /** New boundary projection: arbitrary adapter properties are never durable. */
52
+ export function projectObservation(value: FulfillmentObservation, approveTrackingUrl?: (url: string) => boolean, codecs: FulfillmentObservationCodecs = {}): FulfillmentObservation {
53
+ const privacy = durableStrings(value.diagnostics?.redactedFieldCount), token = privacy.token;
54
+ const diagnostics: NonNullable<FulfillmentObservation["diagnostics"]> = {};
55
+ if (value.diagnostics?.admission) {
56
+ const count = (value: number) => Number.isSafeInteger(value) && value > 0 ? value : 0;
57
+ diagnostics.admission = { rejectedTrackingRefs: count(value.diagnostics.admission.rejectedTrackingRefs), rejectedTrackingUrls: count(value.diagnostics.admission.rejectedTrackingUrls) };
58
+ }
59
+ if (value.diagnostics?.refusal) {
60
+ const { reason, phase, retryable } = value.diagnostics.refusal;
61
+ diagnostics.refusal = { reason: token(reason) as typeof reason, phase: token(phase) as typeof phase, retryable };
62
+ }
63
+ if (value.diagnostics?.stockContention) diagnostics.stockContention = { sqlState: "55P03", phase: "provider_stock_consumed", retryable: true };
64
+ if (value.diagnostics?.annotation) diagnostics.annotation = token(value.diagnostics.annotation) as typeof value.diagnostics.annotation;
65
+ if (value.diagnostics?.recoveredPickedReceipt) {
66
+ const { receiptRef, closed } = value.diagnostics.recoveredPickedReceipt;
67
+ diagnostics.recoveredPickedReceipt = { receiptRef, closed };
68
+ }
69
+ const fact = parseFulfillmentFact(value.fact);
70
+ const safe = {
71
+ ...pollEvidence(value, privacy, codecs),
72
+ fact: { ...fact, mapperVersion: token(fact.mapperVersion) ?? "redacted" }, eventKind: (token(value.eventKind) ?? "redacted") as FulfillmentObservation["eventKind"],
73
+ firstReceivedAt: timestamp.parse(value.firstReceivedAt),
74
+ trackingRefs: value.trackingRefs.flatMap(ref => {
75
+ const projected = projectTrackingReference(ref, privacy, approveTrackingUrl, codecs);
76
+ return projected ? [projected] : [];
77
+ }),
78
+ };
79
+ if (privacy.redactedFieldCount) diagnostics.redactedFieldCount = privacy.redactedFieldCount;
80
+ return { ...safe, ...(Object.keys(diagnostics).length ? { diagnostics } : {}) };
81
+ }
82
+
83
+ const HANDOVER_STATUSES: ReadonlySet<string> = new Set(
84
+ FULFILLMENT_STATUS_MAP.stages.filter(stage => stage.effects.handover).flatMap(stage => stage.localStatus),
85
+ );
86
+ /** Donor finished-picking boundary is narrower than the coarse packing stage. */
87
+ export const observationStatusPolicy = {
88
+ consumesStock: (status: CanonicalLocalFulfillmentStatus) => ["packed", "in_transit", "delivered"].includes(status),
89
+ handsOver: (status: CanonicalLocalFulfillmentStatus) => HANDOVER_STATUSES.has(status),
90
+ suppressDispatched: (status: CanonicalLocalFulfillmentStatus) => status === "delivered",
91
+ trackingStatus: (status: CanonicalLocalFulfillmentStatus) => status as "in_transit" | "delivered",
92
+ };
@@ -0,0 +1,18 @@
1
+ import type { FulfillmentCapabilityDescriptor } from "../contracts/index.js";
2
+
3
+ /** Offline protocol capabilities; selection and account readiness belong to the host. */
4
+ export const omnipackFulfillmentCapabilities: Readonly<FulfillmentCapabilityDescriptor> = Object.freeze<FulfillmentCapabilityDescriptor>({
5
+ providerRef: "omnipack", role: "warehouse", defaultOff: true, selected: false,
6
+ readiness: Object.freeze({ ready: false, refusal: Object.freeze({ reason: "not_ready", phase: "selection", retryable: false }) }),
7
+ operations: Object.freeze({
8
+ dispatch: Object.freeze({ supported: true }), readback: Object.freeze({ supported: true }),
9
+ exactAbsence: Object.freeze({ supported: false, reason: "not_exposed" }), verifyObservation: Object.freeze({ supported: true }),
10
+ syncStock: Object.freeze({ supported: true }), syncProducts: Object.freeze({ supported: true }),
11
+ labelCreate: Object.freeze({ supported: false, reason: "not_exposed" }), labelRead: Object.freeze({ supported: false, reason: "host_only" }),
12
+ labelVoid: Object.freeze({ supported: false, reason: "not_exposed" }), cancel: Object.freeze({ supported: false, reason: "not_exposed" }),
13
+ pickupPoint: Object.freeze({ supported: true }), courierPickup: Object.freeze({ supported: false, reason: "not_exposed" }),
14
+ }),
15
+ authentication: "token", freshness: "not_available", ownsDeliveredNotification: false,
16
+ stockAuthority: "external_stock_master_with_local_reservations", labelReadOwner: "host_storage",
17
+ actionPolicy: Object.freeze({ providerSpecificAdminActions: false, genericAdminActions: true, mutationSurface: "provider_adapter" }),
18
+ });