lane-sdk 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,3249 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var events = require('events');
6
+ var crypto = require('crypto');
7
+ var promises = require('fs/promises');
8
+ var path = require('path');
9
+ var os = require('os');
10
+ var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
11
+ var zod = require('zod');
12
+
13
+ // src/client.ts
14
+ var TIMESTAMP_TOLERANCE_SECONDS = 300;
15
+ var HMACSignature = class {
16
+ secret;
17
+ constructor(secret) {
18
+ this.secret = secret;
19
+ }
20
+ /**
21
+ * Sign request components and return the hex-encoded HMAC-SHA256 signature.
22
+ */
23
+ sign(components) {
24
+ const bodyHash = components.body ? crypto.createHash("sha256").update(components.body).digest("hex") : crypto.createHash("sha256").update("").digest("hex");
25
+ const payload = [
26
+ components.method.toUpperCase(),
27
+ components.path,
28
+ components.timestamp.toString(),
29
+ bodyHash
30
+ ].join("\n");
31
+ return crypto.createHmac("sha256", this.secret).update(payload).digest("hex");
32
+ }
33
+ /**
34
+ * Verify a signature against request components.
35
+ * Uses constant-time comparison to prevent timing attacks.
36
+ */
37
+ verify(signature, components, toleranceSeconds = TIMESTAMP_TOLERANCE_SECONDS) {
38
+ const now = Math.floor(Date.now() / 1e3);
39
+ if (Math.abs(now - components.timestamp) > toleranceSeconds) {
40
+ return false;
41
+ }
42
+ const expected = this.sign(components);
43
+ return constantTimeEqual(expected, signature);
44
+ }
45
+ /**
46
+ * Generate the signature headers for an outgoing request.
47
+ */
48
+ headers(components) {
49
+ const signature = this.sign(components);
50
+ return {
51
+ "X-Lane-Timestamp": components.timestamp.toString(),
52
+ "X-Lane-Signature": `sha256=${signature}`
53
+ };
54
+ }
55
+ };
56
+ function verifyWebhookSignature(payload, signature, secret, timestamp) {
57
+ const hmac = new HMACSignature(secret);
58
+ const sig = signature.startsWith("sha256=") ? signature.slice(7) : signature;
59
+ return hmac.verify(sig, {
60
+ method: "POST",
61
+ path: "/webhook",
62
+ timestamp,
63
+ body: payload
64
+ });
65
+ }
66
+ function constantTimeEqual(a, b) {
67
+ if (a.length !== b.length) {
68
+ return false;
69
+ }
70
+ const bufA = Buffer.from(a, "utf8");
71
+ const bufB = Buffer.from(b, "utf8");
72
+ return crypto.timingSafeEqual(bufA, bufB);
73
+ }
74
+
75
+ // src/errors.ts
76
+ var LaneError = class extends Error {
77
+ /** Machine-readable error code (e.g. 'budget_exceeded'). */
78
+ code;
79
+ /** HTTP status code from the API (or synthetic for client-side errors). */
80
+ statusCode;
81
+ /** Request ID for support and audit trail correlation. */
82
+ requestId;
83
+ /** Whether the caller should retry this request. */
84
+ retryable;
85
+ /** Human-readable suggestion for what the agent should do next. */
86
+ suggestedAction;
87
+ /** Executable suggestion for agents (e.g. "lane.wallet.deposit(2000)"). */
88
+ fix;
89
+ /** Current state context for agent decision-making. */
90
+ currentState;
91
+ constructor(message, options) {
92
+ super(message);
93
+ this.name = "LaneError";
94
+ this.code = options.code;
95
+ this.statusCode = options.statusCode;
96
+ this.requestId = options.requestId ?? "unknown";
97
+ this.retryable = options.retryable ?? false;
98
+ this.suggestedAction = options.suggestedAction;
99
+ Object.setPrototypeOf(this, new.target.prototype);
100
+ }
101
+ /** Serialize to a plain object for structured logging / JSON responses. */
102
+ toJSON() {
103
+ const json = {
104
+ name: this.name,
105
+ message: this.message,
106
+ code: this.code,
107
+ statusCode: this.statusCode,
108
+ requestId: this.requestId,
109
+ retryable: this.retryable,
110
+ suggestedAction: this.suggestedAction
111
+ };
112
+ if (this.fix !== void 0) {
113
+ json.fix = this.fix;
114
+ }
115
+ if (this.currentState !== void 0) {
116
+ json.currentState = this.currentState;
117
+ }
118
+ return json;
119
+ }
120
+ };
121
+ var LaneAuthError = class _LaneAuthError extends LaneError {
122
+ constructor(message, options) {
123
+ super(message, { statusCode: 401, ...options });
124
+ this.name = "LaneAuthError";
125
+ }
126
+ static invalidApiKey(requestId) {
127
+ return new _LaneAuthError("Invalid API key. Check your key and try again.", {
128
+ code: "invalid_api_key",
129
+ statusCode: 401,
130
+ requestId,
131
+ suggestedAction: "Verify the API key is correct. Run `lane whoami` to check authentication."
132
+ });
133
+ }
134
+ static expiredToken(requestId) {
135
+ return new _LaneAuthError("Authentication token has expired.", {
136
+ code: "expired_token",
137
+ statusCode: 401,
138
+ requestId,
139
+ suggestedAction: "Run `lane login` to re-authenticate."
140
+ });
141
+ }
142
+ static insufficientScope(requiredScope, requestId) {
143
+ return new _LaneAuthError(
144
+ `API key lacks required permission: ${requiredScope}`,
145
+ {
146
+ code: "insufficient_scope",
147
+ statusCode: 403,
148
+ requestId,
149
+ suggestedAction: "Generate a new API key with the required permissions in the dashboard."
150
+ }
151
+ );
152
+ }
153
+ };
154
+ var LanePaymentError = class _LanePaymentError extends LaneError {
155
+ constructor(message, options) {
156
+ super(message, options);
157
+ this.name = "LanePaymentError";
158
+ }
159
+ static declined(requestId) {
160
+ return new _LanePaymentError("Payment was declined by the card issuer.", {
161
+ code: "payment_declined",
162
+ statusCode: 402,
163
+ requestId,
164
+ suggestedAction: "The card was declined. Ask the developer to check their card or add a new one."
165
+ });
166
+ }
167
+ static insufficientFunds(currentBalance, required, requestId) {
168
+ const error = new _LanePaymentError("Insufficient wallet balance", {
169
+ code: "insufficient_funds",
170
+ statusCode: 402,
171
+ requestId,
172
+ suggestedAction: "Check wallet balance and deposit more funds"
173
+ });
174
+ if (currentBalance !== void 0 && required !== void 0) {
175
+ error.fix = `lane.wallet.deposit(${required - currentBalance})`;
176
+ error.currentState = { balance: currentBalance, required };
177
+ }
178
+ return error;
179
+ }
180
+ static merchantUnavailable(merchant, requestId) {
181
+ return new _LanePaymentError(
182
+ `Merchant "${merchant}" is temporarily unavailable.`,
183
+ {
184
+ code: "merchant_unavailable",
185
+ statusCode: 502,
186
+ requestId,
187
+ retryable: true,
188
+ suggestedAction: "The merchant endpoint is down. Try again in a few minutes or choose an alternative."
189
+ }
190
+ );
191
+ }
192
+ static serviceError(requestId) {
193
+ return new _LanePaymentError(
194
+ "Payment service encountered an internal error.",
195
+ {
196
+ code: "payment_service_error",
197
+ statusCode: 502,
198
+ requestId,
199
+ retryable: true,
200
+ suggestedAction: "Payment service temporarily unavailable. Try again in a few minutes."
201
+ }
202
+ );
203
+ }
204
+ };
205
+ var LaneBudgetError = class _LaneBudgetError extends LaneError {
206
+ /** The budget limit that was exceeded. */
207
+ limit;
208
+ /** The amount that was requested. */
209
+ requested;
210
+ constructor(message, options) {
211
+ super(message, options);
212
+ this.name = "LaneBudgetError";
213
+ this.limit = options.limit;
214
+ this.requested = options.requested;
215
+ }
216
+ static dailyExceeded(limit, requested, requestId) {
217
+ return new _LaneBudgetError(
218
+ `Daily spending limit of $${(limit / 100).toFixed(2)} exceeded. Requested: $${(requested / 100).toFixed(2)}.`,
219
+ {
220
+ code: "daily_limit_exceeded",
221
+ statusCode: 402,
222
+ requestId,
223
+ limit,
224
+ requested,
225
+ suggestedAction: "Ask the developer to increase the daily limit or approve this transaction."
226
+ }
227
+ );
228
+ }
229
+ static taskExceeded(limit, requested, requestId) {
230
+ return new _LaneBudgetError(
231
+ `Per-task spending limit of $${(limit / 100).toFixed(2)} exceeded. Requested: $${(requested / 100).toFixed(2)}.`,
232
+ {
233
+ code: "task_limit_exceeded",
234
+ statusCode: 402,
235
+ requestId,
236
+ limit,
237
+ requested,
238
+ suggestedAction: "Ask the developer to increase the per-task limit or approve this transaction."
239
+ }
240
+ );
241
+ }
242
+ static monthlyExceeded(limit, requested, requestId) {
243
+ return new _LaneBudgetError(
244
+ `Monthly spending limit of $${(limit / 100).toFixed(2)} exceeded. Requested: $${(requested / 100).toFixed(2)}.`,
245
+ {
246
+ code: "monthly_limit_exceeded",
247
+ statusCode: 402,
248
+ requestId,
249
+ limit,
250
+ requested,
251
+ suggestedAction: "Ask the developer to increase the monthly limit."
252
+ }
253
+ );
254
+ }
255
+ static merchantNotAllowed(merchant, requestId) {
256
+ return new _LaneBudgetError(
257
+ `Merchant "${merchant}" is not on the spending allowlist.`,
258
+ {
259
+ code: "merchant_not_allowed",
260
+ statusCode: 402,
261
+ requestId,
262
+ limit: 0,
263
+ requested: 0,
264
+ suggestedAction: "This merchant is not on your allowlist. Ask the developer to add it via the dashboard."
265
+ }
266
+ );
267
+ }
268
+ toJSON() {
269
+ return {
270
+ ...super.toJSON(),
271
+ limit: this.limit,
272
+ requested: this.requested
273
+ };
274
+ }
275
+ };
276
+ var LaneNotFoundError = class extends LaneError {
277
+ constructor(resource, id, requestId) {
278
+ super(`${resource} "${id}" not found.`, {
279
+ code: `${resource.toLowerCase()}_not_found`,
280
+ statusCode: 404,
281
+ requestId,
282
+ suggestedAction: `The ${resource.toLowerCase()} does not exist or you don't have access to it.`
283
+ });
284
+ this.name = "LaneNotFoundError";
285
+ }
286
+ };
287
+ var LaneRateLimitError = class extends LaneError {
288
+ /** Seconds until the rate limit resets. */
289
+ retryAfter;
290
+ constructor(retryAfter, requestId) {
291
+ super(`Rate limit exceeded. Retry after ${retryAfter} seconds.`, {
292
+ code: "rate_limit_exceeded",
293
+ statusCode: 429,
294
+ requestId,
295
+ retryable: true,
296
+ suggestedAction: `Wait ${retryAfter} seconds before retrying.`
297
+ });
298
+ this.name = "LaneRateLimitError";
299
+ this.retryAfter = retryAfter;
300
+ }
301
+ toJSON() {
302
+ return {
303
+ ...super.toJSON(),
304
+ retryAfter: this.retryAfter
305
+ };
306
+ }
307
+ };
308
+ var LaneValidationError = class extends LaneError {
309
+ fields;
310
+ constructor(fields, requestId) {
311
+ const fieldMessages = fields.map((f) => `${f.field}: ${f.message}`).join("; ");
312
+ super(`Validation error: ${fieldMessages}`, {
313
+ code: "validation_error",
314
+ statusCode: 400,
315
+ requestId,
316
+ suggestedAction: "Check the request parameters and try again."
317
+ });
318
+ this.name = "LaneValidationError";
319
+ this.fields = fields;
320
+ }
321
+ toJSON() {
322
+ return {
323
+ ...super.toJSON(),
324
+ fields: this.fields
325
+ };
326
+ }
327
+ };
328
+ function createErrorFromResponse(body) {
329
+ const { error, requestId } = body;
330
+ if (error.statusCode === 401 || error.statusCode === 403) {
331
+ return new LaneAuthError(error.message, {
332
+ code: error.code,
333
+ statusCode: error.statusCode,
334
+ requestId,
335
+ suggestedAction: error.suggestedAction
336
+ });
337
+ }
338
+ if (error.statusCode === 429) {
339
+ return new LaneRateLimitError(error.retryAfter ?? 60, requestId);
340
+ }
341
+ if (error.statusCode === 400 && error.fields) {
342
+ return new LaneValidationError(error.fields, requestId);
343
+ }
344
+ if (error.code.includes("limit_exceeded") || error.code === "merchant_not_allowed") {
345
+ return new LaneBudgetError(error.message, {
346
+ code: error.code,
347
+ statusCode: error.statusCode,
348
+ requestId,
349
+ limit: error.limit ?? 0,
350
+ requested: error.requested ?? 0,
351
+ suggestedAction: error.suggestedAction
352
+ });
353
+ }
354
+ if (error.code.startsWith("payment_") || error.code === "insufficient_funds" || error.code === "merchant_unavailable") {
355
+ return new LanePaymentError(error.message, {
356
+ code: error.code,
357
+ statusCode: error.statusCode,
358
+ requestId,
359
+ retryable: error.retryable,
360
+ suggestedAction: error.suggestedAction
361
+ });
362
+ }
363
+ if (error.statusCode === 404) {
364
+ return new LaneNotFoundError("Resource", error.code.replace("_not_found", ""), requestId);
365
+ }
366
+ return new LaneError(error.message, {
367
+ code: error.code,
368
+ statusCode: error.statusCode,
369
+ requestId,
370
+ retryable: error.retryable,
371
+ suggestedAction: error.suggestedAction
372
+ });
373
+ }
374
+
375
+ // src/client.ts
376
+ var CircuitBreaker = class {
377
+ constructor(failureThreshold = 3, resetTimeoutMs = 3e4) {
378
+ this.failureThreshold = failureThreshold;
379
+ this.resetTimeoutMs = resetTimeoutMs;
380
+ }
381
+ state = "closed";
382
+ failureCount = 0;
383
+ lastFailureTime = 0;
384
+ get isOpen() {
385
+ if (this.state === "open") {
386
+ if (Date.now() - this.lastFailureTime >= this.resetTimeoutMs) {
387
+ this.state = "half_open";
388
+ return false;
389
+ }
390
+ return true;
391
+ }
392
+ return false;
393
+ }
394
+ recordSuccess() {
395
+ this.failureCount = 0;
396
+ this.state = "closed";
397
+ }
398
+ recordFailure() {
399
+ this.failureCount++;
400
+ this.lastFailureTime = Date.now();
401
+ if (this.failureCount >= this.failureThreshold) {
402
+ this.state = "open";
403
+ }
404
+ }
405
+ };
406
+ var LaneClient = class extends events.EventEmitter {
407
+ config;
408
+ signer;
409
+ circuitBreaker;
410
+ constructor(config) {
411
+ super();
412
+ this.config = config;
413
+ this.signer = new HMACSignature(config.apiKey);
414
+ this.circuitBreaker = new CircuitBreaker(
415
+ config.circuitBreaker?.failureThreshold,
416
+ config.circuitBreaker?.resetTimeoutMs
417
+ );
418
+ }
419
+ /** Whether this client is operating in test mode. */
420
+ get testMode() {
421
+ return this.config.testMode;
422
+ }
423
+ /** The resolved base URL. */
424
+ get baseUrl() {
425
+ return this.config.baseUrl;
426
+ }
427
+ // -------------------------------------------------------------------------
428
+ // Public convenience methods
429
+ // -------------------------------------------------------------------------
430
+ async get(path, query) {
431
+ return this.request({ method: "GET", path, query });
432
+ }
433
+ async post(path, body, idempotencyKey) {
434
+ return this.request({ method: "POST", path, body, idempotencyKey });
435
+ }
436
+ async put(path, body) {
437
+ return this.request({ method: "PUT", path, body });
438
+ }
439
+ async delete(path) {
440
+ return this.request({ method: "DELETE", path });
441
+ }
442
+ // -------------------------------------------------------------------------
443
+ // Core request method with retries
444
+ // -------------------------------------------------------------------------
445
+ async request(options) {
446
+ const requestId = `req_${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
447
+ let lastError;
448
+ const maxAttempts = this.isRetryable(options.method) ? this.config.maxRetries + 1 : 1;
449
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
450
+ if (attempt > 1) {
451
+ this.emit("retry", {
452
+ attempt,
453
+ maxRetries: this.config.maxRetries,
454
+ requestId
455
+ });
456
+ await this.backoff(attempt);
457
+ }
458
+ try {
459
+ return await this.executeRequest(options, requestId);
460
+ } catch (err) {
461
+ lastError = err instanceof Error ? err : new Error(String(err));
462
+ if (err instanceof LaneError && !err.retryable) {
463
+ throw err;
464
+ }
465
+ if (err instanceof LaneRateLimitError) {
466
+ if (attempt < maxAttempts) {
467
+ await sleep(err.retryAfter * 1e3);
468
+ continue;
469
+ }
470
+ throw err;
471
+ }
472
+ }
473
+ }
474
+ throw lastError ?? new Error("Request failed with no error details");
475
+ }
476
+ // -------------------------------------------------------------------------
477
+ // Private
478
+ // -------------------------------------------------------------------------
479
+ async executeRequest(options, requestId) {
480
+ if (this.circuitBreaker.isOpen) {
481
+ throw new LaneError("Service temporarily unavailable (circuit breaker open).", {
482
+ code: "circuit_breaker_open",
483
+ statusCode: 503,
484
+ requestId,
485
+ retryable: true,
486
+ suggestedAction: "Payment service temporarily unavailable. Try again in 30 seconds."
487
+ });
488
+ }
489
+ const url = this.buildUrl(options.path, options.query);
490
+ const bodyStr = options.body ? JSON.stringify(options.body) : void 0;
491
+ const timestamp = Math.floor(Date.now() / 1e3);
492
+ const signatureHeaders = this.signer.headers({
493
+ method: options.method,
494
+ path: options.path,
495
+ timestamp,
496
+ body: bodyStr
497
+ });
498
+ const headers = {
499
+ "Authorization": `Bearer ${this.config.apiKey}`,
500
+ "Content-Type": "application/json",
501
+ "Accept": "application/json",
502
+ "X-Lane-Request-Id": requestId,
503
+ "User-Agent": "lane-sdk/0.1.0",
504
+ ...signatureHeaders
505
+ };
506
+ if (options.idempotencyKey) {
507
+ headers["X-Idempotency-Key"] = options.idempotencyKey;
508
+ }
509
+ this.emit("request", { method: options.method, path: options.path, requestId });
510
+ const startTime = Date.now();
511
+ const controller = new AbortController();
512
+ const timeoutId = setTimeout(
513
+ () => controller.abort(),
514
+ options.timeout ?? this.config.timeout
515
+ );
516
+ try {
517
+ const response = await fetch(url, {
518
+ method: options.method,
519
+ headers,
520
+ body: bodyStr,
521
+ signal: controller.signal
522
+ });
523
+ const durationMs = Date.now() - startTime;
524
+ this.emit("response", {
525
+ statusCode: response.status,
526
+ requestId,
527
+ durationMs
528
+ });
529
+ if (!response.ok) {
530
+ const errorBody = await this.parseErrorBody(response, requestId);
531
+ const error = createErrorFromResponse(errorBody);
532
+ this.emit("error", { error, requestId });
533
+ if (response.status >= 500) {
534
+ this.circuitBreaker.recordFailure();
535
+ }
536
+ throw error;
537
+ }
538
+ this.circuitBreaker.recordSuccess();
539
+ const data = await response.json();
540
+ return {
541
+ data,
542
+ requestId,
543
+ testMode: this.config.testMode,
544
+ rateLimitRemaining: parseIntHeader(response, "X-RateLimit-Remaining"),
545
+ rateLimitLimit: parseIntHeader(response, "X-RateLimit-Limit"),
546
+ rateLimitReset: parseIntHeader(response, "X-RateLimit-Reset")
547
+ };
548
+ } catch (err) {
549
+ if (err instanceof LaneError) throw err;
550
+ if (err instanceof DOMException && err.name === "AbortError") {
551
+ this.circuitBreaker.recordFailure();
552
+ throw new LaneError("Request timed out.", {
553
+ code: "request_timeout",
554
+ statusCode: 408,
555
+ requestId,
556
+ retryable: true,
557
+ suggestedAction: "The request took too long. Try again."
558
+ });
559
+ }
560
+ this.circuitBreaker.recordFailure();
561
+ throw new LaneError(
562
+ `Network error: ${err instanceof Error ? err.message : String(err)}`,
563
+ {
564
+ code: "network_error",
565
+ statusCode: 0,
566
+ requestId,
567
+ retryable: true,
568
+ suggestedAction: "Check your network connection and try again."
569
+ }
570
+ );
571
+ } finally {
572
+ clearTimeout(timeoutId);
573
+ }
574
+ }
575
+ buildUrl(path, query) {
576
+ const url = new URL(path, this.config.baseUrl);
577
+ if (query) {
578
+ for (const [key, value] of Object.entries(query)) {
579
+ if (value !== void 0) {
580
+ url.searchParams.set(key, String(value));
581
+ }
582
+ }
583
+ }
584
+ return url.toString();
585
+ }
586
+ async parseErrorBody(response, requestId) {
587
+ try {
588
+ const body = await response.json();
589
+ if (body.error && body.requestId) {
590
+ return body;
591
+ }
592
+ return {
593
+ error: {
594
+ code: "unknown_error",
595
+ message: JSON.stringify(body),
596
+ statusCode: response.status
597
+ },
598
+ requestId
599
+ };
600
+ } catch {
601
+ return {
602
+ error: {
603
+ code: "unknown_error",
604
+ message: `HTTP ${response.status}: ${response.statusText}`,
605
+ statusCode: response.status
606
+ },
607
+ requestId
608
+ };
609
+ }
610
+ }
611
+ isRetryable(method) {
612
+ return ["GET", "PUT", "DELETE"].includes(method) || method === "POST";
613
+ }
614
+ async backoff(attempt) {
615
+ const baseDelay = Math.min(1e3 * Math.pow(2, attempt - 2), 5e3);
616
+ const jitter = Math.random() * 500;
617
+ await sleep(baseDelay + jitter);
618
+ }
619
+ };
620
+ function sleep(ms) {
621
+ return new Promise((resolve) => setTimeout(resolve, ms));
622
+ }
623
+ function parseIntHeader(response, name) {
624
+ const value = response.headers.get(name);
625
+ if (value === null) return void 0;
626
+ const parsed = parseInt(value, 10);
627
+ return isNaN(parsed) ? void 0 : parsed;
628
+ }
629
+ var LANE_DIR = ".lane";
630
+ var CREDENTIALS_FILE = "credentials.json";
631
+ var SECURE_FILE_MODE = 384;
632
+ var SECURE_DIR_MODE = 448;
633
+ var FileTokenStore = class {
634
+ dirPath;
635
+ filePath;
636
+ constructor(basePath) {
637
+ this.dirPath = basePath ?? path.join(os.homedir(), LANE_DIR);
638
+ this.filePath = path.join(this.dirPath, CREDENTIALS_FILE);
639
+ }
640
+ /**
641
+ * Read credentials from disk. Returns null if file doesn't exist.
642
+ * Warns to stderr if file permissions are too permissive.
643
+ */
644
+ async read() {
645
+ try {
646
+ await this.validatePermissions();
647
+ const raw = await promises.readFile(this.filePath, "utf-8");
648
+ const parsed = JSON.parse(raw);
649
+ return this.validateCredentials(parsed);
650
+ } catch (err) {
651
+ if (isNodeError(err) && err.code === "ENOENT") {
652
+ return null;
653
+ }
654
+ throw err;
655
+ }
656
+ }
657
+ /**
658
+ * Write credentials to disk with secure permissions.
659
+ */
660
+ async write(credentials) {
661
+ await this.ensureDirectory();
662
+ const data = JSON.stringify(credentials, null, 2) + "\n";
663
+ await promises.writeFile(this.filePath, data, { mode: SECURE_FILE_MODE });
664
+ }
665
+ /**
666
+ * Remove the credentials file.
667
+ */
668
+ async clear() {
669
+ try {
670
+ const { unlink } = await import('fs/promises');
671
+ await unlink(this.filePath);
672
+ } catch (err) {
673
+ if (isNodeError(err) && err.code === "ENOENT") {
674
+ return;
675
+ }
676
+ throw err;
677
+ }
678
+ }
679
+ /** Return the path to the credentials file (for CLI display). */
680
+ get path() {
681
+ return this.filePath;
682
+ }
683
+ // -------------------------------------------------------------------------
684
+ // Private
685
+ // -------------------------------------------------------------------------
686
+ async ensureDirectory() {
687
+ await promises.mkdir(this.dirPath, { recursive: true, mode: SECURE_DIR_MODE });
688
+ }
689
+ /**
690
+ * Check file permissions and warn if too open.
691
+ * SOC 2 control: credentials should only be readable by owner.
692
+ */
693
+ async validatePermissions() {
694
+ try {
695
+ const stats = await promises.stat(this.filePath);
696
+ const mode = stats.mode & 511;
697
+ if (mode !== SECURE_FILE_MODE) {
698
+ process.stderr.write(
699
+ `[lane] Warning: ${this.filePath} has permissions ${mode.toString(8)}. Expected ${SECURE_FILE_MODE.toString(8)}. Fixing...
700
+ `
701
+ );
702
+ await promises.chmod(this.filePath, SECURE_FILE_MODE);
703
+ }
704
+ } catch {
705
+ }
706
+ }
707
+ validateCredentials(parsed) {
708
+ if (typeof parsed !== "object" || parsed === null || !("apiKey" in parsed) || typeof parsed["apiKey"] !== "string") {
709
+ return null;
710
+ }
711
+ return parsed;
712
+ }
713
+ };
714
+ function isNodeError(err) {
715
+ return err instanceof Error && "code" in err;
716
+ }
717
+
718
+ // src/config.ts
719
+ var DEFAULT_BASE_URL = "https://api.getonlane.com";
720
+ var DEFAULT_TIMEOUT = 3e4;
721
+ var DEFAULT_MAX_RETRIES = 2;
722
+ async function resolveConfig(options = {}, tokenStore) {
723
+ let apiKey = options.apiKey;
724
+ if (!apiKey) {
725
+ apiKey = process.env["LANE_API_KEY"];
726
+ }
727
+ if (!apiKey) {
728
+ const store = tokenStore ?? new FileTokenStore();
729
+ const creds = await store.read();
730
+ if (creds) {
731
+ apiKey = creds.apiKey;
732
+ }
733
+ }
734
+ if (!apiKey) {
735
+ throw new Error(
736
+ 'No API key found. Provide one via:\n 1. new Lane({ apiKey: "lane_sk_..." })\n 2. LANE_API_KEY environment variable\n 3. Run `lane login` to authenticate'
737
+ );
738
+ }
739
+ const testMode = options.testMode ?? (process.env["LANE_TEST_MODE"] === "true" || apiKey.startsWith("lane_sk_test_"));
740
+ const baseUrl = options.baseUrl ?? process.env["LANE_BASE_URL"] ?? DEFAULT_BASE_URL;
741
+ const timeout = options.timeout ?? (process.env["LANE_TIMEOUT"] ? parseInt(process.env["LANE_TIMEOUT"], 10) : DEFAULT_TIMEOUT);
742
+ const maxRetries = options.maxRetries ?? (process.env["LANE_MAX_RETRIES"] ? parseInt(process.env["LANE_MAX_RETRIES"], 10) : DEFAULT_MAX_RETRIES);
743
+ return Object.freeze({
744
+ apiKey,
745
+ baseUrl,
746
+ testMode,
747
+ timeout,
748
+ maxRetries,
749
+ circuitBreaker: options.circuitBreaker ? Object.freeze({
750
+ failureThreshold: options.circuitBreaker.failureThreshold ?? 3,
751
+ resetTimeoutMs: options.circuitBreaker.resetTimeoutMs ?? 3e4
752
+ }) : void 0
753
+ });
754
+ }
755
+
756
+ // src/resources/base.ts
757
+ var Resource = class {
758
+ client;
759
+ constructor(client) {
760
+ this.client = client;
761
+ }
762
+ // -----------------------------------------------------------------------
763
+ // Convenience wrappers that prepend basePath
764
+ // -----------------------------------------------------------------------
765
+ async _get(subpath = "", query) {
766
+ return this.client.get(`${this.basePath}${subpath}`, query);
767
+ }
768
+ async _post(subpath = "", body, idempotencyKey) {
769
+ return this.client.post(`${this.basePath}${subpath}`, body, idempotencyKey);
770
+ }
771
+ async _put(subpath = "", body) {
772
+ return this.client.put(`${this.basePath}${subpath}`, body);
773
+ }
774
+ async _delete(subpath = "") {
775
+ return this.client.delete(`${this.basePath}${subpath}`);
776
+ }
777
+ /**
778
+ * Helper for paginated list endpoints.
779
+ */
780
+ async _list(subpath = "", params) {
781
+ const query = {};
782
+ if (params) {
783
+ for (const [key, value] of Object.entries(params)) {
784
+ if (value !== void 0) {
785
+ query[key] = typeof value === "object" ? JSON.stringify(value) : value;
786
+ }
787
+ }
788
+ }
789
+ const response = await this.client.get(
790
+ `${this.basePath}${subpath}`,
791
+ query
792
+ );
793
+ return response.data;
794
+ }
795
+ };
796
+
797
+ // src/resources/auth.ts
798
+ var Auth = class extends Resource {
799
+ get basePath() {
800
+ return "/api/sdk/auth";
801
+ }
802
+ /** Get the authenticated developer's profile. */
803
+ async whoami() {
804
+ const res = await this._get("/whoami");
805
+ return res.data;
806
+ }
807
+ /**
808
+ * Start a login session. Returns a URL the developer opens in their browser.
809
+ */
810
+ async login() {
811
+ const res = await this._post("/login");
812
+ return res.data;
813
+ }
814
+ /**
815
+ * Exchange a one-time code (from browser callback) for an API key.
816
+ */
817
+ async exchangeCode(code, sessionId) {
818
+ const res = await this._post("/token", {
819
+ code,
820
+ sessionId
821
+ });
822
+ return res.data;
823
+ }
824
+ /**
825
+ * Rotate the API key. Old key remains valid for 15 minutes.
826
+ */
827
+ async rotateKey() {
828
+ const res = await this._post("/rotate");
829
+ return res.data;
830
+ }
831
+ };
832
+
833
+ // src/resources/wallets.ts
834
+ var Wallets = class extends Resource {
835
+ get basePath() {
836
+ return "/api/sdk/wallets";
837
+ }
838
+ /** Create a new wallet, optionally scoped to an end user. */
839
+ async create(params = {}) {
840
+ const res = await this._post("", {
841
+ userId: params.userId
842
+ }, params.idempotencyKey);
843
+ return res.data;
844
+ }
845
+ /** Get a wallet by ID. */
846
+ async get(walletId) {
847
+ const res = await this._get(`/${walletId}`);
848
+ return res.data;
849
+ }
850
+ /** List all wallets for the authenticated developer. */
851
+ async list(params) {
852
+ return this._list("", params);
853
+ }
854
+ /** List cards in a wallet. Returns last4/brand only — never full PAN. */
855
+ async listCards(walletId) {
856
+ const res = await this._get(`/${walletId}/cards`);
857
+ return res.data;
858
+ }
859
+ /**
860
+ * Get a secure link for the end user to add a card via VGS Collect.
861
+ * PAN goes directly to VGS vault — Lane never sees it.
862
+ */
863
+ async getAddCardLink(walletId) {
864
+ const res = await this._get(`/${walletId}/add-card-link`);
865
+ return res.data;
866
+ }
867
+ /** Revoke a wallet. All linked tokens are immediately invalidated. */
868
+ async revoke(walletId) {
869
+ await this._delete(`/${walletId}`);
870
+ }
871
+ /** Remove a specific card from a wallet. */
872
+ async removeCard(walletId, cardId) {
873
+ await this._delete(`/${walletId}/cards/${cardId}`);
874
+ }
875
+ /** Deposit funds into a wallet. */
876
+ async deposit(walletId, params) {
877
+ const res = await this._post(
878
+ `/${walletId}/deposit`,
879
+ { amount: params.amount },
880
+ params.idempotencyKey
881
+ );
882
+ return res.data;
883
+ }
884
+ /** Get wallet balance and metadata. */
885
+ async balance(walletId) {
886
+ const res = await this._get(`/${walletId ?? "default"}/balance`);
887
+ return res.data;
888
+ }
889
+ /** Configure auto-reload for a wallet. */
890
+ async setAutoReload(walletId, config) {
891
+ const res = await this._put(
892
+ `/${walletId}/auto-reload`,
893
+ config
894
+ );
895
+ return res.data;
896
+ }
897
+ /** Get card attributes for a wallet. */
898
+ async getAttributes(walletId) {
899
+ const res = await this._get(`/${walletId}/card-attributes`);
900
+ return res.data;
901
+ }
902
+ /** Set the checkout profile for a wallet (billing, shipping, email). */
903
+ async setProfile(walletId, profile) {
904
+ const res = await this._post(`/${walletId}/profile`, profile);
905
+ return res.data;
906
+ }
907
+ /** Get the checkout profile. Addresses and email are visible; no sensitive data. */
908
+ async getProfile(walletId) {
909
+ const res = await this._get(`/${walletId}/profile`);
910
+ return res.data;
911
+ }
912
+ /** Update the checkout profile (partial). */
913
+ async updateProfile(walletId, updates) {
914
+ const res = await this._put(`/${walletId}/profile`, updates);
915
+ return res.data;
916
+ }
917
+ /** Save a merchant account (for sites without guest checkout). */
918
+ async saveMerchantAccount(walletId, params) {
919
+ const res = await this._post(`/${walletId}/merchant-accounts`, params);
920
+ return res.data;
921
+ }
922
+ /** List saved merchant accounts (domain + email only). */
923
+ async listMerchantAccounts(walletId) {
924
+ const res = await this._get(`/${walletId}/merchant-accounts`);
925
+ return res.data;
926
+ }
927
+ /** Remove a saved merchant account. */
928
+ async removeMerchantAccount(walletId, accountId) {
929
+ await this._delete(`/${walletId}/merchant-accounts/${accountId}`);
930
+ }
931
+ };
932
+ var Pay = class extends Resource {
933
+ get basePath() {
934
+ return "/api/sdk/pay";
935
+ }
936
+ /**
937
+ * Create a scoped agentic token — amount-limited, merchant-locked,
938
+ * time-bounded. The token can be used to execute a single payment.
939
+ */
940
+ async createToken(params) {
941
+ const res = await this._post("/tokens", {
942
+ walletId: params.walletId,
943
+ amount: params.amount,
944
+ currency: params.currency ?? "USD",
945
+ merchant: params.merchant ?? "*",
946
+ expiresIn: params.expiresIn ?? "1h",
947
+ permissions: params.permissions ?? []
948
+ }, params.idempotencyKey);
949
+ return res.data;
950
+ }
951
+ /**
952
+ * Execute a payment. Routed via the best available path:
953
+ * 1. Billing API (for services like OpenAI, Replicate, Vercel)
954
+ * 2. ACP-enabled merchant checkout
955
+ * 3. VIC agentic token (when available)
956
+ *
957
+ * Idempotency key is strongly recommended to prevent double charges.
958
+ */
959
+ async execute(params) {
960
+ const idempotencyKey = params.idempotencyKey ?? this.generateIdempotencyKey(params);
961
+ const res = await this._post("/execute", {
962
+ tokenId: params.tokenId,
963
+ recipient: params.recipient,
964
+ amount: params.amount,
965
+ currency: params.currency ?? "USD",
966
+ description: params.description
967
+ }, idempotencyKey);
968
+ return res.data;
969
+ }
970
+ generateIdempotencyKey(params) {
971
+ const window = Math.floor(Date.now() / (5 * 60 * 1e3));
972
+ const input = `${params.amount}:${params.recipient}:${params.currency ?? "USD"}:${window}`;
973
+ return `auto_${crypto.createHash("sha256").update(input).digest("hex").slice(0, 24)}`;
974
+ }
975
+ /** Get a transaction by ID. */
976
+ async getTransaction(transactionId) {
977
+ const res = await this._get(`/transactions/${transactionId}`);
978
+ return res.data;
979
+ }
980
+ /** List transactions with optional filters. */
981
+ async listTransactions(params) {
982
+ return this._list("/transactions", params);
983
+ }
984
+ /**
985
+ * Initiate a refund. Full or partial.
986
+ *
987
+ * Refund routing mirrors payment routing:
988
+ * - Billing API merchants: calls their refund API
989
+ * - ACP merchants: initiates refund through PSP
990
+ * - VIC transactions: standard Visa dispute flow
991
+ */
992
+ async refund(params) {
993
+ const res = await this._post("/refunds", {
994
+ transactionId: params.transactionId,
995
+ amount: params.amount,
996
+ reason: params.reason
997
+ }, params.idempotencyKey);
998
+ return res.data;
999
+ }
1000
+ /** Get a refund by ID. */
1001
+ async getRefund(refundId) {
1002
+ const res = await this._get(`/refunds/${refundId}`);
1003
+ return res.data;
1004
+ }
1005
+ };
1006
+
1007
+ // src/resources/products.ts
1008
+ var Products = class extends Resource {
1009
+ get basePath() {
1010
+ return "/api/sdk/products";
1011
+ }
1012
+ /** Search the Lane product catalog. */
1013
+ async search(params) {
1014
+ return this._list("/search", { ...params });
1015
+ }
1016
+ /** Get a product by ID. */
1017
+ async get(productId) {
1018
+ const res = await this._get(`/${productId}`);
1019
+ return res.data;
1020
+ }
1021
+ /** List available merchants. */
1022
+ async listMerchants(params) {
1023
+ return this._list("/merchants", params);
1024
+ }
1025
+ /** Get a merchant by ID or slug. */
1026
+ async getMerchant(merchantIdOrSlug) {
1027
+ const res = await this.client.get(`/api/sdk/merchants/${merchantIdOrSlug}`);
1028
+ return res.data;
1029
+ }
1030
+ };
1031
+
1032
+ // src/resources/checkout.ts
1033
+ var Checkout = class extends Resource {
1034
+ get basePath() {
1035
+ return "/api/sdk/checkout";
1036
+ }
1037
+ /** Create a new checkout session for a product. */
1038
+ async create(params) {
1039
+ const body = {
1040
+ productId: params.productId,
1041
+ walletId: params.walletId,
1042
+ quantity: params.quantity ?? 1
1043
+ };
1044
+ if ("plan" in params && params.plan !== void 0) {
1045
+ body.plan = params.plan;
1046
+ }
1047
+ if ("parameters" in params && params.parameters !== void 0) {
1048
+ body.parameters = params.parameters;
1049
+ }
1050
+ const res = await this._post("", body, params.idempotencyKey);
1051
+ return res.data;
1052
+ }
1053
+ /** Complete a checkout session — processes payment and returns the order. */
1054
+ async complete(sessionId) {
1055
+ const res = await this._post(`/${sessionId}/complete`);
1056
+ return res.data;
1057
+ }
1058
+ /** Get the status of a checkout session. */
1059
+ async get(sessionId) {
1060
+ const res = await this._get(`/${sessionId}`);
1061
+ return res.data;
1062
+ }
1063
+ /** Cancel a pending checkout session. */
1064
+ async cancel(sessionId) {
1065
+ const res = await this._post(`/${sessionId}/cancel`);
1066
+ return res.data;
1067
+ }
1068
+ };
1069
+
1070
+ // src/resources/sell.ts
1071
+ var Sell = class extends Resource {
1072
+ get basePath() {
1073
+ return "/api/sdk/sell";
1074
+ }
1075
+ /**
1076
+ * List a product for sale on the Lane marketplace.
1077
+ *
1078
+ * Lane will:
1079
+ * 1. List the product in the Product API (discoverable by any agent)
1080
+ * 2. Create a proxy endpoint (handles auth + metering)
1081
+ * 3. Generate API keys for buyers automatically
1082
+ * 4. Meter usage and bill buyer wallets
1083
+ * 5. Pay out to seller (minus Lane fee)
1084
+ */
1085
+ async create(params) {
1086
+ const res = await this._post("/products", {
1087
+ name: params.name,
1088
+ type: params.type,
1089
+ endpoint: params.endpoint,
1090
+ mcpEndpoint: params.mcpEndpoint,
1091
+ pricing: params.pricing,
1092
+ description: params.description,
1093
+ auth: params.auth
1094
+ }, params.idempotencyKey);
1095
+ return res.data;
1096
+ }
1097
+ /** Update an existing product listing. */
1098
+ async update(productId, params) {
1099
+ const res = await this._put(`/products/${productId}`, params);
1100
+ return res.data;
1101
+ }
1102
+ /** Deactivate a product listing (soft delete). */
1103
+ async deactivate(productId) {
1104
+ await this._delete(`/products/${productId}`);
1105
+ }
1106
+ /** Get a product you've listed. */
1107
+ async get(productId) {
1108
+ const res = await this._get(`/products/${productId}`);
1109
+ return res.data;
1110
+ }
1111
+ /** List all products you've listed for sale. */
1112
+ async list(params) {
1113
+ return this._list("/products", params);
1114
+ }
1115
+ /** Get analytics for a product you've listed. */
1116
+ async analytics(params) {
1117
+ const res = await this._get(
1118
+ `/products/${params.productId}/analytics`,
1119
+ { period: params.period }
1120
+ );
1121
+ return res.data;
1122
+ }
1123
+ /** List a software product using an SCP manifest. */
1124
+ async createSoftwareProduct(manifest) {
1125
+ const res = await this._post("/software", manifest);
1126
+ return res.data;
1127
+ }
1128
+ };
1129
+
1130
+ // src/resources/admin.ts
1131
+ var Admin = class extends Resource {
1132
+ get basePath() {
1133
+ return "/api/sdk/admin";
1134
+ }
1135
+ // -------------------------------------------------------------------------
1136
+ // Spending
1137
+ // -------------------------------------------------------------------------
1138
+ /** Get spending summary with optional grouping by team/developer/agent. */
1139
+ async spending(params) {
1140
+ const res = await this._get("/spending", params);
1141
+ return res.data;
1142
+ }
1143
+ // -------------------------------------------------------------------------
1144
+ // Budgets
1145
+ // -------------------------------------------------------------------------
1146
+ /** Get current budget configuration. */
1147
+ async getBudget(scope) {
1148
+ const res = await this._get("/budgets", scope);
1149
+ return res.data;
1150
+ }
1151
+ /** Update budget limits. */
1152
+ async setBudget(config) {
1153
+ const res = await this._put("/budgets", config);
1154
+ return res.data;
1155
+ }
1156
+ // -------------------------------------------------------------------------
1157
+ // Team Members
1158
+ // -------------------------------------------------------------------------
1159
+ /** List team members. */
1160
+ async listMembers(params) {
1161
+ return this._list("/members", params);
1162
+ }
1163
+ /** Invite a team member. */
1164
+ async inviteMember(params) {
1165
+ const res = await this._post("/members", params);
1166
+ return res.data;
1167
+ }
1168
+ /** Update a team member's role. */
1169
+ async updateMember(memberId, params) {
1170
+ const res = await this._put(`/members/${memberId}`, params);
1171
+ return res.data;
1172
+ }
1173
+ /** Remove a team member. */
1174
+ async removeMember(memberId) {
1175
+ await this._delete(`/members/${memberId}`);
1176
+ }
1177
+ // -------------------------------------------------------------------------
1178
+ // Account Management (GDPR)
1179
+ // -------------------------------------------------------------------------
1180
+ /**
1181
+ * Export all developer data as JSON (GDPR data portability).
1182
+ */
1183
+ async exportData() {
1184
+ const res = await this._get("/export");
1185
+ return res.data;
1186
+ }
1187
+ /**
1188
+ * Delete the developer account and purge all PII (GDPR right to erasure).
1189
+ * Transaction records are anonymized (amount + merchant retained for regulatory).
1190
+ */
1191
+ async deleteAccount() {
1192
+ const res = await this._delete("/account");
1193
+ return res.data;
1194
+ }
1195
+ };
1196
+
1197
+ // src/resources/webhooks.ts
1198
+ var Webhooks = class extends Resource {
1199
+ get basePath() {
1200
+ return "/api/sdk/webhooks";
1201
+ }
1202
+ /** Register a new webhook endpoint. */
1203
+ async create(params) {
1204
+ const res = await this._post("", params);
1205
+ return res.data;
1206
+ }
1207
+ /** List registered webhooks. */
1208
+ async list() {
1209
+ return this._list();
1210
+ }
1211
+ /** Get a webhook by ID. */
1212
+ async get(webhookId) {
1213
+ const res = await this._get(`/${webhookId}`);
1214
+ return res.data;
1215
+ }
1216
+ /** Update a webhook configuration. */
1217
+ async update(webhookId, params) {
1218
+ const res = await this._put(`/${webhookId}`, params);
1219
+ return res.data;
1220
+ }
1221
+ /** Delete a webhook endpoint. */
1222
+ async delete(webhookId) {
1223
+ await this._delete(`/${webhookId}`);
1224
+ }
1225
+ /**
1226
+ * Verify a webhook payload's signature.
1227
+ *
1228
+ * Use this in your webhook handler to confirm the payload was sent by Lane.
1229
+ *
1230
+ * @param payload - Raw request body string
1231
+ * @param signature - Value of X-Lane-Signature header
1232
+ * @param secret - Your webhook signing secret
1233
+ * @param timestamp - Value of X-Lane-Timestamp header (unix seconds)
1234
+ */
1235
+ verify(payload, signature, secret, timestamp) {
1236
+ return verifyWebhookSignature(payload, signature, secret, timestamp);
1237
+ }
1238
+ /**
1239
+ * Parse and verify a webhook payload in one step.
1240
+ * Throws if signature is invalid.
1241
+ */
1242
+ constructEvent(payload, signature, secret, timestamp) {
1243
+ if (!this.verify(payload, signature, secret, timestamp)) {
1244
+ throw new Error("Invalid webhook signature");
1245
+ }
1246
+ return JSON.parse(payload);
1247
+ }
1248
+ };
1249
+
1250
+ // src/resources/vic.ts
1251
+ var VIC = class extends Resource {
1252
+ get basePath() {
1253
+ return "/api/sdk/vic";
1254
+ }
1255
+ /**
1256
+ * Issue a VIC agentic token. This provisions a Visa network token (DPAN)
1257
+ * scoped to the agent's constraints: amount limits, merchant restrictions,
1258
+ * and time bounds.
1259
+ *
1260
+ * The token works at any Visa-accepting merchant — not just Lane catalog.
1261
+ */
1262
+ async issue(params) {
1263
+ const res = await this._post("/tokens", {
1264
+ walletId: params.walletId,
1265
+ cardId: params.cardId,
1266
+ maxTransactionAmount: params.maxTransactionAmount,
1267
+ allowedMCCs: params.allowedMCCs,
1268
+ expiresIn: params.expiresIn ?? "24h"
1269
+ }, params.idempotencyKey);
1270
+ return res.data;
1271
+ }
1272
+ /**
1273
+ * Get a transaction-specific cryptogram for a VIC token.
1274
+ * Called just before payment execution — the cryptogram is single-use.
1275
+ */
1276
+ async getCryptogram(tokenId, params) {
1277
+ const res = await this._post(
1278
+ `/tokens/${tokenId}/cryptogram`,
1279
+ params
1280
+ );
1281
+ return res.data;
1282
+ }
1283
+ /** Get a VIC token by ID. */
1284
+ async get(tokenId) {
1285
+ const res = await this._get(`/tokens/${tokenId}`);
1286
+ return res.data;
1287
+ }
1288
+ /** List VIC tokens for a wallet. */
1289
+ async list(params) {
1290
+ return this._list("/tokens", params);
1291
+ }
1292
+ /** Revoke a VIC token immediately. */
1293
+ async revoke(tokenId) {
1294
+ const res = await this._post(`/tokens/${tokenId}/revoke`);
1295
+ return res.data;
1296
+ }
1297
+ /** Suspend a VIC token (can be reactivated). */
1298
+ async suspend(tokenId) {
1299
+ const res = await this._post(`/tokens/${tokenId}/suspend`);
1300
+ return res.data;
1301
+ }
1302
+ /** Reactivate a suspended VIC token. */
1303
+ async reactivate(tokenId) {
1304
+ const res = await this._post(`/tokens/${tokenId}/reactivate`);
1305
+ return res.data;
1306
+ }
1307
+ };
1308
+
1309
+ // src/resources/metering.ts
1310
+ var Metering = class extends Resource {
1311
+ get basePath() {
1312
+ return "/api/sdk/metering";
1313
+ }
1314
+ /**
1315
+ * Record a usage event (API call, token consumption, etc.).
1316
+ * Called by the seller's service when a buyer uses it.
1317
+ */
1318
+ async record(params) {
1319
+ const res = await this._post("/usage", {
1320
+ productId: params.productId,
1321
+ buyerId: params.buyerId,
1322
+ quantity: params.quantity,
1323
+ unit: params.unit ?? "calls"
1324
+ }, params.idempotencyKey);
1325
+ return res.data;
1326
+ }
1327
+ /**
1328
+ * Record a batch of usage events efficiently.
1329
+ */
1330
+ async recordBatch(records) {
1331
+ const res = await this._post("/usage/batch", {
1332
+ records
1333
+ });
1334
+ return res.data;
1335
+ }
1336
+ /** Query usage for a product. */
1337
+ async query(params) {
1338
+ return this._list("/usage", params);
1339
+ }
1340
+ /** Get an aggregated usage report. */
1341
+ async report(params) {
1342
+ const res = await this._get("/reports", params);
1343
+ return res.data;
1344
+ }
1345
+ /** Get or update metering config for a product. */
1346
+ async getConfig(productId) {
1347
+ const res = await this._get(`/config/${productId}`);
1348
+ return res.data;
1349
+ }
1350
+ async setConfig(productId, config) {
1351
+ const res = await this._put(`/config/${productId}`, config);
1352
+ return res.data;
1353
+ }
1354
+ };
1355
+
1356
+ // src/resources/payouts.ts
1357
+ var Payouts = class extends Resource {
1358
+ get basePath() {
1359
+ return "/api/sdk/payouts";
1360
+ }
1361
+ /** Get payout configuration. */
1362
+ async getConfig() {
1363
+ const res = await this._get("/config");
1364
+ return res.data;
1365
+ }
1366
+ /** Set up or update payout configuration. */
1367
+ async setConfig(config) {
1368
+ const res = await this._put("/config", config);
1369
+ return res.data;
1370
+ }
1371
+ /** List payouts. */
1372
+ async list(params) {
1373
+ return this._list("", params);
1374
+ }
1375
+ /** Get a specific payout. */
1376
+ async get(payoutId) {
1377
+ const res = await this._get(`/${payoutId}`);
1378
+ return res.data;
1379
+ }
1380
+ };
1381
+
1382
+ // src/resources/teams.ts
1383
+ var Teams = class extends Resource {
1384
+ get basePath() {
1385
+ return "/api/sdk/teams";
1386
+ }
1387
+ /** Create a new team. */
1388
+ async create(params) {
1389
+ const res = await this._post("", params);
1390
+ return res.data;
1391
+ }
1392
+ /** List teams. */
1393
+ async list(params) {
1394
+ return this._list("", params);
1395
+ }
1396
+ /** Get a team by ID. */
1397
+ async get(teamId) {
1398
+ const res = await this._get(`/${teamId}`);
1399
+ return res.data;
1400
+ }
1401
+ /** Update a team. */
1402
+ async update(teamId, params) {
1403
+ const res = await this._put(`/${teamId}`, params);
1404
+ return res.data;
1405
+ }
1406
+ /** Delete a team. */
1407
+ async delete(teamId) {
1408
+ await this._delete(`/${teamId}`);
1409
+ }
1410
+ /** Add a member to a team. */
1411
+ async addMember(teamId, params) {
1412
+ const res = await this._post(`/${teamId}/members`, params);
1413
+ return res.data;
1414
+ }
1415
+ /** List team members. */
1416
+ async listMembers(teamId, params) {
1417
+ return this._list(`/${teamId}/members`, params);
1418
+ }
1419
+ /** Remove a member from a team. */
1420
+ async removeMember(teamId, memberId) {
1421
+ await this._delete(`/${teamId}/members/${memberId}`);
1422
+ }
1423
+ /** Get the hierarchical budget for a team (org > team > developer > agent). */
1424
+ async getHierarchicalBudget(teamId) {
1425
+ const res = await this._get(`/${teamId}/budget/hierarchy`);
1426
+ return res.data;
1427
+ }
1428
+ /** Set the team-level budget. */
1429
+ async setBudget(teamId, budget) {
1430
+ const res = await this._put(`/${teamId}/budget`, budget);
1431
+ return res.data;
1432
+ }
1433
+ };
1434
+
1435
+ // src/resources/agents.ts
1436
+ var Agents = class extends Resource {
1437
+ get basePath() {
1438
+ return "/api/sdk/agents";
1439
+ }
1440
+ /** Register a new agent in the fleet. */
1441
+ async register(params) {
1442
+ const res = await this._post("", params);
1443
+ return res.data;
1444
+ }
1445
+ /** Get an agent by ID. */
1446
+ async get(agentId) {
1447
+ const res = await this._get(`/${agentId}`);
1448
+ return res.data;
1449
+ }
1450
+ /** List agents in the fleet. */
1451
+ async list(params) {
1452
+ return this._list("", params);
1453
+ }
1454
+ /** Update an agent's policy. */
1455
+ async setPolicy(agentId, policy) {
1456
+ const res = await this._put(`/${agentId}/policy`, policy);
1457
+ return res.data;
1458
+ }
1459
+ /** Suspend an agent (temporary, can be reactivated). */
1460
+ async suspend(agentId) {
1461
+ const res = await this._post(`/${agentId}/suspend`);
1462
+ return res.data;
1463
+ }
1464
+ /** Reactivate a suspended agent. */
1465
+ async reactivate(agentId) {
1466
+ const res = await this._post(`/${agentId}/reactivate`);
1467
+ return res.data;
1468
+ }
1469
+ /** Permanently revoke an agent. */
1470
+ async revoke(agentId) {
1471
+ const res = await this._post(`/${agentId}/revoke`);
1472
+ return res.data;
1473
+ }
1474
+ /** Assign a wallet to an agent (per-agent wallet). */
1475
+ async assignWallet(agentId, walletId) {
1476
+ const res = await this._put(`/${agentId}/wallet`, { walletId });
1477
+ return res.data;
1478
+ }
1479
+ };
1480
+
1481
+ // src/resources/audit.ts
1482
+ var Audit = class extends Resource {
1483
+ get basePath() {
1484
+ return "/api/sdk/audit";
1485
+ }
1486
+ /** Query audit log entries. */
1487
+ async query(params) {
1488
+ return this._list("/logs", params ? { ...params } : void 0);
1489
+ }
1490
+ /** Get a specific audit entry by ID. */
1491
+ async get(entryId) {
1492
+ const res = await this._get(`/logs/${entryId}`);
1493
+ return res.data;
1494
+ }
1495
+ /**
1496
+ * Export audit logs for a date range (SOC 2 compliance).
1497
+ * Returns a download URL for the audit log archive.
1498
+ */
1499
+ async export(params) {
1500
+ const res = await this._post("/export", params);
1501
+ return res.data;
1502
+ }
1503
+ /** Get audit summary/statistics for a period. */
1504
+ async summary(params) {
1505
+ const res = await this._get("/summary", params);
1506
+ return res.data;
1507
+ }
1508
+ };
1509
+
1510
+ // src/resources/identity.ts
1511
+ var Identity = class extends Resource {
1512
+ get basePath() {
1513
+ return "/api/sdk/identity";
1514
+ }
1515
+ /**
1516
+ * Register an agent for identity verification.
1517
+ * This starts the KYC chain: Human (developer) vouches for Agent.
1518
+ */
1519
+ async register(params) {
1520
+ const res = await this._post("", params);
1521
+ return res.data;
1522
+ }
1523
+ /**
1524
+ * Complete identity verification using a developer-provided code.
1525
+ * This establishes the Human -> Lane -> Agent chain.
1526
+ */
1527
+ async verify(params) {
1528
+ const res = await this._post(
1529
+ `/${params.identityId}/verify`,
1530
+ { verificationCode: params.verificationCode }
1531
+ );
1532
+ return res.data;
1533
+ }
1534
+ /** Get an agent identity by ID. */
1535
+ async get(identityId) {
1536
+ const res = await this._get(`/${identityId}`);
1537
+ return res.data;
1538
+ }
1539
+ /** List agent identities. */
1540
+ async list(params) {
1541
+ return this._list("", params);
1542
+ }
1543
+ /**
1544
+ * Get the current attestation for a verified identity.
1545
+ * The attestation is a cryptographic proof that Lane has verified the
1546
+ * Human -> Agent chain.
1547
+ */
1548
+ async getAttestation(identityId) {
1549
+ const res = await this._get(`/${identityId}/attestation`);
1550
+ return res.data;
1551
+ }
1552
+ /**
1553
+ * Refresh the attestation (generates a new signed document).
1554
+ * Useful when the previous attestation is nearing expiry.
1555
+ */
1556
+ async refreshAttestation(identityId) {
1557
+ const res = await this._post(`/${identityId}/attestation/refresh`);
1558
+ return res.data;
1559
+ }
1560
+ /** Suspend an agent identity. */
1561
+ async suspend(identityId) {
1562
+ const res = await this._post(`/${identityId}/suspend`);
1563
+ return res.data;
1564
+ }
1565
+ /** Revoke an agent identity permanently. */
1566
+ async revoke(identityId) {
1567
+ const res = await this._post(`/${identityId}/revoke`);
1568
+ return res.data;
1569
+ }
1570
+ };
1571
+
1572
+ // src/resources/subscriptions.ts
1573
+ var Subscriptions = class extends Resource {
1574
+ get basePath() {
1575
+ return "/api/sdk/subscriptions";
1576
+ }
1577
+ /** Create a new subscription. */
1578
+ async create(params) {
1579
+ const res = await this._post("", params);
1580
+ return res.data;
1581
+ }
1582
+ /** List subscriptions, optionally filtered by status. */
1583
+ async list(params) {
1584
+ return this._list("", params);
1585
+ }
1586
+ /** Get a subscription by ID. */
1587
+ async get(subscriptionId) {
1588
+ const res = await this._get(`/${subscriptionId}`);
1589
+ return res.data;
1590
+ }
1591
+ /** Cancel a subscription. */
1592
+ async cancel(subscriptionId) {
1593
+ const res = await this._post(`/${subscriptionId}/cancel`, {});
1594
+ return res.data;
1595
+ }
1596
+ /** Upgrade a subscription to a higher plan. */
1597
+ async upgrade(subscriptionId, params) {
1598
+ const res = await this._post(`/${subscriptionId}/upgrade`, params);
1599
+ return res.data;
1600
+ }
1601
+ /** Downgrade a subscription to a lower plan. */
1602
+ async downgrade(subscriptionId, params) {
1603
+ const res = await this._post(`/${subscriptionId}/downgrade`, params);
1604
+ return res.data;
1605
+ }
1606
+ /** Pause a subscription. */
1607
+ async pause(subscriptionId) {
1608
+ const res = await this._post(`/${subscriptionId}/pause`, {});
1609
+ return res.data;
1610
+ }
1611
+ /** Resume a paused subscription. */
1612
+ async resume(subscriptionId) {
1613
+ const res = await this._post(`/${subscriptionId}/resume`, {});
1614
+ return res.data;
1615
+ }
1616
+ };
1617
+
1618
+ // src/resources/merchants.ts
1619
+ var Merchants = class extends Resource {
1620
+ get basePath() {
1621
+ return "/api/sdk/merchants";
1622
+ }
1623
+ /** List/search the merchant directory. */
1624
+ async list(params) {
1625
+ return this._list("", params);
1626
+ }
1627
+ /** Get a merchant by ID, slug, or domain. */
1628
+ async get(idOrSlug) {
1629
+ const res = await this._get(`/${idOrSlug}`);
1630
+ return res.data;
1631
+ }
1632
+ /** List merchants that support a specific protocol. */
1633
+ async listByProtocol(protocol, params) {
1634
+ return this._list("", { ...params, protocol });
1635
+ }
1636
+ /** List merchants in a specific vertical. */
1637
+ async listByVertical(vertical, params) {
1638
+ return this._list("", { ...params, vertical });
1639
+ }
1640
+ /** List only Lane-onboarded merchants. */
1641
+ async listOnboarded(params) {
1642
+ return this._list("", { ...params, tier: "lane_onboarded" });
1643
+ }
1644
+ /** List all verticals with subcategories and counts. */
1645
+ async verticals() {
1646
+ const res = await this._get("/verticals");
1647
+ return res.data;
1648
+ }
1649
+ /** Discover a protocol-compatible merchant by domain. */
1650
+ async discover(domain) {
1651
+ const res = await this._post("/discover", { domain });
1652
+ return res.data;
1653
+ }
1654
+ };
1655
+
1656
+ // src/lane.ts
1657
+ var Lane = class _Lane {
1658
+ client;
1659
+ _config;
1660
+ // Lazily initialized resources
1661
+ _auth;
1662
+ _wallets;
1663
+ _pay;
1664
+ _products;
1665
+ _checkout;
1666
+ _sell;
1667
+ _admin;
1668
+ _webhooks;
1669
+ _vic;
1670
+ _metering;
1671
+ _payouts;
1672
+ _teams;
1673
+ _agents;
1674
+ _audit;
1675
+ _identity;
1676
+ _subscriptions;
1677
+ _merchants;
1678
+ /**
1679
+ * Private constructor — use `Lane.create()` for async config resolution,
1680
+ * or `new Lane(resolvedConfig)` if you've already resolved config.
1681
+ */
1682
+ constructor(config) {
1683
+ this._config = config;
1684
+ this.client = new LaneClient(config);
1685
+ }
1686
+ /**
1687
+ * Create a new Lane SDK instance with async config resolution.
1688
+ *
1689
+ * Resolves API key from: constructor > env var > credentials file.
1690
+ */
1691
+ static async create(options = {}) {
1692
+ const config = await resolveConfig(options, options.tokenStore);
1693
+ return new _Lane(config);
1694
+ }
1695
+ /**
1696
+ * Create a Lane SDK instance synchronously (requires explicit apiKey).
1697
+ *
1698
+ * Use this when you already have the API key and don't need file-based
1699
+ * credential resolution.
1700
+ */
1701
+ static fromApiKey(apiKey, options = {}) {
1702
+ const testMode = options.testMode ?? apiKey.startsWith("lane_sk_test_");
1703
+ const config = Object.freeze({
1704
+ apiKey,
1705
+ baseUrl: options.baseUrl ?? process.env["LANE_BASE_URL"] ?? "https://api.getonlane.com",
1706
+ testMode,
1707
+ timeout: options.timeout ?? 3e4,
1708
+ maxRetries: options.maxRetries ?? 2,
1709
+ circuitBreaker: options.circuitBreaker ? Object.freeze({
1710
+ failureThreshold: options.circuitBreaker.failureThreshold ?? 3,
1711
+ resetTimeoutMs: options.circuitBreaker.resetTimeoutMs ?? 3e4
1712
+ }) : void 0
1713
+ });
1714
+ return new _Lane(config);
1715
+ }
1716
+ // -------------------------------------------------------------------------
1717
+ // Resource Accessors (lazy initialization)
1718
+ // -------------------------------------------------------------------------
1719
+ /** Authentication — login, whoami, key rotation. */
1720
+ get auth() {
1721
+ return this._auth ??= new Auth(this.client);
1722
+ }
1723
+ /** Wallets — create, list cards, add card, revoke. */
1724
+ get wallets() {
1725
+ return this._wallets ??= new Wallets(this.client);
1726
+ }
1727
+ /** Pay — agentic tokens, payment execution, refunds. */
1728
+ get pay() {
1729
+ return this._pay ??= new Pay(this.client);
1730
+ }
1731
+ /** Products — search catalog, list merchants. */
1732
+ get products() {
1733
+ return this._products ??= new Products(this.client);
1734
+ }
1735
+ /** Checkout — create and complete checkout sessions. */
1736
+ get checkout() {
1737
+ return this._checkout ??= new Checkout(this.client);
1738
+ }
1739
+ /** Sell — list products for sale, analytics (Make-a-SaaS). */
1740
+ get sell() {
1741
+ return this._sell ??= new Sell(this.client);
1742
+ }
1743
+ /** Admin — spending, budgets, team management, GDPR. */
1744
+ get admin() {
1745
+ return this._admin ??= new Admin(this.client);
1746
+ }
1747
+ /** Webhooks — register, verify, manage webhook endpoints. */
1748
+ get webhooks() {
1749
+ return this._webhooks ??= new Webhooks(this.client);
1750
+ }
1751
+ /** VIC — Visa Intelligent Commerce agentic tokens (Phase 2). */
1752
+ get vic() {
1753
+ return this._vic ??= new VIC(this.client);
1754
+ }
1755
+ /** Metering — usage tracking for Make-a-SaaS sellers (Phase 4). */
1756
+ get metering() {
1757
+ return this._metering ??= new Metering(this.client);
1758
+ }
1759
+ /** Payouts — seller disbursements (Phase 4). */
1760
+ get payouts() {
1761
+ return this._payouts ??= new Payouts(this.client);
1762
+ }
1763
+ /** Teams — team management with hierarchical budgets (Phase 5). */
1764
+ get teams() {
1765
+ return this._teams ??= new Teams(this.client);
1766
+ }
1767
+ /** Agents — fleet management with per-agent policies (Phase 5). */
1768
+ get agents() {
1769
+ return this._agents ??= new Agents(this.client);
1770
+ }
1771
+ /** Audit — immutable audit trail for SOC 2 compliance (Phase 5). */
1772
+ get audit() {
1773
+ return this._audit ??= new Audit(this.client);
1774
+ }
1775
+ /** Identity — agent identity and KYC chain (Phase 6). */
1776
+ get identity() {
1777
+ return this._identity ??= new Identity(this.client);
1778
+ }
1779
+ /** Subscriptions — manage software subscriptions (SCP). */
1780
+ get subscriptions() {
1781
+ return this._subscriptions ??= new Subscriptions(this.client);
1782
+ }
1783
+ /** Merchants — discover and search the merchant directory. */
1784
+ get merchants() {
1785
+ return this._merchants ??= new Merchants(this.client);
1786
+ }
1787
+ // -------------------------------------------------------------------------
1788
+ // Observability
1789
+ // -------------------------------------------------------------------------
1790
+ /** Subscribe to SDK events (request, response, error, retry). */
1791
+ on(event, listener) {
1792
+ this.client.on(event, listener);
1793
+ return this;
1794
+ }
1795
+ // -------------------------------------------------------------------------
1796
+ // Metadata
1797
+ // -------------------------------------------------------------------------
1798
+ /** Whether this instance is operating in test mode. */
1799
+ get testMode() {
1800
+ return this._config.testMode;
1801
+ }
1802
+ /** The resolved base URL. */
1803
+ get baseUrl() {
1804
+ return this._config.baseUrl;
1805
+ }
1806
+ };
1807
+
1808
+ // src/mcp/tool.ts
1809
+ var LaneTool = class {
1810
+ lane;
1811
+ constructor(lane) {
1812
+ this.lane = lane;
1813
+ }
1814
+ /**
1815
+ * Public entry point — validates input, runs tool, formats output.
1816
+ * Catches LaneErrors and formats them as structured agent-readable responses.
1817
+ */
1818
+ async execute(input) {
1819
+ try {
1820
+ const parsed = this.definition.inputSchema.parse(input);
1821
+ const result = await this.run(parsed);
1822
+ return {
1823
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
1824
+ };
1825
+ } catch (err) {
1826
+ if (err instanceof LaneError) {
1827
+ return {
1828
+ content: [
1829
+ {
1830
+ type: "text",
1831
+ text: JSON.stringify(
1832
+ {
1833
+ error: err.code,
1834
+ message: err.message,
1835
+ suggestedAction: err.suggestedAction,
1836
+ retryable: err.retryable
1837
+ },
1838
+ null,
1839
+ 2
1840
+ )
1841
+ }
1842
+ ],
1843
+ isError: true
1844
+ };
1845
+ }
1846
+ const message = err instanceof Error ? err.message : String(err);
1847
+ return {
1848
+ content: [{ type: "text", text: JSON.stringify({ error: "tool_error", message }, null, 2) }],
1849
+ isError: true
1850
+ };
1851
+ }
1852
+ }
1853
+ };
1854
+
1855
+ // src/mcp/tools/whoami.ts
1856
+ var WhoamiTool = class extends LaneTool {
1857
+ get definition() {
1858
+ return {
1859
+ name: "whoami",
1860
+ description: "Get the authenticated developer profile. Returns email, plan, and member since date.",
1861
+ inputSchema: zod.z.object({})
1862
+ };
1863
+ }
1864
+ async run() {
1865
+ return this.lane.auth.whoami();
1866
+ }
1867
+ };
1868
+ var inputSchema = zod.z.object({
1869
+ walletId: zod.z.string().optional().describe("Wallet ID. Uses default wallet if not provided.")
1870
+ });
1871
+ var ListCardsTool = class extends LaneTool {
1872
+ get definition() {
1873
+ return {
1874
+ name: "list_cards",
1875
+ description: "List available payment cards. Returns last4 and brand only \u2014 never full card numbers. Use this to check what payment methods are available before making a purchase.",
1876
+ inputSchema
1877
+ };
1878
+ }
1879
+ async run(input) {
1880
+ let walletId = input.walletId;
1881
+ if (!walletId) {
1882
+ const wallets = await this.lane.wallets.list({ limit: 1 });
1883
+ if (wallets.data.length === 0) {
1884
+ return { cards: [], message: "No wallets found. Ask the developer to run `lane add-card`." };
1885
+ }
1886
+ walletId = wallets.data[0].id;
1887
+ }
1888
+ const cards = await this.lane.wallets.listCards(walletId);
1889
+ return { cards };
1890
+ }
1891
+ };
1892
+ var inputSchema2 = zod.z.object({
1893
+ cardId: zod.z.string().optional().describe("Specific card ID to check balance for.")
1894
+ });
1895
+ var CheckBalanceTool = class extends LaneTool {
1896
+ get definition() {
1897
+ return {
1898
+ name: "check_balance",
1899
+ description: "Check remaining spending budget and limits. Returns daily limit, weekly limit, monthly limit, per-task limit, and how much has been spent today. Always check balance before making a payment to avoid exceeding limits.",
1900
+ inputSchema: inputSchema2
1901
+ };
1902
+ }
1903
+ async run(_input) {
1904
+ const [budget, spending] = await Promise.all([
1905
+ this.lane.admin.getBudget(),
1906
+ this.lane.admin.spending({ period: "1d" })
1907
+ ]);
1908
+ return {
1909
+ spentToday: spending.total,
1910
+ currency: spending.currency,
1911
+ limits: {
1912
+ daily: budget.dailyLimit ?? null,
1913
+ weekly: budget.weeklyLimit ?? null,
1914
+ monthly: budget.monthlyLimit ?? null,
1915
+ perTask: budget.perTaskLimit ?? null,
1916
+ confirmationThreshold: budget.confirmationThreshold ?? null
1917
+ },
1918
+ remaining: budget.dailyLimit ? { daily: budget.dailyLimit - spending.total } : null
1919
+ };
1920
+ }
1921
+ };
1922
+ var inputSchema3 = zod.z.object({
1923
+ amount: zod.z.number().positive().describe("Payment amount in dollars (e.g. 20.00)."),
1924
+ currency: zod.z.string().default("USD").describe("Currency code (default: USD)."),
1925
+ recipient: zod.z.string().describe('Merchant or service to pay (e.g. "replicate.com").'),
1926
+ description: zod.z.string().optional().describe("What this payment is for."),
1927
+ idempotencyKey: zod.z.string().optional().describe("Unique key to prevent duplicate charges on retry.")
1928
+ });
1929
+ var PayTool = class extends LaneTool {
1930
+ get definition() {
1931
+ return {
1932
+ name: "pay",
1933
+ description: "Execute a payment to a merchant or service. The payment is routed through the best available path (billing API, ACP, or VIC token). Amount is in dollars. Always check_balance first to ensure the payment is within limits. Provide an idempotencyKey to prevent double charges if you need to retry.",
1934
+ inputSchema: inputSchema3
1935
+ };
1936
+ }
1937
+ async run(input) {
1938
+ const txn = await this.lane.pay.execute({
1939
+ recipient: input.recipient,
1940
+ amount: Math.round(input.amount * 100),
1941
+ // Convert dollars to cents
1942
+ currency: input.currency,
1943
+ description: input.description,
1944
+ idempotencyKey: input.idempotencyKey
1945
+ });
1946
+ return {
1947
+ transactionId: txn.id,
1948
+ amount: txn.amount / 100,
1949
+ // Back to dollars for display
1950
+ currency: txn.currency,
1951
+ recipient: txn.recipient,
1952
+ status: txn.status,
1953
+ receiptUrl: txn.receiptUrl,
1954
+ testMode: txn.testMode
1955
+ };
1956
+ }
1957
+ };
1958
+ var inputSchema4 = zod.z.object({
1959
+ limit: zod.z.number().int().min(1).max(100).default(10).describe("Number of transactions to return."),
1960
+ offset: zod.z.number().int().min(0).default(0).describe("Offset for pagination."),
1961
+ cardId: zod.z.string().optional().describe("Filter by specific card ID.")
1962
+ });
1963
+ var ListTransactionsTool = class extends LaneTool {
1964
+ get definition() {
1965
+ return {
1966
+ name: "list_transactions",
1967
+ description: "List recent payment transactions. Shows amount, recipient, timestamp, status, and description. Use this to review spending history or verify a payment was successful.",
1968
+ inputSchema: inputSchema4
1969
+ };
1970
+ }
1971
+ async run(input) {
1972
+ const result = await this.lane.pay.listTransactions({
1973
+ limit: input.limit,
1974
+ offset: input.offset,
1975
+ cardId: input.cardId
1976
+ });
1977
+ return {
1978
+ transactions: result.data.map((txn) => ({
1979
+ id: txn.id,
1980
+ amount: txn.amount / 100,
1981
+ currency: txn.currency,
1982
+ recipient: txn.recipient,
1983
+ description: txn.description,
1984
+ status: txn.status,
1985
+ testMode: txn.testMode,
1986
+ createdAt: txn.createdAt
1987
+ })),
1988
+ total: result.total,
1989
+ limit: result.limit,
1990
+ offset: result.offset
1991
+ };
1992
+ }
1993
+ };
1994
+ var inputSchema5 = zod.z.object({
1995
+ query: zod.z.string().describe('Search query (e.g. "image resize API", "API credits").'),
1996
+ type: zod.z.enum(["skill", "api", "saas", "opensource"]).optional().describe("Filter by product type."),
1997
+ limit: zod.z.number().int().min(1).max(50).default(10).describe("Number of results.")
1998
+ });
1999
+ var SearchProductsTool = class extends LaneTool {
2000
+ get definition() {
2001
+ return {
2002
+ name: "search_products",
2003
+ description: "Search the Lane product catalog for APIs, skills, SaaS, and open source tools. Returns product name, description, pricing, and seller info. Use this to find services the agent can purchase.",
2004
+ inputSchema: inputSchema5
2005
+ };
2006
+ }
2007
+ async run(input) {
2008
+ const result = await this.lane.products.search({
2009
+ query: input.query,
2010
+ type: input.type,
2011
+ limit: input.limit
2012
+ });
2013
+ return {
2014
+ products: result.data.map((p) => ({
2015
+ id: p.id,
2016
+ name: p.name,
2017
+ description: p.description,
2018
+ type: p.type,
2019
+ pricing: p.pricing,
2020
+ rating: p.rating
2021
+ })),
2022
+ total: result.total
2023
+ };
2024
+ }
2025
+ };
2026
+ var inputSchema6 = zod.z.object({
2027
+ productId: zod.z.string().describe("Product ID from search_products results."),
2028
+ walletId: zod.z.string().optional().describe("Wallet ID. Uses default if not provided."),
2029
+ quantity: zod.z.number().int().min(1).default(1).describe("Quantity to purchase.")
2030
+ });
2031
+ var CheckoutTool = class extends LaneTool {
2032
+ get definition() {
2033
+ return {
2034
+ name: "checkout",
2035
+ description: "Purchase a product from the Lane catalog. Creates a checkout session and completes payment in one step. Returns the order details including any API keys or endpoints. Use search_products first to find the product ID.",
2036
+ inputSchema: inputSchema6
2037
+ };
2038
+ }
2039
+ async run(input) {
2040
+ let walletId = input.walletId;
2041
+ if (!walletId) {
2042
+ const wallets = await this.lane.wallets.list({ limit: 1 });
2043
+ if (wallets.data.length === 0) {
2044
+ return {
2045
+ error: "no_wallet",
2046
+ message: "No wallet found. Ask the developer to run `lane add-card` first."
2047
+ };
2048
+ }
2049
+ walletId = wallets.data[0].id;
2050
+ }
2051
+ const session = await this.lane.checkout.create({
2052
+ productId: input.productId,
2053
+ walletId,
2054
+ quantity: input.quantity
2055
+ });
2056
+ const order = await this.lane.checkout.complete(session.id);
2057
+ return {
2058
+ orderId: order.id,
2059
+ productId: order.productId,
2060
+ amount: order.amount / 100,
2061
+ currency: order.currency,
2062
+ status: order.status,
2063
+ apiKey: order.apiKey,
2064
+ endpoint: order.endpoint
2065
+ };
2066
+ }
2067
+ };
2068
+ var inputSchema7 = zod.z.object({
2069
+ daily: zod.z.number().positive().optional().describe("Daily spending limit in dollars."),
2070
+ weekly: zod.z.number().positive().optional().describe("Weekly spending limit in dollars."),
2071
+ monthly: zod.z.number().positive().optional().describe("Monthly spending limit in dollars."),
2072
+ perTask: zod.z.number().positive().optional().describe("Per-task spending limit in dollars.")
2073
+ });
2074
+ var SetBudgetTool = class extends LaneTool {
2075
+ get definition() {
2076
+ return {
2077
+ name: "set_budget",
2078
+ description: "Set spending limits for agent payments. Amounts are in dollars. Only the authenticated developer can change these limits. The agent cannot exceed these limits when using the pay tool.",
2079
+ inputSchema: inputSchema7
2080
+ };
2081
+ }
2082
+ async run(input) {
2083
+ const config = await this.lane.admin.setBudget({
2084
+ dailyLimit: input.daily ? Math.round(input.daily * 100) : void 0,
2085
+ weeklyLimit: input.weekly ? Math.round(input.weekly * 100) : void 0,
2086
+ monthlyLimit: input.monthly ? Math.round(input.monthly * 100) : void 0,
2087
+ perTaskLimit: input.perTask ? Math.round(input.perTask * 100) : void 0
2088
+ });
2089
+ return {
2090
+ limits: {
2091
+ daily: config.dailyLimit ? config.dailyLimit / 100 : null,
2092
+ weekly: config.weeklyLimit ? config.weeklyLimit / 100 : null,
2093
+ monthly: config.monthlyLimit ? config.monthlyLimit / 100 : null,
2094
+ perTask: config.perTaskLimit ? config.perTaskLimit / 100 : null
2095
+ }
2096
+ };
2097
+ }
2098
+ };
2099
+ var inputSchema8 = zod.z.object({
2100
+ transactionId: zod.z.string().describe("Transaction ID to refund."),
2101
+ amount: zod.z.number().positive().optional().describe("Refund amount in dollars. If omitted, full refund."),
2102
+ reason: zod.z.string().optional().describe("Reason for the refund.")
2103
+ });
2104
+ var RequestRefundTool = class extends LaneTool {
2105
+ get definition() {
2106
+ return {
2107
+ name: "request_refund",
2108
+ description: "Request a refund for a previous transaction. Partial or full refunds are supported. The developer may need to approve refunds above a certain threshold. Use list_transactions to find the transaction ID.",
2109
+ inputSchema: inputSchema8
2110
+ };
2111
+ }
2112
+ async run(input) {
2113
+ const refund = await this.lane.pay.refund({
2114
+ transactionId: input.transactionId,
2115
+ amount: input.amount ? Math.round(input.amount * 100) : void 0,
2116
+ reason: input.reason
2117
+ });
2118
+ return {
2119
+ refundId: refund.id,
2120
+ transactionId: refund.transactionId,
2121
+ amount: refund.amount / 100,
2122
+ status: refund.status,
2123
+ reason: refund.reason
2124
+ };
2125
+ }
2126
+ };
2127
+ var inputSchema9 = zod.z.object({
2128
+ productId: zod.z.string().describe("Product ID to subscribe to."),
2129
+ plan: zod.z.string().describe('Plan ID (e.g. "pro", "starter").'),
2130
+ parameters: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("SCP selection parameters (e.g. custom_domain, webhook_url).")
2131
+ });
2132
+ var SubscribeTool = class extends LaneTool {
2133
+ get definition() {
2134
+ return {
2135
+ name: "subscribe",
2136
+ description: "Subscribe to a software product. Creates a subscription with recurring billing from the wallet. Returns subscription details including access credentials. Use search_products first to find the product and plan IDs.",
2137
+ inputSchema: inputSchema9
2138
+ };
2139
+ }
2140
+ async run(input) {
2141
+ const subscription = await this.lane.subscriptions.create({
2142
+ productId: input.productId,
2143
+ plan: input.plan,
2144
+ parameters: input.parameters,
2145
+ paymentSource: "wallet"
2146
+ });
2147
+ return {
2148
+ subscriptionId: subscription.id,
2149
+ productId: subscription.productId,
2150
+ plan: subscription.plan,
2151
+ status: subscription.status,
2152
+ accessToken: subscription.accessToken,
2153
+ currentPeriodStart: subscription.currentPeriodStart,
2154
+ currentPeriodEnd: subscription.currentPeriodEnd
2155
+ };
2156
+ }
2157
+ };
2158
+ var inputSchema10 = zod.z.object({
2159
+ status: zod.z.enum(["active", "paused", "canceled", "past_due", "trialing"]).optional().describe("Filter by subscription status."),
2160
+ limit: zod.z.number().int().min(1).max(50).default(20).describe("Number of results.")
2161
+ });
2162
+ var ListSubscriptionsTool = class extends LaneTool {
2163
+ get definition() {
2164
+ return {
2165
+ name: "list_subscriptions",
2166
+ description: "List active subscriptions for the current wallet. Returns subscription details including product ID, plan, status, and billing period. Use this to check what software products the agent is currently subscribed to.",
2167
+ inputSchema: inputSchema10
2168
+ };
2169
+ }
2170
+ async run(input) {
2171
+ const result = await this.lane.subscriptions.list({
2172
+ status: input.status,
2173
+ limit: input.limit
2174
+ });
2175
+ return {
2176
+ subscriptions: result.data.map((s) => ({
2177
+ id: s.id,
2178
+ productId: s.productId,
2179
+ plan: s.plan,
2180
+ status: s.status,
2181
+ currentPeriodStart: s.currentPeriodStart,
2182
+ currentPeriodEnd: s.currentPeriodEnd
2183
+ })),
2184
+ total: result.total
2185
+ };
2186
+ }
2187
+ };
2188
+ var inputSchema11 = zod.z.object({
2189
+ query: zod.z.string().describe('Search query (e.g. "monitoring API", "email service").'),
2190
+ type: zod.z.enum(["software", "skill", "oss"]).optional().describe("Filter by software type. Defaults to all software types."),
2191
+ pricingModel: zod.z.enum(["fixed", "subscription", "consumption"]).optional().describe("Filter by pricing model."),
2192
+ limit: zod.z.number().int().min(1).max(50).default(10).describe("Number of results.")
2193
+ });
2194
+ var SearchSoftwareTool = class extends LaneTool {
2195
+ get definition() {
2196
+ return {
2197
+ name: "search_software",
2198
+ description: "Search for software products, SaaS tools, skills, and open source tools in the Lane catalog. Returns product details including SCP manifests, pricing, and plans. More targeted than search_products \u2014 focuses on software and subscribable products.",
2199
+ inputSchema: inputSchema11
2200
+ };
2201
+ }
2202
+ async run(input) {
2203
+ const typeMap = {
2204
+ software: "saas",
2205
+ skill: "skill",
2206
+ oss: "opensource"
2207
+ };
2208
+ const result = await this.lane.products.search({
2209
+ query: input.query,
2210
+ type: input.type ? typeMap[input.type] : void 0,
2211
+ limit: input.limit
2212
+ });
2213
+ return {
2214
+ products: result.data.map((p) => ({
2215
+ id: p.id,
2216
+ name: p.name,
2217
+ description: p.description,
2218
+ type: p.type,
2219
+ pricing: p.pricing,
2220
+ rating: p.rating
2221
+ })),
2222
+ total: result.total
2223
+ };
2224
+ }
2225
+ };
2226
+ var inputSchema12 = zod.z.object({
2227
+ subscriptionId: zod.z.string().describe("Subscription ID to cancel.")
2228
+ });
2229
+ var CancelSubscriptionTool = class extends LaneTool {
2230
+ get definition() {
2231
+ return {
2232
+ name: "cancel_subscription",
2233
+ description: "Cancel an active subscription. The subscription will remain active until the end of the current billing period. Use list_subscriptions first to find the subscription ID.",
2234
+ inputSchema: inputSchema12
2235
+ };
2236
+ }
2237
+ async run(input) {
2238
+ const subscription = await this.lane.subscriptions.cancel(input.subscriptionId);
2239
+ return {
2240
+ subscriptionId: subscription.id,
2241
+ productId: subscription.productId,
2242
+ plan: subscription.plan,
2243
+ status: subscription.status,
2244
+ canceledAt: subscription.canceledAt,
2245
+ currentPeriodEnd: subscription.currentPeriodEnd
2246
+ };
2247
+ }
2248
+ };
2249
+ var inputSchema13 = zod.z.object({
2250
+ query: zod.z.string().optional().describe("Search query to find merchants by name or description."),
2251
+ merchantType: zod.z.enum(["ecommerce", "software", "api", "saas", "skill", "service"]).optional().describe("Filter by merchant type."),
2252
+ vertical: zod.z.string().optional().describe('Filter by vertical (e.g. "fashion", "electronics", "developer_tools").'),
2253
+ subcategory: zod.z.string().optional().describe('Filter by subcategory (e.g. "shoes", "laptops", "hosting").'),
2254
+ tier: zod.z.enum(["lane_onboarded", "protocol"]).optional().describe("Filter by tier: lane_onboarded (full checkout) or protocol (ACP/UCP)."),
2255
+ limit: zod.z.number().int().min(1).max(50).optional().default(10).describe("Max results to return (default 10).")
2256
+ });
2257
+ var DiscoverMerchantsTool = class extends LaneTool {
2258
+ get definition() {
2259
+ return {
2260
+ name: "discover_merchants",
2261
+ description: "Search the Lane merchant directory to find AI-shoppable merchants. Filter by type (ecommerce, software, api, saas, skill, service), vertical (fashion, electronics, developer_tools, etc.), subcategory (shoes, laptops, hosting, etc.), or free-text query. Returns merchant name, domain, capabilities, and supported protocols.",
2262
+ inputSchema: inputSchema13
2263
+ };
2264
+ }
2265
+ async run(input) {
2266
+ const result = await this.lane.merchants.list({
2267
+ query: input.query,
2268
+ merchantType: input.merchantType,
2269
+ vertical: input.vertical,
2270
+ subcategory: input.subcategory,
2271
+ tier: input.tier,
2272
+ limit: input.limit
2273
+ });
2274
+ return {
2275
+ merchants: result.data.map((m) => ({
2276
+ name: m.name,
2277
+ slug: m.slug,
2278
+ domain: m.domain,
2279
+ description: m.description,
2280
+ tier: m.tier,
2281
+ merchantType: m.merchantType,
2282
+ vertical: m.vertical,
2283
+ subcategories: m.subcategories,
2284
+ protocols: m.protocols,
2285
+ capabilities: m.capabilities,
2286
+ productCount: m.productCount
2287
+ })),
2288
+ total: result.total,
2289
+ showing: result.data.length
2290
+ };
2291
+ }
2292
+ };
2293
+
2294
+ // src/mcp/server.ts
2295
+ var LaneMCPServer = class {
2296
+ constructor(lane, options = {}) {
2297
+ this.lane = lane;
2298
+ this.mcp = new mcp_js.McpServer(
2299
+ {
2300
+ name: options.name ?? "lane",
2301
+ version: options.version ?? "0.1.0"
2302
+ },
2303
+ {
2304
+ capabilities: {
2305
+ tools: {}
2306
+ }
2307
+ }
2308
+ );
2309
+ this.tools = [
2310
+ new WhoamiTool(lane),
2311
+ new ListCardsTool(lane),
2312
+ new CheckBalanceTool(lane),
2313
+ new PayTool(lane),
2314
+ new ListTransactionsTool(lane),
2315
+ new SearchProductsTool(lane),
2316
+ new CheckoutTool(lane),
2317
+ new SetBudgetTool(lane),
2318
+ new RequestRefundTool(lane),
2319
+ new SubscribeTool(lane),
2320
+ new ListSubscriptionsTool(lane),
2321
+ new SearchSoftwareTool(lane),
2322
+ new CancelSubscriptionTool(lane),
2323
+ new DiscoverMerchantsTool(lane)
2324
+ ];
2325
+ this.registerTools();
2326
+ }
2327
+ mcp;
2328
+ tools;
2329
+ registerTools() {
2330
+ for (const tool of this.tools) {
2331
+ const def = tool.definition;
2332
+ this.mcp.registerTool(
2333
+ def.name,
2334
+ { description: def.description, inputSchema: def.inputSchema },
2335
+ async (args) => {
2336
+ const result = await tool.execute(args);
2337
+ return { ...result };
2338
+ }
2339
+ );
2340
+ }
2341
+ }
2342
+ };
2343
+
2344
+ // src/vgs/token.ts
2345
+ var TOKEN_REFRESH_MARGIN_MS = 6e4;
2346
+ var VGSTokenManager = class {
2347
+ constructor(config) {
2348
+ this.config = config;
2349
+ }
2350
+ cached = null;
2351
+ /**
2352
+ * Get a valid access token, refreshing if expired or near-expiry.
2353
+ */
2354
+ async getAccessToken() {
2355
+ if (this.cached && Date.now() < this.cached.expiresAt - TOKEN_REFRESH_MARGIN_MS) {
2356
+ return this.cached.accessToken;
2357
+ }
2358
+ return this.refresh();
2359
+ }
2360
+ /**
2361
+ * Force-refresh the token.
2362
+ */
2363
+ async refresh() {
2364
+ const tokenUrl = this.getTokenUrl();
2365
+ const body = new URLSearchParams({
2366
+ grant_type: "client_credentials",
2367
+ client_id: this.config.clientId,
2368
+ client_secret: this.config.clientSecret
2369
+ });
2370
+ const response = await fetch(tokenUrl, {
2371
+ method: "POST",
2372
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2373
+ body: body.toString()
2374
+ });
2375
+ if (!response.ok) {
2376
+ throw LaneAuthError.invalidApiKey(
2377
+ `VGS token exchange failed: ${response.status} ${response.statusText}`
2378
+ );
2379
+ }
2380
+ const data = await response.json();
2381
+ this.cached = {
2382
+ accessToken: data.access_token,
2383
+ expiresAt: Date.now() + data.expires_in * 1e3
2384
+ };
2385
+ return this.cached.accessToken;
2386
+ }
2387
+ /**
2388
+ * Invalidate the cached token.
2389
+ */
2390
+ invalidate() {
2391
+ this.cached = null;
2392
+ }
2393
+ getTokenUrl() {
2394
+ const env = this.config.environment === "live" ? "live" : "sandbox";
2395
+ return `https://auth.verygoodsecurity.com/vaults/${this.config.vaultId}/tokens?env=${env}`;
2396
+ }
2397
+ };
2398
+
2399
+ // src/vgs/proxy.ts
2400
+ var VGSOutboundProxy = class {
2401
+ constructor(config) {
2402
+ this.config = config;
2403
+ this.tokenManager = new VGSTokenManager(config);
2404
+ }
2405
+ tokenManager;
2406
+ /**
2407
+ * Send a request through the VGS outbound proxy.
2408
+ * VGS aliases in the body are de-tokenized and revealed to the target PSP.
2409
+ */
2410
+ async forward(request) {
2411
+ const accessToken = await this.tokenManager.getAccessToken();
2412
+ const proxyUrl = this.getProxyUrl();
2413
+ const response = await fetch(proxyUrl, {
2414
+ method: request.method,
2415
+ headers: {
2416
+ ...request.headers,
2417
+ "Authorization": `Bearer ${accessToken}`,
2418
+ "X-VGS-Route-Id": this.config.routeId,
2419
+ "X-VGS-Upstream-Url": request.url,
2420
+ "Content-Type": "application/json"
2421
+ },
2422
+ body: JSON.stringify(request.body)
2423
+ });
2424
+ const responseBody = await response.json().catch(() => null);
2425
+ if (!response.ok) {
2426
+ throw new LanePaymentError(
2427
+ `VGS proxy request failed: ${response.status}`,
2428
+ {
2429
+ code: "vgs_proxy_error",
2430
+ statusCode: response.status,
2431
+ retryable: response.status >= 500,
2432
+ suggestedAction: "Check VGS vault configuration and PSP endpoint whitelist."
2433
+ }
2434
+ );
2435
+ }
2436
+ const responseHeaders = {};
2437
+ response.headers.forEach((value, key) => {
2438
+ responseHeaders[key] = value;
2439
+ });
2440
+ return {
2441
+ status: response.status,
2442
+ headers: responseHeaders,
2443
+ body: responseBody
2444
+ };
2445
+ }
2446
+ /**
2447
+ * Invalidate the cached VGS access token.
2448
+ */
2449
+ invalidateToken() {
2450
+ this.tokenManager.invalidate();
2451
+ }
2452
+ getProxyUrl() {
2453
+ const env = this.config.environment === "live" ? "live" : "sandbox";
2454
+ return `https://${this.config.vaultId}.${env}.verygoodproxy.com`;
2455
+ }
2456
+ };
2457
+
2458
+ // src/vgs/card-types.ts
2459
+ var BIN_RANGES = [
2460
+ { brand: "visa", prefixes: ["4"], lengths: [13, 16, 19] },
2461
+ { brand: "mastercard", prefixes: ["51", "52", "53", "54", "55", "2221", "2720"], lengths: [16] },
2462
+ { brand: "amex", prefixes: ["34", "37"], lengths: [15] },
2463
+ { brand: "discover", prefixes: ["6011", "644", "645", "646", "647", "648", "649", "65"], lengths: [16, 19] },
2464
+ { brand: "diners", prefixes: ["300", "301", "302", "303", "304", "305", "36", "38"], lengths: [14, 16] },
2465
+ { brand: "jcb", prefixes: ["3528", "3589"], lengths: [16, 17, 18, 19] },
2466
+ { brand: "unionpay", prefixes: ["62", "81"], lengths: [16, 17, 18, 19] }
2467
+ ];
2468
+ function detectCardBrand(cardNumber) {
2469
+ const digits = cardNumber.replace(/\D/g, "");
2470
+ for (const range of BIN_RANGES) {
2471
+ for (const prefix of range.prefixes) {
2472
+ if (digits.startsWith(prefix)) {
2473
+ return range.brand;
2474
+ }
2475
+ }
2476
+ }
2477
+ return "unknown";
2478
+ }
2479
+ function luhnCheck(cardNumber) {
2480
+ const digits = cardNumber.replace(/\D/g, "");
2481
+ if (digits.length < 12 || digits.length > 19) return false;
2482
+ let sum = 0;
2483
+ let alternate = false;
2484
+ for (let i = digits.length - 1; i >= 0; i--) {
2485
+ let n = parseInt(digits[i], 10);
2486
+ if (alternate) {
2487
+ n *= 2;
2488
+ if (n > 9) n -= 9;
2489
+ }
2490
+ sum += n;
2491
+ alternate = !alternate;
2492
+ }
2493
+ return sum % 10 === 0;
2494
+ }
2495
+ function maskCardNumber(cardNumber) {
2496
+ const digits = cardNumber.replace(/\D/g, "");
2497
+ if (digits.length < 4) return "****";
2498
+ return `****${digits.slice(-4)}`;
2499
+ }
2500
+
2501
+ // src/vgs/collect.ts
2502
+ function generateCollectPage(config) {
2503
+ const vgsEnv = config.environment === "live" ? "live" : "sandbox";
2504
+ `https://${config.vaultId}.${vgsEnv}.verygoodproxy.com`;
2505
+ return `<!DOCTYPE html>
2506
+ <html lang="en">
2507
+ <head>
2508
+ <meta charset="UTF-8">
2509
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2510
+ <title>Lane \u2014 Add Payment Method</title>
2511
+ <script src="https://js.verygoodvault.com/vgs-collect/2.18.0/vgs-collect.js"></script>
2512
+ <style>
2513
+ * { box-sizing: border-box; margin: 0; padding: 0; }
2514
+ body {
2515
+ font-family: 'At Aero', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2516
+ background: #0A0A0A;
2517
+ color: #FFFFFF;
2518
+ display: flex;
2519
+ justify-content: center;
2520
+ align-items: center;
2521
+ min-height: 100vh;
2522
+ }
2523
+ .container {
2524
+ width: 100%;
2525
+ max-width: 440px;
2526
+ padding: 40px 32px;
2527
+ background: #141414;
2528
+ border: 1px solid #2A2A2A;
2529
+ border-radius: 16px;
2530
+ }
2531
+ .logo {
2532
+ font-size: 24px;
2533
+ font-weight: 700;
2534
+ color: #1201FF;
2535
+ letter-spacing: -0.5px;
2536
+ margin-bottom: 8px;
2537
+ }
2538
+ .subtitle {
2539
+ color: #888;
2540
+ font-size: 14px;
2541
+ margin-bottom: 32px;
2542
+ }
2543
+ .field-group {
2544
+ margin-bottom: 20px;
2545
+ }
2546
+ .field-group label {
2547
+ display: block;
2548
+ font-size: 12px;
2549
+ font-weight: 600;
2550
+ color: #888;
2551
+ text-transform: uppercase;
2552
+ letter-spacing: 0.5px;
2553
+ margin-bottom: 8px;
2554
+ }
2555
+ .field-wrapper {
2556
+ background: #1A1A1A;
2557
+ border: 1px solid #333;
2558
+ border-radius: 8px;
2559
+ padding: 0;
2560
+ height: 48px;
2561
+ transition: border-color 0.2s;
2562
+ }
2563
+ .field-wrapper:focus-within {
2564
+ border-color: #1201FF;
2565
+ }
2566
+ .field-wrapper iframe {
2567
+ height: 48px !important;
2568
+ }
2569
+ .row {
2570
+ display: flex;
2571
+ gap: 12px;
2572
+ }
2573
+ .row .field-group {
2574
+ flex: 1;
2575
+ }
2576
+ .submit-btn {
2577
+ width: 100%;
2578
+ height: 48px;
2579
+ background: #1201FF;
2580
+ color: #FFFFFF;
2581
+ border: none;
2582
+ border-radius: 8px;
2583
+ font-size: 16px;
2584
+ font-weight: 600;
2585
+ cursor: pointer;
2586
+ margin-top: 24px;
2587
+ transition: background 0.2s;
2588
+ }
2589
+ .submit-btn:hover { background: #0E01CC; }
2590
+ .submit-btn:disabled {
2591
+ background: #333;
2592
+ cursor: not-allowed;
2593
+ }
2594
+ .security-note {
2595
+ text-align: center;
2596
+ margin-top: 16px;
2597
+ font-size: 12px;
2598
+ color: #555;
2599
+ }
2600
+ .security-note svg {
2601
+ vertical-align: middle;
2602
+ margin-right: 4px;
2603
+ }
2604
+ .error-msg {
2605
+ color: #FF4444;
2606
+ font-size: 13px;
2607
+ margin-top: 8px;
2608
+ display: none;
2609
+ }
2610
+ .success-container {
2611
+ text-align: center;
2612
+ display: none;
2613
+ }
2614
+ .success-container .check {
2615
+ font-size: 48px;
2616
+ margin-bottom: 16px;
2617
+ }
2618
+ </style>
2619
+ </head>
2620
+ <body>
2621
+ <div class="container">
2622
+ <div id="form-view">
2623
+ <div class="logo">Lane</div>
2624
+ <div class="subtitle">Add a payment method. Your card data goes directly to our PCI Level 1 vault.</div>
2625
+
2626
+ <form id="card-form">
2627
+ <div class="field-group">
2628
+ <label>Card Number</label>
2629
+ <div id="cc-number" class="field-wrapper"></div>
2630
+ </div>
2631
+
2632
+ <div class="row">
2633
+ <div class="field-group">
2634
+ <label>Expiry</label>
2635
+ <div id="cc-expiry" class="field-wrapper"></div>
2636
+ </div>
2637
+ <div class="field-group">
2638
+ <label>CVC</label>
2639
+ <div id="cc-cvc" class="field-wrapper"></div>
2640
+ </div>
2641
+ </div>
2642
+
2643
+ <div class="field-group">
2644
+ <label>Cardholder Name</label>
2645
+ <div id="cc-name" class="field-wrapper"></div>
2646
+ </div>
2647
+
2648
+ <div id="error-msg" class="error-msg"></div>
2649
+
2650
+ <button type="submit" id="submit-btn" class="submit-btn" disabled>
2651
+ Add Card
2652
+ </button>
2653
+ </form>
2654
+
2655
+ <div class="security-note">
2656
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="#555"><path d="M6 1a3 3 0 0 0-3 3v1H2.5A.5.5 0 0 0 2 5.5v5a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5v-5A.5.5 0 0 0 9.5 5H9V4a3 3 0 0 0-3-3zm2 4H4V4a2 2 0 1 1 4 0v1z"/></svg>
2657
+ Secured by VGS. Lane never sees your card number.
2658
+ </div>
2659
+ </div>
2660
+
2661
+ <div id="success-view" class="success-container">
2662
+ <div class="check">&#10003;</div>
2663
+ <div class="logo">Card Added</div>
2664
+ <div class="subtitle">You can close this window and return to your terminal.</div>
2665
+ </div>
2666
+ </div>
2667
+
2668
+ <script>
2669
+ const form = VGSCollect.create('${config.vaultId}', '${vgsEnv}', function(state) {});
2670
+ const css = {
2671
+ 'font-family': '"JetBrains Mono", monospace',
2672
+ 'font-size': '16px',
2673
+ 'color': '#FFFFFF',
2674
+ 'padding': '12px 16px',
2675
+ '&::placeholder': { color: '#555' },
2676
+ };
2677
+
2678
+ form.field('#cc-number', {
2679
+ type: 'card-number',
2680
+ name: 'card_number',
2681
+ placeholder: '4242 4242 4242 4242',
2682
+ validations: ['required', 'validCardNumber'],
2683
+ css: css,
2684
+ });
2685
+
2686
+ form.field('#cc-expiry', {
2687
+ type: 'card-expiration-date',
2688
+ name: 'card_exp',
2689
+ placeholder: 'MM / YY',
2690
+ validations: ['required', 'validCardExpirationDate'],
2691
+ css: css,
2692
+ });
2693
+
2694
+ form.field('#cc-cvc', {
2695
+ type: 'card-security-code',
2696
+ name: 'card_cvc',
2697
+ placeholder: 'CVC',
2698
+ validations: ['required', 'validCardSecurityCode'],
2699
+ css: css,
2700
+ });
2701
+
2702
+ form.field('#cc-name', {
2703
+ type: 'text',
2704
+ name: 'card_name',
2705
+ placeholder: 'Name on card',
2706
+ validations: ['required'],
2707
+ css: css,
2708
+ });
2709
+
2710
+ // Enable submit when all fields are valid
2711
+ let fieldStates = {};
2712
+ form.on('change', function(state) {
2713
+ fieldStates = state;
2714
+ const allValid = Object.values(state).every(function(f) { return f.isValid; });
2715
+ document.getElementById('submit-btn').disabled = !allValid;
2716
+ });
2717
+
2718
+ document.getElementById('card-form').addEventListener('submit', function(e) {
2719
+ e.preventDefault();
2720
+ var btn = document.getElementById('submit-btn');
2721
+ btn.disabled = true;
2722
+ btn.textContent = 'Processing...';
2723
+ document.getElementById('error-msg').style.display = 'none';
2724
+
2725
+ form.submit(
2726
+ '/post',
2727
+ {
2728
+ headers: { 'Content-Type': 'application/json' },
2729
+ data: function(fields) {
2730
+ return {
2731
+ card_number: fields.card_number,
2732
+ card_exp: fields.card_exp,
2733
+ card_cvc: fields.card_cvc,
2734
+ card_name: fields.card_name,
2735
+ developer_id: '${config.developerId}',
2736
+ };
2737
+ },
2738
+ },
2739
+ function(status, data) {
2740
+ if (status >= 200 && status < 300) {
2741
+ document.getElementById('form-view').style.display = 'none';
2742
+ document.getElementById('success-view').style.display = 'block';
2743
+ // Notify callback
2744
+ fetch('${config.callbackUrl}', {
2745
+ method: 'POST',
2746
+ headers: { 'Content-Type': 'application/json' },
2747
+ body: JSON.stringify({
2748
+ alias: data.card_number,
2749
+ last4: data.last4 || 'xxxx',
2750
+ brand: data.brand || 'unknown',
2751
+ }),
2752
+ });
2753
+ } else {
2754
+ var errEl = document.getElementById('error-msg');
2755
+ errEl.textContent = 'Card could not be saved. Please check your details and try again.';
2756
+ errEl.style.display = 'block';
2757
+ btn.disabled = false;
2758
+ btn.textContent = 'Add Card';
2759
+ }
2760
+ },
2761
+ function(errors) {
2762
+ var errEl = document.getElementById('error-msg');
2763
+ errEl.textContent = 'Validation failed. Please check all fields.';
2764
+ errEl.style.display = 'block';
2765
+ btn.disabled = false;
2766
+ btn.textContent = 'Add Card';
2767
+ }
2768
+ );
2769
+ });
2770
+ </script>
2771
+ </body>
2772
+ </html>`;
2773
+ }
2774
+
2775
+ // src/psp/registry.ts
2776
+ var PSPRegistry = class {
2777
+ adapters = /* @__PURE__ */ new Map();
2778
+ /**
2779
+ * Register an adapter with a priority (lower = higher priority).
2780
+ */
2781
+ register(adapter, priority = 100) {
2782
+ this.adapters.set(adapter.name, { adapter, priority });
2783
+ }
2784
+ /**
2785
+ * Remove an adapter from the registry.
2786
+ */
2787
+ unregister(name) {
2788
+ this.adapters.delete(name);
2789
+ }
2790
+ /**
2791
+ * Get a specific adapter by name.
2792
+ */
2793
+ get(name) {
2794
+ return this.adapters.get(name)?.adapter;
2795
+ }
2796
+ /**
2797
+ * Get the highest-priority adapter (lowest priority number).
2798
+ * Optionally exclude specific adapters (e.g., after a failure).
2799
+ */
2800
+ getPrimary(exclude) {
2801
+ let best;
2802
+ for (const [name, entry] of this.adapters) {
2803
+ if (exclude?.has(name)) continue;
2804
+ if (!best || entry.priority < best.priority) {
2805
+ best = entry;
2806
+ }
2807
+ }
2808
+ return best?.adapter;
2809
+ }
2810
+ /**
2811
+ * Get all registered adapters sorted by priority.
2812
+ */
2813
+ getAll() {
2814
+ return Array.from(this.adapters.values()).sort((a, b) => a.priority - b.priority).map((entry) => entry.adapter);
2815
+ }
2816
+ /**
2817
+ * Run health checks on all adapters. Returns names of healthy adapters.
2818
+ */
2819
+ async healthCheckAll() {
2820
+ const results = await Promise.allSettled(
2821
+ Array.from(this.adapters.entries()).map(async ([name, { adapter }]) => {
2822
+ const ok = await adapter.healthCheck();
2823
+ return ok ? name : null;
2824
+ })
2825
+ );
2826
+ return results.filter((r) => r.status === "fulfilled" && r.value !== null).map((r) => r.value);
2827
+ }
2828
+ };
2829
+
2830
+ // src/psp/adapters/stripe.ts
2831
+ var StripeAdapter = class {
2832
+ name = "stripe";
2833
+ config;
2834
+ constructor(config) {
2835
+ this.config = config;
2836
+ }
2837
+ async charge(params) {
2838
+ const body = {
2839
+ amount: params.amount,
2840
+ currency: params.currency.toLowerCase(),
2841
+ source: {
2842
+ object: "card",
2843
+ number: params.cardAlias,
2844
+ // VGS reveals this to Stripe
2845
+ exp_month: params.expMonth,
2846
+ exp_year: params.expYear,
2847
+ cvc: params.cvvAlias
2848
+ },
2849
+ description: params.merchantDescriptor,
2850
+ metadata: params.metadata ?? {}
2851
+ };
2852
+ const response = await this.config.proxy.forward({
2853
+ url: "https://api.stripe.com/v1/charges",
2854
+ method: "POST",
2855
+ headers: {
2856
+ "Authorization": `Bearer ${this.config.secretKey}`,
2857
+ "Stripe-Version": this.config.apiVersion ?? "2024-12-18.acacia",
2858
+ ...params.idempotencyKey ? { "Idempotency-Key": params.idempotencyKey } : {}
2859
+ },
2860
+ body
2861
+ });
2862
+ const data = response.body;
2863
+ const status = data["status"] === "succeeded" ? "succeeded" : data["status"] === "pending" ? "pending" : "failed";
2864
+ return {
2865
+ pspTransactionId: String(data["id"] ?? ""),
2866
+ status,
2867
+ authorizationCode: data["authorization_code"],
2868
+ declineReason: status === "failed" ? String(data["failure_message"] ?? "unknown") : void 0,
2869
+ rawResponse: data
2870
+ };
2871
+ }
2872
+ async refund(params) {
2873
+ const body = {
2874
+ charge: params.pspTransactionId,
2875
+ ...params.amount !== void 0 ? { amount: params.amount } : {},
2876
+ ...params.reason ? { reason: params.reason } : {}
2877
+ };
2878
+ const response = await this.config.proxy.forward({
2879
+ url: "https://api.stripe.com/v1/refunds",
2880
+ method: "POST",
2881
+ headers: {
2882
+ "Authorization": `Bearer ${this.config.secretKey}`,
2883
+ "Stripe-Version": this.config.apiVersion ?? "2024-12-18.acacia",
2884
+ ...params.idempotencyKey ? { "Idempotency-Key": params.idempotencyKey } : {}
2885
+ },
2886
+ body
2887
+ });
2888
+ const data = response.body;
2889
+ const status = data["status"] === "succeeded" ? "succeeded" : data["status"] === "pending" ? "pending" : "failed";
2890
+ return {
2891
+ pspRefundId: String(data["id"] ?? ""),
2892
+ status,
2893
+ amount: Number(data["amount"] ?? 0),
2894
+ rawResponse: data
2895
+ };
2896
+ }
2897
+ async healthCheck() {
2898
+ try {
2899
+ const response = await fetch("https://api.stripe.com/v1/balance", {
2900
+ headers: { "Authorization": `Bearer ${this.config.secretKey}` }
2901
+ });
2902
+ return response.ok;
2903
+ } catch {
2904
+ return false;
2905
+ }
2906
+ }
2907
+ };
2908
+
2909
+ // src/psp/adapters/worldpay.ts
2910
+ var WorldpayAdapter = class {
2911
+ name = "worldpay";
2912
+ config;
2913
+ constructor(config) {
2914
+ this.config = config;
2915
+ }
2916
+ async charge(params) {
2917
+ const body = {
2918
+ transactionType: "sale",
2919
+ amount: {
2920
+ value: params.amount,
2921
+ currencyCode: params.currency.toUpperCase()
2922
+ },
2923
+ paymentInstrument: {
2924
+ type: "card/plain",
2925
+ cardNumber: params.cardAlias,
2926
+ // VGS reveals to Worldpay
2927
+ expiryDate: {
2928
+ month: params.expMonth,
2929
+ year: params.expYear
2930
+ },
2931
+ cvc: params.cvvAlias
2932
+ },
2933
+ merchant: {
2934
+ entity: this.config.merchantId
2935
+ },
2936
+ narrative: {
2937
+ line1: params.merchantDescriptor
2938
+ }
2939
+ };
2940
+ const response = await this.config.proxy.forward({
2941
+ url: `${this.config.baseUrl}/payments/authorizations`,
2942
+ method: "POST",
2943
+ headers: {
2944
+ "Authorization": `Bearer ${this.config.apiKey}`,
2945
+ ...params.idempotencyKey ? { "Idempotency-Key": params.idempotencyKey } : {}
2946
+ },
2947
+ body
2948
+ });
2949
+ const data = response.body;
2950
+ const outcome = data["outcome"];
2951
+ const status = outcome === "approved" ? "succeeded" : outcome === "pending" ? "pending" : "failed";
2952
+ return {
2953
+ pspTransactionId: String(data["_links"] && data["_links"]["self"] || ""),
2954
+ status,
2955
+ authorizationCode: data["authorizationCode"],
2956
+ declineReason: status === "failed" ? String(data["description"] ?? "declined") : void 0,
2957
+ rawResponse: data
2958
+ };
2959
+ }
2960
+ async refund(params) {
2961
+ const body = {
2962
+ ...params.amount !== void 0 ? { value: { amount: params.amount, currencyCode: "USD" } } : {},
2963
+ ...params.reason ? { reference: params.reason } : {}
2964
+ };
2965
+ const response = await this.config.proxy.forward({
2966
+ url: `${this.config.baseUrl}/payments/${params.pspTransactionId}/refunds`,
2967
+ method: "POST",
2968
+ headers: {
2969
+ "Authorization": `Bearer ${this.config.apiKey}`,
2970
+ ...params.idempotencyKey ? { "Idempotency-Key": params.idempotencyKey } : {}
2971
+ },
2972
+ body
2973
+ });
2974
+ const data = response.body;
2975
+ return {
2976
+ pspRefundId: String(data["refundId"] ?? ""),
2977
+ status: "succeeded",
2978
+ amount: params.amount ?? 0,
2979
+ rawResponse: data
2980
+ };
2981
+ }
2982
+ async healthCheck() {
2983
+ try {
2984
+ const response = await fetch(`${this.config.baseUrl}/health`, {
2985
+ headers: { "Authorization": `Bearer ${this.config.apiKey}` }
2986
+ });
2987
+ return response.ok;
2988
+ } catch {
2989
+ return false;
2990
+ }
2991
+ }
2992
+ };
2993
+
2994
+ // src/psp/adapters/cybersource.ts
2995
+ var CyberSourceAdapter = class {
2996
+ name = "cybersource";
2997
+ config;
2998
+ constructor(config) {
2999
+ this.config = config;
3000
+ }
3001
+ async charge(params) {
3002
+ const csParams = params;
3003
+ const paymentInformation = csParams.isNetworkToken ? {
3004
+ tokenizedCard: {
3005
+ number: params.cardAlias,
3006
+ // VGS reveals to CyberSource
3007
+ expirationMonth: String(params.expMonth).padStart(2, "0"),
3008
+ expirationYear: String(params.expYear),
3009
+ cryptogram: csParams.cryptogram,
3010
+ type: "001"
3011
+ // Visa network token
3012
+ }
3013
+ } : {
3014
+ card: {
3015
+ number: params.cardAlias,
3016
+ // VGS reveals to CyberSource
3017
+ expirationMonth: String(params.expMonth).padStart(2, "0"),
3018
+ expirationYear: String(params.expYear),
3019
+ securityCode: params.cvvAlias
3020
+ }
3021
+ };
3022
+ const body = {
3023
+ clientReferenceInformation: {
3024
+ code: params.idempotencyKey ?? `lane_${Date.now()}`
3025
+ },
3026
+ processingInformation: {
3027
+ capture: true,
3028
+ ...csParams.isNetworkToken ? { paymentSolution: "015" } : {},
3029
+ ...csParams.eci ? { commerceIndicator: csParams.eci } : {}
3030
+ },
3031
+ orderInformation: {
3032
+ amountDetails: {
3033
+ totalAmount: (params.amount / 100).toFixed(2),
3034
+ currency: params.currency.toUpperCase()
3035
+ }
3036
+ },
3037
+ paymentInformation
3038
+ };
3039
+ const response = await this.config.proxy.forward({
3040
+ url: `${this.config.baseUrl}/pts/v2/payments`,
3041
+ method: "POST",
3042
+ headers: {
3043
+ "v-c-merchant-id": this.config.merchantId,
3044
+ "Content-Type": "application/json"
3045
+ },
3046
+ body
3047
+ });
3048
+ const data = response.body;
3049
+ const csStatus = data["status"];
3050
+ const status = csStatus === "AUTHORIZED" || csStatus === "PENDING" ? csStatus === "AUTHORIZED" ? "succeeded" : "pending" : "failed";
3051
+ return {
3052
+ pspTransactionId: String(data["id"] ?? ""),
3053
+ status,
3054
+ authorizationCode: data["processorInformation"]?.["approvalCode"],
3055
+ declineReason: status === "failed" ? String(data["errorInformation"]?.["message"] ?? "declined") : void 0,
3056
+ rawResponse: data
3057
+ };
3058
+ }
3059
+ async refund(params) {
3060
+ const body = {
3061
+ clientReferenceInformation: {
3062
+ code: params.idempotencyKey ?? `lane_ref_${Date.now()}`
3063
+ },
3064
+ orderInformation: {
3065
+ amountDetails: {
3066
+ ...params.amount !== void 0 ? { totalAmount: (params.amount / 100).toFixed(2) } : {},
3067
+ currency: "USD"
3068
+ }
3069
+ }
3070
+ };
3071
+ const response = await this.config.proxy.forward({
3072
+ url: `${this.config.baseUrl}/pts/v2/payments/${params.pspTransactionId}/refunds`,
3073
+ method: "POST",
3074
+ headers: {
3075
+ "v-c-merchant-id": this.config.merchantId,
3076
+ "Content-Type": "application/json"
3077
+ },
3078
+ body
3079
+ });
3080
+ const data = response.body;
3081
+ const csStatus = data["status"];
3082
+ return {
3083
+ pspRefundId: String(data["id"] ?? ""),
3084
+ status: csStatus === "PENDING" ? "pending" : "succeeded",
3085
+ amount: params.amount ?? 0,
3086
+ rawResponse: data
3087
+ };
3088
+ }
3089
+ async healthCheck() {
3090
+ try {
3091
+ const response = await fetch(`${this.config.baseUrl}/reporting/v3/report-definitions`, {
3092
+ headers: { "v-c-merchant-id": this.config.merchantId }
3093
+ });
3094
+ return response.status !== 500;
3095
+ } catch {
3096
+ return false;
3097
+ }
3098
+ }
3099
+ };
3100
+
3101
+ // src/routing/engine.ts
3102
+ var MerchantRegistry = class _MerchantRegistry {
3103
+ merchants = /* @__PURE__ */ new Map();
3104
+ register(capabilities) {
3105
+ this.merchants.set(capabilities.merchantId, capabilities);
3106
+ }
3107
+ get(merchantId) {
3108
+ return this.merchants.get(merchantId);
3109
+ }
3110
+ registerBatch(merchants) {
3111
+ for (const m of merchants) {
3112
+ this.merchants.set(m.merchantId, m);
3113
+ }
3114
+ }
3115
+ /** Create a registry pre-populated from directory capabilities. */
3116
+ static fromDirectory(capabilities) {
3117
+ const registry = new _MerchantRegistry();
3118
+ registry.registerBatch(capabilities);
3119
+ return registry;
3120
+ }
3121
+ /** Clear and repopulate with new capabilities. */
3122
+ reload(capabilities) {
3123
+ this.merchants.clear();
3124
+ this.registerBatch(capabilities);
3125
+ }
3126
+ /** Number of registered merchants. */
3127
+ get size() {
3128
+ return this.merchants.size;
3129
+ }
3130
+ /** List all registered merchant IDs. */
3131
+ listIds() {
3132
+ return Array.from(this.merchants.keys());
3133
+ }
3134
+ };
3135
+ var PaymentRouter = class {
3136
+ constructor(registry) {
3137
+ this.registry = registry;
3138
+ }
3139
+ /**
3140
+ * Decide the payment route for a transaction.
3141
+ */
3142
+ route(context) {
3143
+ const merchant = this.registry.get(context.merchantId);
3144
+ const fallbacks = [];
3145
+ if (context.budget && context.currentSpend !== void 0) {
3146
+ const limit = context.budget.perTaskLimit ?? context.budget.dailyLimit;
3147
+ if (limit !== void 0 && context.amount > limit - context.currentSpend) {
3148
+ return {
3149
+ route: "fallback",
3150
+ merchantId: context.merchantId,
3151
+ reason: "Transaction would exceed budget limits.",
3152
+ fallbacks: []
3153
+ };
3154
+ }
3155
+ }
3156
+ if (merchant?.hasBillingAPI) {
3157
+ fallbacks.push("acp", "vic", "fallback");
3158
+ return {
3159
+ route: "billing_api",
3160
+ merchantId: context.merchantId,
3161
+ reason: "Merchant has direct billing API integration (cheapest path).",
3162
+ fallbacks: this.filterFallbacks(fallbacks, merchant, context)
3163
+ };
3164
+ }
3165
+ if (merchant?.supportsACP) {
3166
+ fallbacks.push("vic", "fallback");
3167
+ return {
3168
+ route: "acp",
3169
+ merchantId: context.merchantId,
3170
+ reason: "Merchant supports Agent Commerce Protocol checkout.",
3171
+ fallbacks: this.filterFallbacks(fallbacks, merchant, context)
3172
+ };
3173
+ }
3174
+ if (context.hasVICToken && context.cardBrand === "visa" && (merchant?.acceptsVisa ?? true)) {
3175
+ fallbacks.push("fallback");
3176
+ return {
3177
+ route: "vic",
3178
+ merchantId: context.merchantId,
3179
+ reason: "Using VIC agentic token for Visa-authenticated payment.",
3180
+ fallbacks
3181
+ };
3182
+ }
3183
+ return {
3184
+ route: "fallback",
3185
+ merchantId: context.merchantId,
3186
+ reason: merchant ? "No preferred route available; using standard card payment." : "Unknown merchant; using standard card payment.",
3187
+ fallbacks: []
3188
+ };
3189
+ }
3190
+ filterFallbacks(routes, merchant, context) {
3191
+ return routes.filter((r) => {
3192
+ if (r === "acp") return merchant.supportsACP;
3193
+ if (r === "vic") return context.hasVICToken && context.cardBrand === "visa";
3194
+ return true;
3195
+ });
3196
+ }
3197
+ };
3198
+
3199
+ // src/index.ts
3200
+ var index_default = Lane;
3201
+
3202
+ exports.Admin = Admin;
3203
+ exports.Agents = Agents;
3204
+ exports.Audit = Audit;
3205
+ exports.Auth = Auth;
3206
+ exports.Checkout = Checkout;
3207
+ exports.CyberSourceAdapter = CyberSourceAdapter;
3208
+ exports.FileTokenStore = FileTokenStore;
3209
+ exports.HMACSignature = HMACSignature;
3210
+ exports.Identity = Identity;
3211
+ exports.Lane = Lane;
3212
+ exports.LaneAuthError = LaneAuthError;
3213
+ exports.LaneBudgetError = LaneBudgetError;
3214
+ exports.LaneClient = LaneClient;
3215
+ exports.LaneError = LaneError;
3216
+ exports.LaneMCPServer = LaneMCPServer;
3217
+ exports.LaneNotFoundError = LaneNotFoundError;
3218
+ exports.LanePaymentError = LanePaymentError;
3219
+ exports.LaneRateLimitError = LaneRateLimitError;
3220
+ exports.LaneValidationError = LaneValidationError;
3221
+ exports.MerchantRegistry = MerchantRegistry;
3222
+ exports.Merchants = Merchants;
3223
+ exports.Metering = Metering;
3224
+ exports.PSPRegistry = PSPRegistry;
3225
+ exports.Pay = Pay;
3226
+ exports.PaymentRouter = PaymentRouter;
3227
+ exports.Payouts = Payouts;
3228
+ exports.Products = Products;
3229
+ exports.Resource = Resource;
3230
+ exports.Sell = Sell;
3231
+ exports.StripeAdapter = StripeAdapter;
3232
+ exports.Subscriptions = Subscriptions;
3233
+ exports.Teams = Teams;
3234
+ exports.VGSOutboundProxy = VGSOutboundProxy;
3235
+ exports.VGSTokenManager = VGSTokenManager;
3236
+ exports.VIC = VIC;
3237
+ exports.Wallets = Wallets;
3238
+ exports.Webhooks = Webhooks;
3239
+ exports.WorldpayAdapter = WorldpayAdapter;
3240
+ exports.createErrorFromResponse = createErrorFromResponse;
3241
+ exports.default = index_default;
3242
+ exports.detectCardBrand = detectCardBrand;
3243
+ exports.generateCollectPage = generateCollectPage;
3244
+ exports.luhnCheck = luhnCheck;
3245
+ exports.maskCardNumber = maskCardNumber;
3246
+ exports.resolveConfig = resolveConfig;
3247
+ exports.verifyWebhookSignature = verifyWebhookSignature;
3248
+ //# sourceMappingURL=index.cjs.map
3249
+ //# sourceMappingURL=index.cjs.map