@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,293 @@
1
+ import { createHash, X509Certificate } from "node:crypto";
2
+ import { classifyPaymentFailure, isPaymentRetryAdviceCode } from "@openlup/core/payment";
3
+ import type { PaymentAdapter, PaymentAttemptPort, PaymentExecutionEnvelope,
4
+ PaymentReadbackEnvelope, PaymentRefusalEnvelope, PaymentVerifyNormalizePort,
5
+ VerifiedMethodEvidence, VerifiedPaymentEvidence } from "../contracts/index.js";
6
+ import type { PaymentExecutionInput, PaymentExecutionResult } from "./donorContracts.js";
7
+ import { tpayPaymentCapabilities } from "./capabilities.js";
8
+ export { tpayPaymentCapabilities } from "./capabilities.js";
9
+ import { createTpayHttpClient } from "./tpayHttpClient.js";
10
+ import { createTpayPaymentExecutionAdapter } from "./tpayPaymentExecutionAdapter.js";
11
+ import { ProviderAttemptPreDispatchError } from "./preparedProviderAttempt.js";
12
+ import { createTpayPaymentReconciliationProvider } from "./tpayPaymentReconciliationProvider.js";
13
+ import { verifyTpayJwsSignature, type TpayJwsCertificateResolver } from "./webhookJwsVerifier.js";
14
+ import { normalizeTpayWebhook, toNormalizedProviderTpayWebhook } from "./webhookNormalizer.js";
15
+
16
+ /** Explicit host configuration. clock returns UTC seconds; imports perform no I/O. */
17
+ export interface TpayPaymentAdapterConfig {
18
+ http: { baseUrl: string; clientId: string; clientSecret: string; fetchImpl: (url: string, init?: {
19
+ method?: string; headers?: Record<string, string>; body?: string; signal?: AbortSignal;
20
+ }) => Promise<{ ok: boolean; status: number; json(): Promise<unknown>; text(): Promise<string> }>; timeoutMs?: number };
21
+ notification: {
22
+ rootCertificatePem: string;
23
+ /** HTTPS certificate directory ending in a slash; resolver must enforce redirect/egress policy. */
24
+ allowedCertificateUrlPrefix: string;
25
+ resolveCertificate: TpayJwsCertificateResolver;
26
+ /** Seven days by default: generous retry allowance assumed until provider schedule is established. */
27
+ maxNotificationAgeSeconds?: number;
28
+ /** Maximum future signed issue time relative to receipt; defaults to 300 seconds. */
29
+ maxClockSkewSeconds?: number;
30
+ };
31
+ urls: { notificationUrl: string; successUrl: string; errorUrl: string };
32
+ modelOActivationEnabled: boolean;
33
+ mode: "live" | "test";
34
+ clock?: () => number;
35
+ }
36
+
37
+
38
+ type Verification = Awaited<ReturnType<PaymentVerifyNormalizePort["verifyAndNormalize"]>>;
39
+ const refusal = (reason: Extract<Verification, { kind: "refusal" }>["reason"]): Verification => ({ kind: "refusal", reason });
40
+
41
+ /** Construction explicitly selects this adapter; the host owns account activation and I/O policy. */
42
+ export function createTpayPaymentAdapter(config: TpayPaymentAdapterConfig): PaymentAdapter {
43
+ const capabilities = tpayPaymentCapabilities;
44
+ const notification = config.notification;
45
+ if (!notification?.rootCertificatePem || typeof notification.resolveCertificate !== "function") throw new Error("Required certificate configuration missing");
46
+ const prefix = new URL(notification.allowedCertificateUrlPrefix);
47
+ if (prefix.protocol !== "https:" || prefix.username || prefix.password || prefix.search || prefix.hash
48
+ || !notification.allowedCertificateUrlPrefix.endsWith("/")) throw new Error("Invalid certificate URL prefix");
49
+ const root = new X509Certificate(notification.rootCertificatePem);
50
+ const maxAge = notification.maxNotificationAgeSeconds ?? 7 * 24 * 60 * 60;
51
+ const maxClockSkew = notification.maxClockSkewSeconds ?? 300;
52
+ if (!Number.isFinite(maxAge) || maxAge < 0 || !Number.isFinite(maxClockSkew) || maxClockSkew < 0 || !["test", "live"].includes(config.mode)
53
+ || typeof config.http.fetchImpl !== "function" || !config.http.clientId || !config.http.clientSecret
54
+ || typeof config.modelOActivationEnabled !== "boolean") throw new Error("Invalid payment configuration");
55
+ for (const url of [config.http.baseUrl, ...Object.values(config.urls)]) {
56
+ const parsed = new URL(url);
57
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password) throw new Error("Invalid payment URL");
58
+ }
59
+ const now = config.clock ?? (() => Date.now() / 1000);
60
+ const client = createTpayHttpClient({ ...config.http, now: () => now() * 1000 });
61
+ const execution = createTpayPaymentExecutionAdapter({ client, ...config.urls,
62
+ modelOActivationEnabled: config.modelOActivationEnabled });
63
+ const reconciliation = createTpayPaymentReconciliationProvider(client);
64
+ const validateHostInput = (input: Parameters<PaymentAttemptPort["execute"]>[0]): void => {
65
+ if (input.mode !== config.mode || !["initial", "recovery", "renewal-unattended"].includes(input.purpose)
66
+ || !capabilities.supportedCurrencies.includes(input.money.currency)) throw new Error("Unsupported payment input");
67
+ };
68
+ const donorInput = (input: Parameters<PaymentAttemptPort["execute"]>[0]): PaymentExecutionInput => {
69
+ if (!input.attemptRef || !input.intentRef || !input.fingerprint || !input.providerIdempotencyKey
70
+ || !Number.isSafeInteger(input.money.amountMinor) || input.money.amountMinor <= 0
71
+ || !input.payer?.email || (input.payer.name !== undefined && typeof input.payer.name !== "string")) {
72
+ throw new Error("Invalid payment input");
73
+ }
74
+ const renewal = input.purpose === "renewal-unattended";
75
+ const family = input.methodSelection?.family;
76
+ if (renewal ? !input.storedMethod?.reference || input.storedMethod.mandate.recurringModel !== "O"
77
+ || input.customerAuthorization !== undefined
78
+ : family !== "bank-redirect" && family !== "one-time-code") throw new Error("Unsupported payment flow");
79
+ if (!renewal && (family === "bank-redirect" ? !input.methodSelection?.channelRef || input.customerAuthorization !== undefined
80
+ : !input.customerAuthorization)) throw new Error("Required payment context missing");
81
+ return {
82
+ idempotencyKey: input.attemptRef, providerIdempotencyKey: input.providerIdempotencyKey,
83
+ providerRequestFingerprint: input.fingerprint, paymentIntentId: input.intentRef, orderRef: input.intentRef,
84
+ amountMinor: input.money.amountMinor, currency: input.money.currency,
85
+ mode: renewal ? "subscription_cycle" : "one_time",
86
+ payer: { email: input.payer.email, name: input.payer.name?.trim() ? input.payer.name : "Customer" },
87
+ ...(renewal ? { providerFlow: "recurring_charge", paymentMethodRef: input.storedMethod!.reference,
88
+ paymentMethodAliasType: "PAYID", paymentMethodRecurringModel: "O" } : {
89
+ transientProviderInput: family === "bank-redirect"
90
+ ? { provider: "tpay", flow: "pbl_one_time", channelId: input.methodSelection!.channelRef }
91
+ : { provider: "tpay", flow: "blik_one_time", blikToken: input.customerAuthorization },
92
+ }),
93
+ };
94
+ };
95
+ const validateInput = (input: Parameters<PaymentAttemptPort["execute"]>[0]): void => {
96
+ validateHostInput(input);
97
+ execution.validateInput?.(donorInput(input));
98
+ };
99
+ return {
100
+ capabilities,
101
+ attemptPort: {
102
+ validateInput,
103
+ async execute(input: Parameters<PaymentAttemptPort["execute"]>[0]): Promise<PaymentExecutionEnvelope> {
104
+ validateHostInput(input);
105
+ let prepared: PaymentExecutionInput;
106
+ try { prepared = donorInput(input); execution.validateInput?.(prepared); }
107
+ catch {
108
+ const reason: PaymentRefusalEnvelope = { code: "fix_and_retry_customer_action", hints: ["dataInvalid"] };
109
+ return { outcome: { kind: "decline", refusal: reason },
110
+ dispatch: { kind: "notDispatched", evidence: { exact: true, phase: "before-dispatch", reason } }, diagnostics: {} };
111
+ }
112
+ let result: PaymentExecutionResult;
113
+ try { result = await execution.execute(prepared); }
114
+ catch (error) {
115
+ if (!(error instanceof ProviderAttemptPreDispatchError)) throw error;
116
+ // A proven document refusal needs corrected data; other trusted no-charge failures can retry.
117
+ const hints: PaymentRefusalEnvelope["hints"] = error.phase === "response_decode"
118
+ && error.code === "tpay_request_refused" && error.dispatchState === "refused" ? ["dataInvalid"] : ["transient"];
119
+ const reason: PaymentRefusalEnvelope = { code: classifyPaymentFailure({ neutralReasonHints: hints }).failureClass, hints };
120
+ return { outcome: { kind: "decline", refusal: reason },
121
+ dispatch: { kind: "notDispatched", evidence: { exact: true, phase: "before-dispatch", reason } }, diagnostics: {} };
122
+ }
123
+ // Only these neutral facts cross the boundary; donor payloads and transient payer/authorization stay local.
124
+ return {
125
+ outcome: result.providerDecline ? { kind: "decline", refusal: neutralDecline(result.providerDecline) }
126
+ : { kind: "attempt", status: result.attemptStatus },
127
+ // API transaction ID is required for readback; notification titles remain distinct.
128
+ dispatch: result.providerSessionId ? { kind: "dispatched", providerPaymentRef: result.providerSessionId } : { kind: "unknown" },
129
+ ...(result.nextActionKind === "redirect" && result.redirectUrl
130
+ ? { handoff: { kind: "redirect" as const, url: result.redirectUrl } } : {}),
131
+ diagnostics: { webhookExpected: result.webhookExpected === true, recoveryRequired: result.recoveryRequired === true },
132
+ };
133
+ },
134
+ },
135
+ verifyPort: {
136
+ async verifyAndNormalize(input) {
137
+ const header = Object.entries(input.headers).find(([key]) => key.toLowerCase() === "x-jws-signature")?.[1];
138
+ if (typeof header !== "string") return refusal("signature");
139
+ const rawBody = Buffer.from(input.rawBody).toString("utf8");
140
+ if (!Buffer.from(rawBody).equals(Buffer.from(input.rawBody))) return refusal("malformed");
141
+ try {
142
+ const current = now() * 1000;
143
+ if (!validAt(root, current)) return refusal("signature");
144
+ const verified = await verifyTpayJwsSignature({ rawBody, signatureHeader: header,
145
+ rootCertificatePem: notification.rootCertificatePem, allowedX5uPrefix: notification.allowedCertificateUrlPrefix,
146
+ resolveCertificate: async url => {
147
+ const pem = await notification.resolveCertificate(url);
148
+ if (!validAt(new X509Certificate(pem), current)) throw new Error("Certificate outside validity window");
149
+ return pem;
150
+ } });
151
+ if (!verified) return refusal("signature");
152
+ } catch { return refusal("signature"); }
153
+ try {
154
+ // Only a signed protected-header issued-at assertion supplies notification time.
155
+ const protectedHeader = JSON.parse(Buffer.from(header.split(".")[0]!, "base64url").toString("utf8")) as { iat?: unknown };
156
+ const asserted = protectedHeader.iat;
157
+ if (asserted !== undefined && (typeof asserted !== "number" || !Number.isFinite(asserted)
158
+ || !Number.isFinite(new Date(asserted * 1000).getTime()))) return refusal("malformed");
159
+ const occurred = typeof asserted === "number" ? new Date(asserted * 1000).toISOString() : null;
160
+ if (occurred !== null) {
161
+ const received = Date.parse(input.receivedAt) / 1000;
162
+ if (!Number.isFinite(received)) return refusal("malformed");
163
+ if (received - (asserted as number) > maxAge || (asserted as number) - received > maxClockSkew) return refusal("stale");
164
+ }
165
+ return mapVerifiedTpayEvent(rawBody, occurred, config.mode, capabilities.supportedCurrencies);
166
+ } catch { return refusal("malformed"); }
167
+ },
168
+ },
169
+ readbackPort: {
170
+ async readPayment(input) {
171
+ const base = { identity: input.identity, expectedMoney: input.expectedMoney, purpose: input.purpose };
172
+ if (input.identity.provider !== "tpay" || !input.identity.providerPaymentRef) return { ...base, kind: "unsupported" };
173
+ try {
174
+ const reading = await reconciliation.readRecoveryPayment({
175
+ providerPaymentId: input.identity.providerPaymentRef, purpose: input.purpose, attempt: input.expectedMoney,
176
+ });
177
+ const observed = reading.status;
178
+ if (!reading.identityMatches || !reading.configuredMoneyMatches || reading.manualReviewRequired
179
+ || !Number.isSafeInteger(input.expectedMoney.amountMinor) || input.expectedMoney.amountMinor <= 0
180
+ || observed.status === "unknown") return unknown(base);
181
+ // Success and nonzero refused money must match; unpaid refusals retain the configured obligation.
182
+ if ((observed.status === "succeeded" || (observed.status === "failed" && observed.amountMinor !== null && observed.amountMinor !== 0))
183
+ && (observed.amountMinor !== input.expectedMoney.amountMinor
184
+ || observed.currency !== input.expectedMoney.currency.toUpperCase())) return unknown(base);
185
+ return { ...base, kind: observed.status, observedMoney: { amountMinor: input.expectedMoney.amountMinor,
186
+ currency: input.expectedMoney.currency.toUpperCase() },
187
+ providerOccurredAt: observed.occurredAt };
188
+ } catch { return unknown(base); }
189
+ },
190
+ },
191
+ };
192
+ }
193
+
194
+ function neutralDecline(decline: NonNullable<PaymentExecutionResult["providerDecline"]>): PaymentRefusalEnvelope {
195
+ const hints = decline.neutralReasonHints ?? [];
196
+ return { code: classifyPaymentFailure({ adviceCode: decline.adviceCode, neutralReasonHints: hints }).failureClass, hints,
197
+ ...(decline.adviceCode && isPaymentRetryAdviceCode(decline.adviceCode) ? { adviceCode: decline.adviceCode } : {}) };
198
+ }
199
+
200
+ function validAt(certificate: X509Certificate, instant: number): boolean {
201
+ return Number.isFinite(instant) && Date.parse(certificate.validFrom) <= instant && instant <= Date.parse(certificate.validTo);
202
+ }
203
+ function unknown(base: Pick<PaymentReadbackEnvelope, "identity" | "expectedMoney" | "purpose">): PaymentReadbackEnvelope {
204
+ return { ...base, kind: "unknown", reason: "indeterminate" };
205
+ }
206
+
207
+ /** All donor outcomes outside the current neutral union are refused here. */
208
+ function mapTpayOutcome(event: string): VerifiedPaymentEvidence["outcome"] | null {
209
+ switch (event) {
210
+ case "payment.succeeded": return "succeeded";
211
+ case "payment.failed": return "failed";
212
+ case "payment.requires_action": return "pending";
213
+ case "payment.refunded": return "refunded";
214
+ default: return null;
215
+ }
216
+ }
217
+ function mapVerifiedTpayEvent(rawBody: string, occurred: string | null, mode: "live" | "test", supportedCurrencies: readonly string[]): Verification {
218
+ const webhook = normalizeTpayWebhook(rawBody);
219
+ const event = webhook.event;
220
+ const payload = event.raw_payload;
221
+ if (payload.testMode !== null && payload.testMode !== undefined
222
+ && payload.testMode !== (mode === "test" ? "1" : "0")) return refusal("unsupported-event");
223
+ const identity = { provider: "tpay", providerOccurredAt: occurred };
224
+ let evidence: Omit<VerifiedPaymentEvidence, "fingerprint"> | Omit<VerifiedMethodEvidence, "fingerprint">;
225
+ if (payload.eventKind === "blik_alias") {
226
+ const kinds: Record<string, VerifiedMethodEvidence["outcome"]> = {
227
+ method_registered: "registered", method_updated: "updated", method_revoked: "revoked", method_expired: "expired",
228
+ };
229
+ const outcome = kinds[webhook.methodLifecycleKind ?? ""];
230
+ if (!outcome) return refusal("unsupported-event");
231
+ const lifecycle = occurred !== null ? toNormalizedProviderTpayWebhook(webhook, occurred).methodLifecycle : null;
232
+ evidence = { ...identity, kind: "method", outcome, providerEventId: `${event.provider_event_id}:${outcome}`,
233
+ methodRef: event.payment_provider_id, setupRef: null, intentRef: event.payment_intent_id ?? null,
234
+ ...(lifecycle ? { methodLifecycle: lifecycle } : {}) };
235
+ } else {
236
+ const outcome = mapTpayOutcome(event.event_type);
237
+ if (!outcome) return refusal("unsupported-event");
238
+ const amount = event.amount_minor;
239
+ const reportedCurrency = payload.currency;
240
+ if (reportedCurrency == null && supportedCurrencies.length !== 1) return refusal("malformed");
241
+ // Currency binds to the adapter's declared single supported currency because
242
+ // attempts in any other currency are refused before dispatch; this is not a silent default.
243
+ const currency = reportedCurrency ?? supportedCurrencies[0];
244
+ const money = Number.isSafeInteger(amount) && amount! > 0 && typeof currency === "string" && /^[A-Za-z]{3}$/u.test(currency)
245
+ ? { amountMinor: amount!, currency: currency.toUpperCase() } : null;
246
+ const originalAmount = typeof payload.transactionAmount === "string"
247
+ ? Math.round(Number(payload.transactionAmount.replace(",", ".")) * 100) : NaN;
248
+ if (outcome === "refunded" && (!money || !Number.isSafeInteger(originalAmount) || originalAmount <= 0)) return refusal("malformed");
249
+ evidence = { ...identity, kind: "money", providerEventId: `${event.payment_provider_id}:${outcome}`,
250
+ providerPaymentRef: event.payment_provider_id, intentRef: event.payment_intent_id ?? null, attemptRef: null, outcome, money,
251
+ ...(outcome === "refunded" && money ? { refund: { kind: "full-reversal" as const, reason: "chargeback" as const,
252
+ operationId: `${event.payment_provider_id}:chargeback`, originalPaymentMoney: { amountMinor: originalAmount, currency: money.currency } } } : {}),
253
+ };
254
+ }
255
+ return { ...evidence, fingerprint: createHash("sha256").update(JSON.stringify(evidence)).digest("hex"),
256
+ providerAuditPayload: payload };
257
+ }
258
+
259
+ /**
260
+ * @experimental Reference-application support surface; may change.
261
+ * Reference consumer: `server/adapters/tpay/tpayAdapterFactory.ts`.
262
+ */
263
+ export { createTpayPaymentExecutionAdapter } from "./tpayPaymentExecutionAdapter.js";
264
+
265
+ /**
266
+ * @experimental Reference-application support surface; may change.
267
+ * Reference consumer: `server/runtime/paymentProviderReadbackRegistry.ts`.
268
+ */
269
+ export { createTpayPaymentReconciliationProvider } from "./tpayPaymentReconciliationProvider.js";
270
+
271
+ /**
272
+ * @experimental Reference-application support surface; may change.
273
+ * Reference consumer: `server/bff/commerce/tpay-channels.ts`.
274
+ */
275
+ export { createTpayHttpClient } from "./tpayHttpClient.js";
276
+
277
+ /**
278
+ * @experimental Reference-application support surface; may change.
279
+ * Reference consumer: `server/adapters/paymentRecoveryGuidance.ts`.
280
+ */
281
+ export { normalizeTpayFailureEvidence } from "./tpayFailureEvidence.js";
282
+
283
+ /**
284
+ * @experimental Reference-application support surface; may change.
285
+ * Reference consumer: `server/adapters/supabase/payment/tpaySubscriptionActivation.ts`.
286
+ */
287
+ export { aliasValue } from "./tpayCreateTransactionInput.js";
288
+
289
+ /**
290
+ * @experimental Reference-application support surface; may change.
291
+ * Reference consumer: `server/shared/preparedProviderAttempt.ts`.
292
+ */
293
+ export { ProviderAttemptPreDispatchError } from "./preparedProviderAttempt.js";
@@ -0,0 +1,58 @@
1
+ import type { PaymentRecoveryMethod, PaymentRecoveryOperation } from "@openlup/core/payment";
2
+
3
+ export type FailureObservationSource = "execution" | "webhook" | "readback";
4
+ export type FailureObservationDisposition = "present" | "absent" | "unreadable" | "read_failed" | "read_timeout";
5
+
6
+ /** Protected provider facts, not a customer message or a retry-policy input. */
7
+ export interface PaymentFailureEvidence {
8
+ version: 1;
9
+ source: FailureObservationSource;
10
+ disposition: FailureObservationDisposition;
11
+ refusalVerified: boolean;
12
+ providerPaymentId: string | null;
13
+ providerChargeId: string | null;
14
+ code: string | null;
15
+ declineCode: string | null;
16
+ adviceCode: string | null;
17
+ adviceOrigin: "provider" | "inferred" | "unknown";
18
+ operation: PaymentRecoveryOperation | null;
19
+ method: PaymentRecoveryMethod | null;
20
+ methodSource: "provider" | "execution_contract" | "unknown";
21
+ }
22
+
23
+ /** Bounded machine tokens only. Free-form response fields never cross this seam. */
24
+ export function evidenceToken(value: unknown): string | null {
25
+ return typeof value === "string" && /^[a-zA-Z0-9_:.-]{1,96}$/.test(value) ? value : null;
26
+ }
27
+
28
+ export function evidenceRecord(value: unknown): Record<string, unknown> {
29
+ return value !== null && typeof value === "object" && !Array.isArray(value)
30
+ ? value as Record<string, unknown> : {};
31
+ }
32
+
33
+ /** Explicit construction prevents accidental payload expansion by object spreading. */
34
+ export function serializeFailureEvidence(input: PaymentFailureEvidence): PaymentFailureEvidence {
35
+ const method = input.method;
36
+ const kind = evidenceToken(method?.kind);
37
+ const recoveryMethodKey = evidenceToken(method?.recoveryMethodKey);
38
+ return {
39
+ version: 1,
40
+ source: input.source,
41
+ disposition: input.disposition,
42
+ refusalVerified: input.refusalVerified,
43
+ providerPaymentId: evidenceToken(input.providerPaymentId),
44
+ providerChargeId: evidenceToken(input.providerChargeId),
45
+ code: evidenceToken(input.code),
46
+ declineCode: evidenceToken(input.declineCode),
47
+ adviceCode: evidenceToken(input.adviceCode),
48
+ adviceOrigin: input.adviceOrigin,
49
+ operation: input.operation,
50
+ method: kind && recoveryMethodKey && method ? { kind, recoveryMethodKey, interaction: method.interaction } : null,
51
+ methodSource: kind && recoveryMethodKey ? input.methodSource : "unknown",
52
+ };
53
+ }
54
+
55
+ /** Persist beside legacy fields; never merge the diagnostic codes into them. */
56
+ export function failureEvidencePayload(evidence: PaymentFailureEvidence | null | undefined): Record<string, unknown> {
57
+ return evidence ? { failureEvidence: serializeFailureEvidence(evidence) } : {};
58
+ }
@@ -0,0 +1,38 @@
1
+ export interface TrustedProviderAttemptPreDispatchFailure {
2
+ phase: "oauth" | "transaction_dispatch";
3
+ code: "tpay_oauth_failed" | "tpay_oauth_timeout" | "tpay_oauth_invalid_response" | "tpay_request_encode_failed" | "tpay_request_deadline_exhausted";
4
+ dispatchState: "not_dispatched";
5
+ }
6
+
7
+ /** Either proof the provider holds no transaction: the POST was never invoked, or it was and the
8
+ * provider refused the request document. Two closed shapes, so neither absorbs the other. */
9
+ export type TrustedProviderAttemptNoChargeFailure = TrustedProviderAttemptPreDispatchFailure | { phase: "response_decode"; code: "tpay_request_refused"; dispatchState: "refused" };
10
+
11
+ export class ProviderAttemptPreDispatchError extends Error {
12
+ readonly phase: TrustedProviderAttemptNoChargeFailure["phase"];
13
+ readonly code: TrustedProviderAttemptNoChargeFailure["code"];
14
+ readonly dispatchState: TrustedProviderAttemptNoChargeFailure["dispatchState"];
15
+ paymentAttemptId: string | null;
16
+ retryCount = 0;
17
+
18
+ constructor(failure: TrustedProviderAttemptNoChargeFailure, paymentAttemptId: string | null = null) {
19
+ const kind = failure.dispatchState === "refused" ? "provider_refused_pre_transaction" : "provider_pre_dispatch_failed";
20
+ super(`${kind}:${failure.phase}:${failure.code}`);
21
+ this.name = "ProviderAttemptPreDispatchError";
22
+ this.phase = failure.phase;
23
+ this.code = failure.code;
24
+ this.dispatchState = failure.dispatchState;
25
+ this.paymentAttemptId = paymentAttemptId;
26
+ }
27
+
28
+ withPaymentAttempt(paymentAttemptId: string): this {
29
+ this.paymentAttemptId = paymentAttemptId;
30
+ return this;
31
+ }
32
+
33
+ withRetryCount(retryCount: number): this {
34
+ this.retryCount = retryCount;
35
+ return this;
36
+ }
37
+ }
38
+
@@ -0,0 +1,99 @@
1
+ const TPAY_REQUEST_ID = /^[a-f0-9]{20}$/;
2
+ const LOGGABLE_PROVIDER_ERROR_CODES = new Set([
3
+ "bank_group_does_not_exist",
4
+ "blik_token_not_present",
5
+ "cannot_create_transaction",
6
+ "channel_passed_with_group",
7
+ "channel_unavailable",
8
+ "channel_unavailable_for_source",
9
+ "configuration_error",
10
+ "country_not_valid",
11
+ "crc_not_unique",
12
+ "field_required",
13
+ "general",
14
+ "incorrect_content_type",
15
+ "internal_server_error",
16
+ "invalid_request_body",
17
+ "is_required",
18
+ "no_payment_data",
19
+ "not_array",
20
+ "not_natural_int",
21
+ "not_string",
22
+ "not_valid",
23
+ "partner_cannot_create_transaction",
24
+ "payment_failed",
25
+ "too_low",
26
+ "transaction_blocked",
27
+ "transaction_lock",
28
+ "ts:channel:amount_outside_range",
29
+ "ts:channel:both_channel_and_group_specified",
30
+ "ts:channel:channel_blocked",
31
+ "ts:channel:channel_disabled",
32
+ "ts:channel:channel_disabled_for_pos",
33
+ "ts:channel:collect_prohibited",
34
+ "ts:channel:group_disabled_for_pos",
35
+ "ts:channel:not_available",
36
+ "ts:channel:not_found",
37
+ "ts:channel:source_prohibited",
38
+ "ts:channel:surcharge_prohibited",
39
+ "ts:country:not_valid",
40
+ "ts:merchant:disabled",
41
+ "ts:merchant:is_partner",
42
+ "ts:merchant:not_found",
43
+ "ts:payer:phone:not_valid",
44
+ "ts:pos:not_found",
45
+ "ts:pos:result_url_not_overridable",
46
+ "ts:request:no_ip",
47
+ "ts:transaction:not_inserted",
48
+ "ts:transaction:payer_data_not_inserted",
49
+ "ts:transaction:payer_info_not_inserted",
50
+ "unexpected_field",
51
+ "unsupported_blik_token",
52
+ "url_override_is_disabled",
53
+ "wrong_alias",
54
+ "wrong_autopayment_alias",
55
+ "wrong_model_data_combination",
56
+ ]);
57
+ const LOGGABLE_PROVIDER_FIELD_NAMES = new Set([
58
+ "amount",
59
+ "currency",
60
+ "description",
61
+ "hiddenDescription",
62
+ "payer.email",
63
+ "payer.name",
64
+ "payer.phone",
65
+ "payer.address",
66
+ "payer.city",
67
+ "payer.country",
68
+ "payer.postalCode",
69
+ // This list carries FIELD NAMES ONLY - `ProviderAttemptFailureDiagnostic` has
70
+ // no field that holds a provider value - so these two entries log the literal
71
+ // strings, never a user agent or an IP address. Their absence is why the
72
+ // 2026-08-28 BLIK outage logged `fieldNames: []` and stayed unattributed for
73
+ // days: `readTpayHttpFailure` filters Tpay's returned `fieldName` through this
74
+ // set, so an unlisted name is silently dropped.
75
+ "payer.ip",
76
+ "payer.userAgent",
77
+ "pay.groupId",
78
+ "pay.channelId",
79
+ "pay.blikPaymentData.blikToken",
80
+ "pay.blikPaymentData.type",
81
+ "pay.blikPaymentData.aliases.value",
82
+ "pay.blikPaymentData.aliases.type",
83
+ "callbacks.notification.url",
84
+ "callbacks.payerUrls.success",
85
+ "callbacks.payerUrls.error",
86
+ ]);
87
+
88
+ export function loggableProviderRequestId(value: unknown): value is string {
89
+ return typeof value === "string" && TPAY_REQUEST_ID.test(value);
90
+ }
91
+
92
+ export function loggableProviderErrorCode(value: unknown): value is string {
93
+ return typeof value === "string" && LOGGABLE_PROVIDER_ERROR_CODES.has(value);
94
+ }
95
+
96
+ export function loggableProviderFieldName(value: unknown): value is string {
97
+ return typeof value === "string" && LOGGABLE_PROVIDER_FIELD_NAMES.has(value);
98
+ }
99
+
@@ -0,0 +1,158 @@
1
+ import type { PaymentExecutionInput, TpayTransientProviderInput } from "./donorContracts.js";
2
+ import type {
3
+ TpayAliasInput,
4
+ TpayPayerInput,
5
+ TpayTransactionCreateInput,
6
+ } from "./tpayHttpClient.js";
7
+
8
+ const TPAY_BLIK_GROUP_ID = 150;
9
+
10
+ /**
11
+ * In-shell account return path (account terminal, localized client-side). When the
12
+ * checkout declares `returnContext: "account"`, the Tpay redirect/return targets
13
+ * this in-account terminal instead of the public `/checkout/payment`
14
+ * page — the origin is preserved, only the path is swapped. Mirrors how the
15
+ * public path is selected server-side and re-localized on the client.
16
+ */
17
+ const ACCOUNT_RETURN_PATH = "/account/order/status";
18
+
19
+ /**
20
+ * Swap the path of an origin-derived return URL to the in-account terminal while
21
+ * preserving the env/origin-driven host. `returnContext` defaults to public, so a
22
+ * caller that omits it gets the byte-identical public URL.
23
+ */
24
+ function returnUrlForContext(baseUrl: string, returnContext: "public" | "account" | undefined): string {
25
+ if (returnContext !== "account") return baseUrl;
26
+ const url = new URL(baseUrl);
27
+ url.pathname = ACCOUNT_RETURN_PATH;
28
+ return url.toString();
29
+ }
30
+
31
+ export function buildCreateTransactionInput(input: {
32
+ input: PaymentExecutionInput;
33
+ transient: TpayTransientProviderInput;
34
+ payer: TpayPayerInput;
35
+ notificationUrl: string;
36
+ successUrl: string;
37
+ errorUrl: string;
38
+ aliasRegistrationOnCodeEnabled: boolean;
39
+ modelOActivationEnabled: boolean;
40
+ }): TpayTransactionCreateInput {
41
+ const { transient } = input;
42
+ const effectiveModel = resolveRecurringModel(transient.recurringModel, input.modelOActivationEnabled);
43
+ const shouldRegisterOneTimeAlias =
44
+ transient.flow === "blik_one_time" && input.aliasRegistrationOnCodeEnabled;
45
+ const returnContext = input.input.returnContext;
46
+ const successUrl = returnUrlForContext(input.successUrl, returnContext);
47
+ const errorUrl = returnUrlForContext(input.errorUrl, returnContext);
48
+ return {
49
+ amount: input.input.amountMinor / 100,
50
+ currency: "PLN",
51
+ description: input.input.orderRef,
52
+ hiddenDescription: input.input.paymentIntentId,
53
+ payer: input.payer,
54
+ notificationUrl: input.notificationUrl,
55
+ successUrl: payerReturnUrl(successUrl, input.input),
56
+ errorUrl: payerReturnUrl(errorUrl, input.input),
57
+ groupId: transient.flow === "pbl_one_time" ? undefined : TPAY_BLIK_GROUP_ID,
58
+ channelId: transient.flow === "pbl_one_time" ? transient.channelId : undefined,
59
+ blikPaymentType: transient.flow === "recurring_charge" ||
60
+ (transient.flow === "blik_one_click" && (input.input.paymentMethodAliasType ?? "PAYID") === "PAYID")
61
+ ? 2
62
+ : undefined,
63
+ blikToken: transient.flow === "blik_one_time" || transient.flow === "blik_recurring_activation"
64
+ ? transient.blikToken
65
+ : undefined,
66
+ // Only meaningful while registering a PAYID mandate: it makes BLIK refuse an
67
+ // incapable bank outright instead of taking the money and silently never
68
+ // showing the mandate invitation. Never set for a plain one-off BLIK.
69
+ refuseNoPayId: transient.flow === "blik_recurring_activation" && effectiveModel === "O"
70
+ ? true
71
+ : undefined,
72
+ alias: transient.flow === "blik_recurring_activation"
73
+ ? {
74
+ value: aliasValue(input.input.paymentIntentId),
75
+ type: "PAYID",
76
+ label: "openlup",
77
+ ...(effectiveModel === "O" ? { recommendedAuthLevel: "NOCONFREQ" as const } : {}),
78
+ autopayment: { model: effectiveModel },
79
+ }
80
+ : shouldRegisterOneTimeAlias
81
+ ? { value: aliasValue(input.input.paymentIntentId), type: "UID", label: "openlup" }
82
+ : (transient.flow === "recurring_charge" || transient.flow === "blik_one_click") && input.input.paymentMethodRef
83
+ ? chargeAlias(
84
+ input.input.paymentMethodRef,
85
+ input.input.paymentMethodAliasType ?? "PAYID",
86
+ input.input.paymentMethodRecurringModel,
87
+ )
88
+ : null,
89
+ simulatorContext: {
90
+ orderId: input.input.orderId,
91
+ paymentIntentId: input.input.paymentIntentId,
92
+ clientId: input.input.clientId,
93
+ providerAttemptKey: input.input.providerIdempotencyKey,
94
+ ...(returnContext === "account" ? { returnPath: ACCOUNT_RETURN_PATH } : {}),
95
+ },
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Alias sent when charging an already-registered mandate.
101
+ *
102
+ * `noDelay` and `recommendedAuthLevel` are autopayment fields: they belong to a
103
+ * `PAYID` mandate under model O and are meaningless on a `UID` one-click alias,
104
+ * which carries no autopayment agreement at all.
105
+ *
106
+ * Keyed on the model the MANDATE was registered under, never on the switch that
107
+ * admits new Model O activations. Production may already hold model M mandates;
108
+ * charging those as model O would both contradict the payer's consent and risk
109
+ * rejection. Unknown or absent means not-model-O — fail closed, never guess.
110
+ */
111
+ function chargeAlias(
112
+ value: string,
113
+ type: "UID" | "PAYID",
114
+ registeredModel: "O" | "M" | undefined,
115
+ ): TpayAliasInput {
116
+ const autopaymentCharge = registeredModel === "O" && type === "PAYID";
117
+ return {
118
+ value,
119
+ type,
120
+ // Needed on every model O charge, not only at registration: without them
121
+ // Tpay emails the payer an approval link instead of collecting, which is
122
+ // model M behaviour. Verified against production 2026-07-20.
123
+ ...(autopaymentCharge ? { noDelay: true, recommendedAuthLevel: "NOCONFREQ" as const } : {}),
124
+ };
125
+ }
126
+
127
+ /**
128
+ * The autopayment model actually sent to Tpay.
129
+ *
130
+ * Once the Model O activation switch is on, every recurring activation registers
131
+ * as model O: it is the only model that permits a variable amount without the
132
+ * payer confirming each charge, which is what a configurable subscription needs.
133
+ * Model M would silently degrade every renewal into an approval prompt.
134
+ *
135
+ * Single source of truth on purpose — the new-activation switch and the alias
136
+ * payload must never disagree about which model this activation attempt is.
137
+ */
138
+ export function resolveRecurringModel(
139
+ requested: "O" | "M" | undefined,
140
+ modelOActivationEnabled: boolean,
141
+ ): "O" | "M" {
142
+ if (modelOActivationEnabled) return "O";
143
+ return requested ?? "M";
144
+ }
145
+
146
+ function payerReturnUrl(baseUrl: string, input: PaymentExecutionInput): string {
147
+ const url = new URL(baseUrl);
148
+ if (input.orderId) url.searchParams.set("orderId", input.orderId);
149
+ url.searchParams.set("paymentIntentId", input.paymentIntentId);
150
+ if (input.clientId) url.searchParams.set("clientId", input.clientId);
151
+ if (input.returnContext === "account" && input.subjectId) url.searchParams.set("subject", input.subjectId);
152
+ url.searchParams.set("order", input.orderRef);
153
+ return url.toString();
154
+ }
155
+
156
+ export function aliasValue(paymentIntentId: string): string {
157
+ return `openlup_${paymentIntentId}`;
158
+ }