@conto_finance/sdk 0.5.5 → 0.6.0-beta.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 +73 -1
- package/dist/index.d.mts +114 -3
- package/dist/index.d.ts +114 -3
- package/dist/index.js +229 -9
- package/dist/index.mjs +227 -9
- package/dist/link-testing.d.mts +37 -0
- package/dist/link-testing.d.ts +37 -0
- package/dist/link-testing.js +264 -0
- package/dist/link-testing.mjs +244 -0
- package/package.json +11 -5
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ var require_package = __commonJS({
|
|
|
46
46
|
"package.json"(exports2, module2) {
|
|
47
47
|
module2.exports = {
|
|
48
48
|
name: "@conto_finance/sdk",
|
|
49
|
-
version: "0.
|
|
49
|
+
version: "0.6.0-beta.1",
|
|
50
50
|
description: "Conto SDK for AI agent payment authorization",
|
|
51
51
|
main: "./dist/index.js",
|
|
52
52
|
module: "./dist/index.mjs",
|
|
@@ -71,10 +71,16 @@ var require_package = __commonJS({
|
|
|
71
71
|
types: "./dist/checkout-verifier.d.ts",
|
|
72
72
|
import: "./dist/checkout-verifier.mjs",
|
|
73
73
|
require: "./dist/checkout-verifier.js"
|
|
74
|
+
},
|
|
75
|
+
"./link-testing": {
|
|
76
|
+
types: "./dist/link-testing.d.ts",
|
|
77
|
+
import: "./dist/link-testing.mjs",
|
|
78
|
+
require: "./dist/link-testing.js"
|
|
74
79
|
}
|
|
75
80
|
},
|
|
76
81
|
publishConfig: {
|
|
77
|
-
access: "public"
|
|
82
|
+
access: "public",
|
|
83
|
+
tag: "beta"
|
|
78
84
|
},
|
|
79
85
|
engines: {
|
|
80
86
|
node: ">=20.19.0"
|
|
@@ -83,11 +89,11 @@ var require_package = __commonJS({
|
|
|
83
89
|
"dist"
|
|
84
90
|
],
|
|
85
91
|
scripts: {
|
|
86
|
-
build: "tsup
|
|
87
|
-
dev: "tsup
|
|
92
|
+
build: "tsup",
|
|
93
|
+
dev: "tsup --watch",
|
|
88
94
|
lint: "tsc --noEmit",
|
|
89
95
|
test: "npm run lint && npm run test:contract && npm run test:consumer",
|
|
90
|
-
"test:contract": "npm run build && node --test test/contract.test.mjs test/playwright.test.mjs test/browserbase.test.mjs test/checkout-verifier.test.mjs test/developer-experience.test.mjs",
|
|
96
|
+
"test:contract": "npm run build && node --test test/contract.test.mjs test/playwright.test.mjs test/browserbase.test.mjs test/checkout-verifier.test.mjs test/developer-experience.test.mjs test/link.test.mjs test/link-testing.test.mjs",
|
|
91
97
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
92
98
|
"verify:release": "node scripts/verify-release.mjs",
|
|
93
99
|
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
@@ -161,6 +167,8 @@ __export(index_exports, {
|
|
|
161
167
|
ControlledExternalCardPaymentTool: () => ControlledExternalCardPaymentTool,
|
|
162
168
|
ExternalCardConfirmationPendingError: () => ExternalCardConfirmationPendingError,
|
|
163
169
|
Giro: () => Conto,
|
|
170
|
+
LinkClient: () => LinkClient,
|
|
171
|
+
LinkPaymentExecutionError: () => LinkPaymentExecutionError,
|
|
164
172
|
TestCardsClient: () => TestCardsClient,
|
|
165
173
|
canonicalizeBrowserCheckoutTarget: () => canonicalizeBrowserCheckoutTarget,
|
|
166
174
|
createVerifiedBrowserCheckoutDriver: () => createVerifiedBrowserCheckoutDriver,
|
|
@@ -1053,7 +1061,7 @@ function isValidBrowserActionObservation(value) {
|
|
|
1053
1061
|
}
|
|
1054
1062
|
}
|
|
1055
1063
|
var ContoError = class extends Error {
|
|
1056
|
-
constructor(message, metadata) {
|
|
1064
|
+
constructor(message, metadata, diagnostics) {
|
|
1057
1065
|
super(message);
|
|
1058
1066
|
this.name = "ContoError";
|
|
1059
1067
|
this.code = metadata.code;
|
|
@@ -1064,6 +1072,30 @@ var ContoError = class extends Error {
|
|
|
1064
1072
|
this.actionUrl = metadata.actionUrl;
|
|
1065
1073
|
this.fieldErrors = metadata.fieldErrors;
|
|
1066
1074
|
this.reasons = metadata.reasons;
|
|
1075
|
+
if (diagnostics !== null && typeof diagnostics === "object" && !Array.isArray(diagnostics) && "version" in diagnostics && diagnostics.version === "conto.setup-diagnostics.v1") {
|
|
1076
|
+
this.diagnostics = diagnostics;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
var LinkPaymentExecutionError = class extends ContoError {
|
|
1081
|
+
constructor(executionId, error) {
|
|
1082
|
+
const original = error instanceof ContoError ? error : void 0;
|
|
1083
|
+
super(
|
|
1084
|
+
original?.message ?? "Link execution could not be confirmed",
|
|
1085
|
+
{
|
|
1086
|
+
code: original?.code ?? "LINK_EXECUTION_UNCONFIRMED",
|
|
1087
|
+
status: original?.status ?? 0,
|
|
1088
|
+
requestId: original?.requestId,
|
|
1089
|
+
retryAfter: original?.retryAfter,
|
|
1090
|
+
fieldErrors: original?.fieldErrors,
|
|
1091
|
+
reasons: original?.reasons,
|
|
1092
|
+
retryGuidance: "Call link.status(executionId) to reconcile. Do not create another purchase or retry execution blindly."
|
|
1093
|
+
},
|
|
1094
|
+
original?.diagnostics
|
|
1095
|
+
);
|
|
1096
|
+
this.nextAction = "RECONCILE";
|
|
1097
|
+
this.name = "LinkPaymentExecutionError";
|
|
1098
|
+
this.executionId = executionId;
|
|
1067
1099
|
}
|
|
1068
1100
|
};
|
|
1069
1101
|
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
@@ -1155,6 +1187,7 @@ var Conto = class {
|
|
|
1155
1187
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
1156
1188
|
assertSecureBaseUrl(this.baseUrl);
|
|
1157
1189
|
this.timeout = config.timeout || 3e4;
|
|
1190
|
+
this.link = new LinkClient(this, config.link);
|
|
1158
1191
|
this.payments = new PaymentsClient(this);
|
|
1159
1192
|
this.cards = new CardsClient(this);
|
|
1160
1193
|
this.connectedCards = new ConnectedCardsClient(this);
|
|
@@ -1167,10 +1200,24 @@ var Conto = class {
|
|
|
1167
1200
|
* explicitly bound to this agent. No Link token or payment artifact is
|
|
1168
1201
|
* returned through the agent SDK.
|
|
1169
1202
|
*/
|
|
1170
|
-
async listStripeLinkPaymentMethods(connectionId) {
|
|
1203
|
+
async listStripeLinkPaymentMethods(connectionId, options) {
|
|
1171
1204
|
return this.request(
|
|
1172
1205
|
"GET",
|
|
1173
|
-
withQuery("/api/sdk/stripe-link/payment-methods", {
|
|
1206
|
+
withQuery("/api/sdk/stripe-link/payment-methods", {
|
|
1207
|
+
connectionId,
|
|
1208
|
+
...options?.mode ? { mode: options.mode } : {}
|
|
1209
|
+
})
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
/** Read live Link configuration diagnostics without retrieving credentials or authorizing payment. */
|
|
1213
|
+
async getLinkSetup(input = {}) {
|
|
1214
|
+
return this.request(
|
|
1215
|
+
"GET",
|
|
1216
|
+
withQuery("/api/sdk/stripe-link/setup", {
|
|
1217
|
+
connectionId: input.connectionId,
|
|
1218
|
+
paymentMethodId: input.paymentMethodId,
|
|
1219
|
+
merchantId: input.merchantId
|
|
1220
|
+
})
|
|
1174
1221
|
);
|
|
1175
1222
|
}
|
|
1176
1223
|
/**
|
|
@@ -1466,7 +1513,8 @@ var Conto = class {
|
|
|
1466
1513
|
if (!response.ok) {
|
|
1467
1514
|
throw new ContoError(
|
|
1468
1515
|
responseErrorMessage(data, response.status),
|
|
1469
|
-
responseErrorMetadata(response, data)
|
|
1516
|
+
responseErrorMetadata(response, data),
|
|
1517
|
+
data !== null && typeof data === "object" && "diagnostics" in data ? data.diagnostics : void 0
|
|
1470
1518
|
);
|
|
1471
1519
|
}
|
|
1472
1520
|
return data;
|
|
@@ -2099,6 +2147,176 @@ var MppClient = class {
|
|
|
2099
2147
|
}
|
|
2100
2148
|
};
|
|
2101
2149
|
var index_default = Conto;
|
|
2150
|
+
var _client, _defaults;
|
|
2151
|
+
var LinkClient = class {
|
|
2152
|
+
constructor(client, defaults) {
|
|
2153
|
+
__privateAdd(this, _client);
|
|
2154
|
+
__privateAdd(this, _defaults);
|
|
2155
|
+
__privateSet(this, _client, client);
|
|
2156
|
+
__privateSet(this, _defaults, defaults ? { ...defaults } : void 0);
|
|
2157
|
+
}
|
|
2158
|
+
/** Inspect the configured live path. This is not a payment authorization or provider connectivity test. */
|
|
2159
|
+
async setup(input = {}) {
|
|
2160
|
+
return __privateGet(this, _client).getLinkSetup({
|
|
2161
|
+
connectionId: input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
2162
|
+
paymentMethodId: input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2163
|
+
merchantId: input.merchantId
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
/** Read masked LIVE funding sources; never selects a source or initiates payment. */
|
|
2167
|
+
async paymentMethods(connectionId) {
|
|
2168
|
+
return __privateGet(this, _client).listStripeLinkPaymentMethods(
|
|
2169
|
+
this.identifier(connectionId ?? __privateGet(this, _defaults)?.connectionId, "connectionId"),
|
|
2170
|
+
{ mode: "LIVE" }
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Begin one exact intent and execute only if already READY. Otherwise return
|
|
2175
|
+
* the owner's approval action or existing state immediately, without polling.
|
|
2176
|
+
*/
|
|
2177
|
+
async pay(input) {
|
|
2178
|
+
const connectionId = this.identifier(
|
|
2179
|
+
input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
2180
|
+
"connectionId"
|
|
2181
|
+
);
|
|
2182
|
+
const paymentMethodId = this.identifier(
|
|
2183
|
+
input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
|
|
2184
|
+
"paymentMethodId"
|
|
2185
|
+
);
|
|
2186
|
+
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()) {
|
|
2187
|
+
throw new ContoError("Supply a positive USD amount in cents, purpose, and line items", {
|
|
2188
|
+
code: "INVALID_LINK_PURCHASE",
|
|
2189
|
+
status: 0
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
const itemTotal = input.lineItems.reduce((sum, item) => {
|
|
2193
|
+
if (!item || typeof item.name !== "string" || !item.name.trim() || !Number.isSafeInteger(item.quantity) || item.quantity <= 0 || !Number.isSafeInteger(item.unitAmountMinor) || item.unitAmountMinor < 0) {
|
|
2194
|
+
throw new ContoError(
|
|
2195
|
+
"Each line item needs a name, positive integer quantity, and price in cents",
|
|
2196
|
+
{
|
|
2197
|
+
code: "INVALID_LINK_PURCHASE",
|
|
2198
|
+
status: 0
|
|
2199
|
+
}
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
return sum + item.quantity * item.unitAmountMinor;
|
|
2203
|
+
}, 0);
|
|
2204
|
+
if (!input.totals && (!Number.isSafeInteger(itemTotal) || itemTotal !== input.amountMinor)) {
|
|
2205
|
+
throw new ContoError(
|
|
2206
|
+
"Supply the exact totals breakdown when the amount differs from the line items",
|
|
2207
|
+
{
|
|
2208
|
+
code: "LINK_TOTALS_REQUIRED",
|
|
2209
|
+
status: 0
|
|
2210
|
+
}
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
const result = await __privateGet(this, _client).beginCommerceExecution({
|
|
2214
|
+
executionMethod: "LINK_PAY_TOKEN",
|
|
2215
|
+
connectionId,
|
|
2216
|
+
paymentMethodId,
|
|
2217
|
+
idempotencyKey: input.idempotencyKey,
|
|
2218
|
+
amountMinor: input.amountMinor,
|
|
2219
|
+
currency: input.currency ?? "USD",
|
|
2220
|
+
purpose: input.purpose,
|
|
2221
|
+
category: input.category,
|
|
2222
|
+
counterparty: { canonicalId: input.merchantId, checkoutUrl: input.checkoutUrl },
|
|
2223
|
+
cart: {
|
|
2224
|
+
lineItems: input.lineItems,
|
|
2225
|
+
totals: input.totals ?? [{ type: "total", label: "Total", amountMinor: input.amountMinor }]
|
|
2226
|
+
},
|
|
2227
|
+
providerPresentation: {
|
|
2228
|
+
approvalSummary: input.purpose.trim().slice(0, 500),
|
|
2229
|
+
lineItemLabels: input.lineItems.map((item) => item.name.trim().slice(0, 120))
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
return this.executeIfReady(result);
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Continue the existing payment after approval. Reads before any mutation and
|
|
2236
|
+
* never submits an execution already processing or requiring reconciliation.
|
|
2237
|
+
*/
|
|
2238
|
+
async resume(executionId) {
|
|
2239
|
+
const id = this.identifier(executionId, "executionId");
|
|
2240
|
+
const current = await __privateGet(this, _client).getCommerceExecution(id);
|
|
2241
|
+
this.assertLinkExecution(current);
|
|
2242
|
+
if (["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(current.execution.status)) {
|
|
2243
|
+
return this.executeIfReady(await __privateGet(this, _client).refreshCommerceExecution(id));
|
|
2244
|
+
}
|
|
2245
|
+
return this.executeIfReady(current);
|
|
2246
|
+
}
|
|
2247
|
+
/** Observe and reconcile provider state. This method never submits a payment. */
|
|
2248
|
+
async status(executionId) {
|
|
2249
|
+
const id = this.identifier(executionId, "executionId");
|
|
2250
|
+
this.assertLinkExecution(await __privateGet(this, _client).getCommerceExecution(id));
|
|
2251
|
+
return this.result(await __privateGet(this, _client).refreshCommerceExecution(id));
|
|
2252
|
+
}
|
|
2253
|
+
/** Request cancellation of this existing Link payment; never retried automatically. */
|
|
2254
|
+
async cancel(executionId) {
|
|
2255
|
+
const id = this.identifier(executionId, "executionId");
|
|
2256
|
+
this.assertLinkExecution(await __privateGet(this, _client).getCommerceExecution(id));
|
|
2257
|
+
try {
|
|
2258
|
+
return this.result(await __privateGet(this, _client).cancelCommerceExecution(id));
|
|
2259
|
+
} catch (error) {
|
|
2260
|
+
throw new LinkPaymentExecutionError(id, error);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
identifier(value, field) {
|
|
2264
|
+
if (typeof value !== "string" || !value.trim() || value.length > 255) {
|
|
2265
|
+
throw new ContoError(
|
|
2266
|
+
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`,
|
|
2267
|
+
{
|
|
2268
|
+
code: "LINK_CONFIGURATION_REQUIRED",
|
|
2269
|
+
status: 0
|
|
2270
|
+
}
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
return value.trim();
|
|
2274
|
+
}
|
|
2275
|
+
assertLinkExecution(result) {
|
|
2276
|
+
const execution = result.execution;
|
|
2277
|
+
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") {
|
|
2278
|
+
throw new ContoError("This execution is not a Link Pay Token checkout", {
|
|
2279
|
+
code: "LINK_EXECUTION_MISMATCH",
|
|
2280
|
+
status: 0
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
async executeIfReady(result) {
|
|
2285
|
+
this.assertLinkExecution(result);
|
|
2286
|
+
if (result.execution.status !== "READY") return this.result(result);
|
|
2287
|
+
try {
|
|
2288
|
+
return this.result(await __privateGet(this, _client).executeCommerceExecution(result.execution.id));
|
|
2289
|
+
} catch (error) {
|
|
2290
|
+
throw new LinkPaymentExecutionError(result.execution.id, error);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
result(result) {
|
|
2294
|
+
this.assertLinkExecution(result);
|
|
2295
|
+
const { execution } = result;
|
|
2296
|
+
const awaitingApproval = ["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(
|
|
2297
|
+
execution.status
|
|
2298
|
+
);
|
|
2299
|
+
const nextAction = execution.status === "READY" ? "RESUME" : execution.status === "NEEDS_RECONCILIATION" ? "RECONCILE" : awaitingApproval && execution.approvalUrl ? "APPROVE_IN_LINK" : awaitingApproval || ["DRAFT", "PROCESSING"].includes(execution.status) ? "WAIT" : [
|
|
2300
|
+
"SETTLED",
|
|
2301
|
+
"COMPLETED",
|
|
2302
|
+
"DENIED",
|
|
2303
|
+
"CANCELLED",
|
|
2304
|
+
"FAILED",
|
|
2305
|
+
"RETURNED",
|
|
2306
|
+
"EXPIRED"
|
|
2307
|
+
].includes(execution.status) ? "NONE" : "RECONCILE";
|
|
2308
|
+
return {
|
|
2309
|
+
executionId: execution.id,
|
|
2310
|
+
status: execution.status,
|
|
2311
|
+
nextAction,
|
|
2312
|
+
...nextAction === "APPROVE_IN_LINK" ? { approvalUrl: execution.approvalUrl } : {},
|
|
2313
|
+
execution,
|
|
2314
|
+
..."checkout" in result ? { checkout: result.checkout } : {}
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
_client = new WeakMap();
|
|
2319
|
+
_defaults = new WeakMap();
|
|
2102
2320
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2103
2321
|
0 && (module.exports = {
|
|
2104
2322
|
BrowserActionFinalizationPendingError,
|
|
@@ -2112,6 +2330,8 @@ var index_default = Conto;
|
|
|
2112
2330
|
ControlledExternalCardPaymentTool,
|
|
2113
2331
|
ExternalCardConfirmationPendingError,
|
|
2114
2332
|
Giro,
|
|
2333
|
+
LinkClient,
|
|
2334
|
+
LinkPaymentExecutionError,
|
|
2115
2335
|
TestCardsClient,
|
|
2116
2336
|
canonicalizeBrowserCheckoutTarget,
|
|
2117
2337
|
createVerifiedBrowserCheckoutDriver,
|
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.
|
|
20
|
+
version: "0.6.0-beta.1",
|
|
21
21
|
description: "Conto SDK for AI agent payment authorization",
|
|
22
22
|
main: "./dist/index.js",
|
|
23
23
|
module: "./dist/index.mjs",
|
|
@@ -42,10 +42,16 @@ 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"
|
|
45
50
|
}
|
|
46
51
|
},
|
|
47
52
|
publishConfig: {
|
|
48
|
-
access: "public"
|
|
53
|
+
access: "public",
|
|
54
|
+
tag: "beta"
|
|
49
55
|
},
|
|
50
56
|
engines: {
|
|
51
57
|
node: ">=20.19.0"
|
|
@@ -54,11 +60,11 @@ var require_package = __commonJS({
|
|
|
54
60
|
"dist"
|
|
55
61
|
],
|
|
56
62
|
scripts: {
|
|
57
|
-
build: "tsup
|
|
58
|
-
dev: "tsup
|
|
63
|
+
build: "tsup",
|
|
64
|
+
dev: "tsup --watch",
|
|
59
65
|
lint: "tsc --noEmit",
|
|
60
66
|
test: "npm run lint && npm run test:contract && npm run test:consumer",
|
|
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",
|
|
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/link.test.mjs test/link-testing.test.mjs",
|
|
62
68
|
"test:consumer": "npm run build && node --test test/consumer.test.mjs",
|
|
63
69
|
"verify:release": "node scripts/verify-release.mjs",
|
|
64
70
|
prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
|
|
@@ -887,7 +893,7 @@ function isValidBrowserActionObservation(value) {
|
|
|
887
893
|
}
|
|
888
894
|
}
|
|
889
895
|
var ContoError = class extends Error {
|
|
890
|
-
constructor(message, metadata) {
|
|
896
|
+
constructor(message, metadata, diagnostics) {
|
|
891
897
|
super(message);
|
|
892
898
|
this.name = "ContoError";
|
|
893
899
|
this.code = metadata.code;
|
|
@@ -898,6 +904,30 @@ var ContoError = class extends Error {
|
|
|
898
904
|
this.actionUrl = metadata.actionUrl;
|
|
899
905
|
this.fieldErrors = metadata.fieldErrors;
|
|
900
906
|
this.reasons = metadata.reasons;
|
|
907
|
+
if (diagnostics !== null && typeof diagnostics === "object" && !Array.isArray(diagnostics) && "version" in diagnostics && diagnostics.version === "conto.setup-diagnostics.v1") {
|
|
908
|
+
this.diagnostics = diagnostics;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
};
|
|
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;
|
|
901
931
|
}
|
|
902
932
|
};
|
|
903
933
|
var ExternalCardConfirmationPendingError = class extends ContoError {
|
|
@@ -989,6 +1019,7 @@ var Conto = class {
|
|
|
989
1019
|
this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
|
|
990
1020
|
assertSecureBaseUrl(this.baseUrl);
|
|
991
1021
|
this.timeout = config.timeout || 3e4;
|
|
1022
|
+
this.link = new LinkClient(this, config.link);
|
|
992
1023
|
this.payments = new PaymentsClient(this);
|
|
993
1024
|
this.cards = new CardsClient(this);
|
|
994
1025
|
this.connectedCards = new ConnectedCardsClient(this);
|
|
@@ -1001,10 +1032,24 @@ var Conto = class {
|
|
|
1001
1032
|
* explicitly bound to this agent. No Link token or payment artifact is
|
|
1002
1033
|
* returned through the agent SDK.
|
|
1003
1034
|
*/
|
|
1004
|
-
async listStripeLinkPaymentMethods(connectionId) {
|
|
1035
|
+
async listStripeLinkPaymentMethods(connectionId, options) {
|
|
1005
1036
|
return this.request(
|
|
1006
1037
|
"GET",
|
|
1007
|
-
withQuery("/api/sdk/stripe-link/payment-methods", {
|
|
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
|
+
})
|
|
1008
1053
|
);
|
|
1009
1054
|
}
|
|
1010
1055
|
/**
|
|
@@ -1300,7 +1345,8 @@ var Conto = class {
|
|
|
1300
1345
|
if (!response.ok) {
|
|
1301
1346
|
throw new ContoError(
|
|
1302
1347
|
responseErrorMessage(data, response.status),
|
|
1303
|
-
responseErrorMetadata(response, data)
|
|
1348
|
+
responseErrorMetadata(response, data),
|
|
1349
|
+
data !== null && typeof data === "object" && "diagnostics" in data ? data.diagnostics : void 0
|
|
1304
1350
|
);
|
|
1305
1351
|
}
|
|
1306
1352
|
return data;
|
|
@@ -1933,6 +1979,176 @@ var MppClient = class {
|
|
|
1933
1979
|
}
|
|
1934
1980
|
};
|
|
1935
1981
|
var index_default = Conto;
|
|
1982
|
+
var _client, _defaults;
|
|
1983
|
+
var LinkClient = class {
|
|
1984
|
+
constructor(client, defaults) {
|
|
1985
|
+
__privateAdd(this, _client);
|
|
1986
|
+
__privateAdd(this, _defaults);
|
|
1987
|
+
__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
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Begin one exact intent and execute only if already READY. Otherwise return
|
|
2007
|
+
* the owner's approval action or existing state immediately, without polling.
|
|
2008
|
+
*/
|
|
2009
|
+
async pay(input) {
|
|
2010
|
+
const connectionId = this.identifier(
|
|
2011
|
+
input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
|
|
2012
|
+
"connectionId"
|
|
2013
|
+
);
|
|
2014
|
+
const paymentMethodId = this.identifier(
|
|
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
|
+
}
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
const result = await __privateGet(this, _client).beginCommerceExecution({
|
|
2046
|
+
executionMethod: "LINK_PAY_TOKEN",
|
|
2047
|
+
connectionId,
|
|
2048
|
+
paymentMethodId,
|
|
2049
|
+
idempotencyKey: input.idempotencyKey,
|
|
2050
|
+
amountMinor: input.amountMinor,
|
|
2051
|
+
currency: input.currency ?? "USD",
|
|
2052
|
+
purpose: input.purpose,
|
|
2053
|
+
category: input.category,
|
|
2054
|
+
counterparty: { canonicalId: input.merchantId, checkoutUrl: input.checkoutUrl },
|
|
2055
|
+
cart: {
|
|
2056
|
+
lineItems: input.lineItems,
|
|
2057
|
+
totals: input.totals ?? [{ type: "total", label: "Total", amountMinor: input.amountMinor }]
|
|
2058
|
+
},
|
|
2059
|
+
providerPresentation: {
|
|
2060
|
+
approvalSummary: input.purpose.trim().slice(0, 500),
|
|
2061
|
+
lineItemLabels: input.lineItems.map((item) => item.name.trim().slice(0, 120))
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
2064
|
+
return this.executeIfReady(result);
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Continue the existing payment after approval. Reads before any mutation and
|
|
2068
|
+
* never submits an execution already processing or requiring reconciliation.
|
|
2069
|
+
*/
|
|
2070
|
+
async resume(executionId) {
|
|
2071
|
+
const id = this.identifier(executionId, "executionId");
|
|
2072
|
+
const current = await __privateGet(this, _client).getCommerceExecution(id);
|
|
2073
|
+
this.assertLinkExecution(current);
|
|
2074
|
+
if (["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(current.execution.status)) {
|
|
2075
|
+
return this.executeIfReady(await __privateGet(this, _client).refreshCommerceExecution(id));
|
|
2076
|
+
}
|
|
2077
|
+
return this.executeIfReady(current);
|
|
2078
|
+
}
|
|
2079
|
+
/** Observe and reconcile provider state. This method never submits a payment. */
|
|
2080
|
+
async status(executionId) {
|
|
2081
|
+
const id = this.identifier(executionId, "executionId");
|
|
2082
|
+
this.assertLinkExecution(await __privateGet(this, _client).getCommerceExecution(id));
|
|
2083
|
+
return this.result(await __privateGet(this, _client).refreshCommerceExecution(id));
|
|
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);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
identifier(value, field) {
|
|
2096
|
+
if (typeof value !== "string" || !value.trim() || value.length > 255) {
|
|
2097
|
+
throw new ContoError(
|
|
2098
|
+
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`,
|
|
2099
|
+
{
|
|
2100
|
+
code: "LINK_CONFIGURATION_REQUIRED",
|
|
2101
|
+
status: 0
|
|
2102
|
+
}
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
return value.trim();
|
|
2106
|
+
}
|
|
2107
|
+
assertLinkExecution(result) {
|
|
2108
|
+
const execution = result.execution;
|
|
2109
|
+
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") {
|
|
2110
|
+
throw new ContoError("This execution is not a Link Pay Token checkout", {
|
|
2111
|
+
code: "LINK_EXECUTION_MISMATCH",
|
|
2112
|
+
status: 0
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
async executeIfReady(result) {
|
|
2117
|
+
this.assertLinkExecution(result);
|
|
2118
|
+
if (result.execution.status !== "READY") return this.result(result);
|
|
2119
|
+
try {
|
|
2120
|
+
return this.result(await __privateGet(this, _client).executeCommerceExecution(result.execution.id));
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
throw new LinkPaymentExecutionError(result.execution.id, error);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
result(result) {
|
|
2126
|
+
this.assertLinkExecution(result);
|
|
2127
|
+
const { execution } = result;
|
|
2128
|
+
const awaitingApproval = ["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(
|
|
2129
|
+
execution.status
|
|
2130
|
+
);
|
|
2131
|
+
const nextAction = execution.status === "READY" ? "RESUME" : execution.status === "NEEDS_RECONCILIATION" ? "RECONCILE" : awaitingApproval && execution.approvalUrl ? "APPROVE_IN_LINK" : awaitingApproval || ["DRAFT", "PROCESSING"].includes(execution.status) ? "WAIT" : [
|
|
2132
|
+
"SETTLED",
|
|
2133
|
+
"COMPLETED",
|
|
2134
|
+
"DENIED",
|
|
2135
|
+
"CANCELLED",
|
|
2136
|
+
"FAILED",
|
|
2137
|
+
"RETURNED",
|
|
2138
|
+
"EXPIRED"
|
|
2139
|
+
].includes(execution.status) ? "NONE" : "RECONCILE";
|
|
2140
|
+
return {
|
|
2141
|
+
executionId: execution.id,
|
|
2142
|
+
status: execution.status,
|
|
2143
|
+
nextAction,
|
|
2144
|
+
...nextAction === "APPROVE_IN_LINK" ? { approvalUrl: execution.approvalUrl } : {},
|
|
2145
|
+
execution,
|
|
2146
|
+
..."checkout" in result ? { checkout: result.checkout } : {}
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
_client = new WeakMap();
|
|
2151
|
+
_defaults = new WeakMap();
|
|
1936
2152
|
export {
|
|
1937
2153
|
BrowserActionFinalizationPendingError,
|
|
1938
2154
|
BrowserCheckoutTargetVerificationError,
|
|
@@ -1945,6 +2161,8 @@ export {
|
|
|
1945
2161
|
ControlledExternalCardPaymentTool,
|
|
1946
2162
|
ExternalCardConfirmationPendingError,
|
|
1947
2163
|
Conto as Giro,
|
|
2164
|
+
LinkClient,
|
|
2165
|
+
LinkPaymentExecutionError,
|
|
1948
2166
|
TestCardsClient,
|
|
1949
2167
|
canonicalizeBrowserCheckoutTarget,
|
|
1950
2168
|
createVerifiedBrowserCheckoutDriver,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LinkPaymentResult, LinkSetupInput, LinkSetupResult, ListStripeLinkPaymentMethodsResult, LinkPaymentInput } from './index.mjs';
|
|
2
|
+
|
|
3
|
+
type LinkTestScenario = 'approved' | 'denied' | 'expired' | 'interrupted';
|
|
4
|
+
type SimulatedLinkPaymentResult = LinkPaymentResult & {
|
|
5
|
+
mode: 'SIMULATED';
|
|
6
|
+
settlementVerified: false;
|
|
7
|
+
};
|
|
8
|
+
interface LinkTestOptions {
|
|
9
|
+
scenario?: LinkTestScenario;
|
|
10
|
+
/** A local fixture limit, not a real Conto policy or available balance. Defaults to 5000 cents. */
|
|
11
|
+
maximumAmountMinor?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create an isolated in-memory test account with the same pay/resume/status API.
|
|
15
|
+
* Approval is explicit through link.approve(), a test-only method. Nothing settles.
|
|
16
|
+
*/
|
|
17
|
+
declare function createLinkTestClient(options?: LinkTestOptions): {
|
|
18
|
+
mode: "SIMULATED";
|
|
19
|
+
link: {
|
|
20
|
+
setup: (input?: LinkSetupInput) => Promise<LinkSetupResult & {
|
|
21
|
+
mode: "SIMULATED";
|
|
22
|
+
settlementVerified: false;
|
|
23
|
+
}>;
|
|
24
|
+
paymentMethods: () => Promise<ListStripeLinkPaymentMethodsResult & {
|
|
25
|
+
mode: "SIMULATED";
|
|
26
|
+
settlementVerified: false;
|
|
27
|
+
}>;
|
|
28
|
+
pay: (input: LinkPaymentInput) => Promise<SimulatedLinkPaymentResult>;
|
|
29
|
+
resume: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
30
|
+
status: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
31
|
+
cancel: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
32
|
+
/** Test-only owner action. Never available on the live Link client. */
|
|
33
|
+
approve: (id: string) => Promise<SimulatedLinkPaymentResult>;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { type LinkTestOptions, type LinkTestScenario, type SimulatedLinkPaymentResult, createLinkTestClient };
|