@conto_finance/sdk 0.6.0 → 0.6.1
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.d.mts +582 -9
- package/dist/index.d.ts +582 -9
- package/dist/index.js +509 -28
- package/dist/index.mjs +496 -23
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ var require_package = __commonJS({
|
|
|
46
46
|
"package.json"(exports2, module2) {
|
|
47
47
|
module2.exports = {
|
|
48
48
|
name: "@conto_finance/sdk",
|
|
49
|
-
version: "0.6.
|
|
49
|
+
version: "0.6.1",
|
|
50
50
|
description: "Conto SDK for AI agent payment authorization",
|
|
51
51
|
main: "./dist/index.js",
|
|
52
52
|
module: "./dist/index.mjs",
|
|
@@ -87,7 +87,7 @@ var require_package = __commonJS({
|
|
|
87
87
|
dev: "tsup --watch",
|
|
88
88
|
lint: "tsc --noEmit",
|
|
89
89
|
test: "npm run lint && npm run test:contract && npm run test:consumer",
|
|
90
|
-
"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",
|
|
90
|
+
"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/purchases.test.mjs",
|
|
91
91
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
92
92
|
"verify:release": "node scripts/verify-release.mjs",
|
|
93
93
|
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
@@ -150,6 +150,7 @@ var require_package_version = __commonJS({
|
|
|
150
150
|
// src/index.ts
|
|
151
151
|
var index_exports = {};
|
|
152
152
|
__export(index_exports, {
|
|
153
|
+
AdminPurchasingClient: () => AdminPurchasingClient,
|
|
153
154
|
BrowserActionFinalizationPendingError: () => BrowserActionFinalizationPendingError,
|
|
154
155
|
BrowserCheckoutTargetVerificationError: () => BrowserCheckoutTargetVerificationError,
|
|
155
156
|
CardsClient: () => CardsClient,
|
|
@@ -167,17 +168,390 @@ __export(index_exports, {
|
|
|
167
168
|
ControlledExternalCardPaymentTool: () => ControlledExternalCardPaymentTool,
|
|
168
169
|
ExternalCardConfirmationPendingError: () => ExternalCardConfirmationPendingError,
|
|
169
170
|
Giro: () => Conto,
|
|
171
|
+
OrganizationPurchasesClient: () => OrganizationPurchasesClient,
|
|
172
|
+
OrganizationRecurringPurchasesClient: () => OrganizationRecurringPurchasesClient,
|
|
173
|
+
PurchasesClient: () => PurchasesClient,
|
|
174
|
+
RecurringPurchasesClient: () => RecurringPurchasesClient,
|
|
170
175
|
TestCardsClient: () => TestCardsClient,
|
|
171
176
|
canonicalizeBrowserCheckoutTarget: () => canonicalizeBrowserCheckoutTarget,
|
|
172
177
|
createVerifiedBrowserCheckoutDriver: () => createVerifiedBrowserCheckoutDriver,
|
|
173
178
|
default: () => index_default,
|
|
174
|
-
fingerprintBrowserCheckoutTarget: () => fingerprintBrowserCheckoutTarget
|
|
179
|
+
fingerprintBrowserCheckoutTarget: () => fingerprintBrowserCheckoutTarget,
|
|
180
|
+
isFinalPurchaseStatus: () => isFinalPurchaseStatus,
|
|
181
|
+
parseServerSentEvents: () => parseServerSentEvents,
|
|
182
|
+
verifyPurchaseWebhook: () => verifyPurchaseWebhook
|
|
175
183
|
});
|
|
176
184
|
module.exports = __toCommonJS(index_exports);
|
|
177
185
|
var Sentry2 = __toESM(require("@sentry/node"));
|
|
178
|
-
var
|
|
186
|
+
var import_node_crypto3 = require("crypto");
|
|
179
187
|
var import_package_version2 = __toESM(require_package_version());
|
|
180
188
|
|
|
189
|
+
// src/purchases.ts
|
|
190
|
+
var import_node_crypto = require("crypto");
|
|
191
|
+
var TERMINAL = /* @__PURE__ */ new Set(["succeeded", "blocked", "failed", "cancelled"]);
|
|
192
|
+
var isFinalPurchaseStatus = (status) => TERMINAL.has(status);
|
|
193
|
+
async function* parseServerSentEvents(body) {
|
|
194
|
+
const reader = body.getReader();
|
|
195
|
+
const decoder = new TextDecoder();
|
|
196
|
+
let buffer = "";
|
|
197
|
+
let frame = { data: [] };
|
|
198
|
+
try {
|
|
199
|
+
while (true) {
|
|
200
|
+
const { value, done } = await reader.read();
|
|
201
|
+
if (done) break;
|
|
202
|
+
buffer += decoder.decode(value, { stream: true });
|
|
203
|
+
let newline;
|
|
204
|
+
while ((newline = buffer.search(/\r?\n/)) >= 0) {
|
|
205
|
+
const line = buffer.slice(0, newline);
|
|
206
|
+
buffer = buffer.slice(newline + (buffer[newline] === "\r" ? 2 : 1));
|
|
207
|
+
if (line === "") {
|
|
208
|
+
if (frame.data.length || frame.event)
|
|
209
|
+
yield { id: frame.id, event: frame.event ?? "message", data: frame.data.join("\n") };
|
|
210
|
+
frame = { data: [] };
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (line.startsWith(":")) continue;
|
|
214
|
+
const colon = line.indexOf(":");
|
|
215
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
216
|
+
const raw = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
217
|
+
if (field === "id") frame.id = raw;
|
|
218
|
+
else if (field === "event") frame.event = raw;
|
|
219
|
+
else if (field === "data") frame.data.push(raw);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} finally {
|
|
223
|
+
reader.releaseLock();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
var sleep = (ms, signal) => new Promise((resolve) => {
|
|
227
|
+
const timer = setTimeout(resolve, ms);
|
|
228
|
+
signal?.addEventListener(
|
|
229
|
+
"abort",
|
|
230
|
+
() => {
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
resolve();
|
|
233
|
+
},
|
|
234
|
+
{ once: true }
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
function requirePurchaseId(id) {
|
|
238
|
+
if (!/^[a-z0-9]{8,64}$/i.test(id)) throw new TypeError("purchase id is not valid");
|
|
239
|
+
return encodeURIComponent(id);
|
|
240
|
+
}
|
|
241
|
+
function requireCycleKey(key) {
|
|
242
|
+
if (!/^[A-Za-z0-9._:-]{1,100}$/.test(key)) throw new TypeError("cycleKey is not valid");
|
|
243
|
+
return encodeURIComponent(key);
|
|
244
|
+
}
|
|
245
|
+
var _transport, _base;
|
|
246
|
+
var RecurringPurchasesClient = class {
|
|
247
|
+
constructor(transport, basePath = "/api/sdk/recurring-purchases") {
|
|
248
|
+
__privateAdd(this, _transport);
|
|
249
|
+
__privateAdd(this, _base);
|
|
250
|
+
__privateSet(this, _transport, transport);
|
|
251
|
+
__privateSet(this, _base, basePath);
|
|
252
|
+
}
|
|
253
|
+
async list(buyerId) {
|
|
254
|
+
return __privateGet(this, _transport).request(
|
|
255
|
+
"GET",
|
|
256
|
+
buyerId ? `${__privateGet(this, _base)}?buyerId=${encodeURIComponent(buyerId)}` : __privateGet(this, _base)
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
async get(id) {
|
|
260
|
+
return __privateGet(this, _transport).request("GET", `${__privateGet(this, _base)}/${requirePurchaseId(id)}`);
|
|
261
|
+
}
|
|
262
|
+
/** Starts one order. The same cycle key returns the same order. */
|
|
263
|
+
async startCycle(id, cycleKey) {
|
|
264
|
+
requireCycleKey(cycleKey);
|
|
265
|
+
return __privateGet(this, _transport).request(
|
|
266
|
+
"POST",
|
|
267
|
+
`${__privateGet(this, _base)}/${requirePurchaseId(id)}/cycles`,
|
|
268
|
+
{ cycleKey },
|
|
269
|
+
{ idempotent: true }
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
/** Skips one order, also before it starts. */
|
|
273
|
+
async skipCycle(id, cycleKey) {
|
|
274
|
+
return __privateGet(this, _transport).request(
|
|
275
|
+
"POST",
|
|
276
|
+
`${__privateGet(this, _base)}/${requirePurchaseId(id)}/cycles/${requireCycleKey(cycleKey)}/skip`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
_transport = new WeakMap();
|
|
281
|
+
_base = new WeakMap();
|
|
282
|
+
var _client;
|
|
283
|
+
var OrganizationRecurringPurchasesClient = class extends RecurringPurchasesClient {
|
|
284
|
+
constructor(client) {
|
|
285
|
+
super(client, "/api/purchasing/recurring-purchases");
|
|
286
|
+
__privateAdd(this, _client);
|
|
287
|
+
__privateSet(this, _client, client);
|
|
288
|
+
}
|
|
289
|
+
/** Stops a repeat order. An order waiting for its payment time closes unpaid. */
|
|
290
|
+
async cancel(id) {
|
|
291
|
+
return __privateGet(this, _client).request(
|
|
292
|
+
"POST",
|
|
293
|
+
`/api/purchasing/recurring-purchases/${requirePurchaseId(id)}/cancel`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
_client = new WeakMap();
|
|
298
|
+
var _transport2, _base2;
|
|
299
|
+
var PurchasesClient = class {
|
|
300
|
+
constructor(transport, basePath = "/api/sdk/purchases") {
|
|
301
|
+
__privateAdd(this, _transport2);
|
|
302
|
+
__privateAdd(this, _base2);
|
|
303
|
+
__privateSet(this, _transport2, transport);
|
|
304
|
+
__privateSet(this, _base2, basePath);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Starts a purchase and returns at once. Reusing the idempotency key with the
|
|
308
|
+
* same input returns the same purchase, so retrying after a lost response is safe.
|
|
309
|
+
*/
|
|
310
|
+
async create(input, options) {
|
|
311
|
+
const idempotencyKey = options?.idempotencyKey?.trim();
|
|
312
|
+
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255)
|
|
313
|
+
throw new TypeError("idempotencyKey must contain 8 to 255 characters");
|
|
314
|
+
if (!Number.isSafeInteger(input.maxCost?.amountMinor) || input.maxCost.amountMinor <= 0)
|
|
315
|
+
throw new TypeError("maxCost.amountMinor must be a positive integer");
|
|
316
|
+
return __privateGet(this, _transport2).request(
|
|
317
|
+
"POST",
|
|
318
|
+
__privateGet(this, _base2),
|
|
319
|
+
{ ...input, idempotencyKey },
|
|
320
|
+
{ idempotent: true }
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
async list(params = {}) {
|
|
324
|
+
const query = new URLSearchParams();
|
|
325
|
+
for (const [key, value] of Object.entries(params))
|
|
326
|
+
if (value !== void 0) query.set(key, String(value));
|
|
327
|
+
const suffix = query.toString();
|
|
328
|
+
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}${suffix ? `?${suffix}` : ""}`);
|
|
329
|
+
}
|
|
330
|
+
async get(id) {
|
|
331
|
+
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}/${requirePurchaseId(id)}`);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Answers an open action whose authority is `agent_or_buyer` with the fields
|
|
335
|
+
* it lists. The acknowledgment means recorded; the purchase shows the result.
|
|
336
|
+
* The same answer again returns the same acknowledgment.
|
|
337
|
+
*/
|
|
338
|
+
async respond(id, input) {
|
|
339
|
+
return __privateGet(this, _transport2).request(
|
|
340
|
+
"POST",
|
|
341
|
+
`${__privateGet(this, _base2)}/${requirePurchaseId(id)}/responses`,
|
|
342
|
+
input,
|
|
343
|
+
{ idempotent: true }
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
async events(id, params = {}) {
|
|
347
|
+
const query = new URLSearchParams({ after: String(params.after ?? 0) });
|
|
348
|
+
if (params.limit) query.set("limit", String(params.limit));
|
|
349
|
+
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}/${requirePurchaseId(id)}/events?${query}`);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Requests cancellation. Before payment the purchase stops. Once payment may
|
|
353
|
+
* have been submitted, `cancellationOutcome` is `too_late` and the purchase
|
|
354
|
+
* stays in reconciliation. Nothing is refunded by this call.
|
|
355
|
+
*/
|
|
356
|
+
async cancel(id) {
|
|
357
|
+
return __privateGet(this, _transport2).request(
|
|
358
|
+
"POST",
|
|
359
|
+
`${__privateGet(this, _base2)}/${requirePurchaseId(id)}/cancel`,
|
|
360
|
+
void 0,
|
|
361
|
+
{ idempotent: true }
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Yields the purchase's events in version order until it reaches a final
|
|
366
|
+
* status. Streams with server-sent events and reconnects from the last
|
|
367
|
+
* version; falls back to polling where streaming is unavailable. A duplicate
|
|
368
|
+
* after a reconnect is dropped. A `purchase.resync` event carries the
|
|
369
|
+
* current purchase when the cursor could not be resumed.
|
|
370
|
+
*/
|
|
371
|
+
async *watch(id, options = {}) {
|
|
372
|
+
const path = `${__privateGet(this, _base2)}/${requirePurchaseId(id)}`;
|
|
373
|
+
const mode = options.mode ?? "auto";
|
|
374
|
+
const interval = Math.max(500, options.pollIntervalMs ?? 2e3);
|
|
375
|
+
const deadline = options.maxWaitMs ? Date.now() + options.maxWaitMs : Infinity;
|
|
376
|
+
let cursor = Math.max(0, options.after ?? 0);
|
|
377
|
+
let streaming = mode !== "poll";
|
|
378
|
+
const emit = (event) => {
|
|
379
|
+
if (event.version <= cursor && event.type !== "purchase.resync") return null;
|
|
380
|
+
cursor = Math.max(cursor, event.version);
|
|
381
|
+
return event;
|
|
382
|
+
};
|
|
383
|
+
while (!options.signal?.aborted && Date.now() < deadline) {
|
|
384
|
+
if (streaming) {
|
|
385
|
+
try {
|
|
386
|
+
const response = await __privateGet(this, _transport2).openStream(
|
|
387
|
+
`${path}/events/stream?after=${cursor}`,
|
|
388
|
+
{ signal: options.signal, lastEventId: String(cursor) }
|
|
389
|
+
);
|
|
390
|
+
if (!response.body) throw new Error("streaming unavailable");
|
|
391
|
+
for await (const frame of parseServerSentEvents(response.body)) {
|
|
392
|
+
if (frame.event === "end") return;
|
|
393
|
+
if (frame.event === "error") break;
|
|
394
|
+
if (frame.event === "resync") {
|
|
395
|
+
const purchase = JSON.parse(frame.data);
|
|
396
|
+
const event2 = {
|
|
397
|
+
id: `resync:${purchase.version}`,
|
|
398
|
+
purchaseId: purchase.id,
|
|
399
|
+
version: purchase.version,
|
|
400
|
+
type: "purchase.resync",
|
|
401
|
+
createdAt: purchase.updatedAt,
|
|
402
|
+
data: purchase
|
|
403
|
+
};
|
|
404
|
+
cursor = purchase.version;
|
|
405
|
+
yield event2;
|
|
406
|
+
if (isFinalPurchaseStatus(purchase.status)) return;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (!frame.data) continue;
|
|
410
|
+
const event = emit(JSON.parse(frame.data));
|
|
411
|
+
if (!event) continue;
|
|
412
|
+
yield event;
|
|
413
|
+
if (isFinalPurchaseStatus(event.data.status)) return;
|
|
414
|
+
}
|
|
415
|
+
continue;
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (options.signal?.aborted) return;
|
|
418
|
+
if (isClientError(error)) throw error;
|
|
419
|
+
if (mode === "stream") {
|
|
420
|
+
await sleep(interval, options.signal);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
streaming = false;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
let page;
|
|
427
|
+
try {
|
|
428
|
+
page = await this.events(id, { after: cursor, limit: 100 });
|
|
429
|
+
} catch (error) {
|
|
430
|
+
if (codeOf(error) !== "CURSOR_INVALID") throw error;
|
|
431
|
+
const purchase = await this.get(id);
|
|
432
|
+
cursor = purchase.version;
|
|
433
|
+
yield {
|
|
434
|
+
id: `resync:${purchase.version}`,
|
|
435
|
+
purchaseId: purchase.id,
|
|
436
|
+
version: purchase.version,
|
|
437
|
+
type: "purchase.resync",
|
|
438
|
+
createdAt: purchase.updatedAt,
|
|
439
|
+
data: purchase
|
|
440
|
+
};
|
|
441
|
+
if (isFinalPurchaseStatus(purchase.status)) return;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
for (const item of page.items) {
|
|
445
|
+
const event = emit(item);
|
|
446
|
+
if (!event) continue;
|
|
447
|
+
yield event;
|
|
448
|
+
if (isFinalPurchaseStatus(event.data.status)) return;
|
|
449
|
+
}
|
|
450
|
+
if (!page.hasMore) await sleep(interval, options.signal);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
_transport2 = new WeakMap();
|
|
455
|
+
_base2 = new WeakMap();
|
|
456
|
+
var codeOf = (error) => error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
457
|
+
var isClientError = (error) => {
|
|
458
|
+
const status = error && typeof error === "object" && "status" in error ? Number(error.status) : 0;
|
|
459
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
460
|
+
};
|
|
461
|
+
var enc = encodeURIComponent;
|
|
462
|
+
var _client2;
|
|
463
|
+
var OrganizationPurchasesClient = class extends PurchasesClient {
|
|
464
|
+
constructor(client) {
|
|
465
|
+
super(client, "/api/purchasing/purchases");
|
|
466
|
+
__privateAdd(this, _client2);
|
|
467
|
+
__privateSet(this, _client2, client);
|
|
468
|
+
}
|
|
469
|
+
async list(params = {}) {
|
|
470
|
+
const query = new URLSearchParams();
|
|
471
|
+
for (const [key, value] of Object.entries(params))
|
|
472
|
+
if (value !== void 0) query.set(key, String(value));
|
|
473
|
+
const suffix = query.toString();
|
|
474
|
+
return __privateGet(this, _client2).request("GET", `/api/purchasing/purchases${suffix ? `?${suffix}` : ""}`);
|
|
475
|
+
}
|
|
476
|
+
/** A single-use link that opens the purchase's buyer actions on Conto's hosted page. */
|
|
477
|
+
createActionLink(id, input = {}) {
|
|
478
|
+
return __privateGet(this, _client2).request(
|
|
479
|
+
"POST",
|
|
480
|
+
`/api/purchasing/purchases/${requirePurchaseId(id)}/action-sessions`,
|
|
481
|
+
input
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
_client2 = new WeakMap();
|
|
486
|
+
var _client3;
|
|
487
|
+
var AdminPurchasingClient = class {
|
|
488
|
+
constructor(client) {
|
|
489
|
+
__privateAdd(this, _client3);
|
|
490
|
+
this.buyers = {
|
|
491
|
+
/** Maps one of your users to a buyer with its own agent, or returns the existing one. */
|
|
492
|
+
create: (input) => __privateGet(this, _client3).request("POST", "/api/purchasing/buyers", input),
|
|
493
|
+
get: (id) => __privateGet(this, _client3).request("GET", `/api/purchasing/buyers/${enc(id)}`),
|
|
494
|
+
list: (params = {}) => __privateGet(this, _client3).request("GET", `/api/purchasing/buyers?limit=${params.limit ?? 20}&offset=${params.offset ?? 0}`),
|
|
495
|
+
/** Stops the buyer's agent. Purchases that may already be paid stay visible. */
|
|
496
|
+
stop: (id) => __privateGet(this, _client3).request("POST", `/api/purchasing/buyers/${enc(id)}/stop`)
|
|
497
|
+
};
|
|
498
|
+
this.paymentMethods = {
|
|
499
|
+
/** A single-use link to the hosted setup page: limits, card and delegation. */
|
|
500
|
+
createSetupLink: (input) => __privateGet(this, _client3).request(
|
|
501
|
+
"POST",
|
|
502
|
+
"/api/purchasing/payment-methods/setup-sessions",
|
|
503
|
+
input
|
|
504
|
+
),
|
|
505
|
+
list: (buyerId) => __privateGet(this, _client3).request(
|
|
506
|
+
"GET",
|
|
507
|
+
`/api/purchasing/payment-methods?buyerId=${enc(buyerId)}`
|
|
508
|
+
),
|
|
509
|
+
remove: (id) => __privateGet(this, _client3).request(
|
|
510
|
+
"DELETE",
|
|
511
|
+
`/api/purchasing/payment-methods/${enc(id)}`
|
|
512
|
+
)
|
|
513
|
+
};
|
|
514
|
+
this.delegations = {
|
|
515
|
+
list: (buyerId) => __privateGet(this, _client3).request(
|
|
516
|
+
"GET",
|
|
517
|
+
`/api/purchasing/delegations?buyerId=${enc(buyerId)}`
|
|
518
|
+
),
|
|
519
|
+
get: (id) => __privateGet(this, _client3).request("GET", `/api/purchasing/delegations/${enc(id)}`),
|
|
520
|
+
revoke: (id) => __privateGet(this, _client3).request(
|
|
521
|
+
"POST",
|
|
522
|
+
`/api/purchasing/delegations/${enc(id)}/revoke`
|
|
523
|
+
)
|
|
524
|
+
};
|
|
525
|
+
this.settings = {
|
|
526
|
+
get: () => __privateGet(this, _client3).request("GET", "/api/purchasing/settings"),
|
|
527
|
+
update: (input) => __privateGet(this, _client3).request(
|
|
528
|
+
"PUT",
|
|
529
|
+
"/api/purchasing/settings",
|
|
530
|
+
input
|
|
531
|
+
)
|
|
532
|
+
};
|
|
533
|
+
__privateSet(this, _client3, client);
|
|
534
|
+
this.purchases = new OrganizationPurchasesClient(client);
|
|
535
|
+
this.recurringPurchases = new OrganizationRecurringPurchasesClient(client);
|
|
536
|
+
}
|
|
537
|
+
/** What this organization can run now: test mode always, live once Conto enables it. */
|
|
538
|
+
capabilities() {
|
|
539
|
+
return __privateGet(this, _client3).request("GET", "/api/purchasing/capabilities");
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
_client3 = new WeakMap();
|
|
543
|
+
function verifyPurchaseWebhook(input) {
|
|
544
|
+
const expected = (0, import_node_crypto.createHmac)("sha256", input.secret).update(input.body).digest();
|
|
545
|
+
const received = Buffer.from(input.signature ?? "", "hex");
|
|
546
|
+
if (received.length !== expected.length || !(0, import_node_crypto.timingSafeEqual)(received, expected))
|
|
547
|
+
throw new Error("Invalid webhook signature");
|
|
548
|
+
const payload = JSON.parse(input.body);
|
|
549
|
+
const age = ((input.now ?? /* @__PURE__ */ new Date()).getTime() - Date.parse(payload.timestamp)) / 1e3;
|
|
550
|
+
if (!Number.isFinite(age) || Math.abs(age) > (input.toleranceSeconds ?? 300))
|
|
551
|
+
throw new Error("Webhook timestamp is outside the tolerance");
|
|
552
|
+
return payload;
|
|
553
|
+
}
|
|
554
|
+
|
|
181
555
|
// src/http.ts
|
|
182
556
|
var PUBLIC_PAYMENT_REASONS = /* @__PURE__ */ new Set([
|
|
183
557
|
"The available balance is insufficient for this payment.",
|
|
@@ -395,7 +769,7 @@ function responseErrorMetadata(response, data) {
|
|
|
395
769
|
}
|
|
396
770
|
|
|
397
771
|
// src/browser-checkout.ts
|
|
398
|
-
var
|
|
772
|
+
var import_node_crypto2 = require("crypto");
|
|
399
773
|
var MAX_URL_LENGTH = 2048;
|
|
400
774
|
var MAX_LOCATOR_LENGTH = 1024;
|
|
401
775
|
var MAX_TEXT_LENGTH = 500;
|
|
@@ -468,7 +842,7 @@ function canonicalizeBrowserCheckoutTarget(snapshot) {
|
|
|
468
842
|
}
|
|
469
843
|
function fingerprintBrowserCheckoutTarget(snapshot) {
|
|
470
844
|
const canonical = canonicalizeBrowserCheckoutTarget(snapshot);
|
|
471
|
-
const digest = (0,
|
|
845
|
+
const digest = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
472
846
|
return `browser_target_sha256_${digest}`;
|
|
473
847
|
}
|
|
474
848
|
function createVerifiedBrowserCheckoutDriver(resolver) {
|
|
@@ -552,14 +926,51 @@ var ContoAdmin = class {
|
|
|
552
926
|
this.cards = new AdminCardsClient(this);
|
|
553
927
|
this.policies = new PoliciesClient(this);
|
|
554
928
|
this.team = new TeamClient(this);
|
|
929
|
+
this.purchasing = new AdminPurchasingClient(this);
|
|
555
930
|
}
|
|
556
|
-
/**
|
|
557
|
-
|
|
931
|
+
/**
|
|
932
|
+
* Opens an authenticated server-sent event stream. Used by
|
|
933
|
+
* `purchasing.purchases.watch()`. The server closes it after about 50 seconds.
|
|
934
|
+
* @internal
|
|
935
|
+
*/
|
|
936
|
+
async openStream(path, init = {}) {
|
|
937
|
+
const controller = new AbortController();
|
|
938
|
+
const abort = () => controller.abort();
|
|
939
|
+
init.signal?.addEventListener("abort", abort, { once: true });
|
|
940
|
+
const timeoutId = setTimeout(abort, Math.max(this.timeout, 7e4));
|
|
941
|
+
try {
|
|
942
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
943
|
+
method: "GET",
|
|
944
|
+
headers: {
|
|
945
|
+
Authorization: `Bearer ${__privateGet(this, _orgApiKey)}`,
|
|
946
|
+
Accept: "text/event-stream",
|
|
947
|
+
"User-Agent": `@conto_finance/sdk-admin/${import_package_version.SDK_PACKAGE_VERSION}`,
|
|
948
|
+
...init.lastEventId ? { "Last-Event-ID": init.lastEventId } : {}
|
|
949
|
+
},
|
|
950
|
+
signal: controller.signal
|
|
951
|
+
});
|
|
952
|
+
if (!response.ok) {
|
|
953
|
+
const data = await readResponseData(response);
|
|
954
|
+
throw new ContoAdminError(
|
|
955
|
+
responseErrorMessage(data, response.status),
|
|
956
|
+
responseErrorMetadata(response, data)
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
return response;
|
|
960
|
+
} finally {
|
|
961
|
+
clearTimeout(timeoutId);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Make an authenticated request to the Conto API. Reads retry. A write retries
|
|
966
|
+
* only when `idempotent` says the API deduplicates it, as purchase creation does.
|
|
967
|
+
*/
|
|
968
|
+
async request(method, path, body, options = {}) {
|
|
558
969
|
const url = `${this.baseUrl}${path}`;
|
|
559
970
|
const controller = new AbortController();
|
|
560
971
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
561
972
|
const normalizedMethod = method.toUpperCase();
|
|
562
|
-
const retryable = ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod);
|
|
973
|
+
const retryable = ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod) || options.idempotent === true;
|
|
563
974
|
try {
|
|
564
975
|
const response = await fetchWithRetry(
|
|
565
976
|
url,
|
|
@@ -1135,7 +1546,7 @@ var CheckoutApprovalTimeoutError = class extends ContoError {
|
|
|
1135
1546
|
}
|
|
1136
1547
|
};
|
|
1137
1548
|
function generatedIdempotencyKey(prefix) {
|
|
1138
|
-
return `${prefix}_${(0,
|
|
1549
|
+
return `${prefix}_${(0, import_node_crypto3.randomUUID)()}`;
|
|
1139
1550
|
}
|
|
1140
1551
|
function requireFinancialIdentifier(value, field) {
|
|
1141
1552
|
if (!value || value.trim().length === 0) {
|
|
@@ -1203,6 +1614,8 @@ var Conto = class {
|
|
|
1203
1614
|
this.testCards = new TestCardsClient(this);
|
|
1204
1615
|
this.x402 = new X402Client(this);
|
|
1205
1616
|
this.mpp = new MppClient(this);
|
|
1617
|
+
this.purchases = new PurchasesClient(this);
|
|
1618
|
+
this.recurringPurchases = new RecurringPurchasesClient(this);
|
|
1206
1619
|
}
|
|
1207
1620
|
/**
|
|
1208
1621
|
* Normalize and durably revise a partial agent purchase proposal without
|
|
@@ -1487,6 +1900,39 @@ var Conto = class {
|
|
|
1487
1900
|
async setup() {
|
|
1488
1901
|
return this.request("GET", "/api/sdk/setup");
|
|
1489
1902
|
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Opens an authenticated server-sent event stream. Used by
|
|
1905
|
+
* `purchases.watch()`. The server closes it after about 50 seconds.
|
|
1906
|
+
* @internal
|
|
1907
|
+
*/
|
|
1908
|
+
async openStream(path, init = {}) {
|
|
1909
|
+
const controller = new AbortController();
|
|
1910
|
+
const abort = () => controller.abort();
|
|
1911
|
+
init.signal?.addEventListener("abort", abort, { once: true });
|
|
1912
|
+
const timeoutId = setTimeout(abort, Math.max(this.timeout, 7e4));
|
|
1913
|
+
try {
|
|
1914
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
1915
|
+
method: "GET",
|
|
1916
|
+
headers: {
|
|
1917
|
+
Authorization: `Bearer ${__privateGet(this, _apiKey)}`,
|
|
1918
|
+
Accept: "text/event-stream",
|
|
1919
|
+
"User-Agent": `@conto_finance/sdk/${import_package_version2.SDK_PACKAGE_VERSION}`,
|
|
1920
|
+
...init.lastEventId ? { "Last-Event-ID": init.lastEventId } : {}
|
|
1921
|
+
},
|
|
1922
|
+
signal: controller.signal
|
|
1923
|
+
});
|
|
1924
|
+
if (!response.ok) {
|
|
1925
|
+
const data = await readResponseData(response);
|
|
1926
|
+
throw new ContoError(
|
|
1927
|
+
responseErrorMessage(data, response.status),
|
|
1928
|
+
responseErrorMetadata(response, data)
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
return response;
|
|
1932
|
+
} finally {
|
|
1933
|
+
clearTimeout(timeoutId);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1490
1936
|
/** Make an authenticated request to the Conto API */
|
|
1491
1937
|
async request(method, path, body, options = {}) {
|
|
1492
1938
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -1759,12 +2205,6 @@ var ConnectedCardsClient = class {
|
|
|
1759
2205
|
currency: input.currency ?? "USD"
|
|
1760
2206
|
});
|
|
1761
2207
|
}
|
|
1762
|
-
/**
|
|
1763
|
-
* List the connected cards and active mandates this agent may draw against.
|
|
1764
|
-
*
|
|
1765
|
-
* The response is masked: no PAN and no provider tokens. This is a read-only
|
|
1766
|
-
* GET, so transport retry on transient failures is safe.
|
|
1767
|
-
*/
|
|
1768
2208
|
/** Record receipt after a fresh draw. Does not confirm checkout or payment settlement. */
|
|
1769
2209
|
async acknowledgeDelivery(requestId) {
|
|
1770
2210
|
return this.client.request(
|
|
@@ -1774,6 +2214,39 @@ var ConnectedCardsClient = class {
|
|
|
1774
2214
|
{ idempotent: true }
|
|
1775
2215
|
);
|
|
1776
2216
|
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Report what happened with the card number after checkout.
|
|
2219
|
+
*
|
|
2220
|
+
* `USED` at the full approved amount closes the draw. `NOT_USED`, or `USED`
|
|
2221
|
+
* with a smaller `amountMinor`, is recorded and shown to a person, who
|
|
2222
|
+
* confirms it before the held amount is released. A report never releases
|
|
2223
|
+
* held spend by itself and does not confirm settlement. Safe to repeat with
|
|
2224
|
+
* the same report. A different second report gets HTTP 409.
|
|
2225
|
+
*
|
|
2226
|
+
* @example
|
|
2227
|
+
* ```typescript
|
|
2228
|
+
* await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'USED' });
|
|
2229
|
+
* await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'USED', amountMinor: 1850 });
|
|
2230
|
+
* await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'NOT_USED' });
|
|
2231
|
+
* ```
|
|
2232
|
+
*/
|
|
2233
|
+
async reportOutcome(requestId, report) {
|
|
2234
|
+
return this.client.request(
|
|
2235
|
+
"POST",
|
|
2236
|
+
`/api/sdk/connected-cards/draws/${encodeURIComponent(requestId)}/outcome`,
|
|
2237
|
+
{
|
|
2238
|
+
outcome: report.outcome,
|
|
2239
|
+
...report.amountMinor !== void 0 ? { amountMinor: report.amountMinor } : {}
|
|
2240
|
+
},
|
|
2241
|
+
{ idempotent: true }
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* List the connected cards and active mandates this agent may draw against.
|
|
2246
|
+
*
|
|
2247
|
+
* The response is masked: no PAN and no provider tokens. This is a read-only
|
|
2248
|
+
* GET, so transport retry on transient failures is safe.
|
|
2249
|
+
*/
|
|
1777
2250
|
async list() {
|
|
1778
2251
|
return this.client.request("GET", "/api/sdk/connected-cards");
|
|
1779
2252
|
}
|
|
@@ -2175,7 +2648,7 @@ function closedCheckpointError(checkpoint) {
|
|
|
2175
2648
|
}
|
|
2176
2649
|
return new CheckoutCheckpointClosedError(checkpoint);
|
|
2177
2650
|
}
|
|
2178
|
-
function
|
|
2651
|
+
function sleep2(ms, signal) {
|
|
2179
2652
|
return new Promise((resolve, reject) => {
|
|
2180
2653
|
if (signal?.aborted) {
|
|
2181
2654
|
reject(signal.reason ?? new Error("Aborted"));
|
|
@@ -2192,11 +2665,11 @@ function sleep(ms, signal) {
|
|
|
2192
2665
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2193
2666
|
});
|
|
2194
2667
|
}
|
|
2195
|
-
var
|
|
2668
|
+
var _client4;
|
|
2196
2669
|
var CheckoutClient = class {
|
|
2197
2670
|
constructor(client) {
|
|
2198
|
-
__privateAdd(this,
|
|
2199
|
-
__privateSet(this,
|
|
2671
|
+
__privateAdd(this, _client4);
|
|
2672
|
+
__privateSet(this, _client4, client);
|
|
2200
2673
|
}
|
|
2201
2674
|
/**
|
|
2202
2675
|
* First checkpoint. Open the payment method only when the stage is APPROVED.
|
|
@@ -2216,7 +2689,7 @@ var CheckoutClient = class {
|
|
|
2216
2689
|
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
2217
2690
|
throw checkoutError("Only USD checkouts are supported");
|
|
2218
2691
|
}
|
|
2219
|
-
const result = await __privateGet(this,
|
|
2692
|
+
const result = await __privateGet(this, _client4).request(
|
|
2220
2693
|
"POST",
|
|
2221
2694
|
"/api/sdk/checkout-checkpoints",
|
|
2222
2695
|
{
|
|
@@ -2254,7 +2727,7 @@ var CheckoutClient = class {
|
|
|
2254
2727
|
}
|
|
2255
2728
|
const remaining = deadline - Date.now();
|
|
2256
2729
|
if (remaining <= 0) throw new CheckoutApprovalTimeoutError(current);
|
|
2257
|
-
await
|
|
2730
|
+
await sleep2(Math.min(pollIntervalMs, remaining), options.signal);
|
|
2258
2731
|
current = await this.get(current.id);
|
|
2259
2732
|
}
|
|
2260
2733
|
}
|
|
@@ -2298,7 +2771,7 @@ var CheckoutClient = class {
|
|
|
2298
2771
|
if (current.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(current);
|
|
2299
2772
|
}
|
|
2300
2773
|
try {
|
|
2301
|
-
const result = await __privateGet(this,
|
|
2774
|
+
const result = await __privateGet(this, _client4).request(
|
|
2302
2775
|
"POST",
|
|
2303
2776
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(current.id)}/confirm`,
|
|
2304
2777
|
{
|
|
@@ -2319,7 +2792,7 @@ var CheckoutClient = class {
|
|
|
2319
2792
|
}
|
|
2320
2793
|
/** Report what happened after a confirmed checkpoint. Safe to repeat with the same outcome. */
|
|
2321
2794
|
async reportOutcome(checkpointId, input) {
|
|
2322
|
-
const result = await __privateGet(this,
|
|
2795
|
+
const result = await __privateGet(this, _client4).request(
|
|
2323
2796
|
"POST",
|
|
2324
2797
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/outcome`,
|
|
2325
2798
|
{
|
|
@@ -2335,7 +2808,7 @@ var CheckoutClient = class {
|
|
|
2335
2808
|
* checkpoint waiting for the owner can be cancelled too.
|
|
2336
2809
|
*/
|
|
2337
2810
|
async cancel(checkpointId) {
|
|
2338
|
-
const result = await __privateGet(this,
|
|
2811
|
+
const result = await __privateGet(this, _client4).request(
|
|
2339
2812
|
"POST",
|
|
2340
2813
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/cancel`,
|
|
2341
2814
|
{},
|
|
@@ -2344,16 +2817,17 @@ var CheckoutClient = class {
|
|
|
2344
2817
|
return result.checkpoint;
|
|
2345
2818
|
}
|
|
2346
2819
|
async get(checkpointId) {
|
|
2347
|
-
const result = await __privateGet(this,
|
|
2820
|
+
const result = await __privateGet(this, _client4).request(
|
|
2348
2821
|
"GET",
|
|
2349
2822
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}`
|
|
2350
2823
|
);
|
|
2351
2824
|
return result.checkpoint;
|
|
2352
2825
|
}
|
|
2353
2826
|
};
|
|
2354
|
-
|
|
2827
|
+
_client4 = new WeakMap();
|
|
2355
2828
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2356
2829
|
0 && (module.exports = {
|
|
2830
|
+
AdminPurchasingClient,
|
|
2357
2831
|
BrowserActionFinalizationPendingError,
|
|
2358
2832
|
BrowserCheckoutTargetVerificationError,
|
|
2359
2833
|
CardsClient,
|
|
@@ -2371,8 +2845,15 @@ _client = new WeakMap();
|
|
|
2371
2845
|
ControlledExternalCardPaymentTool,
|
|
2372
2846
|
ExternalCardConfirmationPendingError,
|
|
2373
2847
|
Giro,
|
|
2848
|
+
OrganizationPurchasesClient,
|
|
2849
|
+
OrganizationRecurringPurchasesClient,
|
|
2850
|
+
PurchasesClient,
|
|
2851
|
+
RecurringPurchasesClient,
|
|
2374
2852
|
TestCardsClient,
|
|
2375
2853
|
canonicalizeBrowserCheckoutTarget,
|
|
2376
2854
|
createVerifiedBrowserCheckoutDriver,
|
|
2377
|
-
fingerprintBrowserCheckoutTarget
|
|
2855
|
+
fingerprintBrowserCheckoutTarget,
|
|
2856
|
+
isFinalPurchaseStatus,
|
|
2857
|
+
parseServerSentEvents,
|
|
2858
|
+
verifyPurchaseWebhook
|
|
2378
2859
|
});
|