xytara 2.5.0 → 2.6.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/.env.example +29 -0
- package/BSV_TERANODE_SETUP.md +43 -0
- package/README.md +66 -7
- package/REAL_PAYMENT_SETUP.md +61 -0
- package/RELEASE_NOTES.md +12 -6
- package/SERVICE_CONTRACT.md +31 -0
- package/START_HERE.md +107 -6
- package/TREASURY_DESTINATIONS.example.json +66 -0
- package/TREASURY_DESTINATIONS.production.template.json +108 -0
- package/adapters/examples/langchain-reference-execution-adapter.js +81 -0
- package/adapters/examples/langchain-reference-execution-adapter.manifest.json +79 -0
- package/adapters/examples/langgraph-reference-execution-adapter.js +81 -0
- package/adapters/examples/langgraph-reference-execution-adapter.manifest.json +79 -0
- package/bin/xytara-first-run.js +244 -0
- package/bin/xytara.js +30 -5
- package/integrations/dispatch.js +7 -2
- package/integrations/registry.js +123 -2
- package/lib/activity_audit_contract.js +322 -0
- package/lib/adapter_depth_contract.js +156 -0
- package/lib/bolt_lane_contract.js +55 -0
- package/lib/bolt_payment_front.js +172 -0
- package/lib/capability_execution_truth.js +269 -0
- package/lib/capability_registry.js +39 -2
- package/lib/checkout_event_security.js +142 -0
- package/lib/command_flow.js +169 -36
- package/lib/commerce_artifacts.js +23 -4
- package/lib/commerce_checkout.js +134 -4
- package/lib/commerce_client.js +20 -0
- package/lib/commerce_economics.js +109 -9
- package/lib/commerce_runtime.js +38 -2
- package/lib/commerce_shell.js +4 -2
- package/lib/external_execution_runtime.js +132 -0
- package/lib/go_live_operator_pack.js +339 -0
- package/lib/http_transport.js +32 -0
- package/lib/integration_matrix_contract.js +6 -0
- package/lib/l402_lane_contract.js +55 -0
- package/lib/l402_payment_front.js +192 -0
- package/lib/network_transport.js +110 -0
- package/lib/operator_intelligence.js +215 -1
- package/lib/payment_config.js +80 -3
- package/lib/payment_front_registry.js +53 -0
- package/lib/payment_protocol_contract.js +165 -0
- package/lib/payment_verification.js +23 -3
- package/lib/pricing_optimization_contract.js +556 -0
- package/lib/release_history.js +15 -0
- package/lib/request_rate_limit.js +144 -0
- package/lib/runtime_bridge.js +10 -2
- package/lib/runtime_operations_worker.js +282 -0
- package/lib/settlement_bsv_live.js +79 -4
- package/lib/stripe_mpp_lane_contract.js +3 -0
- package/lib/stripe_mpp_payment_front.js +158 -0
- package/lib/treasury_destinations_contract.js +543 -0
- package/lib/treasury_egress_policy_contract.js +91 -0
- package/package.json +14 -21
- package/server.js +627 -88
- package/OPERATIONS_RUNBOOK.md +0 -66
- package/OPERATOR_START_HERE.md +0 -63
- package/PROGRAM_COMPLETE_RELEASE.md +0 -63
- package/PROGRAM_STATUS.md +0 -57
- package/PUBLISH_PLAN.md +0 -14
- package/RELEASE_CHECKLIST.md +0 -16
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
|
|
5
|
+
function normalizeString(value) {
|
|
6
|
+
return typeof value === "string" ? value.trim() : "";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function parseBoolean(value, fallback) {
|
|
10
|
+
const normalized = normalizeString(value).toLowerCase();
|
|
11
|
+
if (!normalized) return fallback;
|
|
12
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
13
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function safeEqualHex(left, right) {
|
|
18
|
+
const leftNormalized = normalizeString(left).toLowerCase();
|
|
19
|
+
const rightNormalized = normalizeString(right).toLowerCase();
|
|
20
|
+
if (!leftNormalized || !rightNormalized || leftNormalized.length !== rightNormalized.length) return false;
|
|
21
|
+
try {
|
|
22
|
+
return crypto.timingSafeEqual(Buffer.from(leftNormalized, "hex"), Buffer.from(rightNormalized, "hex"));
|
|
23
|
+
} catch (_error) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadCheckoutEventSecurityConfig() {
|
|
29
|
+
const secret = normalizeString(process.env.XYTARA_CHECKOUT_WEBHOOK_SECRET || process.env.XYTARA_HOSTED_CHECKOUT_WEBHOOK_SECRET);
|
|
30
|
+
const verificationRequired = parseBoolean(
|
|
31
|
+
process.env.XYTARA_CHECKOUT_WEBHOOK_VERIFICATION_REQUIRED,
|
|
32
|
+
secret.length > 0
|
|
33
|
+
);
|
|
34
|
+
const maxAgeSeconds = Math.max(30, Number(process.env.XYTARA_CHECKOUT_WEBHOOK_MAX_AGE_SECONDS || 300) || 300);
|
|
35
|
+
return {
|
|
36
|
+
secret: secret || null,
|
|
37
|
+
verification_required: verificationRequired,
|
|
38
|
+
max_age_seconds: maxAgeSeconds,
|
|
39
|
+
signature_header: "x-xytara-checkout-signature",
|
|
40
|
+
timestamp_header: "x-xytara-checkout-timestamp",
|
|
41
|
+
signature_mode: "hmac_sha256_timestamped"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildCheckoutEventSecuritySummary() {
|
|
46
|
+
const config = loadCheckoutEventSecurityConfig();
|
|
47
|
+
const blockers = [];
|
|
48
|
+
if (config.verification_required && !config.secret) blockers.push("checkout_webhook_secret_missing");
|
|
49
|
+
return {
|
|
50
|
+
ok: true,
|
|
51
|
+
category: "machine-commerce-checkout-event-security-summary",
|
|
52
|
+
summary_version: "xytara-checkout-event-security-summary-v1",
|
|
53
|
+
verification_required: config.verification_required,
|
|
54
|
+
secret_configured: Boolean(config.secret),
|
|
55
|
+
max_age_seconds: config.max_age_seconds,
|
|
56
|
+
signature_header: config.signature_header,
|
|
57
|
+
timestamp_header: config.timestamp_header,
|
|
58
|
+
signature_mode: config.signature_mode,
|
|
59
|
+
ready: blockers.length === 0,
|
|
60
|
+
blockers,
|
|
61
|
+
operator_guidance: config.verification_required
|
|
62
|
+
? "sign checkout event payloads with the configured webhook secret before forwarding them into xytara"
|
|
63
|
+
: "configure XYTARA_CHECKOUT_WEBHOOK_SECRET and keep verification required when hosted checkout is live"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildCheckoutEventSignature(rawBody, secret, timestamp) {
|
|
68
|
+
return crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function verifyCheckoutEventRequest(headers, rawBody) {
|
|
72
|
+
const config = loadCheckoutEventSecurityConfig();
|
|
73
|
+
const headerMap = headers && typeof headers === "object" ? headers : {};
|
|
74
|
+
const signature = normalizeString(headerMap[config.signature_header]);
|
|
75
|
+
const timestamp = normalizeString(headerMap[config.timestamp_header]);
|
|
76
|
+
|
|
77
|
+
if (!config.verification_required && !config.secret) {
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
verification_mode: "verification_not_required",
|
|
81
|
+
signature_mode: config.signature_mode
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!config.secret) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
reason: "checkout_webhook_secret_missing"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!signature) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
reason: "checkout_webhook_signature_missing"
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!timestamp) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
reason: "checkout_webhook_timestamp_missing"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const timestampMs = Date.parse(timestamp);
|
|
107
|
+
if (!Number.isFinite(timestampMs)) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
reason: "checkout_webhook_timestamp_invalid"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const ageSeconds = Math.abs(Date.now() - timestampMs) / 1000;
|
|
115
|
+
if (ageSeconds > config.max_age_seconds) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
reason: "checkout_webhook_timestamp_expired"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const expected = buildCheckoutEventSignature(rawBody, config.secret, timestamp);
|
|
123
|
+
if (!safeEqualHex(expected, signature)) {
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
reason: "checkout_webhook_signature_invalid"
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
ok: true,
|
|
132
|
+
verification_mode: "timestamped_hmac",
|
|
133
|
+
signature_mode: config.signature_mode
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
buildCheckoutEventSecuritySummary,
|
|
139
|
+
buildCheckoutEventSignature,
|
|
140
|
+
loadCheckoutEventSecurityConfig,
|
|
141
|
+
verifyCheckoutEventRequest
|
|
142
|
+
};
|
package/lib/command_flow.js
CHANGED
|
@@ -5,10 +5,11 @@ const {
|
|
|
5
5
|
createQuote,
|
|
6
6
|
hydrateQuoteStates
|
|
7
7
|
} = require("./commerce_runtime");
|
|
8
|
-
const { consumeAccountCredits, buildUsageMeterPreview } = require("./commerce_economics");
|
|
8
|
+
const { consumeAccountCredits, buildUsageMeterPreview, previewCreditSpend } = require("./commerce_economics");
|
|
9
9
|
const { resolveSpendCredential, evaluateAuthorityBindingAction } = require("./account_auth");
|
|
10
10
|
const { validateIntegrationSelection } = require("../integrations/dispatch");
|
|
11
11
|
const { markPaymentAccepted, verifyPaymentAcceptance } = require("./payment_verification");
|
|
12
|
+
const { executeExternalTarget, resolveExecutionTarget } = require("./external_execution_runtime");
|
|
12
13
|
|
|
13
14
|
function hasPaymentHeaders(headers, requiredHeaders) {
|
|
14
15
|
return requiredHeaders.every((headerName) => headers[headerName]);
|
|
@@ -22,7 +23,84 @@ function parseBearerToken(headers) {
|
|
|
22
23
|
return match ? match[1].trim() : null;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
function
|
|
26
|
+
function buildCreditCommitPayload(payload, quote, requiredUnits) {
|
|
27
|
+
return {
|
|
28
|
+
account_id: quote.account_id,
|
|
29
|
+
agent_id: payload.agent_id || null,
|
|
30
|
+
budget_id: payload.budget_id || null,
|
|
31
|
+
pack_id: payload.pack_id || null,
|
|
32
|
+
entitlement_id: payload.entitlement_id || null,
|
|
33
|
+
quote_id: quote.quote_id,
|
|
34
|
+
task_ref: payload.task_ref || null,
|
|
35
|
+
workflow_ref: payload.workflow_ref || null,
|
|
36
|
+
units: requiredUnits,
|
|
37
|
+
reason: "credit_first_execution"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildCreditRejectionResponse(creditDecision, quote) {
|
|
42
|
+
const failureReason = creditDecision && creditDecision.credit_spend_preview
|
|
43
|
+
? creditDecision.credit_spend_preview.failure_reason
|
|
44
|
+
: null;
|
|
45
|
+
const reason = failureReason
|
|
46
|
+
|| (creditDecision && creditDecision.policy_guard && creditDecision.policy_guard.reason)
|
|
47
|
+
|| (creditDecision && creditDecision.entitlement_guard && creditDecision.entitlement_guard.reason)
|
|
48
|
+
|| (creditDecision && creditDecision.reason)
|
|
49
|
+
|| "credit_spend_not_allowed";
|
|
50
|
+
|
|
51
|
+
if (reason === "insufficient_credits") {
|
|
52
|
+
const preview = creditDecision && creditDecision.credit_spend_preview ? creditDecision.credit_spend_preview : {};
|
|
53
|
+
return {
|
|
54
|
+
status: 402,
|
|
55
|
+
payload: {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "insufficient_credits",
|
|
58
|
+
insufficient_credits: {
|
|
59
|
+
account_id: quote.account_id,
|
|
60
|
+
required_units: preview.required_units,
|
|
61
|
+
available_units: preview.available_units,
|
|
62
|
+
recommended_next_surfaces: {
|
|
63
|
+
credits_topup: "/v1/checkout/purchase-intents",
|
|
64
|
+
direct_payment: "/v1/payment-quotes"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
quote
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
status: 403,
|
|
74
|
+
payload: {
|
|
75
|
+
ok: false,
|
|
76
|
+
reason,
|
|
77
|
+
account_id: quote.account_id,
|
|
78
|
+
policy_guard: creditDecision && creditDecision.policy_guard ? creditDecision.policy_guard : null,
|
|
79
|
+
entitlement_guard: creditDecision && creditDecision.entitlement_guard ? creditDecision.entitlement_guard : null,
|
|
80
|
+
quote
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function buildExternalExecutionOptions(payload, quote, authorizationContext) {
|
|
86
|
+
const executionTarget = resolveExecutionTarget(payload, quote);
|
|
87
|
+
if (!executionTarget) return {};
|
|
88
|
+
const execution = await executeExternalTarget(executionTarget, payload, quote, authorizationContext);
|
|
89
|
+
if (!execution || !execution.ok) {
|
|
90
|
+
return {
|
|
91
|
+
error: execution || {
|
|
92
|
+
ok: false,
|
|
93
|
+
status: 502,
|
|
94
|
+
reason: "external_execution_failed"
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
externalExecution: execution
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function executeCommandRequest(state, payload, headers, options) {
|
|
26
104
|
const opts = options || {};
|
|
27
105
|
const requiredHeaders = Array.isArray(opts.requiredHeaders) && opts.requiredHeaders.length > 0
|
|
28
106
|
? opts.requiredHeaders
|
|
@@ -72,10 +150,12 @@ function executeCommandRequest(state, payload, headers, options) {
|
|
|
72
150
|
}
|
|
73
151
|
const quote = createQuote(state, payload);
|
|
74
152
|
if (prefersCredits) {
|
|
75
|
-
const
|
|
153
|
+
const creditPayload = {
|
|
76
154
|
...payload,
|
|
155
|
+
account_id: quote.account_id,
|
|
77
156
|
quoted_units: Number.isFinite(Number(payload.quoted_units)) ? Number(payload.quoted_units) : undefined
|
|
78
|
-
}
|
|
157
|
+
};
|
|
158
|
+
const meteringPreview = buildUsageMeterPreview(state, creditPayload);
|
|
79
159
|
const requiredUnits = meteringPreview.usage_units;
|
|
80
160
|
if (delegatedSpend.ok && delegatedSpend.binding) {
|
|
81
161
|
const authorityDecision = evaluateAuthorityBindingAction(delegatedSpend.binding, "delegated_credit_spend", requiredUnits);
|
|
@@ -96,42 +176,65 @@ function executeCommandRequest(state, payload, headers, options) {
|
|
|
96
176
|
};
|
|
97
177
|
}
|
|
98
178
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
179
|
+
|
|
180
|
+
const creditDecision = previewCreditSpend(state, creditPayload);
|
|
181
|
+
if (!creditDecision.ok || !creditDecision.credit_spend_preview || creditDecision.credit_spend_preview.executable_now !== true) {
|
|
182
|
+
return buildCreditRejectionResponse(creditDecision, quote);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const preCommitAuthorizationContext = delegatedSpend.ok
|
|
186
|
+
? delegatedSpend.authorization_context
|
|
187
|
+
: {
|
|
188
|
+
ok: true,
|
|
189
|
+
verification_mode: "account_credits",
|
|
190
|
+
wallet_id: quote.account_id,
|
|
191
|
+
age_seconds: 0,
|
|
192
|
+
payment_payload: {
|
|
193
|
+
credit_spend_status: "precommit"
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const externalExecutionOptions = await buildExternalExecutionOptions(payload, quote, preCommitAuthorizationContext);
|
|
197
|
+
if (externalExecutionOptions.error) {
|
|
198
|
+
return {
|
|
199
|
+
status: externalExecutionOptions.error.status || 502,
|
|
200
|
+
payload: {
|
|
201
|
+
ok: false,
|
|
202
|
+
reason: externalExecutionOptions.error.reason || "external_execution_failed",
|
|
203
|
+
details: externalExecutionOptions.error.details || null,
|
|
204
|
+
credit_spend_status: "not_committed",
|
|
205
|
+
credit_spend_preview: creditDecision.credit_spend_preview,
|
|
206
|
+
quote
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const creditSpend = consumeAccountCredits(state, buildCreditCommitPayload(payload, quote, requiredUnits));
|
|
111
212
|
if (creditSpend.ok) {
|
|
213
|
+
const paymentVerification = {
|
|
214
|
+
...(delegatedSpend.ok
|
|
215
|
+
? delegatedSpend.authorization_context
|
|
216
|
+
: {
|
|
217
|
+
ok: true,
|
|
218
|
+
verification_mode: "account_credits",
|
|
219
|
+
wallet_id: quote.account_id,
|
|
220
|
+
age_seconds: 0,
|
|
221
|
+
payment_payload: {}
|
|
222
|
+
}),
|
|
223
|
+
payment_payload: {
|
|
224
|
+
...((delegatedSpend.ok && delegatedSpend.authorization_context && delegatedSpend.authorization_context.payment_payload) || {}),
|
|
225
|
+
credit_spend_id: creditSpend.credit_spend.credit_spend_id
|
|
226
|
+
}
|
|
227
|
+
};
|
|
112
228
|
return {
|
|
113
229
|
status: 200,
|
|
114
230
|
payload: createCommandResult(state, {
|
|
115
231
|
...payload,
|
|
116
232
|
payment_preference: "credits_first"
|
|
117
233
|
}, quote, {
|
|
118
|
-
paymentVerification
|
|
119
|
-
...(delegatedSpend.ok
|
|
120
|
-
? delegatedSpend.authorization_context
|
|
121
|
-
: {
|
|
122
|
-
ok: true,
|
|
123
|
-
verification_mode: "account_credits",
|
|
124
|
-
wallet_id: quote.account_id,
|
|
125
|
-
age_seconds: 0,
|
|
126
|
-
payment_payload: {}
|
|
127
|
-
}),
|
|
128
|
-
payment_payload: {
|
|
129
|
-
...((delegatedSpend.ok && delegatedSpend.authorization_context && delegatedSpend.authorization_context.payment_payload) || {}),
|
|
130
|
-
credit_spend_id: creditSpend.credit_spend.credit_spend_id
|
|
131
|
-
}
|
|
132
|
-
},
|
|
234
|
+
paymentVerification,
|
|
133
235
|
creditSpend: creditSpend.credit_spend,
|
|
134
|
-
skipPaymentLedger: true
|
|
236
|
+
skipPaymentLedger: true,
|
|
237
|
+
externalExecution: externalExecutionOptions.externalExecution || null
|
|
135
238
|
})
|
|
136
239
|
};
|
|
137
240
|
}
|
|
@@ -142,6 +245,7 @@ function executeCommandRequest(state, payload, headers, options) {
|
|
|
142
245
|
ok: false,
|
|
143
246
|
reason: creditSpend.reason,
|
|
144
247
|
account_id: quote.account_id,
|
|
248
|
+
provider_execution_completed_but_credit_commit_failed: Boolean(externalExecutionOptions.externalExecution),
|
|
145
249
|
policy_guard: {
|
|
146
250
|
agent_id: creditSpend.agent_id || null,
|
|
147
251
|
budget_id: creditSpend.budget_id || null,
|
|
@@ -161,6 +265,7 @@ function executeCommandRequest(state, payload, headers, options) {
|
|
|
161
265
|
payload: {
|
|
162
266
|
ok: false,
|
|
163
267
|
reason: "insufficient_credits",
|
|
268
|
+
provider_execution_completed_but_credit_commit_failed: Boolean(externalExecutionOptions.externalExecution),
|
|
164
269
|
insufficient_credits: {
|
|
165
270
|
account_id: quote.account_id,
|
|
166
271
|
required_units: creditSpend.required_units,
|
|
@@ -206,7 +311,10 @@ function executeCommandRequest(state, payload, headers, options) {
|
|
|
206
311
|
};
|
|
207
312
|
}
|
|
208
313
|
|
|
209
|
-
const paymentVerification = verifyPaymentAcceptance(state, headers || {}, requiredHeaders, quote
|
|
314
|
+
const paymentVerification = verifyPaymentAcceptance(state, headers || {}, requiredHeaders, quote, {
|
|
315
|
+
payment_protocol: opts.paymentProtocol || (payload && payload.payment_protocol) || null,
|
|
316
|
+
authorization_scheme: opts.authorizationScheme || null
|
|
317
|
+
});
|
|
210
318
|
if (!paymentVerification.ok) {
|
|
211
319
|
return {
|
|
212
320
|
status: paymentVerification.status || 403,
|
|
@@ -219,16 +327,29 @@ function executeCommandRequest(state, payload, headers, options) {
|
|
|
219
327
|
}
|
|
220
328
|
|
|
221
329
|
markPaymentAccepted(state, paymentVerification);
|
|
330
|
+
const externalExecutionOptions = await buildExternalExecutionOptions(payload, quote, paymentVerification);
|
|
331
|
+
if (externalExecutionOptions.error) {
|
|
332
|
+
return {
|
|
333
|
+
status: externalExecutionOptions.error.status || 502,
|
|
334
|
+
payload: {
|
|
335
|
+
ok: false,
|
|
336
|
+
reason: externalExecutionOptions.error.reason || "external_execution_failed",
|
|
337
|
+
details: externalExecutionOptions.error.details || null,
|
|
338
|
+
quote
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
222
342
|
|
|
223
343
|
return {
|
|
224
344
|
status: 200,
|
|
225
345
|
payload: createCommandResult(state, payload, quote, {
|
|
226
|
-
paymentVerification
|
|
346
|
+
paymentVerification,
|
|
347
|
+
externalExecution: externalExecutionOptions.externalExecution || null
|
|
227
348
|
})
|
|
228
349
|
};
|
|
229
350
|
}
|
|
230
351
|
|
|
231
|
-
function executeAuthorizedCommandRequest(state, payload, authorizationContext) {
|
|
352
|
+
async function executeAuthorizedCommandRequest(state, payload, authorizationContext) {
|
|
232
353
|
const validation = validateIntegrationSelection(payload);
|
|
233
354
|
if (!validation.ok) {
|
|
234
355
|
return {
|
|
@@ -258,11 +379,23 @@ function executeAuthorizedCommandRequest(state, payload, authorizationContext) {
|
|
|
258
379
|
? authorizationContext.payment_payload
|
|
259
380
|
: null
|
|
260
381
|
};
|
|
382
|
+
const externalExecutionOptions = await buildExternalExecutionOptions(payload, quote, paymentVerification);
|
|
383
|
+
if (externalExecutionOptions.error) {
|
|
384
|
+
return {
|
|
385
|
+
status: externalExecutionOptions.error.status || 502,
|
|
386
|
+
payload: {
|
|
387
|
+
ok: false,
|
|
388
|
+
reason: externalExecutionOptions.error.reason || "external_execution_failed",
|
|
389
|
+
details: externalExecutionOptions.error.details || null
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
261
393
|
|
|
262
394
|
return {
|
|
263
395
|
status: 200,
|
|
264
396
|
payload: createCommandResult(state, payload, quote, {
|
|
265
|
-
paymentVerification
|
|
397
|
+
paymentVerification,
|
|
398
|
+
externalExecution: externalExecutionOptions.externalExecution || null
|
|
266
399
|
})
|
|
267
400
|
};
|
|
268
401
|
}
|
|
@@ -67,6 +67,12 @@ function createCommandArtifacts(payload, quote, options) {
|
|
|
67
67
|
const normalizedBody = payload && payload.body && typeof payload.body === "object" && !Array.isArray(payload.body)
|
|
68
68
|
? payload.body
|
|
69
69
|
: {};
|
|
70
|
+
const externalExecution = opts.externalExecution && typeof opts.externalExecution === "object"
|
|
71
|
+
? opts.externalExecution
|
|
72
|
+
: null;
|
|
73
|
+
const externalResponse = externalExecution && externalExecution.target_response && typeof externalExecution.target_response === "object"
|
|
74
|
+
? externalExecution.target_response
|
|
75
|
+
: null;
|
|
70
76
|
const resultsByTaskId = Object.fromEntries(resultTasks.map((task, index) => [
|
|
71
77
|
task.task_id,
|
|
72
78
|
(() => {
|
|
@@ -164,7 +170,17 @@ function createCommandArtifacts(payload, quote, options) {
|
|
|
164
170
|
if (task.task_ref === "adapter.mcp.invoke") {
|
|
165
171
|
base.adapter_lane = "mcp";
|
|
166
172
|
base.tool_name = normalizedBody.tool_name || null;
|
|
167
|
-
base.invocation_status = "simulated";
|
|
173
|
+
base.invocation_status = externalExecution ? "provider_completed" : "simulated";
|
|
174
|
+
}
|
|
175
|
+
if (externalExecution) {
|
|
176
|
+
base.execution_provider = externalExecution.provider || null;
|
|
177
|
+
base.execution_target_id = externalExecution.target_id || null;
|
|
178
|
+
if (externalResponse && externalResponse.provider_job_id) base.provider_job_id = externalResponse.provider_job_id;
|
|
179
|
+
if (externalResponse && externalResponse.output_payload) base.output_payload = externalResponse.output_payload;
|
|
180
|
+
if (externalResponse && externalResponse.output_ref) base.output_ref = externalResponse.output_ref;
|
|
181
|
+
base.provider_status = externalResponse && externalResponse.provider_status
|
|
182
|
+
? externalResponse.provider_status
|
|
183
|
+
: "completed";
|
|
168
184
|
}
|
|
169
185
|
return base;
|
|
170
186
|
})()
|
|
@@ -263,7 +279,7 @@ function createCommandArtifacts(payload, quote, options) {
|
|
|
263
279
|
quote_id: quote.quote_id,
|
|
264
280
|
integration_ids: Array.isArray(quote.integration_ids) ? quote.integration_ids : [],
|
|
265
281
|
protocols: Array.isArray(quote.protocols) ? quote.protocols : [],
|
|
266
|
-
output_ref: `output_${transactionId}`,
|
|
282
|
+
output_ref: externalResponse && externalResponse.output_ref ? externalResponse.output_ref : `output_${transactionId}`,
|
|
267
283
|
callback_url: payload.callback_url || null,
|
|
268
284
|
capabilities: quote.capabilities,
|
|
269
285
|
adapters: quote.adapters,
|
|
@@ -280,7 +296,10 @@ function createCommandArtifacts(payload, quote, options) {
|
|
|
280
296
|
capability_scopes: capabilityScopes,
|
|
281
297
|
adapters_used: adaptersUsed,
|
|
282
298
|
integration_ids: Array.isArray(quote.execution_integration_ids) ? quote.execution_integration_ids : [],
|
|
283
|
-
results_by_task_id: resultsByTaskId
|
|
299
|
+
results_by_task_id: resultsByTaskId,
|
|
300
|
+
provider: externalExecution ? externalExecution.provider || null : null,
|
|
301
|
+
provider_target_id: externalExecution ? externalExecution.target_id || null : null,
|
|
302
|
+
provider_job_id: externalResponse && externalResponse.provider_job_id ? externalResponse.provider_job_id : null
|
|
284
303
|
},
|
|
285
304
|
created_at_iso: startedAt.toISOString(),
|
|
286
305
|
completed_at_iso: deferredCompletion ? null : completedAt.toISOString(),
|
|
@@ -304,7 +323,7 @@ function createCommandArtifacts(payload, quote, options) {
|
|
|
304
323
|
...defaultPath,
|
|
305
324
|
status: deferredCompletion ? "pending_completion" : "recorded"
|
|
306
325
|
},
|
|
307
|
-
proof_ref: `proof_${receiptId}`,
|
|
326
|
+
proof_ref: externalResponse && externalResponse.proof_ref ? externalResponse.proof_ref : `proof_${receiptId}`,
|
|
308
327
|
execution_summary: {
|
|
309
328
|
command: payload.command || "command.execute",
|
|
310
329
|
task_count: resultTasks.length,
|
package/lib/commerce_checkout.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const { createRefund } = require("./commerce_cases");
|
|
4
|
-
const { applyRailCredits } = require("./commerce_economics");
|
|
3
|
+
const { createRefund, createRemediationTicket } = require("./commerce_cases");
|
|
4
|
+
const { applyRailCredits, buildCreditBalanceSummary, reverseRailCredits } = require("./commerce_economics");
|
|
5
5
|
const { dispatchRuntimeBridgeRequest } = require("./runtime_bridge");
|
|
6
6
|
|
|
7
7
|
function normalizeString(value, fallback) {
|
|
@@ -142,6 +142,90 @@ function getHostedCheckoutEvent(state, eventId) {
|
|
|
142
142
|
return state.checkoutEvents.get(eventId) || null;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
function getRuntimeBridgeDispatchByPurchaseIntent(state, purchaseIntentId) {
|
|
146
|
+
return Array.from(state.runtimeBridgeDispatches.values()).find((dispatch) =>
|
|
147
|
+
dispatch && dispatch.purchase_intent_id === purchaseIntentId
|
|
148
|
+
) || null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function createHostedCheckoutRemediationTicket(state, purchaseIntent, event, refund, reasonCode, note) {
|
|
152
|
+
return createRemediationTicket(state, {
|
|
153
|
+
transaction_id: null,
|
|
154
|
+
receipt_id: null,
|
|
155
|
+
reason_code: reasonCode,
|
|
156
|
+
action_code: "compensate_or_review",
|
|
157
|
+
note,
|
|
158
|
+
actor: { type: "system", role: "hosted_checkout_compensator" },
|
|
159
|
+
context: {
|
|
160
|
+
purchase_intent_id: purchaseIntent.purchase_intent_id,
|
|
161
|
+
refund_id: refund ? refund.refund_id : null,
|
|
162
|
+
provider_reference: event.provider_reference || null,
|
|
163
|
+
payment_status: event.payment_status || null,
|
|
164
|
+
source: "hosted_checkout_compensation"
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function applyHostedCheckoutCompensation(state, purchaseIntent, event, refund) {
|
|
170
|
+
const balanceBefore = buildCreditBalanceSummary(state, purchaseIntent.account_id);
|
|
171
|
+
const currentAvailableUnits = Number(balanceBefore.available_units || 0);
|
|
172
|
+
const requiredUnits = Number(purchaseIntent.credits_units || 0);
|
|
173
|
+
const runtimeBridgeDispatch = getRuntimeBridgeDispatchByPurchaseIntent(state, purchaseIntent.purchase_intent_id);
|
|
174
|
+
const compensation = {
|
|
175
|
+
compensation_state: "review_required",
|
|
176
|
+
compensation_path: null,
|
|
177
|
+
reversal_credit_event_id: null,
|
|
178
|
+
remediation_ticket_id: null,
|
|
179
|
+
requires_manual_followup: true,
|
|
180
|
+
downstream_execution_detected: Boolean(runtimeBridgeDispatch),
|
|
181
|
+
insufficient_available_units: currentAvailableUnits < requiredUnits
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (purchaseIntent.fulfillment && purchaseIntent.fulfillment.credits_issued) {
|
|
185
|
+
const reversalResult = reverseRailCredits(state, {
|
|
186
|
+
account_id: purchaseIntent.account_id,
|
|
187
|
+
purchase_intent_id: purchaseIntent.purchase_intent_id,
|
|
188
|
+
provider_reference: event.provider_reference,
|
|
189
|
+
reason: event.payment_status === "chargeback"
|
|
190
|
+
? "hosted_checkout_chargeback_compensation"
|
|
191
|
+
: "hosted_checkout_refund_compensation"
|
|
192
|
+
});
|
|
193
|
+
if (reversalResult.ok) {
|
|
194
|
+
compensation.reversal_credit_event_id = reversalResult.reversal.credit_event_id;
|
|
195
|
+
compensation.compensation_path = "credit_reversal";
|
|
196
|
+
compensation.compensation_state = (currentAvailableUnits >= requiredUnits && !runtimeBridgeDispatch)
|
|
197
|
+
? "compensated"
|
|
198
|
+
: "remediation_required";
|
|
199
|
+
compensation.requires_manual_followup = compensation.compensation_state !== "compensated";
|
|
200
|
+
} else {
|
|
201
|
+
compensation.compensation_path = "credit_reversal_failed";
|
|
202
|
+
compensation.compensation_state = "compensation_failed";
|
|
203
|
+
}
|
|
204
|
+
} else if (purchaseIntent.fulfillment && purchaseIntent.fulfillment.execution_authorized) {
|
|
205
|
+
compensation.compensation_path = "execution_remediation";
|
|
206
|
+
compensation.compensation_state = "remediation_required";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (compensation.compensation_state !== "compensated") {
|
|
210
|
+
const note = compensation.downstream_execution_detected
|
|
211
|
+
? "Hosted checkout reversal landed after execution dispatch and needs remediation review"
|
|
212
|
+
: compensation.insufficient_available_units
|
|
213
|
+
? "Hosted checkout reversal landed after funded capacity was partially or fully consumed"
|
|
214
|
+
: "Hosted checkout reversal requires explicit operator review";
|
|
215
|
+
const ticket = createHostedCheckoutRemediationTicket(
|
|
216
|
+
state,
|
|
217
|
+
purchaseIntent,
|
|
218
|
+
event,
|
|
219
|
+
refund,
|
|
220
|
+
event.payment_status === "chargeback" ? "chargeback_compensation_review_required" : "refund_compensation_review_required",
|
|
221
|
+
note
|
|
222
|
+
);
|
|
223
|
+
compensation.remediation_ticket_id = ticket.ticket_id;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return compensation;
|
|
227
|
+
}
|
|
228
|
+
|
|
145
229
|
function listHostedCheckoutRefunds(state, purchaseIntentId) {
|
|
146
230
|
const refunds = Array.from(state.refunds.values()).filter((refund) => refund && refund.purchase_intent_id);
|
|
147
231
|
if (!purchaseIntentId) return refunds;
|
|
@@ -162,6 +246,9 @@ function buildHostedCheckoutOperatorSummary(state, accountId) {
|
|
|
162
246
|
credits_issued_count: purchaseIntents.filter((entry) => entry.fulfillment && entry.fulfillment.credits_issued).length,
|
|
163
247
|
execution_dispatched_count: bridgeDispatches.length,
|
|
164
248
|
refund_review_required_count: purchaseIntents.filter((entry) => entry.customer_status === "refund_review_required" || entry.customer_status === "refund_review_requested").length,
|
|
249
|
+
auto_compensated_count: purchaseIntents.filter((entry) => entry && entry.compensation && entry.compensation.compensation_state === "compensated").length,
|
|
250
|
+
compensation_review_required_count: purchaseIntents.filter((entry) => entry && entry.compensation && entry.compensation.compensation_state === "remediation_required").length,
|
|
251
|
+
compensation_failed_count: purchaseIntents.filter((entry) => entry && entry.compensation && entry.compensation.compensation_state === "compensation_failed").length,
|
|
165
252
|
refund_case_count: refunds.length,
|
|
166
253
|
refund_status_counts: refunds.reduce((acc, refund) => {
|
|
167
254
|
const key = refund && refund.status ? refund.status : "unknown";
|
|
@@ -202,7 +289,7 @@ function requestHostedCheckoutRefund(state, purchaseIntentId, body) {
|
|
|
202
289
|
return refund;
|
|
203
290
|
}
|
|
204
291
|
|
|
205
|
-
function normalizeHostedCheckoutEvent(state, body) {
|
|
292
|
+
async function normalizeHostedCheckoutEvent(state, body) {
|
|
206
293
|
const payload = ensureObject(body);
|
|
207
294
|
const eventId = normalizeString(payload.event_id, null);
|
|
208
295
|
if (eventId && state.checkoutEvents.has(eventId)) {
|
|
@@ -228,6 +315,7 @@ function normalizeHostedCheckoutEvent(state, body) {
|
|
|
228
315
|
? Number(payload.amount_received_minor)
|
|
229
316
|
: purchaseIntent.price.amount_minor,
|
|
230
317
|
currency: normalizeString(payload.currency, purchaseIntent.price.currency),
|
|
318
|
+
security_verification: ensureObject(payload.security_verification),
|
|
231
319
|
timestamp,
|
|
232
320
|
applied_status: "recorded",
|
|
233
321
|
credits_event_id: null,
|
|
@@ -263,7 +351,7 @@ function normalizeHostedCheckoutEvent(state, body) {
|
|
|
263
351
|
credits_issued: true
|
|
264
352
|
};
|
|
265
353
|
} else if (purchaseIntent.purchase_mode === "single_task_execution" || purchaseIntent.entitlement_mode === "execution_authorization") {
|
|
266
|
-
const bridgeDispatch = dispatchRuntimeBridgeRequest(state, {
|
|
354
|
+
const bridgeDispatch = await dispatchRuntimeBridgeRequest(state, {
|
|
267
355
|
request_id: `bridge_${purchaseIntent.purchase_intent_id}`,
|
|
268
356
|
account_id: purchaseIntent.account_id,
|
|
269
357
|
capability_id: purchaseIntent.capability_id,
|
|
@@ -315,6 +403,48 @@ function normalizeHostedCheckoutEvent(state, body) {
|
|
|
315
403
|
}
|
|
316
404
|
});
|
|
317
405
|
nextIntent.latest_refund_id = refund.refund_id;
|
|
406
|
+
const compensation = applyHostedCheckoutCompensation(state, purchaseIntent, event, refund);
|
|
407
|
+
event.compensation = compensation;
|
|
408
|
+
nextIntent.compensation = compensation;
|
|
409
|
+
if (compensation.compensation_state === "compensated") {
|
|
410
|
+
refund.status = "completed";
|
|
411
|
+
refund.action_code = "auto_compensated";
|
|
412
|
+
refund.updated_at_iso = nowIso();
|
|
413
|
+
refund.history = [
|
|
414
|
+
...(Array.isArray(refund.history) ? refund.history : []),
|
|
415
|
+
{
|
|
416
|
+
at_iso: refund.updated_at_iso,
|
|
417
|
+
event: "auto_compensated",
|
|
418
|
+
note: paymentStatus === "chargeback"
|
|
419
|
+
? "Chargeback compensation applied automatically"
|
|
420
|
+
: "Refund compensation applied automatically",
|
|
421
|
+
reason_code: refund.reason_code,
|
|
422
|
+
action_code: "auto_compensated",
|
|
423
|
+
previous_status: "requested",
|
|
424
|
+
next_status: "completed",
|
|
425
|
+
actor: { type: "system", role: "hosted_checkout_compensator" },
|
|
426
|
+
context: {
|
|
427
|
+
purchase_intent_id: purchaseIntent.purchase_intent_id,
|
|
428
|
+
provider_reference: event.provider_reference,
|
|
429
|
+
reversal_credit_event_id: compensation.reversal_credit_event_id
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
].slice(-20);
|
|
433
|
+
state.refunds.set(refund.refund_id, refund);
|
|
434
|
+
nextIntent.customer_status = paymentStatus === "chargeback" ? "chargeback_compensated" : "refund_compensated";
|
|
435
|
+
event.applied_status = "auto_compensated";
|
|
436
|
+
if (nextIntent.fulfillment) {
|
|
437
|
+
nextIntent.fulfillment = {
|
|
438
|
+
...nextIntent.fulfillment,
|
|
439
|
+
credits_issued: false
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
nextIntent.customer_status = paymentStatus === "chargeback" ? "chargeback_review_required" : "refund_review_required";
|
|
444
|
+
event.applied_status = compensation.compensation_state === "compensation_failed"
|
|
445
|
+
? "compensation_failed"
|
|
446
|
+
: "refund_review_required";
|
|
447
|
+
}
|
|
318
448
|
}
|
|
319
449
|
|
|
320
450
|
state.checkoutPurchaseIntents.set(purchaseIntentId, nextIntent);
|