@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.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.5.5",
49
+ version: "0.6.0-beta.2",
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 src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts",
87
- dev: "tsup src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts --watch",
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 test/checkout.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"
@@ -153,6 +159,7 @@ __export(index_exports, {
153
159
  BrowserActionFinalizationPendingError: () => BrowserActionFinalizationPendingError,
154
160
  BrowserCheckoutTargetVerificationError: () => BrowserCheckoutTargetVerificationError,
155
161
  CardsClient: () => CardsClient,
162
+ CheckoutClient: () => CheckoutClient,
156
163
  ConnectedCardsClient: () => ConnectedCardsClient,
157
164
  Conto: () => Conto,
158
165
  ContoAdmin: () => ContoAdmin,
@@ -161,6 +168,8 @@ __export(index_exports, {
161
168
  ControlledExternalCardPaymentTool: () => ControlledExternalCardPaymentTool,
162
169
  ExternalCardConfirmationPendingError: () => ExternalCardConfirmationPendingError,
163
170
  Giro: () => Conto,
171
+ LinkClient: () => LinkClient,
172
+ LinkPaymentExecutionError: () => LinkPaymentExecutionError,
164
173
  TestCardsClient: () => TestCardsClient,
165
174
  canonicalizeBrowserCheckoutTarget: () => canonicalizeBrowserCheckoutTarget,
166
175
  createVerifiedBrowserCheckoutDriver: () => createVerifiedBrowserCheckoutDriver,
@@ -688,8 +697,11 @@ var AdminCardsClient = class {
688
697
  return this.client.request("GET", `/api/external-cards/requests${suffix ? `?${suffix}` : ""}`);
689
698
  }
690
699
  /**
691
- * Resolve a claimed execution after checking the customer's processor or
692
- * receipt. Resolving NOT_EXECUTED releases the reserved policy amount.
700
+ * @deprecated Resolution requires a signed-in user. SDK admin keys and
701
+ * organization API keys receive HTTP 403 INSUFFICIENT_PERMISSIONS from this
702
+ * endpoint. Have a signed-in organization owner or admin review the processor
703
+ * evidence and resolve the request through Conto's authenticated interface.
704
+ * Retained for source compatibility; this method does not bypass that restriction.
693
705
  */
694
706
  async resolveRequest(requestId, input) {
695
707
  return this.client.request(
@@ -1053,7 +1065,7 @@ function isValidBrowserActionObservation(value) {
1053
1065
  }
1054
1066
  }
1055
1067
  var ContoError = class extends Error {
1056
- constructor(message, metadata) {
1068
+ constructor(message, metadata, diagnostics) {
1057
1069
  super(message);
1058
1070
  this.name = "ContoError";
1059
1071
  this.code = metadata.code;
@@ -1064,6 +1076,30 @@ var ContoError = class extends Error {
1064
1076
  this.actionUrl = metadata.actionUrl;
1065
1077
  this.fieldErrors = metadata.fieldErrors;
1066
1078
  this.reasons = metadata.reasons;
1079
+ if (diagnostics !== null && typeof diagnostics === "object" && !Array.isArray(diagnostics) && "version" in diagnostics && diagnostics.version === "conto.setup-diagnostics.v1") {
1080
+ this.diagnostics = diagnostics;
1081
+ }
1082
+ }
1083
+ };
1084
+ var LinkPaymentExecutionError = class extends ContoError {
1085
+ constructor(executionId, error) {
1086
+ const original = error instanceof ContoError ? error : void 0;
1087
+ super(
1088
+ original?.message ?? "Link execution could not be confirmed",
1089
+ {
1090
+ code: original?.code ?? "LINK_EXECUTION_UNCONFIRMED",
1091
+ status: original?.status ?? 0,
1092
+ requestId: original?.requestId,
1093
+ retryAfter: original?.retryAfter,
1094
+ fieldErrors: original?.fieldErrors,
1095
+ reasons: original?.reasons,
1096
+ retryGuidance: "Call link.status(executionId) to reconcile. Do not create another purchase or retry execution blindly."
1097
+ },
1098
+ original?.diagnostics
1099
+ );
1100
+ this.nextAction = "RECONCILE";
1101
+ this.name = "LinkPaymentExecutionError";
1102
+ this.executionId = executionId;
1067
1103
  }
1068
1104
  };
1069
1105
  var ExternalCardConfirmationPendingError = class extends ContoError {
@@ -1155,6 +1191,8 @@ var Conto = class {
1155
1191
  this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
1156
1192
  assertSecureBaseUrl(this.baseUrl);
1157
1193
  this.timeout = config.timeout || 3e4;
1194
+ this.link = new LinkClient(this, config.link);
1195
+ this.checkout = new CheckoutClient(this);
1158
1196
  this.payments = new PaymentsClient(this);
1159
1197
  this.cards = new CardsClient(this);
1160
1198
  this.connectedCards = new ConnectedCardsClient(this);
@@ -1167,10 +1205,24 @@ var Conto = class {
1167
1205
  * explicitly bound to this agent. No Link token or payment artifact is
1168
1206
  * returned through the agent SDK.
1169
1207
  */
1170
- async listStripeLinkPaymentMethods(connectionId) {
1208
+ async listStripeLinkPaymentMethods(connectionId, options) {
1171
1209
  return this.request(
1172
1210
  "GET",
1173
- withQuery("/api/sdk/stripe-link/payment-methods", { connectionId })
1211
+ withQuery("/api/sdk/stripe-link/payment-methods", {
1212
+ connectionId,
1213
+ ...options?.mode ? { mode: options.mode } : {}
1214
+ })
1215
+ );
1216
+ }
1217
+ /** Read live Link configuration diagnostics without retrieving credentials or authorizing payment. */
1218
+ async getLinkSetup(input = {}) {
1219
+ return this.request(
1220
+ "GET",
1221
+ withQuery("/api/sdk/stripe-link/setup", {
1222
+ connectionId: input.connectionId,
1223
+ paymentMethodId: input.paymentMethodId,
1224
+ merchantId: input.merchantId
1225
+ })
1174
1226
  );
1175
1227
  }
1176
1228
  /**
@@ -1466,7 +1518,8 @@ var Conto = class {
1466
1518
  if (!response.ok) {
1467
1519
  throw new ContoError(
1468
1520
  responseErrorMessage(data, response.status),
1469
- responseErrorMetadata(response, data)
1521
+ responseErrorMetadata(response, data),
1522
+ data !== null && typeof data === "object" && "diagnostics" in data ? data.diagnostics : void 0
1470
1523
  );
1471
1524
  }
1472
1525
  return data;
@@ -2099,11 +2152,312 @@ var MppClient = class {
2099
2152
  }
2100
2153
  };
2101
2154
  var index_default = Conto;
2155
+ function checkoutError(message, code = "INVALID_CHECKOUT_CHECKPOINT") {
2156
+ return new ContoError(message, { code, status: 0 });
2157
+ }
2158
+ function checkpointCart(amountMinor, lineItems, totals) {
2159
+ if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0) {
2160
+ throw checkoutError("amountMinor must be a positive integer number of cents");
2161
+ }
2162
+ if (!Array.isArray(lineItems) || lineItems.length === 0) {
2163
+ throw checkoutError("Supply at least one line item");
2164
+ }
2165
+ return {
2166
+ lineItems,
2167
+ totals: totals ?? [{ type: "total", label: "Total", amountMinor }]
2168
+ };
2169
+ }
2170
+ var _client;
2171
+ var CheckoutClient = class {
2172
+ constructor(client) {
2173
+ __privateAdd(this, _client);
2174
+ __privateSet(this, _client, client);
2175
+ }
2176
+ /** First checkpoint. A ContoError means Conto did not approve: stop the checkout. */
2177
+ async begin(input) {
2178
+ const idempotencyKey = input.idempotencyKey?.trim();
2179
+ if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
2180
+ throw checkoutError(
2181
+ "idempotencyKey must contain 8 to 255 characters",
2182
+ "INVALID_IDEMPOTENCY_KEY"
2183
+ );
2184
+ }
2185
+ if (input.currency !== void 0 && input.currency !== "USD") {
2186
+ throw checkoutError("Only USD checkouts are supported");
2187
+ }
2188
+ const result = await __privateGet(this, _client).request(
2189
+ "POST",
2190
+ "/api/sdk/checkout-checkpoints",
2191
+ {
2192
+ idempotencyKey,
2193
+ merchant: input.merchant,
2194
+ amountMinor: input.amountMinor,
2195
+ currency: "USD",
2196
+ purpose: input.purpose,
2197
+ ...input.category ? { category: input.category } : {},
2198
+ ...input.checkoutMethod ? { checkoutMethod: input.checkoutMethod } : {},
2199
+ cart: checkpointCart(input.amountMinor, input.lineItems, input.totals)
2200
+ },
2201
+ { idempotent: true }
2202
+ );
2203
+ return result.checkpoint;
2204
+ }
2205
+ /**
2206
+ * Second checkpoint, right before the payment is submitted. If the total
2207
+ * changed since begin() (tax or shipping, for example), this cancels the
2208
+ * approved checkpoint and starts a new one at the final total, so the agent's
2209
+ * rules see the amount actually being paid. Pay only if this resolves.
2210
+ */
2211
+ async confirm(checkpoint, input) {
2212
+ if (input.currency !== void 0 && input.currency !== "USD") {
2213
+ throw checkoutError("Only USD checkouts are supported");
2214
+ }
2215
+ if (!Number.isSafeInteger(input.amountMinor) || input.amountMinor <= 0) {
2216
+ throw checkoutError("amountMinor must be a positive integer number of cents");
2217
+ }
2218
+ let current = checkpoint;
2219
+ if (input.amountMinor !== checkpoint.amountMinor) {
2220
+ try {
2221
+ await this.cancel(checkpoint.id);
2222
+ } catch (error) {
2223
+ const closed = error instanceof ContoError && error.status === 409 && ["CANCELLED", "EXPIRED", "DENIED"].includes((await this.get(checkpoint.id)).stage);
2224
+ if (!closed) throw error;
2225
+ }
2226
+ current = await this.begin({
2227
+ idempotencyKey: `${checkpoint.idempotencyKey.slice(0, 200)}:final-${input.amountMinor}`,
2228
+ merchant: {
2229
+ id: checkpoint.merchant.id,
2230
+ name: checkpoint.merchant.name,
2231
+ checkoutUrl: checkpoint.merchant.checkoutUrl ?? ""
2232
+ },
2233
+ amountMinor: input.amountMinor,
2234
+ purpose: checkpoint.purpose,
2235
+ ...checkpoint.category ? { category: checkpoint.category } : {},
2236
+ checkoutMethod: checkpoint.checkoutMethod,
2237
+ lineItems: input.lineItems ?? checkpoint.cart.lineItems,
2238
+ totals: input.totals
2239
+ });
2240
+ }
2241
+ const result = await __privateGet(this, _client).request(
2242
+ "POST",
2243
+ `/api/sdk/checkout-checkpoints/${encodeURIComponent(current.id)}/confirm`,
2244
+ {
2245
+ amountMinor: input.amountMinor,
2246
+ currency: "USD",
2247
+ ...input.pageUrl ? { pageUrl: input.pageUrl } : {}
2248
+ },
2249
+ { idempotent: true }
2250
+ );
2251
+ return result.checkpoint;
2252
+ }
2253
+ /** Report what happened after a confirmed checkpoint. Safe to repeat with the same outcome. */
2254
+ async reportOutcome(checkpointId, input) {
2255
+ const result = await __privateGet(this, _client).request(
2256
+ "POST",
2257
+ `/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/outcome`,
2258
+ {
2259
+ outcome: input.outcome,
2260
+ ...input.reference ? { reference: input.reference } : {}
2261
+ },
2262
+ { idempotent: true }
2263
+ );
2264
+ return result.checkpoint;
2265
+ }
2266
+ /** Cancel a checkpoint that was not confirmed and release its spend. */
2267
+ async cancel(checkpointId) {
2268
+ const result = await __privateGet(this, _client).request(
2269
+ "POST",
2270
+ `/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}/cancel`,
2271
+ {},
2272
+ { idempotent: true }
2273
+ );
2274
+ return result.checkpoint;
2275
+ }
2276
+ async get(checkpointId) {
2277
+ const result = await __privateGet(this, _client).request(
2278
+ "GET",
2279
+ `/api/sdk/checkout-checkpoints/${encodeURIComponent(checkpointId)}`
2280
+ );
2281
+ return result.checkpoint;
2282
+ }
2283
+ };
2284
+ _client = new WeakMap();
2285
+ var _client2, _defaults;
2286
+ var LinkClient = class {
2287
+ constructor(client, defaults) {
2288
+ __privateAdd(this, _client2);
2289
+ __privateAdd(this, _defaults);
2290
+ __privateSet(this, _client2, client);
2291
+ __privateSet(this, _defaults, defaults ? { ...defaults } : void 0);
2292
+ }
2293
+ /** Inspect the configured live path. This is not a payment authorization or provider connectivity test. */
2294
+ async setup(input = {}) {
2295
+ return __privateGet(this, _client2).getLinkSetup({
2296
+ connectionId: input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
2297
+ paymentMethodId: input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
2298
+ merchantId: input.merchantId
2299
+ });
2300
+ }
2301
+ /** Read masked LIVE funding sources; never selects a source or initiates payment. */
2302
+ async paymentMethods(connectionId) {
2303
+ return __privateGet(this, _client2).listStripeLinkPaymentMethods(
2304
+ this.identifier(connectionId ?? __privateGet(this, _defaults)?.connectionId, "connectionId"),
2305
+ { mode: "LIVE" }
2306
+ );
2307
+ }
2308
+ /**
2309
+ * Begin one exact intent and execute only if already READY. Otherwise return
2310
+ * the owner's approval action or existing state immediately, without polling.
2311
+ */
2312
+ async pay(input) {
2313
+ const connectionId = this.identifier(
2314
+ input.connectionId ?? __privateGet(this, _defaults)?.connectionId,
2315
+ "connectionId"
2316
+ );
2317
+ const paymentMethodId = this.identifier(
2318
+ input.paymentMethodId ?? __privateGet(this, _defaults)?.paymentMethodId,
2319
+ "paymentMethodId"
2320
+ );
2321
+ 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()) {
2322
+ throw new ContoError("Supply a positive USD amount in cents, purpose, and line items", {
2323
+ code: "INVALID_LINK_PURCHASE",
2324
+ status: 0
2325
+ });
2326
+ }
2327
+ const itemTotal = input.lineItems.reduce((sum, item) => {
2328
+ if (!item || typeof item.name !== "string" || !item.name.trim() || !Number.isSafeInteger(item.quantity) || item.quantity <= 0 || !Number.isSafeInteger(item.unitAmountMinor) || item.unitAmountMinor < 0) {
2329
+ throw new ContoError(
2330
+ "Each line item needs a name, positive integer quantity, and price in cents",
2331
+ {
2332
+ code: "INVALID_LINK_PURCHASE",
2333
+ status: 0
2334
+ }
2335
+ );
2336
+ }
2337
+ return sum + item.quantity * item.unitAmountMinor;
2338
+ }, 0);
2339
+ if (!input.totals && (!Number.isSafeInteger(itemTotal) || itemTotal !== input.amountMinor)) {
2340
+ throw new ContoError(
2341
+ "Supply the exact totals breakdown when the amount differs from the line items",
2342
+ {
2343
+ code: "LINK_TOTALS_REQUIRED",
2344
+ status: 0
2345
+ }
2346
+ );
2347
+ }
2348
+ const result = await __privateGet(this, _client2).beginCommerceExecution({
2349
+ executionMethod: "LINK_PAY_TOKEN",
2350
+ connectionId,
2351
+ paymentMethodId,
2352
+ idempotencyKey: input.idempotencyKey,
2353
+ amountMinor: input.amountMinor,
2354
+ currency: input.currency ?? "USD",
2355
+ purpose: input.purpose,
2356
+ category: input.category,
2357
+ counterparty: { canonicalId: input.merchantId, checkoutUrl: input.checkoutUrl },
2358
+ cart: {
2359
+ lineItems: input.lineItems,
2360
+ totals: input.totals ?? [{ type: "total", label: "Total", amountMinor: input.amountMinor }]
2361
+ },
2362
+ providerPresentation: {
2363
+ approvalSummary: input.purpose.trim().slice(0, 500),
2364
+ lineItemLabels: input.lineItems.map((item) => item.name.trim().slice(0, 120))
2365
+ }
2366
+ });
2367
+ return this.executeIfReady(result);
2368
+ }
2369
+ /**
2370
+ * Continue the existing payment after approval. Reads before any mutation and
2371
+ * never submits an execution already processing or requiring reconciliation.
2372
+ */
2373
+ async resume(executionId) {
2374
+ const id = this.identifier(executionId, "executionId");
2375
+ const current = await __privateGet(this, _client2).getCommerceExecution(id);
2376
+ this.assertLinkExecution(current);
2377
+ if (["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(current.execution.status)) {
2378
+ return this.executeIfReady(await __privateGet(this, _client2).refreshCommerceExecution(id));
2379
+ }
2380
+ return this.executeIfReady(current);
2381
+ }
2382
+ /** Observe and reconcile provider state. This method never submits a payment. */
2383
+ async status(executionId) {
2384
+ const id = this.identifier(executionId, "executionId");
2385
+ this.assertLinkExecution(await __privateGet(this, _client2).getCommerceExecution(id));
2386
+ return this.result(await __privateGet(this, _client2).refreshCommerceExecution(id));
2387
+ }
2388
+ /** Request cancellation of this existing Link payment; never retried automatically. */
2389
+ async cancel(executionId) {
2390
+ const id = this.identifier(executionId, "executionId");
2391
+ this.assertLinkExecution(await __privateGet(this, _client2).getCommerceExecution(id));
2392
+ try {
2393
+ return this.result(await __privateGet(this, _client2).cancelCommerceExecution(id));
2394
+ } catch (error) {
2395
+ throw new LinkPaymentExecutionError(id, error);
2396
+ }
2397
+ }
2398
+ identifier(value, field) {
2399
+ if (typeof value !== "string" || !value.trim() || value.length > 255) {
2400
+ throw new ContoError(
2401
+ 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`,
2402
+ {
2403
+ code: "LINK_CONFIGURATION_REQUIRED",
2404
+ status: 0
2405
+ }
2406
+ );
2407
+ }
2408
+ return value.trim();
2409
+ }
2410
+ assertLinkExecution(result) {
2411
+ const execution = result.execution;
2412
+ 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") {
2413
+ throw new ContoError("This execution is not a Link Pay Token checkout", {
2414
+ code: "LINK_EXECUTION_MISMATCH",
2415
+ status: 0
2416
+ });
2417
+ }
2418
+ }
2419
+ async executeIfReady(result) {
2420
+ this.assertLinkExecution(result);
2421
+ if (result.execution.status !== "READY") return this.result(result);
2422
+ try {
2423
+ return this.result(await __privateGet(this, _client2).executeCommerceExecution(result.execution.id));
2424
+ } catch (error) {
2425
+ throw new LinkPaymentExecutionError(result.execution.id, error);
2426
+ }
2427
+ }
2428
+ result(result) {
2429
+ this.assertLinkExecution(result);
2430
+ const { execution } = result;
2431
+ const awaitingApproval = ["AUTHORIZING", "AWAITING_APPROVAL", "ACTION_REQUIRED"].includes(
2432
+ execution.status
2433
+ );
2434
+ const nextAction = execution.status === "READY" ? "RESUME" : execution.status === "NEEDS_RECONCILIATION" ? "RECONCILE" : awaitingApproval && execution.approvalUrl ? "APPROVE_IN_LINK" : awaitingApproval || ["DRAFT", "PROCESSING"].includes(execution.status) ? "WAIT" : [
2435
+ "SETTLED",
2436
+ "COMPLETED",
2437
+ "DENIED",
2438
+ "CANCELLED",
2439
+ "FAILED",
2440
+ "RETURNED",
2441
+ "EXPIRED"
2442
+ ].includes(execution.status) ? "NONE" : "RECONCILE";
2443
+ return {
2444
+ executionId: execution.id,
2445
+ status: execution.status,
2446
+ nextAction,
2447
+ ...nextAction === "APPROVE_IN_LINK" ? { approvalUrl: execution.approvalUrl } : {},
2448
+ execution,
2449
+ ..."checkout" in result ? { checkout: result.checkout } : {}
2450
+ };
2451
+ }
2452
+ };
2453
+ _client2 = new WeakMap();
2454
+ _defaults = new WeakMap();
2102
2455
  // Annotate the CommonJS export names for ESM import in node:
2103
2456
  0 && (module.exports = {
2104
2457
  BrowserActionFinalizationPendingError,
2105
2458
  BrowserCheckoutTargetVerificationError,
2106
2459
  CardsClient,
2460
+ CheckoutClient,
2107
2461
  ConnectedCardsClient,
2108
2462
  Conto,
2109
2463
  ContoAdmin,
@@ -2112,6 +2466,8 @@ var index_default = Conto;
2112
2466
  ControlledExternalCardPaymentTool,
2113
2467
  ExternalCardConfirmationPendingError,
2114
2468
  Giro,
2469
+ LinkClient,
2470
+ LinkPaymentExecutionError,
2115
2471
  TestCardsClient,
2116
2472
  canonicalizeBrowserCheckoutTarget,
2117
2473
  createVerifiedBrowserCheckoutDriver,