@conto_finance/sdk 0.1.0 → 0.4.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/README.md +455 -4
- package/dist/browserbase.d.mts +139 -0
- package/dist/browserbase.d.ts +139 -0
- package/dist/browserbase.js +1980 -0
- package/dist/browserbase.mjs +604 -0
- package/dist/checkout-verifier.d.mts +95 -0
- package/dist/checkout-verifier.d.ts +95 -0
- package/dist/checkout-verifier.js +400 -0
- package/dist/checkout-verifier.mjs +372 -0
- package/dist/chunk-B7F5QLE5.mjs +34 -0
- package/dist/chunk-HM4EG5MH.mjs +1256 -0
- package/dist/chunk-O5RAAB2U.mjs +123 -0
- package/dist/index.d.mts +1775 -143
- package/dist/index.d.ts +1775 -143
- package/dist/index.js +1467 -160
- package/dist/index.mjs +1348 -188
- package/dist/playwright.d.mts +255 -0
- package/dist/playwright.d.ts +255 -0
- package/dist/playwright.js +1394 -0
- package/dist/playwright.mjs +22 -0
- package/package.json +37 -9
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var require_package = __commonJS({
|
|
|
39
39
|
"package.json"(exports2, module2) {
|
|
40
40
|
module2.exports = {
|
|
41
41
|
name: "@conto_finance/sdk",
|
|
42
|
-
version: "0.
|
|
42
|
+
version: "0.4.0",
|
|
43
43
|
description: "Conto SDK for AI agent payment authorization",
|
|
44
44
|
main: "./dist/index.js",
|
|
45
45
|
module: "./dist/index.mjs",
|
|
@@ -49,22 +49,41 @@ var require_package = __commonJS({
|
|
|
49
49
|
types: "./dist/index.d.ts",
|
|
50
50
|
import: "./dist/index.mjs",
|
|
51
51
|
require: "./dist/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./playwright": {
|
|
54
|
+
types: "./dist/playwright.d.ts",
|
|
55
|
+
import: "./dist/playwright.mjs",
|
|
56
|
+
require: "./dist/playwright.js"
|
|
57
|
+
},
|
|
58
|
+
"./browserbase": {
|
|
59
|
+
types: "./dist/browserbase.d.ts",
|
|
60
|
+
import: "./dist/browserbase.mjs",
|
|
61
|
+
require: "./dist/browserbase.js"
|
|
62
|
+
},
|
|
63
|
+
"./checkout-verifier": {
|
|
64
|
+
types: "./dist/checkout-verifier.d.ts",
|
|
65
|
+
import: "./dist/checkout-verifier.mjs",
|
|
66
|
+
require: "./dist/checkout-verifier.js"
|
|
52
67
|
}
|
|
53
68
|
},
|
|
54
69
|
publishConfig: {
|
|
55
70
|
access: "public"
|
|
56
71
|
},
|
|
57
72
|
engines: {
|
|
58
|
-
node: ">=
|
|
73
|
+
node: ">=20.19.0"
|
|
59
74
|
},
|
|
60
75
|
files: [
|
|
61
76
|
"dist"
|
|
62
77
|
],
|
|
63
78
|
scripts: {
|
|
64
|
-
build: "tsup src/index.ts --format cjs,esm --dts",
|
|
65
|
-
dev: "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
79
|
+
build: "tsup src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts",
|
|
80
|
+
dev: "tsup src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts --watch",
|
|
66
81
|
lint: "tsc --noEmit",
|
|
67
|
-
|
|
82
|
+
test: "npm run lint && npm run test:contract && npm run test:consumer",
|
|
83
|
+
"test:contract": "npm run build && node --test test/contract.test.mjs test/playwright.test.mjs test/browserbase.test.mjs test/checkout-verifier.test.mjs test/developer-experience.test.mjs",
|
|
84
|
+
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
85
|
+
"verify:release": "node scripts/verify-release.mjs",
|
|
86
|
+
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
68
87
|
},
|
|
69
88
|
keywords: [
|
|
70
89
|
"conto",
|
|
@@ -81,18 +100,27 @@ var require_package = __commonJS({
|
|
|
81
100
|
url: "https://github.com/conto-finance/conto.git",
|
|
82
101
|
directory: "packages/sdk"
|
|
83
102
|
},
|
|
84
|
-
homepage: "https://conto.finance/docs/sdk",
|
|
103
|
+
homepage: "https://conto.finance/docs/sdk/installation",
|
|
85
104
|
bugs: {
|
|
86
|
-
|
|
105
|
+
email: "support@conto.finance"
|
|
87
106
|
},
|
|
88
107
|
dependencies: {
|
|
89
|
-
"@sentry/node": "^10.
|
|
108
|
+
"@sentry/node": "^10.63.0"
|
|
109
|
+
},
|
|
110
|
+
peerDependencies: {
|
|
111
|
+
"playwright-core": ">=1.50.0"
|
|
112
|
+
},
|
|
113
|
+
peerDependenciesMeta: {
|
|
114
|
+
"playwright-core": {
|
|
115
|
+
optional: true
|
|
116
|
+
}
|
|
90
117
|
},
|
|
91
118
|
overrides: {
|
|
92
119
|
esbuild: "0.28.1"
|
|
93
120
|
},
|
|
94
121
|
devDependencies: {
|
|
95
|
-
"@types/node": "
|
|
122
|
+
"@types/node": "22.19.18",
|
|
123
|
+
"playwright-core": "1.61.0",
|
|
96
124
|
tsup: "^8.5.1",
|
|
97
125
|
typescript: "^5.9.3"
|
|
98
126
|
}
|
|
@@ -115,18 +143,361 @@ var require_package_version = __commonJS({
|
|
|
115
143
|
// src/index.ts
|
|
116
144
|
var index_exports = {};
|
|
117
145
|
__export(index_exports, {
|
|
146
|
+
BrowserActionFinalizationPendingError: () => BrowserActionFinalizationPendingError,
|
|
147
|
+
BrowserCheckoutTargetVerificationError: () => BrowserCheckoutTargetVerificationError,
|
|
148
|
+
CardsClient: () => CardsClient,
|
|
149
|
+
ConnectedCardsClient: () => ConnectedCardsClient,
|
|
118
150
|
Conto: () => Conto,
|
|
119
151
|
ContoAdmin: () => ContoAdmin,
|
|
152
|
+
ContoAdminError: () => ContoAdminError,
|
|
153
|
+
ContoError: () => ContoError,
|
|
154
|
+
ControlledExternalCardPaymentTool: () => ControlledExternalCardPaymentTool,
|
|
155
|
+
ExternalCardConfirmationPendingError: () => ExternalCardConfirmationPendingError,
|
|
120
156
|
Giro: () => Conto,
|
|
121
|
-
|
|
157
|
+
canonicalizeBrowserCheckoutTarget: () => canonicalizeBrowserCheckoutTarget,
|
|
158
|
+
createVerifiedBrowserCheckoutDriver: () => createVerifiedBrowserCheckoutDriver,
|
|
159
|
+
default: () => index_default,
|
|
160
|
+
fingerprintBrowserCheckoutTarget: () => fingerprintBrowserCheckoutTarget
|
|
122
161
|
});
|
|
123
162
|
module.exports = __toCommonJS(index_exports);
|
|
124
163
|
var Sentry2 = __toESM(require("@sentry/node"));
|
|
164
|
+
var import_node_crypto2 = require("crypto");
|
|
125
165
|
var import_package_version2 = __toESM(require_package_version());
|
|
126
166
|
|
|
167
|
+
// src/http.ts
|
|
168
|
+
var PUBLIC_PAYMENT_REASONS = /* @__PURE__ */ new Set([
|
|
169
|
+
"The available balance is insufficient for this payment.",
|
|
170
|
+
"This payment exceeds a configured spending limit.",
|
|
171
|
+
"This recipient is not allowed by the current payment controls.",
|
|
172
|
+
"This payment is outside the permitted schedule.",
|
|
173
|
+
"The available payment budget is insufficient.",
|
|
174
|
+
"This payment does not meet the current payment controls.",
|
|
175
|
+
"This payment requires approval before it can proceed."
|
|
176
|
+
]);
|
|
177
|
+
function customerPaymentReasons(value) {
|
|
178
|
+
return Array.isArray(value) ? [
|
|
179
|
+
...new Set(
|
|
180
|
+
value.filter(
|
|
181
|
+
(reason) => typeof reason === "string" && PUBLIC_PAYMENT_REASONS.has(reason)
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
] : [];
|
|
185
|
+
}
|
|
186
|
+
var VALIDATION_FIELDS = /* @__PURE__ */ new Set([
|
|
187
|
+
"amount",
|
|
188
|
+
"recipientAddress",
|
|
189
|
+
"senderAddress",
|
|
190
|
+
"recipientName",
|
|
191
|
+
"purpose",
|
|
192
|
+
"category",
|
|
193
|
+
"chainId",
|
|
194
|
+
"walletId",
|
|
195
|
+
"idempotencyKey",
|
|
196
|
+
"txHash",
|
|
197
|
+
"approvalToken"
|
|
198
|
+
]);
|
|
199
|
+
function customerFieldErrors(body, status) {
|
|
200
|
+
if (![400, 422].includes(status) || !["VALIDATION_ERROR", "INVALID_REQUEST", "INVALID_INPUT"].includes(String(body.code)))
|
|
201
|
+
return void 0;
|
|
202
|
+
const fields = record(body.details).fields;
|
|
203
|
+
if (!Array.isArray(fields)) return void 0;
|
|
204
|
+
const names = [
|
|
205
|
+
...new Set(
|
|
206
|
+
fields.map((item) => record(item).field).filter(
|
|
207
|
+
(field) => typeof field === "string" && VALIDATION_FIELDS.has(field)
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
];
|
|
211
|
+
return names.length ? names.map((field) => ({
|
|
212
|
+
field,
|
|
213
|
+
message: "Check this field against the documented request schema."
|
|
214
|
+
})) : void 0;
|
|
215
|
+
}
|
|
216
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
217
|
+
function assertSecureBaseUrl(baseUrl) {
|
|
218
|
+
let parsed;
|
|
219
|
+
try {
|
|
220
|
+
parsed = new URL(baseUrl);
|
|
221
|
+
} catch {
|
|
222
|
+
throw new Error(`Invalid base URL: ${baseUrl}`);
|
|
223
|
+
}
|
|
224
|
+
if (parsed.protocol !== "https:" && parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1") {
|
|
225
|
+
throw new Error("baseUrl must use https (except for localhost)");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function abortError() {
|
|
229
|
+
const error = new Error("Request aborted");
|
|
230
|
+
error.name = "AbortError";
|
|
231
|
+
return error;
|
|
232
|
+
}
|
|
233
|
+
function wait(ms, signal) {
|
|
234
|
+
if (signal?.aborted) return Promise.reject(abortError());
|
|
235
|
+
return new Promise((resolve, reject) => {
|
|
236
|
+
const onAbort = () => {
|
|
237
|
+
clearTimeout(timeout);
|
|
238
|
+
reject(abortError());
|
|
239
|
+
};
|
|
240
|
+
const timeout = setTimeout(() => {
|
|
241
|
+
signal?.removeEventListener("abort", onAbort);
|
|
242
|
+
resolve();
|
|
243
|
+
}, ms);
|
|
244
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function parseRetryAfter(value) {
|
|
248
|
+
if (!value) return void 0;
|
|
249
|
+
const seconds = Number(value);
|
|
250
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
|
|
251
|
+
const date = Date.parse(value);
|
|
252
|
+
if (!Number.isFinite(date)) return void 0;
|
|
253
|
+
return Math.max(0, Math.ceil((date - Date.now()) / 1e3));
|
|
254
|
+
}
|
|
255
|
+
function retryDelayMs(response, attempt) {
|
|
256
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
257
|
+
if (retryAfter !== void 0) {
|
|
258
|
+
return Math.min(retryAfter * 1e3, MAX_RETRY_DELAY_MS);
|
|
259
|
+
}
|
|
260
|
+
return Math.min(1e3 * 2 ** attempt, 1e4);
|
|
261
|
+
}
|
|
262
|
+
async function fetchWithRetry(url, options, policy) {
|
|
263
|
+
const maxRetries = policy.maxRetries ?? 3;
|
|
264
|
+
let lastError;
|
|
265
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
266
|
+
try {
|
|
267
|
+
const response = await fetch(url, options);
|
|
268
|
+
const transient = response.status === 429 || response.status >= 500;
|
|
269
|
+
if (policy.retryable && transient && attempt < maxRetries) {
|
|
270
|
+
await wait(retryDelayMs(response, attempt), options.signal);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
return response;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
276
|
+
if (normalized.name === "AbortError" || options.signal?.aborted) throw normalized;
|
|
277
|
+
lastError = normalized;
|
|
278
|
+
if (!policy.retryable || attempt >= maxRetries) throw normalized;
|
|
279
|
+
await wait(Math.min(1e3 * 2 ** attempt, 1e4), options.signal);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
throw lastError ?? new Error("Request failed after retries");
|
|
283
|
+
}
|
|
284
|
+
async function readResponseData(response) {
|
|
285
|
+
if (typeof response.text !== "function") return response.json();
|
|
286
|
+
const text = await response.text();
|
|
287
|
+
if (!text) return {};
|
|
288
|
+
try {
|
|
289
|
+
return JSON.parse(text);
|
|
290
|
+
} catch {
|
|
291
|
+
return { error: text };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function record(value) {
|
|
295
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
296
|
+
}
|
|
297
|
+
function fallbackCode(status) {
|
|
298
|
+
if (status === 400 || status === 422) return "INVALID_REQUEST";
|
|
299
|
+
if (status === 401) return "AUTH_FAILED";
|
|
300
|
+
if (status === 403) return "FORBIDDEN";
|
|
301
|
+
if (status === 404) return "NOT_FOUND";
|
|
302
|
+
if (status === 409) return "CONFLICT";
|
|
303
|
+
if (status === 429) return "RATE_LIMITED";
|
|
304
|
+
if (status >= 500) return "SERVER_ERROR";
|
|
305
|
+
return "REQUEST_FAILED";
|
|
306
|
+
}
|
|
307
|
+
function safeErrorCode(value, status) {
|
|
308
|
+
return typeof value === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(value) ? value : fallbackCode(status);
|
|
309
|
+
}
|
|
310
|
+
function safeRequestId(...values) {
|
|
311
|
+
return values.find(
|
|
312
|
+
(value) => typeof value === "string" && value.length > 0 && value.length <= 200 && /^[A-Za-z0-9._:@/-]+$/.test(value)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
function safeActionUrl(value, code) {
|
|
316
|
+
if (code !== "ACTION_REQUIRED" || typeof value !== "string" || value.length > 2048) {
|
|
317
|
+
return void 0;
|
|
318
|
+
}
|
|
319
|
+
if (value.startsWith("/") && !value.startsWith("//")) return value;
|
|
320
|
+
try {
|
|
321
|
+
const url = new URL(value);
|
|
322
|
+
return url.protocol === "https:" ? value : void 0;
|
|
323
|
+
} catch {
|
|
324
|
+
return void 0;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function safeErrorMessage(status) {
|
|
328
|
+
if (status === 400 || status === 422) return "The request was invalid.";
|
|
329
|
+
if (status === 401) return "Authentication failed.";
|
|
330
|
+
if (status === 403) return "The request is not permitted.";
|
|
331
|
+
if (status === 404) return "The requested resource was not found.";
|
|
332
|
+
if (status === 409) return "The request conflicts with the current resource state.";
|
|
333
|
+
if (status === 429) return "Too many requests.";
|
|
334
|
+
if (status >= 500) return "The service is temporarily unavailable.";
|
|
335
|
+
return "The request could not be completed.";
|
|
336
|
+
}
|
|
337
|
+
function fallbackRetryGuidance(status) {
|
|
338
|
+
if (status === 400 || status === 422) return "Review the request before trying again.";
|
|
339
|
+
if (status === 401) return "Verify the API key and that it is intended for this client.";
|
|
340
|
+
if (status === 403) return "Verify credentials and permissions before trying again.";
|
|
341
|
+
if (status === 404) return "Verify the resource identifier before trying again.";
|
|
342
|
+
if (status === 409) return "Check the current resource state before trying again.";
|
|
343
|
+
if (status === 429) return "Wait for retryAfter seconds before retrying.";
|
|
344
|
+
if (status >= 500)
|
|
345
|
+
return "Retry only if this operation is read-only or protected by idempotency.";
|
|
346
|
+
return void 0;
|
|
347
|
+
}
|
|
348
|
+
function responseErrorMessage(_data, status) {
|
|
349
|
+
return safeErrorMessage(status);
|
|
350
|
+
}
|
|
351
|
+
function responseErrorMetadata(response, data) {
|
|
352
|
+
const body = record(data);
|
|
353
|
+
const code = safeErrorCode(body.code, response.status);
|
|
354
|
+
const bodyRetryAfter = typeof body.retryAfter === "number" || typeof body.retryAfter === "string" ? parseRetryAfter(String(body.retryAfter)) : void 0;
|
|
355
|
+
return {
|
|
356
|
+
code,
|
|
357
|
+
status: response.status,
|
|
358
|
+
fieldErrors: customerFieldErrors(body, response.status),
|
|
359
|
+
requestId: safeRequestId(
|
|
360
|
+
response.headers.get("x-request-id"),
|
|
361
|
+
response.headers.get("x-correlation-id"),
|
|
362
|
+
body.requestId
|
|
363
|
+
),
|
|
364
|
+
retryAfter: parseRetryAfter(response.headers.get("retry-after")) ?? bodyRetryAfter,
|
|
365
|
+
retryGuidance: fallbackRetryGuidance(response.status),
|
|
366
|
+
actionUrl: safeActionUrl(body.actionUrl, code)
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/browser-checkout.ts
|
|
371
|
+
var import_node_crypto = require("crypto");
|
|
372
|
+
var MAX_URL_LENGTH = 2048;
|
|
373
|
+
var MAX_LOCATOR_LENGTH = 1024;
|
|
374
|
+
var MAX_TEXT_LENGTH = 500;
|
|
375
|
+
var BrowserCheckoutTargetVerificationError = class extends Error {
|
|
376
|
+
constructor(code, message) {
|
|
377
|
+
super(message);
|
|
378
|
+
this.code = code;
|
|
379
|
+
this.name = "BrowserCheckoutTargetVerificationError";
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
function invalid(message) {
|
|
383
|
+
throw new BrowserCheckoutTargetVerificationError("BROWSER_CHECKOUT_TARGET_INVALID", message);
|
|
384
|
+
}
|
|
385
|
+
function canonicalHttpsUrl(value, field) {
|
|
386
|
+
if (typeof value !== "string" || !value || value.length > MAX_URL_LENGTH) {
|
|
387
|
+
invalid(`${field} is invalid.`);
|
|
388
|
+
}
|
|
389
|
+
let url;
|
|
390
|
+
try {
|
|
391
|
+
url = new URL(value);
|
|
392
|
+
} catch {
|
|
393
|
+
invalid(`${field} is invalid.`);
|
|
394
|
+
}
|
|
395
|
+
if (url.protocol !== "https:" || url.username || url.password) {
|
|
396
|
+
invalid(`${field} must use HTTPS without credentials.`);
|
|
397
|
+
}
|
|
398
|
+
url.hostname = url.hostname.toLowerCase();
|
|
399
|
+
url.hash = "";
|
|
400
|
+
return url.toString();
|
|
401
|
+
}
|
|
402
|
+
function canonicalText(value, field, options = {}) {
|
|
403
|
+
if (value === void 0) {
|
|
404
|
+
if (options.required) invalid(`${field} is required.`);
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
if (typeof value !== "string") invalid(`${field} is invalid.`);
|
|
408
|
+
const trimmed = value.trim();
|
|
409
|
+
const normalized = options.collapseWhitespace === false ? trimmed : trimmed.replace(/\s+/g, " ");
|
|
410
|
+
if (options.required && !normalized || normalized.length > (options.maxLength ?? MAX_TEXT_LENGTH)) {
|
|
411
|
+
invalid(`${field} is invalid.`);
|
|
412
|
+
}
|
|
413
|
+
return normalized || null;
|
|
414
|
+
}
|
|
415
|
+
function canonicalizeBrowserCheckoutTarget(snapshot) {
|
|
416
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
417
|
+
invalid("The browser checkout target is invalid.");
|
|
418
|
+
}
|
|
419
|
+
if (typeof snapshot.disabled !== "boolean") {
|
|
420
|
+
invalid("target.disabled is invalid.");
|
|
421
|
+
}
|
|
422
|
+
const tagName = canonicalText(snapshot.tagName, "target.tagName", {
|
|
423
|
+
required: true,
|
|
424
|
+
maxLength: 80
|
|
425
|
+
});
|
|
426
|
+
return Object.freeze({
|
|
427
|
+
pageUrl: canonicalHttpsUrl(snapshot.pageUrl, "target.pageUrl"),
|
|
428
|
+
locator: canonicalText(snapshot.locator, "target.locator", {
|
|
429
|
+
collapseWhitespace: false,
|
|
430
|
+
required: true,
|
|
431
|
+
maxLength: MAX_LOCATOR_LENGTH
|
|
432
|
+
}) ?? "",
|
|
433
|
+
tagName: (tagName ?? "").toLowerCase(),
|
|
434
|
+
role: canonicalText(snapshot.role, "target.role", { maxLength: 80 })?.toLowerCase() ?? null,
|
|
435
|
+
accessibleName: canonicalText(snapshot.accessibleName, "target.accessibleName"),
|
|
436
|
+
targetUrl: snapshot.targetUrl ? canonicalHttpsUrl(snapshot.targetUrl, "target.targetUrl") : null,
|
|
437
|
+
formAction: snapshot.formAction ? canonicalHttpsUrl(snapshot.formAction, "target.formAction") : null,
|
|
438
|
+
opensNewPage: snapshot.opensNewPage === true,
|
|
439
|
+
disabled: snapshot.disabled
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function fingerprintBrowserCheckoutTarget(snapshot) {
|
|
443
|
+
const canonical = canonicalizeBrowserCheckoutTarget(snapshot);
|
|
444
|
+
const digest = (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
445
|
+
return `browser_target_sha256_${digest}`;
|
|
446
|
+
}
|
|
447
|
+
function createVerifiedBrowserCheckoutDriver(resolver) {
|
|
448
|
+
if (!resolver || typeof resolver.resolve !== "function") {
|
|
449
|
+
invalid("A browser checkout target resolver is required.");
|
|
450
|
+
}
|
|
451
|
+
return Object.freeze({
|
|
452
|
+
async dispatch(input) {
|
|
453
|
+
const target = await resolver.resolve(input);
|
|
454
|
+
if (!target) {
|
|
455
|
+
throw new BrowserCheckoutTargetVerificationError(
|
|
456
|
+
"BROWSER_CHECKOUT_TARGET_NOT_FOUND",
|
|
457
|
+
"The approved browser checkout target was not found."
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
if (typeof target.dispatch !== "function") {
|
|
461
|
+
invalid("The resolved browser checkout target cannot be dispatched.");
|
|
462
|
+
}
|
|
463
|
+
const canonical = canonicalizeBrowserCheckoutTarget(target.snapshot);
|
|
464
|
+
const approvedPageUrl = canonicalHttpsUrl(input.action.pageUrl, "action.pageUrl");
|
|
465
|
+
const approvedTargetUrl = input.action.targetUrl ? canonicalHttpsUrl(input.action.targetUrl, "action.targetUrl") : null;
|
|
466
|
+
const observedDestinations = [canonical.targetUrl, canonical.formAction].filter(
|
|
467
|
+
(value) => value !== null
|
|
468
|
+
);
|
|
469
|
+
const destinationsAreBound = approvedTargetUrl === null ? observedDestinations.length === 0 : observedDestinations.length > 0 && observedDestinations.every((value) => value === approvedTargetUrl);
|
|
470
|
+
if (canonical.pageUrl !== approvedPageUrl || !destinationsAreBound || fingerprintBrowserCheckoutTarget(target.snapshot) !== input.action.targetFingerprint.trim()) {
|
|
471
|
+
throw new BrowserCheckoutTargetVerificationError(
|
|
472
|
+
"BROWSER_CHECKOUT_TARGET_CHANGED",
|
|
473
|
+
"The browser checkout target changed after Conto authorization."
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
if (canonical.disabled) {
|
|
477
|
+
throw new BrowserCheckoutTargetVerificationError(
|
|
478
|
+
"BROWSER_CHECKOUT_TARGET_DISABLED",
|
|
479
|
+
"The approved browser checkout target is disabled."
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
return target.dispatch();
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
127
487
|
// src/admin.ts
|
|
128
488
|
var Sentry = __toESM(require("@sentry/node"));
|
|
129
489
|
var import_package_version = __toESM(require_package_version());
|
|
490
|
+
var ContoAdminError = class extends Error {
|
|
491
|
+
constructor(message, metadata) {
|
|
492
|
+
super(message);
|
|
493
|
+
this.name = "ContoAdminError";
|
|
494
|
+
this.code = metadata.code;
|
|
495
|
+
this.status = metadata.status;
|
|
496
|
+
this.requestId = metadata.requestId;
|
|
497
|
+
this.retryAfter = metadata.retryAfter;
|
|
498
|
+
this.retryGuidance = metadata.retryGuidance;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
130
501
|
var ContoAdmin = class {
|
|
131
502
|
constructor(config) {
|
|
132
503
|
if (!config.orgApiKey) {
|
|
@@ -144,77 +515,178 @@ var ContoAdmin = class {
|
|
|
144
515
|
}
|
|
145
516
|
this.orgApiKey = config.orgApiKey;
|
|
146
517
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
518
|
+
assertSecureBaseUrl(this.baseUrl);
|
|
147
519
|
this.timeout = config.timeout || 3e4;
|
|
148
520
|
this.agents = new AgentsClient(this);
|
|
149
521
|
this.wallets = new WalletsClient(this);
|
|
522
|
+
this.cards = new AdminCardsClient(this);
|
|
150
523
|
this.policies = new PoliciesClient(this);
|
|
151
524
|
this.team = new TeamClient(this);
|
|
152
|
-
|
|
153
|
-
async fetchWithRetry(url, options, maxRetries = 3) {
|
|
154
|
-
let lastError = null;
|
|
155
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
156
|
-
try {
|
|
157
|
-
const response = await fetch(url, options);
|
|
158
|
-
if (response.status === 429 || response.status >= 500 && attempt < maxRetries) {
|
|
159
|
-
const retryAfter = response.headers.get("retry-after");
|
|
160
|
-
const delay = retryAfter ? parseInt(retryAfter, 10) * 1e3 : Math.min(1e3 * 2 ** attempt, 1e4);
|
|
161
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
return response;
|
|
165
|
-
} catch (err) {
|
|
166
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
167
|
-
if (attempt < maxRetries) {
|
|
168
|
-
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
169
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
throw lastError || new Error("Request failed after retries");
|
|
525
|
+
this.paymentRails = new PaymentRailsClient(this);
|
|
175
526
|
}
|
|
176
527
|
/** Make an authenticated request to the Conto API */
|
|
177
528
|
async request(method, path, body) {
|
|
178
529
|
const url = `${this.baseUrl}${path}`;
|
|
179
530
|
const controller = new AbortController();
|
|
180
531
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
532
|
+
const normalizedMethod = method.toUpperCase();
|
|
533
|
+
const retryable = ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod);
|
|
181
534
|
try {
|
|
182
|
-
const response = await
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
535
|
+
const response = await fetchWithRetry(
|
|
536
|
+
url,
|
|
537
|
+
{
|
|
538
|
+
method: normalizedMethod,
|
|
539
|
+
headers: {
|
|
540
|
+
Authorization: `Bearer ${this.orgApiKey}`,
|
|
541
|
+
"Content-Type": "application/json",
|
|
542
|
+
"User-Agent": `@conto_finance/sdk-admin/${import_package_version.SDK_PACKAGE_VERSION}`
|
|
543
|
+
},
|
|
544
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
545
|
+
signal: controller.signal
|
|
188
546
|
},
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
clearTimeout(timeoutId);
|
|
193
|
-
const data = await response.json();
|
|
547
|
+
{ retryable }
|
|
548
|
+
);
|
|
549
|
+
const data = await readResponseData(response);
|
|
194
550
|
if (!response.ok) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
throw error;
|
|
551
|
+
throw new ContoAdminError(
|
|
552
|
+
responseErrorMessage(data, response.status),
|
|
553
|
+
responseErrorMetadata(response, data)
|
|
554
|
+
);
|
|
200
555
|
}
|
|
201
556
|
return data;
|
|
202
557
|
} catch (error) {
|
|
203
|
-
|
|
558
|
+
if (error instanceof ContoAdminError) throw error;
|
|
204
559
|
if (error instanceof Error && error.name === "AbortError") {
|
|
205
|
-
const timeoutError = new
|
|
206
|
-
|
|
207
|
-
|
|
560
|
+
const timeoutError = new ContoAdminError("Request timeout", {
|
|
561
|
+
code: "TIMEOUT",
|
|
562
|
+
status: 0,
|
|
563
|
+
retryGuidance: retryable ? "The request is safe to retry." : "Do not retry automatically; inspect the resource before trying again."
|
|
564
|
+
});
|
|
208
565
|
Sentry.captureException(timeoutError, { extra: { method, path } });
|
|
209
566
|
throw timeoutError;
|
|
210
567
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
568
|
+
const networkError = new ContoAdminError("Network request failed", {
|
|
569
|
+
code: "NETWORK_ERROR",
|
|
570
|
+
status: 0,
|
|
571
|
+
retryGuidance: retryable ? "The request is safe to retry." : "Do not retry automatically; inspect the resource before trying again."
|
|
572
|
+
});
|
|
573
|
+
Sentry.captureException(networkError, { extra: { method, path } });
|
|
574
|
+
throw networkError;
|
|
575
|
+
} finally {
|
|
576
|
+
clearTimeout(timeoutId);
|
|
215
577
|
}
|
|
216
578
|
}
|
|
217
579
|
};
|
|
580
|
+
var PaymentRailsClient = class {
|
|
581
|
+
constructor(client) {
|
|
582
|
+
this.client = client;
|
|
583
|
+
}
|
|
584
|
+
/** Read gate and configuration readiness without calling Stripe. */
|
|
585
|
+
async getStripeLinkCheckoutReadiness() {
|
|
586
|
+
return this.client.request("GET", "/api/stripe-link/checkout-readiness");
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Ask Conto to verify that its configured Stripe credential resolves to its
|
|
590
|
+
* configured merchant account. This call never returns either identifier.
|
|
591
|
+
*/
|
|
592
|
+
async verifyStripeLinkCheckoutAccount() {
|
|
593
|
+
return this.client.request("POST", "/api/stripe-link/checkout-readiness");
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
var AdminCardsClient = class {
|
|
597
|
+
constructor(client) {
|
|
598
|
+
this.client = client;
|
|
599
|
+
}
|
|
600
|
+
/** List masked external-card aliases. No card credentials are returned. */
|
|
601
|
+
async list() {
|
|
602
|
+
return this.client.request("GET", "/api/external-cards");
|
|
603
|
+
}
|
|
604
|
+
/** Register a masked alias for a customer card. Never pass PAN or CVC. */
|
|
605
|
+
async register(input) {
|
|
606
|
+
return this.client.request(
|
|
607
|
+
"POST",
|
|
608
|
+
"/api/external-cards",
|
|
609
|
+
input
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
/** Assign a card alias and cooperative policy controls to an agent. */
|
|
613
|
+
async assignToAgent(agentId, input) {
|
|
614
|
+
return this.client.request(
|
|
615
|
+
"POST",
|
|
616
|
+
`/api/agents/${encodeURIComponent(agentId)}/external-cards`,
|
|
617
|
+
input
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
async listForAgent(agentId) {
|
|
621
|
+
return this.client.request("GET", `/api/agents/${encodeURIComponent(agentId)}/external-cards`);
|
|
622
|
+
}
|
|
623
|
+
/** Update limits, merchant rules, hours, and optional start or end dates. */
|
|
624
|
+
async updateAgentAssignment(agentId, cardId, input) {
|
|
625
|
+
return this.client.request(
|
|
626
|
+
"PATCH",
|
|
627
|
+
`/api/agents/${encodeURIComponent(agentId)}/external-cards/${encodeURIComponent(cardId)}`,
|
|
628
|
+
{ action: "UPDATE", ...input }
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
/** Pause an assignment and cancel its unused approvals. */
|
|
632
|
+
async pauseForAgent(agentId, cardId) {
|
|
633
|
+
return this.client.request(
|
|
634
|
+
"PATCH",
|
|
635
|
+
`/api/agents/${encodeURIComponent(agentId)}/external-cards/${encodeURIComponent(cardId)}`,
|
|
636
|
+
{ action: "PAUSE" }
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
/** Resume a paused assignment when its alias and date range permit it. */
|
|
640
|
+
async resumeForAgent(agentId, cardId) {
|
|
641
|
+
return this.client.request(
|
|
642
|
+
"PATCH",
|
|
643
|
+
`/api/agents/${encodeURIComponent(agentId)}/external-cards/${encodeURIComponent(cardId)}`,
|
|
644
|
+
{ action: "RESUME" }
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Revoke an agent's overlay assignment. Unused grants are cancelled; claimed
|
|
649
|
+
* executions remain visible for reconciliation.
|
|
650
|
+
*/
|
|
651
|
+
async revokeFromAgent(agentId, cardId) {
|
|
652
|
+
return this.client.request(
|
|
653
|
+
"DELETE",
|
|
654
|
+
`/api/agents/${encodeURIComponent(agentId)}/external-cards/${encodeURIComponent(cardId)}`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
/** List overlay requests, including executions that need receipt reconciliation. */
|
|
658
|
+
async listRequests(input = {}) {
|
|
659
|
+
const query = new URLSearchParams();
|
|
660
|
+
if (input.status) query.set("status", input.status);
|
|
661
|
+
if (input.limit !== void 0) query.set("limit", String(input.limit));
|
|
662
|
+
const suffix = query.toString();
|
|
663
|
+
return this.client.request("GET", `/api/external-cards/requests${suffix ? `?${suffix}` : ""}`);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Resolve a claimed execution after checking the customer's processor or
|
|
667
|
+
* receipt. Resolving NOT_EXECUTED releases the reserved policy amount.
|
|
668
|
+
*/
|
|
669
|
+
async resolveRequest(requestId, input) {
|
|
670
|
+
return this.client.request(
|
|
671
|
+
"POST",
|
|
672
|
+
`/api/external-cards/requests/${encodeURIComponent(requestId)}/resolve`,
|
|
673
|
+
input
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
/** Pause the Conto-controlled path and revoke live overlay grants. */
|
|
677
|
+
async pause(cardId) {
|
|
678
|
+
return this.setState(cardId, "PAUSED");
|
|
679
|
+
}
|
|
680
|
+
/** Resume the Conto-controlled path. This does not activate a card at its issuer. */
|
|
681
|
+
async resume(cardId) {
|
|
682
|
+
return this.setState(cardId, "ACTIVE");
|
|
683
|
+
}
|
|
684
|
+
async setState(cardId, state) {
|
|
685
|
+
return this.client.request("PATCH", `/api/external-cards/${encodeURIComponent(cardId)}/state`, {
|
|
686
|
+
state
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
};
|
|
218
690
|
var AgentsClient = class {
|
|
219
691
|
constructor(client) {
|
|
220
692
|
this.client = client;
|
|
@@ -239,48 +711,55 @@ var AgentsClient = class {
|
|
|
239
711
|
}
|
|
240
712
|
/** Get a single agent by ID */
|
|
241
713
|
async get(agentId) {
|
|
242
|
-
return this.client.request("GET", `/api/agents/${agentId}`);
|
|
714
|
+
return this.client.request("GET", `/api/agents/${encodeURIComponent(agentId)}`);
|
|
243
715
|
}
|
|
244
716
|
/** Update an agent */
|
|
245
717
|
async update(agentId, input) {
|
|
246
718
|
return this.client.request(
|
|
247
719
|
"PATCH",
|
|
248
|
-
`/api/agents/${agentId}`,
|
|
720
|
+
`/api/agents/${encodeURIComponent(agentId)}`,
|
|
249
721
|
input
|
|
250
722
|
);
|
|
251
723
|
}
|
|
252
724
|
/** Delete an agent (soft delete) */
|
|
253
725
|
async delete(agentId) {
|
|
254
|
-
return this.client.request(
|
|
726
|
+
return this.client.request(
|
|
727
|
+
"DELETE",
|
|
728
|
+
`/api/agents/${encodeURIComponent(agentId)}`
|
|
729
|
+
);
|
|
255
730
|
}
|
|
256
731
|
/** Freeze an agent, blocking all transactions */
|
|
257
732
|
async freeze(agentId, input) {
|
|
258
733
|
return this.client.request(
|
|
259
734
|
"POST",
|
|
260
|
-
`/api/agents/${agentId}/freeze`,
|
|
261
|
-
|
|
735
|
+
`/api/agents/${encodeURIComponent(agentId)}/freeze`,
|
|
736
|
+
{
|
|
737
|
+
...input.freezeWallets !== void 0 && { freezeWallets: input.freezeWallets }
|
|
738
|
+
}
|
|
262
739
|
);
|
|
263
740
|
}
|
|
264
741
|
/** Unfreeze an agent, restoring transaction ability */
|
|
265
742
|
async unfreeze(agentId, input) {
|
|
266
743
|
return this.client.request(
|
|
267
744
|
"POST",
|
|
268
|
-
`/api/agents/${agentId}/unfreeze`,
|
|
269
|
-
|
|
745
|
+
`/api/agents/${encodeURIComponent(agentId)}/unfreeze`,
|
|
746
|
+
{
|
|
747
|
+
...input.unfreezeWallets !== void 0 && { unfreezeWallets: input.unfreezeWallets }
|
|
748
|
+
}
|
|
270
749
|
);
|
|
271
750
|
}
|
|
272
751
|
/** List wallets linked to an agent */
|
|
273
752
|
async listWallets(agentId) {
|
|
274
753
|
return this.client.request(
|
|
275
754
|
"GET",
|
|
276
|
-
`/api/agents/${agentId}/wallets`
|
|
755
|
+
`/api/agents/${encodeURIComponent(agentId)}/wallets`
|
|
277
756
|
);
|
|
278
757
|
}
|
|
279
758
|
/** Link a wallet to an agent with spending limits */
|
|
280
759
|
async linkWallet(agentId, input) {
|
|
281
760
|
return this.client.request(
|
|
282
761
|
"POST",
|
|
283
|
-
`/api/agents/${agentId}/wallets`,
|
|
762
|
+
`/api/agents/${encodeURIComponent(agentId)}/wallets`,
|
|
284
763
|
input
|
|
285
764
|
);
|
|
286
765
|
}
|
|
@@ -288,39 +767,45 @@ var AgentsClient = class {
|
|
|
288
767
|
async unlinkWallet(agentId, walletId) {
|
|
289
768
|
return this.client.request(
|
|
290
769
|
"DELETE",
|
|
291
|
-
`/api/agents/${agentId}/wallets/${walletId}`
|
|
770
|
+
`/api/agents/${encodeURIComponent(agentId)}/wallets/${encodeURIComponent(walletId)}`
|
|
292
771
|
);
|
|
293
772
|
}
|
|
294
773
|
/** List policies assigned to an agent */
|
|
295
774
|
async listPolicies(agentId) {
|
|
296
775
|
return this.client.request(
|
|
297
776
|
"GET",
|
|
298
|
-
`/api/agents/${agentId}/policies`
|
|
777
|
+
`/api/agents/${encodeURIComponent(agentId)}/policies`
|
|
299
778
|
);
|
|
300
779
|
}
|
|
301
780
|
/** Assign a policy to an agent */
|
|
302
|
-
async assignPolicy(agentId, policyId
|
|
303
|
-
return this.client.request(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
781
|
+
async assignPolicy(agentId, policyId) {
|
|
782
|
+
return this.client.request(
|
|
783
|
+
"POST",
|
|
784
|
+
`/api/agents/${encodeURIComponent(agentId)}/policies`,
|
|
785
|
+
{
|
|
786
|
+
policyId
|
|
787
|
+
}
|
|
788
|
+
);
|
|
307
789
|
}
|
|
308
790
|
/** Unassign a policy from an agent */
|
|
309
791
|
async unassignPolicy(agentId, policyId) {
|
|
310
792
|
return this.client.request(
|
|
311
793
|
"DELETE",
|
|
312
|
-
`/api/agents/${agentId}/policies?policyId=${policyId}`
|
|
794
|
+
`/api/agents/${encodeURIComponent(agentId)}/policies?policyId=${encodeURIComponent(policyId)}`
|
|
313
795
|
);
|
|
314
796
|
}
|
|
315
797
|
/** List SDK keys for an agent */
|
|
316
798
|
async listSdkKeys(agentId) {
|
|
317
|
-
return this.client.request(
|
|
799
|
+
return this.client.request(
|
|
800
|
+
"GET",
|
|
801
|
+
`/api/agents/${encodeURIComponent(agentId)}/sdk-keys`
|
|
802
|
+
);
|
|
318
803
|
}
|
|
319
804
|
/** Create an SDK key for an agent (key value is returned only once) */
|
|
320
805
|
async createSdkKey(agentId, input) {
|
|
321
806
|
return this.client.request(
|
|
322
807
|
"POST",
|
|
323
|
-
`/api/agents/${agentId}/sdk-keys`,
|
|
808
|
+
`/api/agents/${encodeURIComponent(agentId)}/sdk-keys`,
|
|
324
809
|
input
|
|
325
810
|
);
|
|
326
811
|
}
|
|
@@ -328,7 +813,7 @@ var AgentsClient = class {
|
|
|
328
813
|
async revokeSdkKey(agentId, keyId) {
|
|
329
814
|
return this.client.request(
|
|
330
815
|
"DELETE",
|
|
331
|
-
`/api/agents/${agentId}/sdk-keys?keyId=${keyId}`
|
|
816
|
+
`/api/agents/${encodeURIComponent(agentId)}/sdk-keys?keyId=${encodeURIComponent(keyId)}`
|
|
332
817
|
);
|
|
333
818
|
}
|
|
334
819
|
};
|
|
@@ -347,7 +832,7 @@ var WalletsClient = class {
|
|
|
347
832
|
const qs = query.toString() ? `?${query.toString()}` : "";
|
|
348
833
|
return this.client.request("GET", `/api/wallets${qs}`);
|
|
349
834
|
}
|
|
350
|
-
/** Create a
|
|
835
|
+
/** Create a managed wallet or register an external address. */
|
|
351
836
|
async create(input) {
|
|
352
837
|
return this.client.request(
|
|
353
838
|
"POST",
|
|
@@ -357,25 +842,28 @@ var WalletsClient = class {
|
|
|
357
842
|
}
|
|
358
843
|
/** Get a single wallet by ID */
|
|
359
844
|
async get(walletId) {
|
|
360
|
-
return this.client.request("GET", `/api/wallets/${walletId}`);
|
|
845
|
+
return this.client.request("GET", `/api/wallets/${encodeURIComponent(walletId)}`);
|
|
361
846
|
}
|
|
362
847
|
/** Update a wallet */
|
|
363
848
|
async update(walletId, input) {
|
|
364
849
|
return this.client.request(
|
|
365
850
|
"PATCH",
|
|
366
|
-
`/api/wallets/${walletId}`,
|
|
851
|
+
`/api/wallets/${encodeURIComponent(walletId)}`,
|
|
367
852
|
input
|
|
368
853
|
);
|
|
369
854
|
}
|
|
370
855
|
/** Delete a wallet (cannot delete if linked to agents) */
|
|
371
856
|
async delete(walletId) {
|
|
372
|
-
return this.client.request(
|
|
857
|
+
return this.client.request(
|
|
858
|
+
"DELETE",
|
|
859
|
+
`/api/wallets/${encodeURIComponent(walletId)}`
|
|
860
|
+
);
|
|
373
861
|
}
|
|
374
862
|
/** Provision a wallet for onchain transactions */
|
|
375
863
|
async provision(walletId) {
|
|
376
864
|
return this.client.request(
|
|
377
865
|
"POST",
|
|
378
|
-
`/api/wallets/${walletId}/provision`,
|
|
866
|
+
`/api/wallets/${encodeURIComponent(walletId)}/provision`,
|
|
379
867
|
{}
|
|
380
868
|
);
|
|
381
869
|
}
|
|
@@ -383,7 +871,7 @@ var WalletsClient = class {
|
|
|
383
871
|
async refreshBalance(walletId) {
|
|
384
872
|
return this.client.request(
|
|
385
873
|
"POST",
|
|
386
|
-
`/api/wallets/${walletId}/refresh-balance`,
|
|
874
|
+
`/api/wallets/${encodeURIComponent(walletId)}/refresh-balance`,
|
|
387
875
|
{}
|
|
388
876
|
);
|
|
389
877
|
}
|
|
@@ -410,43 +898,53 @@ var PoliciesClient = class {
|
|
|
410
898
|
}
|
|
411
899
|
/** Get a single policy by ID */
|
|
412
900
|
async get(policyId) {
|
|
413
|
-
return this.client.request("GET", `/api/policies/${policyId}`);
|
|
901
|
+
return this.client.request("GET", `/api/policies/${encodeURIComponent(policyId)}`);
|
|
414
902
|
}
|
|
415
903
|
/** Update a policy */
|
|
416
904
|
async update(policyId, input) {
|
|
417
905
|
return this.client.request(
|
|
418
906
|
"PATCH",
|
|
419
|
-
`/api/policies/${policyId}`,
|
|
907
|
+
`/api/policies/${encodeURIComponent(policyId)}`,
|
|
420
908
|
input
|
|
421
909
|
);
|
|
422
910
|
}
|
|
423
911
|
/** Delete a policy */
|
|
424
912
|
async delete(policyId) {
|
|
425
|
-
return this.client.request(
|
|
913
|
+
return this.client.request(
|
|
914
|
+
"DELETE",
|
|
915
|
+
`/api/policies/${encodeURIComponent(policyId)}`
|
|
916
|
+
);
|
|
426
917
|
}
|
|
427
918
|
/** List rules for a policy */
|
|
428
919
|
async listRules(policyId) {
|
|
429
|
-
return this.client.request(
|
|
920
|
+
return this.client.request(
|
|
921
|
+
"GET",
|
|
922
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules`
|
|
923
|
+
);
|
|
430
924
|
}
|
|
431
925
|
/** Add a single rule to a policy */
|
|
432
926
|
async addRule(policyId, rule) {
|
|
433
927
|
return this.client.request(
|
|
434
928
|
"POST",
|
|
435
|
-
`/api/policies/${policyId}/rules`,
|
|
929
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules`,
|
|
436
930
|
rule
|
|
437
931
|
);
|
|
438
932
|
}
|
|
439
933
|
/** Add multiple rules to a policy */
|
|
440
934
|
async addRules(policyId, rules) {
|
|
441
|
-
return this.client.request(
|
|
442
|
-
|
|
443
|
-
|
|
935
|
+
return this.client.request(
|
|
936
|
+
"POST",
|
|
937
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules`,
|
|
938
|
+
{
|
|
939
|
+
rules
|
|
940
|
+
}
|
|
941
|
+
);
|
|
444
942
|
}
|
|
445
943
|
/** Update a single rule */
|
|
446
944
|
async updateRule(policyId, ruleId, input) {
|
|
447
945
|
return this.client.request(
|
|
448
946
|
"PATCH",
|
|
449
|
-
`/api/policies/${policyId}/rules/${ruleId}`,
|
|
947
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`,
|
|
450
948
|
input
|
|
451
949
|
);
|
|
452
950
|
}
|
|
@@ -454,14 +952,51 @@ var PoliciesClient = class {
|
|
|
454
952
|
async deleteRule(policyId, ruleId) {
|
|
455
953
|
return this.client.request(
|
|
456
954
|
"DELETE",
|
|
457
|
-
`/api/policies/${policyId}/rules/${ruleId}`
|
|
955
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`
|
|
458
956
|
);
|
|
459
957
|
}
|
|
460
958
|
/** Delete all rules for a policy */
|
|
461
959
|
async deleteAllRules(policyId) {
|
|
462
960
|
return this.client.request(
|
|
463
961
|
"DELETE",
|
|
464
|
-
`/api/policies/${policyId}/rules`
|
|
962
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
/** List the version history for a policy (newest first). */
|
|
966
|
+
async listVersions(policyId, params) {
|
|
967
|
+
const query = new URLSearchParams();
|
|
968
|
+
if (params?.limit) query.set("limit", String(params.limit));
|
|
969
|
+
if (params?.offset) query.set("offset", String(params.offset));
|
|
970
|
+
const qs = query.toString() ? `?${query.toString()}` : "";
|
|
971
|
+
return this.client.request(
|
|
972
|
+
"GET",
|
|
973
|
+
`/api/policies/${encodeURIComponent(policyId)}/versions${qs}`
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
/** Get a single version's snapshot. */
|
|
977
|
+
async getVersion(policyId, version) {
|
|
978
|
+
return this.client.request(
|
|
979
|
+
"GET",
|
|
980
|
+
`/api/policies/${encodeURIComponent(policyId)}/versions/${encodeURIComponent(version)}`
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
/** Diff two versions of a policy. */
|
|
984
|
+
async compareVersions(policyId, from, to) {
|
|
985
|
+
return this.client.request(
|
|
986
|
+
"GET",
|
|
987
|
+
`/api/policies/${encodeURIComponent(policyId)}/versions/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Restore an earlier version. Appends a new ROLLED_BACK version. If the
|
|
992
|
+
* organization requires governance approval for policy changes, the API
|
|
993
|
+
* returns a pending-approval response instead of applying the rollback.
|
|
994
|
+
*/
|
|
995
|
+
async rollbackVersion(policyId, version, options) {
|
|
996
|
+
return this.client.request(
|
|
997
|
+
"POST",
|
|
998
|
+
`/api/policies/${encodeURIComponent(policyId)}/versions/${encodeURIComponent(version)}/rollback`,
|
|
999
|
+
options ?? {}
|
|
465
1000
|
);
|
|
466
1001
|
}
|
|
467
1002
|
};
|
|
@@ -487,6 +1022,107 @@ if (process.env.SENTRY_DSN) {
|
|
|
487
1022
|
enableLogs: true
|
|
488
1023
|
});
|
|
489
1024
|
}
|
|
1025
|
+
function isValidBrowserActionObservation(value) {
|
|
1026
|
+
if (!value || typeof value !== "object") return false;
|
|
1027
|
+
const observation = value;
|
|
1028
|
+
if (typeof observation.reference !== "string" || !observation.reference.trim() || observation.reference.length > 255 || typeof observation.observedAt !== "string" || Number.isNaN(Date.parse(observation.observedAt)) || typeof observation.pageUrl !== "string" || observation.pageUrl.length > 2048) {
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
try {
|
|
1032
|
+
const pageUrl = new URL(observation.pageUrl);
|
|
1033
|
+
return pageUrl.protocol === "https:" && !pageUrl.username && !pageUrl.password;
|
|
1034
|
+
} catch {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
var ContoError = class extends Error {
|
|
1039
|
+
constructor(message, metadata) {
|
|
1040
|
+
super(message);
|
|
1041
|
+
this.name = "ContoError";
|
|
1042
|
+
this.code = metadata.code;
|
|
1043
|
+
this.status = metadata.status;
|
|
1044
|
+
this.requestId = metadata.requestId;
|
|
1045
|
+
this.retryAfter = metadata.retryAfter;
|
|
1046
|
+
this.retryGuidance = metadata.retryGuidance;
|
|
1047
|
+
this.actionUrl = metadata.actionUrl;
|
|
1048
|
+
this.fieldErrors = metadata.fieldErrors;
|
|
1049
|
+
this.reasons = metadata.reasons;
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
1053
|
+
constructor(requestId, confirmationInput, receipt) {
|
|
1054
|
+
super("External card execution completed but confirmation is pending", {
|
|
1055
|
+
code: "CARD_CONFIRMATION_PENDING",
|
|
1056
|
+
status: 0,
|
|
1057
|
+
requestId,
|
|
1058
|
+
retryGuidance: "Persist the receipt and confirmation input, then retry cards.confirm(). Do not execute the card payment again."
|
|
1059
|
+
});
|
|
1060
|
+
this.name = "ExternalCardConfirmationPendingError";
|
|
1061
|
+
this.confirmationInput = confirmationInput;
|
|
1062
|
+
this.receipt = receipt;
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
var BrowserActionFinalizationPendingError = class extends ContoError {
|
|
1066
|
+
constructor(input) {
|
|
1067
|
+
super("Browser action completed but finalization is pending", {
|
|
1068
|
+
code: "BROWSER_ACTION_FINALIZATION_PENDING",
|
|
1069
|
+
status: 0,
|
|
1070
|
+
retryGuidance: "Persist the result and observation, then retry finalizeBrowserAction with the same dispatch attempt. Do not execute the browser action again."
|
|
1071
|
+
});
|
|
1072
|
+
this.name = "BrowserActionFinalizationPendingError";
|
|
1073
|
+
this.authorizationId = input.authorizationId;
|
|
1074
|
+
this.dispatchAttemptId = input.dispatchAttemptId;
|
|
1075
|
+
this.result = input.result;
|
|
1076
|
+
this.observation = input.observation;
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
function generatedIdempotencyKey(prefix) {
|
|
1080
|
+
return `${prefix}_${(0, import_node_crypto2.randomUUID)()}`;
|
|
1081
|
+
}
|
|
1082
|
+
function requireFinancialIdentifier(value, field) {
|
|
1083
|
+
if (!value || value.trim().length === 0) {
|
|
1084
|
+
throw new ContoError(`${field} is required for this financial write`, {
|
|
1085
|
+
code: "IDEMPOTENCY_KEY_REQUIRED",
|
|
1086
|
+
status: 0,
|
|
1087
|
+
retryGuidance: `Generate a stable unique ${field} and reuse it when reconciling or retrying this operation.`
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
if (value.length > 255) {
|
|
1091
|
+
throw new ContoError(`${field} must be 255 characters or fewer`, {
|
|
1092
|
+
code: "INVALID_IDEMPOTENCY_KEY",
|
|
1093
|
+
status: 0
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
return value;
|
|
1097
|
+
}
|
|
1098
|
+
function withQuery(path, query) {
|
|
1099
|
+
if (!query) return path;
|
|
1100
|
+
const searchParams = new URLSearchParams();
|
|
1101
|
+
for (const [key, value] of Object.entries(query)) {
|
|
1102
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
1103
|
+
searchParams.set(key, String(value));
|
|
1104
|
+
}
|
|
1105
|
+
const queryString = searchParams.toString();
|
|
1106
|
+
return queryString ? `${path}?${queryString}` : path;
|
|
1107
|
+
}
|
|
1108
|
+
function rethrowAmbiguousCommerceMutation(error, executionId, action) {
|
|
1109
|
+
if (error instanceof ContoError && (error.status === 0 || error.status >= 500)) {
|
|
1110
|
+
throw new ContoError(error.message, {
|
|
1111
|
+
code: error.code,
|
|
1112
|
+
status: error.status,
|
|
1113
|
+
requestId: error.requestId,
|
|
1114
|
+
retryAfter: error.retryAfter,
|
|
1115
|
+
retryGuidance: action === "authorize" ? `Call getCommerceExecution(${JSON.stringify(
|
|
1116
|
+
executionId
|
|
1117
|
+
)}). If it is PROCESSING, treat the authorization as consumed and do not authorize or click again.` : `Call getCommerceExecution(${JSON.stringify(
|
|
1118
|
+
executionId
|
|
1119
|
+
)}), then refreshCommerceExecution(${JSON.stringify(
|
|
1120
|
+
executionId
|
|
1121
|
+
)}). Do not ${action} again until the existing execution is reconciled.`
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
throw error;
|
|
1125
|
+
}
|
|
490
1126
|
var Conto = class {
|
|
491
1127
|
constructor(config) {
|
|
492
1128
|
if (!config.apiKey) {
|
|
@@ -497,78 +1133,600 @@ var Conto = class {
|
|
|
497
1133
|
}
|
|
498
1134
|
this.apiKey = config.apiKey;
|
|
499
1135
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
1136
|
+
assertSecureBaseUrl(this.baseUrl);
|
|
500
1137
|
this.timeout = config.timeout || 3e4;
|
|
501
1138
|
this.payments = new PaymentsClient(this);
|
|
1139
|
+
this.cards = new CardsClient(this);
|
|
1140
|
+
this.connectedCards = new ConnectedCardsClient(this);
|
|
1141
|
+
this.x402 = new X402Client(this);
|
|
1142
|
+
this.mpp = new MppClient(this);
|
|
502
1143
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
1144
|
+
/**
|
|
1145
|
+
* List masked Link payment methods for a session-created connection that is
|
|
1146
|
+
* explicitly bound to this agent. No Link token or payment artifact is
|
|
1147
|
+
* returned through the agent SDK.
|
|
1148
|
+
*/
|
|
1149
|
+
async listStripeLinkPaymentMethods(connectionId) {
|
|
1150
|
+
return this.request(
|
|
1151
|
+
"GET",
|
|
1152
|
+
withQuery("/api/sdk/stripe-link/payment-methods", { connectionId })
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Normalize and durably revise a partial agent purchase proposal without
|
|
1157
|
+
* creating a payment side effect. Missing facts return as NEEDS_CONTEXT
|
|
1158
|
+
* requirements; changed writes require the exact prior revision.
|
|
1159
|
+
*/
|
|
1160
|
+
async normalizeCommerceProposal(input) {
|
|
1161
|
+
const proposalKey = input.proposalKey?.trim();
|
|
1162
|
+
if (!proposalKey || proposalKey.length < 8 || proposalKey.length > 255) {
|
|
1163
|
+
throw new ContoError("proposalKey must contain 8 to 255 characters", {
|
|
1164
|
+
code: "INVALID_PROPOSAL_KEY",
|
|
1165
|
+
status: 0
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
return this.request(
|
|
1169
|
+
"POST",
|
|
1170
|
+
"/api/sdk/commerce-proposals/normalize",
|
|
1171
|
+
{ ...input, proposalKey },
|
|
1172
|
+
{ idempotent: true }
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
/** Reload one durable proposal head in this agent and tenant scope. */
|
|
1176
|
+
async getCommerceProposal(proposalId) {
|
|
1177
|
+
return this.request(
|
|
1178
|
+
"GET",
|
|
1179
|
+
`/api/sdk/commerce-proposals/${encodeURIComponent(proposalId)}`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Evaluate policy and begin one exact Stripe Link commerce intent.
|
|
1184
|
+
*
|
|
1185
|
+
* The stable idempotency key makes transport retries safe. Reusing it with a
|
|
1186
|
+
* different intent is rejected by the API.
|
|
1187
|
+
*/
|
|
1188
|
+
async beginCommerceExecution(input) {
|
|
1189
|
+
const idempotencyKey = input.idempotencyKey?.trim();
|
|
1190
|
+
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
|
|
1191
|
+
throw new ContoError("idempotencyKey must contain 8 to 255 characters", {
|
|
1192
|
+
code: "INVALID_IDEMPOTENCY_KEY",
|
|
1193
|
+
status: 0,
|
|
1194
|
+
retryGuidance: "Generate one stable identifier per commerce intent and reuse it for retries."
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
return this.request(
|
|
1198
|
+
"POST",
|
|
1199
|
+
input.executionMethod === "AGENT_CHECKOUT" || input.executionMethod === "BROWSER_CHECKOUT" ? "/api/sdk/checkout-guard/executions" : "/api/sdk/commerce-executions",
|
|
1200
|
+
{
|
|
1201
|
+
..."connectionId" in input ? {
|
|
1202
|
+
connectionId: input.connectionId,
|
|
1203
|
+
paymentMethodId: input.paymentMethodId
|
|
1204
|
+
} : {},
|
|
1205
|
+
..."browserSessionId" in input ? {
|
|
1206
|
+
browserSessionId: input.browserSessionId,
|
|
1207
|
+
action: input.action
|
|
1208
|
+
} : {},
|
|
1209
|
+
executionMethod: input.executionMethod ?? "MPP",
|
|
1210
|
+
idempotencyKey,
|
|
1211
|
+
intentId: input.intentId,
|
|
1212
|
+
amountMinor: input.amountMinor,
|
|
1213
|
+
currency: input.currency,
|
|
1214
|
+
purpose: input.purpose,
|
|
1215
|
+
category: input.category,
|
|
1216
|
+
counterparty: input.counterparty,
|
|
1217
|
+
cart: input.cart,
|
|
1218
|
+
providerPresentation: input.providerPresentation
|
|
1219
|
+
},
|
|
1220
|
+
{ idempotent: true }
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
/** Atomically consume the Conto authorization for one exact browser action. */
|
|
1224
|
+
async authorizeCheckoutAction(executionId, browserSessionId, action) {
|
|
1225
|
+
try {
|
|
1226
|
+
return await this.request(
|
|
1227
|
+
"POST",
|
|
1228
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/authorize-action`,
|
|
1229
|
+
{ browserSessionId, action }
|
|
1230
|
+
);
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
rethrowAmbiguousCommerceMutation(error, executionId, "authorize");
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
/** Claim a durable browser action for one stable privileged-runtime attempt. */
|
|
1236
|
+
async claimBrowserAction(authorizationId, dispatchAttemptId, browserSessionId, action) {
|
|
1237
|
+
return this.request(
|
|
1238
|
+
"POST",
|
|
1239
|
+
`/api/sdk/browser-actions/${encodeURIComponent(authorizationId)}/claim`,
|
|
1240
|
+
{ dispatchAttemptId, browserSessionId, action },
|
|
1241
|
+
{ idempotent: true }
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
/** Finalize the exact dispatch attempt; this never performs the browser side effect. */
|
|
1245
|
+
async finalizeBrowserAction(authorizationId, input) {
|
|
1246
|
+
return this.request(
|
|
1247
|
+
"POST",
|
|
1248
|
+
`/api/sdk/browser-actions/${encodeURIComponent(authorizationId)}/finalize`,
|
|
1249
|
+
input,
|
|
1250
|
+
{ idempotent: true }
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Own the complete privileged-browser enforcement sequence. The stable
|
|
1255
|
+
* dispatchAttemptId must be persisted by the runtime before this call.
|
|
1256
|
+
*/
|
|
1257
|
+
async runGuardedCheckout(input, runtime) {
|
|
1258
|
+
const begun = await this.beginCommerceExecution(input);
|
|
1259
|
+
if (!["READY", "PROCESSING"].includes(begun.execution.status)) {
|
|
1260
|
+
throw new ContoError("Checkout action is not ready", {
|
|
1261
|
+
code: "COMMERCE_EXECUTION_NOT_READY",
|
|
1262
|
+
status: 409
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
const authorized = await this.authorizeCheckoutAction(
|
|
1266
|
+
begun.execution.id,
|
|
1267
|
+
input.browserSessionId,
|
|
1268
|
+
input.action
|
|
1269
|
+
);
|
|
1270
|
+
await this.claimBrowserAction(
|
|
1271
|
+
authorized.authorization.authorizationId,
|
|
1272
|
+
runtime.dispatchAttemptId,
|
|
1273
|
+
input.browserSessionId,
|
|
1274
|
+
input.action
|
|
1275
|
+
);
|
|
1276
|
+
let dispatched;
|
|
1277
|
+
try {
|
|
1278
|
+
dispatched = await runtime.execute(authorized.authorization);
|
|
1279
|
+
} catch (error) {
|
|
506
1280
|
try {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (attempt < maxRetries) {
|
|
518
|
-
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
519
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
520
|
-
continue;
|
|
521
|
-
}
|
|
1281
|
+
await this.finalizeBrowserAction(authorized.authorization.authorizationId, {
|
|
1282
|
+
dispatchAttemptId: runtime.dispatchAttemptId,
|
|
1283
|
+
outcome: "INDETERMINATE"
|
|
1284
|
+
});
|
|
1285
|
+
} catch {
|
|
1286
|
+
throw new ContoError("Browser action failed and its final state is indeterminate", {
|
|
1287
|
+
code: "BROWSER_ACTION_DISPATCH_INDETERMINATE",
|
|
1288
|
+
status: 0,
|
|
1289
|
+
retryGuidance: "Do not execute the browser action again. Reconcile the merchant page or payment provider before taking another action."
|
|
1290
|
+
});
|
|
522
1291
|
}
|
|
1292
|
+
throw error;
|
|
523
1293
|
}
|
|
524
|
-
|
|
1294
|
+
if (!dispatched || !isValidBrowserActionObservation(dispatched.observation)) {
|
|
1295
|
+
try {
|
|
1296
|
+
await this.finalizeBrowserAction(authorized.authorization.authorizationId, {
|
|
1297
|
+
dispatchAttemptId: runtime.dispatchAttemptId,
|
|
1298
|
+
outcome: "INDETERMINATE"
|
|
1299
|
+
});
|
|
1300
|
+
} catch {
|
|
1301
|
+
}
|
|
1302
|
+
throw new ContoError("Privileged browser executor returned an invalid observation", {
|
|
1303
|
+
code: "BROWSER_ACTION_OBSERVATION_INVALID",
|
|
1304
|
+
status: 0,
|
|
1305
|
+
retryGuidance: "Do not execute the browser action again. Reconcile the merchant page, then record a valid observation or resolve the indeterminate attempt."
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
try {
|
|
1309
|
+
await this.finalizeBrowserAction(authorized.authorization.authorizationId, {
|
|
1310
|
+
dispatchAttemptId: runtime.dispatchAttemptId,
|
|
1311
|
+
outcome: "CONSUMED",
|
|
1312
|
+
observation: dispatched.observation
|
|
1313
|
+
});
|
|
1314
|
+
} catch {
|
|
1315
|
+
throw new BrowserActionFinalizationPendingError({
|
|
1316
|
+
authorizationId: authorized.authorization.authorizationId,
|
|
1317
|
+
dispatchAttemptId: runtime.dispatchAttemptId,
|
|
1318
|
+
result: dispatched.result,
|
|
1319
|
+
observation: dispatched.observation
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
return {
|
|
1323
|
+
...authorized,
|
|
1324
|
+
dispatchAttemptId: runtime.dispatchAttemptId,
|
|
1325
|
+
result: dispatched.result,
|
|
1326
|
+
observation: dispatched.observation
|
|
1327
|
+
};
|
|
525
1328
|
}
|
|
526
|
-
/**
|
|
527
|
-
|
|
1329
|
+
/**
|
|
1330
|
+
* Reference privileged-driver integration. Only this wrapper should be
|
|
1331
|
+
* exposed as the agent's payment-sensitive browser tool.
|
|
1332
|
+
*/
|
|
1333
|
+
async runGuardedCheckoutWithDriver(input, runtime) {
|
|
1334
|
+
return this.runGuardedCheckout(input, {
|
|
1335
|
+
dispatchAttemptId: runtime.dispatchAttemptId,
|
|
1336
|
+
execute: (authorization) => runtime.driver.dispatch({
|
|
1337
|
+
authorization,
|
|
1338
|
+
browserSessionId: input.browserSessionId,
|
|
1339
|
+
action: input.action
|
|
1340
|
+
})
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Policy-check and authorize an exact action before invoking a legacy callback.
|
|
1345
|
+
* @deprecated Use runGuardedCheckout so the durable claim and final
|
|
1346
|
+
* observation cannot be omitted.
|
|
1347
|
+
*/
|
|
1348
|
+
async guardCheckout(input, executor) {
|
|
1349
|
+
const begun = await this.beginCommerceExecution(input);
|
|
1350
|
+
if (begun.execution.status !== "READY") {
|
|
1351
|
+
throw new ContoError("Checkout action is not ready", {
|
|
1352
|
+
code: "COMMERCE_EXECUTION_NOT_READY",
|
|
1353
|
+
status: 409
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
const authorized = await this.authorizeCheckoutAction(
|
|
1357
|
+
begun.execution.id,
|
|
1358
|
+
input.browserSessionId,
|
|
1359
|
+
input.action
|
|
1360
|
+
);
|
|
1361
|
+
const result = await executor(authorized.authorization);
|
|
1362
|
+
return { ...authorized, result };
|
|
1363
|
+
}
|
|
1364
|
+
/** Read one agent-scoped commerce execution and its normalized evidence. */
|
|
1365
|
+
async getCommerceExecution(executionId) {
|
|
1366
|
+
return this.request(
|
|
1367
|
+
"GET",
|
|
1368
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Observe Link state and reconcile terminal accounting. This operation is
|
|
1373
|
+
* safe to retry because it does not request another checkout execution.
|
|
1374
|
+
*/
|
|
1375
|
+
async refreshCommerceExecution(executionId) {
|
|
1376
|
+
return this.request(
|
|
1377
|
+
"POST",
|
|
1378
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/refresh`,
|
|
1379
|
+
{},
|
|
1380
|
+
{ idempotent: true }
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Execute the approved provider-direct or server-only checkout handoff. Transport retries are
|
|
1385
|
+
* deliberately disabled. Read or refresh the execution after an ambiguous
|
|
1386
|
+
* failure before taking another execution action.
|
|
1387
|
+
*/
|
|
1388
|
+
async executeCommerceExecution(executionId) {
|
|
1389
|
+
try {
|
|
1390
|
+
return await this.request(
|
|
1391
|
+
"POST",
|
|
1392
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/execute`,
|
|
1393
|
+
{}
|
|
1394
|
+
);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
rethrowAmbiguousCommerceMutation(error, executionId, "execute");
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Request cancellation and release reserved spend after Link confirms it.
|
|
1401
|
+
* Transport retries are disabled because a lost provider response requires
|
|
1402
|
+
* reconciliation.
|
|
1403
|
+
*/
|
|
1404
|
+
async cancelCommerceExecution(executionId) {
|
|
1405
|
+
try {
|
|
1406
|
+
return await this.request(
|
|
1407
|
+
"POST",
|
|
1408
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/cancel`,
|
|
1409
|
+
{}
|
|
1410
|
+
);
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
rethrowAmbiguousCommerceMutation(error, executionId, "cancel");
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
/** Read credentials, linked wallets, effective policies, and scopes without creating a payment. */
|
|
1416
|
+
async setup() {
|
|
1417
|
+
return this.request("GET", "/api/sdk/setup");
|
|
1418
|
+
}
|
|
1419
|
+
/** Make an authenticated request to the Conto API */
|
|
1420
|
+
async request(method, path, body, options = {}) {
|
|
528
1421
|
const url = `${this.baseUrl}${path}`;
|
|
529
1422
|
const controller = new AbortController();
|
|
530
1423
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1424
|
+
const normalizedMethod = method.toUpperCase();
|
|
1425
|
+
const retryable = options.idempotent === true || ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod);
|
|
531
1426
|
try {
|
|
532
|
-
const response = await
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1427
|
+
const response = await fetchWithRetry(
|
|
1428
|
+
url,
|
|
1429
|
+
{
|
|
1430
|
+
method: normalizedMethod,
|
|
1431
|
+
headers: {
|
|
1432
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1433
|
+
"Content-Type": "application/json",
|
|
1434
|
+
"User-Agent": `@conto_finance/sdk/${import_package_version2.SDK_PACKAGE_VERSION}`
|
|
1435
|
+
},
|
|
1436
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
1437
|
+
signal: controller.signal
|
|
538
1438
|
},
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
clearTimeout(timeoutId);
|
|
543
|
-
const data = await response.json();
|
|
1439
|
+
{ retryable, maxRetries: options.maxRetries }
|
|
1440
|
+
);
|
|
1441
|
+
const data = await readResponseData(response);
|
|
544
1442
|
if (!response.ok) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
throw error;
|
|
1443
|
+
throw new ContoError(
|
|
1444
|
+
responseErrorMessage(data, response.status),
|
|
1445
|
+
responseErrorMetadata(response, data)
|
|
1446
|
+
);
|
|
550
1447
|
}
|
|
551
1448
|
return data;
|
|
552
1449
|
} catch (error) {
|
|
553
|
-
|
|
1450
|
+
if (error instanceof ContoError) throw error;
|
|
554
1451
|
if (error instanceof Error && error.name === "AbortError") {
|
|
555
|
-
const timeoutError = new
|
|
556
|
-
|
|
557
|
-
|
|
1452
|
+
const timeoutError = new ContoError("Request timeout", {
|
|
1453
|
+
code: "TIMEOUT",
|
|
1454
|
+
status: 0,
|
|
1455
|
+
retryGuidance: retryable ? "The request is safe to retry." : "Do not retry automatically; check operation status before trying again."
|
|
1456
|
+
});
|
|
558
1457
|
Sentry2.captureException(timeoutError, { extra: { method, path } });
|
|
559
1458
|
throw timeoutError;
|
|
560
1459
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
1460
|
+
const networkError = new ContoError("Network request failed", {
|
|
1461
|
+
code: "NETWORK_ERROR",
|
|
1462
|
+
status: 0,
|
|
1463
|
+
retryGuidance: retryable ? "The request is safe to retry." : "Do not retry automatically; check operation status before trying again."
|
|
1464
|
+
});
|
|
1465
|
+
Sentry2.captureException(networkError, { extra: { method, path } });
|
|
1466
|
+
throw networkError;
|
|
1467
|
+
} finally {
|
|
1468
|
+
clearTimeout(timeoutId);
|
|
565
1469
|
}
|
|
566
1470
|
}
|
|
567
1471
|
};
|
|
1472
|
+
var CardsClient = class {
|
|
1473
|
+
constructor(client) {
|
|
1474
|
+
this.client = client;
|
|
1475
|
+
}
|
|
1476
|
+
async approve(input) {
|
|
1477
|
+
const idempotencyKey = input.idempotencyKey?.trim();
|
|
1478
|
+
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 128) {
|
|
1479
|
+
throw new ContoError("idempotencyKey must contain 8 to 128 characters", {
|
|
1480
|
+
code: "INVALID_IDEMPOTENCY_KEY",
|
|
1481
|
+
status: 0,
|
|
1482
|
+
retryGuidance: "Generate one stable identifier per purchase intent and reuse it for retries."
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
return this.client.request(
|
|
1486
|
+
"POST",
|
|
1487
|
+
"/api/sdk/external-cards/approve",
|
|
1488
|
+
{
|
|
1489
|
+
...input,
|
|
1490
|
+
idempotencyKey,
|
|
1491
|
+
currency: input.currency || "USD"
|
|
1492
|
+
},
|
|
1493
|
+
{ idempotent: true }
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
async confirm(requestId, input) {
|
|
1497
|
+
return this.client.request(
|
|
1498
|
+
"POST",
|
|
1499
|
+
`/api/sdk/external-cards/${encodeURIComponent(requestId)}/confirm`,
|
|
1500
|
+
input,
|
|
1501
|
+
{ idempotent: true }
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Atomically claim the approved intent before customer code accesses or uses
|
|
1506
|
+
* the card credential. A request can be claimed only once.
|
|
1507
|
+
*/
|
|
1508
|
+
async begin(requestId, input) {
|
|
1509
|
+
return this.client.request(
|
|
1510
|
+
"POST",
|
|
1511
|
+
`/api/sdk/external-cards/${encodeURIComponent(requestId)}/begin`,
|
|
1512
|
+
input,
|
|
1513
|
+
{ idempotent: true }
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
/** Read execution and reconciliation state without exposing card credentials. */
|
|
1517
|
+
async status(requestId) {
|
|
1518
|
+
return this.client.request(
|
|
1519
|
+
"GET",
|
|
1520
|
+
`/api/sdk/external-cards/${encodeURIComponent(requestId)}`
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Create a reference payment tool whose executor callback is unreachable
|
|
1525
|
+
* until Conto has returned a live approval grant.
|
|
1526
|
+
*
|
|
1527
|
+
* @deprecated Use the separate `@conto_finance/card-access` product package for
|
|
1528
|
+
* customer-hosted card enforcement. This helper remains for compatibility.
|
|
1529
|
+
*/
|
|
1530
|
+
controlled(executor) {
|
|
1531
|
+
return new ControlledExternalCardPaymentTool(this, executor);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
var ControlledExternalCardPaymentTool = class {
|
|
1535
|
+
constructor(cardsClient, executor) {
|
|
1536
|
+
this.cardsClient = cardsClient;
|
|
1537
|
+
this.executor = executor;
|
|
1538
|
+
}
|
|
1539
|
+
async pay(input) {
|
|
1540
|
+
const idempotencyKey = input.idempotencyKey.trim();
|
|
1541
|
+
const approval = await this.cardsClient.approve({ ...input, idempotencyKey });
|
|
1542
|
+
if (!approval.approved || !approval.approvalToken) {
|
|
1543
|
+
const executionAlreadyClaimed = approval.decision === "EXECUTING" || approval.decision === "CONFIRMING";
|
|
1544
|
+
throw new ContoError("External card payment was not approved", {
|
|
1545
|
+
code: executionAlreadyClaimed ? "CARD_EXECUTION_RECONCILIATION_REQUIRED" : approval.decision === "COMPLETED" ? "CARD_PAYMENT_ALREADY_COMPLETED" : approval.decision === "REQUIRES_APPROVAL" ? "REQUIRES_APPROVAL" : "CARD_PAYMENT_DENIED",
|
|
1546
|
+
status: executionAlreadyClaimed || approval.decision === "COMPLETED" ? 409 : approval.decision === "REQUIRES_APPROVAL" ? 202 : 403,
|
|
1547
|
+
requestId: approval.requestId,
|
|
1548
|
+
retryGuidance: executionAlreadyClaimed ? "Do not execute the payment again. Check cards.status() and confirm or reconcile the existing request." : approval.decision === "COMPLETED" ? "The purchase intent is already complete. Use cards.status() to read its transaction." : approval.decision === "REQUIRES_APPROVAL" ? "Wait for review; do not access or use the card credential." : "Review the purchase intent; do not access or use the card credential."
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
if (!approval.expiresAt || Date.parse(approval.expiresAt) <= Date.now()) {
|
|
1552
|
+
throw new ContoError("External card approval has expired", {
|
|
1553
|
+
code: "CARD_APPROVAL_EXPIRED",
|
|
1554
|
+
status: 400,
|
|
1555
|
+
requestId: approval.requestId,
|
|
1556
|
+
retryGuidance: "Request a new approval before accessing or using the card credential."
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
const claim = await this.cardsClient.begin(approval.requestId, {
|
|
1560
|
+
approvalToken: approval.approvalToken
|
|
1561
|
+
});
|
|
1562
|
+
if (!claim.claimed) {
|
|
1563
|
+
throw new ContoError("External card execution was already claimed", {
|
|
1564
|
+
code: "CARD_EXECUTION_RECONCILIATION_REQUIRED",
|
|
1565
|
+
status: 409,
|
|
1566
|
+
requestId: approval.requestId,
|
|
1567
|
+
retryGuidance: "Do not execute the payment again. Check cards.status() and confirm or reconcile the existing request."
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
let execution;
|
|
1571
|
+
try {
|
|
1572
|
+
execution = await this.executor({
|
|
1573
|
+
requestId: approval.requestId,
|
|
1574
|
+
idempotencyKey,
|
|
1575
|
+
cardId: input.cardId,
|
|
1576
|
+
amount: input.amount,
|
|
1577
|
+
currency: input.currency || "USD",
|
|
1578
|
+
merchantName: input.merchantName,
|
|
1579
|
+
merchantId: input.merchantId,
|
|
1580
|
+
merchantCategory: input.merchantCategory,
|
|
1581
|
+
merchantAddress: input.merchantAddress,
|
|
1582
|
+
merchantCountry: input.merchantCountry,
|
|
1583
|
+
purpose: input.purpose,
|
|
1584
|
+
context: input.context
|
|
1585
|
+
});
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
const outcomeError = new ContoError(
|
|
1588
|
+
"External card executor failed after execution was claimed",
|
|
1589
|
+
{
|
|
1590
|
+
code: "CARD_EXECUTION_OUTCOME_UNKNOWN",
|
|
1591
|
+
status: 0,
|
|
1592
|
+
requestId: approval.requestId,
|
|
1593
|
+
retryGuidance: "Do not execute the payment again. Verify the customer card outcome, then reconcile this request."
|
|
1594
|
+
}
|
|
1595
|
+
);
|
|
1596
|
+
Sentry2.captureException(outcomeError, {
|
|
1597
|
+
extra: {
|
|
1598
|
+
requestId: approval.requestId,
|
|
1599
|
+
executorErrorName: error instanceof Error ? error.name : typeof error
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
throw outcomeError;
|
|
1603
|
+
}
|
|
1604
|
+
const confirmationInput = {
|
|
1605
|
+
approvalToken: approval.approvalToken,
|
|
1606
|
+
externalTxId: execution.externalTxId,
|
|
1607
|
+
authorizationCode: execution.authorizationCode,
|
|
1608
|
+
actualAmount: execution.actualAmount,
|
|
1609
|
+
merchantDetails: execution.merchantDetails
|
|
1610
|
+
};
|
|
1611
|
+
let confirmation;
|
|
1612
|
+
try {
|
|
1613
|
+
confirmation = await this.cardsClient.confirm(approval.requestId, confirmationInput);
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
const pending = new ExternalCardConfirmationPendingError(
|
|
1616
|
+
approval.requestId,
|
|
1617
|
+
confirmationInput,
|
|
1618
|
+
execution.receipt
|
|
1619
|
+
);
|
|
1620
|
+
Sentry2.captureException(pending, {
|
|
1621
|
+
extra: {
|
|
1622
|
+
requestId: approval.requestId,
|
|
1623
|
+
confirmationErrorName: error instanceof Error ? error.name : typeof error
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
throw pending;
|
|
1627
|
+
}
|
|
1628
|
+
return {
|
|
1629
|
+
approval,
|
|
1630
|
+
confirmation,
|
|
1631
|
+
receipt: execution.receipt
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
var ConnectedCardsClient = class {
|
|
1636
|
+
constructor(client) {
|
|
1637
|
+
this.client = client;
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Draw one single-use card credential against a verified mandate.
|
|
1641
|
+
*
|
|
1642
|
+
* On an APPROVED, minted draw the result carries live card material in
|
|
1643
|
+
* `credential`, returned exactly ONCE.
|
|
1644
|
+
*
|
|
1645
|
+
* CREDENTIAL SAFETY (load-bearing): the credential is the real, usable card.
|
|
1646
|
+
* It is revealed a single time and cannot be re-fetched. Use it in-process at
|
|
1647
|
+
* the intended merchant checkout only. Never log, print, serialize, persist,
|
|
1648
|
+
* cache, or forward it, and never place it in an error, a monitoring event,
|
|
1649
|
+
* or any other sink. See {@link ConnectedCardCredential}.
|
|
1650
|
+
*
|
|
1651
|
+
* Transport-level retry is deliberately OFF for this call. The server is
|
|
1652
|
+
* idempotent on `idempotencyKey`, so an automatic retry after a successful
|
|
1653
|
+
* mint would replay the draw and return the decision WITHOUT the credential,
|
|
1654
|
+
* silently losing the one-shot card material. If a `draw` fails ambiguously,
|
|
1655
|
+
* do not blindly retry: check the outcome, and reuse the same
|
|
1656
|
+
* `idempotencyKey` only to reconcile the same draw.
|
|
1657
|
+
*
|
|
1658
|
+
* @example
|
|
1659
|
+
* ```typescript
|
|
1660
|
+
* const draw = await conto.connectedCards.draw({
|
|
1661
|
+
* cardId: 'cmm59z8fj000l49h7bjqza11p',
|
|
1662
|
+
* mandateId: 'cmm5a1b2c000m49h7defgh22q',
|
|
1663
|
+
* idempotencyKey: 'checkout_01J8...',
|
|
1664
|
+
* amountMinor: 4999,
|
|
1665
|
+
* merchant: { merchantName: 'Example Store', merchantCategory: '5311' },
|
|
1666
|
+
* });
|
|
1667
|
+
* if (draw.decision === 'APPROVED' && draw.credential) {
|
|
1668
|
+
* // Keep the credential in memory, acknowledge receipt, then use it at checkout.
|
|
1669
|
+
* await conto.connectedCards.acknowledgeDelivery(draw.requestId);
|
|
1670
|
+
* // Do not log or store draw.credential.
|
|
1671
|
+
* }
|
|
1672
|
+
* ```
|
|
1673
|
+
*/
|
|
1674
|
+
async draw(input) {
|
|
1675
|
+
const idempotencyKey = input.idempotencyKey?.trim();
|
|
1676
|
+
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 128) {
|
|
1677
|
+
throw new ContoError("idempotencyKey must contain 8 to 128 characters", {
|
|
1678
|
+
code: "INVALID_IDEMPOTENCY_KEY",
|
|
1679
|
+
status: 0,
|
|
1680
|
+
retryGuidance: "Generate one stable identifier per draw and reuse it only to reconcile the same draw."
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
return this.client.request("POST", "/api/sdk/connected-cards/draws", {
|
|
1684
|
+
...input,
|
|
1685
|
+
idempotencyKey,
|
|
1686
|
+
currency: input.currency ?? "USD"
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* List the connected cards and active mandates this agent may draw against.
|
|
1691
|
+
*
|
|
1692
|
+
* The response is masked: no PAN and no provider tokens. This is a read-only
|
|
1693
|
+
* GET, so transport retry on transient failures is safe.
|
|
1694
|
+
*/
|
|
1695
|
+
/** Record receipt after a fresh draw. Does not confirm checkout or payment settlement. */
|
|
1696
|
+
async acknowledgeDelivery(requestId) {
|
|
1697
|
+
return this.client.request(
|
|
1698
|
+
"POST",
|
|
1699
|
+
`/api/sdk/connected-cards/draws/${encodeURIComponent(requestId)}/acknowledge`,
|
|
1700
|
+
{},
|
|
1701
|
+
{ idempotent: true }
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
async list() {
|
|
1705
|
+
return this.client.request("GET", "/api/sdk/connected-cards");
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
568
1708
|
var PaymentsClient = class {
|
|
569
1709
|
constructor(client) {
|
|
570
1710
|
this.client = client;
|
|
571
1711
|
}
|
|
1712
|
+
/** Authorize an externally signed transfer. This write is not automatically retried. */
|
|
1713
|
+
async approve(input) {
|
|
1714
|
+
return this.client.request(
|
|
1715
|
+
"POST",
|
|
1716
|
+
"/api/sdk/payments/approve",
|
|
1717
|
+
{ ...input },
|
|
1718
|
+
{ maxRetries: 0 }
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
/** Record an existing transfer. Never sends funds; reconcile conflicts using status(). */
|
|
1722
|
+
async confirm(requestId, input) {
|
|
1723
|
+
return this.client.request(
|
|
1724
|
+
"POST",
|
|
1725
|
+
`/api/sdk/payments/${encodeURIComponent(requestId)}/confirm`,
|
|
1726
|
+
{ ...input },
|
|
1727
|
+
{ maxRetries: 0 }
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
572
1730
|
/**
|
|
573
1731
|
* Request authorization for a payment.
|
|
574
1732
|
*
|
|
@@ -598,10 +1756,22 @@ var PaymentsClient = class {
|
|
|
598
1756
|
* ```
|
|
599
1757
|
*/
|
|
600
1758
|
async request(input) {
|
|
1759
|
+
if (typeof input.amount !== "number" || !Number.isFinite(input.amount) || input.amount <= 0) {
|
|
1760
|
+
throw new ContoError("amount must be a positive finite number", {
|
|
1761
|
+
code: "INVALID_AMOUNT",
|
|
1762
|
+
status: 0,
|
|
1763
|
+
retryGuidance: "Provide a positive amount before requesting a payment."
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
const requestInput = {
|
|
1767
|
+
...input,
|
|
1768
|
+
idempotencyKey: input.idempotencyKey ?? generatedIdempotencyKey("conto_payment")
|
|
1769
|
+
};
|
|
601
1770
|
return this.client.request(
|
|
602
1771
|
"POST",
|
|
603
1772
|
"/api/sdk/payments/request",
|
|
604
|
-
|
|
1773
|
+
requestInput,
|
|
1774
|
+
{ idempotent: true }
|
|
605
1775
|
);
|
|
606
1776
|
}
|
|
607
1777
|
/**
|
|
@@ -615,16 +1785,34 @@ var PaymentsClient = class {
|
|
|
615
1785
|
* @example
|
|
616
1786
|
* ```typescript
|
|
617
1787
|
* const result = await conto.payments.execute(request.requestId);
|
|
618
|
-
* console.log(`
|
|
619
|
-
* console.log(`
|
|
1788
|
+
* console.log(`Status: ${result.status}`);
|
|
1789
|
+
* console.log(`Conto transaction: ${result.receipt.transactionId}`);
|
|
1790
|
+
* console.log(`Settlement mode: ${result.receipt.settlementMode}`);
|
|
1791
|
+
* console.log(`Transaction: ${result.receipt.txHash}`);
|
|
1792
|
+
* if (result.receipt.explorerUrl) console.log(`Explorer: ${result.receipt.explorerUrl}`);
|
|
620
1793
|
* ```
|
|
621
1794
|
*/
|
|
622
1795
|
async execute(requestId) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
1796
|
+
try {
|
|
1797
|
+
return await this.client.request(
|
|
1798
|
+
"POST",
|
|
1799
|
+
`/api/sdk/payments/${encodeURIComponent(requestId)}/execute`,
|
|
1800
|
+
{}
|
|
1801
|
+
);
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
if (error instanceof ContoError && error.status === 0) {
|
|
1804
|
+
throw new ContoError(error.message, {
|
|
1805
|
+
code: error.code,
|
|
1806
|
+
status: error.status,
|
|
1807
|
+
requestId: error.requestId,
|
|
1808
|
+
retryAfter: error.retryAfter,
|
|
1809
|
+
retryGuidance: `Check payments.status(${JSON.stringify(
|
|
1810
|
+
requestId
|
|
1811
|
+
)}) before attempting execution again.`
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
throw error;
|
|
1815
|
+
}
|
|
628
1816
|
}
|
|
629
1817
|
/**
|
|
630
1818
|
* Get the status of a payment request.
|
|
@@ -634,15 +1822,15 @@ var PaymentsClient = class {
|
|
|
634
1822
|
* @example
|
|
635
1823
|
* ```typescript
|
|
636
1824
|
* const status = await conto.payments.status(requestId);
|
|
637
|
-
* if (status.transaction?.status === '
|
|
638
|
-
* console.log('Payment confirmed
|
|
1825
|
+
* if (status.transaction?.status === 'completed') {
|
|
1826
|
+
* console.log('Payment confirmed', status.transaction.txHash);
|
|
639
1827
|
* }
|
|
640
1828
|
* ```
|
|
641
1829
|
*/
|
|
642
1830
|
async status(requestId) {
|
|
643
1831
|
return this.client.request(
|
|
644
1832
|
"GET",
|
|
645
|
-
`/api/sdk/payments/${requestId}`,
|
|
1833
|
+
`/api/sdk/payments/${encodeURIComponent(requestId)}`,
|
|
646
1834
|
void 0
|
|
647
1835
|
);
|
|
648
1836
|
}
|
|
@@ -660,7 +1848,9 @@ var PaymentsClient = class {
|
|
|
660
1848
|
* recipientAddress: '0x...',
|
|
661
1849
|
* purpose: 'Service fee'
|
|
662
1850
|
* });
|
|
663
|
-
* console.log(`
|
|
1851
|
+
* console.log(`Payment status: ${result.status}`);
|
|
1852
|
+
* console.log(`Conto transaction: ${result.receipt.transactionId}`);
|
|
1853
|
+
* console.log(`Transaction: ${result.receipt.txHash}`);
|
|
664
1854
|
* } catch (error) {
|
|
665
1855
|
* if (error.code === 'REQUIRES_APPROVAL') {
|
|
666
1856
|
* console.log('Payment needs human approval');
|
|
@@ -674,24 +1864,141 @@ var PaymentsClient = class {
|
|
|
674
1864
|
const { autoExecute: _autoExecute, ...requestInput } = input;
|
|
675
1865
|
const request = await this.request(requestInput);
|
|
676
1866
|
if (request.status === "DENIED") {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1867
|
+
throw new ContoError("Payment was denied.", {
|
|
1868
|
+
code: "PAYMENT_DENIED",
|
|
1869
|
+
status: 403,
|
|
1870
|
+
reasons: customerPaymentReasons(request.reasons),
|
|
1871
|
+
requestId: request.requestId,
|
|
1872
|
+
retryGuidance: "Review the payment request before trying again."
|
|
1873
|
+
});
|
|
681
1874
|
}
|
|
682
1875
|
if (request.status === "REQUIRES_APPROVAL") {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1876
|
+
throw new ContoError("Payment requires manual approval", {
|
|
1877
|
+
code: "REQUIRES_APPROVAL",
|
|
1878
|
+
status: 202,
|
|
1879
|
+
requestId: request.requestId,
|
|
1880
|
+
retryGuidance: "Wait for approval, then execute the same request ID."
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
if (request.status === "ACTION_REQUIRED") {
|
|
1884
|
+
throw new ContoError("Payment requires an additional action", {
|
|
1885
|
+
code: "ACTION_REQUIRED",
|
|
1886
|
+
status: 402,
|
|
1887
|
+
requestId: request.requestId,
|
|
1888
|
+
actionUrl: request.actionUrl,
|
|
1889
|
+
retryGuidance: request.actionUrl ? `Complete the required action at ${request.actionUrl}, then check the payment status.` : "Complete the required action, then check the payment status."
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
if (request.status === "PROCESSING") {
|
|
1893
|
+
throw new ContoError("Payment is processing", {
|
|
1894
|
+
code: "PAYMENT_PROCESSING",
|
|
1895
|
+
status: 202,
|
|
1896
|
+
requestId: request.requestId,
|
|
1897
|
+
retryGuidance: "Check the payment status before taking another payment action."
|
|
1898
|
+
});
|
|
687
1899
|
}
|
|
688
1900
|
return this.execute(request.requestId);
|
|
689
1901
|
}
|
|
690
1902
|
};
|
|
1903
|
+
var X402Client = class {
|
|
1904
|
+
constructor(client) {
|
|
1905
|
+
this.client = client;
|
|
1906
|
+
}
|
|
1907
|
+
async preAuthorize(input) {
|
|
1908
|
+
return this.client.request(
|
|
1909
|
+
"POST",
|
|
1910
|
+
"/api/sdk/x402/pre-authorize",
|
|
1911
|
+
input,
|
|
1912
|
+
{ idempotent: true }
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
async record(input) {
|
|
1916
|
+
const paymentId = requireFinancialIdentifier(input.paymentId, "paymentId");
|
|
1917
|
+
const requestInput = {
|
|
1918
|
+
...input,
|
|
1919
|
+
paymentId
|
|
1920
|
+
};
|
|
1921
|
+
return this.client.request(
|
|
1922
|
+
"POST",
|
|
1923
|
+
"/api/sdk/x402/record",
|
|
1924
|
+
requestInput
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1927
|
+
async budget(options) {
|
|
1928
|
+
return this.client.request(
|
|
1929
|
+
"GET",
|
|
1930
|
+
withQuery("/api/sdk/x402/budget", { sessionId: options?.sessionId }),
|
|
1931
|
+
void 0
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
async services(options) {
|
|
1935
|
+
return this.client.request(
|
|
1936
|
+
"GET",
|
|
1937
|
+
withQuery("/api/sdk/x402/services", {
|
|
1938
|
+
period: options?.period,
|
|
1939
|
+
limit: options?.limit
|
|
1940
|
+
}),
|
|
1941
|
+
void 0
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
var MppClient = class {
|
|
1946
|
+
constructor(client) {
|
|
1947
|
+
this.client = client;
|
|
1948
|
+
}
|
|
1949
|
+
async preAuthorize(input) {
|
|
1950
|
+
return this.client.request(
|
|
1951
|
+
"POST",
|
|
1952
|
+
"/api/sdk/mpp/pre-authorize",
|
|
1953
|
+
input,
|
|
1954
|
+
{ idempotent: true }
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
async record(input) {
|
|
1958
|
+
const credentialId = requireFinancialIdentifier(input.credentialId, "credentialId");
|
|
1959
|
+
const requestInput = {
|
|
1960
|
+
...input,
|
|
1961
|
+
credentialId
|
|
1962
|
+
};
|
|
1963
|
+
return this.client.request(
|
|
1964
|
+
"POST",
|
|
1965
|
+
"/api/sdk/mpp/record",
|
|
1966
|
+
requestInput
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
async budget(options) {
|
|
1970
|
+
return this.client.request(
|
|
1971
|
+
"GET",
|
|
1972
|
+
withQuery("/api/sdk/mpp/budget", { sessionId: options?.sessionId }),
|
|
1973
|
+
void 0
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
async services(options) {
|
|
1977
|
+
return this.client.request(
|
|
1978
|
+
"GET",
|
|
1979
|
+
withQuery("/api/sdk/mpp/services", {
|
|
1980
|
+
period: options?.period,
|
|
1981
|
+
limit: options?.limit
|
|
1982
|
+
}),
|
|
1983
|
+
void 0
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
691
1987
|
var index_default = Conto;
|
|
692
1988
|
// Annotate the CommonJS export names for ESM import in node:
|
|
693
1989
|
0 && (module.exports = {
|
|
1990
|
+
BrowserActionFinalizationPendingError,
|
|
1991
|
+
BrowserCheckoutTargetVerificationError,
|
|
1992
|
+
CardsClient,
|
|
1993
|
+
ConnectedCardsClient,
|
|
694
1994
|
Conto,
|
|
695
1995
|
ContoAdmin,
|
|
696
|
-
|
|
1996
|
+
ContoAdminError,
|
|
1997
|
+
ContoError,
|
|
1998
|
+
ControlledExternalCardPaymentTool,
|
|
1999
|
+
ExternalCardConfirmationPendingError,
|
|
2000
|
+
Giro,
|
|
2001
|
+
canonicalizeBrowserCheckoutTarget,
|
|
2002
|
+
createVerifiedBrowserCheckoutDriver,
|
|
2003
|
+
fingerprintBrowserCheckoutTarget
|
|
697
2004
|
});
|