@conto_finance/sdk 0.7.1 → 0.8.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 +106 -8
- package/dist/index.d.mts +356 -10
- package/dist/index.d.ts +356 -10
- package/dist/index.js +469 -73
- package/dist/index.mjs +467 -73
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -22,7 +22,7 @@ var require_package = __commonJS({
|
|
|
22
22
|
"package.json"(exports, module) {
|
|
23
23
|
module.exports = {
|
|
24
24
|
name: "@conto_finance/sdk",
|
|
25
|
-
version: "0.
|
|
25
|
+
version: "0.8.0",
|
|
26
26
|
description: "Conto SDK for AI agent payment authorization",
|
|
27
27
|
main: "./dist/index.js",
|
|
28
28
|
module: "./dist/index.mjs",
|
|
@@ -68,7 +68,7 @@ var require_package = __commonJS({
|
|
|
68
68
|
dev: "tsup --watch",
|
|
69
69
|
lint: "tsc --noEmit",
|
|
70
70
|
test: "npm run lint && npm run test:contract && npm run test:consumer",
|
|
71
|
-
"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 test/checkout.test.mjs test/link-wallet.test.mjs test/purchases.test.mjs test/allowances.test.mjs test/purchase-watch-lifecycle.test.mjs ../../examples/link-wallet-agent/verify.test.mjs",
|
|
71
|
+
"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 test/checkout.test.mjs test/link-wallet.test.mjs test/purchases.test.mjs test/allowances.test.mjs test/purchase-watch-lifecycle.test.mjs test/spending.test.mjs ../../examples/link-wallet-agent/verify.test.mjs",
|
|
72
72
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
73
73
|
"verify:release": "node scripts/verify-release.mjs",
|
|
74
74
|
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
@@ -198,9 +198,9 @@ function watchRead(pending, signal) {
|
|
|
198
198
|
pending.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
|
-
function requirePurchaseId(
|
|
202
|
-
if (!/^[a-z0-9]{8,64}$/i.test(
|
|
203
|
-
return encodeURIComponent(
|
|
201
|
+
function requirePurchaseId(id2) {
|
|
202
|
+
if (!/^[a-z0-9]{8,64}$/i.test(id2)) throw new TypeError("purchase id is not valid");
|
|
203
|
+
return encodeURIComponent(id2);
|
|
204
204
|
}
|
|
205
205
|
function requireCycleKey(key) {
|
|
206
206
|
if (!/^[A-Za-z0-9._:-]{1,100}$/.test(key)) throw new TypeError("cycleKey is not valid");
|
|
@@ -220,24 +220,24 @@ var RecurringPurchasesClient = class {
|
|
|
220
220
|
buyerId ? `${__privateGet(this, _base)}?buyerId=${encodeURIComponent(buyerId)}` : __privateGet(this, _base)
|
|
221
221
|
);
|
|
222
222
|
}
|
|
223
|
-
async get(
|
|
224
|
-
return __privateGet(this, _transport).request("GET", `${__privateGet(this, _base)}/${requirePurchaseId(
|
|
223
|
+
async get(id2) {
|
|
224
|
+
return __privateGet(this, _transport).request("GET", `${__privateGet(this, _base)}/${requirePurchaseId(id2)}`);
|
|
225
225
|
}
|
|
226
226
|
/** Starts one order. The same cycle key returns the same order. */
|
|
227
|
-
async startCycle(
|
|
227
|
+
async startCycle(id2, cycleKey) {
|
|
228
228
|
requireCycleKey(cycleKey);
|
|
229
229
|
return __privateGet(this, _transport).request(
|
|
230
230
|
"POST",
|
|
231
|
-
`${__privateGet(this, _base)}/${requirePurchaseId(
|
|
231
|
+
`${__privateGet(this, _base)}/${requirePurchaseId(id2)}/cycles`,
|
|
232
232
|
{ cycleKey },
|
|
233
233
|
{ idempotent: true }
|
|
234
234
|
);
|
|
235
235
|
}
|
|
236
236
|
/** Skips one order, also before it starts. */
|
|
237
|
-
async skipCycle(
|
|
237
|
+
async skipCycle(id2, cycleKey) {
|
|
238
238
|
return __privateGet(this, _transport).request(
|
|
239
239
|
"POST",
|
|
240
|
-
`${__privateGet(this, _base)}/${requirePurchaseId(
|
|
240
|
+
`${__privateGet(this, _base)}/${requirePurchaseId(id2)}/cycles/${requireCycleKey(cycleKey)}/skip`
|
|
241
241
|
);
|
|
242
242
|
}
|
|
243
243
|
};
|
|
@@ -251,10 +251,10 @@ var OrganizationRecurringPurchasesClient = class extends RecurringPurchasesClien
|
|
|
251
251
|
__privateSet(this, _client, client);
|
|
252
252
|
}
|
|
253
253
|
/** Stops a repeat order. An order waiting for its payment time closes unpaid. */
|
|
254
|
-
async cancel(
|
|
254
|
+
async cancel(id2) {
|
|
255
255
|
return __privateGet(this, _client).request(
|
|
256
256
|
"POST",
|
|
257
|
-
`/api/purchasing/recurring-purchases/${requirePurchaseId(
|
|
257
|
+
`/api/purchasing/recurring-purchases/${requirePurchaseId(id2)}/cancel`
|
|
258
258
|
);
|
|
259
259
|
}
|
|
260
260
|
};
|
|
@@ -291,36 +291,36 @@ var PurchasesClient = class {
|
|
|
291
291
|
const suffix = query.toString();
|
|
292
292
|
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}${suffix ? `?${suffix}` : ""}`);
|
|
293
293
|
}
|
|
294
|
-
async get(
|
|
295
|
-
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}/${requirePurchaseId(
|
|
294
|
+
async get(id2) {
|
|
295
|
+
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}/${requirePurchaseId(id2)}`);
|
|
296
296
|
}
|
|
297
297
|
/**
|
|
298
298
|
* Answers an open action whose authority is `agent_or_buyer` with the fields
|
|
299
299
|
* it lists. The acknowledgment means recorded; the purchase shows the result.
|
|
300
300
|
* The same answer again returns the same acknowledgment.
|
|
301
301
|
*/
|
|
302
|
-
async respond(
|
|
302
|
+
async respond(id2, input) {
|
|
303
303
|
return __privateGet(this, _transport2).request(
|
|
304
304
|
"POST",
|
|
305
|
-
`${__privateGet(this, _base2)}/${requirePurchaseId(
|
|
305
|
+
`${__privateGet(this, _base2)}/${requirePurchaseId(id2)}/responses`,
|
|
306
306
|
input,
|
|
307
307
|
{ idempotent: true }
|
|
308
308
|
);
|
|
309
309
|
}
|
|
310
|
-
async events(
|
|
310
|
+
async events(id2, params = {}) {
|
|
311
311
|
const query = new URLSearchParams({ after: String(params.after ?? 0) });
|
|
312
312
|
if (params.limit) query.set("limit", String(params.limit));
|
|
313
|
-
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}/${requirePurchaseId(
|
|
313
|
+
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}/${requirePurchaseId(id2)}/events?${query}`);
|
|
314
314
|
}
|
|
315
315
|
/**
|
|
316
316
|
* Requests cancellation. Before payment the purchase stops. Once payment may
|
|
317
317
|
* have been submitted, `cancellationOutcome` is `too_late` and the purchase
|
|
318
318
|
* stays in reconciliation. Nothing is refunded by this call.
|
|
319
319
|
*/
|
|
320
|
-
async cancel(
|
|
320
|
+
async cancel(id2) {
|
|
321
321
|
return __privateGet(this, _transport2).request(
|
|
322
322
|
"POST",
|
|
323
|
-
`${__privateGet(this, _base2)}/${requirePurchaseId(
|
|
323
|
+
`${__privateGet(this, _base2)}/${requirePurchaseId(id2)}/cancel`,
|
|
324
324
|
void 0,
|
|
325
325
|
{ idempotent: true }
|
|
326
326
|
);
|
|
@@ -332,8 +332,8 @@ var PurchasesClient = class {
|
|
|
332
332
|
* after a reconnect is dropped. A `purchase.resync` event carries the
|
|
333
333
|
* current purchase when the cursor could not be resumed.
|
|
334
334
|
*/
|
|
335
|
-
async *watch(
|
|
336
|
-
const path = `${__privateGet(this, _base2)}/${requirePurchaseId(
|
|
335
|
+
async *watch(id2, options = {}) {
|
|
336
|
+
const path = `${__privateGet(this, _base2)}/${requirePurchaseId(id2)}`;
|
|
337
337
|
const mode = options.mode ?? "auto";
|
|
338
338
|
const interval = Math.max(500, options.pollIntervalMs ?? 2e3);
|
|
339
339
|
if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs < 0 || options.maxWaitMs > 2147483647))
|
|
@@ -398,10 +398,10 @@ var PurchasesClient = class {
|
|
|
398
398
|
}
|
|
399
399
|
let page;
|
|
400
400
|
try {
|
|
401
|
-
page = await watchRead(this.events(
|
|
401
|
+
page = await watchRead(this.events(id2, { after: cursor, limit: 100 }), signal);
|
|
402
402
|
} catch (error) {
|
|
403
403
|
if (codeOf(error) !== "CURSOR_INVALID") throw error;
|
|
404
|
-
const purchase = await watchRead(this.get(
|
|
404
|
+
const purchase = await watchRead(this.get(id2), signal);
|
|
405
405
|
cursor = purchase.version;
|
|
406
406
|
yield {
|
|
407
407
|
id: `resync:${purchase.version}`,
|
|
@@ -456,10 +456,10 @@ var OrganizationPurchasesClient = class extends PurchasesClient {
|
|
|
456
456
|
return __privateGet(this, _client2).request("GET", `/api/purchasing/purchases${suffix ? `?${suffix}` : ""}`);
|
|
457
457
|
}
|
|
458
458
|
/** A single-use link that opens the purchase's buyer actions on Conto's hosted page. */
|
|
459
|
-
createActionLink(
|
|
459
|
+
createActionLink(id2, input = {}) {
|
|
460
460
|
return __privateGet(this, _client2).request(
|
|
461
461
|
"POST",
|
|
462
|
-
`/api/purchasing/purchases/${requirePurchaseId(
|
|
462
|
+
`/api/purchasing/purchases/${requirePurchaseId(id2)}/action-sessions`,
|
|
463
463
|
input
|
|
464
464
|
);
|
|
465
465
|
}
|
|
@@ -476,52 +476,52 @@ var AdminPurchasingClient = class {
|
|
|
476
476
|
"GET",
|
|
477
477
|
`/api/purchasing/allowances?${new URLSearchParams({ buyerId: params.buyerId })}`
|
|
478
478
|
),
|
|
479
|
-
get: (
|
|
479
|
+
get: (id2) => __privateGet(this, _client3).request("GET", `/api/purchasing/allowances/${enc(id2)}`),
|
|
480
480
|
/** Stops future credential release; does not undo payments already submitted. */
|
|
481
|
-
revoke: (
|
|
481
|
+
revoke: (id2) => __privateGet(this, _client3).request(
|
|
482
482
|
"POST",
|
|
483
|
-
`/api/purchasing/allowances/${enc(
|
|
483
|
+
`/api/purchasing/allowances/${enc(id2)}/revoke`
|
|
484
484
|
)
|
|
485
485
|
};
|
|
486
486
|
this.buyers = {
|
|
487
487
|
/** Maps one of your users to a buyer with its own agent, or returns the existing one. */
|
|
488
488
|
create: (input) => __privateGet(this, _client3).request("POST", "/api/purchasing/buyers", input),
|
|
489
|
-
get: (
|
|
489
|
+
get: (id2) => __privateGet(this, _client3).request("GET", `/api/purchasing/buyers/${enc(id2)}`),
|
|
490
490
|
list: (params = {}) => __privateGet(this, _client3).request("GET", `/api/purchasing/buyers?limit=${params.limit ?? 20}&offset=${params.offset ?? 0}`),
|
|
491
491
|
/** Stops the buyer's agent. Purchases that may already be paid stay visible. */
|
|
492
|
-
stop: (
|
|
492
|
+
stop: (id2) => __privateGet(this, _client3).request("POST", `/api/purchasing/buyers/${enc(id2)}/stop`),
|
|
493
493
|
/**
|
|
494
494
|
* Lets the buyer pay with your company card within limits you set for
|
|
495
495
|
* them, or replaces those limits. The buyer must not have their own card.
|
|
496
496
|
*/
|
|
497
|
-
setCompanyCard: (
|
|
497
|
+
setCompanyCard: (id2, input) => __privateGet(this, _client3).request(
|
|
498
498
|
"PUT",
|
|
499
|
-
`/api/purchasing/buyers/${enc(
|
|
499
|
+
`/api/purchasing/buyers/${enc(id2)}/company-card`,
|
|
500
500
|
input
|
|
501
501
|
),
|
|
502
502
|
/** The buyer stops paying with your company card. */
|
|
503
|
-
removeCompanyCard: (
|
|
503
|
+
removeCompanyCard: (id2) => __privateGet(this, _client3).request(
|
|
504
504
|
"DELETE",
|
|
505
|
-
`/api/purchasing/buyers/${enc(
|
|
505
|
+
`/api/purchasing/buyers/${enc(id2)}/company-card`
|
|
506
506
|
),
|
|
507
507
|
/**
|
|
508
508
|
* Replaces what the buyer has at home: inventory, usuals, preferences,
|
|
509
509
|
* chosen recipes and their list. The whole snapshot, every time. Needs
|
|
510
510
|
* Conto to enable kitchen context for your organization.
|
|
511
511
|
*/
|
|
512
|
-
setKitchenContext: (
|
|
512
|
+
setKitchenContext: (id2, context) => __privateGet(this, _client3).request(
|
|
513
513
|
"PUT",
|
|
514
|
-
`/api/purchasing/buyers/${enc(
|
|
514
|
+
`/api/purchasing/buyers/${enc(id2)}/context`,
|
|
515
515
|
context
|
|
516
516
|
),
|
|
517
517
|
/** The snapshot Conto holds, with context null when nothing was pushed. */
|
|
518
|
-
getKitchenContext: (
|
|
518
|
+
getKitchenContext: (id2) => __privateGet(this, _client3).request(
|
|
519
519
|
"GET",
|
|
520
|
-
`/api/purchasing/buyers/${enc(
|
|
520
|
+
`/api/purchasing/buyers/${enc(id2)}/context`
|
|
521
521
|
),
|
|
522
|
-
clearKitchenContext: (
|
|
522
|
+
clearKitchenContext: (id2) => __privateGet(this, _client3).request(
|
|
523
523
|
"DELETE",
|
|
524
|
-
`/api/purchasing/buyers/${enc(
|
|
524
|
+
`/api/purchasing/buyers/${enc(id2)}/context`
|
|
525
525
|
)
|
|
526
526
|
};
|
|
527
527
|
/** Cards your organization pays its buyers' purchases with. Test cards only for now. */
|
|
@@ -531,17 +531,17 @@ var AdminPurchasingClient = class {
|
|
|
531
531
|
"GET",
|
|
532
532
|
"/api/purchasing/company-cards"
|
|
533
533
|
),
|
|
534
|
-
get: (
|
|
534
|
+
get: (id2) => __privateGet(this, _client3).request("GET", `/api/purchasing/company-cards/${enc(id2)}`),
|
|
535
535
|
/** Changes the card's own caps. Each buyer's limits stay as they are. */
|
|
536
|
-
update: (
|
|
536
|
+
update: (id2, input) => __privateGet(this, _client3).request(
|
|
537
537
|
"PATCH",
|
|
538
|
-
`/api/purchasing/company-cards/${enc(
|
|
538
|
+
`/api/purchasing/company-cards/${enc(id2)}`,
|
|
539
539
|
input
|
|
540
540
|
),
|
|
541
541
|
/** No buyer can start or pay a purchase with the card after this. */
|
|
542
|
-
remove: (
|
|
542
|
+
remove: (id2) => __privateGet(this, _client3).request(
|
|
543
543
|
"DELETE",
|
|
544
|
-
`/api/purchasing/company-cards/${enc(
|
|
544
|
+
`/api/purchasing/company-cards/${enc(id2)}`
|
|
545
545
|
)
|
|
546
546
|
};
|
|
547
547
|
this.paymentMethods = {
|
|
@@ -555,9 +555,9 @@ var AdminPurchasingClient = class {
|
|
|
555
555
|
"GET",
|
|
556
556
|
`/api/purchasing/payment-methods?buyerId=${enc(buyerId)}`
|
|
557
557
|
),
|
|
558
|
-
remove: (
|
|
558
|
+
remove: (id2) => __privateGet(this, _client3).request(
|
|
559
559
|
"DELETE",
|
|
560
|
-
`/api/purchasing/payment-methods/${enc(
|
|
560
|
+
`/api/purchasing/payment-methods/${enc(id2)}`
|
|
561
561
|
)
|
|
562
562
|
};
|
|
563
563
|
this.delegations = {
|
|
@@ -565,10 +565,10 @@ var AdminPurchasingClient = class {
|
|
|
565
565
|
"GET",
|
|
566
566
|
`/api/purchasing/delegations?buyerId=${enc(buyerId)}`
|
|
567
567
|
),
|
|
568
|
-
get: (
|
|
569
|
-
revoke: (
|
|
568
|
+
get: (id2) => __privateGet(this, _client3).request("GET", `/api/purchasing/delegations/${enc(id2)}`),
|
|
569
|
+
revoke: (id2) => __privateGet(this, _client3).request(
|
|
570
570
|
"POST",
|
|
571
|
-
`/api/purchasing/delegations/${enc(
|
|
571
|
+
`/api/purchasing/delegations/${enc(id2)}/revoke`
|
|
572
572
|
)
|
|
573
573
|
};
|
|
574
574
|
this.settings = {
|
|
@@ -598,6 +598,204 @@ function verifyPurchaseWebhook(input) {
|
|
|
598
598
|
return payload;
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
+
// src/spending.ts
|
|
602
|
+
var id = encodeURIComponent;
|
|
603
|
+
var SpendingClient = class {
|
|
604
|
+
constructor(client) {
|
|
605
|
+
this.client = client;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* CSV of this agent's purchases against budgets in its own organization. Fails with `reasonCode`
|
|
609
|
+
* `EXPORT_TOO_LARGE` above 10,000 purchases.
|
|
610
|
+
*/
|
|
611
|
+
exportPurchases() {
|
|
612
|
+
return this.client.download("/api/sdk/spending/purchases/export");
|
|
613
|
+
}
|
|
614
|
+
downloadEvidence(purchaseId, evidenceId) {
|
|
615
|
+
return this.client.download(
|
|
616
|
+
`/api/sdk/spending/purchases/${id(purchaseId)}/evidence/${id(evidenceId)}`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
/** The budgets that cover this agent, with what is held, settled and available. */
|
|
620
|
+
budgets() {
|
|
621
|
+
return this.client.request("GET", "/api/sdk/budgets");
|
|
622
|
+
}
|
|
623
|
+
tasks(cursor) {
|
|
624
|
+
return this.client.request(
|
|
625
|
+
"GET",
|
|
626
|
+
`/api/sdk/tasks${cursor ? `?cursor=${id(cursor)}` : ""}`
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Ask the budget holder to approve a task budget. Once it is approved, pass its id as
|
|
631
|
+
* `context.taskRequestId` on `payments.request()` from a managed wallet, or as `taskRequestId` on
|
|
632
|
+
* `connectedCards.draw()`. A task's budget applies only to a payment or draw that names the task,
|
|
633
|
+
* and it stops nothing on other paths.
|
|
634
|
+
*/
|
|
635
|
+
requestTask(input) {
|
|
636
|
+
return this.client.request(
|
|
637
|
+
"POST",
|
|
638
|
+
"/api/sdk/tasks",
|
|
639
|
+
{ ...input }
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
/** This agent's purchases against budgets in its own organization. Empty when none has applied. */
|
|
643
|
+
purchases(cursor) {
|
|
644
|
+
return this.client.request(
|
|
645
|
+
"GET",
|
|
646
|
+
`/api/sdk/spending/purchases${cursor ? `?cursor=${id(cursor)}` : ""}`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
purchase(purchaseId) {
|
|
650
|
+
return this.client.request(
|
|
651
|
+
"GET",
|
|
652
|
+
`/api/sdk/spending/purchases/${id(purchaseId)}`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Check a purchase against shared budgets before requesting the payment. It holds nothing and
|
|
657
|
+
* approves nothing. `sourceId` must be a managed wallet linked to this agent, and `currency` must
|
|
658
|
+
* be that wallet's stablecoin, such as `USDC` on Base, `USDC.e` on Tempo or `pathUSD` on Tempo
|
|
659
|
+
* Testnet. A purchase the budgets would refuse throws a `ContoError`.
|
|
660
|
+
*/
|
|
661
|
+
preview(input) {
|
|
662
|
+
return this.client.request("POST", "/api/sdk/spending/purchases/preview", { ...input });
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Attach a PDF, PNG or JPEG of at most 2 MiB, with up to 10 files per purchase. Set the Blob's
|
|
666
|
+
* `type` to `application/pdf`, `image/png` or `image/jpeg`. A Blob with no type, or with a type
|
|
667
|
+
* that does not match the file's first bytes, fails with `reasonCode` `INVALID_FILE`.
|
|
668
|
+
*/
|
|
669
|
+
attachEvidence(purchaseId, file, filename) {
|
|
670
|
+
const form = new FormData();
|
|
671
|
+
form.set("file", file, filename);
|
|
672
|
+
return this.client.request(
|
|
673
|
+
"POST",
|
|
674
|
+
`/api/sdk/spending/purchases/${id(purchaseId)}/evidence`,
|
|
675
|
+
form
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
var AdminSpendingClient = class {
|
|
680
|
+
constructor(client) {
|
|
681
|
+
this.client = client;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* CSV of the organization's purchases against shared budgets. Fails with `reasonCode`
|
|
685
|
+
* `EXPORT_TOO_LARGE` above 10,000 purchases.
|
|
686
|
+
*/
|
|
687
|
+
exportPurchases() {
|
|
688
|
+
return this.client.download("/api/spending/purchases/export");
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Attach a PDF, PNG or JPEG of at most 2 MiB, with up to 10 files per purchase. Set the Blob's
|
|
692
|
+
* `type` to `application/pdf`, `image/png` or `image/jpeg`. A Blob with no type, or with a type
|
|
693
|
+
* that does not match the file's first bytes, fails with `reasonCode` `INVALID_FILE`.
|
|
694
|
+
*/
|
|
695
|
+
attachEvidence(purchaseId, file, filename) {
|
|
696
|
+
const form = new FormData();
|
|
697
|
+
form.set("file", file, filename);
|
|
698
|
+
return this.client.request(
|
|
699
|
+
"POST",
|
|
700
|
+
`/api/spending/purchases/${id(purchaseId)}/evidence`,
|
|
701
|
+
form
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
downloadEvidence(purchaseId, evidenceId) {
|
|
705
|
+
return this.client.download(
|
|
706
|
+
`/api/spending/purchases/${id(purchaseId)}/evidence/${id(evidenceId)}`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
budgets() {
|
|
710
|
+
return this.client.request("GET", "/api/spending/budgets");
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Create a shared budget. Needs an organization API key with `policies:write`.
|
|
714
|
+
*
|
|
715
|
+
* While a budget exists, it stops x402, MPP, external wallet payments, agent-to-agent payments,
|
|
716
|
+
* payment grants, checkout relay, hosted Stripe Link, guarded browser checkout
|
|
717
|
+
* (`runGuardedCheckout()`), card overlays and checkout checkpoints, including
|
|
718
|
+
* `checkout.getLinkCard()`, for every agent it covers. A paused, expired or not yet started budget
|
|
719
|
+
* still stops them. Until its start date, while paused, and after its end date, it also refuses
|
|
720
|
+
* the managed wallet payments and connected card draws it covers, for those agents. A start date
|
|
721
|
+
* cannot be changed.
|
|
722
|
+
* `deleteBudget()` ends a budget. Fails with `reasonCode`
|
|
723
|
+
* `ENROLLMENT_RECONCILIATION_REQUIRED` while the organization or a participating agent has an
|
|
724
|
+
* open commitment.
|
|
725
|
+
*/
|
|
726
|
+
createBudget(input) {
|
|
727
|
+
return this.client.request("POST", "/api/spending/budgets", {
|
|
728
|
+
...input
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Change a budget's amount or active state, or move its end date to a later time in the future.
|
|
733
|
+
* Needs the budget's current `version`. A task budget can only change its active state. Pausing
|
|
734
|
+
* stops new purchases on it, and does not lift what it stops on other payment paths.
|
|
735
|
+
*/
|
|
736
|
+
updateBudget(budgetId, input) {
|
|
737
|
+
return this.client.request(
|
|
738
|
+
"PATCH",
|
|
739
|
+
`/api/spending/budgets/${id(budgetId)}`,
|
|
740
|
+
input
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* End a budget, so it stops applying on every path. Needs the budget's current `version`. Fails
|
|
745
|
+
* with `reasonCode` `BUDGET_HAS_HOLDS` while a purchase holds spend against it, and `TASK_BUDGET`
|
|
746
|
+
* for a task's budget, which ends by closing the task. A linked buyer with no budget left fails
|
|
747
|
+
* with `reasonCode` `LINKED_BUDGET_REQUIRED`.
|
|
748
|
+
*/
|
|
749
|
+
deleteBudget(budgetId, input) {
|
|
750
|
+
return this.client.request(
|
|
751
|
+
"DELETE",
|
|
752
|
+
`/api/spending/budgets/${id(budgetId)}?version=${input.version}`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
tasks(cursor) {
|
|
756
|
+
return this.client.request(
|
|
757
|
+
"GET",
|
|
758
|
+
`/api/spending/tasks${cursor ? `?cursor=${id(cursor)}` : ""}`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
/** Approve, reject or close a task. Needs an organization API key with `policies:write`. */
|
|
762
|
+
decideTask(taskId, input) {
|
|
763
|
+
return this.client.request(
|
|
764
|
+
"POST",
|
|
765
|
+
`/api/spending/tasks/${id(taskId)}/decide`,
|
|
766
|
+
input
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
purchases(cursor) {
|
|
770
|
+
return this.client.request(
|
|
771
|
+
"GET",
|
|
772
|
+
`/api/spending/purchases${cursor ? `?cursor=${id(cursor)}` : ""}`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
purchase(purchaseId) {
|
|
776
|
+
return this.client.request("GET", `/api/spending/purchases/${id(purchaseId)}`);
|
|
777
|
+
}
|
|
778
|
+
/** Record what happened to a purchase. Needs an API key with the `spending:reconcile` scope. */
|
|
779
|
+
reconcile(purchaseId, input) {
|
|
780
|
+
return this.client.request(
|
|
781
|
+
"POST",
|
|
782
|
+
`/api/spending/purchases/${id(purchaseId)}/reconcile`,
|
|
783
|
+
input
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
accounting(purchaseId, input) {
|
|
787
|
+
return this.client.request("PATCH", `/api/spending/purchases/${id(purchaseId)}`, input);
|
|
788
|
+
}
|
|
789
|
+
/** Record the amount, currency and merchant on a file. Conto does not take them from the file. */
|
|
790
|
+
reviewEvidence(purchaseId, evidenceId, input) {
|
|
791
|
+
return this.client.request(
|
|
792
|
+
"PATCH",
|
|
793
|
+
`/api/spending/purchases/${id(purchaseId)}/evidence/${id(evidenceId)}`,
|
|
794
|
+
input
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
|
|
601
799
|
// src/http.ts
|
|
602
800
|
var PUBLIC_PAYMENT_REASONS = /* @__PURE__ */ new Set([
|
|
603
801
|
"The available balance is insufficient for this payment.",
|
|
@@ -639,7 +837,34 @@ var VALIDATION_FIELDS = /* @__PURE__ */ new Set([
|
|
|
639
837
|
"walletId",
|
|
640
838
|
"idempotencyKey",
|
|
641
839
|
"txHash",
|
|
642
|
-
"approvalToken"
|
|
840
|
+
"approvalToken",
|
|
841
|
+
"currency",
|
|
842
|
+
"merchant",
|
|
843
|
+
"sourceId",
|
|
844
|
+
"expiresAt",
|
|
845
|
+
"taskRequestId",
|
|
846
|
+
"purchaseKey",
|
|
847
|
+
"permittedMerchants",
|
|
848
|
+
"permittedSourceIds",
|
|
849
|
+
"name",
|
|
850
|
+
"scope",
|
|
851
|
+
"participantIds",
|
|
852
|
+
"requiresTask",
|
|
853
|
+
"period",
|
|
854
|
+
"periodStart",
|
|
855
|
+
"periodEnd",
|
|
856
|
+
"version",
|
|
857
|
+
"isActive",
|
|
858
|
+
"decision",
|
|
859
|
+
"eventKey",
|
|
860
|
+
"kind",
|
|
861
|
+
"settledAmount",
|
|
862
|
+
"refundedAmount",
|
|
863
|
+
"final",
|
|
864
|
+
"evidenceRef",
|
|
865
|
+
"department",
|
|
866
|
+
"project",
|
|
867
|
+
"accountingCode"
|
|
643
868
|
]);
|
|
644
869
|
function customerFieldErrors(body, status) {
|
|
645
870
|
if (![400, 422].includes(status) || !["VALIDATION_ERROR", "INVALID_REQUEST", "INVALID_INPUT"].includes(String(body.code)))
|
|
@@ -648,9 +873,7 @@ function customerFieldErrors(body, status) {
|
|
|
648
873
|
if (!Array.isArray(fields)) return void 0;
|
|
649
874
|
const names = [
|
|
650
875
|
...new Set(
|
|
651
|
-
fields.map((item) => record(item).field).filter(
|
|
652
|
-
(field) => typeof field === "string" && VALIDATION_FIELDS.has(field)
|
|
653
|
-
)
|
|
876
|
+
fields.map((item) => record(item).field).filter((field) => typeof field === "string").map((field) => field.split(".")[0]).filter((field) => VALIDATION_FIELDS.has(field))
|
|
654
877
|
)
|
|
655
878
|
];
|
|
656
879
|
return names.length ? names.map((field) => ({
|
|
@@ -793,14 +1016,68 @@ function fallbackRetryGuidance(status) {
|
|
|
793
1016
|
function responseErrorMessage(_data, status) {
|
|
794
1017
|
return safeErrorMessage(status);
|
|
795
1018
|
}
|
|
1019
|
+
var SHARED_REASON_CODES = /* @__PURE__ */ new Set([
|
|
1020
|
+
"AGENT_NOT_AUTHORIZED",
|
|
1021
|
+
"AGENT_UNAVAILABLE",
|
|
1022
|
+
"BUDGET_CURRENCY_UNSUPPORTED",
|
|
1023
|
+
"BUDGET_EXPIRED",
|
|
1024
|
+
"BUDGET_HAS_HOLDS",
|
|
1025
|
+
"BUDGET_PAUSED",
|
|
1026
|
+
"BUYER_LINK_REVOKED",
|
|
1027
|
+
"DUPLICATE_PURCHASE",
|
|
1028
|
+
"ENROLLMENT_RECONCILIATION_REQUIRED",
|
|
1029
|
+
"EVIDENCE_CONFLICT",
|
|
1030
|
+
"EVIDENCE_LIMIT",
|
|
1031
|
+
"EVIDENCE_REQUIRED",
|
|
1032
|
+
"EXECUTION_PATH_NOT_CONNECTED",
|
|
1033
|
+
"EXPORT_TOO_LARGE",
|
|
1034
|
+
"INVALID_AMOUNT",
|
|
1035
|
+
"INVALID_EXPIRY",
|
|
1036
|
+
"INVALID_FILE",
|
|
1037
|
+
"INVALID_PARTICIPANTS",
|
|
1038
|
+
"INVALID_RENEWAL",
|
|
1039
|
+
"INVALID_TASK_STATE",
|
|
1040
|
+
"LEGACY_BUDGET_REQUEST",
|
|
1041
|
+
"LINKED_BUDGET_REQUIRED",
|
|
1042
|
+
"LINKED_TASK_UNSUPPORTED",
|
|
1043
|
+
"MERCHANT_NOT_AUTHORIZED",
|
|
1044
|
+
"NOT_FOUND",
|
|
1045
|
+
"PURCHASE_ALREADY_SUBMITTED",
|
|
1046
|
+
"PURCHASE_CONFLICT",
|
|
1047
|
+
"SETTLED_PURCHASE",
|
|
1048
|
+
"SHARED_BUDGET_EXCEEDED",
|
|
1049
|
+
"SOURCE_CURRENCY_MISMATCH",
|
|
1050
|
+
"SOURCE_NOT_AUTHORIZED",
|
|
1051
|
+
"SOURCE_UNAVAILABLE",
|
|
1052
|
+
"STALE_PAYMENT_EVIDENCE",
|
|
1053
|
+
"STALE_VERSION",
|
|
1054
|
+
"TASK_AUTHORITY_UNAVAILABLE",
|
|
1055
|
+
"TASK_BUDGET",
|
|
1056
|
+
"TASK_EXPIRED",
|
|
1057
|
+
"TASK_REAPPROVAL_REQUIRED",
|
|
1058
|
+
"TASK_REQUIRED",
|
|
1059
|
+
"UNSUPPORTED_BUDGET_PERIOD"
|
|
1060
|
+
]);
|
|
1061
|
+
var SHARED_NEXT_ACTIONS = /* @__PURE__ */ new Set([
|
|
1062
|
+
"REQUEST_BUDGET_INCREASE",
|
|
1063
|
+
"INSPECT_BUDGETS",
|
|
1064
|
+
"OWNER_RECONCILIATION",
|
|
1065
|
+
"CHECK_STATUS"
|
|
1066
|
+
]);
|
|
796
1067
|
function responseErrorMetadata(response, data) {
|
|
797
1068
|
const body = record(data);
|
|
798
1069
|
const code = safeErrorCode(body.code, response.status);
|
|
799
1070
|
const bodyRetryAfter = typeof body.retryAfter === "number" || typeof body.retryAfter === "string" ? parseRetryAfter(String(body.retryAfter)) : void 0;
|
|
800
1071
|
const reasons = customerPaymentReasons(body.reasons);
|
|
1072
|
+
const details = record(body.details);
|
|
1073
|
+
const reasonCode = typeof details.reasonCode === "string" && SHARED_REASON_CODES.has(details.reasonCode) ? details.reasonCode : void 0;
|
|
1074
|
+
const nextActions = reasonCode && Array.isArray(details.nextActions) ? details.nextActions.filter(
|
|
1075
|
+
(value) => typeof value === "string" && SHARED_NEXT_ACTIONS.has(value)
|
|
1076
|
+
) : void 0;
|
|
801
1077
|
return {
|
|
802
1078
|
code,
|
|
803
1079
|
status: response.status,
|
|
1080
|
+
...reasonCode ? { reasonCode, nextActions } : {},
|
|
804
1081
|
...reasons.length > 0 ? { reasons } : {},
|
|
805
1082
|
fieldErrors: customerFieldErrors(body, response.status),
|
|
806
1083
|
requestId: safeRequestId(
|
|
@@ -826,6 +1103,9 @@ var ContoAdminError = class extends Error {
|
|
|
826
1103
|
this.requestId = metadata.requestId;
|
|
827
1104
|
this.retryAfter = metadata.retryAfter;
|
|
828
1105
|
this.retryGuidance = metadata.retryGuidance;
|
|
1106
|
+
this.reasonCode = metadata.reasonCode;
|
|
1107
|
+
this.nextActions = metadata.nextActions;
|
|
1108
|
+
this.fieldErrors = metadata.fieldErrors;
|
|
829
1109
|
}
|
|
830
1110
|
};
|
|
831
1111
|
var _orgApiKey;
|
|
@@ -850,6 +1130,7 @@ var ContoAdmin = class {
|
|
|
850
1130
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
851
1131
|
assertSecureBaseUrl(this.baseUrl);
|
|
852
1132
|
this.timeout = config.timeout || 3e4;
|
|
1133
|
+
this.spending = new AdminSpendingClient(this);
|
|
853
1134
|
this.agents = new AgentsClient(this);
|
|
854
1135
|
this.wallets = new WalletsClient(this);
|
|
855
1136
|
this.cards = new AdminCardsClient(this);
|
|
@@ -890,6 +1171,49 @@ var ContoAdmin = class {
|
|
|
890
1171
|
clearTimeout(timeoutId);
|
|
891
1172
|
}
|
|
892
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Download a file from the Conto API as bytes. It retries like a read, and
|
|
1176
|
+
* failures throw ContoAdminError as request() does.
|
|
1177
|
+
*/
|
|
1178
|
+
async download(path) {
|
|
1179
|
+
const controller = new AbortController();
|
|
1180
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1181
|
+
try {
|
|
1182
|
+
const response = await fetchWithRetry(
|
|
1183
|
+
`${this.baseUrl}${path}`,
|
|
1184
|
+
{
|
|
1185
|
+
headers: {
|
|
1186
|
+
Authorization: `Bearer ${__privateGet(this, _orgApiKey)}`,
|
|
1187
|
+
"User-Agent": `@conto_finance/sdk-admin/${import_package_version.SDK_PACKAGE_VERSION}`
|
|
1188
|
+
},
|
|
1189
|
+
// A redirect is an error response here, so the key never follows it.
|
|
1190
|
+
redirect: "manual",
|
|
1191
|
+
signal: controller.signal
|
|
1192
|
+
},
|
|
1193
|
+
{ retryable: true }
|
|
1194
|
+
);
|
|
1195
|
+
if (!response.ok) {
|
|
1196
|
+
const data = await readResponseData(response);
|
|
1197
|
+
throw new ContoAdminError(
|
|
1198
|
+
responseErrorMessage(data, response.status),
|
|
1199
|
+
responseErrorMetadata(response, data)
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
if (error instanceof ContoAdminError) throw error;
|
|
1205
|
+
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
1206
|
+
const failure = new ContoAdminError(timedOut ? "Request timeout" : "Network request failed", {
|
|
1207
|
+
code: timedOut ? "TIMEOUT" : "NETWORK_ERROR",
|
|
1208
|
+
status: 0,
|
|
1209
|
+
retryGuidance: "The request is safe to retry."
|
|
1210
|
+
});
|
|
1211
|
+
Sentry.captureException(failure, { extra: { method: "GET", path } });
|
|
1212
|
+
throw failure;
|
|
1213
|
+
} finally {
|
|
1214
|
+
clearTimeout(timeoutId);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
893
1217
|
/**
|
|
894
1218
|
* Make an authenticated request to the Conto API. Reads retry. A write retries
|
|
895
1219
|
* only when `idempotent` says the API deduplicates it, as purchase creation does.
|
|
@@ -907,10 +1231,10 @@ var ContoAdmin = class {
|
|
|
907
1231
|
method: normalizedMethod,
|
|
908
1232
|
headers: {
|
|
909
1233
|
Authorization: `Bearer ${__privateGet(this, _orgApiKey)}`,
|
|
910
|
-
"Content-Type": "application/json",
|
|
1234
|
+
...body instanceof FormData ? {} : { "Content-Type": "application/json" },
|
|
911
1235
|
"User-Agent": `@conto_finance/sdk-admin/${import_package_version.SDK_PACKAGE_VERSION}`
|
|
912
1236
|
},
|
|
913
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
1237
|
+
body: body instanceof FormData ? body : body ? JSON.stringify(body) : void 0,
|
|
914
1238
|
signal: controller.signal
|
|
915
1239
|
},
|
|
916
1240
|
{ retryable }
|
|
@@ -1415,6 +1739,8 @@ var ContoError = class extends Error {
|
|
|
1415
1739
|
this.actionUrl = metadata.actionUrl;
|
|
1416
1740
|
this.fieldErrors = metadata.fieldErrors;
|
|
1417
1741
|
this.reasons = metadata.reasons;
|
|
1742
|
+
this.reasonCode = metadata.reasonCode;
|
|
1743
|
+
this.nextActions = metadata.nextActions;
|
|
1418
1744
|
if (diagnostics !== null && typeof diagnostics === "object" && !Array.isArray(diagnostics) && "version" in diagnostics && diagnostics.version === "conto.setup-diagnostics.v1") {
|
|
1419
1745
|
this.diagnostics = diagnostics;
|
|
1420
1746
|
}
|
|
@@ -1595,6 +1921,7 @@ var Conto = class {
|
|
|
1595
1921
|
assertSecureBaseUrl(this.baseUrl);
|
|
1596
1922
|
this.timeout = config.timeout || 3e4;
|
|
1597
1923
|
this.checkout = new CheckoutClient(this);
|
|
1924
|
+
this.spending = new SpendingClient(this);
|
|
1598
1925
|
this.payments = new PaymentsClient(this);
|
|
1599
1926
|
this.cards = new CardsClient(this);
|
|
1600
1927
|
this.connectedCards = new ConnectedCardsClient(this);
|
|
@@ -1920,6 +2247,49 @@ var Conto = class {
|
|
|
1920
2247
|
clearTimeout(timeoutId);
|
|
1921
2248
|
}
|
|
1922
2249
|
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Download a file from the Conto API as bytes. It retries like a read, and
|
|
2252
|
+
* failures throw ContoError as request() does.
|
|
2253
|
+
*/
|
|
2254
|
+
async download(path) {
|
|
2255
|
+
const controller = new AbortController();
|
|
2256
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2257
|
+
try {
|
|
2258
|
+
const response = await fetchWithRetry(
|
|
2259
|
+
`${this.baseUrl}${path}`,
|
|
2260
|
+
{
|
|
2261
|
+
headers: {
|
|
2262
|
+
Authorization: `Bearer ${__privateGet(this, _apiKey)}`,
|
|
2263
|
+
"User-Agent": `@conto_finance/sdk/${import_package_version2.SDK_PACKAGE_VERSION}`
|
|
2264
|
+
},
|
|
2265
|
+
// A redirect is an error response here, so the key never follows it.
|
|
2266
|
+
redirect: "manual",
|
|
2267
|
+
signal: controller.signal
|
|
2268
|
+
},
|
|
2269
|
+
{ retryable: true }
|
|
2270
|
+
);
|
|
2271
|
+
if (!response.ok) {
|
|
2272
|
+
const data = await readResponseData(response);
|
|
2273
|
+
throw new ContoError(
|
|
2274
|
+
responseErrorMessage(data, response.status),
|
|
2275
|
+
responseErrorMetadata(response, data)
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
2279
|
+
} catch (error) {
|
|
2280
|
+
if (error instanceof ContoError) throw error;
|
|
2281
|
+
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
2282
|
+
const failure = new ContoError(timedOut ? "Request timeout" : "Network request failed", {
|
|
2283
|
+
code: timedOut ? "TIMEOUT" : "NETWORK_ERROR",
|
|
2284
|
+
status: 0,
|
|
2285
|
+
retryGuidance: "The request is safe to retry."
|
|
2286
|
+
});
|
|
2287
|
+
Sentry2.captureException(failure, { extra: { method: "GET", path } });
|
|
2288
|
+
throw failure;
|
|
2289
|
+
} finally {
|
|
2290
|
+
clearTimeout(timeoutId);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
1923
2293
|
/** Make an authenticated request to the Conto API */
|
|
1924
2294
|
async request(method, path, body, options = {}) {
|
|
1925
2295
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -1934,10 +2304,10 @@ var Conto = class {
|
|
|
1934
2304
|
method: normalizedMethod,
|
|
1935
2305
|
headers: {
|
|
1936
2306
|
Authorization: `Bearer ${__privateGet(this, _apiKey)}`,
|
|
1937
|
-
"Content-Type": "application/json",
|
|
2307
|
+
...body instanceof FormData ? {} : { "Content-Type": "application/json" },
|
|
1938
2308
|
"User-Agent": `@conto_finance/sdk/${import_package_version2.SDK_PACKAGE_VERSION}`
|
|
1939
2309
|
},
|
|
1940
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
2310
|
+
body: body instanceof FormData ? body : body ? JSON.stringify(body) : void 0,
|
|
1941
2311
|
signal: controller.signal
|
|
1942
2312
|
},
|
|
1943
2313
|
{ retryable, maxRetries: options.maxRetries }
|
|
@@ -2298,11 +2668,11 @@ var TestCardsClient = class {
|
|
|
2298
2668
|
}
|
|
2299
2669
|
};
|
|
2300
2670
|
function approvalRetryGuidance(requestId) {
|
|
2301
|
-
const
|
|
2302
|
-
return `An approver in Conto executes the payment when they approve it. After approval, call payments.execute(${
|
|
2671
|
+
const id2 = JSON.stringify(requestId);
|
|
2672
|
+
return `An approver in Conto executes the payment when they approve it. After approval, call payments.execute(${id2}) or payments.status(${id2}) to get the receipt. Do not request the payment again.`;
|
|
2303
2673
|
}
|
|
2304
2674
|
function executeRetryGuidance(code, requestId) {
|
|
2305
|
-
const
|
|
2675
|
+
const id2 = JSON.stringify(requestId);
|
|
2306
2676
|
switch (code) {
|
|
2307
2677
|
case "APPROVAL_PENDING":
|
|
2308
2678
|
return approvalRetryGuidance(requestId);
|
|
@@ -2313,7 +2683,7 @@ function executeRetryGuidance(code, requestId) {
|
|
|
2313
2683
|
return "This payment now needs approval and will not execute. Request a new payment and wait for approval.";
|
|
2314
2684
|
case "PAYMENT_PROCESSING":
|
|
2315
2685
|
case "NEEDS_RECONCILIATION":
|
|
2316
|
-
return `Do not execute again. Check payments.status(${
|
|
2686
|
+
return `Do not execute again. Check payments.status(${id2}) for the outcome.`;
|
|
2317
2687
|
default:
|
|
2318
2688
|
return void 0;
|
|
2319
2689
|
}
|
|
@@ -2803,12 +3173,12 @@ function closedLinkFailure(request) {
|
|
|
2803
3173
|
};
|
|
2804
3174
|
}
|
|
2805
3175
|
}
|
|
2806
|
-
async function closeLinkRequest(wallet,
|
|
3176
|
+
async function closeLinkRequest(wallet, id2) {
|
|
2807
3177
|
try {
|
|
2808
|
-
await wallet.cancel(
|
|
3178
|
+
await wallet.cancel(id2);
|
|
2809
3179
|
return true;
|
|
2810
3180
|
} catch {
|
|
2811
|
-
const status = await wallet.waitForApproval(
|
|
3181
|
+
const status = await wallet.waitForApproval(id2, { timeoutSeconds: 0 }).then((request) => request.status).catch(() => void 0);
|
|
2812
3182
|
return status !== void 0 && LINK_CLOSED_STATUSES.has(status);
|
|
2813
3183
|
}
|
|
2814
3184
|
}
|
|
@@ -2843,7 +3213,7 @@ function notifyLinkApproval(callback, event) {
|
|
|
2843
3213
|
} catch {
|
|
2844
3214
|
}
|
|
2845
3215
|
}
|
|
2846
|
-
var _client4, _CheckoutClient_instances, linkCardFor_fn;
|
|
3216
|
+
var _client4, _CheckoutClient_instances, recordLinkRequest_fn, linkCardFor_fn;
|
|
2847
3217
|
var CheckoutClient = class {
|
|
2848
3218
|
constructor(client) {
|
|
2849
3219
|
__privateAdd(this, _CheckoutClient_instances);
|
|
@@ -2928,7 +3298,7 @@ var CheckoutClient = class {
|
|
|
2928
3298
|
assertPaymentReference(input.paymentReference);
|
|
2929
3299
|
let current = checkpoint;
|
|
2930
3300
|
if (input.amountMinor !== checkpoint.amountMinor) {
|
|
2931
|
-
if (input.paymentReference) {
|
|
3301
|
+
if (input.paymentReference ?? checkpoint.paymentReference) {
|
|
2932
3302
|
throw new ContoError("The Link card covers the approved total, not this one. Do not pay.", {
|
|
2933
3303
|
code: "LINK_CARD_TOTAL_CHANGED",
|
|
2934
3304
|
status: 0,
|
|
@@ -3028,9 +3398,10 @@ var CheckoutClient = class {
|
|
|
3028
3398
|
* 1. Conto runs checkpoint 1 and holds the spend. When a rule requires the
|
|
3029
3399
|
* owner's approval, this waits for it.
|
|
3030
3400
|
* 2. Only then does the wallet ask Link for a card for exactly the approved
|
|
3031
|
-
* amount at this merchant.
|
|
3032
|
-
*
|
|
3033
|
-
*
|
|
3401
|
+
* amount at this merchant. Conto records the Link request at once, so the
|
|
3402
|
+
* hold stays while the card may be usable. The person approves it in
|
|
3403
|
+
* Link. Pass onLinkApproval to get Link's approval page, so they can
|
|
3404
|
+
* approve on the web without the Link app.
|
|
3034
3405
|
* 3. The card is returned. It never reaches Conto.
|
|
3035
3406
|
*
|
|
3036
3407
|
* Then fill in the card, call confirm(result.checkpoint, { amountMinor,
|
|
@@ -3043,7 +3414,9 @@ var CheckoutClient = class {
|
|
|
3043
3414
|
* held spend and throws a LinkCardError. When Conto cannot cancel an open Link
|
|
3044
3415
|
* request, it keeps the hold and throws LINK_CARD_NOT_CANCELLED with the
|
|
3045
3416
|
* request id. Retrying with the same input resumes the same checkpoint and
|
|
3046
|
-
* Link request.
|
|
3417
|
+
* Link request while Conto's approval is open. Once the stage is OUTCOME_UNKNOWN,
|
|
3418
|
+
* a retry throws CHECKOUT_CHECKPOINT_NOT_APPROVED: cancel the Link request, then
|
|
3419
|
+
* the checkpoint.
|
|
3047
3420
|
*/
|
|
3048
3421
|
async getLinkCard(input) {
|
|
3049
3422
|
const {
|
|
@@ -3137,6 +3510,24 @@ var CheckoutClient = class {
|
|
|
3137
3510
|
};
|
|
3138
3511
|
_client4 = new WeakMap();
|
|
3139
3512
|
_CheckoutClient_instances = new WeakSet();
|
|
3513
|
+
recordLinkRequest_fn = async function(checkpoint, linkRequestId) {
|
|
3514
|
+
try {
|
|
3515
|
+
const result = await __privateGet(this, _client4).request(
|
|
3516
|
+
"POST",
|
|
3517
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpoint.id)}/payment-reference`,
|
|
3518
|
+
{ paymentReference: { provider: "LINK", id: linkRequestId } },
|
|
3519
|
+
{ idempotent: true }
|
|
3520
|
+
);
|
|
3521
|
+
return result.checkpoint;
|
|
3522
|
+
} catch (error) {
|
|
3523
|
+
if (!(error instanceof ContoError)) throw error;
|
|
3524
|
+
throw new LinkFailureSignal({
|
|
3525
|
+
kind: LinkCardError,
|
|
3526
|
+
code: "LINK_CARD_NOT_RECORDED",
|
|
3527
|
+
message: "Conto could not record the Link card request."
|
|
3528
|
+
});
|
|
3529
|
+
}
|
|
3530
|
+
};
|
|
3140
3531
|
linkCardFor_fn = async function(checkpoint, wallet, options) {
|
|
3141
3532
|
const { linkApprovalTimeoutMs = 10 * 6e4, signal, onLinkApproval } = options;
|
|
3142
3533
|
if (checkpoint.stage !== "APPROVED") {
|
|
@@ -3169,6 +3560,7 @@ linkCardFor_fn = async function(checkpoint, wallet, options) {
|
|
|
3169
3560
|
message: "Link returned a request for a different amount."
|
|
3170
3561
|
});
|
|
3171
3562
|
}
|
|
3563
|
+
checkpoint = await __privateMethod(this, _CheckoutClient_instances, recordLinkRequest_fn).call(this, checkpoint, request.id);
|
|
3172
3564
|
if (LINK_WAITING_STATUSES.has(request.status)) {
|
|
3173
3565
|
const details = linkApprovalDetails(request);
|
|
3174
3566
|
approvalUrl = details.approvalUrl;
|
|
@@ -3243,6 +3635,7 @@ linkCardFor_fn = async function(checkpoint, wallet, options) {
|
|
|
3243
3635
|
};
|
|
3244
3636
|
export {
|
|
3245
3637
|
AdminPurchasingClient,
|
|
3638
|
+
AdminSpendingClient,
|
|
3246
3639
|
BrowserActionFinalizationPendingError,
|
|
3247
3640
|
BrowserCheckoutTargetVerificationError,
|
|
3248
3641
|
CardsClient,
|
|
@@ -3269,6 +3662,7 @@ export {
|
|
|
3269
3662
|
OrganizationRecurringPurchasesClient,
|
|
3270
3663
|
PurchasesClient,
|
|
3271
3664
|
RecurringPurchasesClient,
|
|
3665
|
+
SpendingClient,
|
|
3272
3666
|
TestCardsClient,
|
|
3273
3667
|
canonicalizeBrowserCheckoutTarget,
|
|
3274
3668
|
createVerifiedBrowserCheckoutDriver,
|