@conto_finance/sdk 0.5.5 → 0.6.0-beta.2

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/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.5.5",
20
+ version: "0.6.0-beta.2",
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 src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts",
58
- dev: "tsup src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts --watch",
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 test/checkout.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"
@@ -522,8 +528,11 @@ var AdminCardsClient = class {
522
528
  return this.client.request("GET", `/api/external-cards/requests${suffix ? `?${suffix}` : ""}`);
523
529
  }
524
530
  /**
525
- * Resolve a claimed execution after checking the customer's processor or
526
- * receipt. Resolving NOT_EXECUTED releases the reserved policy amount.
531
+ * @deprecated Resolution requires a signed-in user. SDK admin keys and
532
+ * organization API keys receive HTTP 403 INSUFFICIENT_PERMISSIONS from this
533
+ * endpoint. Have a signed-in organization owner or admin review the processor
534
+ * evidence and resolve the request through Conto's authenticated interface.
535
+ * Retained for source compatibility; this method does not bypass that restriction.
527
536
  */
528
537
  async resolveRequest(requestId, input) {
529
538
  return this.client.request(
@@ -887,7 +896,7 @@ function isValidBrowserActionObservation(value) {
887
896
  }
888
897
  }
889
898
  var ContoError = class extends Error {
890
- constructor(message, metadata) {
899
+ constructor(message, metadata, diagnostics) {
891
900
  super(message);
892
901
  this.name = "ContoError";
893
902
  this.code = metadata.code;
@@ -898,6 +907,30 @@ var ContoError = class extends Error {
898
907
  this.actionUrl = metadata.actionUrl;
899
908
  this.fieldErrors = metadata.fieldErrors;
900
909
  this.reasons = metadata.reasons;
910
+ if (diagnostics !== null && typeof diagnostics === "object" && !Array.isArray(diagnostics) && "version" in diagnostics && diagnostics.version === "conto.setup-diagnostics.v1") {
911
+ this.diagnostics = diagnostics;
912
+ }
913
+ }
914
+ };
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;
901
934
  }
902
935
  };
903
936
  var ExternalCardConfirmationPendingError = class extends ContoError {
@@ -989,6 +1022,8 @@ var Conto = class {
989
1022
  this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
990
1023
  assertSecureBaseUrl(this.baseUrl);
991
1024
  this.timeout = config.timeout || 3e4;
1025
+ this.link = new LinkClient(this, config.link);
1026
+ this.checkout = new CheckoutClient(this);
992
1027
  this.payments = new PaymentsClient(this);
993
1028
  this.cards = new CardsClient(this);
994
1029
  this.connectedCards = new ConnectedCardsClient(this);
@@ -1001,10 +1036,24 @@ var Conto = class {
1001
1036
  * explicitly bound to this agent. No Link token or payment artifact is
1002
1037
  * returned through the agent SDK.
1003
1038
  */
1004
- async listStripeLinkPaymentMethods(connectionId) {
1039
+ async listStripeLinkPaymentMethods(connectionId, options) {
1005
1040
  return this.request(
1006
1041
  "GET",
1007
- withQuery("/api/sdk/stripe-link/payment-methods", { connectionId })
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
+ })
1008
1057
  );
1009
1058
  }
1010
1059
  /**
@@ -1300,7 +1349,8 @@ var Conto = class {
1300
1349
  if (!response.ok) {
1301
1350
  throw new ContoError(
1302
1351
  responseErrorMessage(data, response.status),
1303
- responseErrorMetadata(response, data)
1352
+ responseErrorMetadata(response, data),
1353
+ data !== null && typeof data === "object" && "diagnostics" in data ? data.diagnostics : void 0
1304
1354
  );
1305
1355
  }
1306
1356
  return data;
@@ -1933,10 +1983,311 @@ var MppClient = class {
1933
1983
  }
1934
1984
  };
1935
1985
  var index_default = Conto;
1986
+ function checkoutError(message, code = "INVALID_CHECKOUT_CHECKPOINT") {
1987
+ return new ContoError(message, { code, status: 0 });
1988
+ }
1989
+ function checkpointCart(amountMinor, lineItems, totals) {
1990
+ if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0) {
1991
+ throw checkoutError("amountMinor must be a positive integer number of cents");
1992
+ }
1993
+ if (!Array.isArray(lineItems) || lineItems.length === 0) {
1994
+ throw checkoutError("Supply at least one line item");
1995
+ }
1996
+ return {
1997
+ lineItems,
1998
+ totals: totals ?? [{ type: "total", label: "Total", amountMinor }]
1999
+ };
2000
+ }
2001
+ var _client;
2002
+ var CheckoutClient = class {
2003
+ constructor(client) {
2004
+ __privateAdd(this, _client);
2005
+ __privateSet(this, _client, client);
2006
+ }
2007
+ /** First checkpoint. A ContoError means Conto did not approve: stop the checkout. */
2008
+ async begin(input) {
2009
+ const idempotencyKey = input.idempotencyKey?.trim();
2010
+ if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
2011
+ throw checkoutError(
2012
+ "idempotencyKey must contain 8 to 255 characters",
2013
+ "INVALID_IDEMPOTENCY_KEY"
2014
+ );
2015
+ }
2016
+ if (input.currency !== void 0 && input.currency !== "USD") {
2017
+ throw checkoutError("Only USD checkouts are supported");
2018
+ }
2019
+ const result = await __privateGet(this, _client).request(
2020
+ "POST",
2021
+ "/api/sdk/checkout-checkpoints",
2022
+ {
2023
+ idempotencyKey,
2024
+ merchant: input.merchant,
2025
+ amountMinor: input.amountMinor,
2026
+ currency: "USD",
2027
+ purpose: input.purpose,
2028
+ ...input.category ? { category: input.category } : {},
2029
+ ...input.checkoutMethod ? { checkoutMethod: input.checkoutMethod } : {},
2030
+ cart: checkpointCart(input.amountMinor, input.lineItems, input.totals)
2031
+ },
2032
+ { idempotent: true }
2033
+ );
2034
+ return result.checkpoint;
2035
+ }
2036
+ /**
2037
+ * Second checkpoint, right before the payment is submitted. If the total
2038
+ * changed since begin() (tax or shipping, for example), this cancels the
2039
+ * 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.
2041
+ */
2042
+ async confirm(checkpoint, input) {
2043
+ if (input.currency !== void 0 && input.currency !== "USD") {
2044
+ throw checkoutError("Only USD checkouts are supported");
2045
+ }
2046
+ if (!Number.isSafeInteger(input.amountMinor) || input.amountMinor <= 0) {
2047
+ throw checkoutError("amountMinor must be a positive integer number of cents");
2048
+ }
2049
+ let current = checkpoint;
2050
+ if (input.amountMinor !== checkpoint.amountMinor) {
2051
+ try {
2052
+ await this.cancel(checkpoint.id);
2053
+ } catch (error) {
2054
+ const closed = error instanceof ContoError && error.status === 409 && ["CANCELLED", "EXPIRED", "DENIED"].includes((await this.get(checkpoint.id)).stage);
2055
+ if (!closed) throw error;
2056
+ }
2057
+ current = await this.begin({
2058
+ idempotencyKey: `${checkpoint.idempotencyKey.slice(0, 200)}:final-${input.amountMinor}`,
2059
+ merchant: {
2060
+ id: checkpoint.merchant.id,
2061
+ name: checkpoint.merchant.name,
2062
+ checkoutUrl: checkpoint.merchant.checkoutUrl ?? ""
2063
+ },
2064
+ amountMinor: input.amountMinor,
2065
+ purpose: checkpoint.purpose,
2066
+ ...checkpoint.category ? { category: checkpoint.category } : {},
2067
+ checkoutMethod: checkpoint.checkoutMethod,
2068
+ lineItems: input.lineItems ?? checkpoint.cart.lineItems,
2069
+ totals: input.totals
2070
+ });
2071
+ }
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
+ }
2084
+ /** Report what happened after a confirmed checkpoint. Safe to repeat with the same outcome. */
2085
+ async reportOutcome(checkpointId, input) {
2086
+ const result = await __privateGet(this, _client).request(
2087
+ "POST",
2088
+ `/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/outcome`,
2089
+ {
2090
+ outcome: input.outcome,
2091
+ ...input.reference ? { reference: input.reference } : {}
2092
+ },
2093
+ { idempotent: true }
2094
+ );
2095
+ return result.checkpoint;
2096
+ }
2097
+ /** Cancel a checkpoint that was not confirmed and release its spend. */
2098
+ async cancel(checkpointId) {
2099
+ const result = await __privateGet(this, _client).request(
2100
+ "POST",
2101
+ `/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/cancel`,
2102
+ {},
2103
+ { idempotent: true }
2104
+ );
2105
+ return result.checkpoint;
2106
+ }
2107
+ async get(checkpointId) {
2108
+ const result = await __privateGet(this, _client).request(
2109
+ "GET",
2110
+ `/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}`
2111
+ );
2112
+ return result.checkpoint;
2113
+ }
2114
+ };
2115
+ _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();
1936
2286
  export {
1937
2287
  BrowserActionFinalizationPendingError,
1938
2288
  BrowserCheckoutTargetVerificationError,
1939
2289
  CardsClient,
2290
+ CheckoutClient,
1940
2291
  ConnectedCardsClient,
1941
2292
  Conto,
1942
2293
  ContoAdmin,
@@ -1945,6 +2296,8 @@ export {
1945
2296
  ControlledExternalCardPaymentTool,
1946
2297
  ExternalCardConfirmationPendingError,
1947
2298
  Conto as Giro,
2299
+ LinkClient,
2300
+ LinkPaymentExecutionError,
1948
2301
  TestCardsClient,
1949
2302
  canonicalizeBrowserCheckoutTarget,
1950
2303
  createVerifiedBrowserCheckoutDriver,
@@ -0,0 +1,49 @@
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 };
@@ -0,0 +1,49 @@
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 };