lane-sdk 0.2.2 → 0.2.8

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.

Potentially problematic release.


This version of lane-sdk might be problematic. Click here for more details.

Files changed (40) hide show
  1. package/SKILL.md +24 -0
  2. package/dist/adapters/crewai/index.cjs +36 -4
  3. package/dist/adapters/crewai/index.cjs.map +1 -1
  4. package/dist/adapters/crewai/index.js +36 -4
  5. package/dist/adapters/crewai/index.js.map +1 -1
  6. package/dist/adapters/langchain/index.cjs +36 -4
  7. package/dist/adapters/langchain/index.cjs.map +1 -1
  8. package/dist/adapters/langchain/index.js +36 -4
  9. package/dist/adapters/langchain/index.js.map +1 -1
  10. package/dist/adapters/openai/index.cjs +36 -4
  11. package/dist/adapters/openai/index.cjs.map +1 -1
  12. package/dist/adapters/openai/index.js +36 -4
  13. package/dist/adapters/openai/index.js.map +1 -1
  14. package/dist/adapters/vercel-ai/index.cjs +36 -4
  15. package/dist/adapters/vercel-ai/index.cjs.map +1 -1
  16. package/dist/adapters/vercel-ai/index.js +36 -4
  17. package/dist/adapters/vercel-ai/index.js.map +1 -1
  18. package/dist/cli/index.js +214 -40
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/index.cjs +36 -4
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.js +36 -4
  23. package/dist/index.js.map +1 -1
  24. package/dist/server/routes/export.cjs +2 -4
  25. package/dist/server/routes/export.cjs.map +1 -1
  26. package/dist/server/routes/export.js +2 -4
  27. package/dist/server/routes/export.js.map +1 -1
  28. package/dist/server/vic-demo.js +1052 -0
  29. package/dist/server/vic-demo.js.map +1 -0
  30. package/dist/server-http.cjs +36 -4
  31. package/dist/server-http.cjs.map +1 -1
  32. package/dist/server-http.js +36 -4
  33. package/dist/server-http.js.map +1 -1
  34. package/dist/server-stdio.cjs +36 -4
  35. package/dist/server-stdio.cjs.map +1 -1
  36. package/dist/server-stdio.js +36 -4
  37. package/dist/server-stdio.js.map +1 -1
  38. package/package.json +6 -3
  39. package/dist/server/routes/export.d.cts +0 -10
  40. package/dist/server/routes/export.d.ts +0 -10
