@conto_finance/sdk 0.6.0-beta.2 → 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/README.md +48 -171
- package/dist/index.d.mts +671 -180
- package/dist/index.d.ts +671 -180
- package/dist/index.js +668 -284
- package/dist/index.mjs +650 -277
- package/package.json +3 -9
- package/dist/link-testing.d.mts +0 -49
- package/dist/link-testing.d.ts +0 -49
- package/dist/link-testing.js +0 -285
- package/dist/link-testing.mjs +0 -265
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",
|
|
@@ -71,16 +71,10 @@ var require_package = __commonJS({
|
|
|
71
71
|
types: "./dist/checkout-verifier.d.ts",
|
|
72
72
|
import: "./dist/checkout-verifier.mjs",
|
|
73
73
|
require: "./dist/checkout-verifier.js"
|
|
74
|
-
},
|
|
75
|
-
"./link-testing": {
|
|
76
|
-
types: "./dist/link-testing.d.ts",
|
|
77
|
-
import: "./dist/link-testing.mjs",
|
|
78
|
-
require: "./dist/link-testing.js"
|
|
79
74
|
}
|
|
80
75
|
},
|
|
81
76
|
publishConfig: {
|
|
82
|
-
access: "public"
|
|
83
|
-
tag: "beta"
|
|
77
|
+
access: "public"
|
|
84
78
|
},
|
|
85
79
|
engines: {
|
|
86
80
|
node: ">=20.19.0"
|
|
@@ -93,7 +87,7 @@ var require_package = __commonJS({
|
|
|
93
87
|
dev: "tsup --watch",
|
|
94
88
|
lint: "tsc --noEmit",
|
|
95
89
|
test: "npm run lint && npm run test:contract && npm run test:consumer",
|
|
96
|
-
"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/
|
|
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",
|
|
97
91
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
98
92
|
"verify:release": "node scripts/verify-release.mjs",
|
|
99
93
|
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
@@ -156,9 +150,15 @@ var require_package_version = __commonJS({
|
|
|
156
150
|
// src/index.ts
|
|
157
151
|
var index_exports = {};
|
|
158
152
|
__export(index_exports, {
|
|
153
|
+
AdminPurchasingClient: () => AdminPurchasingClient,
|
|
159
154
|
BrowserActionFinalizationPendingError: () => BrowserActionFinalizationPendingError,
|
|
160
155
|
BrowserCheckoutTargetVerificationError: () => BrowserCheckoutTargetVerificationError,
|
|
161
156
|
CardsClient: () => CardsClient,
|
|
157
|
+
CheckoutApprovalDeniedError: () => CheckoutApprovalDeniedError,
|
|
158
|
+
CheckoutApprovalExpiredError: () => CheckoutApprovalExpiredError,
|
|
159
|
+
CheckoutApprovalPendingError: () => CheckoutApprovalPendingError,
|
|
160
|
+
CheckoutApprovalTimeoutError: () => CheckoutApprovalTimeoutError,
|
|
161
|
+
CheckoutCheckpointClosedError: () => CheckoutCheckpointClosedError,
|
|
162
162
|
CheckoutClient: () => CheckoutClient,
|
|
163
163
|
ConnectedCardsClient: () => ConnectedCardsClient,
|
|
164
164
|
Conto: () => Conto,
|
|
@@ -168,19 +168,390 @@ __export(index_exports, {
|
|
|
168
168
|
ControlledExternalCardPaymentTool: () => ControlledExternalCardPaymentTool,
|
|
169
169
|
ExternalCardConfirmationPendingError: () => ExternalCardConfirmationPendingError,
|
|
170
170
|
Giro: () => Conto,
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
OrganizationPurchasesClient: () => OrganizationPurchasesClient,
|
|
172
|
+
OrganizationRecurringPurchasesClient: () => OrganizationRecurringPurchasesClient,
|
|
173
|
+
PurchasesClient: () => PurchasesClient,
|
|
174
|
+
RecurringPurchasesClient: () => RecurringPurchasesClient,
|
|
173
175
|
TestCardsClient: () => TestCardsClient,
|
|
174
176
|
canonicalizeBrowserCheckoutTarget: () => canonicalizeBrowserCheckoutTarget,
|
|
175
177
|
createVerifiedBrowserCheckoutDriver: () => createVerifiedBrowserCheckoutDriver,
|
|
176
178
|
default: () => index_default,
|
|
177
|
-
fingerprintBrowserCheckoutTarget: () => fingerprintBrowserCheckoutTarget
|
|
179
|
+
fingerprintBrowserCheckoutTarget: () => fingerprintBrowserCheckoutTarget,
|
|
180
|
+
isFinalPurchaseStatus: () => isFinalPurchaseStatus,
|
|
181
|
+
parseServerSentEvents: () => parseServerSentEvents,
|
|
182
|
+
verifyPurchaseWebhook: () => verifyPurchaseWebhook
|
|
178
183
|
});
|
|
179
184
|
module.exports = __toCommonJS(index_exports);
|
|
180
185
|
var Sentry2 = __toESM(require("@sentry/node"));
|
|
181
|
-
var
|
|
186
|
+
var import_node_crypto3 = require("crypto");
|
|
182
187
|
var import_package_version2 = __toESM(require_package_version());
|
|
183
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
|
+
|
|
184
555
|
// src/http.ts
|
|
185
556
|
var PUBLIC_PAYMENT_REASONS = /* @__PURE__ */ new Set([
|
|
186
557
|
"The available balance is insufficient for this payment.",
|
|
@@ -398,7 +769,7 @@ function responseErrorMetadata(response, data) {
|
|
|
398
769
|
}
|
|
399
770
|
|
|
400
771
|
// src/browser-checkout.ts
|
|
401
|
-
var
|
|
772
|
+
var import_node_crypto2 = require("crypto");
|
|
402
773
|
var MAX_URL_LENGTH = 2048;
|
|
403
774
|
var MAX_LOCATOR_LENGTH = 1024;
|
|
404
775
|
var MAX_TEXT_LENGTH = 500;
|
|
@@ -471,7 +842,7 @@ function canonicalizeBrowserCheckoutTarget(snapshot) {
|
|
|
471
842
|
}
|
|
472
843
|
function fingerprintBrowserCheckoutTarget(snapshot) {
|
|
473
844
|
const canonical = canonicalizeBrowserCheckoutTarget(snapshot);
|
|
474
|
-
const digest = (0,
|
|
845
|
+
const digest = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
475
846
|
return `browser_target_sha256_${digest}`;
|
|
476
847
|
}
|
|
477
848
|
function createVerifiedBrowserCheckoutDriver(resolver) {
|
|
@@ -555,15 +926,51 @@ var ContoAdmin = class {
|
|
|
555
926
|
this.cards = new AdminCardsClient(this);
|
|
556
927
|
this.policies = new PoliciesClient(this);
|
|
557
928
|
this.team = new TeamClient(this);
|
|
558
|
-
this.
|
|
929
|
+
this.purchasing = new AdminPurchasingClient(this);
|
|
559
930
|
}
|
|
560
|
-
/**
|
|
561
|
-
|
|
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 = {}) {
|
|
562
969
|
const url = `${this.baseUrl}${path}`;
|
|
563
970
|
const controller = new AbortController();
|
|
564
971
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
565
972
|
const normalizedMethod = method.toUpperCase();
|
|
566
|
-
const retryable = ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod);
|
|
973
|
+
const retryable = ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod) || options.idempotent === true;
|
|
567
974
|
try {
|
|
568
975
|
const response = await fetchWithRetry(
|
|
569
976
|
url,
|
|
@@ -611,22 +1018,6 @@ var ContoAdmin = class {
|
|
|
611
1018
|
}
|
|
612
1019
|
};
|
|
613
1020
|
_orgApiKey = new WeakMap();
|
|
614
|
-
var PaymentRailsClient = class {
|
|
615
|
-
constructor(client) {
|
|
616
|
-
this.client = client;
|
|
617
|
-
}
|
|
618
|
-
/** Read gate and configuration readiness without calling Stripe. */
|
|
619
|
-
async getStripeLinkCheckoutReadiness() {
|
|
620
|
-
return this.client.request("GET", "/api/stripe-link/checkout-readiness");
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Ask Conto to verify that its configured Stripe credential resolves to its
|
|
624
|
-
* configured merchant account. This call never returns either identifier.
|
|
625
|
-
*/
|
|
626
|
-
async verifyStripeLinkCheckoutAccount() {
|
|
627
|
-
return this.client.request("POST", "/api/stripe-link/checkout-readiness");
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
1021
|
var AdminCardsClient = class {
|
|
631
1022
|
constructor(client) {
|
|
632
1023
|
this.client = client;
|
|
@@ -1081,27 +1472,6 @@ var ContoError = class extends Error {
|
|
|
1081
1472
|
}
|
|
1082
1473
|
}
|
|
1083
1474
|
};
|
|
1084
|
-
var LinkPaymentExecutionError = class extends ContoError {
|
|
1085
|
-
constructor(executionId, error) {
|
|
1086
|
-
const original = error instanceof ContoError ? error : void 0;
|
|
1087
|
-
super(
|
|
1088
|
-
original?.message ?? "Link execution could not be confirmed",
|
|
1089
|
-
{
|
|
1090
|
-
code: original?.code ?? "LINK_EXECUTION_UNCONFIRMED",
|
|
1091
|
-
status: original?.status ?? 0,
|
|
1092
|
-
requestId: original?.requestId,
|
|
1093
|
-
retryAfter: original?.retryAfter,
|
|
1094
|
-
fieldErrors: original?.fieldErrors,
|
|
1095
|
-
reasons: original?.reasons,
|
|
1096
|
-
retryGuidance: "Call link.status(executionId) to reconcile. Do not create another purchase or retry execution blindly."
|
|
1097
|
-
},
|
|
1098
|
-
original?.diagnostics
|
|
1099
|
-
);
|
|
1100
|
-
this.nextAction = "RECONCILE";
|
|
1101
|
-
this.name = "LinkPaymentExecutionError";
|
|
1102
|
-
this.executionId = executionId;
|
|
1103
|
-
}
|
|
1104
|
-
};
|
|
1105
1475
|
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
1106
1476
|
constructor(requestId, confirmationInput, receipt) {
|
|
1107
1477
|
super("External card execution completed but confirmation is pending", {
|
|
@@ -1129,8 +1499,54 @@ var BrowserActionFinalizationPendingError = class extends ContoError {
|
|
|
1129
1499
|
this.observation = input.observation;
|
|
1130
1500
|
}
|
|
1131
1501
|
};
|
|
1502
|
+
var CheckoutCheckpointClosedError = class extends ContoError {
|
|
1503
|
+
constructor(checkpoint, message = `The checkpoint is ${checkpoint.stage.toLowerCase().replace(/_/g, " ")}. Stop this checkout.`, code = "CHECKOUT_CHECKPOINT_CLOSED") {
|
|
1504
|
+
super(message, { code, status: 0 });
|
|
1505
|
+
this.name = "CheckoutCheckpointClosedError";
|
|
1506
|
+
this.checkpoint = checkpoint;
|
|
1507
|
+
this.stage = checkpoint.stage;
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
var CheckoutApprovalDeniedError = class extends CheckoutCheckpointClosedError {
|
|
1511
|
+
constructor(checkpoint) {
|
|
1512
|
+
super(checkpoint, "The owner rejected this purchase. Do not pay.", "CHECKOUT_APPROVAL_DENIED");
|
|
1513
|
+
this.name = "CheckoutApprovalDeniedError";
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
var CheckoutApprovalExpiredError = class extends CheckoutCheckpointClosedError {
|
|
1517
|
+
constructor(checkpoint) {
|
|
1518
|
+
super(
|
|
1519
|
+
checkpoint,
|
|
1520
|
+
"The owner did not approve this purchase in time. Do not pay.",
|
|
1521
|
+
"CHECKOUT_APPROVAL_EXPIRED"
|
|
1522
|
+
);
|
|
1523
|
+
this.name = "CheckoutApprovalExpiredError";
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
var CheckoutApprovalPendingError = class extends ContoError {
|
|
1527
|
+
constructor(checkpoint) {
|
|
1528
|
+
super("The owner has not approved this purchase yet. Do not pay.", {
|
|
1529
|
+
code: "CHECKOUT_APPROVAL_PENDING",
|
|
1530
|
+
status: 0,
|
|
1531
|
+
retryGuidance: "Call checkout.waitForApproval(error.checkpoint), then confirm the approved checkpoint."
|
|
1532
|
+
});
|
|
1533
|
+
this.name = "CheckoutApprovalPendingError";
|
|
1534
|
+
this.checkpoint = checkpoint;
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
var CheckoutApprovalTimeoutError = class extends ContoError {
|
|
1538
|
+
constructor(checkpoint) {
|
|
1539
|
+
super("The owner has not decided yet. Do not pay.", {
|
|
1540
|
+
code: "CHECKOUT_APPROVAL_TIMEOUT",
|
|
1541
|
+
status: 0,
|
|
1542
|
+
retryGuidance: "Wait again with checkout.waitForApproval, or call checkout.cancel to release the held spend."
|
|
1543
|
+
});
|
|
1544
|
+
this.name = "CheckoutApprovalTimeoutError";
|
|
1545
|
+
this.checkpoint = checkpoint;
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1132
1548
|
function generatedIdempotencyKey(prefix) {
|
|
1133
|
-
return `${prefix}_${(0,
|
|
1549
|
+
return `${prefix}_${(0, import_node_crypto3.randomUUID)()}`;
|
|
1134
1550
|
}
|
|
1135
1551
|
function requireFinancialIdentifier(value, field) {
|
|
1136
1552
|
if (!value || value.trim().length === 0) {
|
|
@@ -1191,7 +1607,6 @@ var Conto = class {
|
|
|
1191
1607
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
1192
1608
|
assertSecureBaseUrl(this.baseUrl);
|
|
1193
1609
|
this.timeout = config.timeout || 3e4;
|
|
1194
|
-
this.link = new LinkClient(this, config.link);
|
|
1195
1610
|
this.checkout = new CheckoutClient(this);
|
|
1196
1611
|
this.payments = new PaymentsClient(this);
|
|
1197
1612
|
this.cards = new CardsClient(this);
|
|
@@ -1199,31 +1614,8 @@ var Conto = class {
|
|
|
1199
1614
|
this.testCards = new TestCardsClient(this);
|
|
1200
1615
|
this.x402 = new X402Client(this);
|
|
1201
1616
|
this.mpp = new MppClient(this);
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
* List masked Link payment methods for a session-created connection that is
|
|
1205
|
-
* explicitly bound to this agent. No Link token or payment artifact is
|
|
1206
|
-
* returned through the agent SDK.
|
|
1207
|
-
*/
|
|
1208
|
-
async listStripeLinkPaymentMethods(connectionId, options) {
|
|
1209
|
-
return this.request(
|
|
1210
|
-
"GET",
|
|
1211
|
-
withQuery("/api/sdk/stripe-link/payment-methods", {
|
|
1212
|
-
connectionId,
|
|
1213
|
-
...options?.mode ? { mode: options.mode } : {}
|
|
1214
|
-
})
|
|
1215
|
-
);
|
|
1216
|
-
}
|
|
1217
|
-
/** Read live Link configuration diagnostics without retrieving credentials or authorizing payment. */
|
|
1218
|
-
async getLinkSetup(input = {}) {
|
|
1219
|
-
return this.request(
|
|
1220
|
-
"GET",
|
|
1221
|
-
withQuery("/api/sdk/stripe-link/setup", {
|
|
1222
|
-
connectionId: input.connectionId,
|
|
1223
|
-
paymentMethodId: input.paymentMethodId,
|
|
1224
|
-
merchantId: input.merchantId
|
|
1225
|
-
})
|
|
1226
|
-
);
|
|
1617
|
+
this.purchases = new PurchasesClient(this);
|
|
1618
|
+
this.recurringPurchases = new RecurringPurchasesClient(this);
|
|
1227
1619
|
}
|
|
1228
1620
|
/**
|
|
1229
1621
|
* Normalize and durably revise a partial agent purchase proposal without
|
|
@@ -1305,6 +1697,22 @@ var Conto = class {
|
|
|
1305
1697
|
rethrowAmbiguousCommerceMutation(error, executionId, "authorize");
|
|
1306
1698
|
}
|
|
1307
1699
|
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Report whether an authorized guarded browser checkout was paid. PAID keeps
|
|
1702
|
+
* the held spend counted, NOT_PAID releases it, and UNKNOWN keeps it held for
|
|
1703
|
+
* the owner to review. Safe to repeat with the same outcome.
|
|
1704
|
+
*/
|
|
1705
|
+
async reportCommerceExecutionOutcome(executionId, input) {
|
|
1706
|
+
return this.request(
|
|
1707
|
+
"POST",
|
|
1708
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/outcome`,
|
|
1709
|
+
{
|
|
1710
|
+
outcome: input.outcome,
|
|
1711
|
+
...input.reference ? { reference: input.reference } : {}
|
|
1712
|
+
},
|
|
1713
|
+
{ idempotent: true }
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1308
1716
|
/** Claim a durable browser action for one stable privileged-runtime attempt. */
|
|
1309
1717
|
async claimBrowserAction(authorizationId, dispatchAttemptId, browserSessionId, action) {
|
|
1310
1718
|
return this.request(
|
|
@@ -1492,6 +1900,39 @@ var Conto = class {
|
|
|
1492
1900
|
async setup() {
|
|
1493
1901
|
return this.request("GET", "/api/sdk/setup");
|
|
1494
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
|
+
}
|
|
1495
1936
|
/** Make an authenticated request to the Conto API */
|
|
1496
1937
|
async request(method, path, body, options = {}) {
|
|
1497
1938
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -1764,12 +2205,6 @@ var ConnectedCardsClient = class {
|
|
|
1764
2205
|
currency: input.currency ?? "USD"
|
|
1765
2206
|
});
|
|
1766
2207
|
}
|
|
1767
|
-
/**
|
|
1768
|
-
* List the connected cards and active mandates this agent may draw against.
|
|
1769
|
-
*
|
|
1770
|
-
* The response is masked: no PAN and no provider tokens. This is a read-only
|
|
1771
|
-
* GET, so transport retry on transient failures is safe.
|
|
1772
|
-
*/
|
|
1773
2208
|
/** Record receipt after a fresh draw. Does not confirm checkout or payment settlement. */
|
|
1774
2209
|
async acknowledgeDelivery(requestId) {
|
|
1775
2210
|
return this.client.request(
|
|
@@ -1779,6 +2214,39 @@ var ConnectedCardsClient = class {
|
|
|
1779
2214
|
{ idempotent: true }
|
|
1780
2215
|
);
|
|
1781
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
|
+
*/
|
|
1782
2250
|
async list() {
|
|
1783
2251
|
return this.client.request("GET", "/api/sdk/connected-cards");
|
|
1784
2252
|
}
|
|
@@ -2167,13 +2635,49 @@ function checkpointCart(amountMinor, lineItems, totals) {
|
|
|
2167
2635
|
totals: totals ?? [{ type: "total", label: "Total", amountMinor }]
|
|
2168
2636
|
};
|
|
2169
2637
|
}
|
|
2170
|
-
var
|
|
2638
|
+
var CLOSED_CHECKPOINT_STAGES = [
|
|
2639
|
+
"DENIED",
|
|
2640
|
+
"CANCELLED",
|
|
2641
|
+
"EXPIRED",
|
|
2642
|
+
"NOT_PAID"
|
|
2643
|
+
];
|
|
2644
|
+
function closedCheckpointError(checkpoint) {
|
|
2645
|
+
if (checkpoint.approval?.status === "DENIED") return new CheckoutApprovalDeniedError(checkpoint);
|
|
2646
|
+
if (checkpoint.approval?.status === "EXPIRED") {
|
|
2647
|
+
return new CheckoutApprovalExpiredError(checkpoint);
|
|
2648
|
+
}
|
|
2649
|
+
return new CheckoutCheckpointClosedError(checkpoint);
|
|
2650
|
+
}
|
|
2651
|
+
function sleep2(ms, signal) {
|
|
2652
|
+
return new Promise((resolve, reject) => {
|
|
2653
|
+
if (signal?.aborted) {
|
|
2654
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
const onAbort = () => {
|
|
2658
|
+
clearTimeout(timer);
|
|
2659
|
+
reject(signal?.reason ?? new Error("Aborted"));
|
|
2660
|
+
};
|
|
2661
|
+
const timer = setTimeout(() => {
|
|
2662
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2663
|
+
resolve();
|
|
2664
|
+
}, ms);
|
|
2665
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
var _client4;
|
|
2171
2669
|
var CheckoutClient = class {
|
|
2172
2670
|
constructor(client) {
|
|
2173
|
-
__privateAdd(this,
|
|
2174
|
-
__privateSet(this,
|
|
2671
|
+
__privateAdd(this, _client4);
|
|
2672
|
+
__privateSet(this, _client4, client);
|
|
2175
2673
|
}
|
|
2176
|
-
/**
|
|
2674
|
+
/**
|
|
2675
|
+
* First checkpoint. Open the payment method only when the stage is APPROVED.
|
|
2676
|
+
* AWAITING_APPROVAL means the owner must decide first: call waitForApproval().
|
|
2677
|
+
* A ContoError means Conto did not approve: stop the checkout. A
|
|
2678
|
+
* CheckoutCheckpointClosedError means this idempotency key belongs to a
|
|
2679
|
+
* checkpoint that already closed.
|
|
2680
|
+
*/
|
|
2177
2681
|
async begin(input) {
|
|
2178
2682
|
const idempotencyKey = input.idempotencyKey?.trim();
|
|
2179
2683
|
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
|
|
@@ -2185,7 +2689,7 @@ var CheckoutClient = class {
|
|
|
2185
2689
|
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
2186
2690
|
throw checkoutError("Only USD checkouts are supported");
|
|
2187
2691
|
}
|
|
2188
|
-
const result = await __privateGet(this,
|
|
2692
|
+
const result = await __privateGet(this, _client4).request(
|
|
2189
2693
|
"POST",
|
|
2190
2694
|
"/api/sdk/checkout-checkpoints",
|
|
2191
2695
|
{
|
|
@@ -2200,13 +2704,40 @@ var CheckoutClient = class {
|
|
|
2200
2704
|
},
|
|
2201
2705
|
{ idempotent: true }
|
|
2202
2706
|
);
|
|
2707
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(result.checkpoint.stage)) {
|
|
2708
|
+
throw closedCheckpointError(result.checkpoint);
|
|
2709
|
+
}
|
|
2203
2710
|
return result.checkpoint;
|
|
2204
2711
|
}
|
|
2712
|
+
/**
|
|
2713
|
+
* Waits while the owner decides on a checkpoint in AWAITING_APPROVAL and
|
|
2714
|
+
* resolves to the approved checkpoint. Throws CheckoutApprovalDeniedError,
|
|
2715
|
+
* CheckoutApprovalExpiredError, CheckoutCheckpointClosedError, or
|
|
2716
|
+
* CheckoutApprovalTimeoutError (the spend stays held until you cancel).
|
|
2717
|
+
*/
|
|
2718
|
+
async waitForApproval(checkpoint, options = {}) {
|
|
2719
|
+
const timeoutMs = options.timeoutMs ?? 10 * 6e4;
|
|
2720
|
+
const pollIntervalMs = Math.max(1e3, options.pollIntervalMs ?? 5e3);
|
|
2721
|
+
const deadline = Date.now() + timeoutMs;
|
|
2722
|
+
let current = checkpoint;
|
|
2723
|
+
for (; ; ) {
|
|
2724
|
+
if (current.stage !== "AWAITING_APPROVAL") {
|
|
2725
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(current.stage)) throw closedCheckpointError(current);
|
|
2726
|
+
return current;
|
|
2727
|
+
}
|
|
2728
|
+
const remaining = deadline - Date.now();
|
|
2729
|
+
if (remaining <= 0) throw new CheckoutApprovalTimeoutError(current);
|
|
2730
|
+
await sleep2(Math.min(pollIntervalMs, remaining), options.signal);
|
|
2731
|
+
current = await this.get(current.id);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2205
2734
|
/**
|
|
2206
2735
|
* Second checkpoint, right before the payment is submitted. If the total
|
|
2207
2736
|
* changed since begin() (tax or shipping, for example), this cancels the
|
|
2208
2737
|
* approved checkpoint and starts a new one at the final total, so the agent's
|
|
2209
|
-
* rules see the amount actually being paid. Pay only if this resolves.
|
|
2738
|
+
* rules see the amount actually being paid. Pay only if this resolves. A
|
|
2739
|
+
* CheckoutApprovalPendingError means the owner must approve first: call
|
|
2740
|
+
* waitForApproval(error.checkpoint), then confirm the checkpoint it returns.
|
|
2210
2741
|
*/
|
|
2211
2742
|
async confirm(checkpoint, input) {
|
|
2212
2743
|
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
@@ -2237,22 +2768,31 @@ var CheckoutClient = class {
|
|
|
2237
2768
|
lineItems: input.lineItems ?? checkpoint.cart.lineItems,
|
|
2238
2769
|
totals: input.totals
|
|
2239
2770
|
});
|
|
2771
|
+
if (current.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(current);
|
|
2772
|
+
}
|
|
2773
|
+
try {
|
|
2774
|
+
const result = await __privateGet(this, _client4).request(
|
|
2775
|
+
"POST",
|
|
2776
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(current.id)}/confirm`,
|
|
2777
|
+
{
|
|
2778
|
+
amountMinor: input.amountMinor,
|
|
2779
|
+
currency: "USD",
|
|
2780
|
+
...input.pageUrl ? { pageUrl: input.pageUrl } : {}
|
|
2781
|
+
},
|
|
2782
|
+
{ idempotent: true }
|
|
2783
|
+
);
|
|
2784
|
+
return result.checkpoint;
|
|
2785
|
+
} catch (error) {
|
|
2786
|
+
if (error instanceof ContoError && error.status === 409 && error.code === "APPROVAL_REQUIRED") {
|
|
2787
|
+
const latest = await this.get(current.id);
|
|
2788
|
+
if (latest.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(latest);
|
|
2789
|
+
}
|
|
2790
|
+
throw error;
|
|
2240
2791
|
}
|
|
2241
|
-
const result = await __privateGet(this, _client).request(
|
|
2242
|
-
"POST",
|
|
2243
|
-
`/api/sdk/checkout-checkpoints/${encodeURIComponent(current.id)}/confirm`,
|
|
2244
|
-
{
|
|
2245
|
-
amountMinor: input.amountMinor,
|
|
2246
|
-
currency: "USD",
|
|
2247
|
-
...input.pageUrl ? { pageUrl: input.pageUrl } : {}
|
|
2248
|
-
},
|
|
2249
|
-
{ idempotent: true }
|
|
2250
|
-
);
|
|
2251
|
-
return result.checkpoint;
|
|
2252
2792
|
}
|
|
2253
2793
|
/** Report what happened after a confirmed checkpoint. Safe to repeat with the same outcome. */
|
|
2254
2794
|
async reportOutcome(checkpointId, input) {
|
|
2255
|
-
const result = await __privateGet(this,
|
|
2795
|
+
const result = await __privateGet(this, _client4).request(
|
|
2256
2796
|
"POST",
|
|
2257
2797
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/outcome`,
|
|
2258
2798
|
{
|
|
@@ -2263,9 +2803,12 @@ var CheckoutClient = class {
|
|
|
2263
2803
|
);
|
|
2264
2804
|
return result.checkpoint;
|
|
2265
2805
|
}
|
|
2266
|
-
/**
|
|
2806
|
+
/**
|
|
2807
|
+
* Cancel a checkpoint that was not confirmed and release its spend. A
|
|
2808
|
+
* checkpoint waiting for the owner can be cancelled too.
|
|
2809
|
+
*/
|
|
2267
2810
|
async cancel(checkpointId) {
|
|
2268
|
-
const result = await __privateGet(this,
|
|
2811
|
+
const result = await __privateGet(this, _client4).request(
|
|
2269
2812
|
"POST",
|
|
2270
2813
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/cancel`,
|
|
2271
2814
|
{},
|
|
@@ -2274,189 +2817,25 @@ var CheckoutClient = class {
|
|
|
2274
2817
|
return result.checkpoint;
|
|
2275
2818
|
}
|
|
2276
2819
|
async get(checkpointId) {
|
|
2277
|
-
const result = await __privateGet(this,
|
|
2820
|
+
const result = await __privateGet(this, _client4).request(
|
|
2278
2821
|
"GET",
|
|
2279
2822
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}`
|
|
2280
2823
|
);
|
|
2281
2824
|
return result.checkpoint;
|
|
2282
2825
|
}
|
|
2283
2826
|
};
|
|
2284
|
-
|
|
2285
|
-
var _client2, _defaults;
|
|
2286
|
-
var LinkClient = class {
|
|
2287
|
-
constructor(client, defaults) {
|
|
2288
|
-
__privateAdd(this, _client2);
|
|
2289
|
-
__privateAdd(this, _defaults);
|
|
2290
|
-
__privateSet(this, _client2, client);
|
|
2291
|
-
__privateSet(this, _defaults, defaults ? { ...defaults } : void 0);
|
|
2292
|
-
}
|
|
2293
|
-
/** Inspect the configured live path. This is not a payment authorization or provider connectivity test. */
|
|
2294
|
-
async setup(input = {}) {
|
|
2295
|
-
return __privateGet(this, _client2).getLinkSetup({
|
|
2296
|
-
connectionId: input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
2297
|
-
paymentMethodId: input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2298
|
-
merchantId: input.merchantId
|
|
2299
|
-
});
|
|
2300
|
-
}
|
|
2301
|
-
/** Read masked LIVE funding sources; never selects a source or initiates payment. */
|
|
2302
|
-
async paymentMethods(connectionId) {
|
|
2303
|
-
return __privateGet(this, _client2).listStripeLinkPaymentMethods(
|
|
2304
|
-
this.identifier(connectionId ?? __privateGet(this, _defaults)?.connectionId, "connectionId"),
|
|
2305
|
-
{ mode: "LIVE" }
|
|
2306
|
-
);
|
|
2307
|
-
}
|
|
2308
|
-
/**
|
|
2309
|
-
* Begin one exact intent and execute only if already READY. Otherwise return
|
|
2310
|
-
* the owner's approval action or existing state immediately, without polling.
|
|
2311
|
-
*/
|
|
2312
|
-
async pay(input) {
|
|
2313
|
-
const connectionId = this.identifier(
|
|
2314
|
-
input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
2315
|
-
"connectionId"
|
|
2316
|
-
);
|
|
2317
|
-
const paymentMethodId = this.identifier(
|
|
2318
|
-
input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2319
|
-
"paymentMethodId"
|
|
2320
|
-
);
|
|
2321
|
-
if (!Number.isSafeInteger(input.amountMinor) || input.amountMinor <= 0 || input.currency !== void 0 && input.currency !== "USD" || !Array.isArray(input.lineItems) || input.lineItems.length === 0 || typeof input.purpose !== "string" || !input.purpose.trim()) {
|
|
2322
|
-
throw new ContoError("Supply a positive USD amount in cents, purpose, and line items", {
|
|
2323
|
-
code: "INVALID_LINK_PURCHASE",
|
|
2324
|
-
status: 0
|
|
2325
|
-
});
|
|
2326
|
-
}
|
|
2327
|
-
const itemTotal = input.lineItems.reduce((sum, item) => {
|
|
2328
|
-
if (!item || typeof item.name !== "string" || !item.name.trim() || !Number.isSafeInteger(item.quantity) || item.quantity <= 0 || !Number.isSafeInteger(item.unitAmountMinor) || item.unitAmountMinor < 0) {
|
|
2329
|
-
throw new ContoError(
|
|
2330
|
-
"Each line item needs a name, positive integer quantity, and price in cents",
|
|
2331
|
-
{
|
|
2332
|
-
code: "INVALID_LINK_PURCHASE",
|
|
2333
|
-
status: 0
|
|
2334
|
-
}
|
|
2335
|
-
);
|
|
2336
|
-
}
|
|
2337
|
-
return sum + item.quantity * item.unitAmountMinor;
|
|
2338
|
-
}, 0);
|
|
2339
|
-
if (!input.totals && (!Number.isSafeInteger(itemTotal) || itemTotal !== input.amountMinor)) {
|
|
2340
|
-
throw new ContoError(
|
|
2341
|
-
"Supply the exact totals breakdown when the amount differs from the line items",
|
|
2342
|
-
{
|
|
2343
|
-
code: "LINK_TOTALS_REQUIRED",
|
|
2344
|
-
status: 0
|
|
2345
|
-
}
|
|
2346
|
-
);
|
|
2347
|
-
}
|
|
2348
|
-
const result = await __privateGet(this, _client2).beginCommerceExecution({
|
|
2349
|
-
executionMethod: "LINK_PAY_TOKEN",
|
|
2350
|
-
connectionId,
|
|
2351
|
-
paymentMethodId,
|
|
2352
|
-
idempotencyKey: input.idempotencyKey,
|
|
2353
|
-
amountMinor: input.amountMinor,
|
|
2354
|
-
currency: input.currency ?? "USD",
|
|
2355
|
-
purpose: input.purpose,
|
|
2356
|
-
category: input.category,
|
|
2357
|
-
counterparty: { canonicalId: input.merchantId, checkoutUrl: input.checkoutUrl },
|
|
2358
|
-
cart: {
|
|
2359
|
-
lineItems: input.lineItems,
|
|
2360
|
-
totals: input.totals ?? [{ type: "total", label: "Total", amountMinor: input.amountMinor }]
|
|
2361
|
-
},
|
|
2362
|
-
providerPresentation: {
|
|
2363
|
-
approvalSummary: input.purpose.trim().slice(0, 500),
|
|
2364
|
-
lineItemLabels: input.lineItems.map((item) => item.name.trim().slice(0, 120))
|
|
2365
|
-
}
|
|
2366
|
-
});
|
|
2367
|
-
return this.executeIfReady(result);
|
|
2368
|
-
}
|
|
2369
|
-
/**
|
|
2370
|
-
* Continue the existing payment after approval. Reads before any mutation and
|
|
2371
|
-
* never submits an execution already processing or requiring reconciliation.
|
|
2372
|
-
*/
|
|
2373
|
-
async resume(executionId) {
|
|
2374
|
-
const id = this.identifier(executionId, "executionId");
|
|
2375
|
-
const current = await __privateGet(this, _client2).getCommerceExecution(id);
|
|
2376
|
-
this.assertLinkExecution(current);
|
|
2377
|
-
if (["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(current.execution.status)) {
|
|
2378
|
-
return this.executeIfReady(await __privateGet(this, _client2).refreshCommerceExecution(id));
|
|
2379
|
-
}
|
|
2380
|
-
return this.executeIfReady(current);
|
|
2381
|
-
}
|
|
2382
|
-
/** Observe and reconcile provider state. This method never submits a payment. */
|
|
2383
|
-
async status(executionId) {
|
|
2384
|
-
const id = this.identifier(executionId, "executionId");
|
|
2385
|
-
this.assertLinkExecution(await __privateGet(this, _client2).getCommerceExecution(id));
|
|
2386
|
-
return this.result(await __privateGet(this, _client2).refreshCommerceExecution(id));
|
|
2387
|
-
}
|
|
2388
|
-
/** Request cancellation of this existing Link payment; never retried automatically. */
|
|
2389
|
-
async cancel(executionId) {
|
|
2390
|
-
const id = this.identifier(executionId, "executionId");
|
|
2391
|
-
this.assertLinkExecution(await __privateGet(this, _client2).getCommerceExecution(id));
|
|
2392
|
-
try {
|
|
2393
|
-
return this.result(await __privateGet(this, _client2).cancelCommerceExecution(id));
|
|
2394
|
-
} catch (error) {
|
|
2395
|
-
throw new LinkPaymentExecutionError(id, error);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
identifier(value, field) {
|
|
2399
|
-
if (typeof value !== "string" || !value.trim() || value.length > 255) {
|
|
2400
|
-
throw new ContoError(
|
|
2401
|
-
field === "executionId" ? "executionId is required; use the existing ID returned by link.pay()" : `${field} is required; configure the owner-provisioned Link IDs or supply them explicitly`,
|
|
2402
|
-
{
|
|
2403
|
-
code: "LINK_CONFIGURATION_REQUIRED",
|
|
2404
|
-
status: 0
|
|
2405
|
-
}
|
|
2406
|
-
);
|
|
2407
|
-
}
|
|
2408
|
-
return value.trim();
|
|
2409
|
-
}
|
|
2410
|
-
assertLinkExecution(result) {
|
|
2411
|
-
const execution = result.execution;
|
|
2412
|
-
if (execution?.provider !== "STRIPE_LINK" || execution.executionAdapter !== "STRIPE_LINK" || execution.executionChannel !== "BROWSER" || execution.artifactKind !== void 0 && execution.artifactKind !== "LINK_PAY_TOKEN" || execution.deliveryMode !== void 0 && execution.deliveryMode !== "SERVER_ONLY_ARTIFACT") {
|
|
2413
|
-
throw new ContoError("This execution is not a Link Pay Token checkout", {
|
|
2414
|
-
code: "LINK_EXECUTION_MISMATCH",
|
|
2415
|
-
status: 0
|
|
2416
|
-
});
|
|
2417
|
-
}
|
|
2418
|
-
}
|
|
2419
|
-
async executeIfReady(result) {
|
|
2420
|
-
this.assertLinkExecution(result);
|
|
2421
|
-
if (result.execution.status !== "READY") return this.result(result);
|
|
2422
|
-
try {
|
|
2423
|
-
return this.result(await __privateGet(this, _client2).executeCommerceExecution(result.execution.id));
|
|
2424
|
-
} catch (error) {
|
|
2425
|
-
throw new LinkPaymentExecutionError(result.execution.id, error);
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
result(result) {
|
|
2429
|
-
this.assertLinkExecution(result);
|
|
2430
|
-
const { execution } = result;
|
|
2431
|
-
const awaitingApproval = ["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(
|
|
2432
|
-
execution.status
|
|
2433
|
-
);
|
|
2434
|
-
const nextAction = execution.status === "READY" ? "RESUME" : execution.status === "NEEDS_RECONCILIATION" ? "RECONCILE" : awaitingApproval && execution.approvalUrl ? "APPROVE_IN_LINK" : awaitingApproval || ["DRAFT", "PROCESSING"].includes(execution.status) ? "WAIT" : [
|
|
2435
|
-
"SETTLED",
|
|
2436
|
-
"COMPLETED",
|
|
2437
|
-
"DENIED",
|
|
2438
|
-
"CANCELLED",
|
|
2439
|
-
"FAILED",
|
|
2440
|
-
"RETURNED",
|
|
2441
|
-
"EXPIRED"
|
|
2442
|
-
].includes(execution.status) ? "NONE" : "RECONCILE";
|
|
2443
|
-
return {
|
|
2444
|
-
executionId: execution.id,
|
|
2445
|
-
status: execution.status,
|
|
2446
|
-
nextAction,
|
|
2447
|
-
...nextAction === "APPROVE_IN_LINK" ? { approvalUrl: execution.approvalUrl } : {},
|
|
2448
|
-
execution,
|
|
2449
|
-
..."checkout" in result ? { checkout: result.checkout } : {}
|
|
2450
|
-
};
|
|
2451
|
-
}
|
|
2452
|
-
};
|
|
2453
|
-
_client2 = new WeakMap();
|
|
2454
|
-
_defaults = new WeakMap();
|
|
2827
|
+
_client4 = new WeakMap();
|
|
2455
2828
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2456
2829
|
0 && (module.exports = {
|
|
2830
|
+
AdminPurchasingClient,
|
|
2457
2831
|
BrowserActionFinalizationPendingError,
|
|
2458
2832
|
BrowserCheckoutTargetVerificationError,
|
|
2459
2833
|
CardsClient,
|
|
2834
|
+
CheckoutApprovalDeniedError,
|
|
2835
|
+
CheckoutApprovalExpiredError,
|
|
2836
|
+
CheckoutApprovalPendingError,
|
|
2837
|
+
CheckoutApprovalTimeoutError,
|
|
2838
|
+
CheckoutCheckpointClosedError,
|
|
2460
2839
|
CheckoutClient,
|
|
2461
2840
|
ConnectedCardsClient,
|
|
2462
2841
|
Conto,
|
|
@@ -2466,10 +2845,15 @@ _defaults = new WeakMap();
|
|
|
2466
2845
|
ControlledExternalCardPaymentTool,
|
|
2467
2846
|
ExternalCardConfirmationPendingError,
|
|
2468
2847
|
Giro,
|
|
2469
|
-
|
|
2470
|
-
|
|
2848
|
+
OrganizationPurchasesClient,
|
|
2849
|
+
OrganizationRecurringPurchasesClient,
|
|
2850
|
+
PurchasesClient,
|
|
2851
|
+
RecurringPurchasesClient,
|
|
2471
2852
|
TestCardsClient,
|
|
2472
2853
|
canonicalizeBrowserCheckoutTarget,
|
|
2473
2854
|
createVerifiedBrowserCheckoutDriver,
|
|
2474
|
-
fingerprintBrowserCheckoutTarget
|
|
2855
|
+
fingerprintBrowserCheckoutTarget,
|
|
2856
|
+
isFinalPurchaseStatus,
|
|
2857
|
+
parseServerSentEvents,
|
|
2858
|
+
verifyPurchaseWebhook
|
|
2475
2859
|
});
|