@conto_finance/sdk 0.6.0-beta.2 → 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 +48 -171
- package/dist/index.d.mts +92 -174
- package/dist/index.d.ts +92 -174
- package/dist/index.js +164 -261
- package/dist/index.mjs +159 -259
- 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.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;
|
|
@@ -912,27 +889,6 @@ var ContoError = class extends Error {
|
|
|
912
889
|
}
|
|
913
890
|
}
|
|
914
891
|
};
|
|
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
892
|
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
937
893
|
constructor(requestId, confirmationInput, receipt) {
|
|
938
894
|
super("External card execution completed but confirmation is pending", {
|
|
@@ -960,6 +916,52 @@ var BrowserActionFinalizationPendingError = class extends ContoError {
|
|
|
960
916
|
this.observation = input.observation;
|
|
961
917
|
}
|
|
962
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
|
+
};
|
|
963
965
|
function generatedIdempotencyKey(prefix) {
|
|
964
966
|
return `${prefix}_${randomUUID()}`;
|
|
965
967
|
}
|
|
@@ -1022,7 +1024,6 @@ var Conto = class {
|
|
|
1022
1024
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
1023
1025
|
assertSecureBaseUrl(this.baseUrl);
|
|
1024
1026
|
this.timeout = config.timeout || 3e4;
|
|
1025
|
-
this.link = new LinkClient(this, config.link);
|
|
1026
1027
|
this.checkout = new CheckoutClient(this);
|
|
1027
1028
|
this.payments = new PaymentsClient(this);
|
|
1028
1029
|
this.cards = new CardsClient(this);
|
|
@@ -1031,31 +1032,6 @@ var Conto = class {
|
|
|
1031
1032
|
this.x402 = new X402Client(this);
|
|
1032
1033
|
this.mpp = new MppClient(this);
|
|
1033
1034
|
}
|
|
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
|
-
);
|
|
1058
|
-
}
|
|
1059
1035
|
/**
|
|
1060
1036
|
* Normalize and durably revise a partial agent purchase proposal without
|
|
1061
1037
|
* creating a payment side effect. Missing facts return as NEEDS_CONTEXT
|
|
@@ -1136,6 +1112,22 @@ var Conto = class {
|
|
|
1136
1112
|
rethrowAmbiguousCommerceMutation(error, executionId, "authorize");
|
|
1137
1113
|
}
|
|
1138
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
|
+
}
|
|
1139
1131
|
/** Claim a durable browser action for one stable privileged-runtime attempt. */
|
|
1140
1132
|
async claimBrowserAction(authorizationId, dispatchAttemptId, browserSessionId, action) {
|
|
1141
1133
|
return this.request(
|
|
@@ -1998,13 +1990,49 @@ function checkpointCart(amountMinor, lineItems, totals) {
|
|
|
1998
1990
|
totals: totals ?? [{ type: "total", label: "Total", amountMinor }]
|
|
1999
1991
|
};
|
|
2000
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
|
+
}
|
|
2001
2023
|
var _client;
|
|
2002
2024
|
var CheckoutClient = class {
|
|
2003
2025
|
constructor(client) {
|
|
2004
2026
|
__privateAdd(this, _client);
|
|
2005
2027
|
__privateSet(this, _client, client);
|
|
2006
2028
|
}
|
|
2007
|
-
/**
|
|
2029
|
+
/**
|
|
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.
|
|
2035
|
+
*/
|
|
2008
2036
|
async begin(input) {
|
|
2009
2037
|
const idempotencyKey = input.idempotencyKey?.trim();
|
|
2010
2038
|
if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
|
|
@@ -2031,13 +2059,40 @@ var CheckoutClient = class {
|
|
|
2031
2059
|
},
|
|
2032
2060
|
{ idempotent: true }
|
|
2033
2061
|
);
|
|
2062
|
+
if (CLOSED_CHECKPOINT_STAGES.includes(result.checkpoint.stage)) {
|
|
2063
|
+
throw closedCheckpointError(result.checkpoint);
|
|
2064
|
+
}
|
|
2034
2065
|
return result.checkpoint;
|
|
2035
2066
|
}
|
|
2067
|
+
/**
|
|
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).
|
|
2072
|
+
*/
|
|
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);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2036
2089
|
/**
|
|
2037
2090
|
* Second checkpoint, right before the payment is submitted. If the total
|
|
2038
2091
|
* changed since begin() (tax or shipping, for example), this cancels the
|
|
2039
2092
|
* 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.
|
|
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.
|
|
2041
2096
|
*/
|
|
2042
2097
|
async confirm(checkpoint, input) {
|
|
2043
2098
|
if (input.currency !== void 0 && input.currency !== "USD") {
|
|
@@ -2068,18 +2123,27 @@ var CheckoutClient = class {
|
|
|
2068
2123
|
lineItems: input.lineItems ?? checkpoint.cart.lineItems,
|
|
2069
2124
|
totals: input.totals
|
|
2070
2125
|
});
|
|
2126
|
+
if (current.stage === "AWAITING_APPROVAL") throw new CheckoutApprovalPendingError(current);
|
|
2127
|
+
}
|
|
2128
|
+
try {
|
|
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;
|
|
2140
|
+
} catch (error) {
|
|
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;
|
|
2071
2146
|
}
|
|
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
2147
|
}
|
|
2084
2148
|
/** Report what happened after a confirmed checkpoint. Safe to repeat with the same outcome. */
|
|
2085
2149
|
async reportOutcome(checkpointId, input) {
|
|
@@ -2094,7 +2158,10 @@ var CheckoutClient = class {
|
|
|
2094
2158
|
);
|
|
2095
2159
|
return result.checkpoint;
|
|
2096
2160
|
}
|
|
2097
|
-
/**
|
|
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
|
+
*/
|
|
2098
2165
|
async cancel(checkpointId) {
|
|
2099
2166
|
const result = await __privateGet(this, _client).request(
|
|
2100
2167
|
"POST",
|
|
@@ -2113,180 +2180,15 @@ var CheckoutClient = class {
|
|
|
2113
2180
|
}
|
|
2114
2181
|
};
|
|
2115
2182
|
_client = new WeakMap();
|
|
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();
|
|
2286
2183
|
export {
|
|
2287
2184
|
BrowserActionFinalizationPendingError,
|
|
2288
2185
|
BrowserCheckoutTargetVerificationError,
|
|
2289
2186
|
CardsClient,
|
|
2187
|
+
CheckoutApprovalDeniedError,
|
|
2188
|
+
CheckoutApprovalExpiredError,
|
|
2189
|
+
CheckoutApprovalPendingError,
|
|
2190
|
+
CheckoutApprovalTimeoutError,
|
|
2191
|
+
CheckoutCheckpointClosedError,
|
|
2290
2192
|
CheckoutClient,
|
|
2291
2193
|
ConnectedCardsClient,
|
|
2292
2194
|
Conto,
|
|
@@ -2296,8 +2198,6 @@ export {
|
|
|
2296
2198
|
ControlledExternalCardPaymentTool,
|
|
2297
2199
|
ExternalCardConfirmationPendingError,
|
|
2298
2200
|
Conto as Giro,
|
|
2299
|
-
LinkClient,
|
|
2300
|
-
LinkPaymentExecutionError,
|
|
2301
2201
|
TestCardsClient,
|
|
2302
2202
|
canonicalizeBrowserCheckoutTarget,
|
|
2303
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"
|
package/dist/link-testing.d.mts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { CommerceExecution, CommerceExecutionEvidence, LinkPaymentResult, LinkSetupInput, LinkSetupResult, ListStripeLinkPaymentMethodsResult, LinkPaymentInput } from './index.mjs';
|
|
2
|
-
|
|
3
|
-
type LinkTestScenario = 'approved' | 'denied' | 'expired' | 'interrupted';
|
|
4
|
-
/** Evidence created by the local simulator. It is never a receipt. */
|
|
5
|
-
type SimulatedCommerceExecutionEvidence = CommerceExecutionEvidence & {
|
|
6
|
-
mode: 'SIMULATED';
|
|
7
|
-
};
|
|
8
|
-
/** Execution record created by the local simulator. It does not exist on any Conto server. */
|
|
9
|
-
type SimulatedCommerceExecution = CommerceExecution & {
|
|
10
|
-
mode: 'SIMULATED';
|
|
11
|
-
evidence: SimulatedCommerceExecutionEvidence;
|
|
12
|
-
};
|
|
13
|
-
type SimulatedLinkPaymentResult = LinkPaymentResult & {
|
|
14
|
-
mode: 'SIMULATED';
|
|
15
|
-
settlementVerified: false;
|
|
16
|
-
execution: SimulatedCommerceExecution;
|
|
17
|
-
};
|
|
18
|
-
interface LinkTestOptions {
|
|
19
|
-
scenario?: LinkTestScenario;
|
|
20
|
-
/** A local fixture limit, not a real Conto policy or available balance. Defaults to 5000 cents. */
|
|
21
|
-
maximumAmountMinor?: number;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Create an isolated in-memory test account with the same pay/resume/status API.
|
|
25
|
-
* Approval is explicit through link.approve(), a test-only method. Like the live
|
|
26
|
-
* API, an accepted submission is PROCESSING and only a later status() call
|
|
27
|
-
* reports SETTLED. No money moves, and every result has settlementVerified false.
|
|
28
|
-
*/
|
|
29
|
-
declare function createLinkTestClient(options?: LinkTestOptions): {
|
|
30
|
-
mode: "SIMULATED";
|
|
31
|
-
link: {
|
|
32
|
-
setup: (input?: LinkSetupInput) => Promise<LinkSetupResult & {
|
|
33
|
-
mode: "SIMULATED";
|
|
34
|
-
settlementVerified: false;
|
|
35
|
-
}>;
|
|
36
|
-
paymentMethods: () => Promise<ListStripeLinkPaymentMethodsResult & {
|
|
37
|
-
mode: "SIMULATED";
|
|
38
|
-
settlementVerified: false;
|
|
39
|
-
}>;
|
|
40
|
-
pay: (input: LinkPaymentInput) => Promise<SimulatedLinkPaymentResult>;
|
|
41
|
-
resume: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
42
|
-
status: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
43
|
-
cancel: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
44
|
-
/** Test-only owner action. Never available on the live Link client. */
|
|
45
|
-
approve: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export { type LinkTestOptions, type LinkTestScenario, type SimulatedCommerceExecution, type SimulatedCommerceExecutionEvidence, type SimulatedLinkPaymentResult, createLinkTestClient };
|
package/dist/link-testing.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { CommerceExecution, CommerceExecutionEvidence, LinkPaymentResult, LinkSetupInput, LinkSetupResult, ListStripeLinkPaymentMethodsResult, LinkPaymentInput } from './index.js';
|
|
2
|
-
|
|
3
|
-
type LinkTestScenario = 'approved' | 'denied' | 'expired' | 'interrupted';
|
|
4
|
-
/** Evidence created by the local simulator. It is never a receipt. */
|
|
5
|
-
type SimulatedCommerceExecutionEvidence = CommerceExecutionEvidence & {
|
|
6
|
-
mode: 'SIMULATED';
|
|
7
|
-
};
|
|
8
|
-
/** Execution record created by the local simulator. It does not exist on any Conto server. */
|
|
9
|
-
type SimulatedCommerceExecution = CommerceExecution & {
|
|
10
|
-
mode: 'SIMULATED';
|
|
11
|
-
evidence: SimulatedCommerceExecutionEvidence;
|
|
12
|
-
};
|
|
13
|
-
type SimulatedLinkPaymentResult = LinkPaymentResult & {
|
|
14
|
-
mode: 'SIMULATED';
|
|
15
|
-
settlementVerified: false;
|
|
16
|
-
execution: SimulatedCommerceExecution;
|
|
17
|
-
};
|
|
18
|
-
interface LinkTestOptions {
|
|
19
|
-
scenario?: LinkTestScenario;
|
|
20
|
-
/** A local fixture limit, not a real Conto policy or available balance. Defaults to 5000 cents. */
|
|
21
|
-
maximumAmountMinor?: number;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Create an isolated in-memory test account with the same pay/resume/status API.
|
|
25
|
-
* Approval is explicit through link.approve(), a test-only method. Like the live
|
|
26
|
-
* API, an accepted submission is PROCESSING and only a later status() call
|
|
27
|
-
* reports SETTLED. No money moves, and every result has settlementVerified false.
|
|
28
|
-
*/
|
|
29
|
-
declare function createLinkTestClient(options?: LinkTestOptions): {
|
|
30
|
-
mode: "SIMULATED";
|
|
31
|
-
link: {
|
|
32
|
-
setup: (input?: LinkSetupInput) => Promise<LinkSetupResult & {
|
|
33
|
-
mode: "SIMULATED";
|
|
34
|
-
settlementVerified: false;
|
|
35
|
-
}>;
|
|
36
|
-
paymentMethods: () => Promise<ListStripeLinkPaymentMethodsResult & {
|
|
37
|
-
mode: "SIMULATED";
|
|
38
|
-
settlementVerified: false;
|
|
39
|
-
}>;
|
|
40
|
-
pay: (input: LinkPaymentInput) => Promise<SimulatedLinkPaymentResult>;
|
|
41
|
-
resume: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
42
|
-
status: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
43
|
-
cancel: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
44
|
-
/** Test-only owner action. Never available on the live Link client. */
|
|
45
|
-
approve: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export { type LinkTestOptions, type LinkTestScenario, type SimulatedCommerceExecution, type SimulatedCommerceExecutionEvidence, type SimulatedLinkPaymentResult, createLinkTestClient };
|