@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.
- package/CHANGELOG.md +62 -231
- package/README.md +172 -6
- package/dist/composition/composeBundle.d.ts +6 -0
- package/dist/composition/composeBundle.js +2 -0
- package/dist/composition/index.js +1 -0
- package/dist/payments/adapterConformance.d.ts +8 -0
- package/dist/payments/adapterConformance.js +109 -0
- package/dist/payments/bindings.d.ts +3 -0
- package/dist/payments/bindings.js +17 -0
- package/dist/payments/checkoutActivePaymentActionResolver.d.ts +7 -0
- package/dist/payments/checkoutActivePaymentActionResolver.js +77 -0
- package/dist/payments/checkoutRecoveryPaymentResolver.d.ts +8 -0
- package/dist/payments/checkoutRecoveryPaymentResolver.js +87 -0
- package/dist/payments/contracts/adapter.d.ts +113 -0
- package/dist/payments/contracts/adapter.js +1 -0
- package/dist/payments/contracts/conformance.d.ts +155 -0
- package/dist/payments/contracts/conformance.js +1 -0
- package/dist/payments/contracts/evidence.d.ts +157 -0
- package/dist/payments/contracts/evidence.js +1 -0
- package/dist/payments/contracts/index.d.ts +10 -0
- package/dist/payments/contracts/index.js +1 -0
- package/dist/payments/contracts/preparedAttempt.d.ts +69 -0
- package/dist/payments/contracts/preparedAttempt.js +1 -0
- package/dist/payments/contracts/reconciliation.d.ts +303 -0
- package/dist/payments/contracts/reconciliation.js +1 -0
- package/dist/payments/contracts/repair.d.ts +92 -0
- package/dist/payments/contracts/repair.js +1 -0
- package/dist/payments/contracts/truth.d.ts +92 -0
- package/dist/payments/contracts/truth.js +1 -0
- package/dist/payments/eventConformanceRunner.d.ts +12 -0
- package/dist/payments/eventConformanceRunner.js +125 -0
- package/dist/payments/index.d.ts +16 -0
- package/dist/payments/index.js +16 -0
- package/dist/payments/paymentAttemptSilenceWindow.d.ts +10 -0
- package/dist/payments/paymentAttemptSilenceWindow.js +19 -0
- package/dist/payments/paymentMethodLifecycle.d.ts +11 -0
- package/dist/payments/paymentMethodLifecycle.js +37 -0
- package/dist/payments/paymentProviderReconciliationAbsenceReopen.d.ts +7 -0
- package/dist/payments/paymentProviderReconciliationAbsenceReopen.js +100 -0
- package/dist/payments/paymentProviderReconciliationEvidence.d.ts +36 -0
- package/dist/payments/paymentProviderReconciliationEvidence.js +91 -0
- package/dist/payments/paymentProviderReconciliationWorker.d.ts +4 -0
- package/dist/payments/paymentProviderReconciliationWorker.js +245 -0
- package/dist/payments/paymentTruth.d.ts +8 -0
- package/dist/payments/paymentTruth.js +125 -0
- package/dist/payments/paymentVerifyNowHandler.d.ts +27 -0
- package/dist/payments/paymentVerifyNowHandler.js +70 -0
- package/dist/payments/paymentVerifyNowService.d.ts +23 -0
- package/dist/payments/paymentVerifyNowService.js +93 -0
- package/dist/payments/paymentWebhookPostProcessing.d.ts +22 -0
- package/dist/payments/paymentWebhookPostProcessing.js +15 -0
- package/dist/payments/postgresPaymentTruth.d.ts +10 -0
- package/dist/payments/postgresPaymentTruth.js +126 -0
- package/dist/payments/preparedAttempt.d.ts +6 -0
- package/dist/payments/preparedAttempt.js +70 -0
- package/dist/payments/preparedAttemptFinalization.d.ts +5 -0
- package/dist/payments/preparedAttemptFinalization.js +12 -0
- package/dist/payments/schemaCapabilities.d.ts +10 -0
- package/dist/payments/schemaCapabilities.js +10 -0
- package/dist/payments/sql.d.ts +20 -0
- package/dist/payments/sql.js +6 -0
- package/dist/payments/stripe/classifyDecline.d.ts +46 -0
- package/dist/payments/stripe/classifyDecline.js +58 -0
- package/dist/payments/stripe/declineFailureHints.d.ts +46 -0
- package/dist/payments/stripe/declineFailureHints.js +87 -0
- package/dist/payments/stripe/donorContracts.d.ts +120 -0
- package/dist/payments/stripe/donorContracts.js +1 -0
- package/dist/payments/stripe/executionDecline.d.ts +53 -0
- package/dist/payments/stripe/executionDecline.js +107 -0
- package/dist/payments/stripe/index.d.ts +16 -0
- package/dist/payments/stripe/index.js +239 -0
- package/dist/payments/stripe/intentMapper.d.ts +26 -0
- package/dist/payments/stripe/intentMapper.js +51 -0
- package/dist/payments/stripe/methodFacts.d.ts +10 -0
- package/dist/payments/stripe/methodFacts.js +12 -0
- package/dist/payments/stripe/paymentFailureEvidence.d.ts +26 -0
- package/dist/payments/stripe/paymentFailureEvidence.js +33 -0
- package/dist/payments/stripe/stripeFailureEvidence.d.ts +41 -0
- package/dist/payments/stripe/stripeFailureEvidence.js +82 -0
- package/dist/payments/stripe/stripeIntentConfirmationEvidence.d.ts +28 -0
- package/dist/payments/stripe/stripeIntentConfirmationEvidence.js +32 -0
- package/dist/payments/stripe/stripePaymentReconciliationProvider.d.ts +35 -0
- package/dist/payments/stripe/stripePaymentReconciliationProvider.js +222 -0
- package/dist/payments/stripe/stripeRefundEvidence.d.ts +8 -0
- package/dist/payments/stripe/stripeRefundEvidence.js +17 -0
- package/dist/payments/stripe/stripeSandboxPaymentExecutionAdapter.d.ts +45 -0
- package/dist/payments/stripe/stripeSandboxPaymentExecutionAdapter.js +220 -0
- package/dist/payments/stripe/stripeWebhookNormalizer.d.ts +14 -0
- package/dist/payments/stripe/stripeWebhookNormalizer.js +243 -0
- package/dist/payments/stripe/webhookSignature.d.ts +14 -0
- package/dist/payments/stripe/webhookSignature.js +43 -0
- package/dist/payments/tpay/blikRecurringMapper.d.ts +37 -0
- package/dist/payments/tpay/blikRecurringMapper.js +24 -0
- package/dist/payments/tpay/capabilities.d.ts +3 -0
- package/dist/payments/tpay/capabilities.js +18 -0
- package/dist/payments/tpay/classifyDecline.d.ts +46 -0
- package/dist/payments/tpay/classifyDecline.js +58 -0
- package/dist/payments/tpay/declineFailureHints.d.ts +72 -0
- package/dist/payments/tpay/declineFailureHints.js +37 -0
- package/dist/payments/tpay/donorContracts.d.ts +140 -0
- package/dist/payments/tpay/donorContracts.js +1 -0
- package/dist/payments/tpay/index.d.ts +43 -0
- package/dist/payments/tpay/index.js +264 -0
- package/dist/payments/tpay/paymentFailureEvidence.d.ts +26 -0
- package/dist/payments/tpay/paymentFailureEvidence.js +33 -0
- package/dist/payments/tpay/preparedProviderAttempt.d.ts +22 -0
- package/dist/payments/tpay/preparedProviderAttempt.js +24 -0
- package/dist/payments/tpay/providerAttemptFailureDiagnostic.d.ts +3 -0
- package/dist/payments/tpay/providerAttemptFailureDiagnostic.js +95 -0
- package/dist/payments/tpay/tpayCreateTransactionInput.d.ts +25 -0
- package/dist/payments/tpay/tpayCreateTransactionInput.js +128 -0
- package/dist/payments/tpay/tpayDeclineEvidence.d.ts +126 -0
- package/dist/payments/tpay/tpayDeclineEvidence.js +177 -0
- package/dist/payments/tpay/tpayDispatchFailure.d.ts +60 -0
- package/dist/payments/tpay/tpayDispatchFailure.js +121 -0
- package/dist/payments/tpay/tpayFailureEvidence.d.ts +20 -0
- package/dist/payments/tpay/tpayFailureEvidence.js +61 -0
- package/dist/payments/tpay/tpayHttpClient.d.ts +52 -0
- package/dist/payments/tpay/tpayHttpClient.js +185 -0
- package/dist/payments/tpay/tpayHttpFailure.d.ts +57 -0
- package/dist/payments/tpay/tpayHttpFailure.js +143 -0
- package/dist/payments/tpay/tpayPaymentExecutionAdapter.d.ts +20 -0
- package/dist/payments/tpay/tpayPaymentExecutionAdapter.js +249 -0
- package/dist/payments/tpay/tpayPaymentReconciliationProvider.d.ts +5 -0
- package/dist/payments/tpay/tpayPaymentReconciliationProvider.js +244 -0
- package/dist/payments/tpay/tpayTransactionPayload.d.ts +120 -0
- package/dist/payments/tpay/tpayTransactionPayload.js +96 -0
- package/dist/payments/tpay/wallClockTime.d.ts +31 -0
- package/dist/payments/tpay/wallClockTime.js +57 -0
- package/dist/payments/tpay/webhookJwsVerifier.d.ts +17 -0
- package/dist/payments/tpay/webhookJwsVerifier.js +51 -0
- package/dist/payments/tpay/webhookNormalizer.d.ts +54 -0
- package/dist/payments/tpay/webhookNormalizer.js +200 -0
- package/dist/payments/verifiedEvidenceProjection.d.ts +12 -0
- package/dist/payments/verifiedEvidenceProjection.js +166 -0
- package/dist/payments/verifiedIngress.d.ts +119 -0
- package/dist/payments/verifiedIngress.js +220 -0
- package/package.json +22 -2
- package/release-gates.json +64 -3
- package/sql/20260810180000_payment_settlement_rail.sql +574 -0
- package/sql/20260815150000_payment_truth_reconciliation.sql +479 -0
- package/sql/20260826100000_late_payment_shifts_the_subscription_cycle.sql +215 -0
- package/src/composition/composeBundle.ts +9 -0
- package/src/composition/index.ts +1 -0
- package/src/payments/adapterConformance.ts +118 -0
- package/src/payments/bindings.ts +17 -0
- package/src/payments/checkoutActivePaymentActionResolver.ts +97 -0
- package/src/payments/checkoutRecoveryPaymentResolver.ts +108 -0
- package/src/payments/contracts/adapter.ts +96 -0
- package/src/payments/contracts/conformance.ts +171 -0
- package/src/payments/contracts/evidence.ts +142 -0
- package/src/payments/contracts/index.ts +54 -0
- package/src/payments/contracts/preparedAttempt.ts +86 -0
- package/src/payments/contracts/reconciliation.ts +296 -0
- package/src/payments/contracts/repair.ts +95 -0
- package/src/payments/contracts/truth.ts +108 -0
- package/src/payments/eventConformanceRunner.ts +149 -0
- package/src/payments/index.ts +21 -0
- package/src/payments/paymentAttemptSilenceWindow.ts +26 -0
- package/src/payments/paymentMethodLifecycle.ts +50 -0
- package/src/payments/paymentProviderReconciliationAbsenceReopen.ts +130 -0
- package/src/payments/paymentProviderReconciliationEvidence.ts +117 -0
- package/src/payments/paymentProviderReconciliationWorker.ts +291 -0
- package/src/payments/paymentTruth.ts +131 -0
- package/src/payments/paymentVerifyNowHandler.ts +94 -0
- package/src/payments/paymentVerifyNowService.ts +125 -0
- package/src/payments/paymentWebhookPostProcessing.ts +36 -0
- package/src/payments/postgresPaymentTruth.ts +149 -0
- package/src/payments/preparedAttempt.ts +90 -0
- package/src/payments/preparedAttemptFinalization.ts +13 -0
- package/src/payments/schemaCapabilities.ts +10 -0
- package/src/payments/sql.ts +6 -0
- package/src/payments/stripe/classifyDecline.ts +62 -0
- package/src/payments/stripe/declineFailureHints.ts +89 -0
- package/src/payments/stripe/donorContracts.ts +87 -0
- package/src/payments/stripe/executionDecline.ts +126 -0
- package/src/payments/stripe/index.ts +235 -0
- package/src/payments/stripe/intentMapper.ts +58 -0
- package/src/payments/stripe/methodFacts.ts +21 -0
- package/src/payments/stripe/paymentFailureEvidence.ts +58 -0
- package/src/payments/stripe/stripeFailureEvidence.ts +115 -0
- package/src/payments/stripe/stripeIntentConfirmationEvidence.ts +34 -0
- package/src/payments/stripe/stripePaymentReconciliationProvider.ts +263 -0
- package/src/payments/stripe/stripeRefundEvidence.ts +19 -0
- package/src/payments/stripe/stripeSandboxPaymentExecutionAdapter.ts +283 -0
- package/src/payments/stripe/stripeWebhookNormalizer.ts +277 -0
- package/src/payments/stripe/webhookSignature.ts +55 -0
- package/src/payments/tpay/blikRecurringMapper.ts +42 -0
- package/src/payments/tpay/capabilities.ts +21 -0
- package/src/payments/tpay/classifyDecline.ts +62 -0
- package/src/payments/tpay/declineFailureHints.ts +90 -0
- package/src/payments/tpay/donorContracts.ts +104 -0
- package/src/payments/tpay/index.ts +257 -0
- package/src/payments/tpay/paymentFailureEvidence.ts +58 -0
- package/src/payments/tpay/preparedProviderAttempt.ts +38 -0
- package/src/payments/tpay/providerAttemptFailureDiagnostic.ts +99 -0
- package/src/payments/tpay/tpayCreateTransactionInput.ts +158 -0
- package/src/payments/tpay/tpayDeclineEvidence.ts +233 -0
- package/src/payments/tpay/tpayDispatchFailure.ts +186 -0
- package/src/payments/tpay/tpayFailureEvidence.ts +75 -0
- package/src/payments/tpay/tpayHttpClient.ts +290 -0
- package/src/payments/tpay/tpayHttpFailure.ts +186 -0
- package/src/payments/tpay/tpayPaymentExecutionAdapter.ts +297 -0
- package/src/payments/tpay/tpayPaymentReconciliationProvider.ts +285 -0
- package/src/payments/tpay/tpayTransactionPayload.ts +207 -0
- package/src/payments/tpay/wallClockTime.ts +57 -0
- package/src/payments/tpay/webhookJwsVerifier.ts +82 -0
- package/src/payments/tpay/webhookNormalizer.ts +279 -0
- package/src/payments/verifiedEvidenceProjection.ts +179 -0
- 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;
|