@devlab.group/agent-commerce 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +137 -56
  2. package/dist/ap2.d.ts +2 -2
  3. package/dist/ap2.js +4 -3
  4. package/dist/ap2.js.map +1 -1
  5. package/dist/{authorization-BpbWGGrf.d.ts → authorization-CMkIuAkO.d.ts} +1 -1
  6. package/dist/chunk-2CJNA7MS.js +8 -0
  7. package/dist/chunk-2CJNA7MS.js.map +1 -0
  8. package/dist/{chunk-CKJRITWY.js → chunk-5ZYXBCDX.js} +3 -3
  9. package/dist/{chunk-CKJRITWY.js.map → chunk-5ZYXBCDX.js.map} +1 -1
  10. package/dist/{chunk-3LUJHVKX.js → chunk-6Z2H53CG.js} +5 -4
  11. package/dist/chunk-6Z2H53CG.js.map +1 -0
  12. package/dist/{chunk-53WXXIPR.js → chunk-7ZMMXZ4G.js} +6 -10
  13. package/dist/chunk-7ZMMXZ4G.js.map +1 -0
  14. package/dist/{chunk-HOZA3IWP.js → chunk-ES3V6ECS.js} +16 -6
  15. package/dist/chunk-ES3V6ECS.js.map +1 -0
  16. package/dist/chunk-HNRDZLSN.js +1398 -0
  17. package/dist/chunk-HNRDZLSN.js.map +1 -0
  18. package/dist/{chunk-SYEGCNJZ.js → chunk-IEW7NSP2.js} +23 -5
  19. package/dist/chunk-IEW7NSP2.js.map +1 -0
  20. package/dist/chunk-KA2AX5FH.js +44 -0
  21. package/dist/chunk-KA2AX5FH.js.map +1 -0
  22. package/dist/chunk-Y4RE3XYF.js +581 -0
  23. package/dist/chunk-Y4RE3XYF.js.map +1 -0
  24. package/dist/cli/ap2-MBMQHFX4.js +678 -0
  25. package/dist/cli/ap2-MBMQHFX4.js.map +1 -0
  26. package/dist/cli/chunk-2ZZOGI5I.js +1352 -0
  27. package/dist/cli/chunk-2ZZOGI5I.js.map +1 -0
  28. package/dist/cli/chunk-3EQBCFZY.js +53 -0
  29. package/dist/cli/chunk-3EQBCFZY.js.map +1 -0
  30. package/dist/cli/chunk-3M6U7ILB.js +8 -0
  31. package/dist/cli/chunk-3M6U7ILB.js.map +1 -0
  32. package/dist/cli/chunk-3MGAJ5FX.js +70 -0
  33. package/dist/cli/chunk-3MGAJ5FX.js.map +1 -0
  34. package/dist/cli/chunk-5K4XEAZV.js +113 -0
  35. package/dist/cli/chunk-5K4XEAZV.js.map +1 -0
  36. package/dist/cli/chunk-7JNYFUT3.js +256 -0
  37. package/dist/cli/chunk-7JNYFUT3.js.map +1 -0
  38. package/dist/cli/chunk-AA3FIEAR.js +27 -0
  39. package/dist/cli/chunk-AA3FIEAR.js.map +1 -0
  40. package/dist/cli/chunk-FHCFR2GS.js +17 -0
  41. package/dist/cli/chunk-FHCFR2GS.js.map +1 -0
  42. package/dist/cli/chunk-FRMLWGSS.js +8 -0
  43. package/dist/cli/chunk-FRMLWGSS.js.map +1 -0
  44. package/dist/cli/chunk-KLX2YZDG.js +1400 -0
  45. package/dist/cli/chunk-KLX2YZDG.js.map +1 -0
  46. package/dist/cli/chunk-NSPPEF5M.js +334 -0
  47. package/dist/cli/chunk-NSPPEF5M.js.map +1 -0
  48. package/dist/cli/chunk-OSTCU6ZK.js +44 -0
  49. package/dist/cli/chunk-OSTCU6ZK.js.map +1 -0
  50. package/dist/cli/chunk-QQ3WY5L3.js +271 -0
  51. package/dist/cli/chunk-QQ3WY5L3.js.map +1 -0
  52. package/dist/cli/chunk-RWVPYAKC.js +1742 -0
  53. package/dist/cli/chunk-RWVPYAKC.js.map +1 -0
  54. package/dist/cli/chunk-SPAYKVUP.js +118 -0
  55. package/dist/cli/chunk-SPAYKVUP.js.map +1 -0
  56. package/dist/cli/chunk-SS3QFF5N.js +614 -0
  57. package/dist/cli/chunk-SS3QFF5N.js.map +1 -0
  58. package/dist/cli/chunk-TPC4EJLT.js +11 -0
  59. package/dist/cli/chunk-TPC4EJLT.js.map +1 -0
  60. package/dist/cli/config-4IBGVFSP.js +11 -0
  61. package/dist/cli/config-4IBGVFSP.js.map +1 -0
  62. package/dist/cli/index.js +822 -3392
  63. package/dist/cli/index.js.map +1 -1
  64. package/dist/cli/mcp-CNWQ2MOE.js +559 -0
  65. package/dist/cli/mcp-CNWQ2MOE.js.map +1 -0
  66. package/dist/cli/near-5AY3HWYF.js +58 -0
  67. package/dist/cli/near-5AY3HWYF.js.map +1 -0
  68. package/dist/cli/provider-4BQDH56Y.js +396 -0
  69. package/dist/cli/provider-4BQDH56Y.js.map +1 -0
  70. package/dist/cli/run-5MB2D45X.js +7286 -0
  71. package/dist/cli/run-5MB2D45X.js.map +1 -0
  72. package/dist/cli/svm-3MMYFPC7.js +33 -0
  73. package/dist/cli/svm-3MMYFPC7.js.map +1 -0
  74. package/dist/cli/x402-NKR3WQDQ.js +96 -0
  75. package/dist/cli/x402-NKR3WQDQ.js.map +1 -0
  76. package/dist/{constants-BSBRlKRR.d.ts → constants-nackLItD.d.ts} +2 -1
  77. package/dist/index.d.ts +61 -40
  78. package/dist/index.js +2642 -2205
  79. package/dist/index.js.map +1 -1
  80. package/dist/mcp.d.ts +3 -3
  81. package/dist/mcp.js +15 -20
  82. package/dist/mcp.js.map +1 -1
  83. package/dist/mpp.d.ts +12 -5
  84. package/dist/mpp.js +69 -65
  85. package/dist/mpp.js.map +1 -1
  86. package/dist/near-D6C42VPE.js +58 -0
  87. package/dist/near-D6C42VPE.js.map +1 -0
  88. package/dist/networks-olfflCI_.d.ts +38 -0
  89. package/dist/{protocol-adapter-DonxfAwa.d.ts → protocol-adapter-CfxU8Z2T.d.ts} +2 -2
  90. package/dist/{runtime-CiYbgGpi.d.ts → runtime-7vLNYvmU.d.ts} +25 -3
  91. package/dist/svm-QEUAUNPQ.js +33 -0
  92. package/dist/svm-QEUAUNPQ.js.map +1 -0
  93. package/dist/x402.d.ts +76 -64
  94. package/dist/x402.js +13 -4
  95. package/dist/x402.js.map +1 -1
  96. package/package.json +22 -2
  97. package/dist/chunk-3LUJHVKX.js.map +0 -1
  98. package/dist/chunk-53WXXIPR.js.map +0 -1
  99. package/dist/chunk-HOZA3IWP.js.map +0 -1
  100. package/dist/chunk-RKBGCPCK.js +0 -733
  101. package/dist/chunk-RKBGCPCK.js.map +0 -1
  102. package/dist/chunk-SYEGCNJZ.js.map +0 -1
  103. package/dist/chunk-TWRUNWDR.js +0 -265
  104. package/dist/chunk-TWRUNWDR.js.map +0 -1
  105. package/dist/{payment-flow-DCS882tR.d.ts → guardrails-CX_jYXvZ.d.ts} +8 -8
