@conto_finance/sdk 0.3.1 → 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.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.3.1",
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,6 +49,21 @@ 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: {
@@ -61,10 +76,11 @@ var require_package = __commonJS({
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
- "test:contract": "npm run build && node --test test/contract.test.mjs",
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",
68
84
  "test:consumer": "npm run build && node --test test/consumer.test.mjs",
69
85
  "verify:release": "node scripts/verify-release.mjs",
70
86
  prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
@@ -84,18 +100,27 @@ var require_package = __commonJS({
84
100
  url: "https://github.com/conto-finance/conto.git",
85
101
  directory: "packages/sdk"
86
102
  },
87
- homepage: "https://conto.finance/docs/sdk",
103
+ homepage: "https://conto.finance/docs/sdk/installation",
88
104
  bugs: {
89
- url: "https://github.com/conto-finance/conto/issues"
105
+ email: "support@conto.finance"
90
106
  },
91
107
  dependencies: {
92
108
  "@sentry/node": "^10.63.0"
93
109
  },
110
+ peerDependencies: {
111
+ "playwright-core": ">=1.50.0"
112
+ },
113
+ peerDependenciesMeta: {
114
+ "playwright-core": {
115
+ optional: true
116
+ }
117
+ },
94
118
  overrides: {
95
119
  esbuild: "0.28.1"
96
120
  },
97
121
  devDependencies: {
98
122
  "@types/node": "22.19.18",
123
+ "playwright-core": "1.61.0",
99
124
  tsup: "^8.5.1",
100
125
  typescript: "^5.9.3"
101
126
  }
@@ -118,7 +143,10 @@ var require_package_version = __commonJS({
118
143
  // src/index.ts
119
144
  var index_exports = {};
120
145
  __export(index_exports, {
146
+ BrowserActionFinalizationPendingError: () => BrowserActionFinalizationPendingError,
147
+ BrowserCheckoutTargetVerificationError: () => BrowserCheckoutTargetVerificationError,
121
148
  CardsClient: () => CardsClient,
149
+ ConnectedCardsClient: () => ConnectedCardsClient,
122
150
  Conto: () => Conto,
123
151
  ContoAdmin: () => ContoAdmin,
124
152
  ContoAdminError: () => ContoAdminError,
@@ -126,15 +154,77 @@ __export(index_exports, {
126
154
  ControlledExternalCardPaymentTool: () => ControlledExternalCardPaymentTool,
127
155
  ExternalCardConfirmationPendingError: () => ExternalCardConfirmationPendingError,
128
156
  Giro: () => Conto,
129
- default: () => index_default
157
+ canonicalizeBrowserCheckoutTarget: () => canonicalizeBrowserCheckoutTarget,
158
+ createVerifiedBrowserCheckoutDriver: () => createVerifiedBrowserCheckoutDriver,
159
+ default: () => index_default,
160
+ fingerprintBrowserCheckoutTarget: () => fingerprintBrowserCheckoutTarget
130
161
  });
131
162
  module.exports = __toCommonJS(index_exports);
132
163
  var Sentry2 = __toESM(require("@sentry/node"));
133
- var import_node_crypto = require("crypto");
164
+ var import_node_crypto2 = require("crypto");
134
165
  var import_package_version2 = __toESM(require_package_version());
135
166
 
136
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
+ }
137
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
+ }
138
228
  function abortError() {
139
229
  const error = new Error("Request aborted");
140
230
  error.name = "AbortError";
@@ -265,6 +355,7 @@ function responseErrorMetadata(response, data) {
265
355
  return {
266
356
  code,
267
357
  status: response.status,
358
+ fieldErrors: customerFieldErrors(body, response.status),
268
359
  requestId: safeRequestId(
269
360
  response.headers.get("x-request-id"),
270
361
  response.headers.get("x-correlation-id"),
@@ -276,6 +367,123 @@ function responseErrorMetadata(response, data) {
276
367
  };
277
368
  }
278
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
+
279
487
  // src/admin.ts
280
488
  var Sentry = __toESM(require("@sentry/node"));
281
489
  var import_package_version = __toESM(require_package_version());
@@ -307,12 +515,14 @@ var ContoAdmin = class {
307
515
  }
308
516
  this.orgApiKey = config.orgApiKey;
309
517
  this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
518
+ assertSecureBaseUrl(this.baseUrl);
310
519
  this.timeout = config.timeout || 3e4;
311
520
  this.agents = new AgentsClient(this);
312
521
  this.wallets = new WalletsClient(this);
313
522
  this.cards = new AdminCardsClient(this);
314
523
  this.policies = new PoliciesClient(this);
315
524
  this.team = new TeamClient(this);
525
+ this.paymentRails = new PaymentRailsClient(this);
316
526
  }
317
527
  /** Make an authenticated request to the Conto API */
318
528
  async request(method, path, body) {
@@ -367,6 +577,22 @@ var ContoAdmin = class {
367
577
  }
368
578
  }
369
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
+ };
370
596
  var AdminCardsClient = class {
371
597
  constructor(client) {
372
598
  this.client = client;
@@ -485,44 +711,55 @@ var AgentsClient = class {
485
711
  }
486
712
  /** Get a single agent by ID */
487
713
  async get(agentId) {
488
- return this.client.request("GET", `/api/agents/${agentId}`);
714
+ return this.client.request("GET", `/api/agents/${encodeURIComponent(agentId)}`);
489
715
  }
490
716
  /** Update an agent */
491
717
  async update(agentId, input) {
492
718
  return this.client.request(
493
719
  "PATCH",
494
- `/api/agents/${agentId}`,
720
+ `/api/agents/${encodeURIComponent(agentId)}`,
495
721
  input
496
722
  );
497
723
  }
498
724
  /** Delete an agent (soft delete) */
499
725
  async delete(agentId) {
500
- return this.client.request("DELETE", `/api/agents/${agentId}`);
726
+ return this.client.request(
727
+ "DELETE",
728
+ `/api/agents/${encodeURIComponent(agentId)}`
729
+ );
501
730
  }
502
731
  /** Freeze an agent, blocking all transactions */
503
732
  async freeze(agentId, input) {
504
- return this.client.request("POST", `/api/agents/${agentId}/freeze`, {
505
- ...input.freezeWallets !== void 0 && { freezeWallets: input.freezeWallets }
506
- });
733
+ return this.client.request(
734
+ "POST",
735
+ `/api/agents/${encodeURIComponent(agentId)}/freeze`,
736
+ {
737
+ ...input.freezeWallets !== void 0 && { freezeWallets: input.freezeWallets }
738
+ }
739
+ );
507
740
  }
508
741
  /** Unfreeze an agent, restoring transaction ability */
509
742
  async unfreeze(agentId, input) {
510
- return this.client.request("POST", `/api/agents/${agentId}/unfreeze`, {
511
- ...input.unfreezeWallets !== void 0 && { unfreezeWallets: input.unfreezeWallets }
512
- });
743
+ return this.client.request(
744
+ "POST",
745
+ `/api/agents/${encodeURIComponent(agentId)}/unfreeze`,
746
+ {
747
+ ...input.unfreezeWallets !== void 0 && { unfreezeWallets: input.unfreezeWallets }
748
+ }
749
+ );
513
750
  }
514
751
  /** List wallets linked to an agent */
515
752
  async listWallets(agentId) {
516
753
  return this.client.request(
517
754
  "GET",
518
- `/api/agents/${agentId}/wallets`
755
+ `/api/agents/${encodeURIComponent(agentId)}/wallets`
519
756
  );
520
757
  }
521
758
  /** Link a wallet to an agent with spending limits */
522
759
  async linkWallet(agentId, input) {
523
760
  return this.client.request(
524
761
  "POST",
525
- `/api/agents/${agentId}/wallets`,
762
+ `/api/agents/${encodeURIComponent(agentId)}/wallets`,
526
763
  input
527
764
  );
528
765
  }
@@ -530,38 +767,45 @@ var AgentsClient = class {
530
767
  async unlinkWallet(agentId, walletId) {
531
768
  return this.client.request(
532
769
  "DELETE",
533
- `/api/agents/${agentId}/wallets/${walletId}`
770
+ `/api/agents/${encodeURIComponent(agentId)}/wallets/${encodeURIComponent(walletId)}`
534
771
  );
535
772
  }
536
773
  /** List policies assigned to an agent */
537
774
  async listPolicies(agentId) {
538
775
  return this.client.request(
539
776
  "GET",
540
- `/api/agents/${agentId}/policies`
777
+ `/api/agents/${encodeURIComponent(agentId)}/policies`
541
778
  );
542
779
  }
543
780
  /** Assign a policy to an agent */
544
781
  async assignPolicy(agentId, policyId) {
545
- return this.client.request("POST", `/api/agents/${agentId}/policies`, {
546
- policyId
547
- });
782
+ return this.client.request(
783
+ "POST",
784
+ `/api/agents/${encodeURIComponent(agentId)}/policies`,
785
+ {
786
+ policyId
787
+ }
788
+ );
548
789
  }
549
790
  /** Unassign a policy from an agent */
550
791
  async unassignPolicy(agentId, policyId) {
551
792
  return this.client.request(
552
793
  "DELETE",
553
- `/api/agents/${agentId}/policies?policyId=${policyId}`
794
+ `/api/agents/${encodeURIComponent(agentId)}/policies?policyId=${encodeURIComponent(policyId)}`
554
795
  );
555
796
  }
556
797
  /** List SDK keys for an agent */
557
798
  async listSdkKeys(agentId) {
558
- return this.client.request("GET", `/api/agents/${agentId}/sdk-keys`);
799
+ return this.client.request(
800
+ "GET",
801
+ `/api/agents/${encodeURIComponent(agentId)}/sdk-keys`
802
+ );
559
803
  }
560
804
  /** Create an SDK key for an agent (key value is returned only once) */
561
805
  async createSdkKey(agentId, input) {
562
806
  return this.client.request(
563
807
  "POST",
564
- `/api/agents/${agentId}/sdk-keys`,
808
+ `/api/agents/${encodeURIComponent(agentId)}/sdk-keys`,
565
809
  input
566
810
  );
567
811
  }
@@ -569,7 +813,7 @@ var AgentsClient = class {
569
813
  async revokeSdkKey(agentId, keyId) {
570
814
  return this.client.request(
571
815
  "DELETE",
572
- `/api/agents/${agentId}/sdk-keys?keyId=${keyId}`
816
+ `/api/agents/${encodeURIComponent(agentId)}/sdk-keys?keyId=${encodeURIComponent(keyId)}`
573
817
  );
574
818
  }
575
819
  };
@@ -598,25 +842,28 @@ var WalletsClient = class {
598
842
  }
599
843
  /** Get a single wallet by ID */
600
844
  async get(walletId) {
601
- return this.client.request("GET", `/api/wallets/${walletId}`);
845
+ return this.client.request("GET", `/api/wallets/${encodeURIComponent(walletId)}`);
602
846
  }
603
847
  /** Update a wallet */
604
848
  async update(walletId, input) {
605
849
  return this.client.request(
606
850
  "PATCH",
607
- `/api/wallets/${walletId}`,
851
+ `/api/wallets/${encodeURIComponent(walletId)}`,
608
852
  input
609
853
  );
610
854
  }
611
855
  /** Delete a wallet (cannot delete if linked to agents) */
612
856
  async delete(walletId) {
613
- return this.client.request("DELETE", `/api/wallets/${walletId}`);
857
+ return this.client.request(
858
+ "DELETE",
859
+ `/api/wallets/${encodeURIComponent(walletId)}`
860
+ );
614
861
  }
615
862
  /** Provision a wallet for onchain transactions */
616
863
  async provision(walletId) {
617
864
  return this.client.request(
618
865
  "POST",
619
- `/api/wallets/${walletId}/provision`,
866
+ `/api/wallets/${encodeURIComponent(walletId)}/provision`,
620
867
  {}
621
868
  );
622
869
  }
@@ -624,7 +871,7 @@ var WalletsClient = class {
624
871
  async refreshBalance(walletId) {
625
872
  return this.client.request(
626
873
  "POST",
627
- `/api/wallets/${walletId}/refresh-balance`,
874
+ `/api/wallets/${encodeURIComponent(walletId)}/refresh-balance`,
628
875
  {}
629
876
  );
630
877
  }
@@ -651,43 +898,53 @@ var PoliciesClient = class {
651
898
  }
652
899
  /** Get a single policy by ID */
653
900
  async get(policyId) {
654
- return this.client.request("GET", `/api/policies/${policyId}`);
901
+ return this.client.request("GET", `/api/policies/${encodeURIComponent(policyId)}`);
655
902
  }
656
903
  /** Update a policy */
657
904
  async update(policyId, input) {
658
905
  return this.client.request(
659
906
  "PATCH",
660
- `/api/policies/${policyId}`,
907
+ `/api/policies/${encodeURIComponent(policyId)}`,
661
908
  input
662
909
  );
663
910
  }
664
911
  /** Delete a policy */
665
912
  async delete(policyId) {
666
- return this.client.request("DELETE", `/api/policies/${policyId}`);
913
+ return this.client.request(
914
+ "DELETE",
915
+ `/api/policies/${encodeURIComponent(policyId)}`
916
+ );
667
917
  }
668
918
  /** List rules for a policy */
669
919
  async listRules(policyId) {
670
- return this.client.request("GET", `/api/policies/${policyId}/rules`);
920
+ return this.client.request(
921
+ "GET",
922
+ `/api/policies/${encodeURIComponent(policyId)}/rules`
923
+ );
671
924
  }
672
925
  /** Add a single rule to a policy */
673
926
  async addRule(policyId, rule) {
674
927
  return this.client.request(
675
928
  "POST",
676
- `/api/policies/${policyId}/rules`,
929
+ `/api/policies/${encodeURIComponent(policyId)}/rules`,
677
930
  rule
678
931
  );
679
932
  }
680
933
  /** Add multiple rules to a policy */
681
934
  async addRules(policyId, rules) {
682
- return this.client.request("POST", `/api/policies/${policyId}/rules`, {
683
- rules
684
- });
935
+ return this.client.request(
936
+ "POST",
937
+ `/api/policies/${encodeURIComponent(policyId)}/rules`,
938
+ {
939
+ rules
940
+ }
941
+ );
685
942
  }
686
943
  /** Update a single rule */
687
944
  async updateRule(policyId, ruleId, input) {
688
945
  return this.client.request(
689
946
  "PATCH",
690
- `/api/policies/${policyId}/rules/${ruleId}`,
947
+ `/api/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`,
691
948
  input
692
949
  );
693
950
  }
@@ -695,14 +952,51 @@ var PoliciesClient = class {
695
952
  async deleteRule(policyId, ruleId) {
696
953
  return this.client.request(
697
954
  "DELETE",
698
- `/api/policies/${policyId}/rules/${ruleId}`
955
+ `/api/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`
699
956
  );
700
957
  }
701
958
  /** Delete all rules for a policy */
702
959
  async deleteAllRules(policyId) {
703
960
  return this.client.request(
704
961
  "DELETE",
705
- `/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 ?? {}
706
1000
  );
707
1001
  }
708
1002
  };
@@ -728,6 +1022,19 @@ if (process.env.SENTRY_DSN) {
728
1022
  enableLogs: true
729
1023
  });
730
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
+ }
731
1038
  var ContoError = class extends Error {
732
1039
  constructor(message, metadata) {
733
1040
  super(message);
@@ -738,6 +1045,8 @@ var ContoError = class extends Error {
738
1045
  this.retryAfter = metadata.retryAfter;
739
1046
  this.retryGuidance = metadata.retryGuidance;
740
1047
  this.actionUrl = metadata.actionUrl;
1048
+ this.fieldErrors = metadata.fieldErrors;
1049
+ this.reasons = metadata.reasons;
741
1050
  }
742
1051
  };
743
1052
  var ExternalCardConfirmationPendingError = class extends ContoError {
@@ -753,8 +1062,22 @@ var ExternalCardConfirmationPendingError = class extends ContoError {
753
1062
  this.receipt = receipt;
754
1063
  }
755
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
+ };
756
1079
  function generatedIdempotencyKey(prefix) {
757
- return `${prefix}_${(0, import_node_crypto.randomUUID)()}`;
1080
+ return `${prefix}_${(0, import_node_crypto2.randomUUID)()}`;
758
1081
  }
759
1082
  function requireFinancialIdentifier(value, field) {
760
1083
  if (!value || value.trim().length === 0) {
@@ -782,6 +1105,24 @@ function withQuery(path, query) {
782
1105
  const queryString = searchParams.toString();
783
1106
  return queryString ? `${path}?${queryString}` : path;
784
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
+ }
785
1126
  var Conto = class {
786
1127
  constructor(config) {
787
1128
  if (!config.apiKey) {
@@ -792,13 +1133,290 @@ var Conto = class {
792
1133
  }
793
1134
  this.apiKey = config.apiKey;
794
1135
  this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
1136
+ assertSecureBaseUrl(this.baseUrl);
795
1137
  this.timeout = config.timeout || 3e4;
796
1138
  this.payments = new PaymentsClient(this);
797
1139
  this.cards = new CardsClient(this);
1140
+ this.connectedCards = new ConnectedCardsClient(this);
798
1141
  this.x402 = new X402Client(this);
799
1142
  this.mpp = new MppClient(this);
800
1143
  }
801
- /** Make an authenticated request to the Giro API */
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) {
1280
+ try {
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
+ });
1291
+ }
1292
+ throw error;
1293
+ }
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
+ };
1328
+ }
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 */
802
1420
  async request(method, path, body, options = {}) {
803
1421
  const url = `${this.baseUrl}${path}`;
804
1422
  const controller = new AbortController();
@@ -919,7 +1537,8 @@ var ControlledExternalCardPaymentTool = class {
919
1537
  this.executor = executor;
920
1538
  }
921
1539
  async pay(input) {
922
- const approval = await this.cardsClient.approve(input);
1540
+ const idempotencyKey = input.idempotencyKey.trim();
1541
+ const approval = await this.cardsClient.approve({ ...input, idempotencyKey });
923
1542
  if (!approval.approved || !approval.approvalToken) {
924
1543
  const executionAlreadyClaimed = approval.decision === "EXECUTING" || approval.decision === "CONFIRMING";
925
1544
  throw new ContoError("External card payment was not approved", {
@@ -952,6 +1571,7 @@ var ControlledExternalCardPaymentTool = class {
952
1571
  try {
953
1572
  execution = await this.executor({
954
1573
  requestId: approval.requestId,
1574
+ idempotencyKey,
955
1575
  cardId: input.cardId,
956
1576
  amount: input.amount,
957
1577
  currency: input.currency || "USD",
@@ -976,7 +1596,7 @@ var ControlledExternalCardPaymentTool = class {
976
1596
  Sentry2.captureException(outcomeError, {
977
1597
  extra: {
978
1598
  requestId: approval.requestId,
979
- executorError: error instanceof Error ? error.message : String(error)
1599
+ executorErrorName: error instanceof Error ? error.name : typeof error
980
1600
  }
981
1601
  });
982
1602
  throw outcomeError;
@@ -1000,7 +1620,7 @@ var ControlledExternalCardPaymentTool = class {
1000
1620
  Sentry2.captureException(pending, {
1001
1621
  extra: {
1002
1622
  requestId: approval.requestId,
1003
- confirmationError: error instanceof Error ? error.message : String(error)
1623
+ confirmationErrorName: error instanceof Error ? error.name : typeof error
1004
1624
  }
1005
1625
  });
1006
1626
  throw pending;
@@ -1012,10 +1632,101 @@ var ControlledExternalCardPaymentTool = class {
1012
1632
  };
1013
1633
  }
1014
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
+ };
1015
1708
  var PaymentsClient = class {
1016
1709
  constructor(client) {
1017
1710
  this.client = client;
1018
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
+ }
1019
1730
  /**
1020
1731
  * Request authorization for a payment.
1021
1732
  *
@@ -1045,6 +1756,13 @@ var PaymentsClient = class {
1045
1756
  * ```
1046
1757
  */
1047
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
+ }
1048
1766
  const requestInput = {
1049
1767
  ...input,
1050
1768
  idempotencyKey: input.idempotencyKey ?? generatedIdempotencyKey("conto_payment")
@@ -1112,7 +1830,7 @@ var PaymentsClient = class {
1112
1830
  async status(requestId) {
1113
1831
  return this.client.request(
1114
1832
  "GET",
1115
- `/api/sdk/payments/${requestId}`,
1833
+ `/api/sdk/payments/${encodeURIComponent(requestId)}`,
1116
1834
  void 0
1117
1835
  );
1118
1836
  }
@@ -1149,6 +1867,7 @@ var PaymentsClient = class {
1149
1867
  throw new ContoError("Payment was denied.", {
1150
1868
  code: "PAYMENT_DENIED",
1151
1869
  status: 403,
1870
+ reasons: customerPaymentReasons(request.reasons),
1152
1871
  requestId: request.requestId,
1153
1872
  retryGuidance: "Review the payment request before trying again."
1154
1873
  });
@@ -1166,6 +1885,7 @@ var PaymentsClient = class {
1166
1885
  code: "ACTION_REQUIRED",
1167
1886
  status: 402,
1168
1887
  requestId: request.requestId,
1888
+ actionUrl: request.actionUrl,
1169
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."
1170
1890
  });
1171
1891
  }
@@ -1267,12 +1987,18 @@ var MppClient = class {
1267
1987
  var index_default = Conto;
1268
1988
  // Annotate the CommonJS export names for ESM import in node:
1269
1989
  0 && (module.exports = {
1990
+ BrowserActionFinalizationPendingError,
1991
+ BrowserCheckoutTargetVerificationError,
1270
1992
  CardsClient,
1993
+ ConnectedCardsClient,
1271
1994
  Conto,
1272
1995
  ContoAdmin,
1273
1996
  ContoAdminError,
1274
1997
  ContoError,
1275
1998
  ControlledExternalCardPaymentTool,
1276
1999
  ExternalCardConfirmationPendingError,
1277
- Giro
2000
+ Giro,
2001
+ canonicalizeBrowserCheckoutTarget,
2002
+ createVerifiedBrowserCheckoutDriver,
2003
+ fingerprintBrowserCheckoutTarget
1278
2004
  });