@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,277 @@
1
+ import { stripeWebhookPayload } from "./stripeFailureEvidence.js";
2
+ import { stripeRefundEvidence } from "./stripeRefundEvidence.js";
3
+ import { expiryInstantFromMonthYear, type PaymentMethodLifecycleEvent, type PaymentMethodLifecycleEventKind, type PaymentMethodReplacementFacts } from "@openlup/core/payment";
4
+ import type { NormalizedProviderPaymentWebhook } from "./donorContracts.js";
5
+ import { methodFactSnapshotKeys, type ProviderWebhookWithMethodLifecycle } from "./methodFacts.js";
6
+
7
+ const PROVIDER_KIND = "stripe";
8
+
9
+ /**
10
+ * Method-family events, mapped to the neutral lifecycle vocabulary. They carry
11
+ * no money and match no local attempt, so they ride the canonical `setup.*`
12
+ * kinds and the ingest ledger's allowlist does not move. `attached` is the
13
+ * first delivery carrying the instrument's own facts (the setup event carries
14
+ * only its id); `automatically_updated` is the network rotating those facts
15
+ * under an UNCHANGED consent, never a death; `detached` is the method leaving
16
+ * the payer's file for good. ⚠️ Deploy note: the endpoint's subscribed event
17
+ * list must include these three names or none of them ever arrive — an
18
+ * operator console change, not a code change.
19
+ */
20
+ type MethodLifecycleMapping = { kind: PaymentMethodLifecycleEventKind; canonical: NormalizedProviderPaymentWebhook["eventType"] };
21
+ const METHOD_LIFECYCLE_EVENTS: Record<string, MethodLifecycleMapping> = {
22
+ "payment_method.attached": { kind: "method_registered", canonical: "setup.succeeded" },
23
+ "payment_method.automatically_updated": { kind: "method_updated", canonical: "setup.succeeded" },
24
+ "payment_method.detached": { kind: "method_revoked", canonical: "setup.failed" },
25
+ };
26
+
27
+ const SUPPORTED_EVENT_TYPES = new Set<NormalizedProviderPaymentWebhook["eventType"]>([
28
+ "payment.requires_action",
29
+ "payment.succeeded",
30
+ "payment.failed",
31
+ "payment.refunded",
32
+ "payment.disputed",
33
+ "setup.succeeded",
34
+ "setup.failed",
35
+ "setup.requires_action",
36
+ ]);
37
+
38
+ /**
39
+ * Convert a verified raw Stripe webhook payload into the plural handler's
40
+ * `NormalizedProviderPaymentWebhook` shape.
41
+ *
42
+ * Reusable method extraction (Wave C): populated for `setup_intent.succeeded`
43
+ * always, and for `payment_intent.succeeded` when `setup_future_usage` was
44
+ * requested at PaymentIntent creation. Requires `metadata.clientId` to be set
45
+ * (the adapter does this when the runtime caller passes `clientId` through
46
+ * `PaymentExecutionInput`). When metadata is missing, `reusableMethod` stays
47
+ * null and the plural handler skips the method-ref upsert — safe degradation
48
+ * rather than crashing the webhook.
49
+ */
50
+ export function normalizeStripeWebhookEvent(
51
+ rawPayload: Record<string, unknown>,
52
+ ): ProviderWebhookWithMethodLifecycle {
53
+ const providerEventId = readString(rawPayload, "id");
54
+ const eventType = readString(rawPayload, "type");
55
+ const data = readObject(rawPayload, "data");
56
+ const object = readObject(data, "object");
57
+
58
+ const canonicalEventType = mapStripeEventType(eventType, object);
59
+ const providerPaymentId = resolveLinkagePaymentId(eventType, object);
60
+ // Payment-control compares this against the intent's `amount_cents` (the order
61
+ // total) as a tamper guard, so we want the PaymentIntent's authorized `amount`,
62
+ // not `amount_received`. On a declined/canceled PI Stripe reports
63
+ // `amount_received: 0` while `amount` still holds the full total — and `??`
64
+ // does NOT treat `0` as nullish, so reading `amount_received` first yielded a
65
+ // `0` that tripped the amount-mismatch guard and left every failed event
66
+ // unreconciled (intent stuck `processing`, inventory holds leaked). Prefer
67
+ // `amount`; fall back to `amount_received` only when `amount` is absent.
68
+ const amountMinor =
69
+ readOptionalNumber(object, "amount") ??
70
+ readOptionalNumber(object, "amount_received") ??
71
+ null;
72
+ const currency = readOptionalString(object, "currency")?.toUpperCase() ?? null;
73
+ const occurredAt = readOccurredAt(rawPayload);
74
+ const reusableMethod = extractReusableMethod(canonicalEventType, object);
75
+ const refund = stripeRefundEvidence(eventType, object);
76
+
77
+ return {
78
+ provider: PROVIDER_KIND,
79
+ providerEventId,
80
+ eventType: canonicalEventType,
81
+ providerPaymentId,
82
+ paymentIntentId: null,
83
+ paymentAttemptId: null,
84
+ amountMinor,
85
+ currency,
86
+ occurredAt,
87
+ refundKind: refund.refundKind,
88
+ rawPayload: {
89
+ ...stripeWebhookPayload({ eventId: providerEventId, eventType, providerPaymentId, amountMinor, currency }, object),
90
+ ...(refund.snapshot ? { stripeRefundSnapshot: refund.snapshot } : {}),
91
+ },
92
+ reusableMethod,
93
+ methodLifecycle: extractMethodLifecycle(eventType, object, providerEventId, occurredAt),
94
+ };
95
+ }
96
+
97
+ /** The neutral transition this delivery reports, or null when it reports none. */
98
+ function extractMethodLifecycle(
99
+ rawEventType: string,
100
+ object: Record<string, unknown>,
101
+ providerEventId: string,
102
+ occurredAt: string,
103
+ ): PaymentMethodLifecycleEvent | null {
104
+ const mapping = METHOD_LIFECYCLE_EVENTS[rawEventType];
105
+ // On a method-family event the webhook object IS the method, so its own id is
106
+ // the reference the stored row is keyed by.
107
+ const providerMethodRef = readOptionalString(object, "id");
108
+ if (!mapping || !providerMethodRef) return null;
109
+ const replacement = readMethodFacts(object);
110
+ return { kind: mapping.kind, providerKind: PROVIDER_KIND, providerMethodRef, providerEventId, occurredAt, replacement };
111
+ }
112
+
113
+ /**
114
+ * Scheme label, trailing digits and expiry, from whichever of the three shapes
115
+ * the payload carries them in: the method object itself at the top level, an
116
+ * expanded method one level down, or a charge-shaped object's method details.
117
+ * A payload carrying none of them yields null, not a row of empty facts.
118
+ */
119
+ function readMethodFacts(object: Record<string, unknown>): PaymentMethodReplacementFacts | null {
120
+ const card = readOptionalObject(object, "card")
121
+ ?? readOptionalObject(readOptionalObject(object, "payment_method") ?? {}, "card")
122
+ ?? readOptionalObject(readOptionalObject(object, "payment_method_details") ?? {}, "card");
123
+ if (!card) return null;
124
+ const schemeLabel = readOptionalString(card, "brand") ?? null;
125
+ const lastDigits = readOptionalString(card, "last4") ?? null;
126
+ const expiresAt = expiryInstantFromMonthYear(readOptionalNumber(card, "exp_month") ?? null, readOptionalNumber(card, "exp_year") ?? null);
127
+ if (!schemeLabel && !lastDigits && !expiresAt) return null;
128
+ return { schemeLabel, lastDigits, expiresAt };
129
+ }
130
+
131
+ /**
132
+ * Resolve the provider payment id used to LINK this event to the local payment
133
+ * attempt. Payment-control matches inbound events to an attempt by
134
+ * `commerce_payment_attempts.provider_attempt_id`, which holds the Stripe
135
+ * PaymentIntent id (`pi_…`).
136
+ *
137
+ * For `payment_intent.*` events the webhook object IS the PaymentIntent, so its
138
+ * `id` is already the linkage key. For `charge.*` (incl. `charge.dispute.*`)
139
+ * the object is a Charge (`ch_…`) or Dispute (`dp_…`) whose own id never
140
+ * matches an attempt — but it carries `payment_intent` pointing back at the PI.
141
+ * Using that field is what lets `charge.refunded` / `charge.dispute.created`
142
+ * resolve to the order and drive `applyPaymentResult`; without it refunds and
143
+ * disputes were ingested but silently never applied. Falls back to the object
144
+ * id when `payment_intent` is absent (defensive — keeps the event recordable).
145
+ */
146
+ function resolveLinkagePaymentId(
147
+ stripeEventType: string,
148
+ object: Record<string, unknown>,
149
+ ): string {
150
+ if (stripeEventType.startsWith("charge.")) {
151
+ const paymentIntentId = readOptionalString(object, "payment_intent");
152
+ if (paymentIntentId) return paymentIntentId;
153
+ }
154
+ return readString(object, "id");
155
+ }
156
+
157
+ function mapStripeEventType(
158
+ stripeEventType: string,
159
+ object: Record<string, unknown>,
160
+ ): NormalizedProviderPaymentWebhook["eventType"] {
161
+ if (stripeEventType === "payment_intent.succeeded") return "payment.succeeded";
162
+ if (stripeEventType === "payment_intent.payment_failed") return "payment.failed";
163
+ if (stripeEventType === "payment_intent.canceled") return "payment.failed";
164
+ if (stripeEventType === "payment_intent.requires_action") return "payment.requires_action";
165
+ if (stripeEventType === "setup_intent.succeeded") return "setup.succeeded";
166
+ if (stripeEventType === "setup_intent.setup_failed") return "setup.failed";
167
+ if (stripeEventType === "setup_intent.requires_action") return "setup.requires_action";
168
+ if (stripeEventType === "charge.refunded") return "payment.refunded";
169
+ if (stripeEventType.startsWith("charge.dispute.")) return "payment.disputed";
170
+ const methodLifecycle = METHOD_LIFECYCLE_EVENTS[stripeEventType];
171
+ if (methodLifecycle) return methodLifecycle.canonical;
172
+ const status = readOptionalString(object, "status");
173
+ if (status === "requires_action") return "payment.requires_action";
174
+ const guess = `payment.${stripeEventType}` as NormalizedProviderPaymentWebhook["eventType"];
175
+ if (SUPPORTED_EVENT_TYPES.has(guess)) return guess;
176
+ throw new Error(`Unsupported Stripe webhook event: ${stripeEventType}`);
177
+ }
178
+
179
+ function extractReusableMethod(
180
+ canonicalEventType: NormalizedProviderPaymentWebhook["eventType"],
181
+ object: Record<string, unknown>,
182
+ ): NormalizedProviderPaymentWebhook["reusableMethod"] {
183
+ const isSetupSucceeded = canonicalEventType === "setup.succeeded";
184
+ const isPaymentSucceededWithSaveCard =
185
+ canonicalEventType === "payment.succeeded" &&
186
+ readOptionalString(object, "setup_future_usage") === "off_session";
187
+ if (!isSetupSucceeded && !isPaymentSucceededWithSaveCard) return null;
188
+
189
+ const customer = readOptionalString(object, "customer");
190
+ // The method arrives as a bare id, or as an expanded object when the payload
191
+ // also carries its facts; both address the same stored method.
192
+ const paymentMethod = readOptionalString(object, "payment_method")
193
+ ?? readOptionalString(readOptionalObject(object, "payment_method") ?? {}, "id");
194
+ const metadata = readOptionalObject(object, "metadata");
195
+ const clientId = metadata ? readOptionalString(metadata, "clientId") : undefined;
196
+ const subscriptionId = metadata ? readOptionalString(metadata, "subscriptionId") : undefined;
197
+ const mode = metadata ? readOptionalString(metadata, "mode") : undefined;
198
+ const recoveryCaseId = metadata ? readOptionalString(metadata, "recoveryCaseId") : undefined;
199
+ const metadataSource = metadata ? readOptionalString(metadata, "source") : undefined;
200
+ if (!customer || !paymentMethod || !clientId) return null;
201
+
202
+ return {
203
+ clientId,
204
+ // W11.7 Wave D-4a — recovery setup-method flow tags `subscriptionId` in
205
+ // SetupIntent metadata so this upsert binds the new method to the
206
+ // subscription the dunning case targets. One-time + initial save-card
207
+ // (Wave C) flows leave the field null because no subscription exists yet.
208
+ subscriptionId: subscriptionId ?? null,
209
+ providerCustomerRef: customer,
210
+ providerMethodRef: paymentMethod,
211
+ providerMandateRef: null,
212
+ methodKind: "card",
213
+ status: "active",
214
+ consentSnapshot: {
215
+ source: "stripe_webhook",
216
+ eventType: canonicalEventType,
217
+ checkoutMode: mode ?? null,
218
+ recoveryCaseId: recoveryCaseId ?? null,
219
+ metadataSource: metadataSource ?? null,
220
+ // Additive facts only, and only when the payload actually carried them.
221
+ // The setup event carries the method's id and nothing else, so this is
222
+ // usually empty there and filled later by the method-family delivery.
223
+ ...methodFactSnapshotKeys(readMethodFacts(object)),
224
+ },
225
+ };
226
+ }
227
+
228
+ function readOccurredAt(rawPayload: Record<string, unknown>): string {
229
+ const created = rawPayload.created;
230
+ if (typeof created === "number" && Number.isFinite(created)) {
231
+ return new Date(created * 1000).toISOString();
232
+ }
233
+ if (typeof created === "string") {
234
+ const parsed = new Date(created);
235
+ if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();
236
+ }
237
+ // Stripe always sends `created` on event payloads; fall back defensively if
238
+ // missing rather than refusing the event — downstream RPC still records the
239
+ // signature-verified facts.
240
+ return new Date(0).toISOString();
241
+ }
242
+
243
+ function readObject(value: Record<string, unknown>, key: string): Record<string, unknown> {
244
+ const raw = value[key];
245
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
246
+ throw new Error(`Stripe payload missing object ${key}`);
247
+ }
248
+ return raw as Record<string, unknown>;
249
+ }
250
+
251
+ function readOptionalObject(
252
+ value: Record<string, unknown>,
253
+ key: string,
254
+ ): Record<string, unknown> | null {
255
+ const raw = value[key];
256
+ return raw && typeof raw === "object" && !Array.isArray(raw)
257
+ ? (raw as Record<string, unknown>)
258
+ : null;
259
+ }
260
+
261
+ function readString(value: Record<string, unknown>, key: string): string {
262
+ const raw = value[key];
263
+ if (typeof raw !== "string" || raw.length === 0) {
264
+ throw new Error(`Stripe payload missing string ${key}`);
265
+ }
266
+ return raw;
267
+ }
268
+
269
+ function readOptionalString(value: Record<string, unknown>, key: string): string | undefined {
270
+ const raw = value[key];
271
+ return typeof raw === "string" && raw.length > 0 ? raw : undefined;
272
+ }
273
+
274
+ function readOptionalNumber(value: Record<string, unknown>, key: string): number | undefined {
275
+ const raw = value[key];
276
+ return typeof raw === "number" ? raw : undefined;
277
+ }
@@ -0,0 +1,55 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ export const STRIPE_TIMESTAMP_TOLERANCE_SECONDS = 300;
3
+
4
+ export function verifyStripeSignature(input: {
5
+ rawBody: string;
6
+ signatureHeader: string | undefined;
7
+ /**
8
+ * One or many endpoint secrets to accept. Multi-secret support lets a single
9
+ * route accept signatures from both the dashboard webhook endpoint and a
10
+ * local `stripe listen` CLI tunnel; the signature is accepted if ANY secret
11
+ * verifies. An empty array (or missing single secret) fails closed.
12
+ */
13
+ endpointSecret: string | readonly string[];
14
+ /** Injectable clock (UTC seconds) for deterministic tests; defaults to now. */
15
+ now?: () => number;
16
+ }): boolean {
17
+ const parts = Object.fromEntries(
18
+ (input.signatureHeader ?? "")
19
+ .split(",")
20
+ .map((part) => part.split("="))
21
+ .filter(([key, value]) => key && value) as Array<[string, string]>,
22
+ );
23
+ const timestamp = parts.t;
24
+ const signature = parts.v1;
25
+ if (!timestamp || !signature) return false;
26
+ // Reject replays outside the tolerance window before doing the HMAC work. A
27
+ // valid signature on a stale timestamp (captured-and-replayed event) must not
28
+ // pass; the future-skew bound tolerates small clock drift between Stripe and us.
29
+ const timestampSec = Number(timestamp);
30
+ if (!Number.isFinite(timestampSec)) return false;
31
+ const now = input.now ?? (() => Math.floor(Date.now() / 1000));
32
+ const ageSec = now() - timestampSec;
33
+ if (ageSec > STRIPE_TIMESTAMP_TOLERANCE_SECONDS) return false;
34
+ if (ageSec < -STRIPE_TIMESTAMP_TOLERANCE_SECONDS) return false;
35
+ const secrets = Array.isArray(input.endpointSecret)
36
+ ? input.endpointSecret
37
+ : input.endpointSecret
38
+ ? [input.endpointSecret]
39
+ : [];
40
+ return secrets.some((secret) => {
41
+ if (!secret) return false;
42
+ const expected = hmac(`${timestamp}.${input.rawBody}`, secret);
43
+ return timingSafeStringEqual(signature, expected);
44
+ });
45
+ }
46
+
47
+ function hmac(value: string, secret: string): string {
48
+ return createHmac("sha256", secret).update(value).digest("hex");
49
+ }
50
+
51
+ function timingSafeStringEqual(left: string, right: string): boolean {
52
+ const leftBuffer = Buffer.from(left);
53
+ const rightBuffer = Buffer.from(right);
54
+ return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
55
+ }
@@ -0,0 +1,42 @@
1
+ export interface TpayBlikAliasRegistrationInput {
2
+ orderRef: string;
3
+ amountMinor: number;
4
+ currency: string;
5
+ customerEmail: string;
6
+ staticWebhookUrl: string;
7
+ consentRef: string;
8
+ }
9
+
10
+ export interface TpayBlikAliasChargeInput {
11
+ orderRef: string;
12
+ payId: string;
13
+ amountMinor: number;
14
+ currency: string;
15
+ staticWebhookUrl: string;
16
+ }
17
+
18
+ export function buildTpayBlikAliasRegistrationDraft(input: TpayBlikAliasRegistrationInput) {
19
+ return {
20
+ provider: "tpay",
21
+ requestKind: "blik_alias_registration",
22
+ orderRef: input.orderRef,
23
+ amount: input.amountMinor,
24
+ currency: input.currency,
25
+ customer: { email: input.customerEmail },
26
+ webhookUrl: input.staticWebhookUrl,
27
+ consentRef: input.consentRef,
28
+ fallbackRequiredWhenAliasUnavailable: true,
29
+ };
30
+ }
31
+
32
+ export function buildTpayBlikAliasChargeDraft(input: TpayBlikAliasChargeInput) {
33
+ return {
34
+ provider: "tpay",
35
+ requestKind: "blik_alias_charge",
36
+ orderRef: input.orderRef,
37
+ payId: input.payId,
38
+ amount: input.amountMinor,
39
+ currency: input.currency,
40
+ webhookUrl: input.staticWebhookUrl,
41
+ };
42
+ }
@@ -0,0 +1,21 @@
1
+ import type { PaymentAdapterCapabilities } from "../contracts/index.js";
2
+
3
+ /** Bank-redirect, one-time-code and unattended Model O attempts are supported with host payer/method input; account availability remains verification_pending. */
4
+ export const tpayPaymentCapabilities: Readonly<PaymentAdapterCapabilities> = Object.freeze<PaymentAdapterCapabilities>({
5
+ providerKind: "tpay",
6
+ captureFlows: Object.freeze([
7
+ Object.freeze({ kind: "bank_redirect", handoff: "redirect", methodFamilies: Object.freeze(["bank-redirect"] as const) }),
8
+ Object.freeze({ kind: "one_time_code", handoff: "customer-authorization", methodFamilies: Object.freeze(["one-time-code"] as const) }),
9
+ ]),
10
+ requiresStoredMandateEvidence: true,
11
+ assessMandate: snapshot => snapshot.recurringModel === "O" ? { chargeable: true }
12
+ : { chargeable: false, blockReason: "mandate_model_unsupported_for_unattended_charge" },
13
+ unattendedChargeFlow: "recurring_charge",
14
+ payerContext: Object.freeze({ requiresPayerBlock: true, customerRefFallsBackToContactEmail: false }),
15
+ methodHealth: Object.freeze({ requiresCustomerRef: false, requiredMethodKind: "blik_payid", requiresPayerContact: true }),
16
+ terminalOutcomeReporting: Object.freeze({ kind: "attempt_records", silenceBecomesSuspectAfterMinutes: 360 }),
17
+ mandateUpsertIsSubscriptionScoped: true,
18
+ supportedCurrencies: Object.freeze(["PLN"]), unattended: "supported", readback: "supported", close: "unsupported",
19
+ exactAbsence: "unsupported", refundObservation: "supported", refundCreate: "unsupported", authorizationCaptureSplit: "unsupported",
20
+ });
21
+
@@ -0,0 +1,62 @@
1
+ import { classifyPaymentFailure, type PaymentExecutionProviderDecline, type PaymentFailureClassification, type PaymentFailureHint } from "@openlup/core/payment";
2
+ export const DECLINE_FAILURE_REASONS = {
3
+ /** The payer's bank cannot register a reusable mandate — offer a card instead. */
4
+ mandateUnsupported: "blik_recurring_unsupported_bank",
5
+ /** Declined for a reason the provider did not distinguish. */
6
+ generic: "provider_declined",
7
+ } as const;
8
+
9
+ /** Maps a provider refusal to the stable code persisted as `failure_reason`. */
10
+ export function declineFailureReason(decline: PaymentExecutionProviderDecline): string {
11
+ return decline.mandateUnsupported
12
+ ? DECLINE_FAILURE_REASONS.mandateUnsupported
13
+ : DECLINE_FAILURE_REASONS.generic;
14
+ }
15
+
16
+ /**
17
+ * The one place a refused payment gets a class, for every rail that consumes a
18
+ * synchronous decline: interactive checkout, checkout recovery, and the
19
+ * off-session renewal cron.
20
+ *
21
+ * Provider-agnostic on purpose. Every code the adapter understood has already
22
+ * been translated into `adviceCode` and `neutralReasonHints` by the adapter that
23
+ * owns the vocabulary, so nothing here needs to know which provider refused.
24
+ *
25
+ * The one reading this seam adds is the mandate flag. `mandateUnsupported` is a
26
+ * neutral capability statement any adapter may set, and it is what produces the
27
+ * scheme-named failure reason below; asserting the matching hint is what turns
28
+ * that reason into `mandate_dead` instead of leaving it unclassified. Doing it
29
+ * here rather than per adapter means a second adapter setting the same flag gets
30
+ * the same class without repeating the mapping.
31
+ *
32
+ * DISPLAY-LOAD-BEARING, and NOT a cadence input. Three live readers turn the
33
+ * class this seam decides into what the payer is told: `dunningFailureClassPort`
34
+ * reads it off the case, `customerAccountActionRequiredReadModel` selects it into
35
+ * the account surface, and `paymentRecoverySetupHandler` renders it as the cause
36
+ * sentence on the recovery page. What still does NOT read it is the retry ladder:
37
+ * no cadence, retry decision or scheduling branch consults the class, and that
38
+ * wave stays gated on validating this taxonomy against the historical corpus.
39
+ *
40
+ * `failureReasonKey` overrides the reason this seam would derive itself, and is
41
+ * supplied by a rail that persists a DIFFERENT reason string with the attempt:
42
+ * the reconciliation rail writes its own adapter-prefixed key, not
43
+ * `provider_declined`. That key is the classifier's weakest evidence tier, so
44
+ * the override changes a class only where the rail's own key is mapped; what it
45
+ * always changes is `decidedBy`, and that is the point. The historical-corpus
46
+ * replay reproduces a verdict from the stored attempt row, so the key named in
47
+ * the telemetry has to be the key actually on that row rather than one this seam
48
+ * invented for itself.
49
+ */
50
+ export function classifyDecline(
51
+ decline: PaymentExecutionProviderDecline,
52
+ options: { failureReasonKey?: string } = {},
53
+ ): PaymentFailureClassification {
54
+ const asserted: PaymentFailureHint[] = [...(decline.neutralReasonHints ?? [])];
55
+ if (decline.mandateUnsupported) asserted.push("mandateUnsupported");
56
+ return classifyPaymentFailure({
57
+ adviceCode: decline.adviceCode,
58
+ declineCode: decline.declineCode,
59
+ neutralReasonHints: asserted,
60
+ failureReasonKey: options.failureReasonKey ?? declineFailureReason(decline),
61
+ });
62
+ }
@@ -0,0 +1,90 @@
1
+ import type { PaymentFailureHint, PaymentRetryAdviceCode } from "@openlup/core/payment";
2
+
3
+ /**
4
+ * ⚠️ UNOFFICIAL INFERENCE — the whole table below.
5
+ *
6
+ * The provider publishes no retryable-versus-terminal classification for these
7
+ * numeric refusal codes. Every row is this adapter's reading of the code's
8
+ * documented MEANING, not a documented retry verdict, and one of them
9
+ * (`"100"`) appears in no published table at all. Treat the table as a
10
+ * hypothesis under test. The class it produces is already DISPLAY-load-bearing:
11
+ * it becomes the sentence the payer reads about their own refusal, so a wrong
12
+ * row here misinforms a customer today. It is still not a cadence input: the
13
+ * dunning programme validates this table against the historical refusal corpus
14
+ * before anything in the retry ladder is allowed to branch on it.
15
+ *
16
+ * The contract that would end the inference is OUTSTANDING, not absent: the
17
+ * dunning programme's letter to the provider asks for the official meaning of
18
+ * these codes, and is tracked there as an open item. Until it is answered, a row
19
+ * may be read more narrowly than it is written — see `assertableHints` in
20
+ * `tpayDeclineEvidence.ts`, where each observation decides how much of its own
21
+ * reading it is entitled to state — but no row may be read more widely.
22
+ *
23
+ * Two rails read this table, and both reach it through the attempt log's
24
+ * `paymentErrorCode`: the reconciliation poll, and — since the 2026-08-26
25
+ * incident — the synchronous execution path, which until then decided a
26
+ * mandate refusal by pattern-matching the refusal MESSAGE.
27
+ *
28
+ * Codes arrive as strings because that is how they leave the transport layer.
29
+ *
30
+ * Two rows carry a decision worth stating rather than a lookup:
31
+ *
32
+ * - `"101"` (refused by the payer) and `"107"` (refused on security grounds)
33
+ * are NOT credential death — the instrument may be in perfect health and
34
+ * the payer may approve the very next charge on it. There is no hint for
35
+ * "terminal for this attempt, instrument fine", so this adapter instead
36
+ * states the retry verdict directly, as advice: an unattended repeat of a
37
+ * refusal the payer or their bank made deliberately is the retry that
38
+ * cannot succeed and still costs a scheme fee. Naming it as advice is also
39
+ * the more honest shape — it records what this adapter concludes about
40
+ * RETRYING, without asserting anything about the instrument.
41
+ * - `"100"` and `"102"` get NO reading. Nothing published says what they mean
42
+ * precisely enough to classify, and `indeterminate` is the correct answer to
43
+ * a code we cannot read. They are named in
44
+ * `KNOWN_UNREADABLE_DECLINE_CODES` below, so their absence stays a recorded
45
+ * decision rather than an oversight.
46
+ */
47
+ export interface DeclineCodeReading {
48
+ hints?: readonly PaymentFailureHint[];
49
+ adviceCode?: PaymentRetryAdviceCode;
50
+ }
51
+
52
+ export const DECLINE_CODE_READING_TABLE: Readonly<Record<string, DeclineCodeReading>> = Object.freeze({
53
+ "101": Object.freeze({ adviceCode: "do_not_try_again" } as const),
54
+ "103": Object.freeze({ hints: Object.freeze(["transient"] as const) } as const),
55
+ "104": Object.freeze({ hints: Object.freeze(["transient"] as const) } as const),
56
+ "105": Object.freeze({ hints: Object.freeze(["mandateUnsupported"] as const) } as const),
57
+ "106": Object.freeze({ hints: Object.freeze(["limitExceeded"] as const) } as const),
58
+ "107": Object.freeze({ adviceCode: "do_not_try_again" } as const),
59
+ });
60
+
61
+ /**
62
+ * Codes this adapter has SEEN and still cannot read. They are deliberately absent
63
+ * from the table above rather than mapped to a guess: a row there must state
64
+ * vocabulary the kernel declares, and `indeterminate` is the honest answer to a
65
+ * code whose meaning is unconfirmed. Listing them here separates "we have met
66
+ * this code and refuse to invent its semantics" from "we have never seen it",
67
+ * which read identically at the lookup.
68
+ *
69
+ * - `"100"` — observed in production on 2026-08-26, as the first attempt of a
70
+ * refused mandate registration whose second attempt was `"105"`. Nothing
71
+ * published says what it means, so it maps to no hint and no advice.
72
+ * - `"102"` — same standing, from the same unpublished range.
73
+ *
74
+ * A code moving out of this list into the table is a semantics decision backed by
75
+ * evidence, not a lookup edit.
76
+ */
77
+ export const KNOWN_UNREADABLE_DECLINE_CODES: readonly string[] = Object.freeze(["100", "102"]);
78
+
79
+ const NO_READING: DeclineCodeReading = Object.freeze({});
80
+
81
+ /**
82
+ * This adapter's reading of one refusal code. Own-property lookup, so an
83
+ * inherited key (`constructor`, `toString`) is not a mapping.
84
+ */
85
+ export function declineCodeReading(code: string | undefined): DeclineCodeReading {
86
+ if (code === undefined) return NO_READING;
87
+ return Object.prototype.hasOwnProperty.call(DECLINE_CODE_READING_TABLE, code)
88
+ ? DECLINE_CODE_READING_TABLE[code]
89
+ : NO_READING;
90
+ }
@@ -0,0 +1,104 @@
1
+ /** Private structural contracts for the unchanged execution and observation slices. */
2
+ import type { PaymentExecutionBaseInput, PaymentExecutionBaseResult } from "@openlup/core/payment";
3
+ import type { ProviderReadPurpose } from "../contracts/index.js";
4
+ export type PaymentProviderFlow = "one_time_payment" | "setup_reusable_method" | "off_session_payment" | "blik_one_time" | "blik_one_click" | "pbl_one_time" | "blik_recurring_activation" | "recurring_charge";
5
+ export const PAYMENT_NEXT_ACTION_KINDS = ["redirect", "3ds_challenge", "sca_required"] as const;
6
+ export type PaymentNextActionKind = (typeof PAYMENT_NEXT_ACTION_KINDS)[number];
7
+ export interface PaymentExecutionInput extends PaymentExecutionBaseInput {
8
+ orderRef: string;
9
+ orderId?: string;
10
+ subjectId?: string | null;
11
+ returnContext?: "public" | "account";
12
+ payer?: { email: string; name: string; ip?: string | null; userAgent?: string | null };
13
+ transientProviderInput?: TpayTransientProviderInput | null;
14
+ paymentMethodAliasType?: "UID" | "PAYID";
15
+ paymentMethodRecurringModel?: "O" | "M";
16
+ providerFlow?: PaymentProviderFlow;
17
+ customerRef?: string;
18
+ paymentMethodRef?: string;
19
+ saveForFutureUse?: boolean;
20
+ clientId?: string;
21
+ }
22
+ export interface PaymentExecutionResult extends PaymentExecutionBaseResult {
23
+ nextActionKind: PaymentNextActionKind | null;
24
+ redirectUrl?: string | null;
25
+ clientSecret?: string | null;
26
+ customerProviderRef?: string | null;
27
+ paymentMethodRef?: string | null;
28
+ webhookExpected?: boolean;
29
+ recoveryRequired?: boolean;
30
+ }
31
+ export interface PaymentExecutionPort {
32
+ validateInput?(input: PaymentExecutionInput): void;
33
+ execute(input: PaymentExecutionInput): Promise<PaymentExecutionResult>;
34
+ }
35
+ export type PaymentWebhookEventType = "payment.succeeded" | "payment.failed" | "payment.refunded" |
36
+ "payment.requires_action" | "payment.disputed" | "setup.succeeded" | "setup.failed" | "setup.requires_action";
37
+ export interface CanonicalPaymentEvent {
38
+ provider_event_id: string;
39
+ event_type: PaymentWebhookEventType;
40
+ payment_provider_id: string;
41
+ amount_minor?: number;
42
+ next_action_kind?: PaymentNextActionKind;
43
+ raw_payload: Record<string, unknown>;
44
+ }
45
+ export interface NormalizedProviderPaymentWebhook {
46
+ provider: string;
47
+ providerEventId: string;
48
+ eventType: PaymentWebhookEventType;
49
+ providerPaymentId: string;
50
+ paymentIntentId?: string | null;
51
+ paymentAttemptId?: string | null;
52
+ amountMinor?: number | null;
53
+ currency?: string | null;
54
+ occurredAt: string;
55
+ refundKind?: "full" | "partial" | null;
56
+ rawPayload: Record<string, unknown>;
57
+ reusableMethod?: {
58
+ clientId: string;
59
+ subscriptionId?: string | null;
60
+ providerCustomerRef?: string | null;
61
+ providerMethodRef: string;
62
+ providerMandateRef?: string | null;
63
+ methodKind: "card";
64
+ status: "active";
65
+ consentSnapshot?: Record<string, unknown>;
66
+ } | null;
67
+ }
68
+ export interface ProviderReconciliationStatus {
69
+ status: "succeeded" | "failed" | "pending" | "unknown";
70
+ providerStatus: string;
71
+ occurredAt: string | null;
72
+ failureReason: string | null;
73
+ amountMinor: number | null;
74
+ currency: string | null;
75
+ rawPayload: Record<string, unknown>;
76
+ }
77
+ export interface ClaimedPaymentAttempt {
78
+ paymentAttemptId: string; paymentIntentId: string; paymentId: string; orderId: string;
79
+ subscriptionId: string | null; subscriptionCycleId: string | null; provider: string;
80
+ providerPaymentId: string | null; providerAttemptId: string | null; providerSessionId: string | null;
81
+ attemptStatus: string; intentStatus: string; amountMinor: number; currency: string;
82
+ orderMode: "one_time" | "subscription_cycle"; cycleRetryAttempt: number; cycleNextRetryAt: string | null;
83
+ localUpdatedAt: string;
84
+ }
85
+ export type ProviderRecoveryAction = { kind: "redirect"; url: string };
86
+ export interface PaymentProviderRecoveryProvider {
87
+ resolvePaymentReference(input: { providerAttemptId: string | null; intentProviderPaymentId: string | null;
88
+ providerSessionId: string | null }): string | null;
89
+ readPayment(input: { providerPaymentId: string; attempt?: ClaimedPaymentAttempt;
90
+ purpose?: ProviderReadPurpose }): Promise<ProviderReconciliationStatus>;
91
+ readRecoveryPayment(input: { providerPaymentId: string; attempt: Pick<ClaimedPaymentAttempt, "amountMinor" | "currency">;
92
+ purpose?: ProviderReadPurpose }): Promise<{ status: ProviderReconciliationStatus; identityMatches: boolean;
93
+ configuredMoneyMatches: boolean; manualReviewRequired: boolean; clientAction: ProviderRecoveryAction | null }>;
94
+ closePayment?(input: { providerPaymentId: string }): Promise<ProviderReconciliationStatus>;
95
+ findPaymentByLocalIntent?(input: { attempt: ClaimedPaymentAttempt }): Promise<"found" | "absent">;
96
+ }
97
+
98
+ export interface TpayTransientProviderInput {
99
+ provider: "tpay";
100
+ flow: "blik_one_time" | "blik_one_click" | "pbl_one_time" | "blik_recurring_activation" | "recurring_charge";
101
+ blikToken?: string;
102
+ channelId?: string;
103
+ recurringModel?: "O" | "M";
104
+ }