@@ -0,0 +1,1352 @@
1
+ import { SETTLE_AFTER_BACKEND_METADATA_KEY, REUSE_REJECTED_AUTHORIZATION_METADATA_KEY } from './chunk-7JNYFUT3.js';
2
+ import { RESERVED_INPUT_FIELDS, NOOP_LOGGER, DEFAULT_BACKEND_TIMEOUT_MS, CommerceError, isCommerceError, describeError, toCommerceError } from './chunk-QQ3WY5L3.js';
3
+ import { isRecord } from './chunk-3M6U7ILB.js';
4
+ import { isDeepStrictEqual } from 'util';
5
+
6
+ // src/config/filename.ts
7
+ var DEFAULT_CONFIG_FILENAME = "config.yaml";
8
+
9
+ // src/core/execution/backend-request.ts
10
+ var NEVER_FORWARDED_HEADER = /^(authorization|cookie|set-cookie|host|content-length|content-type|connection|keep-alive|te|trailer|transfer-encoding|upgrade|idempotency-key|proxy-.*)$/;
11
+ var PATH_PARAM_PATTERN = /\{([a-zA-Z0-9_.-]+)\}/g;
12
+ function extractPathParameterNames(url) {
13
+ return [...url.matchAll(PATH_PARAM_PATTERN)].map((match) => match[1]);
14
+ }
15
+ function findUnparsedBraceToken(url) {
16
+ const residue = url.replace(PATH_PARAM_PATTERN, "");
17
+ const index = residue.search(/[{}]/);
18
+ if (index === -1) return void 0;
19
+ const token = /\{[^{}]*\}?|\}/.exec(residue.slice(index));
20
+ return token?.[0] ?? residue[index];
21
+ }
22
+ function validateBackendRequestShape(handler, input, context, backendHeaders) {
23
+ const inputRecord = isRecord(input) ? input : {};
24
+ const parts = buildBackendRequestParts(handler, inputRecord, context);
25
+ buildHeaders(handler, context, backendHeaders);
26
+ let target;
27
+ try {
28
+ target = new URL(parts.url);
29
+ } catch {
30
+ return;
31
+ }
32
+ checkQueryCollision(target, parts.query, context);
33
+ }
34
+ function acceptsBody(method) {
35
+ return method !== "GET" && method !== "DELETE";
36
+ }
37
+ function buildBackendRequestParts(handler, input, context) {
38
+ const bindings = handler.inputBindings;
39
+ if (bindings === void 0) {
40
+ const { url: url2, remaining } = applyPathTemplate(handler.url, input, context);
41
+ return acceptsBody(handler.method) ? { url: url2, query: {}, body: { value: remaining } } : { url: url2, query: remaining };
42
+ }
43
+ const pathValues = bindings.path === void 0 ? {} : resolveBoundGroup(input, bindings.path, "path", context);
44
+ const { url } = applyPathTemplate(handler.url, pathValues, context);
45
+ const query = bindings.query === void 0 ? {} : resolveBoundGroup(input, bindings.query, "query", context);
46
+ const bodyValue = bindings.body === void 0 ? void 0 : input[bindings.body];
47
+ if (bodyValue === void 0 || !acceptsBody(handler.method)) return { url, query };
48
+ return { url, query, body: { value: bodyValue } };
49
+ }
50
+ function resolveBoundGroup(input, key, kind, context) {
51
+ const value = input[key];
52
+ if (value === void 0) return {};
53
+ if (!isRecord(value)) {
54
+ throw new CommerceError(
55
+ "INPUT_INVALID",
56
+ `Input "${key}" must be an object of ${kind} parameters`,
57
+ { ...context, details: { field: key } }
58
+ );
59
+ }
60
+ return value;
61
+ }
62
+ function checkQueryCollision(target, remaining, context) {
63
+ const templateKeys = new Set(target.searchParams.keys());
64
+ for (const key of Object.keys(remaining)) {
65
+ if (templateKeys.has(key)) {
66
+ throw new CommerceError(
67
+ "INPUT_INVALID",
68
+ `Input key "${key}" collides with a query parameter already set on backend.url`,
69
+ { requestId: context.requestId, resourceId: context.resourceId, details: { field: key } }
70
+ );
71
+ }
72
+ }
73
+ }
74
+ var TRAVERSAL_PATH_VALUES = /* @__PURE__ */ new Set(["", ".", ".."]);
75
+ function applyPathTemplate(template, input, context) {
76
+ const remaining = { ...input };
77
+ let missing;
78
+ let invalid;
79
+ const url = template.replace(PATH_PARAM_PATTERN, (_match, key) => {
80
+ if (!Object.hasOwn(remaining, key)) {
81
+ missing = key;
82
+ return "";
83
+ }
84
+ const value = remaining[key];
85
+ delete remaining[key];
86
+ const raw = stringifyPrimitive(value);
87
+ if (TRAVERSAL_PATH_VALUES.has(raw)) {
88
+ invalid = key;
89
+ return "";
90
+ }
91
+ return encodeURIComponent(raw);
92
+ });
93
+ if (missing !== void 0) {
94
+ throw new CommerceError("INPUT_INVALID", `Path parameter "${missing}" was not supplied`, {
95
+ ...context,
96
+ details: { field: missing }
97
+ });
98
+ }
99
+ if (invalid !== void 0) {
100
+ throw new CommerceError("INPUT_INVALID", `Path parameter "${invalid}" is not a valid value`, {
101
+ ...context,
102
+ details: { field: invalid }
103
+ });
104
+ }
105
+ return { url, remaining };
106
+ }
107
+ function stringifyPrimitive(value) {
108
+ if (value === void 0 || value === null) return "";
109
+ if (typeof value === "string") return value;
110
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
111
+ try {
112
+ return JSON.stringify(value);
113
+ } catch {
114
+ return String(value);
115
+ }
116
+ }
117
+ function buildHeaders(handler, context, forwarded = {}) {
118
+ let headers;
119
+ try {
120
+ headers = new Headers(handler.headers);
121
+ } catch {
122
+ throw invalidHeaderError(context);
123
+ }
124
+ for (const [name, value] of Object.entries(forwarded)) {
125
+ if (NEVER_FORWARDED_HEADER.test(name.toLowerCase())) continue;
126
+ try {
127
+ if (!headers.has(name)) headers.set(name, value);
128
+ } catch {
129
+ throw new CommerceError("INPUT_INVALID", "A forwarded request header is invalid", {
130
+ ...context,
131
+ details: { reason: "invalid-forwarded-header" }
132
+ });
133
+ }
134
+ }
135
+ return headers;
136
+ }
137
+ function setHeader(headers, name, value, context) {
138
+ try {
139
+ headers.set(name, value);
140
+ } catch {
141
+ throw invalidHeaderError(context);
142
+ }
143
+ }
144
+ function invalidHeaderError(context) {
145
+ return new CommerceError("BACKEND_ERROR", "Backend request headers are invalid", {
146
+ ...context,
147
+ details: { reason: "invalid-header" }
148
+ });
149
+ }
150
+
151
+ // src/core/execution/backend-http.ts
152
+ var MAX_BODY_SNIPPET_LENGTH = 512;
153
+ var IDEMPOTENCY_KEY_HEADER = "idempotency-key";
154
+ var MAX_RESPONSE_BODY_BYTES = 1024 * 1024;
155
+ var BackendErrorResponse = class {
156
+ constructor(status, body, headers = {}) {
157
+ this.status = status;
158
+ this.body = body;
159
+ this.headers = headers;
160
+ }
161
+ status;
162
+ body;
163
+ headers;
164
+ };
165
+ var HttpBackendExecutor = class {
166
+ fetchImpl;
167
+ logger;
168
+ constructor(options = {}) {
169
+ this.fetchImpl = options.fetchImpl ?? fetch;
170
+ this.logger = options.logger ?? NOOP_LOGGER;
171
+ }
172
+ async call(handler, request) {
173
+ const timeoutMs = handler.timeoutMs ?? DEFAULT_BACKEND_TIMEOUT_MS;
174
+ const inputRecord = isRecord(request.input) ? request.input : {};
175
+ const context = { requestId: request.requestId, resourceId: request.resourceId };
176
+ const parts = buildBackendRequestParts(handler, inputRecord, context);
177
+ let target;
178
+ try {
179
+ target = new URL(parts.url);
180
+ } catch (error) {
181
+ throw new CommerceError("BACKEND_ERROR", "Backend URL could not be constructed from input", {
182
+ ...context,
183
+ details: { reason: "invalid-url" },
184
+ cause: error
185
+ });
186
+ }
187
+ const prefixPathname = literalPrefixPathname(handler);
188
+ if (prefixPathname !== void 0 && !target.pathname.startsWith(prefixPathname)) {
189
+ throw new CommerceError(
190
+ "INPUT_INVALID",
191
+ "Path parameters resolved outside the configured backend path",
192
+ context
193
+ );
194
+ }
195
+ const headers = buildHeaders(handler, context, request.backendHeaders);
196
+ if (request.idempotencyKey !== void 0) {
197
+ setHeader(headers, IDEMPOTENCY_KEY_HEADER, request.idempotencyKey, context);
198
+ }
199
+ let body;
200
+ checkQueryCollision(target, parts.query, context);
201
+ for (const [key, value] of Object.entries(parts.query)) {
202
+ target.searchParams.set(key, stringifyPrimitive(value));
203
+ }
204
+ if (parts.body !== void 0) {
205
+ body = JSON.stringify(parts.body.value);
206
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
207
+ }
208
+ const started = performance.now();
209
+ let response;
210
+ try {
211
+ response = await this.fetchImpl(target, {
212
+ method: handler.method,
213
+ headers,
214
+ ...body !== void 0 ? { body } : {},
215
+ redirect: "manual",
216
+ signal: AbortSignal.timeout(timeoutMs)
217
+ });
218
+ } catch (error) {
219
+ const durationMs2 = Math.round(performance.now() - started);
220
+ if (isAbortError(error)) {
221
+ throw new CommerceError(
222
+ "BACKEND_TIMEOUT",
223
+ `Backend request timed out after ${timeoutMs}ms`,
224
+ {
225
+ requestId: request.requestId,
226
+ resourceId: request.resourceId,
227
+ details: { timeoutMs, durationMs: durationMs2 },
228
+ cause: error
229
+ }
230
+ );
231
+ }
232
+ throw new CommerceError("BACKEND_ERROR", "Backend request failed (transport error)", {
233
+ requestId: request.requestId,
234
+ resourceId: request.resourceId,
235
+ details: { durationMs: durationMs2 },
236
+ cause: error
237
+ });
238
+ }
239
+ const durationMs = Math.round(performance.now() - started);
240
+ const isRedirect = response.type === "opaqueredirect" || response.status >= 300 && response.status < 400;
241
+ if (isRedirect) {
242
+ throw new CommerceError(
243
+ "BACKEND_ERROR",
244
+ "Backend responded with a redirect, which is not followed",
245
+ {
246
+ requestId: request.requestId,
247
+ resourceId: request.resourceId,
248
+ details: { status: response.status, reason: "redirect-not-followed" }
249
+ }
250
+ );
251
+ }
252
+ const responseHeaders = Object.fromEntries(response.headers);
253
+ const contentType = response.headers.get("content-type") ?? "";
254
+ let parsedBody;
255
+ try {
256
+ parsedBody = await parseBody(response, contentType);
257
+ } catch (error) {
258
+ const tooLarge = isCommerceError(error);
259
+ throw new CommerceError(
260
+ "BACKEND_ERROR",
261
+ tooLarge ? "Backend response exceeded the maximum allowed size" : "Backend response could not be read",
262
+ {
263
+ requestId: request.requestId,
264
+ resourceId: request.resourceId,
265
+ details: tooLarge ? { reason: "response-too-large", maxBytes: MAX_RESPONSE_BODY_BYTES } : { reason: "read-error" },
266
+ cause: error
267
+ }
268
+ );
269
+ }
270
+ if (response.status < 200 || response.status >= 300) {
271
+ this.logger.debug(
272
+ {
273
+ requestId: request.requestId,
274
+ resourceId: request.resourceId,
275
+ status: response.status,
276
+ bodySnippet: truncateSnippet(parsedBody)
277
+ },
278
+ "Backend responded with a non-2xx status"
279
+ );
280
+ throw new CommerceError("BACKEND_ERROR", `Backend responded with status ${response.status}`, {
281
+ requestId: request.requestId,
282
+ resourceId: request.resourceId,
283
+ details: { status: response.status },
284
+ cause: new BackendErrorResponse(response.status, parsedBody, responseHeaders)
285
+ });
286
+ }
287
+ return {
288
+ status: response.status,
289
+ headers: responseHeaders,
290
+ body: parsedBody,
291
+ durationMs
292
+ };
293
+ }
294
+ };
295
+ var prefixPathnames = /* @__PURE__ */ new WeakMap();
296
+ function literalPrefixPathname(handler) {
297
+ let cached = prefixPathnames.get(handler);
298
+ if (cached === void 0) {
299
+ try {
300
+ cached = new URL(handler.url.split("{")[0] ?? handler.url).pathname;
301
+ } catch {
302
+ cached = null;
303
+ }
304
+ prefixPathnames.set(handler, cached);
305
+ }
306
+ return cached ?? void 0;
307
+ }
308
+ async function readBodyCapped(response, maxBytes) {
309
+ if (!response.body) return "";
310
+ const reader = response.body.getReader();
311
+ const chunks = [];
312
+ let total = 0;
313
+ try {
314
+ for (; ; ) {
315
+ const { done, value } = await reader.read();
316
+ if (done) break;
317
+ total += value.byteLength;
318
+ if (total > maxBytes) {
319
+ await reader.cancel();
320
+ throw new CommerceError("BACKEND_ERROR", `response body exceeded ${maxBytes} bytes`);
321
+ }
322
+ chunks.push(value);
323
+ }
324
+ } finally {
325
+ reader.releaseLock();
326
+ }
327
+ return Buffer.concat(chunks).toString("utf8");
328
+ }
329
+ async function parseBody(response, contentType) {
330
+ const text = await readBodyCapped(response, MAX_RESPONSE_BODY_BYTES);
331
+ if (text.length === 0) return text;
332
+ if (contentType.includes("json")) {
333
+ try {
334
+ return JSON.parse(text);
335
+ } catch {
336
+ return text;
337
+ }
338
+ }
339
+ return text;
340
+ }
341
+ function truncateSnippet(body) {
342
+ const text = typeof body === "string" ? body : safeStringify(body);
343
+ return text.length > MAX_BODY_SNIPPET_LENGTH ? `${text.slice(0, MAX_BODY_SNIPPET_LENGTH)}\u2026` : text;
344
+ }
345
+ function safeStringify(value) {
346
+ try {
347
+ return JSON.stringify(value) ?? String(value);
348
+ } catch {
349
+ return String(value);
350
+ }
351
+ }
352
+ function isAbortError(error) {
353
+ return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
354
+ }
355
+ var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
356
+ "object",
357
+ "string",
358
+ "number",
359
+ "integer",
360
+ "boolean",
361
+ "array",
362
+ "null"
363
+ ]);
364
+ function compileJsonSchema(schema) {
365
+ return (input) => {
366
+ if (schema === void 0) return { valid: true, value: input };
367
+ const errors = validateNode(schema, input, "$");
368
+ return errors.length === 0 ? { valid: true, value: input } : { valid: false, errors };
369
+ };
370
+ }
371
+ function matchesType(type, value) {
372
+ switch (type) {
373
+ case "object":
374
+ return isRecord(value);
375
+ case "array":
376
+ return Array.isArray(value);
377
+ case "string":
378
+ return typeof value === "string";
379
+ case "boolean":
380
+ return typeof value === "boolean";
381
+ case "number":
382
+ return typeof value === "number" && Number.isFinite(value);
383
+ case "integer":
384
+ return typeof value === "number" && Number.isInteger(value);
385
+ case "null":
386
+ return value === null;
387
+ default:
388
+ return true;
389
+ }
390
+ }
391
+ function describeType(value) {
392
+ if (value === null) return "null";
393
+ if (Array.isArray(value)) return "array";
394
+ return typeof value;
395
+ }
396
+ function deepEqual(a, b) {
397
+ return a === b || isDeepStrictEqual(a, b);
398
+ }
399
+ function own(obj, key) {
400
+ return Object.hasOwn(obj, key) ? obj[key] : void 0;
401
+ }
402
+ function typeList(raw) {
403
+ if (typeof raw === "string") return [raw];
404
+ if (Array.isArray(raw) && raw.every((entry) => typeof entry === "string")) {
405
+ return raw;
406
+ }
407
+ return void 0;
408
+ }
409
+ function isObjectSchemaNode(schema) {
410
+ const types = typeList(schema["type"]);
411
+ if (types !== void 0) return types.includes("object");
412
+ return Object.hasOwn(schema, "properties") || Object.hasOwn(schema, "required");
413
+ }
414
+ function validateNode(schema, value, path) {
415
+ const errors = [];
416
+ const types = typeList(own(schema, "type"));
417
+ const recognizedTypes = types?.filter((entry) => SUPPORTED_TYPES.has(entry));
418
+ if (types !== void 0 && recognizedTypes !== void 0 && recognizedTypes.length === types.length && types.length > 0) {
419
+ if (!types.some((entry) => matchesType(entry, value))) {
420
+ errors.push({
421
+ path,
422
+ message: `expected type "${types.join(" | ")}" but got ${describeType(value)}`
423
+ });
424
+ return errors;
425
+ }
426
+ }
427
+ const enumRaw = own(schema, "enum");
428
+ if (Array.isArray(enumRaw)) {
429
+ const allowed = enumRaw;
430
+ if (!allowed.some((candidate) => deepEqual(candidate, value))) {
431
+ errors.push({ path, message: `must be one of ${JSON.stringify(allowed)}` });
432
+ }
433
+ }
434
+ if (isObjectSchemaNode(schema) && isRecord(value)) {
435
+ errors.push(...validateObject(schema, value, path));
436
+ } else if ((types?.includes("array") ?? false) && Array.isArray(value)) {
437
+ errors.push(...validateArray(schema, value, path));
438
+ }
439
+ return errors;
440
+ }
441
+ function validateObject(schema, value, path) {
442
+ const errors = [];
443
+ const propsRaw = own(schema, "properties");
444
+ const properties = isRecord(propsRaw) ? propsRaw : {};
445
+ const requiredRaw = own(schema, "required");
446
+ const required = Array.isArray(requiredRaw) ? requiredRaw.filter((entry) => typeof entry === "string") : [];
447
+ for (const key of required) {
448
+ if (!Object.hasOwn(value, key)) {
449
+ errors.push({ path: `${path}.${key}`, message: "is required" });
450
+ }
451
+ }
452
+ const additional = own(schema, "additionalProperties");
453
+ for (const key of Object.keys(value)) {
454
+ const propSchemaRaw = own(properties, key);
455
+ if (propSchemaRaw !== void 0) {
456
+ if (isRecord(propSchemaRaw)) {
457
+ errors.push(...validateNode(propSchemaRaw, value[key], `${path}.${key}`));
458
+ }
459
+ continue;
460
+ }
461
+ if (additional === false) {
462
+ errors.push({ path: `${path}.${key}`, message: "additional property not allowed" });
463
+ } else if (isRecord(additional)) {
464
+ errors.push(...validateNode(additional, value[key], `${path}.${key}`));
465
+ }
466
+ }
467
+ return errors;
468
+ }
469
+ function validateArray(schema, value, path) {
470
+ const errors = [];
471
+ const itemsRaw = own(schema, "items");
472
+ if (isRecord(itemsRaw)) {
473
+ value.forEach((item, index) => {
474
+ errors.push(...validateNode(itemsRaw, item, `${path}[${index}]`));
475
+ });
476
+ }
477
+ return errors;
478
+ }
479
+
480
+ // src/core/execution/pipeline/authorization.ts
481
+ function resolveAuthorizationProviders(providers, resource, requestId) {
482
+ const required = resource.authorization?.required ?? [];
483
+ return required.map((method) => {
484
+ const found = providers.find((provider) => provider.name === method);
485
+ if (!found) {
486
+ throw new CommerceError(
487
+ "CONFIG_INVALID",
488
+ `Resource "${resource.id}" requires authorization method "${method}", which is not enabled`,
489
+ { requestId, resourceId: resource.id }
490
+ );
491
+ }
492
+ return found;
493
+ });
494
+ }
495
+ function createAuthorizeAndReserve(ctx) {
496
+ const { options, safeEmit, buildEvent } = ctx;
497
+ async function authorizeAndReserve(request, resource, providers, requirement, input) {
498
+ if (providers.length === 0) return void 0;
499
+ const submission = request.authorization;
500
+ const provider = providers.find((candidate) => candidate.name === submission?.method);
501
+ const unmet = providers.filter((candidate) => candidate !== provider).map((c) => c.name);
502
+ if (submission === void 0 || provider === void 0 || unmet.length > 0) {
503
+ await safeEmit(
504
+ buildEvent({
505
+ type: "authorization.rejected",
506
+ requestId: request.requestId,
507
+ resourceId: resource.id,
508
+ adapter: request.protocol,
509
+ status: "error",
510
+ data: { reason: "AUTHORIZATION_REQUIRED", methods: unmet }
511
+ })
512
+ );
513
+ throw new CommerceError(
514
+ "AUTHORIZATION_REQUIRED",
515
+ `Resource "${resource.id}" requires authorization: ${unmet.join(", ")}`,
516
+ {
517
+ requestId: request.requestId,
518
+ resourceId: resource.id,
519
+ details: { required: providers.map((candidate) => candidate.name), missing: unmet }
520
+ }
521
+ );
522
+ }
523
+ let verification;
524
+ try {
525
+ verification = await provider.verifyAndReserve({
526
+ requestId: request.requestId,
527
+ resourceId: resource.id,
528
+ input,
529
+ submission,
530
+ requirement
531
+ });
532
+ } catch (error) {
533
+ const mapped = isCommerceError(error) ? error : new CommerceError(
534
+ "AUTHORIZATION_PROVIDER_UNAVAILABLE",
535
+ "Authorization provider is unavailable",
536
+ {
537
+ requestId: request.requestId,
538
+ resourceId: resource.id,
539
+ cause: error
540
+ }
541
+ );
542
+ await safeEmit(
543
+ buildEvent({
544
+ type: "authorization.rejected",
545
+ requestId: request.requestId,
546
+ resourceId: resource.id,
547
+ adapter: request.protocol,
548
+ status: "error",
549
+ data: { reason: mapped.code, method: provider.name }
550
+ })
551
+ );
552
+ throw mapped;
553
+ }
554
+ await safeEmit(
555
+ buildEvent({
556
+ type: "authorization.verified",
557
+ requestId: request.requestId,
558
+ resourceId: resource.id,
559
+ adapter: request.protocol,
560
+ status: "ok",
561
+ data: { method: verification.method, reference: verification.reference }
562
+ })
563
+ );
564
+ const context = { requestId: request.requestId, resourceId: resource.id };
565
+ return {
566
+ record: {
567
+ method: verification.method,
568
+ reference: verification.reference,
569
+ ...verification.metadata !== void 0 ? { metadata: verification.metadata } : {}
570
+ },
571
+ async finalize(action) {
572
+ try {
573
+ await provider[action](verification.reservationId, context);
574
+ } catch (error) {
575
+ options.logger.error(
576
+ { err: describeError(error), requestId: request.requestId, action },
577
+ "Authorization finalization failed; the reservation stays reserved"
578
+ );
579
+ }
580
+ }
581
+ };
582
+ }
583
+ return authorizeAndReserve;
584
+ }
585
+
586
+ // src/core/execution/pipeline/challenge.ts
587
+ function retryChallenge(requirement, reason) {
588
+ const envelope = requirement.challenge.envelope;
589
+ if (envelope === void 0 && reason === void 0) return {};
590
+ return {
591
+ details: {
592
+ ...envelope !== void 0 ? { challenge: envelope } : {},
593
+ ...reason !== void 0 ? { reason } : {}
594
+ }
595
+ };
596
+ }
597
+ var FINISHED_ATTEMPT_STATUSES = /* @__PURE__ */ new Set(["settled", "rejected"]);
598
+ function replayError(error, requirement) {
599
+ if (error.code !== "PAYMENT_REPLAYED") return error;
600
+ const status = error.details?.["attemptStatus"];
601
+ const finished = typeof status === "string" && FINISHED_ATTEMPT_STATUSES.has(status);
602
+ const network = requirement.network;
603
+ if (!finished && network === void 0) return error;
604
+ return new CommerceError("PAYMENT_REPLAYED", error.message, {
605
+ ...error.requestId !== void 0 ? { requestId: error.requestId } : {},
606
+ ...error.resourceId !== void 0 ? { resourceId: error.resourceId } : {},
607
+ ...finished ? { httpStatus: 402 } : {},
608
+ details: {
609
+ ...error.details,
610
+ ...network !== void 0 ? { network } : {},
611
+ ...finished ? retryChallenge(requirement).details : {}
612
+ }
613
+ });
614
+ }
615
+
616
+ // src/core/execution/pipeline/admission.ts
617
+ function createPaymentAdmission(ctx, authorizeAndReserve) {
618
+ const { options, safeEmit, buildEvent } = ctx;
619
+ return async function admitPayment(request, resource, pricing, validInput) {
620
+ const provider = pickProvider(options.paymentProviders, resource.paymentMethods);
621
+ if (!provider) {
622
+ throw new CommerceError(
623
+ "PAYMENT_PROVIDER_UNAVAILABLE",
624
+ `No enabled payment provider for resource "${resource.id}"`,
625
+ {
626
+ requestId: request.requestId,
627
+ resourceId: resource.id,
628
+ details: { paymentMethods: resource.paymentMethods }
629
+ }
630
+ );
631
+ }
632
+ const authProviders = resolveAuthorizationProviders(
633
+ options.authorizationProviders ?? [],
634
+ resource,
635
+ request.requestId
636
+ );
637
+ let requirement;
638
+ try {
639
+ requirement = await provider.createRequirement({
640
+ requestId: request.requestId,
641
+ resource,
642
+ amount: pricing.amount,
643
+ currency: pricing.currency,
644
+ requestedAt: options.clock.nowIso(),
645
+ // Transport facts a rail may bind into its challenge, such as a body digest
646
+ ...request.metadata !== void 0 ? { metadata: request.metadata } : {}
647
+ });
648
+ } catch (error) {
649
+ throw isCommerceError(error) ? error : new CommerceError("PAYMENT_PROVIDER_UNAVAILABLE", "Payment provider is unavailable", {
650
+ requestId: request.requestId,
651
+ resourceId: resource.id,
652
+ cause: error
653
+ });
654
+ }
655
+ if (!request.payment) {
656
+ await safeEmit(
657
+ buildEvent({
658
+ type: "payment.required",
659
+ requestId: request.requestId,
660
+ resourceId: resource.id,
661
+ adapter: request.protocol,
662
+ paymentProvider: provider.name
663
+ })
664
+ );
665
+ return {
666
+ kind: "payment-required",
667
+ requestId: request.requestId,
668
+ resourceId: resource.id,
669
+ requirement,
670
+ ...authProviders.length > 0 ? { authorization: authProviders.map((p) => p.requirement) } : {}
671
+ };
672
+ }
673
+ const payment = request.payment;
674
+ let verification;
675
+ try {
676
+ if (provider.selectRequirement) {
677
+ requirement = await provider.selectRequirement({
678
+ requestId: request.requestId,
679
+ resource,
680
+ requirement,
681
+ submission: payment
682
+ });
683
+ }
684
+ verification = await provider.verify({
685
+ requestId: request.requestId,
686
+ resource,
687
+ requirement,
688
+ submission: payment
689
+ });
690
+ } catch (error) {
691
+ const mapped = isCommerceError(error) ? error : new CommerceError("PAYMENT_INVALID", "Payment verification failed", {
692
+ requestId: request.requestId,
693
+ resourceId: resource.id,
694
+ cause: error,
695
+ ...retryChallenge(requirement)
696
+ });
697
+ await safeEmit(
698
+ buildEvent({
699
+ type: "payment.rejected",
700
+ requestId: request.requestId,
701
+ resourceId: resource.id,
702
+ adapter: request.protocol,
703
+ paymentProvider: provider.name,
704
+ status: "error",
705
+ data: { reason: mapped.code }
706
+ })
707
+ );
708
+ throw mapped;
709
+ }
710
+ if (verification.status !== "verified") {
711
+ await safeEmit(
712
+ buildEvent({
713
+ type: "payment.rejected",
714
+ requestId: request.requestId,
715
+ resourceId: resource.id,
716
+ adapter: request.protocol,
717
+ paymentProvider: provider.name,
718
+ status: "error",
719
+ data: verification.rejectionReason ? { reason: verification.rejectionReason } : {}
720
+ })
721
+ );
722
+ throw new CommerceError(
723
+ "PAYMENT_INVALID",
724
+ verification.rejectionReason ?? "Payment was rejected",
725
+ {
726
+ requestId: request.requestId,
727
+ resourceId: resource.id,
728
+ ...retryChallenge(requirement, verification.rejectionReason)
729
+ }
730
+ );
731
+ }
732
+ if (!verification.replayKey) {
733
+ await safeEmit(
734
+ buildEvent({
735
+ type: "payment.rejected",
736
+ requestId: request.requestId,
737
+ resourceId: resource.id,
738
+ adapter: request.protocol,
739
+ paymentProvider: provider.name,
740
+ status: "error",
741
+ data: { reason: "missing-replay-key" }
742
+ })
743
+ );
744
+ throw new CommerceError("PAYMENT_INVALID", "Payment provider did not return a replay key", {
745
+ requestId: request.requestId,
746
+ resourceId: resource.id
747
+ });
748
+ }
749
+ const replayKey = verification.replayKey;
750
+ const hold = await authorizeAndReserve(
751
+ request,
752
+ resource,
753
+ authProviders,
754
+ requirement,
755
+ validInput
756
+ );
757
+ try {
758
+ await options.store.reservePaymentAttempt({
759
+ requestId: request.requestId,
760
+ resourceId: resource.id,
761
+ provider: provider.name,
762
+ replayKey,
763
+ amount: verification.amount,
764
+ currency: verification.currency,
765
+ ...verification.payer !== void 0 ? { payer: verification.payer } : {},
766
+ ...verification.payee !== void 0 ? { payee: verification.payee } : {},
767
+ ...requirement.metadata?.[REUSE_REJECTED_AUTHORIZATION_METADATA_KEY] === true ? { reclaimRejected: true } : {}
768
+ });
769
+ } catch (error) {
770
+ const mapped = toCommerceError(
771
+ error,
772
+ "STORAGE_ERROR",
773
+ "Payment attempt could not be recorded"
774
+ );
775
+ await hold?.finalize("release");
776
+ await safeEmit(
777
+ buildEvent({
778
+ type: "payment.rejected",
779
+ requestId: request.requestId,
780
+ resourceId: resource.id,
781
+ adapter: request.protocol,
782
+ paymentProvider: provider.name,
783
+ status: "error",
784
+ data: { reason: mapped.code }
785
+ })
786
+ );
787
+ throw replayError(mapped, requirement);
788
+ }
789
+ await safeEmit(
790
+ buildEvent({
791
+ type: "payment.verified",
792
+ requestId: request.requestId,
793
+ resourceId: resource.id,
794
+ adapter: request.protocol,
795
+ paymentProvider: provider.name,
796
+ status: "ok"
797
+ })
798
+ );
799
+ return {
800
+ kind: "admitted",
801
+ settleArgs: {
802
+ request,
803
+ resource,
804
+ provider,
805
+ requirement,
806
+ payment,
807
+ verification,
808
+ replayKey,
809
+ hold
810
+ }
811
+ };
812
+ };
813
+ }
814
+ function pickProvider(providers, methods) {
815
+ for (const method of methods) {
816
+ const found = providers.find((provider) => provider.name === method);
817
+ if (found) return found;
818
+ }
819
+ return void 0;
820
+ }
821
+
822
+ // src/core/execution/pipeline/context.ts
823
+ function createPipelineContext(options) {
824
+ async function safeEmit(event) {
825
+ try {
826
+ await options.events.emit(event);
827
+ } catch (error) {
828
+ options.logger.error(
829
+ { err: describeError(error), eventType: event.type, requestId: event.requestId },
830
+ "Event sink failed; commerce flow continues"
831
+ );
832
+ }
833
+ }
834
+ async function safePersist(op, label, requestId) {
835
+ try {
836
+ await op();
837
+ } catch (error) {
838
+ options.logger.error(
839
+ { err: describeError(error), requestId },
840
+ `Persistence failed: ${label}`
841
+ );
842
+ }
843
+ }
844
+ function buildEvent(partial) {
845
+ return { id: options.ids.next("evt"), at: options.clock.nowIso(), ...partial };
846
+ }
847
+ return { options, safeEmit, safePersist, buildEvent };
848
+ }
849
+
850
+ // src/core/execution/pipeline/settlement.ts
851
+ function createSettlement(ctx) {
852
+ const { options, safeEmit, safePersist, buildEvent } = ctx;
853
+ async function settle(args) {
854
+ const { request, resource, provider, requirement, payment, verification, replayKey, hold } = args;
855
+ let settlement;
856
+ try {
857
+ settlement = await provider.settle({
858
+ requestId: request.requestId,
859
+ resource,
860
+ requirement,
861
+ submission: payment,
862
+ verification
863
+ });
864
+ } catch (error) {
865
+ const notSubmitted = notSubmittedReason(error);
866
+ if (notSubmitted !== void 0) {
867
+ await hold?.finalize("release");
868
+ await safePersist(
869
+ () => options.store.updatePaymentAttempt({
870
+ replayKey,
871
+ status: "rejected",
872
+ rejectionReason: notSubmitted
873
+ }),
874
+ "updatePaymentAttempt(not-submitted)",
875
+ request.requestId
876
+ );
877
+ await safeEmit(
878
+ buildEvent({
879
+ type: "payment.rejected",
880
+ requestId: request.requestId,
881
+ resourceId: resource.id,
882
+ adapter: request.protocol,
883
+ paymentProvider: provider.name,
884
+ status: "error",
885
+ data: { reason: "settlement-not-submitted", providerReason: notSubmitted }
886
+ })
887
+ );
888
+ throw new CommerceError(
889
+ "PAYMENT_PROVIDER_UNAVAILABLE",
890
+ "The payment provider could not settle the payment; nothing was charged",
891
+ { requestId: request.requestId, resourceId: resource.id, cause: error }
892
+ );
893
+ }
894
+ const txHash = settlementTxHash(error);
895
+ await hold?.finalize("markUncertain");
896
+ await safePersist(
897
+ () => options.store.updatePaymentAttempt({
898
+ replayKey,
899
+ status: "settlement-uncertain",
900
+ ...txHash !== void 0 ? { externalReference: txHash } : {},
901
+ ...error instanceof Error ? { rejectionReason: error.message } : {}
902
+ }),
903
+ "updatePaymentAttempt(settlement-uncertain)",
904
+ request.requestId
905
+ );
906
+ await safeEmit(
907
+ buildEvent({
908
+ type: "payment.rejected",
909
+ requestId: request.requestId,
910
+ resourceId: resource.id,
911
+ adapter: request.protocol,
912
+ paymentProvider: provider.name,
913
+ status: "error",
914
+ data: {
915
+ reason: "settlement-uncertain",
916
+ ...txHash !== void 0 ? { transactionHash: txHash } : {}
917
+ }
918
+ })
919
+ );
920
+ throw new CommerceError("PAYMENT_SETTLEMENT_FAILED", "Settlement could not be confirmed", {
921
+ requestId: request.requestId,
922
+ resourceId: resource.id,
923
+ cause: error,
924
+ details: {
925
+ settlementUncertain: true,
926
+ ...txHash !== void 0 ? { transactionHash: txHash } : {},
927
+ ...settlementParties(requirement, verification)
928
+ }
929
+ });
930
+ }
931
+ if (settlement.status !== "settled") {
932
+ await hold?.finalize("release");
933
+ await safePersist(
934
+ () => options.store.updatePaymentAttempt({
935
+ replayKey,
936
+ status: "rejected",
937
+ ...settlement.rejectionReason !== void 0 ? { rejectionReason: settlement.rejectionReason } : {},
938
+ ...settlement.externalReference !== void 0 ? { externalReference: settlement.externalReference } : {}
939
+ }),
940
+ "updatePaymentAttempt(rejected)",
941
+ request.requestId
942
+ );
943
+ await safeEmit(
944
+ buildEvent({
945
+ type: "payment.rejected",
946
+ requestId: request.requestId,
947
+ resourceId: resource.id,
948
+ adapter: request.protocol,
949
+ paymentProvider: provider.name,
950
+ status: "error",
951
+ data: settlement.rejectionReason ? { reason: settlement.rejectionReason } : {}
952
+ })
953
+ );
954
+ const rejectionDetails = retryChallenge(requirement, settlement.rejectionReason).details;
955
+ throw new CommerceError(
956
+ "PAYMENT_SETTLEMENT_FAILED",
957
+ settlement.rejectionReason ?? "Settlement was rejected",
958
+ {
959
+ requestId: request.requestId,
960
+ resourceId: resource.id,
961
+ httpStatus: 402,
962
+ details: {
963
+ ...rejectionDetails,
964
+ ...settlementParties(requirement, settlement, verification)
965
+ }
966
+ }
967
+ );
968
+ }
969
+ await hold?.finalize("consume");
970
+ await safePersist(
971
+ () => options.store.updatePaymentAttempt({
972
+ replayKey,
973
+ status: "settled",
974
+ ...settlement.externalReference !== void 0 ? { externalReference: settlement.externalReference } : {}
975
+ }),
976
+ "updatePaymentAttempt(settled)",
977
+ request.requestId
978
+ );
979
+ await safeEmit(
980
+ buildEvent({
981
+ type: "payment.settled",
982
+ requestId: request.requestId,
983
+ resourceId: resource.id,
984
+ adapter: request.protocol,
985
+ paymentProvider: provider.name,
986
+ status: "ok"
987
+ })
988
+ );
989
+ return settlement;
990
+ }
991
+ async function abandonSettlement(args, backendErrorCode) {
992
+ const { request, resource, provider, replayKey, hold } = args;
993
+ await hold?.finalize("release");
994
+ await safePersist(
995
+ () => options.store.updatePaymentAttempt({
996
+ replayKey,
997
+ status: "rejected",
998
+ rejectionReason: "backend_failed"
999
+ }),
1000
+ "updatePaymentAttempt(backend-failed)",
1001
+ request.requestId
1002
+ );
1003
+ await safeEmit(
1004
+ buildEvent({
1005
+ type: "payment.rejected",
1006
+ requestId: request.requestId,
1007
+ resourceId: resource.id,
1008
+ adapter: request.protocol,
1009
+ paymentProvider: provider.name,
1010
+ status: "error",
1011
+ data: { reason: "backend-failed", backendErrorCode }
1012
+ })
1013
+ );
1014
+ }
1015
+ return { settle, abandonSettlement };
1016
+ }
1017
+ function notSubmittedReason(error) {
1018
+ if (!isCommerceError(error) || error.details?.["settlementNotSubmitted"] !== true) {
1019
+ return void 0;
1020
+ }
1021
+ const reason = error.details["reason"];
1022
+ return typeof reason === "string" ? reason : "settlement_not_submitted";
1023
+ }
1024
+ function settlementTxHash(error) {
1025
+ const hash = isCommerceError(error) ? error.details?.["transactionHash"] : void 0;
1026
+ return typeof hash === "string" ? hash : void 0;
1027
+ }
1028
+ function settlementParties(requirement, ...results) {
1029
+ const network = results.find((r) => r.network !== void 0)?.network ?? requirement.network;
1030
+ const payer = results.find((r) => r.payer !== void 0)?.payer;
1031
+ return {
1032
+ ...network !== void 0 ? { network } : {},
1033
+ ...payer !== void 0 ? { payer } : {}
1034
+ };
1035
+ }
1036
+
1037
+ // src/core/execution/pipeline/index.ts
1038
+ function createExecutionPipeline(options) {
1039
+ const validators = /* @__PURE__ */ new Map();
1040
+ function getValidator(resource) {
1041
+ const cached = validators.get(resource.id);
1042
+ if (cached) return cached;
1043
+ const compiled = compileJsonSchema(resource.inputSchema);
1044
+ validators.set(resource.id, compiled);
1045
+ return compiled;
1046
+ }
1047
+ const ctx = createPipelineContext(options);
1048
+ const { safeEmit, safePersist, buildEvent } = ctx;
1049
+ const { settle, abandonSettlement } = createSettlement(ctx);
1050
+ const admitPayment = createPaymentAdmission(ctx, createAuthorizeAndReserve(ctx));
1051
+ async function execute(request) {
1052
+ const pipelineStart = options.clock.monotonicMs();
1053
+ const resource = options.resources.get(request.resourceId);
1054
+ if (!resource?.exposedVia.includes(request.protocol)) {
1055
+ throw new CommerceError(
1056
+ "RESOURCE_NOT_FOUND",
1057
+ `Resource "${request.resourceId}" was not found`,
1058
+ {
1059
+ requestId: request.requestId,
1060
+ resourceId: request.resourceId
1061
+ }
1062
+ );
1063
+ }
1064
+ await safeEmit(
1065
+ buildEvent({
1066
+ type: "resource.requested",
1067
+ requestId: request.requestId,
1068
+ resourceId: resource.id,
1069
+ adapter: request.protocol
1070
+ })
1071
+ );
1072
+ const strippedInput = stripReservedFields(request.input);
1073
+ const validation = getValidator(resource)(strippedInput);
1074
+ if (!validation.valid) {
1075
+ throw new CommerceError(
1076
+ "INPUT_INVALID",
1077
+ `Input for resource "${resource.id}" failed validation`,
1078
+ {
1079
+ requestId: request.requestId,
1080
+ resourceId: resource.id,
1081
+ details: { errors: validation.errors }
1082
+ }
1083
+ );
1084
+ }
1085
+ const validInput = validation.value;
1086
+ validateBackendRequestShape(
1087
+ resource.handler,
1088
+ validInput,
1089
+ { requestId: request.requestId, resourceId: resource.id },
1090
+ request.backendHeaders
1091
+ );
1092
+ if (resource.pricing.type === "dynamic") {
1093
+ throw new CommerceError(
1094
+ "CONFIG_INVALID",
1095
+ `Resource "${resource.id}" uses unsupported dynamic pricing`,
1096
+ {
1097
+ requestId: request.requestId,
1098
+ resourceId: resource.id
1099
+ }
1100
+ );
1101
+ }
1102
+ if (resource.pricing.type !== "fixed" && (resource.authorization?.required.length ?? 0) > 0) {
1103
+ throw new CommerceError(
1104
+ "CONFIG_INVALID",
1105
+ `Resource "${resource.id}" requires authorization but is not a paid resource`,
1106
+ {
1107
+ requestId: request.requestId,
1108
+ resourceId: resource.id
1109
+ }
1110
+ );
1111
+ }
1112
+ let paymentResult;
1113
+ let authorization;
1114
+ let deferredSettlement;
1115
+ if (resource.pricing.type === "fixed") {
1116
+ const admission = await admitPayment(request, resource, resource.pricing, validInput);
1117
+ if (admission.kind === "payment-required") return admission;
1118
+ const { settleArgs } = admission;
1119
+ authorization = settleArgs.hold?.record;
1120
+ if (settleArgs.requirement.metadata?.[SETTLE_AFTER_BACKEND_METADATA_KEY] === true) {
1121
+ deferredSettlement = settleArgs;
1122
+ } else {
1123
+ paymentResult = await settle(settleArgs);
1124
+ }
1125
+ }
1126
+ const backendStart = options.clock.monotonicMs();
1127
+ let backendResponse;
1128
+ try {
1129
+ backendResponse = await options.backend.call(resource.handler, {
1130
+ requestId: request.requestId,
1131
+ resourceId: resource.id,
1132
+ input: validInput,
1133
+ ...request.idempotencyKey !== void 0 ? { idempotencyKey: request.idempotencyKey } : {},
1134
+ ...request.backendHeaders !== void 0 ? { backendHeaders: request.backendHeaders } : {}
1135
+ });
1136
+ } catch (error) {
1137
+ const commerceError = toCommerceError(error, "BACKEND_ERROR", "Backend call failed");
1138
+ await safeEmit(
1139
+ buildEvent({
1140
+ type: "backend.failed",
1141
+ requestId: request.requestId,
1142
+ resourceId: resource.id,
1143
+ adapter: request.protocol,
1144
+ status: "error",
1145
+ durationMs: Math.round(options.clock.monotonicMs() - backendStart),
1146
+ data: { code: commerceError.code }
1147
+ })
1148
+ );
1149
+ if (deferredSettlement !== void 0) {
1150
+ await abandonSettlement(deferredSettlement, commerceError.code);
1151
+ throw commerceError;
1152
+ }
1153
+ if (paymentResult !== void 0) {
1154
+ const failedReceipt = {
1155
+ id: options.ids.next("receipt"),
1156
+ requestId: request.requestId,
1157
+ resourceId: resource.id,
1158
+ deliveredAt: options.clock.nowIso(),
1159
+ backendStatus: backendErrorStatus(commerceError),
1160
+ protocol: request.protocol,
1161
+ payment: paymentResult,
1162
+ ...authorization !== void 0 ? { authorization } : {},
1163
+ metadata: { delivered: false, backendErrorCode: commerceError.code }
1164
+ };
1165
+ await safePersist(
1166
+ () => options.store.saveReceipt(failedReceipt),
1167
+ "saveReceipt(backend-failed-after-settlement)",
1168
+ request.requestId
1169
+ );
1170
+ throw new CommerceError(
1171
+ "BACKEND_ERROR",
1172
+ "Payment settled but the backend call failed; the payment was not refunded automatically",
1173
+ {
1174
+ requestId: request.requestId,
1175
+ resourceId: resource.id,
1176
+ cause: commerceError,
1177
+ details: {
1178
+ payment: {
1179
+ status: paymentResult.status,
1180
+ provider: paymentResult.provider,
1181
+ amount: paymentResult.amount,
1182
+ currency: paymentResult.currency,
1183
+ ...paymentResult.network !== void 0 ? { network: paymentResult.network } : {},
1184
+ ...paymentResult.externalReference !== void 0 ? { externalReference: paymentResult.externalReference } : {},
1185
+ ...paymentResult.payer !== void 0 ? { payer: paymentResult.payer } : {},
1186
+ ...typeof paymentResult.metadata?.["amountBaseUnits"] === "string" ? { amountBaseUnits: paymentResult.metadata["amountBaseUnits"] } : {},
1187
+ ...typeof paymentResult.metadata?.["receipt"] === "string" ? { receipt: paymentResult.metadata["receipt"] } : {}
1188
+ }
1189
+ }
1190
+ }
1191
+ );
1192
+ }
1193
+ throw commerceError;
1194
+ }
1195
+ await safeEmit(
1196
+ buildEvent({
1197
+ type: "backend.called",
1198
+ requestId: request.requestId,
1199
+ resourceId: resource.id,
1200
+ adapter: request.protocol,
1201
+ status: "ok",
1202
+ durationMs: backendResponse.durationMs,
1203
+ data: { status: backendResponse.status }
1204
+ })
1205
+ );
1206
+ if (deferredSettlement !== void 0) {
1207
+ paymentResult = await settle(deferredSettlement);
1208
+ }
1209
+ const receipt = {
1210
+ id: options.ids.next("receipt"),
1211
+ requestId: request.requestId,
1212
+ resourceId: resource.id,
1213
+ deliveredAt: options.clock.nowIso(),
1214
+ backendStatus: backendResponse.status,
1215
+ durationMs: backendResponse.durationMs,
1216
+ protocol: request.protocol,
1217
+ ...paymentResult !== void 0 ? { payment: paymentResult } : {},
1218
+ ...authorization !== void 0 ? { authorization } : {}
1219
+ };
1220
+ await safePersist(() => options.store.saveReceipt(receipt), "saveReceipt", request.requestId);
1221
+ const totalDurationMs = Math.round(options.clock.monotonicMs() - pipelineStart);
1222
+ await safeEmit(
1223
+ buildEvent({
1224
+ type: "resource.delivered",
1225
+ requestId: request.requestId,
1226
+ resourceId: resource.id,
1227
+ adapter: request.protocol,
1228
+ status: "ok",
1229
+ durationMs: totalDurationMs
1230
+ })
1231
+ );
1232
+ return {
1233
+ kind: "delivered",
1234
+ requestId: request.requestId,
1235
+ resourceId: resource.id,
1236
+ backendStatus: backendResponse.status,
1237
+ body: backendResponse.body,
1238
+ headers: backendResponse.headers,
1239
+ ...paymentResult !== void 0 ? { payment: paymentResult } : {},
1240
+ receipt,
1241
+ durationMs: totalDurationMs
1242
+ };
1243
+ }
1244
+ return { execute };
1245
+ }
1246
+ function backendErrorStatus(error) {
1247
+ const status = error.details?.["status"];
1248
+ return typeof status === "number" ? status : 0;
1249
+ }
1250
+ var RESERVED_INPUT_KEYS = [...RESERVED_INPUT_FIELDS, "__proto__"];
1251
+ function stripReservedFields(input) {
1252
+ if (!isRecord(input)) return input;
1253
+ if (!RESERVED_INPUT_KEYS.some((key) => Object.hasOwn(input, key))) return input;
1254
+ const rest = {};
1255
+ for (const key of Object.keys(input)) {
1256
+ if (!RESERVED_INPUT_KEYS.includes(key)) rest[key] = input[key];
1257
+ }
1258
+ return rest;
1259
+ }
1260
+
1261
+ // src/core/execution/registry.ts
1262
+ var ResourceRegistryImpl = class {
1263
+ byId = /* @__PURE__ */ new Map();
1264
+ ordered;
1265
+ constructor(resources) {
1266
+ for (const resource of resources) {
1267
+ if (this.byId.has(resource.id)) {
1268
+ throw new CommerceError("CONFIG_INVALID", `Duplicate resource id "${resource.id}"`, {
1269
+ details: { resourceId: resource.id }
1270
+ });
1271
+ }
1272
+ this.byId.set(resource.id, resource);
1273
+ }
1274
+ this.ordered = [...resources];
1275
+ }
1276
+ get(id) {
1277
+ return this.byId.get(id);
1278
+ }
1279
+ list() {
1280
+ return this.ordered;
1281
+ }
1282
+ listExposedVia(protocol) {
1283
+ return this.ordered.filter((resource) => resource.exposedVia.includes(protocol));
1284
+ }
1285
+ has(id) {
1286
+ return this.byId.has(id);
1287
+ }
1288
+ };
1289
+ function createResourceRegistry(resources) {
1290
+ return new ResourceRegistryImpl(resources);
1291
+ }
1292
+
1293
+ // src/core/execution/store-event-sink.ts
1294
+ function createStoreEventSink(options) {
1295
+ return {
1296
+ async emit(event) {
1297
+ try {
1298
+ await options.store.appendEvent(event);
1299
+ } catch (error) {
1300
+ options.logger.error(
1301
+ { err: describeError(error), eventType: event.type, requestId: event.requestId },
1302
+ "Failed to persist commerce event"
1303
+ );
1304
+ }
1305
+ }
1306
+ };
1307
+ }
1308
+
1309
+ // src/protocols/acp/constants.ts
1310
+ var ACP_SPEC_VERSION = "2026-04-17";
1311
+ var ACP_API_VERSION = "2026-04-17";
1312
+ var ACP_WELL_KNOWN_PATH = "/.well-known/acp.json";
1313
+ var ACP_CHECKOUT_OPERATIONS = [
1314
+ "createCheckoutSession",
1315
+ "updateCheckoutSession",
1316
+ "getCheckoutSession",
1317
+ "completeCheckoutSession",
1318
+ "cancelCheckoutSession"
1319
+ ];
1320
+ var ACP_OPERATION_INPUT_KEYS = {
1321
+ createCheckoutSession: ["body"],
1322
+ updateCheckoutSession: ["path", "body"],
1323
+ getCheckoutSession: ["path"],
1324
+ completeCheckoutSession: ["path", "body"],
1325
+ cancelCheckoutSession: ["path"]
1326
+ };
1327
+ var ACP_OPERATION_OPTIONAL_INPUT_KEYS = {
1328
+ cancelCheckoutSession: ["body"]
1329
+ };
1330
+ var ACP_SESSION_ID_INPUT_KEY = "checkout_session_id";
1331
+ var ACP_JSON_MEDIA_TYPE = "application/json";
1332
+ var ACP_API_VERSION_HEADER = "api-version";
1333
+ var ACP_REQUEST_ID_HEADER = "request-id";
1334
+ var ACP_IDEMPOTENCY_KEY_HEADER = "idempotency-key";
1335
+ var ACP_IDEMPOTENT_REPLAYED_HEADER = "idempotent-replayed";
1336
+ var ACP_IN_FLIGHT_RETRY_AFTER_SECONDS = 1;
1337
+ var ACP_MAX_REQUEST_ID_LENGTH = 128;
1338
+ var ACP_FORWARDED_HEADERS = [
1339
+ "accept-language",
1340
+ "user-agent",
1341
+ ACP_REQUEST_ID_HEADER,
1342
+ ACP_API_VERSION_HEADER,
1343
+ "signature",
1344
+ "timestamp"
1345
+ ];
1346
+ var ACP_MAX_FORWARDED_HEADER_LENGTH = 2048;
1347
+ var ACP_MERCHANT_RETRY_AFTER_DEFAULT_SECONDS = 5;
1348
+ var ACP_MERCHANT_RETRY_AFTER_MAX_SECONDS = 300;
1349
+
1350
+ export { ACP_API_VERSION, ACP_API_VERSION_HEADER, ACP_CHECKOUT_OPERATIONS, ACP_FORWARDED_HEADERS, ACP_IDEMPOTENCY_KEY_HEADER, ACP_IDEMPOTENT_REPLAYED_HEADER, ACP_IN_FLIGHT_RETRY_AFTER_SECONDS, ACP_JSON_MEDIA_TYPE, ACP_MAX_FORWARDED_HEADER_LENGTH, ACP_MAX_REQUEST_ID_LENGTH, ACP_MERCHANT_RETRY_AFTER_DEFAULT_SECONDS, ACP_MERCHANT_RETRY_AFTER_MAX_SECONDS, ACP_OPERATION_INPUT_KEYS, ACP_OPERATION_OPTIONAL_INPUT_KEYS, ACP_REQUEST_ID_HEADER, ACP_SESSION_ID_INPUT_KEY, ACP_SPEC_VERSION, ACP_WELL_KNOWN_PATH, BackendErrorResponse, DEFAULT_CONFIG_FILENAME, HttpBackendExecutor, createExecutionPipeline, createResourceRegistry, createStoreEventSink, extractPathParameterNames, findUnparsedBraceToken, isObjectSchemaNode };
1351
+ //# sourceMappingURL=chunk-2ZZOGI5I.js.map
1352
+ //# sourceMappingURL=chunk-2ZZOGI5I.js.map