@openlup/platform 0.20.0 → 0.21.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 (210) hide show
  1. package/CHANGELOG.md +62 -231
  2. package/README.md +172 -6
  3. package/dist/composition/composeBundle.d.ts +6 -0
  4. package/dist/composition/composeBundle.js +2 -0
  5. package/dist/composition/index.js +1 -0
  6. package/dist/payments/adapterConformance.d.ts +8 -0
  7. package/dist/payments/adapterConformance.js +109 -0
  8. package/dist/payments/bindings.d.ts +3 -0
  9. package/dist/payments/bindings.js +17 -0
  10. package/dist/payments/checkoutActivePaymentActionResolver.d.ts +7 -0
  11. package/dist/payments/checkoutActivePaymentActionResolver.js +77 -0
  12. package/dist/payments/checkoutRecoveryPaymentResolver.d.ts +8 -0
  13. package/dist/payments/checkoutRecoveryPaymentResolver.js +87 -0
  14. package/dist/payments/contracts/adapter.d.ts +113 -0
  15. package/dist/payments/contracts/adapter.js +1 -0
  16. package/dist/payments/contracts/conformance.d.ts +155 -0
  17. package/dist/payments/contracts/conformance.js +1 -0
  18. package/dist/payments/contracts/evidence.d.ts +157 -0
  19. package/dist/payments/contracts/evidence.js +1 -0
  20. package/dist/payments/contracts/index.d.ts +10 -0
  21. package/dist/payments/contracts/index.js +1 -0
  22. package/dist/payments/contracts/preparedAttempt.d.ts +69 -0
  23. package/dist/payments/contracts/preparedAttempt.js +1 -0
  24. package/dist/payments/contracts/reconciliation.d.ts +303 -0
  25. package/dist/payments/contracts/reconciliation.js +1 -0
  26. package/dist/payments/contracts/repair.d.ts +92 -0
  27. package/dist/payments/contracts/repair.js +1 -0
  28. package/dist/payments/contracts/truth.d.ts +92 -0
  29. package/dist/payments/contracts/truth.js +1 -0
  30. package/dist/payments/eventConformanceRunner.d.ts +12 -0
  31. package/dist/payments/eventConformanceRunner.js +125 -0
  32. package/dist/payments/index.d.ts +16 -0
  33. package/dist/payments/index.js +16 -0
  34. package/dist/payments/paymentAttemptSilenceWindow.d.ts +10 -0
  35. package/dist/payments/paymentAttemptSilenceWindow.js +19 -0
  36. package/dist/payments/paymentMethodLifecycle.d.ts +11 -0
  37. package/dist/payments/paymentMethodLifecycle.js +37 -0
  38. package/dist/payments/paymentProviderReconciliationAbsenceReopen.d.ts +7 -0
  39. package/dist/payments/paymentProviderReconciliationAbsenceReopen.js +100 -0
  40. package/dist/payments/paymentProviderReconciliationEvidence.d.ts +36 -0
  41. package/dist/payments/paymentProviderReconciliationEvidence.js +91 -0
  42. package/dist/payments/paymentProviderReconciliationWorker.d.ts +4 -0
  43. package/dist/payments/paymentProviderReconciliationWorker.js +245 -0
  44. package/dist/payments/paymentTruth.d.ts +8 -0
  45. package/dist/payments/paymentTruth.js +125 -0
  46. package/dist/payments/paymentVerifyNowHandler.d.ts +27 -0
  47. package/dist/payments/paymentVerifyNowHandler.js +70 -0
  48. package/dist/payments/paymentVerifyNowService.d.ts +23 -0
  49. package/dist/payments/paymentVerifyNowService.js +93 -0
  50. package/dist/payments/paymentWebhookPostProcessing.d.ts +22 -0
  51. package/dist/payments/paymentWebhookPostProcessing.js +15 -0
  52. package/dist/payments/postgresPaymentTruth.d.ts +10 -0
  53. package/dist/payments/postgresPaymentTruth.js +126 -0
  54. package/dist/payments/preparedAttempt.d.ts +6 -0
  55. package/dist/payments/preparedAttempt.js +70 -0
  56. package/dist/payments/preparedAttemptFinalization.d.ts +5 -0
  57. package/dist/payments/preparedAttemptFinalization.js +12 -0
  58. package/dist/payments/schemaCapabilities.d.ts +10 -0
  59. package/dist/payments/schemaCapabilities.js +10 -0
  60. package/dist/payments/sql.d.ts +20 -0
  61. package/dist/payments/sql.js +6 -0
  62. package/dist/payments/stripe/classifyDecline.d.ts +46 -0
  63. package/dist/payments/stripe/classifyDecline.js +58 -0
  64. package/dist/payments/stripe/declineFailureHints.d.ts +46 -0
  65. package/dist/payments/stripe/declineFailureHints.js +87 -0
  66. package/dist/payments/stripe/donorContracts.d.ts +120 -0
  67. package/dist/payments/stripe/donorContracts.js +1 -0
  68. package/dist/payments/stripe/executionDecline.d.ts +53 -0
  69. package/dist/payments/stripe/executionDecline.js +107 -0
  70. package/dist/payments/stripe/index.d.ts +16 -0
  71. package/dist/payments/stripe/index.js +239 -0
  72. package/dist/payments/stripe/intentMapper.d.ts +26 -0
  73. package/dist/payments/stripe/intentMapper.js +51 -0
  74. package/dist/payments/stripe/methodFacts.d.ts +10 -0
  75. package/dist/payments/stripe/methodFacts.js +12 -0
  76. package/dist/payments/stripe/paymentFailureEvidence.d.ts +26 -0
  77. package/dist/payments/stripe/paymentFailureEvidence.js +33 -0
  78. package/dist/payments/stripe/stripeFailureEvidence.d.ts +41 -0
  79. package/dist/payments/stripe/stripeFailureEvidence.js +82 -0
  80. package/dist/payments/stripe/stripeIntentConfirmationEvidence.d.ts +28 -0
  81. package/dist/payments/stripe/stripeIntentConfirmationEvidence.js +32 -0
  82. package/dist/payments/stripe/stripePaymentReconciliationProvider.d.ts +35 -0
  83. package/dist/payments/stripe/stripePaymentReconciliationProvider.js +222 -0
  84. package/dist/payments/stripe/stripeRefundEvidence.d.ts +8 -0
  85. package/dist/payments/stripe/stripeRefundEvidence.js +17 -0
  86. package/dist/payments/stripe/stripeSandboxPaymentExecutionAdapter.d.ts +45 -0
  87. package/dist/payments/stripe/stripeSandboxPaymentExecutionAdapter.js +220 -0
  88. package/dist/payments/stripe/stripeWebhookNormalizer.d.ts +14 -0
  89. package/dist/payments/stripe/stripeWebhookNormalizer.js +243 -0
  90. package/dist/payments/stripe/webhookSignature.d.ts +14 -0
  91. package/dist/payments/stripe/webhookSignature.js +43 -0
  92. package/dist/payments/tpay/blikRecurringMapper.d.ts +37 -0
  93. package/dist/payments/tpay/blikRecurringMapper.js +24 -0
  94. package/dist/payments/tpay/capabilities.d.ts +3 -0
  95. package/dist/payments/tpay/capabilities.js +18 -0
  96. package/dist/payments/tpay/classifyDecline.d.ts +46 -0
  97. package/dist/payments/tpay/classifyDecline.js +58 -0
  98. package/dist/payments/tpay/declineFailureHints.d.ts +72 -0
  99. package/dist/payments/tpay/declineFailureHints.js +37 -0
  100. package/dist/payments/tpay/donorContracts.d.ts +140 -0
  101. package/dist/payments/tpay/donorContracts.js +1 -0
  102. package/dist/payments/tpay/index.d.ts +43 -0
  103. package/dist/payments/tpay/index.js +264 -0
  104. package/dist/payments/tpay/paymentFailureEvidence.d.ts +26 -0
  105. package/dist/payments/tpay/paymentFailureEvidence.js +33 -0
  106. package/dist/payments/tpay/preparedProviderAttempt.d.ts +22 -0
  107. package/dist/payments/tpay/preparedProviderAttempt.js +24 -0
  108. package/dist/payments/tpay/providerAttemptFailureDiagnostic.d.ts +3 -0
  109. package/dist/payments/tpay/providerAttemptFailureDiagnostic.js +95 -0
  110. package/dist/payments/tpay/tpayCreateTransactionInput.d.ts +25 -0
  111. package/dist/payments/tpay/tpayCreateTransactionInput.js +128 -0
  112. package/dist/payments/tpay/tpayDeclineEvidence.d.ts +126 -0
  113. package/dist/payments/tpay/tpayDeclineEvidence.js +177 -0
  114. package/dist/payments/tpay/tpayDispatchFailure.d.ts +60 -0
  115. package/dist/payments/tpay/tpayDispatchFailure.js +121 -0
  116. package/dist/payments/tpay/tpayFailureEvidence.d.ts +20 -0
  117. package/dist/payments/tpay/tpayFailureEvidence.js +61 -0
  118. package/dist/payments/tpay/tpayHttpClient.d.ts +52 -0
  119. package/dist/payments/tpay/tpayHttpClient.js +185 -0
  120. package/dist/payments/tpay/tpayHttpFailure.d.ts +57 -0
  121. package/dist/payments/tpay/tpayHttpFailure.js +143 -0
  122. package/dist/payments/tpay/tpayPaymentExecutionAdapter.d.ts +20 -0
  123. package/dist/payments/tpay/tpayPaymentExecutionAdapter.js +249 -0
  124. package/dist/payments/tpay/tpayPaymentReconciliationProvider.d.ts +5 -0
  125. package/dist/payments/tpay/tpayPaymentReconciliationProvider.js +244 -0
  126. package/dist/payments/tpay/tpayTransactionPayload.d.ts +120 -0
  127. package/dist/payments/tpay/tpayTransactionPayload.js +96 -0
  128. package/dist/payments/tpay/wallClockTime.d.ts +31 -0
  129. package/dist/payments/tpay/wallClockTime.js +57 -0
  130. package/dist/payments/tpay/webhookJwsVerifier.d.ts +17 -0
  131. package/dist/payments/tpay/webhookJwsVerifier.js +51 -0
  132. package/dist/payments/tpay/webhookNormalizer.d.ts +54 -0
  133. package/dist/payments/tpay/webhookNormalizer.js +200 -0
  134. package/dist/payments/verifiedEvidenceProjection.d.ts +12 -0
  135. package/dist/payments/verifiedEvidenceProjection.js +166 -0
  136. package/dist/payments/verifiedIngress.d.ts +119 -0
  137. package/dist/payments/verifiedIngress.js +220 -0
  138. package/package.json +22 -2
  139. package/release-gates.json +64 -3
  140. package/sql/20260810180000_payment_settlement_rail.sql +574 -0
  141. package/sql/20260815150000_payment_truth_reconciliation.sql +479 -0
  142. package/sql/20260826100000_late_payment_shifts_the_subscription_cycle.sql +215 -0
  143. package/src/composition/composeBundle.ts +9 -0
  144. package/src/composition/index.ts +1 -0
  145. package/src/payments/adapterConformance.ts +118 -0
  146. package/src/payments/bindings.ts +17 -0
  147. package/src/payments/checkoutActivePaymentActionResolver.ts +97 -0
  148. package/src/payments/checkoutRecoveryPaymentResolver.ts +108 -0
  149. package/src/payments/contracts/adapter.ts +96 -0
  150. package/src/payments/contracts/conformance.ts +171 -0
  151. package/src/payments/contracts/evidence.ts +142 -0
  152. package/src/payments/contracts/index.ts +54 -0
  153. package/src/payments/contracts/preparedAttempt.ts +86 -0
  154. package/src/payments/contracts/reconciliation.ts +296 -0
  155. package/src/payments/contracts/repair.ts +95 -0
  156. package/src/payments/contracts/truth.ts +108 -0
  157. package/src/payments/eventConformanceRunner.ts +149 -0
  158. package/src/payments/index.ts +21 -0
  159. package/src/payments/paymentAttemptSilenceWindow.ts +26 -0
  160. package/src/payments/paymentMethodLifecycle.ts +50 -0
  161. package/src/payments/paymentProviderReconciliationAbsenceReopen.ts +130 -0
  162. package/src/payments/paymentProviderReconciliationEvidence.ts +117 -0
  163. package/src/payments/paymentProviderReconciliationWorker.ts +291 -0
  164. package/src/payments/paymentTruth.ts +131 -0
  165. package/src/payments/paymentVerifyNowHandler.ts +94 -0
  166. package/src/payments/paymentVerifyNowService.ts +125 -0
  167. package/src/payments/paymentWebhookPostProcessing.ts +36 -0
  168. package/src/payments/postgresPaymentTruth.ts +149 -0
  169. package/src/payments/preparedAttempt.ts +90 -0
  170. package/src/payments/preparedAttemptFinalization.ts +13 -0
  171. package/src/payments/schemaCapabilities.ts +10 -0
  172. package/src/payments/sql.ts +6 -0
  173. package/src/payments/stripe/classifyDecline.ts +62 -0
  174. package/src/payments/stripe/declineFailureHints.ts +89 -0
  175. package/src/payments/stripe/donorContracts.ts +87 -0
  176. package/src/payments/stripe/executionDecline.ts +126 -0
  177. package/src/payments/stripe/index.ts +235 -0
  178. package/src/payments/stripe/intentMapper.ts +58 -0
  179. package/src/payments/stripe/methodFacts.ts +21 -0
  180. package/src/payments/stripe/paymentFailureEvidence.ts +58 -0
  181. package/src/payments/stripe/stripeFailureEvidence.ts +115 -0
  182. package/src/payments/stripe/stripeIntentConfirmationEvidence.ts +34 -0
  183. package/src/payments/stripe/stripePaymentReconciliationProvider.ts +263 -0
  184. package/src/payments/stripe/stripeRefundEvidence.ts +19 -0
  185. package/src/payments/stripe/stripeSandboxPaymentExecutionAdapter.ts +283 -0
  186. package/src/payments/stripe/stripeWebhookNormalizer.ts +277 -0
  187. package/src/payments/stripe/webhookSignature.ts +55 -0
  188. package/src/payments/tpay/blikRecurringMapper.ts +42 -0
  189. package/src/payments/tpay/capabilities.ts +21 -0
  190. package/src/payments/tpay/classifyDecline.ts +62 -0
  191. package/src/payments/tpay/declineFailureHints.ts +90 -0
  192. package/src/payments/tpay/donorContracts.ts +104 -0
  193. package/src/payments/tpay/index.ts +257 -0
  194. package/src/payments/tpay/paymentFailureEvidence.ts +58 -0
  195. package/src/payments/tpay/preparedProviderAttempt.ts +38 -0
  196. package/src/payments/tpay/providerAttemptFailureDiagnostic.ts +99 -0
  197. package/src/payments/tpay/tpayCreateTransactionInput.ts +158 -0
  198. package/src/payments/tpay/tpayDeclineEvidence.ts +233 -0
  199. package/src/payments/tpay/tpayDispatchFailure.ts +186 -0
  200. package/src/payments/tpay/tpayFailureEvidence.ts +75 -0
  201. package/src/payments/tpay/tpayHttpClient.ts +290 -0
  202. package/src/payments/tpay/tpayHttpFailure.ts +186 -0
  203. package/src/payments/tpay/tpayPaymentExecutionAdapter.ts +297 -0
  204. package/src/payments/tpay/tpayPaymentReconciliationProvider.ts +285 -0
  205. package/src/payments/tpay/tpayTransactionPayload.ts +207 -0
  206. package/src/payments/tpay/wallClockTime.ts +57 -0
  207. package/src/payments/tpay/webhookJwsVerifier.ts +82 -0
  208. package/src/payments/tpay/webhookNormalizer.ts +279 -0
  209. package/src/payments/verifiedEvidenceProjection.ts +179 -0
  210. package/src/payments/verifiedIngress.ts +317 -0
