@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.mjs
CHANGED
|
@@ -17,7 +17,7 @@ var require_package = __commonJS({
|
|
|
17
17
|
"package.json"(exports, module) {
|
|
18
18
|
module.exports = {
|
|
19
19
|
name: "@conto_finance/sdk",
|
|
20
|
-
version: "0.6.
|
|
20
|
+
version: "0.6.1",
|
|
21
21
|
description: "Conto SDK for AI agent payment authorization",
|
|
22
22
|
main: "./dist/index.js",
|
|
23
23
|
module: "./dist/index.mjs",
|
|
@@ -42,16 +42,10 @@ var require_package = __commonJS({
|
|
|
42
42
|
types: "./dist/checkout-verifier.d.ts",
|
|
43
43
|
import: "./dist/checkout-verifier.mjs",
|
|
44
44
|
require: "./dist/checkout-verifier.js"
|
|
45
|
-
},
|
|
46
|
-
"./link-testing": {
|
|
47
|
-
types: "./dist/link-testing.d.ts",
|
|
48
|
-
import: "./dist/link-testing.mjs",
|
|
49
|
-
require: "./dist/link-testing.js"
|
|
50
45
|
}
|
|
51
46
|
},
|
|
52
47
|
publishConfig: {
|
|
53
|
-
access: "public"
|
|
54
|
-
tag: "beta"
|
|
48
|
+
access: "public"
|
|
55
49
|
},
|
|
56
50
|
engines: {
|
|
57
51
|
node: ">=20.19.0"
|
|
@@ -64,7 +58,7 @@ var require_package = __commonJS({
|
|
|
64
58
|
dev: "tsup --watch",
|
|
65
59
|
lint: "tsc --noEmit",
|
|
66
60
|
test: "npm run lint && npm run test:contract && npm run test:consumer",
|
|
67
|
-
"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/
|
|
61
|
+
"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",
|
|
68
62
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
69
63
|
"verify:release": "node scripts/verify-release.mjs",
|
|
70
64
|
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
@@ -129,6 +123,372 @@ var import_package_version2 = __toESM(require_package_version());
|
|
|
129
123
|
import * as Sentry2 from "@sentry/node";
|
|
130
124
|
import { randomUUID } from "crypto";
|
|
131
125
|
|
|
126
|
+
// src/purchases.ts
|
|
127
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
128
|
+
var TERMINAL = /* @__PURE__ */ new Set(["succeeded", "blocked", "failed", "cancelled"]);
|
|
129
|
+
var isFinalPurchaseStatus = (status) => TERMINAL.has(status);
|
|
130
|
+
async function* parseServerSentEvents(body) {
|
|
131
|
+
const reader = body.getReader();
|
|
132
|
+
const decoder = new TextDecoder();
|
|
133
|
+
let buffer = "";
|
|
134
|
+
let frame = { data: [] };
|
|
135
|
+
try {
|
|
136
|
+
while (true) {
|
|
137
|
+
const { value, done } = await reader.read();
|
|
138
|
+
if (done) break;
|
|
139
|
+
buffer += decoder.decode(value, { stream: true });
|
|
140
|
+
let newline;
|
|
141
|
+
while ((newline = buffer.search(/\r?\n/)) >= 0) {
|
|
142
|
+
const line = buffer.slice(0, newline);
|
|
143
|
+
buffer = buffer.slice(newline + (buffer[newline] === "\r" ? 2 : 1));
|
|
144
|
+
if (line === "") {
|
|
145
|
+
if (frame.data.length || frame.event)
|
|
146
|
+
yield { id: frame.id, event: frame.event ?? "message", data: frame.data.join("\n") };
|
|
147
|
+
frame = { data: [] };
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (line.startsWith(":")) continue;
|
|
151
|
+
const colon = line.indexOf(":");
|
|
152
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
153
|
+
const raw = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
154
|
+
if (field === "id") frame.id = raw;
|
|
155
|
+
else if (field === "event") frame.event = raw;
|
|
156
|
+
else if (field === "data") frame.data.push(raw);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} finally {
|
|
160
|
+
reader.releaseLock();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
var sleep = (ms, signal) => new Promise((resolve) => {
|
|
164
|
+
const timer = setTimeout(resolve, ms);
|
|
165
|
+
signal?.addEventListener(
|
|
166
|
+
"abort",
|
|
167
|
+
() => {
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
resolve();
|
|
170
|
+
},
|
|
171
|
+
{ once: true }
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
function requirePurchaseId(id) {
|
|
175
|
+
if (!/^[a-z0-9]{8,64}$/i.test(id)) throw new TypeError("purchase id is not valid");
|
|
176
|
+
return encodeURIComponent(id);
|
|
177
|
+
}
|
|
178
|
+
function requireCycleKey(key) {
|
|
179
|
+
if (!/^[A-Za-z0-9._:-]{1,100}$/.test(key)) throw new TypeError("cycleKey is not valid");
|
|
180
|
+
return encodeURIComponent(key);
|
|
181
|
+
}
|
|
182
|
+
var _transport, _base;
|
|
183
|
+
var RecurringPurchasesClient = class {
|
|
184
|
+
constructor(transport, basePath = "/api/sdk/recurring-purchases") {
|
|
185
|
+
__privateAdd(this, _transport);
|
|
186
|
+
__privateAdd(this, _base);
|
|
187
|
+
__privateSet(this, _transport, transport);
|
|
188
|
+
__privateSet(this, _base, basePath);
|
|
189
|
+
}
|
|
190
|
+
async list(buyerId) {
|
|
191
|
+
return __privateGet(this, _transport).request(
|
|
192
|
+
"GET",
|
|
193
|
+
buyerId ? `${__privateGet(this, _base)}?buyerId=${encodeURIComponent(buyerId)}` : __privateGet(this, _base)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
async get(id) {
|
|
197
|
+
return __privateGet(this, _transport).request("GET", `${__privateGet(this, _base)}/${requirePurchaseId(id)}`);
|
|
198
|
+
}
|
|
199
|
+
/** Starts one order. The same cycle key returns the same order. */
|
|
200
|
+
async startCycle(id, cycleKey) {
|
|
201
|
+
requireCycleKey(cycleKey);
|
|
202
|
+
return __privateGet(this, _transport).request(
|
|
203
|
+
"POST",
|
|
204
|
+
`${__privateGet(this, _base)}/${requirePurchaseId(id)}/cycles`,
|
|
205
|
+
{ cycleKey },
|
|
206
|
+
{ idempotent: true }
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
/** Skips one order, also before it starts. */
|
|
210
|
+
async skipCycle(id, cycleKey) {
|
|
211
|
+
return __privateGet(this, _transport).request(
|
|
212
|
+
"POST",
|
|
213
|
+
`${__privateGet(this, _base)}/${requirePurchaseId(id)}/cycles/${requireCycleKey(cycleKey)}/skip`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
_transport = new WeakMap();
|
|
218
|
+
_base = new WeakMap();
|
|
219
|
+
var _client;
|
|
220
|
+
var OrganizationRecurringPurchasesClient = class extends RecurringPurchasesClient {
|
|
221
|
+
constructor(client) {
|
|
222
|
+
super(client, "/api/purchasing/recurring-purchases");
|
|
223
|
+
__privateAdd(this, _client);
|
|
224
|
+
__privateSet(this, _client, client);
|
|
225
|
+
}
|
|
226
|
+
/** Stops a repeat order. An order waiting for its payment time closes unpaid. */
|
|
227
|
+
async cancel(id) {
|
|
228
|
+
return __privateGet(this, _client).request(
|
|
229
|
+
"POST",
|
|
230
|
+
`/api/purchasing/recurring-purchases/${requirePurchaseId(id)}/cancel`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
_client = new WeakMap();
|
|
235
|
+
var _transport2, _base2;
|
|
236
|
+
var PurchasesClient = class {
|
|
237
|
+
constructor(transport, basePath = "/api/sdk/purchases") {
|
|
238
|
+
__privateAdd(this, _transport2);
|
|
239
|
+
__privateAdd(this, _base2);
|
|
240
|
+
__privateSet(this, _transport2, transport);
|
|
241
|
+
__privateSet(this, _base2, basePath);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Starts a purchase and returns at once. Reusing the idempotency key with the
|
|
245
|
+
* same input returns the same purchase, so retrying after a lost response is safe.
|
|
246
|
+
*/
|
|
247
|
+
async create(input, options) {
|
|
248
|
+
const idempotencyKey = options?.idempotencyKey?.trim();
|
|
249
|
+
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255)
|
|
250
|
+
throw new TypeError("idempotencyKey must contain 8 to 255 characters");
|
|
251
|
+
if (!Number.isSafeInteger(input.maxCost?.amountMinor) || input.maxCost.amountMinor <= 0)
|
|
252
|
+
throw new TypeError("maxCost.amountMinor must be a positive integer");
|
|
253
|
+
return __privateGet(this, _transport2).request(
|
|
254
|
+
"POST",
|
|
255
|
+
__privateGet(this, _base2),
|
|
256
|
+
{ ...input, idempotencyKey },
|
|
257
|
+
{ idempotent: true }
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
async list(params = {}) {
|
|
261
|
+
const query = new URLSearchParams();
|
|
262
|
+
for (const [key, value] of Object.entries(params))
|
|
263
|
+
if (value !== void 0) query.set(key, String(value));
|
|
264
|
+
const suffix = query.toString();
|
|
265
|
+
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}${suffix ? `?${suffix}` : ""}`);
|
|
266
|
+
}
|
|
267
|
+
async get(id) {
|
|
268
|
+
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}/${requirePurchaseId(id)}`);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Answers an open action whose authority is `agent_or_buyer` with the fields
|
|
272
|
+
* it lists. The acknowledgment means recorded; the purchase shows the result.
|
|
273
|
+
* The same answer again returns the same acknowledgment.
|
|
274
|
+
*/
|
|
275
|
+
async respond(id, input) {
|
|
276
|
+
return __privateGet(this, _transport2).request(
|
|
277
|
+
"POST",
|
|
278
|
+
`${__privateGet(this, _base2)}/${requirePurchaseId(id)}/responses`,
|
|
279
|
+
input,
|
|
280
|
+
{ idempotent: true }
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
async events(id, params = {}) {
|
|
284
|
+
const query = new URLSearchParams({ after: String(params.after ?? 0) });
|
|
285
|
+
if (params.limit) query.set("limit", String(params.limit));
|
|
286
|
+
return __privateGet(this, _transport2).request("GET", `${__privateGet(this, _base2)}/${requirePurchaseId(id)}/events?${query}`);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Requests cancellation. Before payment the purchase stops. Once payment may
|
|
290
|
+
* have been submitted, `cancellationOutcome` is `too_late` and the purchase
|
|
291
|
+
* stays in reconciliation. Nothing is refunded by this call.
|
|
292
|
+
*/
|
|
293
|
+
async cancel(id) {
|
|
294
|
+
return __privateGet(this, _transport2).request(
|
|
295
|
+
"POST",
|
|
296
|
+
`${__privateGet(this, _base2)}/${requirePurchaseId(id)}/cancel`,
|
|
297
|
+
void 0,
|
|
298
|
+
{ idempotent: true }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Yields the purchase's events in version order until it reaches a final
|
|
303
|
+
* status. Streams with server-sent events and reconnects from the last
|
|
304
|
+
* version; falls back to polling where streaming is unavailable. A duplicate
|
|
305
|
+
* after a reconnect is dropped. A `purchase.resync` event carries the
|
|
306
|
+
* current purchase when the cursor could not be resumed.
|
|
307
|
+
*/
|
|
308
|
+
async *watch(id, options = {}) {
|
|
309
|
+
const path = `${__privateGet(this, _base2)}/${requirePurchaseId(id)}`;
|
|
310
|
+
const mode = options.mode ?? "auto";
|
|
311
|
+
const interval = Math.max(500, options.pollIntervalMs ?? 2e3);
|
|
312
|
+
const deadline = options.maxWaitMs ? Date.now() + options.maxWaitMs : Infinity;
|
|
313
|
+
let cursor = Math.max(0, options.after ?? 0);
|
|
314
|
+
let streaming = mode !== "poll";
|
|
315
|
+
const emit = (event) => {
|
|
316
|
+
if (event.version <= cursor && event.type !== "purchase.resync") return null;
|
|
317
|
+
cursor = Math.max(cursor, event.version);
|
|
318
|
+
return event;
|
|
319
|
+
};
|
|
320
|
+
while (!options.signal?.aborted && Date.now() < deadline) {
|
|
321
|
+
if (streaming) {
|
|
322
|
+
try {
|
|
323
|
+
const response = await __privateGet(this, _transport2).openStream(
|
|
324
|
+
`${path}/events/stream?after=${cursor}`,
|
|
325
|
+
{ signal: options.signal, lastEventId: String(cursor) }
|
|
326
|
+
);
|
|
327
|
+
if (!response.body) throw new Error("streaming unavailable");
|
|
328
|
+
for await (const frame of parseServerSentEvents(response.body)) {
|
|
329
|
+
if (frame.event === "end") return;
|
|
330
|
+
if (frame.event === "error") break;
|
|
331
|
+
if (frame.event === "resync") {
|
|
332
|
+
const purchase = JSON.parse(frame.data);
|
|
333
|
+
const event2 = {
|
|
334
|
+
id: `resync:${purchase.version}`,
|
|
335
|
+
purchaseId: purchase.id,
|
|
336
|
+
version: purchase.version,
|
|
337
|
+
type: "purchase.resync",
|
|
338
|
+
createdAt: purchase.updatedAt,
|
|
339
|
+
data: purchase
|
|
340
|
+
};
|
|
341
|
+
cursor = purchase.version;
|
|
342
|
+
yield event2;
|
|
343
|
+
if (isFinalPurchaseStatus(purchase.status)) return;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (!frame.data) continue;
|
|
347
|
+
const event = emit(JSON.parse(frame.data));
|
|
348
|
+
if (!event) continue;
|
|
349
|
+
yield event;
|
|
350
|
+
if (isFinalPurchaseStatus(event.data.status)) return;
|
|
351
|
+
}
|
|
352
|
+
continue;
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (options.signal?.aborted) return;
|
|
355
|
+
if (isClientError(error)) throw error;
|
|
356
|
+
if (mode === "stream") {
|
|
357
|
+
await sleep(interval, options.signal);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
streaming = false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
let page;
|
|
364
|
+
try {
|
|
365
|
+
page = await this.events(id, { after: cursor, limit: 100 });
|
|
366
|
+
} catch (error) {
|
|
367
|
+
if (codeOf(error) !== "CURSOR_INVALID") throw error;
|
|
368
|
+
const purchase = await this.get(id);
|
|
369
|
+
cursor = purchase.version;
|
|
370
|
+
yield {
|
|
371
|
+
id: `resync:${purchase.version}`,
|
|
372
|
+
purchaseId: purchase.id,
|
|
373
|
+
version: purchase.version,
|
|
374
|
+
type: "purchase.resync",
|
|
375
|
+
createdAt: purchase.updatedAt,
|
|
376
|
+
data: purchase
|
|
377
|
+
};
|
|
378
|
+
if (isFinalPurchaseStatus(purchase.status)) return;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
for (const item of page.items) {
|
|
382
|
+
const event = emit(item);
|
|
383
|
+
if (!event) continue;
|
|
384
|
+
yield event;
|
|
385
|
+
if (isFinalPurchaseStatus(event.data.status)) return;
|
|
386
|
+
}
|
|
387
|
+
if (!page.hasMore) await sleep(interval, options.signal);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
_transport2 = new WeakMap();
|
|
392
|
+
_base2 = new WeakMap();
|
|
393
|
+
var codeOf = (error) => error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
394
|
+
var isClientError = (error) => {
|
|
395
|
+
const status = error && typeof error === "object" && "status" in error ? Number(error.status) : 0;
|
|
396
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
397
|
+
};
|
|
398
|
+
var enc = encodeURIComponent;
|
|
399
|
+
var _client2;
|
|
400
|
+
var OrganizationPurchasesClient = class extends PurchasesClient {
|
|
401
|
+
constructor(client) {
|
|
402
|
+
super(client, "/api/purchasing/purchases");
|
|
403
|
+
__privateAdd(this, _client2);
|
|
404
|
+
__privateSet(this, _client2, client);
|
|
405
|
+
}
|
|
406
|
+
async list(params = {}) {
|
|
407
|
+
const query = new URLSearchParams();
|
|
408
|
+
for (const [key, value] of Object.entries(params))
|
|
409
|
+
if (value !== void 0) query.set(key, String(value));
|
|
410
|
+
const suffix = query.toString();
|
|
411
|
+
return __privateGet(this, _client2).request("GET", `/api/purchasing/purchases${suffix ? `?${suffix}` : ""}`);
|
|
412
|
+
}
|
|
413
|
+
/** A single-use link that opens the purchase's buyer actions on Conto's hosted page. */
|
|
414
|
+
createActionLink(id, input = {}) {
|
|
415
|
+
return __privateGet(this, _client2).request(
|
|
416
|
+
"POST",
|
|
417
|
+
`/api/purchasing/purchases/${requirePurchaseId(id)}/action-sessions`,
|
|
418
|
+
input
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
_client2 = new WeakMap();
|
|
423
|
+
var _client3;
|
|
424
|
+
var AdminPurchasingClient = class {
|
|
425
|
+
constructor(client) {
|
|
426
|
+
__privateAdd(this, _client3);
|
|
427
|
+
this.buyers = {
|
|
428
|
+
/** Maps one of your users to a buyer with its own agent, or returns the existing one. */
|
|
429
|
+
create: (input) => __privateGet(this, _client3).request("POST", "/api/purchasing/buyers", input),
|
|
430
|
+
get: (id) => __privateGet(this, _client3).request("GET", `/api/purchasing/buyers/${enc(id)}`),
|
|
431
|
+
list: (params = {}) => __privateGet(this, _client3).request("GET", `/api/purchasing/buyers?limit=${params.limit ?? 20}&offset=${params.offset ?? 0}`),
|
|
432
|
+
/** Stops the buyer's agent. Purchases that may already be paid stay visible. */
|
|
433
|
+
stop: (id) => __privateGet(this, _client3).request("POST", `/api/purchasing/buyers/${enc(id)}/stop`)
|
|
434
|
+
};
|
|
435
|
+
this.paymentMethods = {
|
|
436
|
+
/** A single-use link to the hosted setup page: limits, card and delegation. */
|
|
437
|
+
createSetupLink: (input) => __privateGet(this, _client3).request(
|
|
438
|
+
"POST",
|
|
439
|
+
"/api/purchasing/payment-methods/setup-sessions",
|
|
440
|
+
input
|
|
441
|
+
),
|
|
442
|
+
list: (buyerId) => __privateGet(this, _client3).request(
|
|
443
|
+
"GET",
|
|
444
|
+
`/api/purchasing/payment-methods?buyerId=${enc(buyerId)}`
|
|
445
|
+
),
|
|
446
|
+
remove: (id) => __privateGet(this, _client3).request(
|
|
447
|
+
"DELETE",
|
|
448
|
+
`/api/purchasing/payment-methods/${enc(id)}`
|
|
449
|
+
)
|
|
450
|
+
};
|
|
451
|
+
this.delegations = {
|
|
452
|
+
list: (buyerId) => __privateGet(this, _client3).request(
|
|
453
|
+
"GET",
|
|
454
|
+
`/api/purchasing/delegations?buyerId=${enc(buyerId)}`
|
|
455
|
+
),
|
|
456
|
+
get: (id) => __privateGet(this, _client3).request("GET", `/api/purchasing/delegations/${enc(id)}`),
|
|
457
|
+
revoke: (id) => __privateGet(this, _client3).request(
|
|
458
|
+
"POST",
|
|
459
|
+
`/api/purchasing/delegations/${enc(id)}/revoke`
|
|
460
|
+
)
|
|
461
|
+
};
|
|
462
|
+
this.settings = {
|
|
463
|
+
get: () => __privateGet(this, _client3).request("GET", "/api/purchasing/settings"),
|
|
464
|
+
update: (input) => __privateGet(this, _client3).request(
|
|
465
|
+
"PUT",
|
|
466
|
+
"/api/purchasing/settings",
|
|
467
|
+
input
|
|
468
|
+
)
|
|
469
|
+
};
|
|
470
|
+
__privateSet(this, _client3, client);
|
|
471
|
+
this.purchases = new OrganizationPurchasesClient(client);
|
|
472
|
+
this.recurringPurchases = new OrganizationRecurringPurchasesClient(client);
|
|
473
|
+
}
|
|
474
|
+
/** What this organization can run now: test mode always, live once Conto enables it. */
|
|
475
|
+
capabilities() {
|
|
476
|
+
return __privateGet(this, _client3).request("GET", "/api/purchasing/capabilities");
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
_client3 = new WeakMap();
|
|
480
|
+
function verifyPurchaseWebhook(input) {
|
|
481
|
+
const expected = createHmac("sha256", input.secret).update(input.body).digest();
|
|
482
|
+
const received = Buffer.from(input.signature ?? "", "hex");
|
|
483
|
+
if (received.length !== expected.length || !timingSafeEqual(received, expected))
|
|
484
|
+
throw new Error("Invalid webhook signature");
|
|
485
|
+
const payload = JSON.parse(input.body);
|
|
486
|
+
const age = ((input.now ?? /* @__PURE__ */ new Date()).getTime() - Date.parse(payload.timestamp)) / 1e3;
|
|
487
|
+
if (!Number.isFinite(age) || Math.abs(age) > (input.toleranceSeconds ?? 300))
|
|
488
|
+
throw new Error("Webhook timestamp is outside the tolerance");
|
|
489
|
+
return payload;
|
|
490
|
+
}
|
|
491
|
+
|
|
132
492
|
// src/http.ts
|
|
133
493
|
var PUBLIC_PAYMENT_REASONS = /* @__PURE__ */ new Set([
|
|
134
494
|
"The available balance is insufficient for this payment.",
|
|
@@ -386,15 +746,51 @@ var ContoAdmin = class {
|
|
|
386
746
|
this.cards = new AdminCardsClient(this);
|
|
387
747
|
this.policies = new PoliciesClient(this);
|
|
388
748
|
this.team = new TeamClient(this);
|
|
389
|
-
this.
|
|
749
|
+
this.purchasing = new AdminPurchasingClient(this);
|
|
390
750
|
}
|
|
391
|
-
/**
|
|
392
|
-
|
|
751
|
+
/**
|
|
752
|
+
* Opens an authenticated server-sent event stream. Used by
|
|
753
|
+
* `purchasing.purchases.watch()`. The server closes it after about 50 seconds.
|
|
754
|
+
* @internal
|
|
755
|
+
*/
|
|
756
|
+
async openStream(path, init = {}) {
|
|
757
|
+
const controller = new AbortController();
|
|
758
|
+
const abort = () => controller.abort();
|
|
759
|
+
init.signal?.addEventListener("abort", abort, { once: true });
|
|
760
|
+
const timeoutId = setTimeout(abort, Math.max(this.timeout, 7e4));
|
|
761
|
+
try {
|
|
762
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
763
|
+
method: "GET",
|
|
764
|
+
headers: {
|
|
765
|
+
Authorization: `Bearer ${__privateGet(this, _orgApiKey)}`,
|
|
766
|
+
Accept: "text/event-stream",
|
|
767
|
+
"User-Agent": `@conto_finance/sdk-admin/${import_package_version.SDK_PACKAGE_VERSION}`,
|
|
768
|
+
...init.lastEventId ? { "Last-Event-ID": init.lastEventId } : {}
|
|
769
|
+
},
|
|
770
|
+
signal: controller.signal
|
|
771
|
+
});
|
|
772
|
+
if (!response.ok) {
|
|
773
|
+
const data = await readResponseData(response);
|
|
774
|
+
throw new ContoAdminError(
|
|
775
|
+
responseErrorMessage(data, response.status),
|
|
776
|
+
responseErrorMetadata(response, data)
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
return response;
|
|
780
|
+
} finally {
|
|
781
|
+
clearTimeout(timeoutId);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Make an authenticated request to the Conto API. Reads retry. A write retries
|
|
786
|
+
* only when `idempotent` says the API deduplicates it, as purchase creation does.
|
|
787
|
+
*/
|
|
788
|
+
async request(method, path, body, options = {}) {
|
|
393
789
|
const url = `${this.baseUrl}${path}`;
|
|
394
790
|
const controller = new AbortController();
|
|
395
791
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
396
792
|
const normalizedMethod = method.toUpperCase();
|
|
397
|
-
const retryable = ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod);
|
|
793
|
+
const retryable = ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod) || options.idempotent === true;
|
|
398
794
|
try {
|
|
399
795
|
const response = await fetchWithRetry(
|
|
400
796
|
url,
|
|
@@ -442,22 +838,6 @@ var ContoAdmin = class {
|
|
|
442
838
|
}
|
|
443
839
|
};
|
|
444
840
|
_orgApiKey = new WeakMap();
|
|
445
|
-
var PaymentRailsClient = class {
|
|
446
|
-
constructor(client) {
|
|
447
|
-
this.client = client;
|
|
448
|
-
}
|
|
449
|
-
/** Read gate and configuration readiness without calling Stripe. */
|
|
450
|
-
async getStripeLinkCheckoutReadiness() {
|
|
451
|
-
return this.client.request("GET", "/api/stripe-link/checkout-readiness");
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Ask Conto to verify that its configured Stripe credential resolves to its
|
|
455
|
-
* configured merchant account. This call never returns either identifier.
|
|
456
|
-
*/
|
|
457
|
-
async verifyStripeLinkCheckoutAccount() {
|
|
458
|
-
return this.client.request("POST", "/api/stripe-link/checkout-readiness");
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
841
|
var AdminCardsClient = class {
|
|
462
842
|
constructor(client) {
|
|
463
843
|
this.client = client;
|
|
@@ -912,27 +1292,6 @@ var ContoError = class extends Error {
|
|
|
912
1292
|
}
|
|
913
1293
|
}
|
|
914
1294
|
};
|
|
915
|
-
var LinkPaymentExecutionError = class extends ContoError {
|
|
916
|
-
constructor(executionId, error) {
|
|
917
|
-
const original = error instanceof ContoError ? error : void 0;
|
|
918
|
-
super(
|
|
919
|
-
original?.message ?? "Link execution could not be confirmed",
|
|
920
|
-
{
|
|
921
|
-
code: original?.code ?? "LINK_EXECUTION_UNCONFIRMED",
|
|
922
|
-
status: original?.status ?? 0,
|
|
923
|
-
requestId: original?.requestId,
|
|
924
|
-
retryAfter: original?.retryAfter,
|
|
925
|
-
fieldErrors: original?.fieldErrors,
|
|
926
|
-
reasons: original?.reasons,
|
|
927
|
-
retryGuidance: "Call link.status(executionId) to reconcile. Do not create another purchase or retry execution blindly."
|
|
928
|
-
},
|
|
929
|
-
original?.diagnostics
|
|
930
|
-
);
|
|
931
|
-
this.nextAction = "RECONCILE";
|
|
932
|
-
this.name = "LinkPaymentExecutionError";
|
|
933
|
-
this.executionId = executionId;
|
|
934
|
-
}
|
|
935
|
-
};
|
|
936
1295
|
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
937
1296
|
constructor(requestId, confirmationInput, receipt) {
|
|
938
1297
|
super("External card execution completed but confirmation is pending", {
|
|
@@ -960,6 +1319,52 @@ var BrowserActionFinalizationPendingError = class extends ContoError {
|
|
|
960
1319
|
this.observation = input.observation;
|
|
961
1320
|
}
|
|
962
1321
|
};
|
|
1322
|
+
var CheckoutCheckpointClosedError = class extends ContoError {
|
|
1323
|
+
constructor(checkpoint, message = `The checkpoint is ${checkpoint.stage.toLowerCase().replace(/_/g, " ")}. Stop this checkout.`, code = "CHECKOUT_CHECKPOINT_CLOSED") {
|
|
1324
|
+
super(message, { code, status: 0 });
|
|
1325
|
+
this.name = "CheckoutCheckpointClosedError";
|
|
1326
|
+
this.checkpoint = checkpoint;
|
|
1327
|
+
this.stage = checkpoint.stage;
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
var CheckoutApprovalDeniedError = class extends CheckoutCheckpointClosedError {
|
|
1331
|
+
constructor(checkpoint) {
|
|
1332
|
+
super(checkpoint, "The owner rejected this purchase. Do not pay.", "CHECKOUT_APPROVAL_DENIED");
|
|
1333
|
+
this.name = "CheckoutApprovalDeniedError";
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
var CheckoutApprovalExpiredError = class extends CheckoutCheckpointClosedError {
|
|
1337
|
+
constructor(checkpoint) {
|
|
1338
|
+
super(
|
|
1339
|
+
checkpoint,
|
|
1340
|
+
"The owner did not approve this purchase in time. Do not pay.",
|
|
1341
|
+
"CHECKOUT_APPROVAL_EXPIRED"
|
|
1342
|
+
);
|
|
1343
|
+
this.name = "CheckoutApprovalExpiredError";
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
var CheckoutApprovalPendingError = class extends ContoError {
|
|
1347
|
+
constructor(checkpoint) {
|
|
1348
|
+
super("The owner has not approved this purchase yet. Do not pay.", {
|
|
1349
|
+
code: "CHECKOUT_APPROVAL_PENDING",
|
|
1350
|
+
status: 0,
|
|
1351
|
+
retryGuidance: "Call checkout.waitForApproval(error.checkpoint), then confirm the approved checkpoint."
|
|
1352
|
+
});
|
|
1353
|
+
this.name = "CheckoutApprovalPendingError";
|
|
1354
|
+
this.checkpoint = checkpoint;
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
var CheckoutApprovalTimeoutError = class extends ContoError {
|
|
1358
|
+
constructor(checkpoint) {
|
|
1359
|
+
super("The owner has not decided yet. Do not pay.", {
|
|
1360
|
+
code: "CHECKOUT_APPROVAL_TIMEOUT",
|
|
1361
|
+
status: 0,
|
|
1362
|
+
retryGuidance: "Wait again with checkout.waitForApproval, or call checkout.cancel to release the held spend."
|
|
1363
|
+
});
|
|
1364
|
+
this.name = "CheckoutApprovalTimeoutError";
|
|
1365
|
+
this.checkpoint = checkpoint;
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
963
1368
|
function generatedIdempotencyKey(prefix) {
|
|
964
1369
|
return `${prefix}_${randomUUID()}`;
|
|
965
1370
|
}
|
|
@@ -1022,7 +1427,6 @@ var Conto = class {
|
|
|
1022
1427
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
1023
1428
|
assertSecureBaseUrl(this.baseUrl);
|
|
1024
1429
|
this.timeout = config.timeout || 3e4;
|
|
1025
|
-
this.link = new LinkClient(this, config.link);
|
|
1026
1430
|
this.checkout = new CheckoutClient(this);
|
|
1027
1431
|
this.payments = new PaymentsClient(this);
|
|
1028
1432
|
this.cards = new CardsClient(this);
|
|
@@ -1030,31 +1434,8 @@ var Conto = class {
|
|
|
1030
1434
|
this.testCards = new TestCardsClient(this);
|
|
1031
1435
|
this.x402 = new X402Client(this);
|
|
1032
1436
|
this.mpp = new MppClient(this);
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
* List masked Link payment methods for a session-created connection that is
|
|
1036
|
-
* explicitly bound to this agent. No Link token or payment artifact is
|
|
1037
|
-
* returned through the agent SDK.
|
|
1038
|
-
*/
|
|
1039
|
-
async listStripeLinkPaymentMethods(connectionId, options) {
|
|
1040
|
-
return this.request(
|
|
1041
|
-
"GET",
|
|
1042
|
-
withQuery("/api/sdk/stripe-link/payment-methods", {
|
|
1043
|
-
connectionId,
|
|
1044
|
-
...options?.mode ? { mode: options.mode } : {}
|
|
1045
|
-
})
|
|
1046
|
-
);
|
|
1047
|
-
}
|
|
1048
|
-
/** Read live Link configuration diagnostics without retrieving credentials or authorizing payment. */
|
|
1049
|
-
async getLinkSetup(input = {}) {
|
|
1050
|
-
return this.request(
|
|
1051
|
-
"GET",
|
|
1052
|
-
withQuery("/api/sdk/stripe-link/setup", {
|
|
1053
|
-
connectionId: input.connectionId,
|
|
1054
|
-
paymentMethodId: input.paymentMethodId,
|
|
1055
|
-
merchantId: input.merchantId
|
|
1056
|
-
})
|
|
1057
|
-
);
|
|
1437
|
+
this.purchases = new PurchasesClient(this);
|
|
1438
|
+
this.recurringPurchases = new RecurringPurchasesClient(this);
|
|
1058
1439
|
}
|
|
1059
1440
|
/**
|
|
1060
1441
|
* Normalize and durably revise a partial agent purchase proposal without
|
|
@@ -1136,6 +1517,22 @@ var Conto = class {
|
|
|
1136
1517
|
rethrowAmbiguousCommerceMutation(error, executionId, "authorize");
|
|
1137
1518
|
}
|
|
1138
1519
|
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Report whether an authorized guarded browser checkout was paid. PAID keeps
|
|
1522
|
+
* the held spend counted, NOT_PAID releases it, and UNKNOWN keeps it held for
|
|
1523
|
+
* the owner to review. Safe to repeat with the same outcome.
|
|
1524
|
+
*/
|
|
1525
|
+
async reportCommerceExecutionOutcome(executionId, input) {
|
|
1526
|
+
return this.request(
|
|
1527
|
+
"POST",
|
|
1528
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/outcome`,
|
|
1529
|
+
{
|
|
1530
|
+
outcome: input.outcome,
|
|
1531
|
+
...input.reference ? { reference: input.reference } : {}
|
|
1532
|
+
},
|
|
1533
|
+
{ idempotent: true }
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1139
1536
|
/** Claim a durable browser action for one stable privileged-runtime attempt. */
|
|
1140
1537
|
async claimBrowserAction(authorizationId, dispatchAttemptId, browserSessionId, action) {
|
|
1141
1538
|
return this.request(
|
|
@@ -1323,6 +1720,39 @@ var Conto = class {
|
|
|
1323
1720
|
async setup() {
|
|
1324
1721
|
return this.request("GET", "/api/sdk/setup");
|
|
1325
1722
|
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Opens an authenticated server-sent event stream. Used by
|
|
1725
|
+
* `purchases.watch()`. The server closes it after about 50 seconds.
|
|
1726
|
+
* @internal
|
|
1727
|
+
*/
|
|
1728
|
+
async openStream(path, init = {}) {
|
|
1729
|
+
const controller = new AbortController();
|
|
1730
|
+
const abort = () => controller.abort();
|
|
1731
|
+
init.signal?.addEventListener("abort", abort, { once: true });
|
|
1732
|
+
const timeoutId = setTimeout(abort, Math.max(this.timeout, 7e4));
|
|
1733
|
+
try {
|
|
1734
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
1735
|
+
method: "GET",
|
|
1736
|
+
headers: {
|
|
1737
|
+
Authorization: `Bearer ${__privateGet(this, _apiKey)}`,
|
|
1738
|
+
Accept: "text/event-stream",
|
|
1739
|
+
"User-Agent": `@conto_finance/sdk/${import_package_version2.SDK_PACKAGE_VERSION}`,
|
|
1740
|
+
...init.lastEventId ? { "Last-Event-ID": init.lastEventId } : {}
|
|
1741
|
+
},
|
|
1742
|
+
signal: controller.signal
|
|
1743
|
+
});
|
|
1744
|
+
if (!response.ok) {
|
|
1745
|
+
const data = await readResponseData(response);
|
|
1746
|
+
throw new ContoError(
|
|
1747
|
+
responseErrorMessage(data, response.status),
|
|
1748
|
+
responseErrorMetadata(response, data)
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
return response;
|
|
1752
|
+
} finally {
|
|
1753
|
+
clearTimeout(timeoutId);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1326
1756
|
/** Make an authenticated request to the Conto API */
|
|
1327
1757
|
async request(method, path, body, options = {}) {
|
|
1328
1758
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -1595,12 +2025,6 @@ var ConnectedCardsClient = class {
|
|
|
1595
2025
|
currency: input.currency ?? "USD"
|
|
1596
2026
|
});
|
|
1597
2027
|
}
|
|
1598
|
-
/**
|
|
1599
|
-
* List the connected cards and active mandates this agent may draw against.
|
|
1600
|
-
*
|
|
1601
|
-
* The response is masked: no PAN and no provider tokens. This is a read-only
|
|
1602
|
-
* GET, so transport retry on transient failures is safe.
|
|
1603
|
-
*/
|
|
1604
2028
|
/** Record receipt after a fresh draw. Does not confirm checkout or payment settlement. */
|
|
1605
2029
|
async acknowledgeDelivery(requestId) {
|
|
1606
2030
|
return this.client.request(
|
|
@@ -1610,6 +2034,39 @@ var ConnectedCardsClient = class {
|
|
|
1610
2034
|
{ idempotent: true }
|
|
1611
2035
|
);
|
|
1612
2036
|
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Report what happened with the card number after checkout.
|
|
2039
|
+
*
|
|
2040
|
+
* `USED` at the full approved amount closes the draw. `NOT_USED`, or `USED`
|
|
2041
|
+
* with a smaller `amountMinor`, is recorded and shown to a person, who
|
|
2042
|
+
* confirms it before the held amount is released. A report never releases
|
|
2043
|
+
* held spend by itself and does not confirm settlement. Safe to repeat with
|
|
2044
|
+
* the same report. A different second report gets HTTP 409.
|
|
2045
|
+
*
|
|
2046
|
+
* @example
|
|
2047
|
+
* ```typescript
|
|
2048
|
+
* await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'USED' });
|
|
2049
|
+
* await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'USED', amountMinor: 1850 });
|
|
2050
|
+
* await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'NOT_USED' });
|
|
2051
|
+
* ```
|
|
2052
|
+
*/
|
|
2053
|
+
async reportOutcome(requestId, report) {
|
|
2054
|
+
return this.client.request(
|
|
2055
|
+
"POST",
|
|
2056
|
+
`/api/sdk/connected-cards/draws/${encodeURIComponent(requestId)}/outcome`,
|
|
2057
|
+
{
|
|
2058
|
+
outcome: report.outcome,
|
|
2059
|
+
...report.amountMinor !== void 0 ? { amountMinor: report.amountMinor } : {}
|
|
2060
|
+
},
|
|
2061
|
+
{ idempotent: true }
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* List the connected cards and active mandates this agent may draw against.
|
|
2066
|
+
*
|
|
2067
|
+
* The response is masked: no PAN and no provider tokens. This is a read-only
|
|
2068
|
+
* GET, so transport retry on transient failures is safe.
|
|
2069
|
+
*/
|
|
1613
2070
|
async list() {
|
|
1614
2071
|
return this.client.request("GET", "/api/sdk/connected-cards");
|
|
1615
2072
|
}
|
|
@@ -1998,13 +2455,49 @@ function checkpointCart(amountMinor, lineItems, totals) {
|
|
|
1998
2455
|
totals: totals ?? [{ type: "total", label: "Total", amountMinor }]
|
|
1999
2456
|
};
|
|
2000
2457
|
}
|
|
2001
|
-
var
|
|
2458
|
+
var CLOSED_CHECKPOINT_STAGES = [
|
|
2459
|
+
"DENIED",
|
|
2460
|
+
"CANCELLED",
|
|
2461
|
+
"EXPIRED",
|
|
2462
|
+
"NOT_PAID"
|
|
2463
|
+
];
|
|
2464
|
+
function closedCheckpointError(checkpoint) {
|
|
2465
|
+
if (checkpoint.approval?.status === "DENIED") return new CheckoutApprovalDeniedError(checkpoint);
|
|
2466
|
+
if (checkpoint.approval?.status === "EXPIRED") {
|
|
2467
|
+
return new CheckoutApprovalExpiredError(checkpoint);
|
|
2468
|
+
}
|
|
2469
|
+
return new CheckoutCheckpointClosedError(checkpoint);
|
|
2470
|
+
}
|
|
2471
|
+
function sleep2(ms, signal) {
|
|
2472
|
+
return new Promise((resolve, reject) => {
|
|
2473
|
+
if (signal?.aborted) {
|
|
2474
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
const onAbort = () => {
|
|
2478
|
+
clearTimeout(timer);
|
|
2479
|
+
reject(signal?.reason ?? new Error("Aborted"));
|
|
2480
|
+
};
|
|
2481
|
+
const timer = setTimeout(() => {
|
|
2482
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2483
|
+
resolve();
|
|
2484
|
+
}, ms);
|
|
2485
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
var _client4;
|
|
2002
2489
|
var CheckoutClient = class {
|
|
2003
2490
|
constructor(client) {
|
|
2004
|
-
__privateAdd(this,
|
|
2005
|
-
__privateSet(this,
|
|
2491
|
+
__privateAdd(this, _client4);
|
|
2492
|
+
__privateSet(this, _client4, client);
|
|
2006
2493
|
}
|
|
2007
|
-
/**
|
|
2494
|
+
/**
|
|
2495
|
+
* First checkpoint. Open the payment method only when the stage is APPROVED.
|
|
2496
|
+
* AWAITING_APPROVAL means the owner must decide first: call waitForApproval().
|
|
2497
|
+
* A ContoError means Conto did not approve: stop the checkout. A
|
|
2498
|
+
* CheckoutCheckpointClosedError means this idempotency key belongs to a
|
|
2499
|
+
* checkpoint that already closed.
|
|
2500
|
+
*/
|
|
2008
2501
|
async begin(input) {
|
|
2009
2502
|
const idempotencyKey = input.idempotencyKey?.trim();
|
|
2010
2503
|
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
|
|
@@ -2016,7 +2509,7 @@ var CheckoutClient = class {
|
|
|
2016
2509
|
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
2017
2510
|
throw checkoutError("Only USD checkouts are supported");
|
|
2018
2511
|
}
|
|
2019
|
-
const result = await __privateGet(this,
|
|
2512
|
+
const result = await __privateGet(this, _client4).request(
|
|
2020
2513
|
"POST",
|
|
2021
2514
|
"/api/sdk/checkout-checkpoints",
|
|
2022
2515
|
{
|
|
@@ -2031,13 +2524,40 @@ var CheckoutClient = class {
|
|
|
2031
2524
|
},
|
|
2032
2525
|
{ idempotent: true }
|
|
2033
2526
|
);
|
|
2527
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(result.checkpoint.stage)) {
|
|
2528
|
+
throw closedCheckpointError(result.checkpoint);
|
|
2529
|
+
}
|
|
2034
2530
|
return result.checkpoint;
|
|
2035
2531
|
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Waits while the owner decides on a checkpoint in AWAITING_APPROVAL and
|
|
2534
|
+
* resolves to the approved checkpoint. Throws CheckoutApprovalDeniedError,
|
|
2535
|
+
* CheckoutApprovalExpiredError, CheckoutCheckpointClosedError, or
|
|
2536
|
+
* CheckoutApprovalTimeoutError (the spend stays held until you cancel).
|
|
2537
|
+
*/
|
|
2538
|
+
async waitForApproval(checkpoint, options = {}) {
|
|
2539
|
+
const timeoutMs = options.timeoutMs ?? 10 * 6e4;
|
|
2540
|
+
const pollIntervalMs = Math.max(1e3, options.pollIntervalMs ?? 5e3);
|
|
2541
|
+
const deadline = Date.now() + timeoutMs;
|
|
2542
|
+
let current = checkpoint;
|
|
2543
|
+
for (; ; ) {
|
|
2544
|
+
if (current.stage !== "AWAITING_APPROVAL") {
|
|
2545
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(current.stage)) throw closedCheckpointError(current);
|
|
2546
|
+
return current;
|
|
2547
|
+
}
|
|
2548
|
+
const remaining = deadline - Date.now();
|
|
2549
|
+
if (remaining <= 0) throw new CheckoutApprovalTimeoutError(current);
|
|
2550
|
+
await sleep2(Math.min(pollIntervalMs, remaining), options.signal);
|
|
2551
|
+
current = await this.get(current.id);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2036
2554
|
/**
|
|
2037
2555
|
* Second checkpoint, right before the payment is submitted. If the total
|
|
2038
2556
|
* changed since begin() (tax or shipping, for example), this cancels the
|
|
2039
2557
|
* approved checkpoint and starts a new one at the final total, so the agent's
|
|
2040
|
-
* rules see the amount actually being paid. Pay only if this resolves.
|
|
2558
|
+
* rules see the amount actually being paid. Pay only if this resolves. A
|
|
2559
|
+
* CheckoutApprovalPendingError means the owner must approve first: call
|
|
2560
|
+
* waitForApproval(error.checkpoint), then confirm the checkpoint it returns.
|
|
2041
2561
|
*/
|
|
2042
2562
|
async confirm(checkpoint, input) {
|
|
2043
2563
|
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
@@ -2068,22 +2588,31 @@ var CheckoutClient = class {
|
|
|
2068
2588
|
lineItems: input.lineItems ?? checkpoint.cart.lineItems,
|
|
2069
2589
|
totals: input.totals
|
|
2070
2590
|
});
|
|
2591
|
+
if (current.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(current);
|
|
2592
|
+
}
|
|
2593
|
+
try {
|
|
2594
|
+
const result = await __privateGet(this, _client4).request(
|
|
2595
|
+
"POST",
|
|
2596
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(current.id)}/confirm`,
|
|
2597
|
+
{
|
|
2598
|
+
amountMinor: input.amountMinor,
|
|
2599
|
+
currency: "USD",
|
|
2600
|
+
...input.pageUrl ? { pageUrl: input.pageUrl } : {}
|
|
2601
|
+
},
|
|
2602
|
+
{ idempotent: true }
|
|
2603
|
+
);
|
|
2604
|
+
return result.checkpoint;
|
|
2605
|
+
} catch (error) {
|
|
2606
|
+
if (error instanceof ContoError && error.status === 409 && error.code === "APPROVAL_REQUIRED") {
|
|
2607
|
+
const latest = await this.get(current.id);
|
|
2608
|
+
if (latest.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(latest);
|
|
2609
|
+
}
|
|
2610
|
+
throw error;
|
|
2071
2611
|
}
|
|
2072
|
-
const result = await __privateGet(this, _client).request(
|
|
2073
|
-
"POST",
|
|
2074
|
-
`/api/sdk/checkout-checkpoints/${encodeURIComponent(current.id)}/confirm`,
|
|
2075
|
-
{
|
|
2076
|
-
amountMinor: input.amountMinor,
|
|
2077
|
-
currency: "USD",
|
|
2078
|
-
...input.pageUrl ? { pageUrl: input.pageUrl } : {}
|
|
2079
|
-
},
|
|
2080
|
-
{ idempotent: true }
|
|
2081
|
-
);
|
|
2082
|
-
return result.checkpoint;
|
|
2083
2612
|
}
|
|
2084
2613
|
/** Report what happened after a confirmed checkpoint. Safe to repeat with the same outcome. */
|
|
2085
2614
|
async reportOutcome(checkpointId, input) {
|
|
2086
|
-
const result = await __privateGet(this,
|
|
2615
|
+
const result = await __privateGet(this, _client4).request(
|
|
2087
2616
|
"POST",
|
|
2088
2617
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/outcome`,
|
|
2089
2618
|
{
|
|
@@ -2094,9 +2623,12 @@ var CheckoutClient = class {
|
|
|
2094
2623
|
);
|
|
2095
2624
|
return result.checkpoint;
|
|
2096
2625
|
}
|
|
2097
|
-
/**
|
|
2626
|
+
/**
|
|
2627
|
+
* Cancel a checkpoint that was not confirmed and release its spend. A
|
|
2628
|
+
* checkpoint waiting for the owner can be cancelled too.
|
|
2629
|
+
*/
|
|
2098
2630
|
async cancel(checkpointId) {
|
|
2099
|
-
const result = await __privateGet(this,
|
|
2631
|
+
const result = await __privateGet(this, _client4).request(
|
|
2100
2632
|
"POST",
|
|
2101
2633
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/cancel`,
|
|
2102
2634
|
{},
|
|
@@ -2105,188 +2637,24 @@ var CheckoutClient = class {
|
|
|
2105
2637
|
return result.checkpoint;
|
|
2106
2638
|
}
|
|
2107
2639
|
async get(checkpointId) {
|
|
2108
|
-
const result = await __privateGet(this,
|
|
2640
|
+
const result = await __privateGet(this, _client4).request(
|
|
2109
2641
|
"GET",
|
|
2110
2642
|
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}`
|
|
2111
2643
|
);
|
|
2112
2644
|
return result.checkpoint;
|
|
2113
2645
|
}
|
|
2114
2646
|
};
|
|
2115
|
-
|
|
2116
|
-
var _client2, _defaults;
|
|
2117
|
-
var LinkClient = class {
|
|
2118
|
-
constructor(client, defaults) {
|
|
2119
|
-
__privateAdd(this, _client2);
|
|
2120
|
-
__privateAdd(this, _defaults);
|
|
2121
|
-
__privateSet(this, _client2, client);
|
|
2122
|
-
__privateSet(this, _defaults, defaults ? { ...defaults } : void 0);
|
|
2123
|
-
}
|
|
2124
|
-
/** Inspect the configured live path. This is not a payment authorization or provider connectivity test. */
|
|
2125
|
-
async setup(input = {}) {
|
|
2126
|
-
return __privateGet(this, _client2).getLinkSetup({
|
|
2127
|
-
connectionId: input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
2128
|
-
paymentMethodId: input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2129
|
-
merchantId: input.merchantId
|
|
2130
|
-
});
|
|
2131
|
-
}
|
|
2132
|
-
/** Read masked LIVE funding sources; never selects a source or initiates payment. */
|
|
2133
|
-
async paymentMethods(connectionId) {
|
|
2134
|
-
return __privateGet(this, _client2).listStripeLinkPaymentMethods(
|
|
2135
|
-
this.identifier(connectionId ?? __privateGet(this, _defaults)?.connectionId, "connectionId"),
|
|
2136
|
-
{ mode: "LIVE" }
|
|
2137
|
-
);
|
|
2138
|
-
}
|
|
2139
|
-
/**
|
|
2140
|
-
* Begin one exact intent and execute only if already READY. Otherwise return
|
|
2141
|
-
* the owner's approval action or existing state immediately, without polling.
|
|
2142
|
-
*/
|
|
2143
|
-
async pay(input) {
|
|
2144
|
-
const connectionId = this.identifier(
|
|
2145
|
-
input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
2146
|
-
"connectionId"
|
|
2147
|
-
);
|
|
2148
|
-
const paymentMethodId = this.identifier(
|
|
2149
|
-
input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2150
|
-
"paymentMethodId"
|
|
2151
|
-
);
|
|
2152
|
-
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()) {
|
|
2153
|
-
throw new ContoError("Supply a positive USD amount in cents, purpose, and line items", {
|
|
2154
|
-
code: "INVALID_LINK_PURCHASE",
|
|
2155
|
-
status: 0
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
const itemTotal = input.lineItems.reduce((sum, item) => {
|
|
2159
|
-
if (!item || typeof item.name !== "string" || !item.name.trim() || !Number.isSafeInteger(item.quantity) || item.quantity <= 0 || !Number.isSafeInteger(item.unitAmountMinor) || item.unitAmountMinor < 0) {
|
|
2160
|
-
throw new ContoError(
|
|
2161
|
-
"Each line item needs a name, positive integer quantity, and price in cents",
|
|
2162
|
-
{
|
|
2163
|
-
code: "INVALID_LINK_PURCHASE",
|
|
2164
|
-
status: 0
|
|
2165
|
-
}
|
|
2166
|
-
);
|
|
2167
|
-
}
|
|
2168
|
-
return sum + item.quantity * item.unitAmountMinor;
|
|
2169
|
-
}, 0);
|
|
2170
|
-
if (!input.totals && (!Number.isSafeInteger(itemTotal) || itemTotal !== input.amountMinor)) {
|
|
2171
|
-
throw new ContoError(
|
|
2172
|
-
"Supply the exact totals breakdown when the amount differs from the line items",
|
|
2173
|
-
{
|
|
2174
|
-
code: "LINK_TOTALS_REQUIRED",
|
|
2175
|
-
status: 0
|
|
2176
|
-
}
|
|
2177
|
-
);
|
|
2178
|
-
}
|
|
2179
|
-
const result = await __privateGet(this, _client2).beginCommerceExecution({
|
|
2180
|
-
executionMethod: "LINK_PAY_TOKEN",
|
|
2181
|
-
connectionId,
|
|
2182
|
-
paymentMethodId,
|
|
2183
|
-
idempotencyKey: input.idempotencyKey,
|
|
2184
|
-
amountMinor: input.amountMinor,
|
|
2185
|
-
currency: input.currency ?? "USD",
|
|
2186
|
-
purpose: input.purpose,
|
|
2187
|
-
category: input.category,
|
|
2188
|
-
counterparty: { canonicalId: input.merchantId, checkoutUrl: input.checkoutUrl },
|
|
2189
|
-
cart: {
|
|
2190
|
-
lineItems: input.lineItems,
|
|
2191
|
-
totals: input.totals ?? [{ type: "total", label: "Total", amountMinor: input.amountMinor }]
|
|
2192
|
-
},
|
|
2193
|
-
providerPresentation: {
|
|
2194
|
-
approvalSummary: input.purpose.trim().slice(0, 500),
|
|
2195
|
-
lineItemLabels: input.lineItems.map((item) => item.name.trim().slice(0, 120))
|
|
2196
|
-
}
|
|
2197
|
-
});
|
|
2198
|
-
return this.executeIfReady(result);
|
|
2199
|
-
}
|
|
2200
|
-
/**
|
|
2201
|
-
* Continue the existing payment after approval. Reads before any mutation and
|
|
2202
|
-
* never submits an execution already processing or requiring reconciliation.
|
|
2203
|
-
*/
|
|
2204
|
-
async resume(executionId) {
|
|
2205
|
-
const id = this.identifier(executionId, "executionId");
|
|
2206
|
-
const current = await __privateGet(this, _client2).getCommerceExecution(id);
|
|
2207
|
-
this.assertLinkExecution(current);
|
|
2208
|
-
if (["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(current.execution.status)) {
|
|
2209
|
-
return this.executeIfReady(await __privateGet(this, _client2).refreshCommerceExecution(id));
|
|
2210
|
-
}
|
|
2211
|
-
return this.executeIfReady(current);
|
|
2212
|
-
}
|
|
2213
|
-
/** Observe and reconcile provider state. This method never submits a payment. */
|
|
2214
|
-
async status(executionId) {
|
|
2215
|
-
const id = this.identifier(executionId, "executionId");
|
|
2216
|
-
this.assertLinkExecution(await __privateGet(this, _client2).getCommerceExecution(id));
|
|
2217
|
-
return this.result(await __privateGet(this, _client2).refreshCommerceExecution(id));
|
|
2218
|
-
}
|
|
2219
|
-
/** Request cancellation of this existing Link payment; never retried automatically. */
|
|
2220
|
-
async cancel(executionId) {
|
|
2221
|
-
const id = this.identifier(executionId, "executionId");
|
|
2222
|
-
this.assertLinkExecution(await __privateGet(this, _client2).getCommerceExecution(id));
|
|
2223
|
-
try {
|
|
2224
|
-
return this.result(await __privateGet(this, _client2).cancelCommerceExecution(id));
|
|
2225
|
-
} catch (error) {
|
|
2226
|
-
throw new LinkPaymentExecutionError(id, error);
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
identifier(value, field) {
|
|
2230
|
-
if (typeof value !== "string" || !value.trim() || value.length > 255) {
|
|
2231
|
-
throw new ContoError(
|
|
2232
|
-
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`,
|
|
2233
|
-
{
|
|
2234
|
-
code: "LINK_CONFIGURATION_REQUIRED",
|
|
2235
|
-
status: 0
|
|
2236
|
-
}
|
|
2237
|
-
);
|
|
2238
|
-
}
|
|
2239
|
-
return value.trim();
|
|
2240
|
-
}
|
|
2241
|
-
assertLinkExecution(result) {
|
|
2242
|
-
const execution = result.execution;
|
|
2243
|
-
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") {
|
|
2244
|
-
throw new ContoError("This execution is not a Link Pay Token checkout", {
|
|
2245
|
-
code: "LINK_EXECUTION_MISMATCH",
|
|
2246
|
-
status: 0
|
|
2247
|
-
});
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
async executeIfReady(result) {
|
|
2251
|
-
this.assertLinkExecution(result);
|
|
2252
|
-
if (result.execution.status !== "READY") return this.result(result);
|
|
2253
|
-
try {
|
|
2254
|
-
return this.result(await __privateGet(this, _client2).executeCommerceExecution(result.execution.id));
|
|
2255
|
-
} catch (error) {
|
|
2256
|
-
throw new LinkPaymentExecutionError(result.execution.id, error);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
result(result) {
|
|
2260
|
-
this.assertLinkExecution(result);
|
|
2261
|
-
const { execution } = result;
|
|
2262
|
-
const awaitingApproval = ["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(
|
|
2263
|
-
execution.status
|
|
2264
|
-
);
|
|
2265
|
-
const nextAction = execution.status === "READY" ? "RESUME" : execution.status === "NEEDS_RECONCILIATION" ? "RECONCILE" : awaitingApproval && execution.approvalUrl ? "APPROVE_IN_LINK" : awaitingApproval || ["DRAFT", "PROCESSING"].includes(execution.status) ? "WAIT" : [
|
|
2266
|
-
"SETTLED",
|
|
2267
|
-
"COMPLETED",
|
|
2268
|
-
"DENIED",
|
|
2269
|
-
"CANCELLED",
|
|
2270
|
-
"FAILED",
|
|
2271
|
-
"RETURNED",
|
|
2272
|
-
"EXPIRED"
|
|
2273
|
-
].includes(execution.status) ? "NONE" : "RECONCILE";
|
|
2274
|
-
return {
|
|
2275
|
-
executionId: execution.id,
|
|
2276
|
-
status: execution.status,
|
|
2277
|
-
nextAction,
|
|
2278
|
-
...nextAction === "APPROVE_IN_LINK" ? { approvalUrl: execution.approvalUrl } : {},
|
|
2279
|
-
execution,
|
|
2280
|
-
..."checkout" in result ? { checkout: result.checkout } : {}
|
|
2281
|
-
};
|
|
2282
|
-
}
|
|
2283
|
-
};
|
|
2284
|
-
_client2 = new WeakMap();
|
|
2285
|
-
_defaults = new WeakMap();
|
|
2647
|
+
_client4 = new WeakMap();
|
|
2286
2648
|
export {
|
|
2649
|
+
AdminPurchasingClient,
|
|
2287
2650
|
BrowserActionFinalizationPendingError,
|
|
2288
2651
|
BrowserCheckoutTargetVerificationError,
|
|
2289
2652
|
CardsClient,
|
|
2653
|
+
CheckoutApprovalDeniedError,
|
|
2654
|
+
CheckoutApprovalExpiredError,
|
|
2655
|
+
CheckoutApprovalPendingError,
|
|
2656
|
+
CheckoutApprovalTimeoutError,
|
|
2657
|
+
CheckoutCheckpointClosedError,
|
|
2290
2658
|
CheckoutClient,
|
|
2291
2659
|
ConnectedCardsClient,
|
|
2292
2660
|
Conto,
|
|
@@ -2296,11 +2664,16 @@ export {
|
|
|
2296
2664
|
ControlledExternalCardPaymentTool,
|
|
2297
2665
|
ExternalCardConfirmationPendingError,
|
|
2298
2666
|
Conto as Giro,
|
|
2299
|
-
|
|
2300
|
-
|
|
2667
|
+
OrganizationPurchasesClient,
|
|
2668
|
+
OrganizationRecurringPurchasesClient,
|
|
2669
|
+
PurchasesClient,
|
|
2670
|
+
RecurringPurchasesClient,
|
|
2301
2671
|
TestCardsClient,
|
|
2302
2672
|
canonicalizeBrowserCheckoutTarget,
|
|
2303
2673
|
createVerifiedBrowserCheckoutDriver,
|
|
2304
2674
|
index_default as default,
|
|
2305
|
-
fingerprintBrowserCheckoutTarget
|
|
2675
|
+
fingerprintBrowserCheckoutTarget,
|
|
2676
|
+
isFinalPurchaseStatus,
|
|
2677
|
+
parseServerSentEvents,
|
|
2678
|
+
verifyPurchaseWebhook
|
|
2306
2679
|
};
|