@@ -0,0 +1,1052 @@
1
+ // server/vic-demo/index.ts
2
+ import "dotenv/config";
3
+ import crypto3 from "crypto";
4
+ import express from "express";
5
+
6
+ // server/vic-demo/routes.ts
7
+ import crypto2 from "crypto";
8
+ import { Router } from "express";
9
+
10
+ // server/vic-demo/helpers.ts
11
+ import crypto from "crypto";
12
+ function generateDPAN() {
13
+ const prefix = "4";
14
+ let pan = prefix;
15
+ for (let i = 1; i < 15; i++) {
16
+ pan += Math.floor(Math.random() * 10).toString();
17
+ }
18
+ let sum = 0;
19
+ for (let i = 0; i < pan.length; i++) {
20
+ let digit = parseInt(pan.charAt(pan.length - 1 - i), 10);
21
+ if (i % 2 === 0) {
22
+ digit *= 2;
23
+ if (digit > 9) digit -= 9;
24
+ }
25
+ sum += digit;
26
+ }
27
+ pan += ((10 - sum % 10) % 10).toString();
28
+ return pan;
29
+ }
30
+ function generateCryptogram() {
31
+ return crypto.randomBytes(20).toString("base64");
32
+ }
33
+ function parseExpiry(expiresIn) {
34
+ const match = expiresIn.match(/^(\d+)(h|d|m)$/);
35
+ if (!match || !match[1] || !match[2]) {
36
+ return new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString();
37
+ }
38
+ const value = parseInt(match[1], 10);
39
+ const unit = match[2];
40
+ let ms;
41
+ switch (unit) {
42
+ case "h":
43
+ ms = value * 60 * 60 * 1e3;
44
+ break;
45
+ case "d":
46
+ ms = value * 24 * 60 * 60 * 1e3;
47
+ break;
48
+ case "m":
49
+ ms = value * 60 * 1e3;
50
+ break;
51
+ default:
52
+ ms = 24 * 60 * 60 * 1e3;
53
+ }
54
+ return new Date(Date.now() + ms).toISOString();
55
+ }
56
+ function generateId(prefix) {
57
+ return `${prefix}_${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
58
+ }
59
+
60
+ // server/vic-demo/routes.ts
61
+ var tokens = /* @__PURE__ */ new Map();
62
+ var intents = /* @__PURE__ */ new Map();
63
+ var wallets = /* @__PURE__ */ new Map();
64
+ var cards = /* @__PURE__ */ new Map();
65
+ var instructions = /* @__PURE__ */ new Map();
66
+ var mandates = /* @__PURE__ */ new Map();
67
+ var confirmations = /* @__PURE__ */ new Map();
68
+ var credentials = /* @__PURE__ */ new Map();
69
+ var enrolledCards = /* @__PURE__ */ new Map();
70
+ var idempotencyCache = /* @__PURE__ */ new Map();
71
+ function toVICTokenResponse(token) {
72
+ return {
73
+ id: token.id,
74
+ walletId: token.walletId,
75
+ networkTokenId: token.networkTokenId,
76
+ last4: token.last4,
77
+ brand: token.brand,
78
+ status: token.status,
79
+ expiresAt: token.expiresAt,
80
+ createdAt: token.createdAt
81
+ };
82
+ }
83
+ function checkIdempotency(req, res) {
84
+ const key = req.headers["x-idempotency-key"];
85
+ if (!key) return false;
86
+ const cached = idempotencyCache.get(key);
87
+ if (cached) {
88
+ res.status(cached.statusCode).json(cached.body);
89
+ return true;
90
+ }
91
+ return false;
92
+ }
93
+ function cacheIdempotent(req, statusCode, body) {
94
+ const key = req.headers["x-idempotency-key"];
95
+ if (key) {
96
+ idempotencyCache.set(key, { statusCode, body, createdAt: Date.now() });
97
+ }
98
+ }
99
+ function createVICRoutes() {
100
+ const router = Router();
101
+ router.get("/intents", (_req, res) => {
102
+ const results = Array.from(intents.values());
103
+ const limit = Math.min(parseInt(_req.query.limit) || 20, 100);
104
+ const offset = parseInt(_req.query.offset) || 0;
105
+ let filtered = results;
106
+ if (_req.query.tokenId) {
107
+ filtered = filtered.filter((i) => i.tokenId === _req.query.tokenId);
108
+ }
109
+ if (_req.query.status) {
110
+ filtered = filtered.filter((i) => i.status === _req.query.status);
111
+ }
112
+ res.json({
113
+ data: filtered.slice(offset, offset + limit),
114
+ total: filtered.length,
115
+ limit,
116
+ offset
117
+ });
118
+ });
119
+ router.put("/intents/:intentId", (req, res) => {
120
+ const intent = intents.get(req.params.intentId);
121
+ if (!intent) {
122
+ res.status(404).json({ error: { code: "not_found", message: "Purchase intent not found" } });
123
+ return;
124
+ }
125
+ if (intent.status === "cancelled" || intent.status === "confirmed") {
126
+ res.status(400).json({
127
+ error: { code: "invalid_state", message: `Cannot update intent in status: ${intent.status}` }
128
+ });
129
+ return;
130
+ }
131
+ if (req.body.amount !== void 0) intent.amount = req.body.amount;
132
+ if (req.body.currency !== void 0) intent.currency = req.body.currency;
133
+ if (req.body.merchantId !== void 0) intent.merchantId = req.body.merchantId;
134
+ if (req.body.merchantName !== void 0) intent.merchantName = req.body.merchantName;
135
+ if (req.body.description !== void 0) intent.description = req.body.description;
136
+ if (req.body.mandates !== void 0) intent.mandates = req.body.mandates;
137
+ intent.status = "updated";
138
+ intent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
139
+ console.log(`[VIC Demo] Intent updated: ${intent.id}`);
140
+ res.json({ intentId: intent.id });
141
+ });
142
+ router.delete("/intents/:intentId", (req, res) => {
143
+ const intent = intents.get(req.params.intentId);
144
+ if (!intent) {
145
+ res.status(404).json({ error: { code: "not_found", message: "Purchase intent not found" } });
146
+ return;
147
+ }
148
+ if (intent.status === "confirmed") {
149
+ res.status(400).json({
150
+ error: { code: "invalid_state", message: "Cannot cancel a confirmed intent" }
151
+ });
152
+ return;
153
+ }
154
+ intent.status = "cancelled";
155
+ intent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
156
+ console.log(`[VIC Demo] Intent cancelled: ${intent.id}`);
157
+ res.json({ cancelled: true, intentId: intent.id });
158
+ });
159
+ router.post("/intents/:intentId/confirm", (req, res) => {
160
+ const intent = intents.get(req.params.intentId);
161
+ if (!intent) {
162
+ res.status(404).json({ error: { code: "not_found", message: "Purchase intent not found" } });
163
+ return;
164
+ }
165
+ if (intent.status === "cancelled") {
166
+ res.status(400).json({
167
+ error: { code: "invalid_state", message: "Cannot confirm a cancelled intent" }
168
+ });
169
+ return;
170
+ }
171
+ intent.status = "confirmed";
172
+ intent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
173
+ console.log(`[VIC Demo] Transaction confirmed: ${intent.id} (txn: ${req.body.transactionId}, status: ${req.body.status})`);
174
+ res.json({
175
+ confirmed: true,
176
+ intentId: intent.id,
177
+ transactionId: req.body.transactionId,
178
+ confirmedAt: intent.updatedAt
179
+ });
180
+ });
181
+ router.post("/intents/:intentId/credentials", (req, res) => {
182
+ const intent = intents.get(req.params.intentId);
183
+ if (!intent) {
184
+ res.status(404).json({ error: { code: "not_found", message: "Purchase intent not found" } });
185
+ return;
186
+ }
187
+ if (intent.status === "cancelled") {
188
+ res.status(400).json({
189
+ error: { code: "invalid_state", message: "Cannot retrieve credentials for a cancelled intent" }
190
+ });
191
+ return;
192
+ }
193
+ const token = tokens.get(intent.tokenId);
194
+ const card = token ? Array.from(cards.values()).find((c) => c.walletId === token.walletId) : null;
195
+ const cryptogram = generateCryptogram();
196
+ intent.status = "credentials_retrieved";
197
+ intent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
198
+ console.log(`[VIC Demo] Credentials retrieved for intent: ${intent.id}`);
199
+ res.json({
200
+ tokenNumber: intent.networkTokenId,
201
+ cryptogram,
202
+ eci: "05",
203
+ expMonth: card?.expMonth ?? 12,
204
+ expYear: card?.expYear ?? 2030
205
+ });
206
+ });
207
+ router.post("/", (req, res) => {
208
+ if (checkIdempotency(req, res)) return;
209
+ const { walletId, cardId, maxTransactionAmount, allowedMCCs, expiresIn } = req.body;
210
+ if (!walletId || !cardId) {
211
+ res.status(400).json({
212
+ error: { code: "validation_error", message: "walletId and cardId are required" }
213
+ });
214
+ return;
215
+ }
216
+ const wallet = wallets.get(walletId);
217
+ if (!wallet) {
218
+ res.status(404).json({ error: { code: "not_found", message: `Wallet ${walletId} not found` } });
219
+ return;
220
+ }
221
+ const card = cards.get(cardId);
222
+ if (!card || card.walletId !== walletId) {
223
+ res.status(404).json({ error: { code: "not_found", message: `Card ${cardId} not found in wallet` } });
224
+ return;
225
+ }
226
+ if (card.status !== "active") {
227
+ res.status(400).json({
228
+ error: { code: "card_not_active", message: `Card ${cardId} is ${card.status}` }
229
+ });
230
+ return;
231
+ }
232
+ const tokenId = generateId("vic_tok");
233
+ const dpan = generateDPAN();
234
+ const now = (/* @__PURE__ */ new Date()).toISOString();
235
+ const expiresAt = parseExpiry(expiresIn ?? "24h");
236
+ const token = {
237
+ id: tokenId,
238
+ walletId,
239
+ cardId,
240
+ networkTokenId: dpan,
241
+ last4: card.last4,
242
+ brand: "visa",
243
+ status: "active",
244
+ maxTransactionAmount,
245
+ allowedMCCs,
246
+ expiresAt,
247
+ createdAt: now
248
+ };
249
+ tokens.set(tokenId, token);
250
+ const intentId = crypto2.randomUUID();
251
+ intents.set(intentId, {
252
+ id: intentId,
253
+ tokenId,
254
+ networkTokenId: dpan,
255
+ amount: maxTransactionAmount ?? 0,
256
+ currency: "USD",
257
+ merchantId: null,
258
+ merchantName: null,
259
+ description: null,
260
+ mandates: allowedMCCs ? [{
261
+ mandateId: crypto2.randomUUID(),
262
+ declineThreshold: { amount: String(maxTransactionAmount ?? 0), currencyCode: "USD" },
263
+ effectiveUntilTime: expiresAt,
264
+ description: `MCC-restricted: ${allowedMCCs.join(", ")}`
265
+ }] : [],
266
+ status: "created",
267
+ createdAt: now,
268
+ updatedAt: now
269
+ });
270
+ console.log(`[VIC Demo] Token issued: ${tokenId} \u2192 DPAN ***${dpan.slice(-4)} (intent: ${intentId})`);
271
+ const body = toVICTokenResponse(token);
272
+ cacheIdempotent(req, 201, body);
273
+ res.status(201).json(body);
274
+ });
275
+ router.get("/", (req, res) => {
276
+ let results = Array.from(tokens.values());
277
+ if (req.query.walletId) {
278
+ results = results.filter((t) => t.walletId === req.query.walletId);
279
+ }
280
+ if (req.query.status) {
281
+ results = results.filter((t) => t.status === req.query.status);
282
+ }
283
+ if (req.query.cardId) {
284
+ results = results.filter((t) => t.cardId === req.query.cardId);
285
+ }
286
+ const limit = Math.min(parseInt(req.query.limit) || 20, 100);
287
+ const offset = parseInt(req.query.offset) || 0;
288
+ const total = results.length;
289
+ const page = results.slice(offset, offset + limit);
290
+ res.json({
291
+ data: page.map(toVICTokenResponse),
292
+ total,
293
+ limit,
294
+ offset
295
+ });
296
+ });
297
+ router.get("/:id", (req, res) => {
298
+ const token = tokens.get(req.params.id);
299
+ if (!token) {
300
+ res.status(404).json({ error: { code: "not_found", message: "Token not found" } });
301
+ return;
302
+ }
303
+ if (token.status === "active" && new Date(token.expiresAt) < /* @__PURE__ */ new Date()) {
304
+ token.status = "expired";
305
+ }
306
+ res.json(toVICTokenResponse(token));
307
+ });
308
+ router.post("/:id/cryptogram", (req, res) => {
309
+ const token = tokens.get(req.params.id);
310
+ if (!token) {
311
+ res.status(404).json({ error: { code: "not_found", message: "Token not found" } });
312
+ return;
313
+ }
314
+ if (token.status === "active" && new Date(token.expiresAt) < /* @__PURE__ */ new Date()) {
315
+ token.status = "expired";
316
+ }
317
+ if (token.status !== "active") {
318
+ res.status(400).json({
319
+ error: { code: "token_not_active", message: `Token is ${token.status}` }
320
+ });
321
+ return;
322
+ }
323
+ if (token.maxTransactionAmount && req.body.amount > token.maxTransactionAmount) {
324
+ res.status(400).json({
325
+ error: {
326
+ code: "amount_exceeded",
327
+ message: `Amount ${req.body.amount} exceeds token max ${token.maxTransactionAmount}`
328
+ }
329
+ });
330
+ return;
331
+ }
332
+ const card = cards.get(token.cardId) ?? Array.from(cards.values()).find((c) => c.walletId === token.walletId);
333
+ const cryptogram = generateCryptogram();
334
+ const intentId = crypto2.randomUUID();
335
+ const now = (/* @__PURE__ */ new Date()).toISOString();
336
+ intents.set(intentId, {
337
+ id: intentId,
338
+ tokenId: token.id,
339
+ networkTokenId: token.networkTokenId,
340
+ amount: req.body.amount ?? 0,
341
+ currency: req.body.currency ?? "USD",
342
+ merchantId: req.body.merchantId ?? null,
343
+ merchantName: null,
344
+ description: null,
345
+ mandates: [],
346
+ status: "credentials_retrieved",
347
+ createdAt: now,
348
+ updatedAt: now
349
+ });
350
+ console.log(`[VIC Demo] Credentials retrieved for token: ${token.id} (intent: ${intentId})`);
351
+ res.json({
352
+ tokenNumber: token.networkTokenId,
353
+ cryptogram,
354
+ eci: "05",
355
+ expMonth: card?.expMonth ?? 12,
356
+ expYear: card?.expYear ?? 2030
357
+ });
358
+ });
359
+ router.post("/:id/revoke", (req, res) => {
360
+ const token = tokens.get(req.params.id);
361
+ if (!token) {
362
+ res.status(404).json({ error: { code: "not_found", message: "Token not found" } });
363
+ return;
364
+ }
365
+ if (token.status === "revoked") {
366
+ res.status(400).json({
367
+ error: { code: "invalid_state", message: "Token is already revoked" }
368
+ });
369
+ return;
370
+ }
371
+ token.status = "revoked";
372
+ console.log(`[VIC Demo] Token revoked: ${token.id}`);
373
+ res.json(toVICTokenResponse(token));
374
+ });
375
+ router.post("/:id/suspend", (req, res) => {
376
+ const token = tokens.get(req.params.id);
377
+ if (!token) {
378
+ res.status(404).json({ error: { code: "not_found", message: "Token not found" } });
379
+ return;
380
+ }
381
+ if (token.status !== "active") {
382
+ res.status(400).json({
383
+ error: { code: "invalid_state", message: `Cannot suspend token in status: ${token.status}` }
384
+ });
385
+ return;
386
+ }
387
+ token.status = "suspended";
388
+ console.log(`[VIC Demo] Token suspended: ${token.id}`);
389
+ res.json(toVICTokenResponse(token));
390
+ });
391
+ router.post("/:id/reactivate", (req, res) => {
392
+ const token = tokens.get(req.params.id);
393
+ if (!token) {
394
+ res.status(404).json({ error: { code: "not_found", message: "Token not found" } });
395
+ return;
396
+ }
397
+ if (token.status !== "suspended") {
398
+ res.status(400).json({
399
+ error: { code: "invalid_state", message: `Cannot reactivate token in status: ${token.status}` }
400
+ });
401
+ return;
402
+ }
403
+ token.status = "active";
404
+ console.log(`[VIC Demo] Token reactivated: ${token.id}`);
405
+ res.json(toVICTokenResponse(token));
406
+ });
407
+ return router;
408
+ }
409
+ function createInstructionRoutes() {
410
+ const router = Router();
411
+ router.post("/", (req, res) => {
412
+ if (checkIdempotency(req, res)) return;
413
+ const now = (/* @__PURE__ */ new Date()).toISOString();
414
+ const id = generateId("instr");
415
+ const instruction = {
416
+ id,
417
+ walletId: req.body.walletId,
418
+ type: req.body.type ?? "payment",
419
+ amount: req.body.amount,
420
+ currency: req.body.currency ?? "USD",
421
+ merchant: req.body.merchant,
422
+ permissions: req.body.permissions ?? ["pay"],
423
+ constraints: req.body.constraints ?? {},
424
+ status: "pending_confirmation",
425
+ spentCents: 0,
426
+ remainingCents: req.body.amount ?? null,
427
+ expiresAt: req.body.expiresIn ? parseExpiry(req.body.expiresIn) : void 0,
428
+ createdAt: now
429
+ };
430
+ instructions.set(id, instruction);
431
+ confirmations.set(id, []);
432
+ console.log(`[VIC Demo] Instruction created: ${id} (amount: ${instruction.amount}, merchant: ${instruction.merchant})`);
433
+ const body = { ...instruction };
434
+ cacheIdempotent(req, 201, body);
435
+ res.status(201).json(body);
436
+ });
437
+ router.get("/", (_req, res) => {
438
+ const results = Array.from(instructions.values());
439
+ const limit = Math.min(parseInt(_req.query.limit) || 20, 100);
440
+ const offset = parseInt(_req.query.offset) || 0;
441
+ res.json({
442
+ data: results.slice(offset, offset + limit),
443
+ total: results.length,
444
+ limit,
445
+ offset
446
+ });
447
+ });
448
+ router.get("/:id", (req, res) => {
449
+ const instruction = instructions.get(req.params.id);
450
+ if (!instruction) {
451
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
452
+ return;
453
+ }
454
+ res.json(instruction);
455
+ });
456
+ router.put("/:id", (req, res) => {
457
+ const instruction = instructions.get(req.params.id);
458
+ if (!instruction) {
459
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
460
+ return;
461
+ }
462
+ if (req.body.amount !== void 0) instruction.amount = req.body.amount;
463
+ if (req.body.currency !== void 0) instruction.currency = req.body.currency;
464
+ if (req.body.merchant !== void 0) instruction.merchant = req.body.merchant;
465
+ if (req.body.permissions !== void 0) instruction.permissions = req.body.permissions;
466
+ if (req.body.constraints !== void 0) instruction.constraints = req.body.constraints;
467
+ res.json(instruction);
468
+ });
469
+ router.post("/:id/cancel", (req, res) => {
470
+ const instruction = instructions.get(req.params.id);
471
+ if (!instruction) {
472
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
473
+ return;
474
+ }
475
+ instruction.status = "cancelled";
476
+ console.log(`[VIC Demo] Instruction cancelled: ${instruction.id}`);
477
+ res.json(instruction);
478
+ });
479
+ router.post("/:id/mandate", (req, res) => {
480
+ const instruction = instructions.get(req.params.id);
481
+ if (!instruction) {
482
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
483
+ return;
484
+ }
485
+ const mandate = {
486
+ id: generateId("mdt"),
487
+ instructionId: instruction.id,
488
+ type: req.body.type ?? "spending_limit",
489
+ config: req.body.config ?? {},
490
+ status: "active",
491
+ spentCents: 0,
492
+ remainingCents: req.body.config?.maxAmount ?? 0,
493
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
494
+ };
495
+ mandates.set(mandate.id, mandate);
496
+ res.status(201).json(mandate);
497
+ });
498
+ router.get("/:id/mandate", (req, res) => {
499
+ const instruction = instructions.get(req.params.id);
500
+ if (!instruction) {
501
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
502
+ return;
503
+ }
504
+ const result = Array.from(mandates.values()).filter((m) => m.instructionId === instruction.id);
505
+ res.json(result);
506
+ });
507
+ router.get("/:id/mandate/:mandateId", (req, res) => {
508
+ const mandate = mandates.get(req.params.mandateId);
509
+ if (!mandate || mandate.instructionId !== req.params.id) {
510
+ res.status(404).json({ error: { code: "not_found", message: "Mandate not found" } });
511
+ return;
512
+ }
513
+ res.json(mandate);
514
+ });
515
+ router.put("/:id/mandate/:mandateId", (req, res) => {
516
+ const mandate = mandates.get(req.params.mandateId);
517
+ if (!mandate || mandate.instructionId !== req.params.id) {
518
+ res.status(404).json({ error: { code: "not_found", message: "Mandate not found" } });
519
+ return;
520
+ }
521
+ if (req.body.type !== void 0) mandate.type = req.body.type;
522
+ if (req.body.config !== void 0) mandate.config = req.body.config;
523
+ res.json(mandate);
524
+ });
525
+ router.delete("/:id/mandate/:mandateId", (req, res) => {
526
+ const mandate = mandates.get(req.params.mandateId);
527
+ if (!mandate || mandate.instructionId !== req.params.id) {
528
+ res.status(404).json({ error: { code: "not_found", message: "Mandate not found" } });
529
+ return;
530
+ }
531
+ mandates.delete(mandate.id);
532
+ res.json({ deleted: true });
533
+ });
534
+ router.get("/:id/confirmation", (req, res) => {
535
+ const instruction = instructions.get(req.params.id);
536
+ if (!instruction) {
537
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
538
+ return;
539
+ }
540
+ const result = confirmations.get(instruction.id) ?? [];
541
+ res.json(result);
542
+ });
543
+ router.post("/:id/confirmation", (req, res) => {
544
+ const instruction = instructions.get(req.params.id);
545
+ if (!instruction) {
546
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
547
+ return;
548
+ }
549
+ const confirmation = {
550
+ id: generateId("conf"),
551
+ instructionId: instruction.id,
552
+ confirmedBy: req.body.confirmedBy,
553
+ confirmationType: req.body.confirmationType ?? "biometric",
554
+ details: {
555
+ method: req.body.method ?? "touchId",
556
+ timestamp: req.body.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
557
+ machineId: req.body.machineId,
558
+ ...req.body
559
+ },
560
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
561
+ };
562
+ const existing = confirmations.get(instruction.id) ?? [];
563
+ existing.push(confirmation);
564
+ confirmations.set(instruction.id, existing);
565
+ instruction.status = "confirmed";
566
+ console.log(`[VIC Demo] Confirmation created for instruction: ${instruction.id} (type: ${confirmation.confirmationType})`);
567
+ res.status(201).json(confirmation);
568
+ });
569
+ router.post("/:id/credential", (req, res) => {
570
+ const instruction = instructions.get(req.params.id);
571
+ if (!instruction) {
572
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
573
+ return;
574
+ }
575
+ const credential = {
576
+ id: generateId("cred"),
577
+ instructionId: instruction.id,
578
+ credentialType: "vic_dpan",
579
+ credentialData: {
580
+ tokenNumber: generateDPAN(),
581
+ cryptogram: generateCryptogram(),
582
+ eci: "05",
583
+ expMonth: 12,
584
+ expYear: 2030
585
+ },
586
+ expiresAt: instruction.expiresAt,
587
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
588
+ };
589
+ credentials.set(credential.id, credential);
590
+ console.log(`[VIC Demo] Credential issued for instruction: ${instruction.id}`);
591
+ res.status(201).json(credential);
592
+ });
593
+ router.get("/:id/budget", (req, res) => {
594
+ const instruction = instructions.get(req.params.id);
595
+ if (!instruction) {
596
+ res.status(404).json({ error: { code: "not_found", message: "Instruction not found" } });
597
+ return;
598
+ }
599
+ const instructionMandates = Array.from(mandates.values()).filter((m) => m.instructionId === instruction.id);
600
+ res.json({
601
+ amountCents: instruction.amount ?? null,
602
+ spentCents: instruction.spentCents,
603
+ remainingCents: instruction.remainingCents ?? (instruction.amount ? instruction.amount - instruction.spentCents : 0),
604
+ mandateCount: instructionMandates.length,
605
+ completedMandates: instructionMandates.filter((m) => m.status === "completed").length,
606
+ mandates: instructionMandates.map((m) => ({
607
+ id: m.id,
608
+ name: m.type,
609
+ spentCents: m.spentCents,
610
+ remainingCents: m.remainingCents,
611
+ maxAmountCents: m.config.maxAmount ?? null,
612
+ status: m.status
613
+ }))
614
+ });
615
+ });
616
+ return router;
617
+ }
618
+ function createVICFlowRoutes() {
619
+ const router = Router();
620
+ router.post("/enroll", (req, res) => {
621
+ const { walletId, cardId } = req.body;
622
+ if (!walletId || !cardId) {
623
+ res.status(400).json({ error: { code: "validation_error", message: "walletId and cardId are required" } });
624
+ return;
625
+ }
626
+ const card = cards.get(cardId);
627
+ if (!card || card.walletId !== walletId) {
628
+ res.status(404).json({ error: { code: "not_found", message: "Card not found in wallet" } });
629
+ return;
630
+ }
631
+ const existing = Array.from(enrolledCards.values()).find(
632
+ (e) => e.instrumentIdentifierId === cardId && e.status === "ACTIVE"
633
+ );
634
+ if (existing) {
635
+ res.json({ data: existing });
636
+ return;
637
+ }
638
+ const dpan = generateDPAN();
639
+ const enrolled = {
640
+ instrumentIdentifierId: cardId,
641
+ clientCorrelationId: req.body.clientCorrelationId ?? crypto2.randomUUID(),
642
+ tokenReferenceId: crypto2.randomBytes(16).toString("hex"),
643
+ dpan,
644
+ expMonth: card.expMonth,
645
+ expYear: card.expYear,
646
+ status: "ACTIVE",
647
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
648
+ };
649
+ enrolledCards.set(cardId, enrolled);
650
+ const tokenId = generateId("vic_tok");
651
+ const now = (/* @__PURE__ */ new Date()).toISOString();
652
+ tokens.set(tokenId, {
653
+ id: tokenId,
654
+ walletId,
655
+ cardId,
656
+ networkTokenId: dpan,
657
+ last4: card.last4,
658
+ brand: "visa",
659
+ status: "active",
660
+ expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toISOString(),
661
+ createdAt: now
662
+ });
663
+ console.log(`[VIC Demo] Card enrolled: ${cardId} \u2192 DPAN ***${dpan.slice(-4)}`);
664
+ res.status(201).json({ data: enrolled });
665
+ });
666
+ router.post("/intents", (req, res) => {
667
+ const { networkTokenId, amount, currency, merchantId, merchantName, description } = req.body;
668
+ if (!networkTokenId) {
669
+ res.status(400).json({ error: { code: "validation_error", message: "networkTokenId is required" } });
670
+ return;
671
+ }
672
+ const token = Array.from(tokens.values()).find((t) => t.networkTokenId === networkTokenId || t.id === networkTokenId);
673
+ if (!token) {
674
+ res.status(404).json({ error: { code: "not_found", message: "Network token not found" } });
675
+ return;
676
+ }
677
+ const intentId = crypto2.randomUUID();
678
+ const now = (/* @__PURE__ */ new Date()).toISOString();
679
+ const intent = {
680
+ id: intentId,
681
+ tokenId: token.id,
682
+ networkTokenId: token.networkTokenId,
683
+ amount: amount ?? 0,
684
+ currency: currency ?? "USD",
685
+ merchantId: merchantId ?? null,
686
+ merchantName: merchantName ?? null,
687
+ description: description ?? null,
688
+ mandates: req.body.mandates ?? [],
689
+ status: "created",
690
+ createdAt: now,
691
+ updatedAt: now
692
+ };
693
+ intents.set(intentId, intent);
694
+ console.log(`[VIC Demo] Purchase intent created: ${intentId} (token: ${token.id})`);
695
+ res.status(201).json({ data: { intentId } });
696
+ });
697
+ router.put("/intents/:id", (req, res) => {
698
+ const intent = intents.get(req.params.id);
699
+ if (!intent) {
700
+ res.status(404).json({ error: { code: "not_found", message: "Purchase intent not found" } });
701
+ return;
702
+ }
703
+ if (intent.status === "cancelled" || intent.status === "confirmed") {
704
+ res.status(400).json({
705
+ error: { code: "invalid_state", message: `Cannot update intent in status: ${intent.status}` }
706
+ });
707
+ return;
708
+ }
709
+ if (req.body.amount !== void 0) intent.amount = req.body.amount;
710
+ if (req.body.currency !== void 0) intent.currency = req.body.currency;
711
+ if (req.body.merchantId !== void 0) intent.merchantId = req.body.merchantId;
712
+ if (req.body.merchantName !== void 0) intent.merchantName = req.body.merchantName;
713
+ if (req.body.description !== void 0) intent.description = req.body.description;
714
+ if (req.body.mandates !== void 0) intent.mandates = req.body.mandates;
715
+ intent.status = "updated";
716
+ intent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
717
+ res.json({ data: { intentId: intent.id } });
718
+ });
719
+ router.delete("/intents/:id", (req, res) => {
720
+ const intent = intents.get(req.params.id);
721
+ if (!intent) {
722
+ res.status(404).json({ error: { code: "not_found", message: "Purchase intent not found" } });
723
+ return;
724
+ }
725
+ if (intent.status === "confirmed") {
726
+ res.status(400).json({
727
+ error: { code: "invalid_state", message: "Cannot cancel a confirmed intent" }
728
+ });
729
+ return;
730
+ }
731
+ intent.status = "cancelled";
732
+ intent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
733
+ res.json({ cancelled: true, intentId: intent.id });
734
+ });
735
+ router.post("/intents/:id/credentials", (req, res) => {
736
+ const intent = intents.get(req.params.id);
737
+ if (!intent) {
738
+ res.status(404).json({ error: { code: "not_found", message: "Purchase intent not found" } });
739
+ return;
740
+ }
741
+ if (intent.status === "cancelled") {
742
+ res.status(400).json({
743
+ error: { code: "invalid_state", message: "Cannot retrieve credentials for a cancelled intent" }
744
+ });
745
+ return;
746
+ }
747
+ const token = tokens.get(intent.tokenId);
748
+ const card = token ? cards.get(token.cardId) ?? Array.from(cards.values()).find((c) => c.walletId === token.walletId) : null;
749
+ const cryptogram = generateCryptogram();
750
+ intent.status = "credentials_retrieved";
751
+ intent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
752
+ console.log(`[VIC Demo] Credentials retrieved for intent: ${intent.id}`);
753
+ res.json({
754
+ data: {
755
+ tokenNumber: intent.networkTokenId,
756
+ cryptogram,
757
+ eci: "05",
758
+ expMonth: card?.expMonth ?? 12,
759
+ expYear: card?.expYear ?? 2030
760
+ }
761
+ });
762
+ });
763
+ router.post("/intents/:id/confirm", (req, res) => {
764
+ const intent = intents.get(req.params.id);
765
+ if (!intent) {
766
+ res.status(404).json({ error: { code: "not_found", message: "Purchase intent not found" } });
767
+ return;
768
+ }
769
+ intent.status = "confirmed";
770
+ intent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
771
+ console.log(`[VIC Demo] Transaction confirmed: ${intent.id}`);
772
+ res.json({ data: { confirmed: true } });
773
+ });
774
+ router.get("/tokens", (_req, res) => {
775
+ const results = Array.from(tokens.values());
776
+ let filtered = results;
777
+ if (_req.query.walletId) {
778
+ filtered = filtered.filter((t) => t.walletId === _req.query.walletId);
779
+ }
780
+ res.json({ data: filtered.map(toVICTokenResponse) });
781
+ });
782
+ router.get("/tokens/:id", (req, res) => {
783
+ const token = tokens.get(req.params.id);
784
+ if (!token) {
785
+ res.status(404).json({ error: { code: "not_found", message: "Token not found" } });
786
+ return;
787
+ }
788
+ res.json({ data: toVICTokenResponse(token) });
789
+ });
790
+ return router;
791
+ }
792
+
793
+ // server/vic-demo/index.ts
794
+ var app = express();
795
+ app.use(express.json());
796
+ app.use((_req, res, next) => {
797
+ res.header("Access-Control-Allow-Origin", "*");
798
+ res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
799
+ res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Lane-Request-Id, X-Idempotency-Key, X-Lane-Signature, X-Lane-Timestamp");
800
+ if (_req.method === "OPTIONS") {
801
+ res.status(204).send();
802
+ return;
803
+ }
804
+ next();
805
+ });
806
+ app.use("/agent", (req, _res, next) => {
807
+ const auth = req.headers.authorization;
808
+ if (!auth || !auth.startsWith("Bearer ")) {
809
+ _res.status(401).json({
810
+ error: { code: "unauthorized", message: "Bearer token required", statusCode: 401 }
811
+ });
812
+ return;
813
+ }
814
+ req.developer = { id: "dev_demo_001", name: "Demo Developer" };
815
+ next();
816
+ });
817
+ app.use("/vic", (req, _res, next) => {
818
+ const auth = req.headers.authorization;
819
+ if (!auth || !auth.startsWith("Bearer ")) {
820
+ _res.status(401).json({
821
+ error: { code: "unauthorized", message: "Bearer token required", statusCode: 401 }
822
+ });
823
+ return;
824
+ }
825
+ req.developer = { id: "dev_demo_001", name: "Demo Developer" };
826
+ next();
827
+ });
828
+ wallets.set("wallet_demo_001", {
829
+ id: "wallet_demo_001",
830
+ balance: 1e5,
831
+ // $1,000.00
832
+ currency: "USD",
833
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
834
+ });
835
+ wallets.set("wallet_demo_002", {
836
+ id: "wallet_demo_002",
837
+ balance: 5e5,
838
+ // $5,000.00
839
+ currency: "USD",
840
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
841
+ });
842
+ cards.set("card_demo_001", {
843
+ id: "card_demo_001",
844
+ walletId: "wallet_demo_001",
845
+ last4: "4242",
846
+ brand: "visa",
847
+ expMonth: 12,
848
+ expYear: 2030,
849
+ status: "active"
850
+ });
851
+ cards.set("card_demo_002", {
852
+ id: "card_demo_002",
853
+ walletId: "wallet_demo_002",
854
+ last4: "1234",
855
+ brand: "visa",
856
+ expMonth: 6,
857
+ expYear: 2029,
858
+ status: "active"
859
+ });
860
+ app.get("/health", (_req, res) => {
861
+ res.json({
862
+ service: "VIC Demo Server",
863
+ status: "healthy",
864
+ version: "1.1.0",
865
+ note: "Mock implementation of Lane VIC API for demos and development",
866
+ features: [
867
+ "VIC token lifecycle (issue, suspend, reactivate, revoke)",
868
+ "Payment credentials (DPAN + cryptogram)",
869
+ "Purchase intent management (create, update, cancel, confirm)",
870
+ "Instruction lifecycle with biometric confirmation",
871
+ "Raw Visa IC 6-step flow endpoints",
872
+ "Idempotency key support"
873
+ ],
874
+ stats: {
875
+ tokens: tokens.size,
876
+ intents: intents.size,
877
+ instructions: instructions.size,
878
+ wallets: wallets.size,
879
+ cards: cards.size
880
+ },
881
+ seedData: {
882
+ wallets: ["wallet_demo_001 ($1,000.00)", "wallet_demo_002 ($5,000.00)"],
883
+ cards: ["card_demo_001 (Visa ***4242)", "card_demo_002 (Visa ***1234)"]
884
+ }
885
+ });
886
+ });
887
+ app.use("/agent/token", createVICRoutes());
888
+ app.use("/agent/instruction", createInstructionRoutes());
889
+ app.use("/vic", createVICFlowRoutes());
890
+ app.get("/agent/wallet", (_req, res) => {
891
+ const allWallets = Array.from(wallets.values()).map((w) => ({
892
+ id: w.id,
893
+ label: `Demo Wallet (${w.id})`,
894
+ developerId: "dev_demo_001",
895
+ status: "active",
896
+ createdAt: w.createdAt,
897
+ updatedAt: w.createdAt
898
+ }));
899
+ const limit = Math.min(parseInt(_req.query.limit) || 20, 100);
900
+ const offset = parseInt(_req.query.offset) || 0;
901
+ res.json({
902
+ data: allWallets.slice(offset, offset + limit),
903
+ total: allWallets.length,
904
+ limit,
905
+ offset
906
+ });
907
+ });
908
+ app.get("/agent/wallet/:walletId", (req, res) => {
909
+ const wallet = wallets.get(req.params.walletId);
910
+ if (!wallet) {
911
+ res.status(404).json({ error: { code: "not_found", message: "Wallet not found" } });
912
+ return;
913
+ }
914
+ res.json({
915
+ id: wallet.id,
916
+ label: `Demo Wallet (${wallet.id})`,
917
+ developerId: "dev_demo_001",
918
+ status: "active",
919
+ createdAt: wallet.createdAt,
920
+ updatedAt: wallet.createdAt
921
+ });
922
+ });
923
+ app.get("/agent/wallet/:walletId/cards", (req, res) => {
924
+ const walletCards = Array.from(cards.values()).filter((c) => c.walletId === req.params.walletId).map((c) => ({
925
+ id: c.id,
926
+ last4: c.last4,
927
+ brand: c.brand,
928
+ status: c.status,
929
+ isDefault: true,
930
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
931
+ }));
932
+ res.json(walletCards);
933
+ });
934
+ app.get("/agent/wallet/:walletId/balance", (req, res) => {
935
+ const walletId = req.params.walletId;
936
+ const wallet = walletId === "default" ? Array.from(wallets.values())[0] : wallets.get(walletId);
937
+ if (!wallet) {
938
+ res.status(404).json({ error: { code: "not_found", message: "Wallet not found" } });
939
+ return;
940
+ }
941
+ res.json({
942
+ balance: wallet.balance,
943
+ currency: wallet.currency
944
+ });
945
+ });
946
+ app.post("/agent/auth/verify-sdk-access", (_req, res) => {
947
+ res.json({ access: true, demo: true });
948
+ });
949
+ app.get("/agent/admin/whoami", (_req, res) => {
950
+ res.json({
951
+ id: "dev_demo_001",
952
+ email: "demo@getonlane.com",
953
+ plan: "demo",
954
+ memberSince: "2024-01-01"
955
+ });
956
+ });
957
+ app.get("/agent/admin/budgets", (_req, res) => {
958
+ res.json({
959
+ dailyLimit: 5e4,
960
+ weeklyLimit: 2e5,
961
+ monthlyLimit: 5e5,
962
+ perTaskLimit: 1e4
963
+ });
964
+ });
965
+ app.get("/agent/card", (_req, res) => {
966
+ const allCards = Array.from(cards.values()).map((c) => ({
967
+ id: c.id,
968
+ last4: c.last4,
969
+ brand: c.brand,
970
+ status: c.status,
971
+ isDefault: true,
972
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
973
+ }));
974
+ const limit = Math.min(parseInt(_req.query.limit) || 20, 100);
975
+ const offset = parseInt(_req.query.offset) || 0;
976
+ res.json({
977
+ data: allCards.slice(offset, offset + limit),
978
+ total: allCards.length,
979
+ limit,
980
+ offset
981
+ });
982
+ });
983
+ app.post("/agent/pay", (req, res) => {
984
+ res.json({
985
+ id: `txn_demo_${crypto3.randomUUID().slice(0, 8)}`,
986
+ amount: req.body.amount ?? 0,
987
+ currency: req.body.currency ?? "USD",
988
+ recipient: req.body.recipient ?? "demo-merchant",
989
+ description: req.body.description,
990
+ status: "success",
991
+ testMode: true,
992
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
993
+ });
994
+ });
995
+ app.get("/agent/transaction", (_req, res) => {
996
+ const limit = Math.min(parseInt(_req.query.limit) || 20, 100);
997
+ const offset = parseInt(_req.query.offset) || 0;
998
+ res.json({ data: [], total: 0, limit, offset });
999
+ });
1000
+ app.get("/agent/discovery", (_req, res) => {
1001
+ res.json({
1002
+ name: "Lane VIC Demo",
1003
+ version: "1.1.0",
1004
+ capabilities: ["vic", "instructions", "wallets"],
1005
+ authMethods: ["bearer"]
1006
+ });
1007
+ });
1008
+ app.use((err, _req, res, _next) => {
1009
+ const requestId = _req.headers["x-lane-request-id"] ?? `req_${Date.now()}`;
1010
+ console.error("[VIC Demo] Error:", err.message);
1011
+ res.status(500).json({
1012
+ error: {
1013
+ code: "internal_error",
1014
+ message: err.message,
1015
+ statusCode: 500
1016
+ },
1017
+ requestId
1018
+ });
1019
+ });
1020
+ var PORT = parseInt(process.env.PORT ?? "3020", 10);
1021
+ var server = app.listen(PORT, () => {
1022
+ console.log(`[VIC Demo] Server running on http://localhost:${PORT}`);
1023
+ console.log(`[VIC Demo] Health: http://localhost:${PORT}/health`);
1024
+ console.log(`[VIC Demo]`);
1025
+ console.log(`[VIC Demo] Routes:`);
1026
+ console.log(`[VIC Demo] SDK VIC tokens: /agent/token/*`);
1027
+ console.log(`[VIC Demo] SDK instructions: /agent/instruction/*`);
1028
+ console.log(`[VIC Demo] SDK wallets: /agent/wallet/*`);
1029
+ console.log(`[VIC Demo] Visa IC 6-step: /vic/*`);
1030
+ console.log(`[VIC Demo]`);
1031
+ console.log(`[VIC Demo] Seed data:`);
1032
+ console.log(`[VIC Demo] Wallet: wallet_demo_001 ($1,000.00)`);
1033
+ console.log(`[VIC Demo] Wallet: wallet_demo_002 ($5,000.00)`);
1034
+ console.log(`[VIC Demo] Card: card_demo_001 (Visa ***4242)`);
1035
+ console.log(`[VIC Demo] Card: card_demo_002 (Visa ***1234)`);
1036
+ console.log(`[VIC Demo]`);
1037
+ console.log(`[VIC Demo] SDK usage:`);
1038
+ console.log(`[VIC Demo] const lane = Lane._unsafeCreate({ baseUrl: 'http://localhost:${PORT}' });`);
1039
+ console.log(`[VIC Demo] const token = await lane.vic.issue({ walletId: 'wallet_demo_001', cardId: 'card_demo_001' });`);
1040
+ console.log(`[VIC Demo] const creds = await lane.vic.getPaymentCredentials(token.id, { amount: 1999, currency: 'USD', merchantId: 'amazon.com' });`);
1041
+ });
1042
+ for (const signal of ["SIGTERM", "SIGINT"]) {
1043
+ process.on(signal, () => {
1044
+ console.log(`[VIC Demo] Received ${signal}, shutting down...`);
1045
+ server.close(() => process.exit(0));
1046
+ });
1047
+ }
1048
+ var vic_demo_default = app;
1049
+ export {
1050
+ vic_demo_default as default
1051
+ };
1052
+ //# sourceMappingURL=vic-demo.js.map