@@ -0,0 +1,46 @@
1
+ import { type PaymentExecutionProviderDecline, type PaymentFailureClassification } from "@openlup/core/payment";
2
+ export declare const DECLINE_FAILURE_REASONS: {
3
+ /** The payer's bank cannot register a reusable mandate — offer a card instead. */
4
+ readonly mandateUnsupported: "blik_recurring_unsupported_bank";
5
+ /** Declined for a reason the provider did not distinguish. */
6
+ readonly generic: "provider_declined";
7
+ };
8
+ /** Maps a provider refusal to the stable code persisted as `failure_reason`. */
9
+ export declare function declineFailureReason(decline: PaymentExecutionProviderDecline): string;
10
+ /**
11
+ * The one place a refused payment gets a class, for every rail that consumes a
12
+ * synchronous decline: interactive checkout, checkout recovery, and the
13
+ * off-session renewal cron.
14
+ *
15
+ * Provider-agnostic on purpose. Every code the adapter understood has already
16
+ * been translated into `adviceCode` and `neutralReasonHints` by the adapter that
17
+ * owns the vocabulary, so nothing here needs to know which provider refused.
18
+ *
19
+ * The one reading this seam adds is the mandate flag. `mandateUnsupported` is a
20
+ * neutral capability statement any adapter may set, and it is what produces the
21
+ * scheme-named failure reason below; asserting the matching hint is what turns
22
+ * that reason into `mandate_dead` instead of leaving it unclassified. Doing it
23
+ * here rather than per adapter means a second adapter setting the same flag gets
24
+ * the same class without repeating the mapping.
25
+ *
26
+ * DISPLAY-LOAD-BEARING, and NOT a cadence input. Three live readers turn the
27
+ * class this seam decides into what the payer is told: `dunningFailureClassPort`
28
+ * reads it off the case, `customerAccountActionRequiredReadModel` selects it into
29
+ * the account surface, and `paymentRecoverySetupHandler` renders it as the cause
30
+ * sentence on the recovery page. What still does NOT read it is the retry ladder:
31
+ * no cadence, retry decision or scheduling branch consults the class, and that
32
+ * wave stays gated on validating this taxonomy against the historical corpus.
33
+ *
34
+ * `failureReasonKey` overrides the reason this seam would derive itself, and is
35
+ * supplied by a rail that persists a DIFFERENT reason string with the attempt:
36
+ * the reconciliation rail writes its own adapter-prefixed key, not
37
+ * `provider_declined`. That key is the classifier's weakest evidence tier, so
38
+ * the override changes a class only where the rail's own key is mapped; what it
39
+ * always changes is `decidedBy`, and that is the point. The historical-corpus
40
+ * replay reproduces a verdict from the stored attempt row, so the key named in
41
+ * the telemetry has to be the key actually on that row rather than one this seam
42
+ * invented for itself.
43
+ */
44
+ export declare function classifyDecline(decline: PaymentExecutionProviderDecline, options?: {
45
+ failureReasonKey?: string;
46
+ }): PaymentFailureClassification;
@@ -0,0 +1,58 @@
1
+ import { classifyPaymentFailure } from "@openlup/core/payment";
2
+ export const DECLINE_FAILURE_REASONS = {
3
+ /** The payer's bank cannot register a reusable mandate — offer a card instead. */
4
+ mandateUnsupported: "blik_recurring_unsupported_bank",
5
+ /** Declined for a reason the provider did not distinguish. */
6
+ generic: "provider_declined",
7
+ };
8
+ /** Maps a provider refusal to the stable code persisted as `failure_reason`. */
9
+ export function declineFailureReason(decline) {
10
+ return decline.mandateUnsupported
11
+ ? DECLINE_FAILURE_REASONS.mandateUnsupported
12
+ : DECLINE_FAILURE_REASONS.generic;
13
+ }
14
+ /**
15
+ * The one place a refused payment gets a class, for every rail that consumes a
16
+ * synchronous decline: interactive checkout, checkout recovery, and the
17
+ * off-session renewal cron.
18
+ *
19
+ * Provider-agnostic on purpose. Every code the adapter understood has already
20
+ * been translated into `adviceCode` and `neutralReasonHints` by the adapter that
21
+ * owns the vocabulary, so nothing here needs to know which provider refused.
22
+ *
23
+ * The one reading this seam adds is the mandate flag. `mandateUnsupported` is a
24
+ * neutral capability statement any adapter may set, and it is what produces the
25
+ * scheme-named failure reason below; asserting the matching hint is what turns
26
+ * that reason into `mandate_dead` instead of leaving it unclassified. Doing it
27
+ * here rather than per adapter means a second adapter setting the same flag gets
28
+ * the same class without repeating the mapping.
29
+ *
30
+ * DISPLAY-LOAD-BEARING, and NOT a cadence input. Three live readers turn the
31
+ * class this seam decides into what the payer is told: `dunningFailureClassPort`
32
+ * reads it off the case, `customerAccountActionRequiredReadModel` selects it into
33
+ * the account surface, and `paymentRecoverySetupHandler` renders it as the cause
34
+ * sentence on the recovery page. What still does NOT read it is the retry ladder:
35
+ * no cadence, retry decision or scheduling branch consults the class, and that
36
+ * wave stays gated on validating this taxonomy against the historical corpus.
37
+ *
38
+ * `failureReasonKey` overrides the reason this seam would derive itself, and is
39
+ * supplied by a rail that persists a DIFFERENT reason string with the attempt:
40
+ * the reconciliation rail writes its own adapter-prefixed key, not
41
+ * `provider_declined`. That key is the classifier's weakest evidence tier, so
42
+ * the override changes a class only where the rail's own key is mapped; what it
43
+ * always changes is `decidedBy`, and that is the point. The historical-corpus
44
+ * replay reproduces a verdict from the stored attempt row, so the key named in
45
+ * the telemetry has to be the key actually on that row rather than one this seam
46
+ * invented for itself.
47
+ */
48
+ export function classifyDecline(decline, options = {}) {
49
+ const asserted = [...(decline.neutralReasonHints ?? [])];
50
+ if (decline.mandateUnsupported)
51
+ asserted.push("mandateUnsupported");
52
+ return classifyPaymentFailure({
53
+ adviceCode: decline.adviceCode,
54
+ declineCode: decline.declineCode,
55
+ neutralReasonHints: asserted,
56
+ failureReasonKey: options.failureReasonKey ?? declineFailureReason(decline),
57
+ });
58
+ }
@@ -0,0 +1,72 @@
1
+ import type { PaymentFailureHint, PaymentRetryAdviceCode } from "@openlup/core/payment";
2
+ /**
3
+ * ⚠️ UNOFFICIAL INFERENCE — the whole table below.
4
+ *
5
+ * The provider publishes no retryable-versus-terminal classification for these
6
+ * numeric refusal codes. Every row is this adapter's reading of the code's
7
+ * documented MEANING, not a documented retry verdict, and one of them
8
+ * (`"100"`) appears in no published table at all. Treat the table as a
9
+ * hypothesis under test. The class it produces is already DISPLAY-load-bearing:
10
+ * it becomes the sentence the payer reads about their own refusal, so a wrong
11
+ * row here misinforms a customer today. It is still not a cadence input: the
12
+ * dunning programme validates this table against the historical refusal corpus
13
+ * before anything in the retry ladder is allowed to branch on it.
14
+ *
15
+ * The contract that would end the inference is OUTSTANDING, not absent: the
16
+ * dunning programme's letter to the provider asks for the official meaning of
17
+ * these codes, and is tracked there as an open item. Until it is answered, a row
18
+ * may be read more narrowly than it is written — see `assertableHints` in
19
+ * `tpayDeclineEvidence.ts`, where each observation decides how much of its own
20
+ * reading it is entitled to state — but no row may be read more widely.
21
+ *
22
+ * Two rails read this table, and both reach it through the attempt log's
23
+ * `paymentErrorCode`: the reconciliation poll, and — since the 2026-08-26
24
+ * incident — the synchronous execution path, which until then decided a
25
+ * mandate refusal by pattern-matching the refusal MESSAGE.
26
+ *
27
+ * Codes arrive as strings because that is how they leave the transport layer.
28
+ *
29
+ * Two rows carry a decision worth stating rather than a lookup:
30
+ *
31
+ * - `"101"` (refused by the payer) and `"107"` (refused on security grounds)
32
+ * are NOT credential death — the instrument may be in perfect health and
33
+ * the payer may approve the very next charge on it. There is no hint for
34
+ * "terminal for this attempt, instrument fine", so this adapter instead
35
+ * states the retry verdict directly, as advice: an unattended repeat of a
36
+ * refusal the payer or their bank made deliberately is the retry that
37
+ * cannot succeed and still costs a scheme fee. Naming it as advice is also
38
+ * the more honest shape — it records what this adapter concludes about
39
+ * RETRYING, without asserting anything about the instrument.
40
+ * - `"100"` and `"102"` get NO reading. Nothing published says what they mean
41
+ * precisely enough to classify, and `indeterminate` is the correct answer to
42
+ * a code we cannot read. They are named in
43
+ * `KNOWN_UNREADABLE_DECLINE_CODES` below, so their absence stays a recorded
44
+ * decision rather than an oversight.
45
+ */
46
+ export interface DeclineCodeReading {
47
+ hints?: readonly PaymentFailureHint[];
48
+ adviceCode?: PaymentRetryAdviceCode;
49
+ }
50
+ export declare const DECLINE_CODE_READING_TABLE: Readonly<Record<string, DeclineCodeReading>>;
51
+ /**
52
+ * Codes this adapter has SEEN and still cannot read. They are deliberately absent
53
+ * from the table above rather than mapped to a guess: a row there must state
54
+ * vocabulary the kernel declares, and `indeterminate` is the honest answer to a
55
+ * code whose meaning is unconfirmed. Listing them here separates "we have met
56
+ * this code and refuse to invent its semantics" from "we have never seen it",
57
+ * which read identically at the lookup.
58
+ *
59
+ * - `"100"` — observed in production on 2026-08-26, as the first attempt of a
60
+ * refused mandate registration whose second attempt was `"105"`. Nothing
61
+ * published says what it means, so it maps to no hint and no advice.
62
+ * - `"102"` — same standing, from the same unpublished range.
63
+ *
64
+ * A code moving out of this list into the table is a semantics decision backed by
65
+ * evidence, not a lookup edit.
66
+ */
67
+ export declare const KNOWN_UNREADABLE_DECLINE_CODES: readonly string[];
68
+ /**
69
+ * This adapter's reading of one refusal code. Own-property lookup, so an
70
+ * inherited key (`constructor`, `toString`) is not a mapping.
71
+ */
72
+ export declare function declineCodeReading(code: string | undefined): DeclineCodeReading;
@@ -0,0 +1,37 @@
1
+ export const DECLINE_CODE_READING_TABLE = Object.freeze({
2
+ "101": Object.freeze({ adviceCode: "do_not_try_again" }),
3
+ "103": Object.freeze({ hints: Object.freeze(["transient"]) }),
4
+ "104": Object.freeze({ hints: Object.freeze(["transient"]) }),
5
+ "105": Object.freeze({ hints: Object.freeze(["mandateUnsupported"]) }),
6
+ "106": Object.freeze({ hints: Object.freeze(["limitExceeded"]) }),
7
+ "107": Object.freeze({ adviceCode: "do_not_try_again" }),
8
+ });
9
+ /**
10
+ * Codes this adapter has SEEN and still cannot read. They are deliberately absent
11
+ * from the table above rather than mapped to a guess: a row there must state
12
+ * vocabulary the kernel declares, and `indeterminate` is the honest answer to a
13
+ * code whose meaning is unconfirmed. Listing them here separates "we have met
14
+ * this code and refuse to invent its semantics" from "we have never seen it",
15
+ * which read identically at the lookup.
16
+ *
17
+ * - `"100"` — observed in production on 2026-08-26, as the first attempt of a
18
+ * refused mandate registration whose second attempt was `"105"`. Nothing
19
+ * published says what it means, so it maps to no hint and no advice.
20
+ * - `"102"` — same standing, from the same unpublished range.
21
+ *
22
+ * A code moving out of this list into the table is a semantics decision backed by
23
+ * evidence, not a lookup edit.
24
+ */
25
+ export const KNOWN_UNREADABLE_DECLINE_CODES = Object.freeze(["100", "102"]);
26
+ const NO_READING = Object.freeze({});
27
+ /**
28
+ * This adapter's reading of one refusal code. Own-property lookup, so an
29
+ * inherited key (`constructor`, `toString`) is not a mapping.
30
+ */
31
+ export function declineCodeReading(code) {
32
+ if (code === undefined)
33
+ return NO_READING;
34
+ return Object.prototype.hasOwnProperty.call(DECLINE_CODE_READING_TABLE, code)
35
+ ? DECLINE_CODE_READING_TABLE[code]
36
+ : NO_READING;
37
+ }
@@ -0,0 +1,140 @@
1
+ /** Private structural contracts for the unchanged execution and observation slices. */
2
+ import type { PaymentExecutionBaseInput, PaymentExecutionBaseResult } from "@openlup/core/payment";
3
+ import type { ProviderReadPurpose } from "../contracts/index.js";
4
+ export type PaymentProviderFlow = "one_time_payment" | "setup_reusable_method" | "off_session_payment" | "blik_one_time" | "blik_one_click" | "pbl_one_time" | "blik_recurring_activation" | "recurring_charge";
5
+ export declare const PAYMENT_NEXT_ACTION_KINDS: readonly ["redirect", "3ds_challenge", "sca_required"];
6
+ export type PaymentNextActionKind = (typeof PAYMENT_NEXT_ACTION_KINDS)[number];
7
+ export interface PaymentExecutionInput extends PaymentExecutionBaseInput {
8
+ orderRef: string;
9
+ orderId?: string;
10
+ subjectId?: string | null;
11
+ returnContext?: "public" | "account";
12
+ payer?: {
13
+ email: string;
14
+ name: string;
15
+ ip?: string | null;
16
+ userAgent?: string | null;
17
+ };
18
+ transientProviderInput?: TpayTransientProviderInput | null;
19
+ paymentMethodAliasType?: "UID" | "PAYID";
20
+ paymentMethodRecurringModel?: "O" | "M";
21
+ providerFlow?: PaymentProviderFlow;
22
+ customerRef?: string;
23
+ paymentMethodRef?: string;
24
+ saveForFutureUse?: boolean;
25
+ clientId?: string;
26
+ }
27
+ export interface PaymentExecutionResult extends PaymentExecutionBaseResult {
28
+ nextActionKind: PaymentNextActionKind | null;
29
+ redirectUrl?: string | null;
30
+ clientSecret?: string | null;
31
+ customerProviderRef?: string | null;
32
+ paymentMethodRef?: string | null;
33
+ webhookExpected?: boolean;
34
+ recoveryRequired?: boolean;
35
+ }
36
+ export interface PaymentExecutionPort {
37
+ validateInput?(input: PaymentExecutionInput): void;
38
+ execute(input: PaymentExecutionInput): Promise<PaymentExecutionResult>;
39
+ }
40
+ export type PaymentWebhookEventType = "payment.succeeded" | "payment.failed" | "payment.refunded" | "payment.requires_action" | "payment.disputed" | "setup.succeeded" | "setup.failed" | "setup.requires_action";
41
+ export interface CanonicalPaymentEvent {
42
+ provider_event_id: string;
43
+ event_type: PaymentWebhookEventType;
44
+ payment_provider_id: string;
45
+ amount_minor?: number;
46
+ next_action_kind?: PaymentNextActionKind;
47
+ raw_payload: Record<string, unknown>;
48
+ }
49
+ export interface NormalizedProviderPaymentWebhook {
50
+ provider: string;
51
+ providerEventId: string;
52
+ eventType: PaymentWebhookEventType;
53
+ providerPaymentId: string;
54
+ paymentIntentId?: string | null;
55
+ paymentAttemptId?: string | null;
56
+ amountMinor?: number | null;
57
+ currency?: string | null;
58
+ occurredAt: string;
59
+ refundKind?: "full" | "partial" | null;
60
+ rawPayload: Record<string, unknown>;
61
+ reusableMethod?: {
62
+ clientId: string;
63
+ subscriptionId?: string | null;
64
+ providerCustomerRef?: string | null;
65
+ providerMethodRef: string;
66
+ providerMandateRef?: string | null;
67
+ methodKind: "card";
68
+ status: "active";
69
+ consentSnapshot?: Record<string, unknown>;
70
+ } | null;
71
+ }
72
+ export interface ProviderReconciliationStatus {
73
+ status: "succeeded" | "failed" | "pending" | "unknown";
74
+ providerStatus: string;
75
+ occurredAt: string | null;
76
+ failureReason: string | null;
77
+ amountMinor: number | null;
78
+ currency: string | null;
79
+ rawPayload: Record<string, unknown>;
80
+ }
81
+ export interface ClaimedPaymentAttempt {
82
+ paymentAttemptId: string;
83
+ paymentIntentId: string;
84
+ paymentId: string;
85
+ orderId: string;
86
+ subscriptionId: string | null;
87
+ subscriptionCycleId: string | null;
88
+ provider: string;
89
+ providerPaymentId: string | null;
90
+ providerAttemptId: string | null;
91
+ providerSessionId: string | null;
92
+ attemptStatus: string;
93
+ intentStatus: string;
94
+ amountMinor: number;
95
+ currency: string;
96
+ orderMode: "one_time" | "subscription_cycle";
97
+ cycleRetryAttempt: number;
98
+ cycleNextRetryAt: string | null;
99
+ localUpdatedAt: string;
100
+ }
101
+ export type ProviderRecoveryAction = {
102
+ kind: "redirect";
103
+ url: string;
104
+ };
105
+ export interface PaymentProviderRecoveryProvider {
106
+ resolvePaymentReference(input: {
107
+ providerAttemptId: string | null;
108
+ intentProviderPaymentId: string | null;
109
+ providerSessionId: string | null;
110
+ }): string | null;
111
+ readPayment(input: {
112
+ providerPaymentId: string;
113
+ attempt?: ClaimedPaymentAttempt;
114
+ purpose?: ProviderReadPurpose;
115
+ }): Promise<ProviderReconciliationStatus>;
116
+ readRecoveryPayment(input: {
117
+ providerPaymentId: string;
118
+ attempt: Pick<ClaimedPaymentAttempt, "amountMinor" | "currency">;
119
+ purpose?: ProviderReadPurpose;
120
+ }): Promise<{
121
+ status: ProviderReconciliationStatus;
122
+ identityMatches: boolean;
123
+ configuredMoneyMatches: boolean;
124
+ manualReviewRequired: boolean;
125
+ clientAction: ProviderRecoveryAction | null;
126
+ }>;
127
+ closePayment?(input: {
128
+ providerPaymentId: string;
129
+ }): Promise<ProviderReconciliationStatus>;
130
+ findPaymentByLocalIntent?(input: {
131
+ attempt: ClaimedPaymentAttempt;
132
+ }): Promise<"found" | "absent">;
133
+ }
134
+ export interface TpayTransientProviderInput {
135
+ provider: "tpay";
136
+ flow: "blik_one_time" | "blik_one_click" | "pbl_one_time" | "blik_recurring_activation" | "recurring_charge";
137
+ blikToken?: string;
138
+ channelId?: string;
139
+ recurringModel?: "O" | "M";
140
+ }
@@ -0,0 +1 @@
1
+ export const PAYMENT_NEXT_ACTION_KINDS = ["redirect", "3ds_challenge", "sca_required"];
@@ -0,0 +1,43 @@
1
+ import type { PaymentAdapter } from "../contracts/index.js";
2
+ export { tpayPaymentCapabilities } from "./capabilities.js";
3
+ import { type TpayJwsCertificateResolver } from "./webhookJwsVerifier.js";
4
+ /** Explicit host configuration. clock returns UTC seconds; imports perform no I/O. */
5
+ export interface TpayPaymentAdapterConfig {
6
+ http: {
7
+ baseUrl: string;
8
+ clientId: string;
9
+ clientSecret: string;
10
+ fetchImpl: (url: string, init?: {
11
+ method?: string;
12
+ headers?: Record<string, string>;
13
+ body?: string;
14
+ signal?: AbortSignal;
15
+ }) => Promise<{
16
+ ok: boolean;
17
+ status: number;
18
+ json(): Promise<unknown>;
19
+ text(): Promise<string>;
20
+ }>;
21
+ timeoutMs?: number;
22
+ };
23
+ notification: {
24
+ rootCertificatePem: string;
25
+ /** HTTPS certificate directory ending in a slash; resolver must enforce redirect/egress policy. */
26
+ allowedCertificateUrlPrefix: string;
27
+ resolveCertificate: TpayJwsCertificateResolver;
28
+ /** Seven days by default: generous retry allowance assumed until provider schedule is established. */
29
+ maxNotificationAgeSeconds?: number;
30
+ /** Maximum future signed issue time relative to receipt; defaults to 300 seconds. */
31
+ maxClockSkewSeconds?: number;
32
+ };
33
+ urls: {
34
+ notificationUrl: string;
35
+ successUrl: string;
36
+ errorUrl: string;
37
+ };
38
+ modelOActivationEnabled: boolean;
39
+ mode: "live" | "test";
40
+ clock?: () => number;
41
+ }
42
+ /** Construction explicitly selects this adapter; the host owns account activation and I/O policy. */
43
+ export declare function createTpayPaymentAdapter(config: TpayPaymentAdapterConfig): PaymentAdapter;