@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/README.md +309 -3
- package/dist/browserbase.d.mts +139 -0
- package/dist/browserbase.d.ts +139 -0
- package/dist/browserbase.js +1980 -0
- package/dist/browserbase.mjs +604 -0
- package/dist/checkout-verifier.d.mts +95 -0
- package/dist/checkout-verifier.d.ts +95 -0
- package/dist/checkout-verifier.js +400 -0
- package/dist/checkout-verifier.mjs +372 -0
- package/dist/chunk-B7F5QLE5.mjs +34 -0
- package/dist/chunk-HM4EG5MH.mjs +1256 -0
- package/dist/chunk-O5RAAB2U.mjs +123 -0
- package/dist/index.d.mts +975 -4
- package/dist/index.d.ts +975 -4
- package/dist/index.js +777 -51
- package/dist/index.mjs +661 -77
- package/dist/playwright.d.mts +255 -0
- package/dist/playwright.d.ts +255 -0
- package/dist/playwright.js +1394 -0
- package/dist/playwright.mjs +22 -0
- package/package.json +31 -6
package/dist/index.mjs
CHANGED
|
@@ -1,39 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
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,6 +24,21 @@ 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: {
|
|
@@ -55,10 +51,11 @@ var require_package = __commonJS({
|
|
|
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
|
-
|
|
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",
|
|
62
59
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
63
60
|
"verify:release": "node scripts/verify-release.mjs",
|
|
64
61
|
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
@@ -78,18 +75,27 @@ var require_package = __commonJS({
|
|
|
78
75
|
url: "https://github.com/conto-finance/conto.git",
|
|
79
76
|
directory: "packages/sdk"
|
|
80
77
|
},
|
|
81
|
-
homepage: "https://conto.finance/docs/sdk",
|
|
78
|
+
homepage: "https://conto.finance/docs/sdk/installation",
|
|
82
79
|
bugs: {
|
|
83
|
-
|
|
80
|
+
email: "support@conto.finance"
|
|
84
81
|
},
|
|
85
82
|
dependencies: {
|
|
86
83
|
"@sentry/node": "^10.63.0"
|
|
87
84
|
},
|
|
85
|
+
peerDependencies: {
|
|
86
|
+
"playwright-core": ">=1.50.0"
|
|
87
|
+
},
|
|
88
|
+
peerDependenciesMeta: {
|
|
89
|
+
"playwright-core": {
|
|
90
|
+
optional: true
|
|
91
|
+
}
|
|
92
|
+
},
|
|
88
93
|
overrides: {
|
|
89
94
|
esbuild: "0.28.1"
|
|
90
95
|
},
|
|
91
96
|
devDependencies: {
|
|
92
97
|
"@types/node": "22.19.18",
|
|
98
|
+
"playwright-core": "1.61.0",
|
|
93
99
|
tsup: "^8.5.1",
|
|
94
100
|
typescript: "^5.9.3"
|
|
95
101
|
}
|
|
@@ -115,7 +121,66 @@ import * as Sentry2 from "@sentry/node";
|
|
|
115
121
|
import { randomUUID } from "crypto";
|
|
116
122
|
|
|
117
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
|
+
}
|
|
118
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
|
+
}
|
|
119
184
|
function abortError() {
|
|
120
185
|
const error = new Error("Request aborted");
|
|
121
186
|
error.name = "AbortError";
|
|
@@ -246,6 +311,7 @@ function responseErrorMetadata(response, data) {
|
|
|
246
311
|
return {
|
|
247
312
|
code,
|
|
248
313
|
status: response.status,
|
|
314
|
+
fieldErrors: customerFieldErrors(body, response.status),
|
|
249
315
|
requestId: safeRequestId(
|
|
250
316
|
response.headers.get("x-request-id"),
|
|
251
317
|
response.headers.get("x-correlation-id"),
|
|
@@ -288,12 +354,14 @@ var ContoAdmin = class {
|
|
|
288
354
|
}
|
|
289
355
|
this.orgApiKey = config.orgApiKey;
|
|
290
356
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
357
|
+
assertSecureBaseUrl(this.baseUrl);
|
|
291
358
|
this.timeout = config.timeout || 3e4;
|
|
292
359
|
this.agents = new AgentsClient(this);
|
|
293
360
|
this.wallets = new WalletsClient(this);
|
|
294
361
|
this.cards = new AdminCardsClient(this);
|
|
295
362
|
this.policies = new PoliciesClient(this);
|
|
296
363
|
this.team = new TeamClient(this);
|
|
364
|
+
this.paymentRails = new PaymentRailsClient(this);
|
|
297
365
|
}
|
|
298
366
|
/** Make an authenticated request to the Conto API */
|
|
299
367
|
async request(method, path, body) {
|
|
@@ -348,6 +416,22 @@ var ContoAdmin = class {
|
|
|
348
416
|
}
|
|
349
417
|
}
|
|
350
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
|
+
};
|
|
351
435
|
var AdminCardsClient = class {
|
|
352
436
|
constructor(client) {
|
|
353
437
|
this.client = client;
|
|
@@ -466,44 +550,55 @@ var AgentsClient = class {
|
|
|
466
550
|
}
|
|
467
551
|
/** Get a single agent by ID */
|
|
468
552
|
async get(agentId) {
|
|
469
|
-
return this.client.request("GET", `/api/agents/${agentId}`);
|
|
553
|
+
return this.client.request("GET", `/api/agents/${encodeURIComponent(agentId)}`);
|
|
470
554
|
}
|
|
471
555
|
/** Update an agent */
|
|
472
556
|
async update(agentId, input) {
|
|
473
557
|
return this.client.request(
|
|
474
558
|
"PATCH",
|
|
475
|
-
`/api/agents/${agentId}`,
|
|
559
|
+
`/api/agents/${encodeURIComponent(agentId)}`,
|
|
476
560
|
input
|
|
477
561
|
);
|
|
478
562
|
}
|
|
479
563
|
/** Delete an agent (soft delete) */
|
|
480
564
|
async delete(agentId) {
|
|
481
|
-
return this.client.request(
|
|
565
|
+
return this.client.request(
|
|
566
|
+
"DELETE",
|
|
567
|
+
`/api/agents/${encodeURIComponent(agentId)}`
|
|
568
|
+
);
|
|
482
569
|
}
|
|
483
570
|
/** Freeze an agent, blocking all transactions */
|
|
484
571
|
async freeze(agentId, input) {
|
|
485
|
-
return this.client.request(
|
|
486
|
-
|
|
487
|
-
|
|
572
|
+
return this.client.request(
|
|
573
|
+
"POST",
|
|
574
|
+
`/api/agents/${encodeURIComponent(agentId)}/freeze`,
|
|
575
|
+
{
|
|
576
|
+
...input.freezeWallets !== void 0 && { freezeWallets: input.freezeWallets }
|
|
577
|
+
}
|
|
578
|
+
);
|
|
488
579
|
}
|
|
489
580
|
/** Unfreeze an agent, restoring transaction ability */
|
|
490
581
|
async unfreeze(agentId, input) {
|
|
491
|
-
return this.client.request(
|
|
492
|
-
|
|
493
|
-
|
|
582
|
+
return this.client.request(
|
|
583
|
+
"POST",
|
|
584
|
+
`/api/agents/${encodeURIComponent(agentId)}/unfreeze`,
|
|
585
|
+
{
|
|
586
|
+
...input.unfreezeWallets !== void 0 && { unfreezeWallets: input.unfreezeWallets }
|
|
587
|
+
}
|
|
588
|
+
);
|
|
494
589
|
}
|
|
495
590
|
/** List wallets linked to an agent */
|
|
496
591
|
async listWallets(agentId) {
|
|
497
592
|
return this.client.request(
|
|
498
593
|
"GET",
|
|
499
|
-
`/api/agents/${agentId}/wallets`
|
|
594
|
+
`/api/agents/${encodeURIComponent(agentId)}/wallets`
|
|
500
595
|
);
|
|
501
596
|
}
|
|
502
597
|
/** Link a wallet to an agent with spending limits */
|
|
503
598
|
async linkWallet(agentId, input) {
|
|
504
599
|
return this.client.request(
|
|
505
600
|
"POST",
|
|
506
|
-
`/api/agents/${agentId}/wallets`,
|
|
601
|
+
`/api/agents/${encodeURIComponent(agentId)}/wallets`,
|
|
507
602
|
input
|
|
508
603
|
);
|
|
509
604
|
}
|
|
@@ -511,38 +606,45 @@ var AgentsClient = class {
|
|
|
511
606
|
async unlinkWallet(agentId, walletId) {
|
|
512
607
|
return this.client.request(
|
|
513
608
|
"DELETE",
|
|
514
|
-
`/api/agents/${agentId}/wallets/${walletId}`
|
|
609
|
+
`/api/agents/${encodeURIComponent(agentId)}/wallets/${encodeURIComponent(walletId)}`
|
|
515
610
|
);
|
|
516
611
|
}
|
|
517
612
|
/** List policies assigned to an agent */
|
|
518
613
|
async listPolicies(agentId) {
|
|
519
614
|
return this.client.request(
|
|
520
615
|
"GET",
|
|
521
|
-
`/api/agents/${agentId}/policies`
|
|
616
|
+
`/api/agents/${encodeURIComponent(agentId)}/policies`
|
|
522
617
|
);
|
|
523
618
|
}
|
|
524
619
|
/** Assign a policy to an agent */
|
|
525
620
|
async assignPolicy(agentId, policyId) {
|
|
526
|
-
return this.client.request(
|
|
527
|
-
|
|
528
|
-
|
|
621
|
+
return this.client.request(
|
|
622
|
+
"POST",
|
|
623
|
+
`/api/agents/${encodeURIComponent(agentId)}/policies`,
|
|
624
|
+
{
|
|
625
|
+
policyId
|
|
626
|
+
}
|
|
627
|
+
);
|
|
529
628
|
}
|
|
530
629
|
/** Unassign a policy from an agent */
|
|
531
630
|
async unassignPolicy(agentId, policyId) {
|
|
532
631
|
return this.client.request(
|
|
533
632
|
"DELETE",
|
|
534
|
-
`/api/agents/${agentId}/policies?policyId=${policyId}`
|
|
633
|
+
`/api/agents/${encodeURIComponent(agentId)}/policies?policyId=${encodeURIComponent(policyId)}`
|
|
535
634
|
);
|
|
536
635
|
}
|
|
537
636
|
/** List SDK keys for an agent */
|
|
538
637
|
async listSdkKeys(agentId) {
|
|
539
|
-
return this.client.request(
|
|
638
|
+
return this.client.request(
|
|
639
|
+
"GET",
|
|
640
|
+
`/api/agents/${encodeURIComponent(agentId)}/sdk-keys`
|
|
641
|
+
);
|
|
540
642
|
}
|
|
541
643
|
/** Create an SDK key for an agent (key value is returned only once) */
|
|
542
644
|
async createSdkKey(agentId, input) {
|
|
543
645
|
return this.client.request(
|
|
544
646
|
"POST",
|
|
545
|
-
`/api/agents/${agentId}/sdk-keys`,
|
|
647
|
+
`/api/agents/${encodeURIComponent(agentId)}/sdk-keys`,
|
|
546
648
|
input
|
|
547
649
|
);
|
|
548
650
|
}
|
|
@@ -550,7 +652,7 @@ var AgentsClient = class {
|
|
|
550
652
|
async revokeSdkKey(agentId, keyId) {
|
|
551
653
|
return this.client.request(
|
|
552
654
|
"DELETE",
|
|
553
|
-
`/api/agents/${agentId}/sdk-keys?keyId=${keyId}`
|
|
655
|
+
`/api/agents/${encodeURIComponent(agentId)}/sdk-keys?keyId=${encodeURIComponent(keyId)}`
|
|
554
656
|
);
|
|
555
657
|
}
|
|
556
658
|
};
|
|
@@ -579,25 +681,28 @@ var WalletsClient = class {
|
|
|
579
681
|
}
|
|
580
682
|
/** Get a single wallet by ID */
|
|
581
683
|
async get(walletId) {
|
|
582
|
-
return this.client.request("GET", `/api/wallets/${walletId}`);
|
|
684
|
+
return this.client.request("GET", `/api/wallets/${encodeURIComponent(walletId)}`);
|
|
583
685
|
}
|
|
584
686
|
/** Update a wallet */
|
|
585
687
|
async update(walletId, input) {
|
|
586
688
|
return this.client.request(
|
|
587
689
|
"PATCH",
|
|
588
|
-
`/api/wallets/${walletId}`,
|
|
690
|
+
`/api/wallets/${encodeURIComponent(walletId)}`,
|
|
589
691
|
input
|
|
590
692
|
);
|
|
591
693
|
}
|
|
592
694
|
/** Delete a wallet (cannot delete if linked to agents) */
|
|
593
695
|
async delete(walletId) {
|
|
594
|
-
return this.client.request(
|
|
696
|
+
return this.client.request(
|
|
697
|
+
"DELETE",
|
|
698
|
+
`/api/wallets/${encodeURIComponent(walletId)}`
|
|
699
|
+
);
|
|
595
700
|
}
|
|
596
701
|
/** Provision a wallet for onchain transactions */
|
|
597
702
|
async provision(walletId) {
|
|
598
703
|
return this.client.request(
|
|
599
704
|
"POST",
|
|
600
|
-
`/api/wallets/${walletId}/provision`,
|
|
705
|
+
`/api/wallets/${encodeURIComponent(walletId)}/provision`,
|
|
601
706
|
{}
|
|
602
707
|
);
|
|
603
708
|
}
|
|
@@ -605,7 +710,7 @@ var WalletsClient = class {
|
|
|
605
710
|
async refreshBalance(walletId) {
|
|
606
711
|
return this.client.request(
|
|
607
712
|
"POST",
|
|
608
|
-
`/api/wallets/${walletId}/refresh-balance`,
|
|
713
|
+
`/api/wallets/${encodeURIComponent(walletId)}/refresh-balance`,
|
|
609
714
|
{}
|
|
610
715
|
);
|
|
611
716
|
}
|
|
@@ -632,43 +737,53 @@ var PoliciesClient = class {
|
|
|
632
737
|
}
|
|
633
738
|
/** Get a single policy by ID */
|
|
634
739
|
async get(policyId) {
|
|
635
|
-
return this.client.request("GET", `/api/policies/${policyId}`);
|
|
740
|
+
return this.client.request("GET", `/api/policies/${encodeURIComponent(policyId)}`);
|
|
636
741
|
}
|
|
637
742
|
/** Update a policy */
|
|
638
743
|
async update(policyId, input) {
|
|
639
744
|
return this.client.request(
|
|
640
745
|
"PATCH",
|
|
641
|
-
`/api/policies/${policyId}`,
|
|
746
|
+
`/api/policies/${encodeURIComponent(policyId)}`,
|
|
642
747
|
input
|
|
643
748
|
);
|
|
644
749
|
}
|
|
645
750
|
/** Delete a policy */
|
|
646
751
|
async delete(policyId) {
|
|
647
|
-
return this.client.request(
|
|
752
|
+
return this.client.request(
|
|
753
|
+
"DELETE",
|
|
754
|
+
`/api/policies/${encodeURIComponent(policyId)}`
|
|
755
|
+
);
|
|
648
756
|
}
|
|
649
757
|
/** List rules for a policy */
|
|
650
758
|
async listRules(policyId) {
|
|
651
|
-
return this.client.request(
|
|
759
|
+
return this.client.request(
|
|
760
|
+
"GET",
|
|
761
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules`
|
|
762
|
+
);
|
|
652
763
|
}
|
|
653
764
|
/** Add a single rule to a policy */
|
|
654
765
|
async addRule(policyId, rule) {
|
|
655
766
|
return this.client.request(
|
|
656
767
|
"POST",
|
|
657
|
-
`/api/policies/${policyId}/rules`,
|
|
768
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules`,
|
|
658
769
|
rule
|
|
659
770
|
);
|
|
660
771
|
}
|
|
661
772
|
/** Add multiple rules to a policy */
|
|
662
773
|
async addRules(policyId, rules) {
|
|
663
|
-
return this.client.request(
|
|
664
|
-
|
|
665
|
-
|
|
774
|
+
return this.client.request(
|
|
775
|
+
"POST",
|
|
776
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules`,
|
|
777
|
+
{
|
|
778
|
+
rules
|
|
779
|
+
}
|
|
780
|
+
);
|
|
666
781
|
}
|
|
667
782
|
/** Update a single rule */
|
|
668
783
|
async updateRule(policyId, ruleId, input) {
|
|
669
784
|
return this.client.request(
|
|
670
785
|
"PATCH",
|
|
671
|
-
`/api/policies/${policyId}/rules/${ruleId}`,
|
|
786
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`,
|
|
672
787
|
input
|
|
673
788
|
);
|
|
674
789
|
}
|
|
@@ -676,14 +791,51 @@ var PoliciesClient = class {
|
|
|
676
791
|
async deleteRule(policyId, ruleId) {
|
|
677
792
|
return this.client.request(
|
|
678
793
|
"DELETE",
|
|
679
|
-
`/api/policies/${policyId}/rules/${ruleId}`
|
|
794
|
+
`/api/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`
|
|
680
795
|
);
|
|
681
796
|
}
|
|
682
797
|
/** Delete all rules for a policy */
|
|
683
798
|
async deleteAllRules(policyId) {
|
|
684
799
|
return this.client.request(
|
|
685
800
|
"DELETE",
|
|
686
|
-
`/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 ?? {}
|
|
687
839
|
);
|
|
688
840
|
}
|
|
689
841
|
};
|
|
@@ -709,6 +861,19 @@ if (process.env.SENTRY_DSN) {
|
|
|
709
861
|
enableLogs: true
|
|
710
862
|
});
|
|
711
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
|
+
}
|
|
712
877
|
var ContoError = class extends Error {
|
|
713
878
|
constructor(message, metadata) {
|
|
714
879
|
super(message);
|
|
@@ -719,6 +884,8 @@ var ContoError = class extends Error {
|
|
|
719
884
|
this.retryAfter = metadata.retryAfter;
|
|
720
885
|
this.retryGuidance = metadata.retryGuidance;
|
|
721
886
|
this.actionUrl = metadata.actionUrl;
|
|
887
|
+
this.fieldErrors = metadata.fieldErrors;
|
|
888
|
+
this.reasons = metadata.reasons;
|
|
722
889
|
}
|
|
723
890
|
};
|
|
724
891
|
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
@@ -734,6 +901,20 @@ var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
|
734
901
|
this.receipt = receipt;
|
|
735
902
|
}
|
|
736
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
|
+
};
|
|
737
918
|
function generatedIdempotencyKey(prefix) {
|
|
738
919
|
return `${prefix}_${randomUUID()}`;
|
|
739
920
|
}
|
|
@@ -763,6 +944,24 @@ function withQuery(path, query) {
|
|
|
763
944
|
const queryString = searchParams.toString();
|
|
764
945
|
return queryString ? `${path}?${queryString}` : path;
|
|
765
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
|
+
}
|
|
766
965
|
var Conto = class {
|
|
767
966
|
constructor(config) {
|
|
768
967
|
if (!config.apiKey) {
|
|
@@ -773,13 +972,290 @@ var Conto = class {
|
|
|
773
972
|
}
|
|
774
973
|
this.apiKey = config.apiKey;
|
|
775
974
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
975
|
+
assertSecureBaseUrl(this.baseUrl);
|
|
776
976
|
this.timeout = config.timeout || 3e4;
|
|
777
977
|
this.payments = new PaymentsClient(this);
|
|
778
978
|
this.cards = new CardsClient(this);
|
|
979
|
+
this.connectedCards = new ConnectedCardsClient(this);
|
|
779
980
|
this.x402 = new X402Client(this);
|
|
780
981
|
this.mpp = new MppClient(this);
|
|
781
982
|
}
|
|
782
|
-
/**
|
|
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
|
+
}
|
|
1073
|
+
}
|
|
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) {
|
|
1119
|
+
try {
|
|
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 {
|
|
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
|
+
});
|
|
1146
|
+
}
|
|
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
|
+
};
|
|
1167
|
+
}
|
|
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 */
|
|
783
1259
|
async request(method, path, body, options = {}) {
|
|
784
1260
|
const url = `${this.baseUrl}${path}`;
|
|
785
1261
|
const controller = new AbortController();
|
|
@@ -900,7 +1376,8 @@ var ControlledExternalCardPaymentTool = class {
|
|
|
900
1376
|
this.executor = executor;
|
|
901
1377
|
}
|
|
902
1378
|
async pay(input) {
|
|
903
|
-
const
|
|
1379
|
+
const idempotencyKey = input.idempotencyKey.trim();
|
|
1380
|
+
const approval = await this.cardsClient.approve({ ...input, idempotencyKey });
|
|
904
1381
|
if (!approval.approved || !approval.approvalToken) {
|
|
905
1382
|
const executionAlreadyClaimed = approval.decision === "EXECUTING" || approval.decision === "CONFIRMING";
|
|
906
1383
|
throw new ContoError("External card payment was not approved", {
|
|
@@ -933,6 +1410,7 @@ var ControlledExternalCardPaymentTool = class {
|
|
|
933
1410
|
try {
|
|
934
1411
|
execution = await this.executor({
|
|
935
1412
|
requestId: approval.requestId,
|
|
1413
|
+
idempotencyKey,
|
|
936
1414
|
cardId: input.cardId,
|
|
937
1415
|
amount: input.amount,
|
|
938
1416
|
currency: input.currency || "USD",
|
|
@@ -957,7 +1435,7 @@ var ControlledExternalCardPaymentTool = class {
|
|
|
957
1435
|
Sentry2.captureException(outcomeError, {
|
|
958
1436
|
extra: {
|
|
959
1437
|
requestId: approval.requestId,
|
|
960
|
-
|
|
1438
|
+
executorErrorName: error instanceof Error ? error.name : typeof error
|
|
961
1439
|
}
|
|
962
1440
|
});
|
|
963
1441
|
throw outcomeError;
|
|
@@ -981,7 +1459,7 @@ var ControlledExternalCardPaymentTool = class {
|
|
|
981
1459
|
Sentry2.captureException(pending, {
|
|
982
1460
|
extra: {
|
|
983
1461
|
requestId: approval.requestId,
|
|
984
|
-
|
|
1462
|
+
confirmationErrorName: error instanceof Error ? error.name : typeof error
|
|
985
1463
|
}
|
|
986
1464
|
});
|
|
987
1465
|
throw pending;
|
|
@@ -993,10 +1471,101 @@ var ControlledExternalCardPaymentTool = class {
|
|
|
993
1471
|
};
|
|
994
1472
|
}
|
|
995
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
|
+
});
|
|
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");
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
996
1547
|
var PaymentsClient = class {
|
|
997
1548
|
constructor(client) {
|
|
998
1549
|
this.client = client;
|
|
999
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
|
+
}
|
|
1000
1569
|
/**
|
|
1001
1570
|
* Request authorization for a payment.
|
|
1002
1571
|
*
|
|
@@ -1026,6 +1595,13 @@ var PaymentsClient = class {
|
|
|
1026
1595
|
* ```
|
|
1027
1596
|
*/
|
|
1028
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
|
+
}
|
|
1029
1605
|
const requestInput = {
|
|
1030
1606
|
...input,
|
|
1031
1607
|
idempotencyKey: input.idempotencyKey ?? generatedIdempotencyKey("conto_payment")
|
|
@@ -1093,7 +1669,7 @@ var PaymentsClient = class {
|
|
|
1093
1669
|
async status(requestId) {
|
|
1094
1670
|
return this.client.request(
|
|
1095
1671
|
"GET",
|
|
1096
|
-
`/api/sdk/payments/${requestId}`,
|
|
1672
|
+
`/api/sdk/payments/${encodeURIComponent(requestId)}`,
|
|
1097
1673
|
void 0
|
|
1098
1674
|
);
|
|
1099
1675
|
}
|
|
@@ -1130,6 +1706,7 @@ var PaymentsClient = class {
|
|
|
1130
1706
|
throw new ContoError("Payment was denied.", {
|
|
1131
1707
|
code: "PAYMENT_DENIED",
|
|
1132
1708
|
status: 403,
|
|
1709
|
+
reasons: customerPaymentReasons(request.reasons),
|
|
1133
1710
|
requestId: request.requestId,
|
|
1134
1711
|
retryGuidance: "Review the payment request before trying again."
|
|
1135
1712
|
});
|
|
@@ -1147,6 +1724,7 @@ var PaymentsClient = class {
|
|
|
1147
1724
|
code: "ACTION_REQUIRED",
|
|
1148
1725
|
status: 402,
|
|
1149
1726
|
requestId: request.requestId,
|
|
1727
|
+
actionUrl: request.actionUrl,
|
|
1150
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."
|
|
1151
1729
|
});
|
|
1152
1730
|
}
|
|
@@ -1247,7 +1825,10 @@ var MppClient = class {
|
|
|
1247
1825
|
};
|
|
1248
1826
|
var index_default = Conto;
|
|
1249
1827
|
export {
|
|
1828
|
+
BrowserActionFinalizationPendingError,
|
|
1829
|
+
BrowserCheckoutTargetVerificationError,
|
|
1250
1830
|
CardsClient,
|
|
1831
|
+
ConnectedCardsClient,
|
|
1251
1832
|
Conto,
|
|
1252
1833
|
ContoAdmin,
|
|
1253
1834
|
ContoAdminError,
|
|
@@ -1255,5 +1836,8 @@ export {
|
|
|
1255
1836
|
ControlledExternalCardPaymentTool,
|
|
1256
1837
|
ExternalCardConfirmationPendingError,
|
|
1257
1838
|
Conto as Giro,
|
|
1258
|
-
|
|
1839
|
+
canonicalizeBrowserCheckoutTarget,
|
|
1840
|
+
createVerifiedBrowserCheckoutDriver,
|
|
1841
|
+
index_default as default,
|
|
1842
|
+
fingerprintBrowserCheckoutTarget
|
|
1259
1843
|
};
|