@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,220 @@
|
|
|
1
|
+
import { projectVerifiedEvidence } from "./verifiedEvidenceProjection.js";
|
|
2
|
+
const PAYMENT_WEBHOOK_EVENT_TYPES = [
|
|
3
|
+
"payment.succeeded",
|
|
4
|
+
"payment.failed",
|
|
5
|
+
"payment.refunded",
|
|
6
|
+
"payment.requires_action",
|
|
7
|
+
"payment.disputed",
|
|
8
|
+
"setup.succeeded",
|
|
9
|
+
"setup.failed",
|
|
10
|
+
"setup.requires_action",
|
|
11
|
+
];
|
|
12
|
+
/** Verified ingress boundary; persistence and transport response writing belong to the host. */
|
|
13
|
+
export function createVerifiedPaymentIngress({ provider, verifyPort, paymentControlPort, methodRefPort, afterProcessed, createPorts, resolveMethodOwner, refundEvidencePolicy = "require-evidence", operationalEvents = () => { }, }) {
|
|
14
|
+
return { async receive(input) {
|
|
15
|
+
let evidence;
|
|
16
|
+
try {
|
|
17
|
+
evidence = await verifyPort.verifyAndNormalize(input);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { status: 400, data: { error: "Invalid provider webhook signature" } };
|
|
21
|
+
}
|
|
22
|
+
if (evidence.kind === "refusal") {
|
|
23
|
+
return evidence.reason === "unsupported-event"
|
|
24
|
+
? { status: 200, data: { provider, acknowledged: true, skipped: "unsupported_event" } }
|
|
25
|
+
: { status: 400, data: { error: "Invalid provider webhook signature" } };
|
|
26
|
+
}
|
|
27
|
+
const verifiedEvidence = evidence;
|
|
28
|
+
let event;
|
|
29
|
+
const res = { status: 503, data: {} };
|
|
30
|
+
const sendSuccess = (target, data) => { target.status = 200; target.data = data; };
|
|
31
|
+
const sendBffError = (target, code, error) => {
|
|
32
|
+
target.status = code === "BAD_REQUEST" ? 400 : code === "CONFLICT" ? 409 : 503;
|
|
33
|
+
target.data = { error };
|
|
34
|
+
};
|
|
35
|
+
await processEvent();
|
|
36
|
+
return res;
|
|
37
|
+
async function processEvent() {
|
|
38
|
+
try {
|
|
39
|
+
try {
|
|
40
|
+
event = projectVerifiedEvidence(verifiedEvidence, input, refundEvidencePolicy);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof Error && error.message === "payment_control_result_amount_mismatch")
|
|
44
|
+
throw error;
|
|
45
|
+
sendBffError(res, "BAD_REQUEST", "Invalid provider webhook evidence");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (event.provider !== provider) {
|
|
49
|
+
sendBffError(res, "BAD_REQUEST", "Provider webhook route mismatch");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (verifiedEvidence.kind === "method" && verifiedEvidence.reusableMethod && resolveMethodOwner) {
|
|
53
|
+
const owner = await resolveMethodOwner(verifiedEvidence);
|
|
54
|
+
event = projectVerifiedEvidence(verifiedEvidence, input, refundEvidencePolicy, owner ?? undefined);
|
|
55
|
+
}
|
|
56
|
+
const ports = createPorts
|
|
57
|
+
? await createPorts(event)
|
|
58
|
+
: { paymentControlPort: paymentControlPort, methodRefPort, afterProcessed };
|
|
59
|
+
if (!ports.paymentControlPort)
|
|
60
|
+
throw new Error("Provider webhook port unavailable");
|
|
61
|
+
const ingested = await ports.paymentControlPort.ingestPaymentEvent(event);
|
|
62
|
+
if (ingested.occurredAt)
|
|
63
|
+
event = { ...event, occurredAt: ingested.occurredAt };
|
|
64
|
+
const resultStatus = mapResultStatus(event, refundEvidencePolicy);
|
|
65
|
+
if (resultStatus && ingested.paymentIntentId && ports.paymentControlPort.applyPaymentResult) {
|
|
66
|
+
await ports.paymentControlPort.applyPaymentResult({
|
|
67
|
+
idempotencyKey: `provider-webhook:${provider}:${event.providerEventId}:apply`,
|
|
68
|
+
paymentIntentId: ingested.paymentIntentId,
|
|
69
|
+
paymentEventId: ingested.paymentEventId,
|
|
70
|
+
resultStatus,
|
|
71
|
+
occurredAt: event.occurredAt,
|
|
72
|
+
failureReason: event.eventType === "payment.failed" ? "provider_webhook_failed" : null,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else if (isMoneyMoved(resultStatus) && !ingested.paymentIntentId && !ingested.replayed) {
|
|
76
|
+
// Operator policy: payments should only originate at the storefront
|
|
77
|
+
// checkout. A money-moved provider event (succeeded / refunded /
|
|
78
|
+
// disputed) that matched no local intent is an out-of-band payment
|
|
79
|
+
// (provider panel payment-link, dashboard link/invoice, wallet, card
|
|
80
|
+
// terminal). It is recorded in inbound_provider_events and ACKed, but
|
|
81
|
+
// there is no order to settle, invoice, or fulfill — the safe no-op above
|
|
82
|
+
// is intentional and preserved. Emit a vendor-neutral, non-paging
|
|
83
|
+
// reconciliation signal so the money is never silently lost.
|
|
84
|
+
// Fires once per unique provider event (`!replayed`): the ingest RPC is
|
|
85
|
+
// idempotent on (provider, provider_event_id), so PSP re-delivery is a
|
|
86
|
+
// natural throttle.
|
|
87
|
+
operationalEvents({
|
|
88
|
+
name: "provider_payment_unmatched_out_of_band",
|
|
89
|
+
domain: "payment",
|
|
90
|
+
surface: "webhook",
|
|
91
|
+
details: {
|
|
92
|
+
provider,
|
|
93
|
+
providerPaymentId: event.providerPaymentId,
|
|
94
|
+
eventType: event.eventType,
|
|
95
|
+
resultStatus,
|
|
96
|
+
amountMinor: event.amountMinor ?? null,
|
|
97
|
+
currency: event.currency ?? null,
|
|
98
|
+
occurredAt: event.occurredAt,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (ports.methodRefPort) {
|
|
103
|
+
await ports.methodRefPort.upsertFromWebhook(event);
|
|
104
|
+
}
|
|
105
|
+
if (ports.afterProcessed) {
|
|
106
|
+
await ports.afterProcessed({ event, ingested, resultStatus });
|
|
107
|
+
}
|
|
108
|
+
// Reached only when every setup mutation above resolved (a throw 5xx's →
|
|
109
|
+
// provider redelivers into a still-'received' row). Runs on replays too,
|
|
110
|
+
// so redelivery self-heals rows stuck from before this closer existed.
|
|
111
|
+
if (resultStatus === null && event.eventType.startsWith("setup.") && ports.paymentControlPort.markSetupEventProcessed) {
|
|
112
|
+
await ports.paymentControlPort.markSetupEventProcessed({ paymentEventId: ingested.paymentEventId });
|
|
113
|
+
}
|
|
114
|
+
sendSuccess(res, {
|
|
115
|
+
provider,
|
|
116
|
+
providerEventId: event.providerEventId,
|
|
117
|
+
paymentEventId: ingested.paymentEventId,
|
|
118
|
+
replayed: ingested.replayed,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
// A genuine idempotency conflict (same key, different payload) is a client
|
|
124
|
+
// 409, not a transient 5xx — a 503 would make the provider retry a request
|
|
125
|
+
// that can never succeed. The method-ref RPC raises this on a fingerprint
|
|
126
|
+
// mismatch (SQLSTATE 23505).
|
|
127
|
+
if (/payment_method_ref_idempotency_conflict|commerce_idempotency_conflict|\b23505\b/.test(message)) {
|
|
128
|
+
sendBffError(res, "CONFLICT", "Webhook idempotency conflict");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// A `succeeded` result that arrives AFTER the provisional-subscription
|
|
132
|
+
// sweep has already terminalized the intent (cancelled/refunded/…) is
|
|
133
|
+
// rejected by `apply_result` with `payment_control_result_terminal_intent`
|
|
134
|
+
// (ERRCODE 22023). The money question is already settled from the control
|
|
135
|
+
// plane's perspective, so this is a "no work left to do" outcome, not a
|
|
136
|
+
// transient upstream failure — ACK 200 so the provider stops
|
|
137
|
+
// retrying for days (retry-storm / charge-without-fulfillment risk). We
|
|
138
|
+
// match the RPC's exception text specifically, NOT the bare 22023 code,
|
|
139
|
+
// because 22023 is also raised for genuine mismatches (amount / currency /
|
|
140
|
+
// intent-not-found) that must still surface as 503.
|
|
141
|
+
if (event && /payment_control_result_terminal_intent/.test(message)) {
|
|
142
|
+
operationalEvents({
|
|
143
|
+
name: "provider_payment_result_after_terminal_intent",
|
|
144
|
+
domain: "payment",
|
|
145
|
+
surface: "webhook",
|
|
146
|
+
details: {
|
|
147
|
+
provider,
|
|
148
|
+
providerEventId: event.providerEventId,
|
|
149
|
+
providerPaymentId: event.providerPaymentId,
|
|
150
|
+
eventType: event.eventType,
|
|
151
|
+
occurredAt: event.occurredAt,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
sendSuccess(res, {
|
|
155
|
+
provider,
|
|
156
|
+
providerEventId: event.providerEventId,
|
|
157
|
+
acknowledged: true,
|
|
158
|
+
skipped: "result_terminal_intent",
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// The event cannot be attributed to the intent's exact active attempt:
|
|
163
|
+
// either it named another attempt (the slow-webhook shape and possible
|
|
164
|
+
// double charge) or ingest could not correlate it at all. Neither answer
|
|
165
|
+
// changes on redelivery, so ACK 200 instead of creating an endless retry
|
|
166
|
+
// loop. The durable inbound event and this reason route operator recovery.
|
|
167
|
+
// Match named reasons only, never the shared 22023 SQLSTATE.
|
|
168
|
+
if (event && /payment_control_result_attempt_(?:mismatch|unresolved)/.test(message)) {
|
|
169
|
+
const reason = message.includes("payment_control_result_attempt_unresolved")
|
|
170
|
+
? "attempt_unresolved"
|
|
171
|
+
: "attempt_mismatch";
|
|
172
|
+
operationalEvents({
|
|
173
|
+
name: "provider_payment_result_attempt_mismatch",
|
|
174
|
+
domain: "payment",
|
|
175
|
+
surface: "webhook",
|
|
176
|
+
// `providerPaymentId`, not `providerEventId`, is the operator's handle:
|
|
177
|
+
// the detail allowlist carries the former and drops the latter, and it
|
|
178
|
+
// is the value `ingest_event` correlated the attempt from.
|
|
179
|
+
details: {
|
|
180
|
+
provider,
|
|
181
|
+
providerPaymentId: event.providerPaymentId,
|
|
182
|
+
eventType: event.eventType,
|
|
183
|
+
resultStatus: mapResultStatus(event, refundEvidencePolicy),
|
|
184
|
+
occurredAt: event.occurredAt,
|
|
185
|
+
reason,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
sendSuccess(res, {
|
|
189
|
+
provider,
|
|
190
|
+
providerEventId: event.providerEventId,
|
|
191
|
+
acknowledged: true,
|
|
192
|
+
skipped: "result_attempt_mismatch",
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Provider webhook processing failed");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} };
|
|
200
|
+
}
|
|
201
|
+
function mapResultStatus(event, refundEvidencePolicy) {
|
|
202
|
+
if (event.eventType === "payment.succeeded")
|
|
203
|
+
return "succeeded";
|
|
204
|
+
if (event.eventType === "payment.failed")
|
|
205
|
+
return "failed";
|
|
206
|
+
if (event.eventType === "payment.refunded") {
|
|
207
|
+
if (refundEvidencePolicy === "require-evidence" && (event.refundKind == null || !event.providerPaymentId.trim() || !Number.isSafeInteger(event.amountMinor) || (event.amountMinor ?? 0) <= 0 || !event.currency?.trim()))
|
|
208
|
+
return null;
|
|
209
|
+
return event.refundKind === "partial" ? "partially_refunded" : "refunded";
|
|
210
|
+
}
|
|
211
|
+
if (event.eventType === "payment.disputed")
|
|
212
|
+
return "disputed";
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
// "Money moved" for reconciliation purposes: a settled charge, a refund, or a
|
|
216
|
+
// chargeback/dispute. `failed` (no money moved) and `null` (setup / non-money
|
|
217
|
+
// events) are excluded — an unmatched failed/setup event needs no reconciliation.
|
|
218
|
+
function isMoneyMoved(resultStatus) {
|
|
219
|
+
return resultStatus !== null && resultStatus !== "failed";
|
|
220
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlup/platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Runnable server substrate: Node HTTP runtime, BFF dispatch, scheduler, storage, data gateways and bundle composition; development preview.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"api:update": "npm run build && node --experimental-strip-types ./scripts/api-contract.ts --update"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@openlup/core": "0.
|
|
34
|
+
"@openlup/core": "0.21.0",
|
|
35
35
|
"pg": "^8.22.0",
|
|
36
36
|
"@aws-sdk/client-s3": "^3.1144.0",
|
|
37
37
|
"@types/pg": "^8.20.0"
|
|
@@ -175,6 +175,26 @@
|
|
|
175
175
|
"types": "./dist/shipping/contracts/index.d.ts",
|
|
176
176
|
"import": "./dist/shipping/contracts/index.js",
|
|
177
177
|
"default": "./dist/shipping/contracts/index.js"
|
|
178
|
+
},
|
|
179
|
+
"./payments": {
|
|
180
|
+
"types": "./dist/payments/index.d.ts",
|
|
181
|
+
"import": "./dist/payments/index.js",
|
|
182
|
+
"default": "./dist/payments/index.js"
|
|
183
|
+
},
|
|
184
|
+
"./payments/contracts": {
|
|
185
|
+
"types": "./dist/payments/contracts/index.d.ts",
|
|
186
|
+
"import": "./dist/payments/contracts/index.js",
|
|
187
|
+
"default": "./dist/payments/contracts/index.js"
|
|
188
|
+
},
|
|
189
|
+
"./payments/stripe": {
|
|
190
|
+
"types": "./dist/payments/stripe/index.d.ts",
|
|
191
|
+
"import": "./dist/payments/stripe/index.js",
|
|
192
|
+
"default": "./dist/payments/stripe/index.js"
|
|
193
|
+
},
|
|
194
|
+
"./payments/tpay": {
|
|
195
|
+
"types": "./dist/payments/tpay/index.d.ts",
|
|
196
|
+
"import": "./dist/payments/tpay/index.js",
|
|
197
|
+
"default": "./dist/payments/tpay/index.js"
|
|
178
198
|
}
|
|
179
199
|
},
|
|
180
200
|
"repository": {
|
package/release-gates.json
CHANGED
|
@@ -355,6 +355,66 @@
|
|
|
355
355
|
],
|
|
356
356
|
"entrypoint": "dist/shipping/contracts/index.d.ts",
|
|
357
357
|
"snapshot": "api/shipping-contracts.api.md"
|
|
358
|
+
},
|
|
359
|
+
"./payments": {
|
|
360
|
+
"role": "capability",
|
|
361
|
+
"maturity": "experimental",
|
|
362
|
+
"compatibilityPolicy": "internal-candidate",
|
|
363
|
+
"packageSmokeEvidence": [
|
|
364
|
+
"test/packageBoundary.test.ts",
|
|
365
|
+
"test/payments/preparedAttempt.test.ts",
|
|
366
|
+
"test/payments/verifiedIngress.test.ts",
|
|
367
|
+
"test/payments/refundEvidence.test.ts",
|
|
368
|
+
"test/payments/truth.test.ts",
|
|
369
|
+
"test/payments/reconciliation.test.ts",
|
|
370
|
+
"test/payments/adapterConformance.test.ts",
|
|
371
|
+
"test/payments/postgresPaymentTruth.test.ts"
|
|
372
|
+
],
|
|
373
|
+
"entrypoint": "dist/payments/index.d.ts",
|
|
374
|
+
"snapshot": "api/payments.api.md"
|
|
375
|
+
},
|
|
376
|
+
"./payments/contracts": {
|
|
377
|
+
"role": "capability",
|
|
378
|
+
"maturity": "experimental",
|
|
379
|
+
"compatibilityPolicy": "internal-candidate",
|
|
380
|
+
"packageSmokeEvidence": [
|
|
381
|
+
"test/packageBoundary.test.ts",
|
|
382
|
+
"test/payments/contracts.test.ts",
|
|
383
|
+
"test/payments/verifiedIngress.test.ts",
|
|
384
|
+
"test/payments/refundEvidence.test.ts",
|
|
385
|
+
"test/payments/truth.test.ts",
|
|
386
|
+
"test/payments/reconciliation.test.ts",
|
|
387
|
+
"test/payments/adapterConformance.test.ts",
|
|
388
|
+
"test/payments/postgresPaymentTruth.test.ts"
|
|
389
|
+
],
|
|
390
|
+
"entrypoint": "dist/payments/contracts/index.d.ts",
|
|
391
|
+
"snapshot": "api/payments-contracts.api.md"
|
|
392
|
+
},
|
|
393
|
+
"./payments/stripe": {
|
|
394
|
+
"role": "capability",
|
|
395
|
+
"maturity": "experimental",
|
|
396
|
+
"compatibilityPolicy": "internal-candidate",
|
|
397
|
+
"packageSmokeEvidence": [
|
|
398
|
+
"test/payments/stripe/conformance.test.ts",
|
|
399
|
+
"test/payments/stripe/factory.test.ts",
|
|
400
|
+
"test/payments/stripe/stripeWebhookNormalizer.test.ts",
|
|
401
|
+
"test/payments/stripe/stripeSandboxPaymentExecutionAdapter.test.ts"
|
|
402
|
+
],
|
|
403
|
+
"entrypoint": "dist/payments/stripe/index.d.ts",
|
|
404
|
+
"snapshot": "api/payments-stripe.api.md"
|
|
405
|
+
},
|
|
406
|
+
"./payments/tpay": {
|
|
407
|
+
"role": "capability",
|
|
408
|
+
"maturity": "experimental",
|
|
409
|
+
"compatibilityPolicy": "internal-candidate",
|
|
410
|
+
"packageSmokeEvidence": [
|
|
411
|
+
"test/payments/tpay/factory.test.ts",
|
|
412
|
+
"test/payments/tpay/conformance.test.ts",
|
|
413
|
+
"test/payments/tpay/webhookNormalizer.test.ts",
|
|
414
|
+
"test/payments/tpay/tpayPaymentExecutionAdapter.test.ts"
|
|
415
|
+
],
|
|
416
|
+
"entrypoint": "dist/payments/tpay/index.d.ts",
|
|
417
|
+
"snapshot": "api/payments-tpay.api.md"
|
|
358
418
|
}
|
|
359
419
|
},
|
|
360
420
|
"pack": {
|
|
@@ -370,8 +430,8 @@
|
|
|
370
430
|
"maxUnpackedBytes": 5734400,
|
|
371
431
|
"maxFiles": 2100,
|
|
372
432
|
"baseline": {
|
|
373
|
-
"version": "0.
|
|
374
|
-
"packedBytes":
|
|
433
|
+
"version": "0.20.0",
|
|
434
|
+
"packedBytes": 809376
|
|
375
435
|
},
|
|
376
436
|
"browserEntries": {
|
|
377
437
|
"./catalog/contracts": 4096,
|
|
@@ -380,7 +440,8 @@
|
|
|
380
440
|
"./inventory/contracts": 32,
|
|
381
441
|
"./pricing/contracts": 416,
|
|
382
442
|
"./promotions/contracts": 32,
|
|
383
|
-
"./shipping/contracts": 32
|
|
443
|
+
"./shipping/contracts": 32,
|
|
444
|
+
"./payments/contracts": 32
|
|
384
445
|
}
|
|
385
446
|
}
|
|
386
447
|
}
|