@openlup/platform 0.20.0 → 0.22.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 (458) hide show
  1. package/CHANGELOG.md +272 -278
  2. package/README.md +195 -6
  3. package/dist/composition/composeBundle.d.ts +13 -0
  4. package/dist/composition/composeBundle.js +4 -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 +2 -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 +93 -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 +105 -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 +52 -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 +38 -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 +86 -0
  46. package/dist/fulfillment/contracts/store.js +1 -0
  47. package/dist/fulfillment/contracts/sync.d.ts +170 -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 +7 -0
  56. package/dist/fulfillment/dispatchProjection.js +18 -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 +37 -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 +27 -0
  70. package/dist/fulfillment/migrations.js +8 -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 +11 -0
  76. package/dist/fulfillment/observationProjection.js +37 -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 +37 -0
  80. package/dist/fulfillment/omnipack/client.js +162 -0
  81. package/dist/fulfillment/omnipack/index.d.ts +52 -0
  82. package/dist/fulfillment/omnipack/index.js +349 -0
  83. package/dist/fulfillment/omnipack/mappers.d.ts +73 -0
  84. package/dist/fulfillment/omnipack/mappers.js +177 -0
  85. package/dist/fulfillment/omnipack/observation.d.ts +21 -0
  86. package/dist/fulfillment/omnipack/observation.js +57 -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/trackingReferences.d.ts +16 -0
  106. package/dist/fulfillment/omnipack/trackingReferences.js +52 -0
  107. package/dist/fulfillment/paidFulfillmentRecovery.d.ts +67 -0
  108. package/dist/fulfillment/paidFulfillmentRecovery.js +154 -0
  109. package/dist/fulfillment/paidHandoff.d.ts +27 -0
  110. package/dist/fulfillment/paidHandoff.js +86 -0
  111. package/dist/fulfillment/paidHandoffPorts.d.ts +24 -0
  112. package/dist/fulfillment/paidHandoffPorts.js +3 -0
  113. package/dist/fulfillment/postgresDeliveryAlignment.d.ts +4 -0
  114. package/dist/fulfillment/postgresDeliveryAlignment.js +12 -0
  115. package/dist/fulfillment/postgresDispatchStore.d.ts +16 -0
  116. package/dist/fulfillment/postgresDispatchStore.js +44 -0
  117. package/dist/fulfillment/postgresEvidenceReadPort.d.ts +25 -0
  118. package/dist/fulfillment/postgresEvidenceReadPort.js +45 -0
  119. package/dist/fulfillment/postgresPaidFulfillmentRecovery.d.ts +25 -0
  120. package/dist/fulfillment/postgresPaidFulfillmentRecovery.js +26 -0
  121. package/dist/fulfillment/postgresPaidHandoffStore.d.ts +4 -0
  122. package/dist/fulfillment/postgresPaidHandoffStore.js +10 -0
  123. package/dist/fulfillment/postgresShipmentSpine.d.ts +63 -0
  124. package/dist/fulfillment/postgresShipmentSpine.js +232 -0
  125. package/dist/fulfillment/postgresStockSyncPort.d.ts +8 -0
  126. package/dist/fulfillment/postgresStockSyncPort.js +63 -0
  127. package/dist/fulfillment/productReconciliationEvidenceHandler.d.ts +36 -0
  128. package/dist/fulfillment/productReconciliationEvidenceHandler.js +50 -0
  129. package/dist/fulfillment/providerCapabilities.d.ts +9 -0
  130. package/dist/fulfillment/providerCapabilities.js +17 -0
  131. package/dist/fulfillment/providerCommandLedger.d.ts +143 -0
  132. package/dist/fulfillment/providerCommandLedger.js +14 -0
  133. package/dist/fulfillment/providerDispatchAcceptance.d.ts +26 -0
  134. package/dist/fulfillment/providerDispatchAcceptance.js +23 -0
  135. package/dist/fulfillment/providerDispatchError.d.ts +6 -0
  136. package/dist/fulfillment/providerDispatchError.js +5 -0
  137. package/dist/fulfillment/providerDispatchWorker.d.ts +31 -0
  138. package/dist/fulfillment/providerDispatchWorker.js +172 -0
  139. package/dist/fulfillment/providerProductSyncWorker.d.ts +28 -0
  140. package/dist/fulfillment/providerProductSyncWorker.js +122 -0
  141. package/dist/fulfillment/providerReconciliationError.d.ts +9 -0
  142. package/dist/fulfillment/providerReconciliationError.js +17 -0
  143. package/dist/fulfillment/providerReconciliationTerminalSkip.d.ts +2 -0
  144. package/dist/fulfillment/providerReconciliationTerminalSkip.js +6 -0
  145. package/dist/fulfillment/providerReconciliationTimestamp.d.ts +1 -0
  146. package/dist/fulfillment/providerReconciliationTimestamp.js +27 -0
  147. package/dist/fulfillment/providerReconciliationWorker.d.ts +82 -0
  148. package/dist/fulfillment/providerReconciliationWorker.js +151 -0
  149. package/dist/fulfillment/providerStockAvailability.d.ts +13 -0
  150. package/dist/fulfillment/providerStockAvailability.js +51 -0
  151. package/dist/fulfillment/providerStockSyncWorker.d.ts +37 -0
  152. package/dist/fulfillment/providerStockSyncWorker.js +186 -0
  153. package/dist/fulfillment/providerTrackingReferences.d.ts +15 -0
  154. package/dist/fulfillment/providerTrackingReferences.js +23 -0
  155. package/dist/fulfillment/replacementRefusals.d.ts +5 -0
  156. package/dist/fulfillment/replacementRefusals.js +38 -0
  157. package/dist/fulfillment/replacementShipmentHandler.d.ts +5 -0
  158. package/dist/fulfillment/replacementShipmentHandler.js +46 -0
  159. package/dist/fulfillment/schemaCapabilities.d.ts +13 -0
  160. package/dist/fulfillment/schemaCapabilities.js +7 -0
  161. package/dist/fulfillment/shipmentsOverviewHandler.d.ts +16 -0
  162. package/dist/fulfillment/shipmentsOverviewHandler.js +54 -0
  163. package/dist/fulfillment/statusMap.d.ts +2 -0
  164. package/dist/fulfillment/statusMap.js +1 -0
  165. package/dist/fulfillment/trackingProgress.d.ts +1 -0
  166. package/dist/fulfillment/trackingProgress.js +1 -0
  167. package/dist/fulfillment/verifiedIngress.d.ts +52 -0
  168. package/dist/fulfillment/verifiedIngress.js +66 -0
  169. package/dist/payments/adapterConformance.d.ts +8 -0
  170. package/dist/payments/adapterConformance.js +109 -0
  171. package/dist/payments/bindings.d.ts +3 -0
  172. package/dist/payments/bindings.js +17 -0
  173. package/dist/payments/checkoutActivePaymentActionResolver.d.ts +7 -0
  174. package/dist/payments/checkoutActivePaymentActionResolver.js +77 -0
  175. package/dist/payments/checkoutRecoveryPaymentResolver.d.ts +8 -0
  176. package/dist/payments/checkoutRecoveryPaymentResolver.js +87 -0
  177. package/dist/payments/contracts/adapter.d.ts +113 -0
  178. package/dist/payments/contracts/adapter.js +1 -0
  179. package/dist/payments/contracts/conformance.d.ts +155 -0
  180. package/dist/payments/contracts/conformance.js +1 -0
  181. package/dist/payments/contracts/evidence.d.ts +157 -0
  182. package/dist/payments/contracts/evidence.js +1 -0
  183. package/dist/payments/contracts/index.d.ts +10 -0
  184. package/dist/payments/contracts/index.js +1 -0
  185. package/dist/payments/contracts/preparedAttempt.d.ts +69 -0
  186. package/dist/payments/contracts/preparedAttempt.js +1 -0
  187. package/dist/payments/contracts/reconciliation.d.ts +303 -0
  188. package/dist/payments/contracts/reconciliation.js +1 -0
  189. package/dist/payments/contracts/repair.d.ts +92 -0
  190. package/dist/payments/contracts/repair.js +1 -0
  191. package/dist/payments/contracts/truth.d.ts +92 -0
  192. package/dist/payments/contracts/truth.js +1 -0
  193. package/dist/payments/eventConformanceRunner.d.ts +12 -0
  194. package/dist/payments/eventConformanceRunner.js +125 -0
  195. package/dist/payments/index.d.ts +16 -0
  196. package/dist/payments/index.js +16 -0
  197. package/dist/payments/paymentAttemptSilenceWindow.d.ts +10 -0
  198. package/dist/payments/paymentAttemptSilenceWindow.js +19 -0
  199. package/dist/payments/paymentMethodLifecycle.d.ts +11 -0
  200. package/dist/payments/paymentMethodLifecycle.js +37 -0
  201. package/dist/payments/paymentProviderReconciliationAbsenceReopen.d.ts +7 -0
  202. package/dist/payments/paymentProviderReconciliationAbsenceReopen.js +100 -0
  203. package/dist/payments/paymentProviderReconciliationEvidence.d.ts +36 -0
  204. package/dist/payments/paymentProviderReconciliationEvidence.js +91 -0
  205. package/dist/payments/paymentProviderReconciliationWorker.d.ts +4 -0
  206. package/dist/payments/paymentProviderReconciliationWorker.js +245 -0
  207. package/dist/payments/paymentTruth.d.ts +8 -0
  208. package/dist/payments/paymentTruth.js +125 -0
  209. package/dist/payments/paymentVerifyNowHandler.d.ts +27 -0
  210. package/dist/payments/paymentVerifyNowHandler.js +70 -0
  211. package/dist/payments/paymentVerifyNowService.d.ts +23 -0
  212. package/dist/payments/paymentVerifyNowService.js +93 -0
  213. package/dist/payments/paymentWebhookPostProcessing.d.ts +22 -0
  214. package/dist/payments/paymentWebhookPostProcessing.js +15 -0
  215. package/dist/payments/postgresPaymentTruth.d.ts +10 -0
  216. package/dist/payments/postgresPaymentTruth.js +126 -0
  217. package/dist/payments/preparedAttempt.d.ts +6 -0
  218. package/dist/payments/preparedAttempt.js +70 -0
  219. package/dist/payments/preparedAttemptFinalization.d.ts +5 -0
  220. package/dist/payments/preparedAttemptFinalization.js +12 -0
  221. package/dist/payments/schemaCapabilities.d.ts +10 -0
  222. package/dist/payments/schemaCapabilities.js +10 -0
  223. package/dist/payments/sql.d.ts +20 -0
  224. package/dist/payments/sql.js +6 -0
  225. package/dist/payments/stripe/classifyDecline.d.ts +46 -0
  226. package/dist/payments/stripe/classifyDecline.js +58 -0
  227. package/dist/payments/stripe/declineFailureHints.d.ts +46 -0
  228. package/dist/payments/stripe/declineFailureHints.js +87 -0
  229. package/dist/payments/stripe/donorContracts.d.ts +120 -0
  230. package/dist/payments/stripe/donorContracts.js +1 -0
  231. package/dist/payments/stripe/executionDecline.d.ts +53 -0
  232. package/dist/payments/stripe/executionDecline.js +107 -0
  233. package/dist/payments/stripe/index.d.ts +41 -0
  234. package/dist/payments/stripe/index.js +264 -0
  235. package/dist/payments/stripe/intentMapper.d.ts +26 -0
  236. package/dist/payments/stripe/intentMapper.js +51 -0
  237. package/dist/payments/stripe/methodFacts.d.ts +10 -0
  238. package/dist/payments/stripe/methodFacts.js +12 -0
  239. package/dist/payments/stripe/paymentFailureEvidence.d.ts +26 -0
  240. package/dist/payments/stripe/paymentFailureEvidence.js +33 -0
  241. package/dist/payments/stripe/stripeFailureEvidence.d.ts +41 -0
  242. package/dist/payments/stripe/stripeFailureEvidence.js +82 -0
  243. package/dist/payments/stripe/stripeIntentConfirmationEvidence.d.ts +28 -0
  244. package/dist/payments/stripe/stripeIntentConfirmationEvidence.js +32 -0
  245. package/dist/payments/stripe/stripePaymentReconciliationProvider.d.ts +35 -0
  246. package/dist/payments/stripe/stripePaymentReconciliationProvider.js +222 -0
  247. package/dist/payments/stripe/stripeRefundEvidence.d.ts +8 -0
  248. package/dist/payments/stripe/stripeRefundEvidence.js +17 -0
  249. package/dist/payments/stripe/stripeSandboxPaymentExecutionAdapter.d.ts +45 -0
  250. package/dist/payments/stripe/stripeSandboxPaymentExecutionAdapter.js +220 -0
  251. package/dist/payments/stripe/stripeWebhookNormalizer.d.ts +14 -0
  252. package/dist/payments/stripe/stripeWebhookNormalizer.js +243 -0
  253. package/dist/payments/stripe/webhookSignature.d.ts +14 -0
  254. package/dist/payments/stripe/webhookSignature.js +43 -0
  255. package/dist/payments/tpay/blikRecurringMapper.d.ts +37 -0
  256. package/dist/payments/tpay/blikRecurringMapper.js +24 -0
  257. package/dist/payments/tpay/capabilities.d.ts +3 -0
  258. package/dist/payments/tpay/capabilities.js +18 -0
  259. package/dist/payments/tpay/classifyDecline.d.ts +46 -0
  260. package/dist/payments/tpay/classifyDecline.js +58 -0
  261. package/dist/payments/tpay/declineFailureHints.d.ts +72 -0
  262. package/dist/payments/tpay/declineFailureHints.js +37 -0
  263. package/dist/payments/tpay/donorContracts.d.ts +140 -0
  264. package/dist/payments/tpay/donorContracts.js +1 -0
  265. package/dist/payments/tpay/index.d.ts +73 -0
  266. package/dist/payments/tpay/index.js +294 -0
  267. package/dist/payments/tpay/paymentFailureEvidence.d.ts +26 -0
  268. package/dist/payments/tpay/paymentFailureEvidence.js +33 -0
  269. package/dist/payments/tpay/preparedProviderAttempt.d.ts +22 -0
  270. package/dist/payments/tpay/preparedProviderAttempt.js +24 -0
  271. package/dist/payments/tpay/providerAttemptFailureDiagnostic.d.ts +3 -0
  272. package/dist/payments/tpay/providerAttemptFailureDiagnostic.js +95 -0
  273. package/dist/payments/tpay/tpayCreateTransactionInput.d.ts +25 -0
  274. package/dist/payments/tpay/tpayCreateTransactionInput.js +128 -0
  275. package/dist/payments/tpay/tpayDeclineEvidence.d.ts +126 -0
  276. package/dist/payments/tpay/tpayDeclineEvidence.js +177 -0
  277. package/dist/payments/tpay/tpayDispatchFailure.d.ts +60 -0
  278. package/dist/payments/tpay/tpayDispatchFailure.js +121 -0
  279. package/dist/payments/tpay/tpayFailureEvidence.d.ts +20 -0
  280. package/dist/payments/tpay/tpayFailureEvidence.js +61 -0
  281. package/dist/payments/tpay/tpayHttpClient.d.ts +52 -0
  282. package/dist/payments/tpay/tpayHttpClient.js +185 -0
  283. package/dist/payments/tpay/tpayHttpFailure.d.ts +57 -0
  284. package/dist/payments/tpay/tpayHttpFailure.js +143 -0
  285. package/dist/payments/tpay/tpayPaymentExecutionAdapter.d.ts +20 -0
  286. package/dist/payments/tpay/tpayPaymentExecutionAdapter.js +249 -0
  287. package/dist/payments/tpay/tpayPaymentReconciliationProvider.d.ts +5 -0
  288. package/dist/payments/tpay/tpayPaymentReconciliationProvider.js +244 -0
  289. package/dist/payments/tpay/tpayTransactionPayload.d.ts +120 -0
  290. package/dist/payments/tpay/tpayTransactionPayload.js +96 -0
  291. package/dist/payments/tpay/wallClockTime.d.ts +31 -0
  292. package/dist/payments/tpay/wallClockTime.js +57 -0
  293. package/dist/payments/tpay/webhookJwsVerifier.d.ts +17 -0
  294. package/dist/payments/tpay/webhookJwsVerifier.js +51 -0
  295. package/dist/payments/tpay/webhookNormalizer.d.ts +54 -0
  296. package/dist/payments/tpay/webhookNormalizer.js +200 -0
  297. package/dist/payments/verifiedEvidenceProjection.d.ts +12 -0
  298. package/dist/payments/verifiedEvidenceProjection.js +166 -0
  299. package/dist/payments/verifiedIngress.d.ts +119 -0
  300. package/dist/payments/verifiedIngress.js +220 -0
  301. package/package.json +37 -2
  302. package/release-gates.json +105 -3
  303. package/sql/20260808210000_fulfillment_shipment_rail.sql +810 -0
  304. package/sql/20260810180000_payment_settlement_rail.sql +574 -0
  305. package/sql/20260815150000_payment_truth_reconciliation.sql +479 -0
  306. package/sql/20260819150000_fulfillment_shipment_sequence.sql +98 -0
  307. package/sql/20260826100000_late_payment_shifts_the_subscription_cycle.sql +215 -0
  308. package/sql/20261022080000_fulfillment_command_ledger.sql +786 -0
  309. package/sql/20261022090000_fulfillment_delivery_alignment.sql +1106 -0
  310. package/src/composition/composeBundle.ts +13 -0
  311. package/src/composition/fulfillmentContribution.ts +28 -0
  312. package/src/composition/index.ts +2 -0
  313. package/src/fulfillment/adapterConformance.ts +195 -0
  314. package/src/fulfillment/bindings.ts +26 -0
  315. package/src/fulfillment/commerceFulfillmentHandlers.ts +263 -0
  316. package/src/fulfillment/commerceFulfillmentReadModel.ts +257 -0
  317. package/src/fulfillment/contracts/alignment.ts +13 -0
  318. package/src/fulfillment/contracts/bindings.ts +30 -0
  319. package/src/fulfillment/contracts/capabilities.ts +15 -0
  320. package/src/fulfillment/contracts/conformance.ts +61 -0
  321. package/src/fulfillment/contracts/customerAlignment.ts +4 -0
  322. package/src/fulfillment/contracts/dispatch.ts +58 -0
  323. package/src/fulfillment/contracts/index.ts +18 -0
  324. package/src/fulfillment/contracts/label.ts +9 -0
  325. package/src/fulfillment/contracts/nativeUnsupported.ts +6 -0
  326. package/src/fulfillment/contracts/observation.ts +18 -0
  327. package/src/fulfillment/contracts/operator.ts +31 -0
  328. package/src/fulfillment/contracts/read.ts +42 -0
  329. package/src/fulfillment/contracts/readback.ts +11 -0
  330. package/src/fulfillment/contracts/replacement.ts +15 -0
  331. package/src/fulfillment/contracts/store.ts +26 -0
  332. package/src/fulfillment/contracts/sync.ts +61 -0
  333. package/src/fulfillment/currentFulfillmentParcel.ts +2 -0
  334. package/src/fulfillment/delayReadModel.ts +60 -0
  335. package/src/fulfillment/deliveryAlignmentReadModel.ts +15 -0
  336. package/src/fulfillment/dispatchProjection.ts +20 -0
  337. package/src/fulfillment/fulfillmentObservationEffects.ts +86 -0
  338. package/src/fulfillment/handlerSchemas.ts +255 -0
  339. package/src/fulfillment/handlerSupport.ts +25 -0
  340. package/src/fulfillment/index.ts +38 -0
  341. package/src/fulfillment/labelLifecycle.ts +11 -0
  342. package/src/fulfillment/lowStockEvidenceHandler.ts +60 -0
  343. package/src/fulfillment/migrations.ts +8 -0
  344. package/src/fulfillment/nativeUnsupported.ts +23 -0
  345. package/src/fulfillment/normalizedFulfillmentFacts.ts +2 -0
  346. package/src/fulfillment/observationProjection.ts +41 -0
  347. package/src/fulfillment/omnipack/capabilities.ts +18 -0
  348. package/src/fulfillment/omnipack/client.ts +258 -0
  349. package/src/fulfillment/omnipack/index.ts +312 -0
  350. package/src/fulfillment/omnipack/mappers.ts +261 -0
  351. package/src/fulfillment/omnipack/observation.ts +61 -0
  352. package/src/fulfillment/omnipack/omnipackDispatchPayload.ts +93 -0
  353. package/src/fulfillment/omnipack/omnipackOutboundOrderPayload.ts +194 -0
  354. package/src/fulfillment/omnipack/omnipackProductPackPayload.ts +47 -0
  355. package/src/fulfillment/omnipack/omnipackProductPayload.ts +67 -0
  356. package/src/fulfillment/omnipack/omnipackReconciliationPayload.ts +7 -0
  357. package/src/fulfillment/omnipack/omnipackStatusVocabulary.ts +103 -0
  358. package/src/fulfillment/omnipack/omnipackWebhookPayload.ts +73 -0
  359. package/src/fulfillment/omnipack/productSyncMappers.ts +74 -0
  360. package/src/fulfillment/omnipack/providerError.ts +64 -0
  361. package/src/fulfillment/omnipack/trackingReferences.ts +77 -0
  362. package/src/fulfillment/paidFulfillmentRecovery.ts +252 -0
  363. package/src/fulfillment/paidHandoff.ts +122 -0
  364. package/src/fulfillment/paidHandoffPorts.ts +21 -0
  365. package/src/fulfillment/postgresDeliveryAlignment.ts +14 -0
  366. package/src/fulfillment/postgresDispatchStore.ts +55 -0
  367. package/src/fulfillment/postgresEvidenceReadPort.ts +59 -0
  368. package/src/fulfillment/postgresPaidFulfillmentRecovery.ts +29 -0
  369. package/src/fulfillment/postgresPaidHandoffStore.ts +11 -0
  370. package/src/fulfillment/postgresShipmentSpine.ts +321 -0
  371. package/src/fulfillment/postgresStockSyncPort.ts +65 -0
  372. package/src/fulfillment/productReconciliationEvidenceHandler.ts +64 -0
  373. package/src/fulfillment/providerCapabilities.ts +18 -0
  374. package/src/fulfillment/providerCommandLedger.ts +53 -0
  375. package/src/fulfillment/providerDispatchAcceptance.ts +31 -0
  376. package/src/fulfillment/providerDispatchError.ts +7 -0
  377. package/src/fulfillment/providerDispatchWorker.ts +131 -0
  378. package/src/fulfillment/providerProductSyncWorker.ts +154 -0
  379. package/src/fulfillment/providerReconciliationError.ts +20 -0
  380. package/src/fulfillment/providerReconciliationTerminalSkip.ts +8 -0
  381. package/src/fulfillment/providerReconciliationTimestamp.ts +34 -0
  382. package/src/fulfillment/providerReconciliationWorker.ts +150 -0
  383. package/src/fulfillment/providerStockAvailability.ts +60 -0
  384. package/src/fulfillment/providerStockSyncWorker.ts +222 -0
  385. package/src/fulfillment/providerTrackingReferences.ts +31 -0
  386. package/src/fulfillment/replacementRefusals.ts +43 -0
  387. package/src/fulfillment/replacementShipmentHandler.ts +47 -0
  388. package/src/fulfillment/schemaCapabilities.ts +7 -0
  389. package/src/fulfillment/shipmentsOverviewHandler.ts +73 -0
  390. package/src/fulfillment/statusMap.ts +2 -0
  391. package/src/fulfillment/trackingProgress.ts +1 -0
  392. package/src/fulfillment/verifiedIngress.ts +80 -0
  393. package/src/payments/adapterConformance.ts +118 -0
  394. package/src/payments/bindings.ts +17 -0
  395. package/src/payments/checkoutActivePaymentActionResolver.ts +97 -0
  396. package/src/payments/checkoutRecoveryPaymentResolver.ts +108 -0
  397. package/src/payments/contracts/adapter.ts +96 -0
  398. package/src/payments/contracts/conformance.ts +171 -0
  399. package/src/payments/contracts/evidence.ts +142 -0
  400. package/src/payments/contracts/index.ts +54 -0
  401. package/src/payments/contracts/preparedAttempt.ts +86 -0
  402. package/src/payments/contracts/reconciliation.ts +296 -0
  403. package/src/payments/contracts/repair.ts +95 -0
  404. package/src/payments/contracts/truth.ts +108 -0
  405. package/src/payments/eventConformanceRunner.ts +149 -0
  406. package/src/payments/index.ts +21 -0
  407. package/src/payments/paymentAttemptSilenceWindow.ts +26 -0
  408. package/src/payments/paymentMethodLifecycle.ts +50 -0
  409. package/src/payments/paymentProviderReconciliationAbsenceReopen.ts +130 -0
  410. package/src/payments/paymentProviderReconciliationEvidence.ts +117 -0
  411. package/src/payments/paymentProviderReconciliationWorker.ts +291 -0
  412. package/src/payments/paymentTruth.ts +131 -0
  413. package/src/payments/paymentVerifyNowHandler.ts +94 -0
  414. package/src/payments/paymentVerifyNowService.ts +125 -0
  415. package/src/payments/paymentWebhookPostProcessing.ts +36 -0
  416. package/src/payments/postgresPaymentTruth.ts +149 -0
  417. package/src/payments/preparedAttempt.ts +90 -0
  418. package/src/payments/preparedAttemptFinalization.ts +13 -0
  419. package/src/payments/schemaCapabilities.ts +10 -0
  420. package/src/payments/sql.ts +6 -0
  421. package/src/payments/stripe/classifyDecline.ts +62 -0
  422. package/src/payments/stripe/declineFailureHints.ts +89 -0
  423. package/src/payments/stripe/donorContracts.ts +87 -0
  424. package/src/payments/stripe/executionDecline.ts +126 -0
  425. package/src/payments/stripe/index.ts +265 -0
  426. package/src/payments/stripe/intentMapper.ts +58 -0
  427. package/src/payments/stripe/methodFacts.ts +21 -0
  428. package/src/payments/stripe/paymentFailureEvidence.ts +58 -0
  429. package/src/payments/stripe/stripeFailureEvidence.ts +115 -0
  430. package/src/payments/stripe/stripeIntentConfirmationEvidence.ts +34 -0
  431. package/src/payments/stripe/stripePaymentReconciliationProvider.ts +263 -0
  432. package/src/payments/stripe/stripeRefundEvidence.ts +19 -0
  433. package/src/payments/stripe/stripeSandboxPaymentExecutionAdapter.ts +283 -0
  434. package/src/payments/stripe/stripeWebhookNormalizer.ts +277 -0
  435. package/src/payments/stripe/webhookSignature.ts +55 -0
  436. package/src/payments/tpay/blikRecurringMapper.ts +42 -0
  437. package/src/payments/tpay/capabilities.ts +21 -0
  438. package/src/payments/tpay/classifyDecline.ts +62 -0
  439. package/src/payments/tpay/declineFailureHints.ts +90 -0
  440. package/src/payments/tpay/donorContracts.ts +104 -0
  441. package/src/payments/tpay/index.ts +293 -0
  442. package/src/payments/tpay/paymentFailureEvidence.ts +58 -0
  443. package/src/payments/tpay/preparedProviderAttempt.ts +38 -0
  444. package/src/payments/tpay/providerAttemptFailureDiagnostic.ts +99 -0
  445. package/src/payments/tpay/tpayCreateTransactionInput.ts +158 -0
  446. package/src/payments/tpay/tpayDeclineEvidence.ts +233 -0
  447. package/src/payments/tpay/tpayDispatchFailure.ts +186 -0
  448. package/src/payments/tpay/tpayFailureEvidence.ts +75 -0
  449. package/src/payments/tpay/tpayHttpClient.ts +290 -0
  450. package/src/payments/tpay/tpayHttpFailure.ts +186 -0
  451. package/src/payments/tpay/tpayPaymentExecutionAdapter.ts +297 -0
  452. package/src/payments/tpay/tpayPaymentReconciliationProvider.ts +285 -0
  453. package/src/payments/tpay/tpayTransactionPayload.ts +207 -0
  454. package/src/payments/tpay/wallClockTime.ts +57 -0
  455. package/src/payments/tpay/webhookJwsVerifier.ts +82 -0
  456. package/src/payments/tpay/webhookNormalizer.ts +279 -0
  457. package/src/payments/verifiedEvidenceProjection.ts +179 -0
  458. package/src/payments/verifiedIngress.ts +317 -0
