@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,109 @@
|
|
|
1
|
+
import { projectVerifiedEvidence } from "./verifiedEvidenceProjection.js";
|
|
2
|
+
import { validatePaymentExecutionContractResult } from "@openlup/core/testing";
|
|
3
|
+
/** Runner-independent adapter proof. Verification fixtures are owned by each adapter. */
|
|
4
|
+
export async function runPaymentAdapterConformance(subjects) {
|
|
5
|
+
const observations = [];
|
|
6
|
+
const cases = [];
|
|
7
|
+
for (const subject of subjects) {
|
|
8
|
+
const { adapter, executionInput, readbackInput } = subject;
|
|
9
|
+
const provider = adapter.capabilities.providerKind;
|
|
10
|
+
requireFact(executionInput.money.currency.length > 0, "configured currency");
|
|
11
|
+
adapter.attemptPort.validateInput(executionInput);
|
|
12
|
+
const result = await adapter.attemptPort.execute(executionInput);
|
|
13
|
+
const { handoff, ...execution } = result;
|
|
14
|
+
const sensitiveValues = [executionInput.payer?.email, executionInput.payer?.name, executionInput.customerAuthorization,
|
|
15
|
+
handoff?.kind === "client-confirmed" ? handoff.payload : handoff?.url].filter((v) => v !== undefined && v.length > 0);
|
|
16
|
+
const sensitiveForms = sensitiveValues.flatMap(value => [value, JSON.stringify(value).slice(1, -1)]);
|
|
17
|
+
const payerForms = [executionInput.payer?.email, executionInput.payer?.name]
|
|
18
|
+
.filter((value) => Boolean(value?.length))
|
|
19
|
+
.flatMap(value => [value, JSON.stringify(value).slice(1, -1)]);
|
|
20
|
+
const input = {
|
|
21
|
+
idempotencyKey: executionInput.attemptRef, providerIdempotencyKey: executionInput.providerIdempotencyKey,
|
|
22
|
+
providerRequestFingerprint: executionInput.fingerprint, paymentIntentId: executionInput.intentRef,
|
|
23
|
+
amountMinor: executionInput.money.amountMinor, currency: executionInput.money.currency, mode: "one_time",
|
|
24
|
+
};
|
|
25
|
+
const validation = validatePaymentExecutionContractResult({ input, expectedProvider: provider, sensitiveValues: sensitiveForms,
|
|
26
|
+
result: { provider, providerAttemptId: result.dispatch.kind === "dispatched" ? result.dispatch.providerPaymentRef : null,
|
|
27
|
+
providerSessionId: null, attemptStatus: result.outcome.kind === "attempt" ? result.outcome.status : "processing",
|
|
28
|
+
nextActionKind: null, requestPayload: { providerIdempotencyKey: input.providerIdempotencyKey,
|
|
29
|
+
providerRequestFingerprint: input.providerRequestFingerprint }, responsePayload: result.diagnostics },
|
|
30
|
+
});
|
|
31
|
+
requireFact(validation.ok, "execution diagnostics and taxonomy");
|
|
32
|
+
requireFact(!payerForms.some(value => JSON.stringify(result).includes(value)), "execution payer redaction");
|
|
33
|
+
if (adapter.capabilities.payerContext.requiresPayerBlock && !executionInput.payer) {
|
|
34
|
+
requireFact(result.dispatch.kind === "notDispatched" && result.dispatch.evidence.exact
|
|
35
|
+
&& result.dispatch.evidence.phase === "before-dispatch", "missing required input exact notDispatched");
|
|
36
|
+
}
|
|
37
|
+
const returned = await adapter.readbackPort.readPayment(readbackInput);
|
|
38
|
+
const { handoff: readbackHandoff, ...readback } = "handoff" in returned ? returned : { ...returned, handoff: undefined };
|
|
39
|
+
requireFact(!sensitiveForms.some(value => JSON.stringify(readback).includes(value)), "readback secret redaction");
|
|
40
|
+
requireFact(sameIdentity(readback.identity, readbackInput.identity) && sameMoney(readback.expectedMoney, readbackInput.expectedMoney), "readback exact identity and money");
|
|
41
|
+
requireFact(adapter.capabilities.readback === "supported" ? returned.kind !== "unsupported" : returned.kind === "unsupported", "readback capability");
|
|
42
|
+
if ("observedMoney" in returned)
|
|
43
|
+
requireFact(sameMoney(returned.observedMoney, readbackInput.expectedMoney), "observed money");
|
|
44
|
+
if (adapter.capabilities.close === "supported") {
|
|
45
|
+
requireFact(typeof adapter.readbackPort.close === "function", "close method present");
|
|
46
|
+
const closed = await adapter.readbackPort.close(readbackInput);
|
|
47
|
+
requireFact(closed.kind !== "unsupported" && sameIdentity(closed.identity, readbackInput.identity)
|
|
48
|
+
&& sameMoney(closed.expectedMoney, readbackInput.expectedMoney), "close exact identity and money");
|
|
49
|
+
}
|
|
50
|
+
else if (adapter.readbackPort.close) {
|
|
51
|
+
requireFact((await adapter.readbackPort.close(readbackInput)).kind === "unsupported", "close refuses");
|
|
52
|
+
}
|
|
53
|
+
let absence;
|
|
54
|
+
if (adapter.capabilities.exactAbsence === "supported")
|
|
55
|
+
requireFact(adapter.absencePort !== undefined, "absence port present");
|
|
56
|
+
if (adapter.absencePort) {
|
|
57
|
+
requireFact(subject.absenceInput !== undefined, "absence input present");
|
|
58
|
+
absence = await adapter.absencePort.proveAbsence(subject.absenceInput);
|
|
59
|
+
requireFact(sameIdentity(absence.identity, subject.absenceInput.identity)
|
|
60
|
+
&& sameMoney(absence.expectedMoney, subject.absenceInput.expectedMoney), "absence exact identity and money");
|
|
61
|
+
requireFact(adapter.capabilities.exactAbsence === "supported" ? absence.kind !== "unsupported" : absence.kind === "unsupported", "absence capability");
|
|
62
|
+
if (absence.kind === "absent")
|
|
63
|
+
requireFact(absence.evidence.exact && absence.evidence.complete
|
|
64
|
+
&& absence.evidence.lookupReference.trim().length > 0, "complete absence evidence");
|
|
65
|
+
}
|
|
66
|
+
const fingerprints = new Map();
|
|
67
|
+
const moneyEvents = [];
|
|
68
|
+
for (const fixture of subject.verification) {
|
|
69
|
+
const verified = await adapter.verifyPort.verifyAndNormalize(fixture.input);
|
|
70
|
+
requireFact(JSON.stringify(verified) === JSON.stringify(fixture.expected), "verified fixture");
|
|
71
|
+
if (verified.kind !== "refusal") {
|
|
72
|
+
requireFact(verified.kind !== "money" || (verified.money !== null
|
|
73
|
+
&& Number.isSafeInteger(verified.money.amountMinor) && verified.money.amountMinor > 0
|
|
74
|
+
&& verified.money.currency.trim().length > 0)
|
|
75
|
+
|| (verified.outcome !== "succeeded" && verified.outcome !== "refunded" && verified.outcome !== "disputed"), "settling outcome requires positive money");
|
|
76
|
+
requireFact(!sensitiveForms.some(value => JSON.stringify({ payload: projectVerifiedEvidence(verified, fixture.input, "require-evidence").rawPayload, diagnostics: result.diagnostics }).includes(value)), "audit payload secret redaction");
|
|
77
|
+
const outcome = verified.kind + ":" + verified.outcome;
|
|
78
|
+
const previous = fingerprints.get(verified.providerEventId);
|
|
79
|
+
if (previous)
|
|
80
|
+
requireFact(previous.outcome === outcome && previous.fingerprint === verified.fingerprint, "replay identity fingerprint");
|
|
81
|
+
if (verified.kind === "money") {
|
|
82
|
+
for (const event of moneyEvents) {
|
|
83
|
+
if (event.reference === verified.providerPaymentRef && event.outcome !== outcome) {
|
|
84
|
+
requireFact(event.eventId !== verified.providerEventId && event.fingerprint !== verified.fingerprint, "changing outcome identity");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
moneyEvents.push({ reference: verified.providerPaymentRef, outcome, eventId: verified.providerEventId, fingerprint: verified.fingerprint });
|
|
88
|
+
}
|
|
89
|
+
fingerprints.set(verified.providerEventId, { fingerprint: verified.fingerprint, outcome });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Transient handoffs are deliberately excluded from the returned evidence.
|
|
93
|
+
void readbackHandoff;
|
|
94
|
+
observations.push({ subjectId: subject.subjectId, execution, readback, ...(absence ? { absence } : {}) });
|
|
95
|
+
cases.push({ subjectIds: [subject.subjectId], name: "execution, verification, capability and exact readback", passed: true });
|
|
96
|
+
}
|
|
97
|
+
return { subjects: observations, cases };
|
|
98
|
+
}
|
|
99
|
+
function requireFact(value, label) {
|
|
100
|
+
if (!value)
|
|
101
|
+
throw new Error(`payment_adapter_conformance:${label}`);
|
|
102
|
+
}
|
|
103
|
+
function sameMoney(left, right) {
|
|
104
|
+
return left.amountMinor === right.amountMinor && left.currency === right.currency;
|
|
105
|
+
}
|
|
106
|
+
function sameIdentity(left, right) {
|
|
107
|
+
return left.provider === right.provider && left.providerPaymentRef === right.providerPaymentRef
|
|
108
|
+
&& left.intentRef === right.intentRef && left.attemptRef === right.attemptRef;
|
|
109
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** One host-owned scope per runtime unit; no driver or environment discovery. */
|
|
2
|
+
export function createPaymentServices(options) {
|
|
3
|
+
if (options.schema?.truth !== "supported" || !options.openTruth)
|
|
4
|
+
return null;
|
|
5
|
+
const openTruth = options.openTruth;
|
|
6
|
+
return {
|
|
7
|
+
async run(work) {
|
|
8
|
+
const scope = await openTruth();
|
|
9
|
+
try {
|
|
10
|
+
return await work(scope.port);
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
await scope.close();
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CheckoutActivePaymentActionResolver, CheckoutActionReadProvider } from "./contracts/repair.js";
|
|
2
|
+
import type { PaymentVerifyReadPort } from "./contracts/reconciliation.js";
|
|
3
|
+
export declare function createCheckoutActivePaymentActionResolver(deps: {
|
|
4
|
+
readPort: PaymentVerifyReadPort;
|
|
5
|
+
providers: Partial<Record<string, CheckoutActionReadProvider>>;
|
|
6
|
+
railHandoffKinds(rail: string): readonly ("redirect" | "client-confirmed")[];
|
|
7
|
+
}): CheckoutActivePaymentActionResolver;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { toClaimedAttemptShape } from "./paymentVerifyNowService.js";
|
|
2
|
+
const LIVE_INTENT_STATUSES = new Set(["requires_action", "processing"]);
|
|
3
|
+
const LIVE_ATTEMPT_STATUSES = new Set(["sent_to_provider", "requires_action", "processing"]);
|
|
4
|
+
export function createCheckoutActivePaymentActionResolver(deps) {
|
|
5
|
+
return {
|
|
6
|
+
async readActiveAction(authority) {
|
|
7
|
+
try {
|
|
8
|
+
const initial = await deps.readPort.readVerifiableAttempt({
|
|
9
|
+
orderId: authority.orderId,
|
|
10
|
+
paymentIntentId: authority.paymentIntentId,
|
|
11
|
+
});
|
|
12
|
+
if (!matchesAuthority(initial, authority) || !isLive(initial))
|
|
13
|
+
return null;
|
|
14
|
+
const provider = deps.providers[authority.executionRail];
|
|
15
|
+
const providerPaymentId = provider?.resolvePaymentReference(initial) ?? null;
|
|
16
|
+
if (!provider || !providerPaymentId)
|
|
17
|
+
return null;
|
|
18
|
+
const attempt = toClaimedAttemptShape(initial, providerPaymentId);
|
|
19
|
+
const reading = await provider.readRecoveryPayment({
|
|
20
|
+
providerPaymentId,
|
|
21
|
+
attempt,
|
|
22
|
+
purpose: "active_checkout",
|
|
23
|
+
});
|
|
24
|
+
if (!reading.clientAction
|
|
25
|
+
|| !actionMatchesRail(reading.clientAction, authority.executionRail, deps.railHandoffKinds)
|
|
26
|
+
|| reading.manualReviewRequired
|
|
27
|
+
|| !reading.identityMatches
|
|
28
|
+
|| !reading.configuredMoneyMatches
|
|
29
|
+
|| reading.status.status !== "pending")
|
|
30
|
+
return null;
|
|
31
|
+
const current = await deps.readPort.readVerifiableAttempt({
|
|
32
|
+
orderId: authority.orderId,
|
|
33
|
+
paymentIntentId: authority.paymentIntentId,
|
|
34
|
+
});
|
|
35
|
+
if (!sameCanonicalAttempt(initial, current) || !isLive(current))
|
|
36
|
+
return null;
|
|
37
|
+
return reading.clientAction;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function matchesAuthority(snapshot, authority) {
|
|
46
|
+
return Boolean(snapshot)
|
|
47
|
+
&& snapshot?.orderId === authority.orderId
|
|
48
|
+
&& snapshot.orderClientId === authority.clientId
|
|
49
|
+
&& snapshot.paymentIntentId === authority.paymentIntentId
|
|
50
|
+
&& snapshot.paymentAttemptId === authority.paymentAttemptId
|
|
51
|
+
&& snapshot.provider === authority.executionRail;
|
|
52
|
+
}
|
|
53
|
+
function isLive(snapshot) {
|
|
54
|
+
return Boolean(snapshot)
|
|
55
|
+
&& LIVE_INTENT_STATUSES.has(snapshot?.intentStatus ?? "")
|
|
56
|
+
&& LIVE_ATTEMPT_STATUSES.has(snapshot?.attemptStatus ?? "");
|
|
57
|
+
}
|
|
58
|
+
function actionMatchesRail(action, rail, railHandoffKinds) {
|
|
59
|
+
return action.provider === rail && railHandoffKinds(rail).includes(action.kind);
|
|
60
|
+
}
|
|
61
|
+
function sameCanonicalAttempt(before, after) {
|
|
62
|
+
return Boolean(after)
|
|
63
|
+
&& after?.orderId === before.orderId
|
|
64
|
+
&& after.orderClientId === before.orderClientId
|
|
65
|
+
&& after.paymentIntentId === before.paymentIntentId
|
|
66
|
+
&& after.paymentAttemptId === before.paymentAttemptId
|
|
67
|
+
&& after.paymentId === before.paymentId
|
|
68
|
+
&& after.provider === before.provider
|
|
69
|
+
&& after.providerAttemptId === before.providerAttemptId
|
|
70
|
+
&& after.providerSessionId === before.providerSessionId
|
|
71
|
+
&& after.intentProviderPaymentId === before.intentProviderPaymentId
|
|
72
|
+
&& after.intentStatus === before.intentStatus
|
|
73
|
+
&& after.attemptStatus === before.attemptStatus
|
|
74
|
+
&& after.amountMinor === before.amountMinor
|
|
75
|
+
&& after.currency.toUpperCase() === before.currency.toUpperCase()
|
|
76
|
+
&& after.localUpdatedAt === before.localUpdatedAt;
|
|
77
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CheckoutRecoveryPaymentResolver, PaymentProviderRecoveryProvider } from "./contracts/repair.js";
|
|
2
|
+
import type { PaymentVerifyReadPort, PaymentProviderReconciliationPort } from "./contracts/reconciliation.js";
|
|
3
|
+
export declare function createCheckoutRecoveryPaymentResolver(deps: {
|
|
4
|
+
readPort: PaymentVerifyReadPort;
|
|
5
|
+
applyPort: Pick<PaymentProviderReconciliationPort, "applyTerminalResult">;
|
|
6
|
+
providers: Partial<Record<string, PaymentProviderRecoveryProvider>>;
|
|
7
|
+
now?: () => Date;
|
|
8
|
+
}): CheckoutRecoveryPaymentResolver;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { terminalAmountMismatch } from "./paymentProviderReconciliationEvidence.js";
|
|
2
|
+
import { toClaimedAttemptShape, verifyPaymentAttemptNow } from "./paymentVerifyNowService.js";
|
|
3
|
+
const LIVE_ATTEMPT_STATUSES = new Set(["sent_to_provider", "requires_action", "processing", "created"]);
|
|
4
|
+
export function createCheckoutRecoveryPaymentResolver(deps) {
|
|
5
|
+
const now = deps.now ?? (() => new Date());
|
|
6
|
+
return {
|
|
7
|
+
async resolve(input) {
|
|
8
|
+
if (!input.paymentIntentId)
|
|
9
|
+
return { kind: "retry_new" };
|
|
10
|
+
const snapshot = await deps.readPort.readVerifiableAttempt({
|
|
11
|
+
orderId: input.orderId,
|
|
12
|
+
paymentIntentId: input.paymentIntentId,
|
|
13
|
+
});
|
|
14
|
+
if (!snapshot) {
|
|
15
|
+
return input.intentStatus === "created" || input.intentStatus === "failed"
|
|
16
|
+
? { kind: "retry_new" }
|
|
17
|
+
: { kind: "awaiting_provider" };
|
|
18
|
+
}
|
|
19
|
+
return resolveSnapshot(snapshot, deps, now);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function resolveSnapshot(snapshot, deps, now) {
|
|
24
|
+
if (snapshot.attemptStatus === "succeeded" || snapshot.intentStatus === "succeeded") {
|
|
25
|
+
return { kind: "paid" };
|
|
26
|
+
}
|
|
27
|
+
if (snapshot.attemptStatus === "failed" || snapshot.intentStatus === "failed") {
|
|
28
|
+
return { kind: "retry_new" };
|
|
29
|
+
}
|
|
30
|
+
if (!LIVE_ATTEMPT_STATUSES.has(snapshot.attemptStatus)) {
|
|
31
|
+
return { kind: "awaiting_provider" };
|
|
32
|
+
}
|
|
33
|
+
const provider = deps.providers[snapshot.provider];
|
|
34
|
+
const providerPaymentId = provider?.resolvePaymentReference(snapshot) ?? null;
|
|
35
|
+
if (!provider || !providerPaymentId)
|
|
36
|
+
return { kind: "awaiting_provider" };
|
|
37
|
+
const attempt = toClaimedAttemptShape(snapshot, providerPaymentId);
|
|
38
|
+
try {
|
|
39
|
+
// Every caller of this resolver is a buyer holding a recovery link open, so
|
|
40
|
+
// an intent still waiting for its first payment method is one they can be
|
|
41
|
+
// handed back — not one to close under them and replace.
|
|
42
|
+
const recovery = await provider.readRecoveryPayment({
|
|
43
|
+
providerPaymentId,
|
|
44
|
+
attempt,
|
|
45
|
+
purpose: "active_checkout",
|
|
46
|
+
});
|
|
47
|
+
if (recovery.manualReviewRequired
|
|
48
|
+
|| !recovery.identityMatches
|
|
49
|
+
|| !recovery.configuredMoneyMatches) {
|
|
50
|
+
return { kind: "manual_review" };
|
|
51
|
+
}
|
|
52
|
+
if (recovery.clientAction) {
|
|
53
|
+
return { kind: "resume_existing", clientAction: recovery.clientAction };
|
|
54
|
+
}
|
|
55
|
+
if (recovery.status.status === "pending" || recovery.status.status === "unknown") {
|
|
56
|
+
return { kind: "awaiting_provider" };
|
|
57
|
+
}
|
|
58
|
+
if (terminalAmountMismatch(attempt, recovery.status)) {
|
|
59
|
+
return { kind: "manual_review" };
|
|
60
|
+
}
|
|
61
|
+
const verified = await verifyPaymentAttemptNow({
|
|
62
|
+
snapshot,
|
|
63
|
+
applyPort: deps.applyPort,
|
|
64
|
+
providers: deps.providers,
|
|
65
|
+
providerPaymentId,
|
|
66
|
+
providerStatus: recovery.status,
|
|
67
|
+
now,
|
|
68
|
+
});
|
|
69
|
+
if (verified.status !== "succeeded" && verified.status !== "failed") {
|
|
70
|
+
return { kind: "awaiting_provider" };
|
|
71
|
+
}
|
|
72
|
+
const current = await deps.readPort.readVerifiableAttempt({
|
|
73
|
+
orderId: snapshot.orderId,
|
|
74
|
+
paymentIntentId: snapshot.paymentIntentId,
|
|
75
|
+
});
|
|
76
|
+
if (current?.attemptStatus === "succeeded" || current?.intentStatus === "succeeded") {
|
|
77
|
+
return { kind: "paid" };
|
|
78
|
+
}
|
|
79
|
+
if (current?.attemptStatus === "failed" || current?.intentStatus === "failed") {
|
|
80
|
+
return { kind: "retry_new" };
|
|
81
|
+
}
|
|
82
|
+
return verified.status === "succeeded" ? { kind: "paid" } : { kind: "retry_new" };
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return { kind: "awaiting_provider" };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { PaymentExecutionAttemptStatus, PaymentFailureClass, PaymentFailureHint, PaymentProviderCapabilityDescriptor, PaymentRetryAdviceCode, StoredMandateSnapshot } from "@openlup/core/payment";
|
|
2
|
+
import type { PaymentAbsencePort, PaymentReadbackPort, PaymentVerifyNormalizePort } from "./evidence.js";
|
|
3
|
+
declare const transientSecret: unique symbol;
|
|
4
|
+
/**
|
|
5
|
+
* One-time customer authorization or opaque client handoff, held only in memory.
|
|
6
|
+
* Never persist, log, fingerprint, or include in request/response diagnostics.
|
|
7
|
+
* The host brands a value only at the transient client/server input boundary.
|
|
8
|
+
*/
|
|
9
|
+
export type TransientSecret = string & {
|
|
10
|
+
readonly [transientSecret]: true;
|
|
11
|
+
};
|
|
12
|
+
/** Hosted navigation or client confirmation; opaque payloads are transient only. */
|
|
13
|
+
export type PaymentMethodHandoff = {
|
|
14
|
+
kind: "redirect";
|
|
15
|
+
url: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "client-confirmed";
|
|
18
|
+
payload: TransientSecret;
|
|
19
|
+
};
|
|
20
|
+
/** Only core neutral taxonomy crosses the adapter boundary; no provider prose. */
|
|
21
|
+
export interface PaymentRefusalEnvelope {
|
|
22
|
+
code: PaymentFailureClass;
|
|
23
|
+
hints: readonly PaymentFailureHint[];
|
|
24
|
+
adviceCode?: PaymentRetryAdviceCode;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A non-terminal attempt or synchronous decline; terminal money is evidence-owned.
|
|
28
|
+
* Diagnostics must exclude transient secrets, handoff payloads, raw bodies and
|
|
29
|
+
* provider prose. Persist only explicitly redacted request/response diagnostics.
|
|
30
|
+
*/
|
|
31
|
+
export interface PaymentExecutionEnvelope {
|
|
32
|
+
outcome: {
|
|
33
|
+
kind: "attempt";
|
|
34
|
+
status: PaymentExecutionAttemptStatus;
|
|
35
|
+
} | {
|
|
36
|
+
kind: "decline";
|
|
37
|
+
refusal: PaymentRefusalEnvelope;
|
|
38
|
+
};
|
|
39
|
+
dispatch: {
|
|
40
|
+
kind: "dispatched";
|
|
41
|
+
providerPaymentRef: string;
|
|
42
|
+
} | {
|
|
43
|
+
kind: "notDispatched";
|
|
44
|
+
evidence: {
|
|
45
|
+
exact: true;
|
|
46
|
+
phase: "before-dispatch";
|
|
47
|
+
reason: PaymentRefusalEnvelope;
|
|
48
|
+
};
|
|
49
|
+
} | {
|
|
50
|
+
kind: "unknown";
|
|
51
|
+
};
|
|
52
|
+
handoff?: PaymentMethodHandoff;
|
|
53
|
+
diagnostics: Readonly<Record<string, string | number | boolean | null>>;
|
|
54
|
+
}
|
|
55
|
+
/** Host identity/key/fingerprint and configured integer-minor money pass verbatim.
|
|
56
|
+
* Missing adapter-required input must refuse before dispatch with exact
|
|
57
|
+
* notDispatched evidence (phase before-dispatch), never unknown or dispatched.
|
|
58
|
+
*/
|
|
59
|
+
export interface PaymentAttemptPort {
|
|
60
|
+
validateInput(input: {
|
|
61
|
+
attemptRef: string;
|
|
62
|
+
intentRef: string;
|
|
63
|
+
fingerprint: string;
|
|
64
|
+
providerIdempotencyKey: string;
|
|
65
|
+
money: {
|
|
66
|
+
amountMinor: number;
|
|
67
|
+
currency: string;
|
|
68
|
+
};
|
|
69
|
+
mode: "live" | "test";
|
|
70
|
+
purpose: "initial" | "renewal-unattended" | "recovery";
|
|
71
|
+
methodSelection?: {
|
|
72
|
+
family: "card" | "wallet" | "bank-redirect" | "one-time-code";
|
|
73
|
+
channelRef?: string;
|
|
74
|
+
};
|
|
75
|
+
/** Transient dispatch input only; never persist, audit, log or diagnose. */
|
|
76
|
+
payer?: {
|
|
77
|
+
email: string;
|
|
78
|
+
name?: string;
|
|
79
|
+
};
|
|
80
|
+
storedMethod?: {
|
|
81
|
+
reference: string;
|
|
82
|
+
mandate: StoredMandateSnapshot;
|
|
83
|
+
};
|
|
84
|
+
/** Client-entered, server-submitted authorization; never persisted or echoed. */
|
|
85
|
+
customerAuthorization?: TransientSecret;
|
|
86
|
+
}): void;
|
|
87
|
+
execute(input: Parameters<PaymentAttemptPort["validateInput"]>[0]): Promise<PaymentExecutionEnvelope>;
|
|
88
|
+
}
|
|
89
|
+
/** Platform overlay; the shared core descriptor remains unchanged. */
|
|
90
|
+
export interface PaymentAdapterCapabilities extends Omit<PaymentProviderCapabilityDescriptor, "captureFlows"> {
|
|
91
|
+
captureFlows: readonly {
|
|
92
|
+
kind: string;
|
|
93
|
+
handoff: PaymentMethodHandoff["kind"] | "customer-authorization";
|
|
94
|
+
methodFamilies: readonly ("card" | "wallet" | "bank-redirect" | "one-time-code")[];
|
|
95
|
+
}[];
|
|
96
|
+
supportedCurrencies: readonly string[];
|
|
97
|
+
unattended: "supported" | "unsupported";
|
|
98
|
+
readback: "supported" | "unsupported";
|
|
99
|
+
close: "supported" | "unsupported";
|
|
100
|
+
exactAbsence: "supported" | "unsupported";
|
|
101
|
+
refundObservation: "supported" | "unsupported";
|
|
102
|
+
refundCreate: "unsupported";
|
|
103
|
+
authorizationCaptureSplit: "unsupported";
|
|
104
|
+
}
|
|
105
|
+
/** The neutral bundle an adapter factory returns; optional ports match declared capabilities. */
|
|
106
|
+
export interface PaymentAdapter {
|
|
107
|
+
capabilities: PaymentAdapterCapabilities;
|
|
108
|
+
attemptPort: PaymentAttemptPort;
|
|
109
|
+
verifyPort: PaymentVerifyNormalizePort;
|
|
110
|
+
readbackPort: PaymentReadbackPort;
|
|
111
|
+
absencePort?: PaymentAbsencePort;
|
|
112
|
+
}
|
|
113
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { PaymentAdapter, PaymentAttemptPort, PaymentExecutionEnvelope } from "./adapter.js";
|
|
2
|
+
import type { PaymentAbsenceEnvelope, PaymentAbsencePort, PaymentReadbackEnvelope, PaymentReadbackPort } from "./evidence.js";
|
|
3
|
+
/** A fixture binds its own identity, adapter ports and inputs; scenarios accept multiple subjects. */
|
|
4
|
+
export interface PaymentAdapterConformanceSubject {
|
|
5
|
+
subjectId: string;
|
|
6
|
+
adapter: PaymentAdapter;
|
|
7
|
+
executionInput: Parameters<PaymentAttemptPort["execute"]>[0];
|
|
8
|
+
readbackInput: Parameters<PaymentReadbackPort["readPayment"]>[0];
|
|
9
|
+
/** Required when the adapter supplies an absence port. */
|
|
10
|
+
absenceInput?: Parameters<PaymentAbsencePort["proveAbsence"]>[0];
|
|
11
|
+
}
|
|
12
|
+
/** Runner-independent observations; never include transient input or raw diagnostics. */
|
|
13
|
+
export interface PaymentAdapterConformanceEvidence {
|
|
14
|
+
subjects: readonly {
|
|
15
|
+
subjectId: string;
|
|
16
|
+
execution: Omit<PaymentExecutionEnvelope, "handoff">;
|
|
17
|
+
readback: PaymentReadbackEnvelope & {
|
|
18
|
+
handoff?: never;
|
|
19
|
+
};
|
|
20
|
+
absence?: PaymentAbsenceEnvelope;
|
|
21
|
+
}[];
|
|
22
|
+
cases: readonly {
|
|
23
|
+
subjectIds: readonly string[];
|
|
24
|
+
name: string;
|
|
25
|
+
passed: boolean;
|
|
26
|
+
}[];
|
|
27
|
+
}
|
|
28
|
+
import type { PaymentExecutionBaseInput, PaymentExecutionContractResult } from "@openlup/core/testing";
|
|
29
|
+
export type ExecutionCapability = {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly durabilityClaim: false;
|
|
32
|
+
};
|
|
33
|
+
export type SimulatorResult = PaymentExecutionContractResult & {
|
|
34
|
+
providerDecline?: {
|
|
35
|
+
code?: string;
|
|
36
|
+
mandateUnsupported?: boolean;
|
|
37
|
+
} | null;
|
|
38
|
+
};
|
|
39
|
+
export type ExecutionSubject<TInput extends PaymentExecutionBaseInput, TResult extends SimulatorResult> = {
|
|
40
|
+
readonly execute: (request: TInput) => Promise<TResult>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The expected provider identity is a property of the adapter under proof, not of
|
|
44
|
+
* this contract. The collector that constructs the subject owns that literal, so a
|
|
45
|
+
* second runtime bundle proves the same behaviour under its own provider identity
|
|
46
|
+
* and this contract names no provider at all.
|
|
47
|
+
*/
|
|
48
|
+
export type ExecutionProviderIdentity = {
|
|
49
|
+
readonly expectedProvider: string;
|
|
50
|
+
};
|
|
51
|
+
export type ExecutionEvidence = {
|
|
52
|
+
readonly capability: ExecutionCapability;
|
|
53
|
+
readonly executions: number;
|
|
54
|
+
readonly providerCalls: number;
|
|
55
|
+
readonly capturedEffects: number;
|
|
56
|
+
readonly permanentRefusals: number;
|
|
57
|
+
};
|
|
58
|
+
export type AggregateKey = "outOfOrder" | "lateSuccess";
|
|
59
|
+
export type ResultStatus = "succeeded" | "failed";
|
|
60
|
+
export type DurableEventCounts = {
|
|
61
|
+
clients: number;
|
|
62
|
+
orders: number;
|
|
63
|
+
payments: number;
|
|
64
|
+
intents: number;
|
|
65
|
+
attempts: number;
|
|
66
|
+
events: number;
|
|
67
|
+
transitions: number;
|
|
68
|
+
idempotencyKeys: number;
|
|
69
|
+
outboxEvents: number;
|
|
70
|
+
};
|
|
71
|
+
export type DurableEventAggregate = {
|
|
72
|
+
orderId: string;
|
|
73
|
+
paymentId: string;
|
|
74
|
+
paymentIntentId: string;
|
|
75
|
+
paymentAttemptId: string;
|
|
76
|
+
orderStatus: string;
|
|
77
|
+
paymentStatus: string;
|
|
78
|
+
intentStatus: string;
|
|
79
|
+
attemptStatus: string;
|
|
80
|
+
};
|
|
81
|
+
/** Fixture cardinality that stays constant for the whole run. */
|
|
82
|
+
export type DurableEventFixtureCardinality = Pick<DurableEventCounts, "clients" | "orders" | "payments" | "intents" | "attempts">;
|
|
83
|
+
/** Durable writes the adapter emits per step. */
|
|
84
|
+
export type DurableEventWriteCardinality = Pick<DurableEventCounts, "events" | "transitions" | "idempotencyKeys" | "outboxEvents">;
|
|
85
|
+
/**
|
|
86
|
+
* Exact write cardinality is adapter-owned, not part of the portable contract:
|
|
87
|
+
* how many transitions, idempotency keys, and outbox rows one payment event
|
|
88
|
+
* produces is a property of the writer and outbox fan-out under proof, and a
|
|
89
|
+
* second runtime bundle emits its own. The behavioural assertions below (no
|
|
90
|
+
* second canonical event, no regression after a stale failure, convergence on
|
|
91
|
+
* late success) are the portable part; the collector that owns the adapter
|
|
92
|
+
* injects its own numbers so they are still asserted at the exact same step.
|
|
93
|
+
*/
|
|
94
|
+
export type DurableEventCardinalityExpectations = {
|
|
95
|
+
readonly fixture: DurableEventFixtureCardinality;
|
|
96
|
+
readonly baseline: DurableEventWriteCardinality;
|
|
97
|
+
readonly afterOutOfOrderSuccess: DurableEventWriteCardinality;
|
|
98
|
+
readonly afterOutOfOrderFailure: DurableEventWriteCardinality;
|
|
99
|
+
readonly afterDuplicate: DurableEventWriteCardinality;
|
|
100
|
+
readonly afterLateFailure: DurableEventWriteCardinality;
|
|
101
|
+
readonly final: DurableEventWriteCardinality;
|
|
102
|
+
};
|
|
103
|
+
export type DurableEventSnapshot = {
|
|
104
|
+
counts: DurableEventCounts;
|
|
105
|
+
aggregates: Record<AggregateKey, DurableEventAggregate>;
|
|
106
|
+
events: Array<{
|
|
107
|
+
id: string;
|
|
108
|
+
eventKey: string;
|
|
109
|
+
paymentIntentId: string;
|
|
110
|
+
paymentAttemptId: string;
|
|
111
|
+
processingStatus: string;
|
|
112
|
+
}>;
|
|
113
|
+
};
|
|
114
|
+
export type EventReceipt = {
|
|
115
|
+
paymentEventId: string;
|
|
116
|
+
replayed: boolean;
|
|
117
|
+
};
|
|
118
|
+
export type DurableEventCapability = {
|
|
119
|
+
readonly name: string;
|
|
120
|
+
readonly durabilityClaim: true;
|
|
121
|
+
assertSafe(): void;
|
|
122
|
+
reset(): Promise<{
|
|
123
|
+
residualRows: number;
|
|
124
|
+
}>;
|
|
125
|
+
seed(): Promise<void>;
|
|
126
|
+
snapshot(): Promise<DurableEventSnapshot>;
|
|
127
|
+
ingest(input: {
|
|
128
|
+
aggregate: AggregateKey;
|
|
129
|
+
eventKey: string;
|
|
130
|
+
resultStatus: ResultStatus;
|
|
131
|
+
occurredAt: string;
|
|
132
|
+
}): Promise<EventReceipt>;
|
|
133
|
+
apply(input: {
|
|
134
|
+
aggregate: AggregateKey;
|
|
135
|
+
paymentEventId: string;
|
|
136
|
+
eventKey: string;
|
|
137
|
+
resultStatus: ResultStatus;
|
|
138
|
+
occurredAt: string;
|
|
139
|
+
}): Promise<{
|
|
140
|
+
replayed: boolean;
|
|
141
|
+
}>;
|
|
142
|
+
};
|
|
143
|
+
export type DurableEventEvidence = {
|
|
144
|
+
capability: {
|
|
145
|
+
name: string;
|
|
146
|
+
durabilityClaim: true;
|
|
147
|
+
};
|
|
148
|
+
baseline: DurableEventSnapshot;
|
|
149
|
+
afterOutOfOrderSuccess: DurableEventSnapshot;
|
|
150
|
+
afterOutOfOrderFailure: DurableEventSnapshot;
|
|
151
|
+
afterDuplicate: DurableEventSnapshot;
|
|
152
|
+
afterLateFailure: DurableEventSnapshot;
|
|
153
|
+
final: DurableEventSnapshot;
|
|
154
|
+
cleanupResidualRows: 0;
|
|
155
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|