@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,244 @@
1
+ import { refusalCapture, tpayFailureEvidence } from "./tpayFailureEvidence.js";
2
+ import { failureEvidencePayload } from "./paymentFailureEvidence.js";
3
+ import { parseInstant, wallClockToUtc } from "./wallClockTime.js";
4
+ import { declineCodeReading } from "./declineFailureHints.js";
5
+ import { attemptDeclineObservation, lastRefusalRow } from "./tpayDeclineEvidence.js";
6
+ export function createTpayPaymentReconciliationProvider(client) {
7
+ return {
8
+ resolvePaymentReference(input) {
9
+ // Tpay's API address is its transactionId. `title`/TR-* is merchant
10
+ // display data, not a readback identifier.
11
+ return input.providerSessionId;
12
+ },
13
+ async readRecoveryPayment(input) {
14
+ const transaction = await client.getTransaction(input.providerPaymentId);
15
+ const status = normalizeTpayTransaction(transaction);
16
+ const identityMatches = tpayIdentityMatches(transaction, input.providerPaymentId);
17
+ const configuredMoneyMatches = tpayConfiguredMoneyMatches(transaction, input.attempt);
18
+ const manualReviewRequired = tpayMoneyMoved(transaction);
19
+ let clientAction = null;
20
+ if (identityMatches && !manualReviewRequired && status.status === "pending") {
21
+ const url = validatedTpayPaymentUrl(transaction);
22
+ clientAction = url ? { kind: "redirect", url } : null;
23
+ }
24
+ return { status, identityMatches, configuredMoneyMatches, manualReviewRequired, clientAction };
25
+ },
26
+ async readPayment(input) {
27
+ const transaction = await client.getTransaction(input.providerPaymentId);
28
+ return normalizeTpayTransaction(transaction);
29
+ },
30
+ };
31
+ }
32
+ function tpayMoneyMoved(transaction) {
33
+ const status = readStatus(transaction).trim().toLowerCase();
34
+ return status === "chargeback" || status === "refunded";
35
+ }
36
+ function tpayIdentityMatches(transaction, providerPaymentId) {
37
+ const actual = readOptionalString(transaction, "transactionId") ?? readOptionalString(transaction, "id");
38
+ return actual === providerPaymentId;
39
+ }
40
+ function tpayConfiguredMoneyMatches(transaction, attempt) {
41
+ return readConfiguredAmountMinor(transaction) === attempt.amountMinor
42
+ && readCurrency(transaction)?.toUpperCase() === attempt.currency.toUpperCase();
43
+ }
44
+ const TPAY_PAYMENT_HOSTS = new Set(["secure.tpay.com", "secure.sandbox.tpay.com"]);
45
+ function validatedTpayPaymentUrl(transaction) {
46
+ const raw = transaction.transactionPaymentUrl;
47
+ if (typeof raw !== "string" || raw.length === 0)
48
+ return null;
49
+ try {
50
+ const parsed = new URL(raw);
51
+ return parsed.protocol === "https:"
52
+ && parsed.port === ""
53
+ && parsed.username === ""
54
+ && parsed.password === ""
55
+ && TPAY_PAYMENT_HOSTS.has(parsed.hostname.toLowerCase())
56
+ ? parsed.toString()
57
+ : null;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ export function normalizeTpayTransaction(transaction) {
64
+ const providerStatus = readStatus(transaction);
65
+ const statusFromField = normalizeTpayStatus(providerStatus);
66
+ // This rail leaves its status field non-terminal when the bank refuses, reporting
67
+ // the reason beside it in TWO carriers instead — per attempt, and in an errors
68
+ // array — so reading only the status field left a refused charge polling forever
69
+ // (what stranded a renewal on 2026-08-12). Attempts win when both carry a code:
70
+ // being the dated carrier, they are the only one that can say which is current.
71
+ const payments = record(transaction.payments);
72
+ const refusal = statusFromField !== "succeeded"
73
+ ? lastRefusal(payments.attempts, "paymentErrorCode") ?? lastRefusal(payments.errors, "errorCode")
74
+ : null;
75
+ const status = refusal ? "failed" : statusFromField;
76
+ return {
77
+ status,
78
+ providerStatus,
79
+ occurredAt: refusal?.occurredAt ?? readDate(transaction),
80
+ failureReason: refusal
81
+ ? `tpay_decline_${refusal.code}`
82
+ : status === "failed" ? `tpay_${providerStatus}` : null,
83
+ amountMinor: readAmountMinor(transaction),
84
+ currency: readCurrency(transaction),
85
+ rawPayload: sanitizedTpayTransaction(transaction, providerStatus, refusal),
86
+ };
87
+ }
88
+ /**
89
+ * The last row carrying a refusal code, dated and read. The scan is shared with
90
+ * the execution path, which needs the same "which code does this stand on"
91
+ * answer the instant the provider refuses; only the date and the reading are
92
+ * added. Undated rows yield no instant, so the caller uses the charge's own date.
93
+ */
94
+ function lastRefusal(rows, codeKey) {
95
+ const refusal = lastRefusalRow(rows, codeKey);
96
+ if (!refusal)
97
+ return null;
98
+ return {
99
+ code: refusal.code,
100
+ occurredAt: parseAttemptDate(refusal.row.date),
101
+ reading: declineCodeReading(refusal.code),
102
+ };
103
+ }
104
+ /**
105
+ * This rail labels no timestamp with a zone, on either carrier, and both are its
106
+ * own local wall clock. Reading them as UTC moved every instant this adapter
107
+ * produced one or two hours into the future: on 2026-08-15 a decline that landed
108
+ * 14:46Z was recorded as 16:46Z, later than the cron run that wrote it. The zone
109
+ * belongs to the rail, not to the merchant, so it is named here — beside the
110
+ * carriers it explains — and passed to the generic conversion.
111
+ */
112
+ const REPORTING_TIME_ZONE = "Europe/Warsaw";
113
+ /** Attempt timestamps arrive as `DD.MM.YYYY HH:mm`, which `Date.parse` misreads. */
114
+ function parseAttemptDate(value) {
115
+ if (typeof value !== "string")
116
+ return null;
117
+ const match = /^(\d{2})\.(\d{2})\.(\d{4})[ T](\d{2}):(\d{2})/.exec(value.trim());
118
+ if (!match)
119
+ return parseDate(value);
120
+ const [, day, month, year, hour, minute] = match;
121
+ return wallClockToUtc(`${year}-${month}-${day}T${hour}:${minute}:00`, REPORTING_TIME_ZONE);
122
+ }
123
+ function normalizeTpayStatus(status) {
124
+ const normalized = status.trim().toLowerCase();
125
+ if (["correct", "paid", "true", "success", "settled"].includes(normalized))
126
+ return "succeeded";
127
+ if ([
128
+ "failed",
129
+ "failure",
130
+ "error",
131
+ "false",
132
+ "declined",
133
+ "rejected",
134
+ "cancelled",
135
+ "canceled",
136
+ "expired",
137
+ ].includes(normalized)) {
138
+ return "failed";
139
+ }
140
+ if ([
141
+ "new",
142
+ "pending",
143
+ "created",
144
+ "processing",
145
+ "in_progress",
146
+ "authorized",
147
+ "requires_action",
148
+ "pending_confirmation",
149
+ ].includes(normalized)) {
150
+ return "pending";
151
+ }
152
+ return "unknown";
153
+ }
154
+ function readStatus(transaction) {
155
+ for (const key of ["status", "tr_status", "transactionStatus", "state"]) {
156
+ const raw = transaction[key];
157
+ if (typeof raw === "string" && raw.trim())
158
+ return raw.trim();
159
+ if (typeof raw === "boolean")
160
+ return raw ? "true" : "false";
161
+ }
162
+ return "unknown";
163
+ }
164
+ function readDate(transaction) {
165
+ const nestedDate = record(transaction.date);
166
+ for (const key of ["realization", "creation"]) {
167
+ const parsed = parseDate(nestedDate[key]);
168
+ if (parsed)
169
+ return parsed;
170
+ }
171
+ for (const key of ["date", "updatedAt", "updated_at", "createdAt", "created_at"]) {
172
+ const parsed = parseDate(transaction[key]);
173
+ if (parsed)
174
+ return parsed;
175
+ }
176
+ return null;
177
+ }
178
+ /** The transaction carrier sends `YYYY-MM-DD HH:mm:ss` — zone-less, so read in the rail's zone. */
179
+ function parseDate(value) {
180
+ return parseInstant(value, REPORTING_TIME_ZONE);
181
+ }
182
+ function readAmountMinor(transaction) {
183
+ return readAmountMinorFrom(transaction, ["amountMinor", "paidAmountMinor", "tr_amount_minor", "tr_paid_minor"], ["paidAmount", "amount", "tr_paid", "tr_amount"]);
184
+ }
185
+ function readConfiguredAmountMinor(transaction) {
186
+ return readAmountMinorFrom(transaction, ["amountMinor", "tr_amount_minor"], ["amount", "tr_amount"]);
187
+ }
188
+ function readAmountMinorFrom(transaction, minorKeys, majorKeys) {
189
+ for (const key of minorKeys) {
190
+ const raw = transaction[key];
191
+ if (typeof raw === "number" && Number.isFinite(raw))
192
+ return Math.round(raw);
193
+ if (typeof raw === "string" && /^\d+$/.test(raw.trim()))
194
+ return Number(raw);
195
+ }
196
+ for (const key of majorKeys) {
197
+ const raw = transaction[key];
198
+ const value = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw.replace(",", ".")) : NaN;
199
+ if (Number.isFinite(value))
200
+ return Math.round(value * 100);
201
+ }
202
+ return null;
203
+ }
204
+ function readCurrency(transaction) {
205
+ for (const key of ["currency", "tr_currency"]) {
206
+ const raw = transaction[key];
207
+ if (typeof raw === "string" && raw.trim().length === 3)
208
+ return raw.trim().toUpperCase();
209
+ }
210
+ return null;
211
+ }
212
+ /**
213
+ * Classification goes through the shared `classifyDecline` seam, the same one
214
+ * the synchronous path and the card rail use, so there is exactly one place a
215
+ * refusal becomes a class. Only neutral evidence crosses: this provider's code
216
+ * vocabulary is translated here and travels no further. Since PR 2724 the class
217
+ * reaches the case and becomes the payer's cause sentence; it is no cadence input.
218
+ */
219
+ function sanitizedTpayTransaction(transaction, providerStatus, refusal = null) {
220
+ return {
221
+ // Before this, the only record of why a charge failed was discarded on
222
+ // every poll.
223
+ ...(refusal ? refusalCapture(refusal, `tpay_decline_${refusal.code}`) : {}),
224
+ ...failureEvidencePayload(refusal || ["failed", "failure", "error", "false", "declined", "rejected"].includes(providerStatus.trim().toLowerCase()) ? tpayFailureEvidence({
225
+ source: "readback", providerPaymentId: readOptionalString(transaction, "transactionId"),
226
+ refusalVerified: true, flow: null,
227
+ observation: refusal ? { code: refusal.code, disposition: "present" } : attemptDeclineObservation(transaction), reading: refusal?.reading,
228
+ }) : null),
229
+ provider: "tpay",
230
+ transactionId: readOptionalString(transaction, "transactionId") ?? readOptionalString(transaction, "id"),
231
+ title: readOptionalString(transaction, "title") ?? readOptionalString(transaction, "tr_id"),
232
+ status: providerStatus,
233
+ requestIdPresent: Boolean(readOptionalString(transaction, "requestId")),
234
+ amountMinorPresent: readAmountMinor(transaction) !== null,
235
+ currency: readCurrency(transaction),
236
+ };
237
+ }
238
+ function readOptionalString(value, key) {
239
+ const raw = value[key];
240
+ return typeof raw === "string" && raw.trim() ? raw.trim() : null;
241
+ }
242
+ function record(value) {
243
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
244
+ }
@@ -0,0 +1,120 @@
1
+ export interface TpayPayerInput {
2
+ email: string;
3
+ name: string;
4
+ ip?: string | null;
5
+ userAgent?: string | null;
6
+ }
7
+ /**
8
+ * BLIK alias sent on transaction create.
9
+ *
10
+ * `UID` is merchant-scoped one-click; `PAYID` is the recurring mandate.
11
+ * `recommendedAuthLevel` is a SIBLING of `autopayment`, not a field inside it
12
+ * (Tpay SDK `Model/Objects/Transactions/Alias.php`), and Tpay documents it as
13
+ * available and required only for autopayment model `O`, where it omits the
14
+ * per-charge authorization screen.
15
+ */
16
+ export interface TpayAliasInput {
17
+ value: string;
18
+ type: "UID" | "PAYID";
19
+ label?: string;
20
+ recommendedAuthLevel?: "NOCONFREQ";
21
+ /**
22
+ * Collect immediately instead of giving the payer 72 hours to approve. Required
23
+ * on every model `O` charge; available for models `A` and `O` only.
24
+ */
25
+ noDelay?: boolean;
26
+ /**
27
+ * `frequency` is deliberately absent: Tpay requires it for model `A`, treats it
28
+ * as optional for `M`, and does not accept it for `O`. We only ever register
29
+ * variable-amount mandates, so no caller needs it.
30
+ */
31
+ autopayment?: {
32
+ model: "A" | "M" | "O";
33
+ };
34
+ }
35
+ export interface TpayTransactionCreateInput {
36
+ amount: number;
37
+ currency: "PLN";
38
+ description: string;
39
+ hiddenDescription: string;
40
+ payer: TpayPayerInput;
41
+ notificationUrl: string;
42
+ successUrl: string;
43
+ errorUrl: string;
44
+ groupId?: number;
45
+ channelId?: string;
46
+ blikPaymentType?: 2;
47
+ blikToken?: string;
48
+ /**
49
+ * Ask BLIK to reject the transaction outright when the payer's bank does not
50
+ * support recurring payments, instead of taking the money and silently never
51
+ * showing the mandate invitation. Only meaningful when registering a `PAYID`
52
+ * alias; never set it for a plain one-off BLIK payment.
53
+ */
54
+ refuseNoPayId?: boolean;
55
+ alias?: TpayAliasInput | null;
56
+ simulatorContext?: {
57
+ orderId?: string;
58
+ paymentIntentId: string;
59
+ clientId?: string;
60
+ /** Stable per provider attempt; simulator-only and never sent to Tpay. */
61
+ providerAttemptKey?: string;
62
+ /**
63
+ * In-account terminal path the simulator panel returns to after the buyer
64
+ * settles (account `returnContext` only). Absent for the public flow, which
65
+ * keeps the simulator page's own `/checkout/payment` return so the
66
+ * public path stays byte-identical (and client-side localized).
67
+ */
68
+ returnPath?: string;
69
+ };
70
+ }
71
+ /** One entry of Tpay's `payments.errors[]`. `errorCode` is low-cardinality. */
72
+ export interface TpayPaymentError {
73
+ errorCode: string;
74
+ errorMessage: string;
75
+ fieldName: string | null;
76
+ }
77
+ /**
78
+ * Create-transaction result.
79
+ *
80
+ * ⚠️ Tpay signals a synchronous BLIK rejection ONLY through `payments.errors[]`
81
+ * and the absence of `payments.payIdEligible`. Verified against production
82
+ * 2026-07-20: a rejected recurring BLIK still returns HTTP 200 with
83
+ * `result: "success"`, top-level `status: "pending"` AND `payments.status:
84
+ * "pending"`. Those three fields therefore prove nothing — never branch on them
85
+ * to decide success. `payments.method` is equally useless: it reads
86
+ * `"pay_by_link"` even for a settled BLIK PayID charge.
87
+ *
88
+ * This matters because Tpay has no async channel for failures — the transaction
89
+ * webhook's `tr_status` only ever carries `true` or `chargeback`, so a decline
90
+ * that is not read out of this response is never reported at all.
91
+ */
92
+ export interface TpayTransactionCreated {
93
+ transactionId: string;
94
+ title: string;
95
+ status: string;
96
+ transactionPaymentUrl: string | null;
97
+ requestId: string | null;
98
+ /**
99
+ * Whether BLIK considers the payer's bank capable of registering a PayID
100
+ * mandate. Present and `true` when eligible; absent entirely when Tpay
101
+ * rejected the transaction.
102
+ *
103
+ * ⚠️ UNDOCUMENTED — observed empirically, absent from Tpay's OpenAPI spec and
104
+ * from their official SDK. Treat as a hint that may vanish without notice and
105
+ * always keep `errors` as the fallback signal.
106
+ */
107
+ payIdEligible: boolean | null;
108
+ /** Synchronous provider errors. Empty when the request was accepted. */
109
+ errors: TpayPaymentError[];
110
+ }
111
+ /**
112
+ * Builds the `POST /transactions` wire payload.
113
+ *
114
+ * Every BLIK-specific field lives under `pay.blikPaymentData`; `aliases` carries
115
+ * the mandate. `refuseNoPayId` sits beside `aliases` (not inside it), while
116
+ * `recommendedAuthLevel` and `noDelay` sit inside the alias object as siblings of
117
+ * `autopayment` — matching Tpay's SDK object model and verified against
118
+ * production on 2026-07-20.
119
+ */
120
+ export declare function buildTpayTransactionPayload(input: TpayTransactionCreateInput): Record<string, unknown>;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Per-field maxima Tpay enforces on `payer.*`, read from the provider's own SDK
3
+ * rather than inferred. In `tpay-com/tpay-openapi-php`,
4
+ * `src/Model/Objects/Transactions/Payer.php` binds every accepted wire key to a
5
+ * field model, and each model declares the `$maxLength` the API applies:
6
+ *
7
+ * email `src/Model/Fields/Person/Email.php` 255
8
+ * name `src/Model/Fields/Payer/Name.php` 255
9
+ * ip `src/Model/Fields/Payer/IP.php` 255
10
+ * userAgent `src/Model/Fields/Payer/UserAgent.php` 255
11
+ *
12
+ * ⚠️ The unit is BYTES, not characters: `src/Model/Fields/FieldValidator.php`
13
+ * measures with PHP `strlen()`, which counts bytes. A 255-CHARACTER name in an
14
+ * accented or non-Latin script can be far more than 255 bytes in UTF-8 and would
15
+ * still be refused, so the clamp below counts bytes.
16
+ *
17
+ * An over-long field is refused synchronously with HTTP 400 `not_valid`. On BLIK
18
+ * the code then never reaches the bank and the buyer sees no prompt at all: the
19
+ * Facebook iOS in-app WebView user agent is 271 characters, which cost four
20
+ * orders and two cancelled subscriptions from 2026-08-28.
21
+ */
22
+ const TPAY_PAYER_MAX_BYTES = {
23
+ email: 255,
24
+ name: 255,
25
+ ip: 255,
26
+ userAgent: 255,
27
+ };
28
+ /**
29
+ * Fits one payer field inside the provider's documented maximum.
30
+ *
31
+ * ⛔ Truncate, never omit, and for every one of the four keys. `Field.php` runs
32
+ * a `pattern` check only for models that declare one and none of these four
33
+ * does, so a truncated value stays structurally valid; `getRequiredFields()`
34
+ * makes `email` and `name` mandatory, so dropping either only buys a different
35
+ * 400; and `ts:request:no_ip` is a real provider error, so dropping `ip` trades
36
+ * one refusal for another. Truncation also keeps the prefix that a fraud review
37
+ * or a support ticket can still read.
38
+ *
39
+ * Lives here, in the single function that serialises the wire body, so no payer
40
+ * producer can bypass it.
41
+ */
42
+ function payerFieldWithinLimit(key, value) {
43
+ const maxBytes = TPAY_PAYER_MAX_BYTES[key];
44
+ const bytes = new TextEncoder().encode(value);
45
+ if (bytes.length <= maxBytes)
46
+ return value;
47
+ let end = maxBytes;
48
+ // Never cut inside a code point: UTF-8 continuation bytes are 0b10xxxxxx, so
49
+ // walk back off them rather than emitting a replacement character.
50
+ while (end > 0 && (bytes[end] & 0xc0) === 0x80)
51
+ end -= 1;
52
+ return new TextDecoder().decode(bytes.subarray(0, end));
53
+ }
54
+ /**
55
+ * Builds the `POST /transactions` wire payload.
56
+ *
57
+ * Every BLIK-specific field lives under `pay.blikPaymentData`; `aliases` carries
58
+ * the mandate. `refuseNoPayId` sits beside `aliases` (not inside it), while
59
+ * `recommendedAuthLevel` and `noDelay` sit inside the alias object as siblings of
60
+ * `autopayment` — matching Tpay's SDK object model and verified against
61
+ * production on 2026-07-20.
62
+ */
63
+ export function buildTpayTransactionPayload(input) {
64
+ const pay = {};
65
+ if (input.groupId)
66
+ pay.groupId = input.groupId;
67
+ if (input.channelId)
68
+ pay.channelId = input.channelId;
69
+ if (input.blikToken || input.alias) {
70
+ pay.blikPaymentData = {
71
+ ...(input.blikPaymentType ? { type: input.blikPaymentType } : {}),
72
+ ...(input.blikToken ? { blikToken: input.blikToken } : {}),
73
+ ...(input.refuseNoPayId ? { refuseNoPayId: true } : {}),
74
+ ...(input.alias ? { aliases: input.alias } : {}),
75
+ };
76
+ }
77
+ return {
78
+ amount: input.amount,
79
+ currency: input.currency,
80
+ description: input.description,
81
+ hiddenDescription: input.hiddenDescription,
82
+ payer: {
83
+ email: payerFieldWithinLimit("email", input.payer.email),
84
+ name: payerFieldWithinLimit("name", input.payer.name),
85
+ ...(input.payer.ip ? { ip: payerFieldWithinLimit("ip", input.payer.ip) } : {}),
86
+ ...(input.payer.userAgent
87
+ ? { userAgent: payerFieldWithinLimit("userAgent", input.payer.userAgent) }
88
+ : {}),
89
+ },
90
+ ...(Object.keys(pay).length > 0 ? { pay } : {}),
91
+ callbacks: {
92
+ notification: { url: input.notificationUrl },
93
+ payerUrls: { success: input.successUrl, error: input.errorUrl },
94
+ },
95
+ };
96
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Reading a timestamp from a rail that does not say what clock it is on.
3
+ *
4
+ * A value carrying `Z` or `±HH:mm` is already an instant and is honoured as
5
+ * written. A value without one is a WALL CLOCK — a label on someone's local
6
+ * time — and turning it into an instant needs the zone that clock runs on.
7
+ * `Date.parse` resolves such a value against the HOST zone, which is a silent
8
+ * trap: it reads correct on a laptop set to the rail's own zone and skews by
9
+ * the offset on the UTC runtime that serves production. Callers therefore pass
10
+ * the zone explicitly; there is no default, because a wrong default is exactly
11
+ * the bug this module exists to remove.
12
+ */
13
+ /**
14
+ * The UTC instant of a zone-less `YYYY-MM-DDTHH:mm[:ss]` read in `timeZone`.
15
+ *
16
+ * Two passes, because the offset is a function of the answer being computed:
17
+ * the first prices the naive reading, the second re-prices it at the instant
18
+ * that produced. That is what makes the conversion hold on both sides of a DST
19
+ * transition rather than only in the half-year it was written in — a single
20
+ * pass reads the hour after a spring-forward an hour early. A local time the
21
+ * transition skips or repeats resolves through the offset in force at the first
22
+ * pass; no wall clock can separate those two readings.
23
+ */
24
+ export declare function wallClockToUtc(wallClock: string, timeZone: string): string | null;
25
+ /**
26
+ * An ISO UTC instant for `value`, or `null` when it does not carry one. A
27
+ * zone-less value is read as a wall clock in `timeZone`; a value stating its
28
+ * own offset keeps it. Never invents an instant — the absence of a readable
29
+ * timestamp must stay distinguishable from a timestamp.
30
+ */
31
+ export declare function parseInstant(value: unknown, timeZone: string): string | null;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Reading a timestamp from a rail that does not say what clock it is on.
3
+ *
4
+ * A value carrying `Z` or `±HH:mm` is already an instant and is honoured as
5
+ * written. A value without one is a WALL CLOCK — a label on someone's local
6
+ * time — and turning it into an instant needs the zone that clock runs on.
7
+ * `Date.parse` resolves such a value against the HOST zone, which is a silent
8
+ * trap: it reads correct on a laptop set to the rail's own zone and skews by
9
+ * the offset on the UTC runtime that serves production. Callers therefore pass
10
+ * the zone explicitly; there is no default, because a wrong default is exactly
11
+ * the bug this module exists to remove.
12
+ */
13
+ /** A date-time with no `Z` and no `±HH:mm`, i.e. a bare wall-clock label. */
14
+ const ZONELESS_DATE_TIME = /^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)$/;
15
+ /** Milliseconds `timeZone` is ahead of UTC at `at` (negative when west of it). */
16
+ function zoneOffsetMs(at, timeZone) {
17
+ const zone = new Intl.DateTimeFormat("en", { timeZone, timeZoneName: "longOffset" })
18
+ .formatToParts(at).find((part) => part.type === "timeZoneName")?.value;
19
+ const match = /^GMT([+-])(\d{1,2}):(\d{2})$/.exec(zone ?? "");
20
+ if (!match)
21
+ return 0;
22
+ const [, sign, hours, minutes] = match;
23
+ return (sign === "-" ? -1 : 1) * (Number(hours) * 60 + Number(minutes)) * 60_000;
24
+ }
25
+ /**
26
+ * The UTC instant of a zone-less `YYYY-MM-DDTHH:mm[:ss]` read in `timeZone`.
27
+ *
28
+ * Two passes, because the offset is a function of the answer being computed:
29
+ * the first prices the naive reading, the second re-prices it at the instant
30
+ * that produced. That is what makes the conversion hold on both sides of a DST
31
+ * transition rather than only in the half-year it was written in — a single
32
+ * pass reads the hour after a spring-forward an hour early. A local time the
33
+ * transition skips or repeats resolves through the offset in force at the first
34
+ * pass; no wall clock can separate those two readings.
35
+ */
36
+ export function wallClockToUtc(wallClock, timeZone) {
37
+ const asIfUtc = Date.parse(`${wallClock}Z`);
38
+ if (!Number.isFinite(asIfUtc))
39
+ return null;
40
+ const firstPass = asIfUtc - zoneOffsetMs(new Date(asIfUtc), timeZone);
41
+ return new Date(asIfUtc - zoneOffsetMs(new Date(firstPass), timeZone)).toISOString();
42
+ }
43
+ /**
44
+ * An ISO UTC instant for `value`, or `null` when it does not carry one. A
45
+ * zone-less value is read as a wall clock in `timeZone`; a value stating its
46
+ * own offset keeps it. Never invents an instant — the absence of a readable
47
+ * timestamp must stay distinguishable from a timestamp.
48
+ */
49
+ export function parseInstant(value, timeZone) {
50
+ if (typeof value !== "string" || !value.trim())
51
+ return null;
52
+ const trimmed = value.trim();
53
+ const zoneless = ZONELESS_DATE_TIME.exec(trimmed);
54
+ if (zoneless)
55
+ return wallClockToUtc(`${zoneless[1]}T${zoneless[2]}`, timeZone);
56
+ return Number.isFinite(Date.parse(trimmed)) ? new Date(trimmed).toISOString() : null;
57
+ }
@@ -0,0 +1,17 @@
1
+ export interface TpayJwsCertificateResolver {
2
+ (url: string): Promise<string>;
3
+ }
4
+ export interface TpayJwsVerificationInput {
5
+ rawBody: string;
6
+ signatureHeader: string | undefined;
7
+ resolveCertificate: TpayJwsCertificateResolver;
8
+ rootCertificatePem: string;
9
+ allowedX5uPrefix?: string;
10
+ verifyCertificateChain?: (signingCertificatePem: string, rootCertificatePem: string) => boolean;
11
+ verifyDetachedSignature?: (input: {
12
+ signingInput: string;
13
+ signature: Buffer;
14
+ signingCertificatePem: string;
15
+ }) => boolean;
16
+ }
17
+ export declare function verifyTpayJwsSignature({ rawBody, signatureHeader, resolveCertificate, rootCertificatePem, allowedX5uPrefix, verifyCertificateChain: verifyChain, verifyDetachedSignature: verifyDetached, }: TpayJwsVerificationInput): Promise<boolean>;
@@ -0,0 +1,51 @@
1
+ import { verify as verifySignature, X509Certificate } from "node:crypto";
2
+ export async function verifyTpayJwsSignature({ rawBody, signatureHeader, resolveCertificate, rootCertificatePem, allowedX5uPrefix = "https://secure.sandbox.tpay.com/", verifyCertificateChain: verifyChain = verifyCertificateChain, verifyDetachedSignature: verifyDetached = defaultVerifyDetachedSignature, }) {
3
+ if (!signatureHeader || !rootCertificatePem)
4
+ return false;
5
+ const parts = signatureHeader.split(".");
6
+ if (parts.length < 3 || !parts[0] || !parts[2])
7
+ return false;
8
+ const header = parseJwsHeader(parts[0]);
9
+ if (!header?.x5u || !header.x5u.startsWith(allowedX5uPrefix))
10
+ return false;
11
+ const signingCertificatePem = await resolveCertificate(header.x5u);
12
+ if (!verifyChain(signingCertificatePem, rootCertificatePem))
13
+ return false;
14
+ const signedPayload = `${parts[0]}.${base64Url(Buffer.from(rawBody, "utf8"))}`;
15
+ const signature = base64UrlDecode(parts[2]);
16
+ return verifyDetached({ signingInput: signedPayload, signature, signingCertificatePem });
17
+ }
18
+ function parseJwsHeader(encoded) {
19
+ try {
20
+ const parsed = JSON.parse(base64UrlDecode(encoded).toString("utf8"));
21
+ if (!parsed || typeof parsed !== "object")
22
+ return null;
23
+ const x5u = parsed.x5u;
24
+ return typeof x5u === "string" ? { x5u } : null;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ function verifyCertificateChain(signingCertificatePem, rootCertificatePem) {
31
+ try {
32
+ const signing = new X509Certificate(signingCertificatePem);
33
+ const root = new X509Certificate(rootCertificatePem);
34
+ return signing.verify(root.publicKey);
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ function defaultVerifyDetachedSignature(input) {
41
+ const publicKey = new X509Certificate(input.signingCertificatePem).publicKey;
42
+ return verifySignature("RSA-SHA256", Buffer.from(input.signingInput), publicKey, input.signature);
43
+ }
44
+ function base64Url(value) {
45
+ return value.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
46
+ }
47
+ function base64UrlDecode(value) {
48
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
49
+ const padded = normalized.padEnd(normalized.length + ((4 - normalized.length % 4) % 4), "=");
50
+ return Buffer.from(padded, "base64");
51
+ }
@@ -0,0 +1,54 @@
1
+ import type { PaymentMethodLifecycleEvent, PaymentMethodLifecycleEventKind } from "@openlup/core/payment";
2
+ declare const PROVIDER_KIND = "tpay";
3
+ type TpayCanonicalPaymentEvent = {
4
+ provider_event_id: string;
5
+ event_type: "payment.succeeded" | "payment.failed" | "payment.refunded" | "payment.requires_action" | "payment.disputed" | "setup.succeeded" | "setup.failed" | "setup.requires_action";
6
+ payment_provider_id: string;
7
+ payment_intent_id?: string | null;
8
+ amount_minor?: number;
9
+ raw_payload: Record<string, unknown>;
10
+ };
11
+ export interface NormalizedTpayMethodRef {
12
+ clientId: string | null;
13
+ subscriptionId?: string | null;
14
+ providerMethodRef: string;
15
+ methodKind: "blik_payid";
16
+ status: "active" | "inactive";
17
+ consentSnapshot: Record<string, unknown>;
18
+ }
19
+ export interface NormalizedTpayWebhook {
20
+ event: TpayCanonicalPaymentEvent;
21
+ methodRef: NormalizedTpayMethodRef | null;
22
+ /**
23
+ * The neutral transition this delivery reports about the stored mandate. The
24
+ * instant it happened is not known at this layer, so the full neutral event
25
+ * is assembled by the provider-webhook mapper below.
26
+ */
27
+ methodLifecycleKind: PaymentMethodLifecycleEventKind | null;
28
+ responseBody: "TRUE";
29
+ }
30
+ type NormalizedTpayProviderWebhook = {
31
+ provider: typeof PROVIDER_KIND;
32
+ providerEventId: string;
33
+ eventType: TpayCanonicalPaymentEvent["event_type"];
34
+ providerPaymentId: string;
35
+ paymentIntentId?: string | null;
36
+ amountMinor: number | null;
37
+ currency: string | null;
38
+ occurredAt: string;
39
+ rawPayload: Record<string, unknown>;
40
+ reusableMethod: {
41
+ clientId: string;
42
+ subscriptionId: string | null;
43
+ providerCustomerRef: null;
44
+ providerMethodRef: string;
45
+ providerMandateRef: null;
46
+ methodKind: "blik_payid";
47
+ status: "active" | "inactive";
48
+ consentSnapshot: Record<string, unknown>;
49
+ } | null;
50
+ methodLifecycle: PaymentMethodLifecycleEvent | null;
51
+ };
52
+ export declare function normalizeTpayWebhook(rawBody: string): NormalizedTpayWebhook;
53
+ export declare function toNormalizedProviderTpayWebhook(webhook: NormalizedTpayWebhook, occurredAt: string): NormalizedTpayProviderWebhook;
54
+ export {};