@@ -0,0 +1,252 @@
1
+ const COMMERCE_ORDER_PAID_EVENT_TYPE = "commerce.order.paid";
2
+
3
+ type PaidFulfillmentRecoveryAction =
4
+ | "wait_for_outbox" | "requeue_discarded_order_paid_outbox" | "inspect_processed_without_effect"
5
+ | "inspect_missing_order_paid_outbox"
6
+ | "monitor_dispatch" | "inspect_missing_dispatch_ref" | "inspect_stale_dispatch_submission"
7
+ | "inspect_uncertain_dispatch" | "inspect_failed_dispatch"
8
+ | "inspect_created_dispatch_without_provider_order_id" | "inspect_terminal_dispatch"
9
+ | "inspect_unknown_dispatch_status";
10
+
11
+ type PaidFulfillmentRecoveryReason =
12
+ | "paid_order_without_fulfillment_order" | "order_paid_outbox_missing" | "order_paid_outbox_discarded"
13
+ | "order_paid_outbox_processed_without_fulfillment" | "provider_dispatch_in_progress"
14
+ | "provider_fulfillment_without_dispatch_ref" | "provider_dispatch_submission_stale"
15
+ | "provider_dispatch_outcome_uncertain" | "provider_dispatch_failed"
16
+ | "provider_dispatch_created_without_provider_order_id"
17
+ | "provider_dispatch_terminal_without_progress" | "provider_dispatch_status_unknown";
18
+
19
+ type PaidFulfillmentRecoveryPosture =
20
+ | "automatic_local_requeue_safe" | "wait_for_existing_automation" | "operator_review_required";
21
+
22
+ type PaidFulfillmentOrderRow = Record<string, unknown> & {
23
+ id: string; status?: string | null; mode?: string | null;
24
+ created_at?: string | null; updated_at?: string | null;
25
+ };
26
+
27
+ type PaidFulfillmentOrderEvidenceRow = Record<string, unknown> & {
28
+ id: string; order_id: string; provider_kind?: string | null; status?: string | null;
29
+ created_at?: string | null; updated_at?: string | null;
30
+ };
31
+
32
+ type PaidFulfillmentDispatchRefRow = Record<string, unknown> & {
33
+ id?: string | null; fulfillment_order_id: string; provider_order_id?: string | null;
34
+ status?: string | null; created_at?: string | null; updated_at?: string | null;
35
+ };
36
+
37
+ type PaidFulfillmentOutboxEventRow = Record<string, unknown> & {
38
+ id: string; event_type: string; status: string; aggregate_id?: string | null;
39
+ created_at?: string | null; updated_at?: string | null; available_at?: string | null;
40
+ attempts?: number | null;
41
+ };
42
+
43
+ type PaidFulfillmentRecoveryCandidate = {
44
+ orderId: string; orderStatus: string; orderMode: string | null;
45
+ fulfillmentOrderId: string | null; providerKind: string | null;
46
+ dispatchRefId: string | null; dispatchStatus: string | null;
47
+ outboxEventId: string | null; outboxStatus: string | null; outboxAttempts: number | null;
48
+ reason: PaidFulfillmentRecoveryReason;
49
+ recommendedAction: PaidFulfillmentRecoveryAction;
50
+ recoveryPosture: PaidFulfillmentRecoveryPosture;
51
+ ageSeconds: number;
52
+ };
53
+
54
+ type PaidFulfillmentRecoveryPlan = { checkedOrders: number; candidates: PaidFulfillmentRecoveryCandidate[] };
55
+
56
+ export function collectPaidFulfillmentRecoveryCandidates(input: {
57
+ providerRef: string;
58
+ orders: PaidFulfillmentOrderRow[];
59
+ fulfillmentOrders: PaidFulfillmentOrderEvidenceRow[];
60
+ dispatchRefs?: PaidFulfillmentDispatchRefRow[];
61
+ orderPaidOutboxEvents: PaidFulfillmentOutboxEventRow[];
62
+ now: Date;
63
+ minimumAgeSeconds?: number;
64
+ dispatchSubmissionStaleSeconds?: number;
65
+ }): PaidFulfillmentRecoveryPlan {
66
+ const minimumAgeSeconds = input.minimumAgeSeconds ?? 30 * 60;
67
+ const minimumAgeMs = minimumAgeSeconds * 1000;
68
+ const dispatchSubmissionStaleSeconds = input.dispatchSubmissionStaleSeconds ?? 5 * 60;
69
+ const fulfillmentByOrder = groupBy(input.fulfillmentOrders, (row) => row.order_id);
70
+ const dispatchRefsByFulfillment = groupBy(
71
+ input.dispatchRefs ?? [],
72
+ (row) => row.fulfillment_order_id,
73
+ );
74
+ const outboxByOrder = latestBy(
75
+ input.orderPaidOutboxEvents.filter((row) => row.event_type === COMMERCE_ORDER_PAID_EVENT_TYPE),
76
+ (row) => row.aggregate_id ?? "",
77
+ );
78
+ const candidates: PaidFulfillmentRecoveryCandidate[] = [];
79
+ let checkedOrders = 0;
80
+
81
+ for (const order of input.orders) {
82
+ const status = clean(order.status);
83
+ if (!["paid", "fulfillment_pending"].includes(status)) continue;
84
+ const ageSeconds = secondsSince(order.updated_at ?? order.created_at ?? "", input.now);
85
+ if (ageSeconds * 1000 < minimumAgeMs) continue;
86
+ checkedOrders += 1;
87
+
88
+ const outbox = outboxByOrder.get(order.id);
89
+ const fulfillments = fulfillmentByOrder.get(order.id) ?? [];
90
+ if (fulfillments.length === 0) {
91
+ candidates.push(candidateForMissingFulfillment(order, outbox, ageSeconds));
92
+ continue;
93
+ }
94
+
95
+ for (const fulfillment of fulfillments) {
96
+ const providerKind = clean(fulfillment.provider_kind);
97
+ if (providerKind !== input.providerRef) continue;
98
+ const latestDispatchRef = latestBy(
99
+ dispatchRefsByFulfillment.get(fulfillment.id) ?? [],
100
+ (row) => row.fulfillment_order_id,
101
+ ).get(fulfillment.id);
102
+ const decision = dispatchRecoveryDecision({
103
+ dispatchRef: latestDispatchRef,
104
+ fulfillmentCreatedAt: fulfillment.updated_at ?? fulfillment.created_at,
105
+ now: input.now,
106
+ missingRefGraceSeconds: minimumAgeSeconds,
107
+ staleSeconds: dispatchSubmissionStaleSeconds,
108
+ });
109
+ if (!decision) continue;
110
+ candidates.push({
111
+ ...baseCandidate(order, outbox, ageSeconds),
112
+ fulfillmentOrderId: fulfillment.id,
113
+ providerKind: input.providerRef,
114
+ dispatchRefId: latestDispatchRef?.id ?? null,
115
+ dispatchStatus: clean(latestDispatchRef?.status) || null,
116
+ ...decision,
117
+ });
118
+ }
119
+ }
120
+
121
+ candidates.sort((a, b) => b.ageSeconds - a.ageSeconds || a.orderId.localeCompare(b.orderId));
122
+ return { checkedOrders, candidates };
123
+ }
124
+
125
+ function candidateForMissingFulfillment(
126
+ order: PaidFulfillmentOrderRow,
127
+ outbox: PaidFulfillmentOutboxEventRow | undefined,
128
+ ageSeconds: number,
129
+ ): PaidFulfillmentRecoveryCandidate {
130
+ const outboxStatus = clean(outbox?.status);
131
+ const decision = !outbox
132
+ ? recoveryDecision("order_paid_outbox_missing", "inspect_missing_order_paid_outbox")
133
+ : outboxStatus === "discarded"
134
+ ? recoveryDecision("order_paid_outbox_discarded", "requeue_discarded_order_paid_outbox", "automatic_local_requeue_safe")
135
+ : outboxStatus === "processed"
136
+ ? recoveryDecision("order_paid_outbox_processed_without_fulfillment", "inspect_processed_without_effect")
137
+ : recoveryDecision("paid_order_without_fulfillment_order", "wait_for_outbox", "wait_for_existing_automation");
138
+ return { ...baseCandidate(order, outbox, ageSeconds), ...decision };
139
+ }
140
+
141
+ function baseCandidate(
142
+ order: PaidFulfillmentOrderRow,
143
+ outbox: PaidFulfillmentOutboxEventRow | undefined,
144
+ ageSeconds: number,
145
+ ): Omit<PaidFulfillmentRecoveryCandidate, "reason" | "recommendedAction" | "recoveryPosture"> {
146
+ return {
147
+ orderId: order.id,
148
+ orderStatus: clean(order.status),
149
+ orderMode: clean(order.mode) || null,
150
+ fulfillmentOrderId: null,
151
+ providerKind: null,
152
+ dispatchRefId: null,
153
+ dispatchStatus: null,
154
+ outboxEventId: outbox?.id ?? null,
155
+ outboxStatus: clean(outbox?.status) || null,
156
+ outboxAttempts: typeof outbox?.attempts === "number" ? outbox.attempts : null,
157
+ ageSeconds,
158
+ };
159
+ }
160
+
161
+ function dispatchRecoveryDecision(input: {
162
+ dispatchRef: PaidFulfillmentDispatchRefRow | undefined;
163
+ fulfillmentCreatedAt: string | null | undefined;
164
+ now: Date;
165
+ missingRefGraceSeconds: number;
166
+ staleSeconds: number;
167
+ }): Pick<PaidFulfillmentRecoveryCandidate, "reason" | "recommendedAction" | "recoveryPosture"> | null {
168
+ const { dispatchRef, now, staleSeconds } = input;
169
+ if (!dispatchRef) {
170
+ if (!isAtLeastAgeOrUnknown(input.fulfillmentCreatedAt, now, input.missingRefGraceSeconds)) return null;
171
+ return recoveryDecision("provider_fulfillment_without_dispatch_ref", "inspect_missing_dispatch_ref");
172
+ }
173
+ const status = clean(dispatchRef.status);
174
+ if (status === "draft") {
175
+ return recoveryDecision("provider_dispatch_in_progress", "monitor_dispatch", "wait_for_existing_automation");
176
+ }
177
+ if (status === "submitting") {
178
+ return isAtLeastAgeOrUnknown(dispatchRef.updated_at ?? dispatchRef.created_at, now, staleSeconds)
179
+ ? recoveryDecision("provider_dispatch_submission_stale", "inspect_stale_dispatch_submission")
180
+ : recoveryDecision("provider_dispatch_in_progress", "monitor_dispatch", "wait_for_existing_automation");
181
+ }
182
+ if (status === "uncertain") {
183
+ return recoveryDecision("provider_dispatch_outcome_uncertain", "inspect_uncertain_dispatch");
184
+ }
185
+ if (status === "failed") {
186
+ return recoveryDecision("provider_dispatch_failed", "inspect_failed_dispatch");
187
+ }
188
+ if (status === "created") {
189
+ return dispatchRef.provider_order_id?.trim()
190
+ ? null
191
+ : recoveryDecision(
192
+ "provider_dispatch_created_without_provider_order_id",
193
+ "inspect_created_dispatch_without_provider_order_id",
194
+ );
195
+ }
196
+ if (["cancel_requested", "cancelled"].includes(status)) {
197
+ return recoveryDecision("provider_dispatch_terminal_without_progress", "inspect_terminal_dispatch");
198
+ }
199
+ return recoveryDecision("provider_dispatch_status_unknown", "inspect_unknown_dispatch_status");
200
+ }
201
+
202
+ function recoveryDecision(
203
+ reason: PaidFulfillmentRecoveryReason,
204
+ recommendedAction: PaidFulfillmentRecoveryAction,
205
+ recoveryPosture: PaidFulfillmentRecoveryPosture = "operator_review_required",
206
+ ) {
207
+ return { reason, recommendedAction, recoveryPosture };
208
+ }
209
+
210
+ function latestBy<T extends { created_at?: string | null; updated_at?: string | null }>(
211
+ rows: T[],
212
+ key: (row: T) => string,
213
+ ): Map<string, T> {
214
+ const result = new Map<string, T>();
215
+ for (const row of rows) {
216
+ const rowKey = key(row);
217
+ if (!rowKey) continue;
218
+ const current = result.get(rowKey);
219
+ if (!current || timestamp(row.updated_at ?? row.created_at) >= timestamp(current.updated_at ?? current.created_at)) {
220
+ result.set(rowKey, row);
221
+ }
222
+ }
223
+ return result;
224
+ }
225
+
226
+ function groupBy<T>(rows: T[], key: (row: T) => string): Map<string, T[]> {
227
+ const groups = new Map<string, T[]>();
228
+ for (const row of rows) {
229
+ const rowKey = key(row);
230
+ const group = groups.get(rowKey);
231
+ if (group) group.push(row);
232
+ else groups.set(rowKey, [row]);
233
+ }
234
+ return groups;
235
+ }
236
+
237
+ function clean(value: string | null | undefined): string {
238
+ return value?.trim().toLowerCase() ?? "";
239
+ }
240
+ function timestamp(value: string | null | undefined): number {
241
+ const parsed = value ? Date.parse(value) : 0;
242
+ return Number.isFinite(parsed) ? parsed : 0;
243
+ }
244
+ function secondsSince(value: string, now: Date): number {
245
+ const parsed = Date.parse(value);
246
+ if (!Number.isFinite(parsed)) return 0;
247
+ return Math.max(0, Math.floor((now.getTime() - parsed) / 1000));
248
+ }
249
+ function isAtLeastAgeOrUnknown(value: string | null | undefined, now: Date, seconds: number): boolean {
250
+ const parsed = timestamp(value);
251
+ return parsed <= 0 || now.getTime() - parsed > seconds * 1000;
252
+ }
@@ -0,0 +1,122 @@
1
+ import { z } from "zod";
2
+ import type {
3
+ OutboxEventRow,
4
+ OutboxHandler,
5
+ OutboxHandlerOutcome,
6
+ } from "@openlup/core/outbox";
7
+ import type { OrderPaidFulfillmentPort } from "./paidHandoffPorts.js";
8
+
9
+ const COMMERCE_ORDER_PAID_EVENT_TYPE = "commerce.order.paid";
10
+ const HANDLER_TIMEOUT_MS = 15_000;
11
+
12
+ const outboxOrderPaidFulfillmentDescriptor = Object.freeze({
13
+ eventType: COMMERCE_ORDER_PAID_EVENT_TYPE, timeoutMs: HANDLER_TIMEOUT_MS,
14
+ });
15
+
16
+ // Risk seam (optional): assess a paid order BEFORE fulfillment. Declared here so
17
+ // this handler stays independent of the host's risk implementation, which can
18
+ // supply a structurally compatible port.
19
+ interface OrderPaidRiskAssessmentPort {
20
+ assessPaidOrder(input: {
21
+ orderUuid: string;
22
+ outboxEventId: string;
23
+ signal: AbortSignal;
24
+ }): Promise<
25
+ | { kind: "allow"; detail?: Record<string, unknown> }
26
+ | { kind: "review" | "block"; detail?: Record<string, unknown> }
27
+ | { kind: "retryable"; reason: string }
28
+ | { kind: "fatal"; reason: string }
29
+ >;
30
+ }
31
+
32
+ // Frozen minimal parse: validate only the orderUuid forwarded to the fulfillment
33
+ // port. This handler owns the subset schema so a producer-side payload change
34
+ // does not send otherwise valid in-flight events to the dead-letter queue.
35
+ const payloadSubsetSchema = z
36
+ .object({
37
+ orderUuid: z.guid(),
38
+ })
39
+ .passthrough();
40
+
41
+ export function createOutboxOrderPaidFulfillmentHandler(deps: {
42
+ fulfillmentPort: OrderPaidFulfillmentPort;
43
+ // Optional risk seam. When set AND enabled, a paid order is risk-assessed
44
+ // before fulfillment; a review/block holds the order (no dispatch).
45
+ riskPort?: OrderPaidRiskAssessmentPort;
46
+ isRiskEnabled?: () => boolean;
47
+ }): OutboxHandler<AbortSignal> {
48
+ return {
49
+ ...outboxOrderPaidFulfillmentDescriptor,
50
+ async handle(row: OutboxEventRow, signal: AbortSignal): Promise<OutboxHandlerOutcome> {
51
+ const parsed = parseOutboxPayload(payloadSubsetSchema, row.payload);
52
+ if (parsed.ok === false) {
53
+ return parsed.outcome;
54
+ }
55
+
56
+ // Risk gate (flag-OFF by default): assess the paid order before any
57
+ // fulfillment work. review/block → hold (ack without dispatch, the case is
58
+ // persisted for manual review); retryable/fatal map to the worker ladder.
59
+ if (deps.riskPort && deps.isRiskEnabled?.()) {
60
+ const risk = await deps.riskPort.assessPaidOrder({
61
+ orderUuid: parsed.data.orderUuid,
62
+ outboxEventId: row.id,
63
+ signal,
64
+ });
65
+ switch (risk.kind) {
66
+ case "allow":
67
+ break;
68
+ case "review":
69
+ case "block":
70
+ return {
71
+ kind: "processed",
72
+ detail: { skipped: "risk_review_hold", risk: risk.detail ?? {} },
73
+ };
74
+ case "retryable":
75
+ return { kind: "retry", reason: truncateReason(risk.reason) };
76
+ case "fatal":
77
+ return { kind: "discard", reason: truncateReason(risk.reason) };
78
+ }
79
+ }
80
+
81
+ const result = await deps.fulfillmentPort.ensureFulfilledFromPaidOrder({
82
+ orderUuid: parsed.data.orderUuid,
83
+ outboxEventId: row.id,
84
+ signal,
85
+ });
86
+
87
+ switch (result.kind) {
88
+ case "completed":
89
+ case "skipped":
90
+ return { kind: "processed", detail: result.detail };
91
+ case "manual_review":
92
+ // Terminal but NOT a silent discard: the port already placed the
93
+ // durable observable signal (an active fulfillment_exception hold).
94
+ // Settle the event as processed so it is not parked in the discard/
95
+ // error queue, and record why in the outbox metadata.
96
+ return {
97
+ kind: "processed",
98
+ detail: {
99
+ ...result.detail,
100
+ outcome: "manual_review",
101
+ reason: truncateReason(result.reason),
102
+ },
103
+ };
104
+ case "retryable":
105
+ return { kind: "retry", reason: truncateReason(result.reason) };
106
+ case "snooze":
107
+ return { kind: "snooze", reason: truncateReason(result.reason) };
108
+ case "fatal":
109
+ return { kind: "discard", reason: truncateReason(result.reason) };
110
+ }
111
+ },
112
+ };
113
+ }
114
+
115
+ function truncateReason(value: string): string { return value.length <= 300 ? value : `${value.slice(0, 300)}…`; }
116
+ function parseOutboxPayload<T>(schema: import("zod").ZodType<T>, payload: unknown):
117
+ { ok: true; data: T } | { ok: false; outcome: OutboxHandlerOutcome } {
118
+ const parsed = schema.safeParse(payload);
119
+ if (parsed.success) return { ok: true, data: parsed.data };
120
+ const detail = parsed.error.issues.map(issue => `${issue.path.join(".") || "payload"}: ${issue.message}`).join("; ");
121
+ return { ok: false, outcome: { kind: "discard", reason: `contract_parse_failed: ${truncateReason(detail)}` } };
122
+ }
@@ -0,0 +1,21 @@
1
+ // Neutral port owned by the commerce.order.paid dispatch handler. Concrete
2
+ // persistence and provider adapters belong to the host composition.
3
+
4
+ export type OrderPaidFulfillmentResult =
5
+ | { kind: "completed" | "skipped"; detail?: Record<string, unknown> }
6
+ | { kind: "retryable"; reason: string }
7
+ | { kind: "snooze"; reason: string }
8
+ | { kind: "fatal"; reason: string }
9
+ // Paid order that cannot be auto-fulfilled and was routed to an observable
10
+ // manual-review state (an active fulfillment_exception hold). Terminal but
11
+ // NOT a silent discard: the hold is the durable signal. No fulfillment order
12
+ // or label is created for this outcome.
13
+ | { kind: "manual_review"; reason: string; detail?: Record<string, unknown> };
14
+
15
+ export interface OrderPaidFulfillmentPort {
16
+ ensureFulfilledFromPaidOrder(input: {
17
+ orderUuid: string;
18
+ outboxEventId: string;
19
+ signal: AbortSignal;
20
+ }): Promise<OrderPaidFulfillmentResult>;
21
+ }
@@ -0,0 +1,14 @@
1
+ import type { DeliveryAlignmentPort } from './contracts/index.js';
2
+ import { nativeCall, type PgExecutor } from './postgresDispatchStore.js';
3
+ import { readDeliveryAlignment } from './deliveryAlignmentReadModel.js';
4
+
5
+ /** Trusted arrival ingestion only. Operator replacement attestation has no caller here. */
6
+ export function createPostgresDeliveryAlignmentPort(db: PgExecutor): DeliveryAlignmentPort {
7
+ return {
8
+ read: ref => readDeliveryAlignment(db, ref),
9
+ async recordArrival(v) {
10
+ const receipt = await nativeCall<{ replayed: boolean }>(db, 'fulfillment_record_alignment_arrival', [v.requestKey, v.parcelRef, v.deliveredAt, v.evidenceRef]);
11
+ return { ...await readDeliveryAlignment(db, v.parcelRef), replayed: receipt.replayed };
12
+ },
13
+ };
14
+ }
@@ -0,0 +1,55 @@
1
+ import type { DispatchRecord, FulfillmentDispatchRequest, FulfillmentStorePort } from './contracts/index.js';
2
+ import { dispatchEvidence, dispatchResult } from './dispatchProjection.js';
3
+ import type { createFulfillmentCommandLedger } from './providerCommandLedger.js';
4
+
5
+ /** Caller supplies a driver and its selected host preparation/data boundary. */
6
+ export interface PgExecutor {
7
+ query(text: string, values?: unknown[]): Promise<{ rows: Record<string, unknown>[] }>;
8
+ }
9
+ export async function nativeCall<T>(db: PgExecutor, name: string, values: unknown[]): Promise<T> {
10
+ const result = await db.query(`SELECT public.${name}(${values.map((_, index) => `$${index + 1}`).join(',')}) AS response`, values);
11
+ if (!result.rows[0] || result.rows[0].response === undefined) throw new Error('fulfillment_native_response_missing');
12
+ return result.rows[0].response as T;
13
+ }
14
+
15
+ export function createPostgresFulfillmentDispatchStore(db: PgExecutor, host: {
16
+ /** Synchronize authoritative withholding/contact revision on prepare and every source edit. */
17
+ prepare(fulfillmentRef: string): ReturnType<FulfillmentStorePort['prepare']>;
18
+ readCandidate(fulfillmentRef: string): Promise<FulfillmentDispatchRequest | null>;
19
+ }): FulfillmentStorePort & { commandLedger: ReturnType<typeof createFulfillmentCommandLedger> } {
20
+ return {
21
+ readCandidate: ref => host.readCandidate(ref),
22
+ prepare: ref => host.prepare(ref),
23
+ async read(key) {
24
+ const result = await db.query('SELECT public.fulfillment_dispatch_read(ref) AS response FROM public.fulfillment_dispatch_refs ref WHERE request_key=$1', [key]);
25
+ return result.rows[0]?.response as DispatchRecord | undefined ?? null;
26
+ },
27
+ claim: input => nativeCall(db, 'fulfillment_dispatch_claim', [input.requestKey, input.fulfillmentRef, input.orderRef, input.parcelRef, input.mode, JSON.stringify(dispatchEvidence(input.evidence))]),
28
+ recordProviderAttempt: (key, ref, evidence, mode) => nativeCall(db, 'fulfillment_record_provider_attempt', [key, ref, JSON.stringify(dispatchEvidence(evidence)), mode]),
29
+ begin: input => nativeCall(db, 'fulfillment_dispatch_begin', [input.dispatchRef, input.commandFingerprint]),
30
+ finalize: input => nativeCall(db, 'fulfillment_dispatch_finish', [input.dispatchRef, JSON.stringify(dispatchResult(input.result)), false]),
31
+ acknowledge: input => nativeCall(db, 'fulfillment_dispatch_finish', [input.dispatchRef, JSON.stringify(dispatchResult(input.acceptance)), true]),
32
+ async markStaleSubmissionsUncertain(staleBefore) {
33
+ const result = await db.query("UPDATE public.fulfillment_dispatch_refs SET status='uncertain',updated_at=now() WHERE status='submitting' AND updated_at<$1 RETURNING id", [staleBefore]);
34
+ return result.rows.length;
35
+ },
36
+ commandLedger: commandLedger(db),
37
+ };
38
+ }
39
+ function commandLedger(db: PgExecutor): ReturnType<typeof createFulfillmentCommandLedger> {
40
+ const fence = (v: { commandId: string; claimToken: string; claimGeneration: number; expectedVersion: number }) => [v.commandId, v.claimToken, v.claimGeneration, v.expectedVersion];
41
+ return {
42
+ enqueue: v => nativeCall(db, 'commerce_fulfillment_enqueue_provider_command', [v.fulfillmentRef, v.providerRef, v.commandKind, v.contractVersion, v.idempotencyKey, v.commandKeyFingerprint, v.commandPayloadFingerprint, v.providerIdempotencyKeyFingerprint, v.routingFingerprint, v.configurationFingerprint, v.correlationKeyFingerprint, v.retryBudget]),
43
+ async claim(v) {
44
+ const result = await db.query('SELECT public.commerce_fulfillment_claim_provider_commands($1,$2,$3,$4,$5) AS response', [v.providerRef, v.commandKind, v.limit, v.claimOwnerFingerprint, v.leaseSeconds]);
45
+ return result.rows.map(row => row.response) as Awaited<ReturnType<ReturnType<typeof createFulfillmentCommandLedger>['claim']>>;
46
+ },
47
+ markSubmitting: v => nativeCall(db, 'commerce_fulfillment_mark_command_submitting', [...fence(v), v.providerAttemptRef]),
48
+ applyWriteOutcome: v => nativeCall(db, 'commerce_fulfillment_apply_command_write_outcome', [...fence(v), v.identityFingerprint, v.kind, v.reason, v.kind === 'rejected' ? v.retryNotBefore : null, v.kind === 'accepted' ? v.providerReferenceFingerprint : null, v.providerAttemptRef, v.kind === 'rejected' ? v.proof.kind : null, v.kind === 'rejected' ? v.proof.fingerprint : null, v.kind === 'rejected' ? v.proof.observedAt : null, v.kind === 'rejected' ? v.proof.source : null]),
49
+ confirm: v => nativeCall(db, 'commerce_fulfillment_confirm_provider_command', [...fence(v), v.identityFingerprint, v.providerAttemptRef, v.proof.kind, v.proof.fingerprint, v.proof.observedAt, v.proof.source]),
50
+ reconcile: v => nativeCall(db, 'commerce_fulfillment_reconcile_provider_command', [v.commandId, v.expectedVersion, v.identityFingerprint, v.proof.kind, v.proof.fingerprint, v.proof.observedAt, v.proof.source]),
51
+ resolve: v => nativeCall(db, 'commerce_fulfillment_resolve_uncertain_command', [v.commandId, v.expectedVersion, v.identityFingerprint, v.decision, v.reason, v.proof.kind, v.proof.fingerprint, v.proof.observedAt, v.proof.source, v.decision === 'retry_wait' ? v.retryNotBefore : null]),
52
+ release: v => nativeCall(db, 'commerce_fulfillment_release_or_cancel_command', [...fence(v), v.action, v.reason]),
53
+ recoverExpired: v => nativeCall(db, 'commerce_fulfillment_recover_expired_provider_commands', [v.limit]),
54
+ };
55
+ }
@@ -0,0 +1,59 @@
1
+ import type { PgExecutor as PgQueryExecutor } from './postgresDispatchStore.js';
2
+ interface AdminLowStockEvidenceItem {
3
+ id: string; sku: string; thresholdKind: string; severity: string; status: string;
4
+ providerForSaleQuantity: number | null; localAvailableQuantity: number | null;
5
+ forecastDays: null; firstSeenAt: string | null; lastSeenAt: string | null; resolvedAt: string | null;
6
+ }
7
+ interface FulfillmentLowStockEvidencePort {
8
+ getLowStockEvidence(input: { statusFilter: 'all' | 'open' | 'resolved' }): Promise<{ statusFilter: string; openCount: number; items: AdminLowStockEvidenceItem[] }>;
9
+ }
10
+ const READ = `SELECT id, sku, threshold_kind, severity, status,
11
+ provider_for_sale_quantity, local_available_quantity,
12
+ first_seen_at, last_seen_at, resolved_at
13
+ FROM public.fulfillment_stock_evidence
14
+ WHERE source_key = $1::text AND ($2::text = 'all' OR status = $2::text)
15
+ ORDER BY last_seen_at DESC, id ASC`;
16
+ const COUNT = `SELECT count(*)::integer AS count
17
+ FROM public.fulfillment_stock_evidence
18
+ WHERE source_key = $1::text AND status = 'open'`;
19
+
20
+ export function createPostgresFulfillmentLowStockEvidencePort(
21
+ executor: PgQueryExecutor,
22
+ sourceKey: string,
23
+ ): FulfillmentLowStockEvidencePort {
24
+ const key = sourceKey.trim();
25
+ if (!key) throw new Error("fulfillment_stock_source_key_required");
26
+ return { async getLowStockEvidence({ statusFilter }) {
27
+ const [rows, count] = await Promise.all([
28
+ executor.query(READ, [key, statusFilter]),
29
+ executor.query(COUNT, [key]),
30
+ ]);
31
+ return {
32
+ statusFilter,
33
+ openCount: integer(count.rows[0]?.count),
34
+ items: rows.rows.map(mapItem),
35
+ };
36
+ } };
37
+ }
38
+
39
+ function mapItem(row: Record<string, unknown>): AdminLowStockEvidenceItem {
40
+ return {
41
+ id: String(row.id),
42
+ sku: String(row.sku),
43
+ thresholdKind: row.threshold_kind as AdminLowStockEvidenceItem["thresholdKind"],
44
+ severity: row.severity as AdminLowStockEvidenceItem["severity"],
45
+ status: row.status as AdminLowStockEvidenceItem["status"],
46
+ providerForSaleQuantity: nullableInteger(row.provider_for_sale_quantity),
47
+ localAvailableQuantity: nullableInteger(row.local_available_quantity),
48
+ forecastDays: null,
49
+ firstSeenAt: nullableText(row.first_seen_at),
50
+ lastSeenAt: nullableText(row.last_seen_at),
51
+ resolvedAt: nullableText(row.resolved_at),
52
+ };
53
+ }
54
+
55
+ function integer(value: unknown): number {
56
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
57
+ }
58
+ function nullableInteger(value: unknown): number | null { return value === null ? null : integer(value); }
59
+ function nullableText(value: unknown): string | null { return value instanceof Date ? value.toISOString() : typeof value === "string" && value ? value : null; }
@@ -0,0 +1,29 @@
1
+ import type { collectPaidFulfillmentRecoveryCandidates } from './paidFulfillmentRecovery.js';
2
+ import type { PgExecutor } from './postgresDispatchStore.js';
3
+ type RecoveryInput = Parameters<typeof collectPaidFulfillmentRecoveryCandidates>[0];
4
+
5
+ /** Read the selected native port's local obligations; never infer external absence. */
6
+ export function createPostgresPaidFulfillmentRecoveryOpsPort(db: PgExecutor, selection: { portKey: string; providerRef: string }) {
7
+ return {
8
+ async readRecoveryInputs(input: { minimumAgeSeconds: number; limit: number; now: Date; orderIds?: string[] }): Promise<Pick<RecoveryInput,'orders' | 'fulfillmentOrders' | 'dispatchRefs' | 'orderPaidOutboxEvents'>> {
9
+ const ids=[...new Set(input.orderIds ?? [])];
10
+ const orders=(await db.query(`SELECT id,status,metadata->>'mode' AS mode,created_at::text,updated_at::text FROM public.commerce_orders
11
+ WHERE status=ANY($1::text[]) AND updated_at<=$2 AND ($4::uuid[]='{}'::uuid[] OR id=ANY($4::uuid[])) ORDER BY updated_at ASC LIMIT $3`,
12
+ [['paid','fulfillment_pending'],new Date(input.now.getTime()-input.minimumAgeSeconds*1000),input.limit,ids])).rows as RecoveryInput['orders'];
13
+ const orderIds=orders.map(row => row.id);
14
+ if (!orderIds.length) return {orders:[],fulfillmentOrders:[],dispatchRefs:[],orderPaidOutboxEvents:[]};
15
+ const [shipments,refs,events]=await Promise.all([
16
+ db.query('SELECT id,order_id,$3::text AS provider_kind,status,created_at::text,updated_at::text FROM public.fulfillment_shipments WHERE order_id=ANY($1::uuid[]) AND port_key=$2 ORDER BY created_at DESC',[orderIds,selection.portKey,selection.providerRef]),
17
+ db.query('SELECT ref.id,ref.shipment_id AS fulfillment_order_id,ref.external_ref AS provider_order_id,ref.status,ref.updated_at::text FROM public.fulfillment_dispatch_refs ref JOIN public.fulfillment_shipments shipment ON shipment.id=ref.shipment_id WHERE ref.order_id=ANY($1::uuid[]) AND shipment.port_key=$2',[orderIds,selection.portKey]),
18
+ db.query("SELECT id,event_type,status,aggregate_id,created_at::text,available_at::text,attempts,metadata->>'updatedAt' AS updated_at FROM public.outbox_events WHERE event_type=$1 AND aggregate_id=ANY($2::uuid[]) ORDER BY created_at DESC",['commerce.order.paid',orderIds]),
19
+ ]);
20
+ return {orders,fulfillmentOrders:shipments.rows as RecoveryInput['fulfillmentOrders'],dispatchRefs:refs.rows as RecoveryInput['dispatchRefs'],orderPaidOutboxEvents:events.rows as RecoveryInput['orderPaidOutboxEvents']};
21
+ },
22
+ async requeueDiscardedOrderPaidOutboxEvents(input: { eventIds: string[]; limit?: number; requeuedBy: string; reason: string }) {
23
+ const ids=[...new Set(input.eventIds.filter(Boolean))];
24
+ if (!ids.length) return {requeuedCount:0,eventIds:[]};
25
+ const result=await db.query('SELECT id FROM public.outbox_requeue_discarded(p_event_ids=>$1::uuid[],p_event_type=>$2,p_limit=>$3,p_requeued_by=>$4,p_reason=>$5)',[ids,'commerce.order.paid',input.limit ?? ids.length,input.requeuedBy,input.reason]);
26
+ return {requeuedCount:result.rows.length,eventIds:result.rows.map(row => String(row.id))};
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,11 @@
1
+ import type { FulfillmentPaidHandoffPort } from './contracts/index.js';
2
+ import { nativeCall, type PgExecutor } from './postgresDispatchStore.js';
3
+
4
+ /** Select an existing native port explicitly; never route by a private provider default. */
5
+ export function createPostgresPaidFulfillmentHandoffStore(db: PgExecutor, portKey: string): FulfillmentPaidHandoffPort {
6
+ if (!portKey.trim()) throw new Error('fulfillment_port_key_required');
7
+ return {
8
+ ensureObligation: v => nativeCall(db, 'fulfillment_ensure_paid_handoff', [v.requestKey, v.orderRef, v.paidFactRef, portKey]),
9
+ acknowledge: v => nativeCall(db, 'fulfillment_ack_paid_handoff', [v.requestKey, v.fulfillmentRef]),
10
+ };
11
+ }