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