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/README.md +1061 -0
- package/dist/cli/index.js +7413 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +3249 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1985 -0
- package/dist/index.d.ts +1985 -0
- package/dist/index.js +3200 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.js +2610 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server-http.cjs +2378 -0
- package/dist/server-http.cjs.map +1 -0
- package/dist/server-http.js +2376 -0
- package/dist/server-http.js.map +1 -0
- package/dist/server-stdio.cjs +2356 -0
- package/dist/server-stdio.cjs.map +1 -0
- package/dist/server-stdio.js +2354 -0
- package/dist/server-stdio.js.map +1 -0
- package/package.json +68 -0
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">✓</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
|