@conto_finance/sdk 0.6.0-beta.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -228
- package/dist/index.d.mts +178 -166
- package/dist/index.d.ts +178 -166
- package/dist/index.js +265 -226
- package/dist/index.mjs +259 -224
- package/package.json +3 -9
- package/dist/link-testing.d.mts +0 -37
- package/dist/link-testing.d.ts +0 -37
- package/dist/link-testing.js +0 -264
- package/dist/link-testing.mjs +0 -244
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.0
|
|
20
|
+
version: "0.6.0",
|
|
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",
|
|
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"
|
|
@@ -386,7 +380,6 @@ var ContoAdmin = class {
|
|
|
386
380
|
this.cards = new AdminCardsClient(this);
|
|
387
381
|
this.policies = new PoliciesClient(this);
|
|
388
382
|
this.team = new TeamClient(this);
|
|
389
|
-
this.paymentRails = new PaymentRailsClient(this);
|
|
390
383
|
}
|
|
391
384
|
/** Make an authenticated request to the Conto API */
|
|
392
385
|
async request(method, path, body) {
|
|
@@ -442,22 +435,6 @@ var ContoAdmin = class {
|
|
|
442
435
|
}
|
|
443
436
|
};
|
|
444
437
|
_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
438
|
var AdminCardsClient = class {
|
|
462
439
|
constructor(client) {
|
|
463
440
|
this.client = client;
|
|
@@ -528,8 +505,11 @@ var AdminCardsClient = class {
|
|
|
528
505
|
return this.client.request("GET", `/api/external-cards/requests${suffix ? `?${suffix}` : ""}`);
|
|
529
506
|
}
|
|
530
507
|
/**
|
|
531
|
-
*
|
|
532
|
-
*
|
|
508
|
+
* @deprecated Resolution requires a signed-in user. SDK admin keys and
|
|
509
|
+
* organization API keys receive HTTP 403 INSUFFICIENT_PERMISSIONS from this
|
|
510
|
+
* endpoint. Have a signed-in organization owner or admin review the processor
|
|
511
|
+
* evidence and resolve the request through Conto's authenticated interface.
|
|
512
|
+
* Retained for source compatibility; this method does not bypass that restriction.
|
|
533
513
|
*/
|
|
534
514
|
async resolveRequest(requestId, input) {
|
|
535
515
|
return this.client.request(
|
|
@@ -909,27 +889,6 @@ var ContoError = class extends Error {
|
|
|
909
889
|
}
|
|
910
890
|
}
|
|
911
891
|
};
|
|
912
|
-
var LinkPaymentExecutionError = class extends ContoError {
|
|
913
|
-
constructor(executionId, error) {
|
|
914
|
-
const original = error instanceof ContoError ? error : void 0;
|
|
915
|
-
super(
|
|
916
|
-
original?.message ?? "Link execution could not be confirmed",
|
|
917
|
-
{
|
|
918
|
-
code: original?.code ?? "LINK_EXECUTION_UNCONFIRMED",
|
|
919
|
-
status: original?.status ?? 0,
|
|
920
|
-
requestId: original?.requestId,
|
|
921
|
-
retryAfter: original?.retryAfter,
|
|
922
|
-
fieldErrors: original?.fieldErrors,
|
|
923
|
-
reasons: original?.reasons,
|
|
924
|
-
retryGuidance: "Call link.status(executionId) to reconcile. Do not create another purchase or retry execution blindly."
|
|
925
|
-
},
|
|
926
|
-
original?.diagnostics
|
|
927
|
-
);
|
|
928
|
-
this.nextAction = "RECONCILE";
|
|
929
|
-
this.name = "LinkPaymentExecutionError";
|
|
930
|
-
this.executionId = executionId;
|
|
931
|
-
}
|
|
932
|
-
};
|
|
933
892
|
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
934
893
|
constructor(requestId, confirmationInput, receipt) {
|
|
935
894
|
super("External card execution completed but confirmation is pending", {
|
|
@@ -957,6 +916,52 @@ var BrowserActionFinalizationPendingError = class extends ContoError {
|
|
|
957
916
|
this.observation = input.observation;
|
|
958
917
|
}
|
|
959
918
|
};
|
|
919
|
+
var CheckoutCheckpointClosedError = class extends ContoError {
|
|
920
|
+
constructor(checkpoint, message = `The checkpoint is ${checkpoint.stage.toLowerCase().replace(/_/g, " ")}. Stop this checkout.`, code = "CHECKOUT_CHECKPOINT_CLOSED") {
|
|
921
|
+
super(message, { code, status: 0 });
|
|
922
|
+
this.name = "CheckoutCheckpointClosedError";
|
|
923
|
+
this.checkpoint = checkpoint;
|
|
924
|
+
this.stage = checkpoint.stage;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
var CheckoutApprovalDeniedError = class extends CheckoutCheckpointClosedError {
|
|
928
|
+
constructor(checkpoint) {
|
|
929
|
+
super(checkpoint, "The owner rejected this purchase. Do not pay.", "CHECKOUT_APPROVAL_DENIED");
|
|
930
|
+
this.name = "CheckoutApprovalDeniedError";
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
var CheckoutApprovalExpiredError = class extends CheckoutCheckpointClosedError {
|
|
934
|
+
constructor(checkpoint) {
|
|
935
|
+
super(
|
|
936
|
+
checkpoint,
|
|
937
|
+
"The owner did not approve this purchase in time. Do not pay.",
|
|
938
|
+
"CHECKOUT_APPROVAL_EXPIRED"
|
|
939
|
+
);
|
|
940
|
+
this.name = "CheckoutApprovalExpiredError";
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
var CheckoutApprovalPendingError = class extends ContoError {
|
|
944
|
+
constructor(checkpoint) {
|
|
945
|
+
super("The owner has not approved this purchase yet. Do not pay.", {
|
|
946
|
+
code: "CHECKOUT_APPROVAL_PENDING",
|
|
947
|
+
status: 0,
|
|
948
|
+
retryGuidance: "Call checkout.waitForApproval(error.checkpoint), then confirm the approved checkpoint."
|
|
949
|
+
});
|
|
950
|
+
this.name = "CheckoutApprovalPendingError";
|
|
951
|
+
this.checkpoint = checkpoint;
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
var CheckoutApprovalTimeoutError = class extends ContoError {
|
|
955
|
+
constructor(checkpoint) {
|
|
956
|
+
super("The owner has not decided yet. Do not pay.", {
|
|
957
|
+
code: "CHECKOUT_APPROVAL_TIMEOUT",
|
|
958
|
+
status: 0,
|
|
959
|
+
retryGuidance: "Wait again with checkout.waitForApproval, or call checkout.cancel to release the held spend."
|
|
960
|
+
});
|
|
961
|
+
this.name = "CheckoutApprovalTimeoutError";
|
|
962
|
+
this.checkpoint = checkpoint;
|
|
963
|
+
}
|
|
964
|
+
};
|
|
960
965
|
function generatedIdempotencyKey(prefix) {
|
|
961
966
|
return `${prefix}_${randomUUID()}`;
|
|
962
967
|
}
|
|
@@ -1019,7 +1024,7 @@ var Conto = class {
|
|
|
1019
1024
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
1020
1025
|
assertSecureBaseUrl(this.baseUrl);
|
|
1021
1026
|
this.timeout = config.timeout || 3e4;
|
|
1022
|
-
this.
|
|
1027
|
+
this.checkout = new CheckoutClient(this);
|
|
1023
1028
|
this.payments = new PaymentsClient(this);
|
|
1024
1029
|
this.cards = new CardsClient(this);
|
|
1025
1030
|
this.connectedCards = new ConnectedCardsClient(this);
|
|
@@ -1027,31 +1032,6 @@ var Conto = class {
|
|
|
1027
1032
|
this.x402 = new X402Client(this);
|
|
1028
1033
|
this.mpp = new MppClient(this);
|
|
1029
1034
|
}
|
|
1030
|
-
/**
|
|
1031
|
-
* List masked Link payment methods for a session-created connection that is
|
|
1032
|
-
* explicitly bound to this agent. No Link token or payment artifact is
|
|
1033
|
-
* returned through the agent SDK.
|
|
1034
|
-
*/
|
|
1035
|
-
async listStripeLinkPaymentMethods(connectionId, options) {
|
|
1036
|
-
return this.request(
|
|
1037
|
-
"GET",
|
|
1038
|
-
withQuery("/api/sdk/stripe-link/payment-methods", {
|
|
1039
|
-
connectionId,
|
|
1040
|
-
...options?.mode ? { mode: options.mode } : {}
|
|
1041
|
-
})
|
|
1042
|
-
);
|
|
1043
|
-
}
|
|
1044
|
-
/** Read live Link configuration diagnostics without retrieving credentials or authorizing payment. */
|
|
1045
|
-
async getLinkSetup(input = {}) {
|
|
1046
|
-
return this.request(
|
|
1047
|
-
"GET",
|
|
1048
|
-
withQuery("/api/sdk/stripe-link/setup", {
|
|
1049
|
-
connectionId: input.connectionId,
|
|
1050
|
-
paymentMethodId: input.paymentMethodId,
|
|
1051
|
-
merchantId: input.merchantId
|
|
1052
|
-
})
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
1035
|
/**
|
|
1056
1036
|
* Normalize and durably revise a partial agent purchase proposal without
|
|
1057
1037
|
* creating a payment side effect. Missing facts return as NEEDS_CONTEXT
|
|
@@ -1132,6 +1112,22 @@ var Conto = class {
|
|
|
1132
1112
|
rethrowAmbiguousCommerceMutation(error, executionId, "authorize");
|
|
1133
1113
|
}
|
|
1134
1114
|
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Report whether an authorized guarded browser checkout was paid. PAID keeps
|
|
1117
|
+
* the held spend counted, NOT_PAID releases it, and UNKNOWN keeps it held for
|
|
1118
|
+
* the owner to review. Safe to repeat with the same outcome.
|
|
1119
|
+
*/
|
|
1120
|
+
async reportCommerceExecutionOutcome(executionId, input) {
|
|
1121
|
+
return this.request(
|
|
1122
|
+
"POST",
|
|
1123
|
+
`/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/outcome`,
|
|
1124
|
+
{
|
|
1125
|
+
outcome: input.outcome,
|
|
1126
|
+
...input.reference ? { reference: input.reference } : {}
|
|
1127
|
+
},
|
|
1128
|
+
{ idempotent: true }
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1135
1131
|
/** Claim a durable browser action for one stable privileged-runtime attempt. */
|
|
1136
1132
|
async claimBrowserAction(authorizationId, dispatchAttemptId, browserSessionId, action) {
|
|
1137
1133
|
return this.request(
|
|
@@ -1979,180 +1975,221 @@ var MppClient = class {
|
|
|
1979
1975
|
}
|
|
1980
1976
|
};
|
|
1981
1977
|
var index_default = Conto;
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1978
|
+
function checkoutError(message, code = "INVALID_CHECKOUT_CHECKPOINT") {
|
|
1979
|
+
return new ContoError(message, { code, status: 0 });
|
|
1980
|
+
}
|
|
1981
|
+
function checkpointCart(amountMinor, lineItems, totals) {
|
|
1982
|
+
if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0) {
|
|
1983
|
+
throw checkoutError("amountMinor must be a positive integer number of cents");
|
|
1984
|
+
}
|
|
1985
|
+
if (!Array.isArray(lineItems) || lineItems.length === 0) {
|
|
1986
|
+
throw checkoutError("Supply at least one line item");
|
|
1987
|
+
}
|
|
1988
|
+
return {
|
|
1989
|
+
lineItems,
|
|
1990
|
+
totals: totals ?? [{ type: "total", label: "Total", amountMinor }]
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
var CLOSED_CHECKPOINT_STAGES = [
|
|
1994
|
+
"DENIED",
|
|
1995
|
+
"CANCELLED",
|
|
1996
|
+
"EXPIRED",
|
|
1997
|
+
"NOT_PAID"
|
|
1998
|
+
];
|
|
1999
|
+
function closedCheckpointError(checkpoint) {
|
|
2000
|
+
if (checkpoint.approval?.status === "DENIED") return new CheckoutApprovalDeniedError(checkpoint);
|
|
2001
|
+
if (checkpoint.approval?.status === "EXPIRED") {
|
|
2002
|
+
return new CheckoutApprovalExpiredError(checkpoint);
|
|
2003
|
+
}
|
|
2004
|
+
return new CheckoutCheckpointClosedError(checkpoint);
|
|
2005
|
+
}
|
|
2006
|
+
function sleep(ms, signal) {
|
|
2007
|
+
return new Promise((resolve, reject) => {
|
|
2008
|
+
if (signal?.aborted) {
|
|
2009
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
const onAbort = () => {
|
|
2013
|
+
clearTimeout(timer);
|
|
2014
|
+
reject(signal?.reason ?? new Error("Aborted"));
|
|
2015
|
+
};
|
|
2016
|
+
const timer = setTimeout(() => {
|
|
2017
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2018
|
+
resolve();
|
|
2019
|
+
}, ms);
|
|
2020
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
var _client;
|
|
2024
|
+
var CheckoutClient = class {
|
|
2025
|
+
constructor(client) {
|
|
1985
2026
|
__privateAdd(this, _client);
|
|
1986
|
-
__privateAdd(this, _defaults);
|
|
1987
2027
|
__privateSet(this, _client, client);
|
|
1988
|
-
__privateSet(this, _defaults, defaults ? { ...defaults } : void 0);
|
|
1989
|
-
}
|
|
1990
|
-
/** Inspect the configured live path. This is not a payment authorization or provider connectivity test. */
|
|
1991
|
-
async setup(input = {}) {
|
|
1992
|
-
return __privateGet(this, _client).getLinkSetup({
|
|
1993
|
-
connectionId: input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
1994
|
-
paymentMethodId: input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
1995
|
-
merchantId: input.merchantId
|
|
1996
|
-
});
|
|
1997
|
-
}
|
|
1998
|
-
/** Read masked LIVE funding sources; never selects a source or initiates payment. */
|
|
1999
|
-
async paymentMethods(connectionId) {
|
|
2000
|
-
return __privateGet(this, _client).listStripeLinkPaymentMethods(
|
|
2001
|
-
this.identifier(connectionId ?? __privateGet(this, _defaults)?.connectionId, "connectionId"),
|
|
2002
|
-
{ mode: "LIVE" }
|
|
2003
|
-
);
|
|
2004
2028
|
}
|
|
2005
2029
|
/**
|
|
2006
|
-
*
|
|
2007
|
-
* the owner
|
|
2030
|
+
* First checkpoint. Open the payment method only when the stage is APPROVED.
|
|
2031
|
+
* AWAITING_APPROVAL means the owner must decide first: call waitForApproval().
|
|
2032
|
+
* A ContoError means Conto did not approve: stop the checkout. A
|
|
2033
|
+
* CheckoutCheckpointClosedError means this idempotency key belongs to a
|
|
2034
|
+
* checkpoint that already closed.
|
|
2008
2035
|
*/
|
|
2009
|
-
async
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2016
|
-
"paymentMethodId"
|
|
2017
|
-
);
|
|
2018
|
-
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()) {
|
|
2019
|
-
throw new ContoError("Supply a positive USD amount in cents, purpose, and line items", {
|
|
2020
|
-
code: "INVALID_LINK_PURCHASE",
|
|
2021
|
-
status: 0
|
|
2022
|
-
});
|
|
2023
|
-
}
|
|
2024
|
-
const itemTotal = input.lineItems.reduce((sum, item) => {
|
|
2025
|
-
if (!item || typeof item.name !== "string" || !item.name.trim() || !Number.isSafeInteger(item.quantity) || item.quantity <= 0 || !Number.isSafeInteger(item.unitAmountMinor) || item.unitAmountMinor < 0) {
|
|
2026
|
-
throw new ContoError(
|
|
2027
|
-
"Each line item needs a name, positive integer quantity, and price in cents",
|
|
2028
|
-
{
|
|
2029
|
-
code: "INVALID_LINK_PURCHASE",
|
|
2030
|
-
status: 0
|
|
2031
|
-
}
|
|
2032
|
-
);
|
|
2033
|
-
}
|
|
2034
|
-
return sum + item.quantity * item.unitAmountMinor;
|
|
2035
|
-
}, 0);
|
|
2036
|
-
if (!input.totals && (!Number.isSafeInteger(itemTotal) || itemTotal !== input.amountMinor)) {
|
|
2037
|
-
throw new ContoError(
|
|
2038
|
-
"Supply the exact totals breakdown when the amount differs from the line items",
|
|
2039
|
-
{
|
|
2040
|
-
code: "LINK_TOTALS_REQUIRED",
|
|
2041
|
-
status: 0
|
|
2042
|
-
}
|
|
2036
|
+
async begin(input) {
|
|
2037
|
+
const idempotencyKey = input.idempotencyKey?.trim();
|
|
2038
|
+
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
|
|
2039
|
+
throw checkoutError(
|
|
2040
|
+
"idempotencyKey must contain 8 to 255 characters",
|
|
2041
|
+
"INVALID_IDEMPOTENCY_KEY"
|
|
2043
2042
|
);
|
|
2044
2043
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2044
|
+
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
2045
|
+
throw checkoutError("Only USD checkouts are supported");
|
|
2046
|
+
}
|
|
2047
|
+
const result = await __privateGet(this, _client).request(
|
|
2048
|
+
"POST",
|
|
2049
|
+
"/api/sdk/checkout-checkpoints",
|
|
2050
|
+
{
|
|
2051
|
+
idempotencyKey,
|
|
2052
|
+
merchant: input.merchant,
|
|
2053
|
+
amountMinor: input.amountMinor,
|
|
2054
|
+
currency: "USD",
|
|
2055
|
+
purpose: input.purpose,
|
|
2056
|
+
...input.category ? { category: input.category } : {},
|
|
2057
|
+
...input.checkoutMethod ? { checkoutMethod: input.checkoutMethod } : {},
|
|
2058
|
+
cart: checkpointCart(input.amountMinor, input.lineItems, input.totals)
|
|
2058
2059
|
},
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
}
|
|
2064
|
-
return
|
|
2060
|
+
{ idempotent: true }
|
|
2061
|
+
);
|
|
2062
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(result.checkpoint.stage)) {
|
|
2063
|
+
throw closedCheckpointError(result.checkpoint);
|
|
2064
|
+
}
|
|
2065
|
+
return result.checkpoint;
|
|
2065
2066
|
}
|
|
2066
2067
|
/**
|
|
2067
|
-
*
|
|
2068
|
-
*
|
|
2068
|
+
* Waits while the owner decides on a checkpoint in AWAITING_APPROVAL and
|
|
2069
|
+
* resolves to the approved checkpoint. Throws CheckoutApprovalDeniedError,
|
|
2070
|
+
* CheckoutApprovalExpiredError, CheckoutCheckpointClosedError, or
|
|
2071
|
+
* CheckoutApprovalTimeoutError (the spend stays held until you cancel).
|
|
2069
2072
|
*/
|
|
2070
|
-
async
|
|
2071
|
-
const
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
}
|
|
2085
|
-
/** Request cancellation of this existing Link payment; never retried automatically. */
|
|
2086
|
-
async cancel(executionId) {
|
|
2087
|
-
const id = this.identifier(executionId, "executionId");
|
|
2088
|
-
this.assertLinkExecution(await __privateGet(this, _client).getCommerceExecution(id));
|
|
2089
|
-
try {
|
|
2090
|
-
return this.result(await __privateGet(this, _client).cancelCommerceExecution(id));
|
|
2091
|
-
} catch (error) {
|
|
2092
|
-
throw new LinkPaymentExecutionError(id, error);
|
|
2073
|
+
async waitForApproval(checkpoint, options = {}) {
|
|
2074
|
+
const timeoutMs = options.timeoutMs ?? 10 * 6e4;
|
|
2075
|
+
const pollIntervalMs = Math.max(1e3, options.pollIntervalMs ?? 5e3);
|
|
2076
|
+
const deadline = Date.now() + timeoutMs;
|
|
2077
|
+
let current = checkpoint;
|
|
2078
|
+
for (; ; ) {
|
|
2079
|
+
if (current.stage !== "AWAITING_APPROVAL") {
|
|
2080
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(current.stage)) throw closedCheckpointError(current);
|
|
2081
|
+
return current;
|
|
2082
|
+
}
|
|
2083
|
+
const remaining = deadline - Date.now();
|
|
2084
|
+
if (remaining <= 0) throw new CheckoutApprovalTimeoutError(current);
|
|
2085
|
+
await sleep(Math.min(pollIntervalMs, remaining), options.signal);
|
|
2086
|
+
current = await this.get(current.id);
|
|
2093
2087
|
}
|
|
2094
2088
|
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2089
|
+
/**
|
|
2090
|
+
* Second checkpoint, right before the payment is submitted. If the total
|
|
2091
|
+
* changed since begin() (tax or shipping, for example), this cancels the
|
|
2092
|
+
* approved checkpoint and starts a new one at the final total, so the agent's
|
|
2093
|
+
* rules see the amount actually being paid. Pay only if this resolves. A
|
|
2094
|
+
* CheckoutApprovalPendingError means the owner must approve first: call
|
|
2095
|
+
* waitForApproval(error.checkpoint), then confirm the checkpoint it returns.
|
|
2096
|
+
*/
|
|
2097
|
+
async confirm(checkpoint, input) {
|
|
2098
|
+
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
2099
|
+
throw checkoutError("Only USD checkouts are supported");
|
|
2104
2100
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
if (
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2101
|
+
if (!Number.isSafeInteger(input.amountMinor) || input.amountMinor <= 0) {
|
|
2102
|
+
throw checkoutError("amountMinor must be a positive integer number of cents");
|
|
2103
|
+
}
|
|
2104
|
+
let current = checkpoint;
|
|
2105
|
+
if (input.amountMinor !== checkpoint.amountMinor) {
|
|
2106
|
+
try {
|
|
2107
|
+
await this.cancel(checkpoint.id);
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
const closed = error instanceof ContoError && error.status === 409 && ["CANCELLED", "EXPIRED", "DENIED"].includes((await this.get(checkpoint.id)).stage);
|
|
2110
|
+
if (!closed) throw error;
|
|
2111
|
+
}
|
|
2112
|
+
current = await this.begin({
|
|
2113
|
+
idempotencyKey: `${checkpoint.idempotencyKey.slice(0, 200)}:final-${input.amountMinor}`,
|
|
2114
|
+
merchant: {
|
|
2115
|
+
id: checkpoint.merchant.id,
|
|
2116
|
+
name: checkpoint.merchant.name,
|
|
2117
|
+
checkoutUrl: checkpoint.merchant.checkoutUrl ?? ""
|
|
2118
|
+
},
|
|
2119
|
+
amountMinor: input.amountMinor,
|
|
2120
|
+
purpose: checkpoint.purpose,
|
|
2121
|
+
...checkpoint.category ? { category: checkpoint.category } : {},
|
|
2122
|
+
checkoutMethod: checkpoint.checkoutMethod,
|
|
2123
|
+
lineItems: input.lineItems ?? checkpoint.cart.lineItems,
|
|
2124
|
+
totals: input.totals
|
|
2113
2125
|
});
|
|
2126
|
+
if (current.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(current);
|
|
2114
2127
|
}
|
|
2115
|
-
}
|
|
2116
|
-
async executeIfReady(result) {
|
|
2117
|
-
this.assertLinkExecution(result);
|
|
2118
|
-
if (result.execution.status !== "READY") return this.result(result);
|
|
2119
2128
|
try {
|
|
2120
|
-
|
|
2129
|
+
const result = await __privateGet(this, _client).request(
|
|
2130
|
+
"POST",
|
|
2131
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(current.id)}/confirm`,
|
|
2132
|
+
{
|
|
2133
|
+
amountMinor: input.amountMinor,
|
|
2134
|
+
currency: "USD",
|
|
2135
|
+
...input.pageUrl ? { pageUrl: input.pageUrl } : {}
|
|
2136
|
+
},
|
|
2137
|
+
{ idempotent: true }
|
|
2138
|
+
);
|
|
2139
|
+
return result.checkpoint;
|
|
2121
2140
|
} catch (error) {
|
|
2122
|
-
|
|
2141
|
+
if (error instanceof ContoError && error.status === 409 && error.code === "APPROVAL_REQUIRED") {
|
|
2142
|
+
const latest = await this.get(current.id);
|
|
2143
|
+
if (latest.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(latest);
|
|
2144
|
+
}
|
|
2145
|
+
throw error;
|
|
2123
2146
|
}
|
|
2124
2147
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
const
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
+
/** Report what happened after a confirmed checkpoint. Safe to repeat with the same outcome. */
|
|
2149
|
+
async reportOutcome(checkpointId, input) {
|
|
2150
|
+
const result = await __privateGet(this, _client).request(
|
|
2151
|
+
"POST",
|
|
2152
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/outcome`,
|
|
2153
|
+
{
|
|
2154
|
+
outcome: input.outcome,
|
|
2155
|
+
...input.reference ? { reference: input.reference } : {}
|
|
2156
|
+
},
|
|
2157
|
+
{ idempotent: true }
|
|
2158
|
+
);
|
|
2159
|
+
return result.checkpoint;
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Cancel a checkpoint that was not confirmed and release its spend. A
|
|
2163
|
+
* checkpoint waiting for the owner can be cancelled too.
|
|
2164
|
+
*/
|
|
2165
|
+
async cancel(checkpointId) {
|
|
2166
|
+
const result = await __privateGet(this, _client).request(
|
|
2167
|
+
"POST",
|
|
2168
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/cancel`,
|
|
2169
|
+
{},
|
|
2170
|
+
{ idempotent: true }
|
|
2171
|
+
);
|
|
2172
|
+
return result.checkpoint;
|
|
2173
|
+
}
|
|
2174
|
+
async get(checkpointId) {
|
|
2175
|
+
const result = await __privateGet(this, _client).request(
|
|
2176
|
+
"GET",
|
|
2177
|
+
`/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}`
|
|
2178
|
+
);
|
|
2179
|
+
return result.checkpoint;
|
|
2148
2180
|
}
|
|
2149
2181
|
};
|
|
2150
2182
|
_client = new WeakMap();
|
|
2151
|
-
_defaults = new WeakMap();
|
|
2152
2183
|
export {
|
|
2153
2184
|
BrowserActionFinalizationPendingError,
|
|
2154
2185
|
BrowserCheckoutTargetVerificationError,
|
|
2155
2186
|
CardsClient,
|
|
2187
|
+
CheckoutApprovalDeniedError,
|
|
2188
|
+
CheckoutApprovalExpiredError,
|
|
2189
|
+
CheckoutApprovalPendingError,
|
|
2190
|
+
CheckoutApprovalTimeoutError,
|
|
2191
|
+
CheckoutCheckpointClosedError,
|
|
2192
|
+
CheckoutClient,
|
|
2156
2193
|
ConnectedCardsClient,
|
|
2157
2194
|
Conto,
|
|
2158
2195
|
ContoAdmin,
|
|
@@ -2161,8 +2198,6 @@ export {
|
|
|
2161
2198
|
ControlledExternalCardPaymentTool,
|
|
2162
2199
|
ExternalCardConfirmationPendingError,
|
|
2163
2200
|
Conto as Giro,
|
|
2164
|
-
LinkClient,
|
|
2165
|
-
LinkPaymentExecutionError,
|
|
2166
2201
|
TestCardsClient,
|
|
2167
2202
|
canonicalizeBrowserCheckoutTarget,
|
|
2168
2203
|
createVerifiedBrowserCheckoutDriver,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conto_finance/sdk",
|
|
3
|
-
"version": "0.6.0
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Conto SDK for AI agent payment authorization",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -25,16 +25,10 @@
|
|
|
25
25
|
"types": "./dist/checkout-verifier.d.ts",
|
|
26
26
|
"import": "./dist/checkout-verifier.mjs",
|
|
27
27
|
"require": "./dist/checkout-verifier.js"
|
|
28
|
-
},
|
|
29
|
-
"./link-testing": {
|
|
30
|
-
"types": "./dist/link-testing.d.ts",
|
|
31
|
-
"import": "./dist/link-testing.mjs",
|
|
32
|
-
"require": "./dist/link-testing.js"
|
|
33
28
|
}
|
|
34
29
|
},
|
|
35
30
|
"publishConfig": {
|
|
36
|
-
"access": "public"
|
|
37
|
-
"tag": "beta"
|
|
31
|
+
"access": "public"
|
|
38
32
|
},
|
|
39
33
|
"engines": {
|
|
40
34
|
"node": ">=20.19.0"
|
|
@@ -47,7 +41,7 @@
|
|
|
47
41
|
"dev": "tsup --watch",
|
|
48
42
|
"lint": "tsc --noEmit",
|
|
49
43
|
"test": "npm run lint && npm run test:contract && npm run test:consumer",
|
|
50
|
-
"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/
|
|
44
|
+
"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",
|
|
51
45
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
52
46
|
"verify:release": "node scripts/verify-release.mjs",
|
|
53
47
|
"prepublishOnly": "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|