arispay 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/dist/cli.js +204 -68
  2. package/package.json +1 -1
  3. package/src/cli.ts +206 -58
package/dist/cli.js CHANGED
@@ -110,6 +110,78 @@ ${bold("Get started")}
110
110
  }
111
111
  }
112
112
  }
113
+ async function collectAndTokenizeCard(client, userId) {
114
+ console.log(`
115
+ ${dim("Enter your card details below. They are sent directly to Fiserv for")}`);
116
+ console.log(` ${dim("tokenization \u2014 ArisPay never sees or stores your full card number.")}
117
+ `);
118
+ const cardNumber = await promptSecret(` Card number: `);
119
+ if (!cardNumber) {
120
+ console.log(dim(" Cancelled."));
121
+ return false;
122
+ }
123
+ const expiry = await prompt(` Expiry ${dim("(MM/YY)")}: `);
124
+ if (!expiry) {
125
+ console.log(dim(" Cancelled."));
126
+ return false;
127
+ }
128
+ const securityCode = await promptSecret(` CVV: `);
129
+ if (!securityCode) {
130
+ console.log(dim(" Cancelled."));
131
+ return false;
132
+ }
133
+ const [expiryMonth, expiryYear] = expiry.split("/").map((s) => s.trim());
134
+ if (!expiryMonth || !expiryYear) {
135
+ console.log(` ${dim("Invalid expiry format. Use MM/YY.")}`);
136
+ return false;
137
+ }
138
+ console.log(`
139
+ ${dim("Tokenizing and verifying card...")}`);
140
+ try {
141
+ const result = await client.users.addPaymentMethod({
142
+ userId,
143
+ type: "card",
144
+ cardNumber: cardNumber.replace(/\s/g, ""),
145
+ expiryMonth,
146
+ expiryYear: expiryYear.length === 2 ? `20${expiryYear}` : expiryYear,
147
+ securityCode
148
+ });
149
+ const r = result;
150
+ if (r.requiresVerification) {
151
+ const challengeUrl = r.challengeUrl;
152
+ if (challengeUrl) {
153
+ console.log();
154
+ console.log(` ${bold("Card verification required")}`);
155
+ console.log(` ${dim("Your bank requires 3D Secure verification. Open this link:")}
156
+ `);
157
+ console.log(` ${cyan(challengeUrl)}
158
+ `);
159
+ console.log(` ${dim("Complete the verification in your browser, then come back here.")}`);
160
+ await prompt(`
161
+ Press Enter when done...`);
162
+ success("Card added (verification pending)\n");
163
+ } else {
164
+ console.log();
165
+ success("Card tokenized (verification in progress)\n");
166
+ }
167
+ console.log(` ${bold("Card:")} ${r.cardBrand?.toUpperCase() || "Card"} ending in ${r.cardLast4 || "****"}
168
+ `);
169
+ return true;
170
+ }
171
+ console.log();
172
+ success(`Card verified!
173
+ `);
174
+ console.log(` ${bold("Card:")} ${r.cardBrand?.toUpperCase() || "Card"} ending in ${r.cardLast4 || "****"}`);
175
+ console.log(`
176
+ ${dim("Your card is now linked and verified. You can make payments.")}
177
+ `);
178
+ return true;
179
+ } catch (e) {
180
+ console.log();
181
+ console.error(` \x1B[31m\u2717\x1B[0m ${e.message || "Failed to tokenize card"}`);
182
+ return false;
183
+ }
184
+ }
113
185
  async function wizardConnectAgent(client) {
114
186
  console.log(`
115
187
  ${bold("Connect your agent to ArisPay")}`);
@@ -122,12 +194,13 @@ ${bold("Connect your agent to ArisPay")}`);
122
194
  return;
123
195
  }
124
196
  console.log(`
125
- ${dim("How does your agent make payments?")}`);
126
- console.log(` ${dim(" platform \u2014 Your app triggers payments on behalf of the agent (default)")}`);
127
- console.log(` ${dim(" autonomous \u2014 The agent decides when to pay, within spend limits you set")}
197
+ ${dim("How does your agent make payments?")}
198
+ `);
199
+ console.log(` ${cyan("[1]")} Platform ${dim("\u2014 Your app triggers payments on behalf of the agent")}`);
200
+ console.log(` ${cyan("[2]")} Autonomous ${dim("\u2014 The agent decides when to pay, within spend limits you set")}
128
201
  `);
129
- const modeInput = await prompt(` Mode ${dim("[platform]")}: `);
130
- const mode = modeInput === "autonomous" ? "autonomous" : "platform";
202
+ const modeChoice = await prompt(` Enter choice ${dim("[1]")}: `);
203
+ const mode = modeChoice === "2" ? "autonomous" : "platform";
131
204
  console.log(`
132
205
  ${dim("Connecting agent...")}`);
133
206
  try {
@@ -138,19 +211,110 @@ ${bold("Connect your agent to ArisPay")}`);
138
211
  });
139
212
  const a = agent;
140
213
  const config = loadConfig(BRAND);
141
- saveConfig(BRAND, { ...config, agentId: a.id });
214
+ saveConfig(BRAND, { ...config, agentId: a.id, agentName: a.name || name, agentMode: a.mode || mode });
142
215
  console.log();
143
216
  success(`Agent connected!
144
217
  `);
145
- console.log(` ${dim("ArisPay generated an Ed25519 keypair for your agent \u2014 this is its")}`);
146
- console.log(` ${dim("cryptographic identity for signing payments (RFC 9421 TAP).")}`);
147
- console.log(` ${dim("A circuit breaker has been set up to auto-pause if anything looks off.")}
218
+ console.log(` ${dim("ArisPay has created a Visa TAP cryptographic identity for your agent.")}`);
219
+ console.log(` ${dim("Every payment your agent makes is signed with this identity so merchants")}`);
220
+ console.log(` ${dim("can verify it is authorised. A circuit breaker is also in place to")}`);
221
+ console.log(` ${dim("auto-pause your agent if unusual spending is detected.")}
148
222
  `);
149
223
  console.log(` ${bold("Agent ID:")} ${a.id}`);
150
224
  console.log(` ${bold("Name:")} ${a.name}`);
151
225
  console.log(` ${bold("Mode:")} ${a.mode || mode}`);
152
226
  console.log(` ${bold("Status:")} ${a.status}
153
227
  `);
228
+ if (mode === "autonomous") {
229
+ console.log(` ${bold("Set spend limits")}`);
230
+ console.log(` ${dim("Autonomous agents need spend limits so they can only spend within bounds you control.")}
231
+ `);
232
+ const perTxStr = await prompt(` Max per transaction ${dim("(e.g. 50.00)")}: $`);
233
+ const dailyStr = await prompt(` Max per day ${dim("(e.g. 200.00)")}: $`);
234
+ const monthlyStr = await prompt(` Max per month ${dim("(e.g. 1000.00)")}: $`);
235
+ const perTx = parseFloat(perTxStr);
236
+ const daily = parseFloat(dailyStr);
237
+ const monthly = parseFloat(monthlyStr);
238
+ if (perTx || daily || monthly) {
239
+ let endUserId = config.endUserId;
240
+ if (!endUserId) {
241
+ try {
242
+ const user = await client.users.create({ externalId: `cli_${Date.now()}` });
243
+ endUserId = user.id;
244
+ saveConfig(BRAND, { ...config, agentId: a.id, agentName: a.name || name, endUserId });
245
+ } catch {
246
+ }
247
+ }
248
+ if (endUserId) {
249
+ try {
250
+ await client.users.setLimits({
251
+ userId: endUserId,
252
+ agentId: a.id,
253
+ ...perTx ? { maxPerTransaction: Math.round(perTx * 100) } : {},
254
+ ...daily ? { maxDaily: Math.round(daily * 100) } : {},
255
+ ...monthly ? { maxMonthly: Math.round(monthly * 100) } : {}
256
+ });
257
+ console.log();
258
+ success("Spend limits set\n");
259
+ } catch (e) {
260
+ console.log();
261
+ console.error(` \x1B[31m\u2717\x1B[0m ${e.message || "Failed to set spend limits"}`);
262
+ }
263
+ }
264
+ } else {
265
+ console.log(`
266
+ ${dim("No limits set. You can configure them later on your dashboard.")}
267
+ `);
268
+ }
269
+ console.log(` ${bold("Fund your agent")}`);
270
+ console.log(` ${dim("Autonomous agents spend from their own balance. You need to add a")}`);
271
+ console.log(` ${dim("payment method and top up your agent before it can make payments.")}
272
+ `);
273
+ const fundNow = await prompt(` Add a card and fund your agent now? ${dim("(Y/n)")}: `);
274
+ if (fundNow.toLowerCase() !== "n") {
275
+ const latestConfig = loadConfig(BRAND);
276
+ let endUserId = latestConfig.endUserId;
277
+ if (!endUserId) {
278
+ try {
279
+ const user = await client.users.create({ externalId: `cli_${Date.now()}` });
280
+ endUserId = user.id;
281
+ saveConfig(BRAND, { ...latestConfig, endUserId });
282
+ } catch (e) {
283
+ console.error(` \x1B[31m\u2717\x1B[0m ${e.message || "Failed to set up account"}`);
284
+ return;
285
+ }
286
+ }
287
+ const cardOk = await collectAndTokenizeCard(client, endUserId);
288
+ if (!cardOk) return;
289
+ const topupStr = await prompt(`
290
+ Amount to load ${dim("(e.g. 50.00)")}: $`);
291
+ const topupDollars = parseFloat(topupStr);
292
+ if (!topupDollars || isNaN(topupDollars)) {
293
+ console.log(dim(" Skipped. You can top up later from your dashboard."));
294
+ } else {
295
+ console.log(`
296
+ ${dim(`Loading $${topupDollars.toFixed(2)} to ${a.name}...`)}`);
297
+ try {
298
+ const result = await client.agents.topup(a.id, {
299
+ amount: Math.round(topupDollars * 100),
300
+ userId: endUserId,
301
+ currency: "USD",
302
+ description: "Initial top-up via CLI"
303
+ });
304
+ console.log();
305
+ success(`Agent funded! Balance: $${((result.balance ?? result.availableBalance ?? 0) / 100).toFixed(2)}
306
+ `);
307
+ } catch (e) {
308
+ console.log();
309
+ console.error(` \x1B[31m\u2717\x1B[0m ${e.message || "Failed to top up agent"}`);
310
+ }
311
+ }
312
+ } else {
313
+ console.log(`
314
+ ${dim("You can add a payment method and fund your agent from the menu or dashboard.")}
315
+ `);
316
+ }
317
+ }
154
318
  } catch (e) {
155
319
  console.log();
156
320
  console.error(` \x1B[31m\u2717\x1B[0m ${e.message || "Failed to connect agent"}`);
@@ -187,54 +351,7 @@ ${bold("Add a payment method")}`);
187
351
  `);
188
352
  const methodChoice = await prompt(` Enter choice: `);
189
353
  if (methodChoice === "1") {
190
- console.log(`
191
- ${dim("Enter your card details below. They are sent directly to Fiserv for")}`);
192
- console.log(` ${dim("tokenization \u2014 ArisPay never sees or stores your full card number.")}
193
- `);
194
- const cardNumber = await promptSecret(` Card number: `);
195
- if (!cardNumber) {
196
- console.log(dim(" Cancelled."));
197
- return;
198
- }
199
- const expiry = await prompt(` Expiry ${dim("(MM/YY)")}: `);
200
- if (!expiry) {
201
- console.log(dim(" Cancelled."));
202
- return;
203
- }
204
- const securityCode = await promptSecret(` CVV: `);
205
- if (!securityCode) {
206
- console.log(dim(" Cancelled."));
207
- return;
208
- }
209
- const [expiryMonth, expiryYear] = expiry.split("/").map((s) => s.trim());
210
- if (!expiryMonth || !expiryYear) {
211
- console.log(` ${dim("Invalid expiry format. Use MM/YY.")}`);
212
- return;
213
- }
214
- console.log(`
215
- ${dim("Tokenizing card...")}`);
216
- try {
217
- const result = await client.users.addPaymentMethod({
218
- userId,
219
- type: "card",
220
- cardNumber: cardNumber.replace(/\s/g, ""),
221
- expiryMonth,
222
- expiryYear: expiryYear.length === 2 ? `20${expiryYear}` : expiryYear,
223
- securityCode
224
- });
225
- const r = result;
226
- console.log();
227
- success(`Card tokenized!
228
- `);
229
- console.log(` ${bold("Card:")} ${r.cardBrand?.toUpperCase() || "Card"} ending in ${r.cardLast4 || "****"}`);
230
- console.log(` ${bold("Token:")} ${dim(r.tokenId || "stored securely")}`);
231
- console.log(`
232
- ${dim("Your card is now linked to your agent. You can make test payments.")}
233
- `);
234
- } catch (e) {
235
- console.log();
236
- console.error(` \x1B[31m\u2717\x1B[0m ${e.message || "Failed to tokenize card"}`);
237
- }
354
+ await collectAndTokenizeCard(client, userId);
238
355
  } else if (methodChoice === "2") {
239
356
  console.log();
240
357
  const chain = await prompt(` Chain ${dim("(ethereum, base, polygon, solana)")}: `);
@@ -278,13 +395,16 @@ ${bold("Add a payment method")}`);
278
395
  async function wizardTestPayment(client) {
279
396
  console.log(`
280
397
  ${bold("Make a test payment")}`);
281
- console.log(` ${dim("Verify your integration works by sending a test payment.")}
398
+ console.log(` ${dim("Verify your integration works by sending a test payment.")}`);
399
+ console.log(` ${dim("This is a sandbox test \u2014 no real funds are moved.")}
282
400
  `);
283
401
  const config = loadConfig(BRAND);
284
402
  const storedAgentId = config.agentId;
403
+ const storedAgentName = config.agentName;
285
404
  let agentId;
286
405
  if (storedAgentId) {
287
- const useStored = await prompt(` Use agent ${dim(storedAgentId.slice(0, 12) + "...")}? ${dim("(Y/n)")}: `);
406
+ const agentLabel = storedAgentName ? bold(storedAgentName) : dim(storedAgentId.slice(0, 12) + "...");
407
+ const useStored = await prompt(` Use agent ${agentLabel}? ${dim("(Y/n)")}: `);
288
408
  if (useStored.toLowerCase() === "n") {
289
409
  agentId = await prompt(` Agent ID: `);
290
410
  if (!agentId) {
@@ -301,27 +421,43 @@ ${bold("Make a test payment")}`);
301
421
  return;
302
422
  }
303
423
  }
304
- const amountStr = await prompt(` Amount in cents ${dim("(e.g. 500 = $5.00)")}: `);
305
- const amount = Number(amountStr);
306
- if (!amount || isNaN(amount)) {
424
+ const amountStr = await prompt(` Amount ${dim("(e.g. 5.00)")}: $`);
425
+ const dollars = parseFloat(amountStr);
426
+ if (!dollars || isNaN(dollars)) {
307
427
  console.log(dim(" Invalid amount. Cancelled."));
308
428
  return;
309
429
  }
310
- const memo = await prompt(` What is this payment for? `);
430
+ const amount = Math.round(dollars * 100);
431
+ const memo = await prompt(` Description ${dim("(appears on the transaction record)")}: `);
311
432
  if (!memo) {
312
433
  console.log(dim(" Cancelled."));
313
434
  return;
314
435
  }
436
+ const agentMode = config.agentMode;
437
+ const isAutonomous = agentMode === "autonomous";
438
+ if (!isAutonomous) {
439
+ const userId = config.endUserId;
440
+ if (!userId) {
441
+ console.log(`
442
+ ${dim("No payment method set up yet. Run option [2] first.")}`);
443
+ return;
444
+ }
445
+ }
315
446
  console.log(`
316
- ${dim(`Sending $${(amount / 100).toFixed(2)} test payment...`)}`);
447
+ ${dim(`Sending $${dollars.toFixed(2)} test payment...`)}`);
317
448
  try {
318
- const payment = await client.payments.create({
449
+ const paymentParams = {
319
450
  agentId,
320
451
  amount,
321
452
  currency: "USD",
322
453
  memo,
323
454
  idempotencyKey: `cli_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
324
- });
455
+ };
456
+ if (!isAutonomous) {
457
+ paymentParams.userId = config.endUserId;
458
+ paymentParams.merchantUrl = "https://test.arispay.app";
459
+ }
460
+ const payment = await client.payments.create(paymentParams);
325
461
  const p = payment;
326
462
  console.log();
327
463
  if (p.status === "succeeded") {
@@ -386,8 +522,8 @@ ${bold("Examples:")}
386
522
  arispay init
387
523
  arispay agents create --name "BookingBot" --mode autonomous
388
524
  arispay agents list
389
- arispay pay --agent <id> --amount 2000 --memo "Dinner for 2"
390
- arispay transactions --agent <id> --from 2025-01-01
525
+ arispay pay --agent <id> --amount 20.00 --memo "Dinner for 2"
526
+ arispay transactions --agent <id> --from 2026-01-01
391
527
 
392
528
  ${bold("Environment:")}
393
529
  ARISPAY_API_KEY Override API key
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arispay",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "SDK and CLI for enabling agent-initiated payments via Visa TAP + Fiserv",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/cli.ts CHANGED
@@ -130,6 +130,73 @@ async function postInitMenu(config: { apiKey?: string; environment?: string; bas
130
130
  }
131
131
  }
132
132
 
133
+ // ── Card Tokenization + 3DS Helper ──────────────────────────────────
134
+
135
+ async function collectAndTokenizeCard(
136
+ client: ArisPayClient,
137
+ userId: string,
138
+ ): Promise<boolean> {
139
+ console.log(`\n ${dim('Enter your card details below. They are sent directly to Fiserv for')}`);
140
+ console.log(` ${dim('tokenization — ArisPay never sees or stores your full card number.')}\n`);
141
+
142
+ const cardNumber = await promptSecret(` Card number: `);
143
+ if (!cardNumber) { console.log(dim(' Cancelled.')); return false; }
144
+
145
+ const expiry = await prompt(` Expiry ${dim('(MM/YY)')}: `);
146
+ if (!expiry) { console.log(dim(' Cancelled.')); return false; }
147
+
148
+ const securityCode = await promptSecret(` CVV: `);
149
+ if (!securityCode) { console.log(dim(' Cancelled.')); return false; }
150
+
151
+ const [expiryMonth, expiryYear] = expiry.split('/').map(s => s.trim());
152
+ if (!expiryMonth || !expiryYear) {
153
+ console.log(` ${dim('Invalid expiry format. Use MM/YY.')}`);
154
+ return false;
155
+ }
156
+
157
+ console.log(`\n ${dim('Tokenizing and verifying card...')}`);
158
+ try {
159
+ const result = await client.users.addPaymentMethod({
160
+ userId,
161
+ type: 'card',
162
+ cardNumber: cardNumber.replace(/\s/g, ''),
163
+ expiryMonth,
164
+ expiryYear: expiryYear.length === 2 ? `20${expiryYear}` : expiryYear,
165
+ securityCode,
166
+ });
167
+ const r = result as any;
168
+
169
+ if (r.requiresVerification) {
170
+ // 3DS verification required — open browser
171
+ const challengeUrl = r.challengeUrl;
172
+ if (challengeUrl) {
173
+ console.log();
174
+ console.log(` ${bold('Card verification required')}`);
175
+ console.log(` ${dim('Your bank requires 3D Secure verification. Open this link:')}\n`);
176
+ console.log(` ${cyan(challengeUrl)}\n`);
177
+ console.log(` ${dim('Complete the verification in your browser, then come back here.')}`);
178
+ await prompt(`\n Press Enter when done...`);
179
+ success('Card added (verification pending)\n');
180
+ } else {
181
+ console.log();
182
+ success('Card tokenized (verification in progress)\n');
183
+ }
184
+ console.log(` ${bold('Card:')} ${r.cardBrand?.toUpperCase() || 'Card'} ending in ${r.cardLast4 || '****'}\n`);
185
+ return true;
186
+ }
187
+
188
+ console.log();
189
+ success(`Card verified!\n`);
190
+ console.log(` ${bold('Card:')} ${r.cardBrand?.toUpperCase() || 'Card'} ending in ${r.cardLast4 || '****'}`);
191
+ console.log(`\n ${dim('Your card is now linked and verified. You can make payments.')}\n`);
192
+ return true;
193
+ } catch (e: any) {
194
+ console.log();
195
+ console.error(` \x1b[31m✗\x1b[0m ${e.message || 'Failed to tokenize card'}`);
196
+ return false;
197
+ }
198
+ }
199
+
133
200
  // ── Wizard: Connect Agent ────────────────────────────────────────────
134
201
 
135
202
  async function wizardConnectAgent(client: ArisPayClient): Promise<void> {
@@ -140,12 +207,12 @@ async function wizardConnectAgent(client: ArisPayClient): Promise<void> {
140
207
  const name = await prompt(` What is your agent called? `);
141
208
  if (!name) { console.log(dim(' Cancelled.')); return; }
142
209
 
143
- console.log(`\n ${dim('How does your agent make payments?')}`);
144
- console.log(` ${dim(' platform — Your app triggers payments on behalf of the agent (default)')}`);
145
- console.log(` ${dim(' autonomous — The agent decides when to pay, within spend limits you set')}\n`);
210
+ console.log(`\n ${dim('How does your agent make payments?')}\n`);
211
+ console.log(` ${cyan('[1]')} Platform ${dim('— Your app triggers payments on behalf of the agent')}`);
212
+ console.log(` ${cyan('[2]')} Autonomous ${dim('— The agent decides when to pay, within spend limits you set')}\n`);
146
213
 
147
- const modeInput = await prompt(` Mode ${dim('[platform]')}: `);
148
- const mode = modeInput === 'autonomous' ? 'autonomous' : 'platform';
214
+ const modeChoice = await prompt(` Enter choice ${dim('[1]')}: `);
215
+ const mode = modeChoice === '2' ? 'autonomous' : 'platform';
149
216
 
150
217
  console.log(`\n ${dim('Connecting agent...')}`);
151
218
 
@@ -159,17 +226,113 @@ async function wizardConnectAgent(client: ArisPayClient): Promise<void> {
159
226
 
160
227
  // Store agent ID so other steps can auto-fill it
161
228
  const config = loadConfig(BRAND);
162
- saveConfig(BRAND, { ...config, agentId: a.id } as any);
229
+ saveConfig(BRAND, { ...config, agentId: a.id, agentName: a.name || name, agentMode: a.mode || mode } as any);
163
230
 
164
231
  console.log();
165
232
  success(`Agent connected!\n`);
166
- console.log(` ${dim('ArisPay generated an Ed25519 keypair for your agent — this is its')}`);
167
- console.log(` ${dim('cryptographic identity for signing payments (RFC 9421 TAP).')}`);
168
- console.log(` ${dim('A circuit breaker has been set up to auto-pause if anything looks off.')}\n`);
233
+ console.log(` ${dim('ArisPay has created a Visa TAP cryptographic identity for your agent.')}`);
234
+ console.log(` ${dim('Every payment your agent makes is signed with this identity so merchants')}`);
235
+ console.log(` ${dim('can verify it is authorised. A circuit breaker is also in place to')}`);
236
+ console.log(` ${dim('auto-pause your agent if unusual spending is detected.')}\n`);
169
237
  console.log(` ${bold('Agent ID:')} ${a.id}`);
170
238
  console.log(` ${bold('Name:')} ${a.name}`);
171
239
  console.log(` ${bold('Mode:')} ${a.mode || mode}`);
172
240
  console.log(` ${bold('Status:')} ${a.status}\n`);
241
+
242
+ // Prompt for spend limits when autonomous
243
+ if (mode === 'autonomous') {
244
+ console.log(` ${bold('Set spend limits')}`);
245
+ console.log(` ${dim('Autonomous agents need spend limits so they can only spend within bounds you control.')}\n`);
246
+
247
+ const perTxStr = await prompt(` Max per transaction ${dim('(e.g. 50.00)')}: $`);
248
+ const dailyStr = await prompt(` Max per day ${dim('(e.g. 200.00)')}: $`);
249
+ const monthlyStr = await prompt(` Max per month ${dim('(e.g. 1000.00)')}: $`);
250
+
251
+ const perTx = parseFloat(perTxStr);
252
+ const daily = parseFloat(dailyStr);
253
+ const monthly = parseFloat(monthlyStr);
254
+
255
+ if (perTx || daily || monthly) {
256
+ // Need an end user to attach limits — create one if not stored
257
+ let endUserId = (config as any).endUserId;
258
+ if (!endUserId) {
259
+ try {
260
+ const user = await client.users.create({ externalId: `cli_${Date.now()}` });
261
+ endUserId = (user as any).id;
262
+ saveConfig(BRAND, { ...config, agentId: a.id, agentName: a.name || name, endUserId } as any);
263
+ } catch { /* will be created later in payment method step */ }
264
+ }
265
+
266
+ if (endUserId) {
267
+ try {
268
+ await client.users.setLimits({
269
+ userId: endUserId,
270
+ agentId: a.id,
271
+ ...(perTx ? { maxPerTransaction: Math.round(perTx * 100) } : {}),
272
+ ...(daily ? { maxDaily: Math.round(daily * 100) } : {}),
273
+ ...(monthly ? { maxMonthly: Math.round(monthly * 100) } : {}),
274
+ });
275
+ console.log();
276
+ success('Spend limits set\n');
277
+ } catch (e: any) {
278
+ console.log();
279
+ console.error(` \x1b[31m✗\x1b[0m ${e.message || 'Failed to set spend limits'}`);
280
+ }
281
+ }
282
+ } else {
283
+ console.log(`\n ${dim('No limits set. You can configure them later on your dashboard.')}\n`);
284
+ }
285
+
286
+ // Offer to fund the autonomous agent
287
+ console.log(` ${bold('Fund your agent')}`);
288
+ console.log(` ${dim('Autonomous agents spend from their own balance. You need to add a')}`);
289
+ console.log(` ${dim('payment method and top up your agent before it can make payments.')}\n`);
290
+
291
+ const fundNow = await prompt(` Add a card and fund your agent now? ${dim('(Y/n)')}: `);
292
+ if (fundNow.toLowerCase() !== 'n') {
293
+ // Ensure end user exists
294
+ const latestConfig = loadConfig(BRAND);
295
+ let endUserId = (latestConfig as any).endUserId;
296
+ if (!endUserId) {
297
+ try {
298
+ const user = await client.users.create({ externalId: `cli_${Date.now()}` });
299
+ endUserId = (user as any).id;
300
+ saveConfig(BRAND, { ...latestConfig, endUserId } as any);
301
+ } catch (e: any) {
302
+ console.error(` \x1b[31m✗\x1b[0m ${e.message || 'Failed to set up account'}`);
303
+ return;
304
+ }
305
+ }
306
+
307
+ // Collect card details and verify via 3DS
308
+ const cardOk = await collectAndTokenizeCard(client, endUserId);
309
+ if (!cardOk) return;
310
+
311
+ // Top up
312
+ const topupStr = await prompt(`\n Amount to load ${dim('(e.g. 50.00)')}: $`);
313
+ const topupDollars = parseFloat(topupStr);
314
+ if (!topupDollars || isNaN(topupDollars)) {
315
+ console.log(dim(' Skipped. You can top up later from your dashboard.'));
316
+ } else {
317
+ console.log(`\n ${dim(`Loading $${topupDollars.toFixed(2)} to ${a.name}...`)}`);
318
+ try {
319
+ const result = await client.agents.topup(a.id, {
320
+ amount: Math.round(topupDollars * 100),
321
+ userId: endUserId,
322
+ currency: 'USD',
323
+ description: 'Initial top-up via CLI',
324
+ }) as any;
325
+ console.log();
326
+ success(`Agent funded! Balance: $${((result.balance ?? result.availableBalance ?? 0) / 100).toFixed(2)}\n`);
327
+ } catch (e: any) {
328
+ console.log();
329
+ console.error(` \x1b[31m✗\x1b[0m ${e.message || 'Failed to top up agent'}`);
330
+ }
331
+ }
332
+ } else {
333
+ console.log(`\n ${dim('You can add a payment method and fund your agent from the menu or dashboard.')}\n`);
334
+ }
335
+ }
173
336
  } catch (e: any) {
174
337
  console.log();
175
338
  console.error(` \x1b[31m✗\x1b[0m ${e.message || 'Failed to connect agent'}`);
@@ -210,44 +373,7 @@ async function wizardAddPaymentMethod(client: ArisPayClient): Promise<void> {
210
373
  const methodChoice = await prompt(` Enter choice: `);
211
374
 
212
375
  if (methodChoice === '1') {
213
- console.log(`\n ${dim('Enter your card details below. They are sent directly to Fiserv for')}`);
214
- console.log(` ${dim('tokenization — ArisPay never sees or stores your full card number.')}\n`);
215
-
216
- const cardNumber = await promptSecret(` Card number: `);
217
- if (!cardNumber) { console.log(dim(' Cancelled.')); return; }
218
-
219
- const expiry = await prompt(` Expiry ${dim('(MM/YY)')}: `);
220
- if (!expiry) { console.log(dim(' Cancelled.')); return; }
221
-
222
- const securityCode = await promptSecret(` CVV: `);
223
- if (!securityCode) { console.log(dim(' Cancelled.')); return; }
224
-
225
- const [expiryMonth, expiryYear] = expiry.split('/').map(s => s.trim());
226
- if (!expiryMonth || !expiryYear) {
227
- console.log(` ${dim('Invalid expiry format. Use MM/YY.')}`);
228
- return;
229
- }
230
-
231
- console.log(`\n ${dim('Tokenizing card...')}`);
232
- try {
233
- const result = await client.users.addPaymentMethod({
234
- userId,
235
- type: 'card',
236
- cardNumber: cardNumber.replace(/\s/g, ''),
237
- expiryMonth,
238
- expiryYear: expiryYear.length === 2 ? `20${expiryYear}` : expiryYear,
239
- securityCode,
240
- });
241
- const r = result as any;
242
- console.log();
243
- success(`Card tokenized!\n`);
244
- console.log(` ${bold('Card:')} ${r.cardBrand?.toUpperCase() || 'Card'} ending in ${r.cardLast4 || '****'}`);
245
- console.log(` ${bold('Token:')} ${dim(r.tokenId || 'stored securely')}`);
246
- console.log(`\n ${dim('Your card is now linked to your agent. You can make test payments.')}\n`);
247
- } catch (e: any) {
248
- console.log();
249
- console.error(` \x1b[31m✗\x1b[0m ${e.message || 'Failed to tokenize card'}`);
250
- }
376
+ await collectAndTokenizeCard(client, userId);
251
377
  } else if (methodChoice === '2') {
252
378
  console.log();
253
379
  const chain = await prompt(` Chain ${dim('(ethereum, base, polygon, solana)')}: `);
@@ -283,14 +409,17 @@ async function wizardAddPaymentMethod(client: ArisPayClient): Promise<void> {
283
409
 
284
410
  async function wizardTestPayment(client: ArisPayClient): Promise<void> {
285
411
  console.log(`\n${bold('Make a test payment')}`);
286
- console.log(` ${dim('Verify your integration works by sending a test payment.')}\n`);
412
+ console.log(` ${dim('Verify your integration works by sending a test payment.')}`);
413
+ console.log(` ${dim('This is a sandbox test — no real funds are moved.')}\n`);
287
414
 
288
415
  const config = loadConfig(BRAND);
289
416
  const storedAgentId = (config as any).agentId;
417
+ const storedAgentName = (config as any).agentName;
290
418
 
291
419
  let agentId: string;
292
420
  if (storedAgentId) {
293
- const useStored = await prompt(` Use agent ${dim(storedAgentId.slice(0, 12) + '...')}? ${dim('(Y/n)')}: `);
421
+ const agentLabel = storedAgentName ? bold(storedAgentName) : dim(storedAgentId.slice(0, 12) + '...');
422
+ const useStored = await prompt(` Use agent ${agentLabel}? ${dim('(Y/n)')}: `);
294
423
  if (useStored.toLowerCase() === 'n') {
295
424
  agentId = await prompt(` Agent ID: `);
296
425
  if (!agentId) { console.log(dim(' Cancelled.')); return; }
@@ -302,23 +431,42 @@ async function wizardTestPayment(client: ArisPayClient): Promise<void> {
302
431
  if (!agentId) { console.log(dim(' Cancelled.')); return; }
303
432
  }
304
433
 
305
- const amountStr = await prompt(` Amount in cents ${dim('(e.g. 500 = $5.00)')}: `);
306
- const amount = Number(amountStr);
307
- if (!amount || isNaN(amount)) { console.log(dim(' Invalid amount. Cancelled.')); return; }
434
+ const amountStr = await prompt(` Amount ${dim('(e.g. 5.00)')}: $`);
435
+ const dollars = parseFloat(amountStr);
436
+ if (!dollars || isNaN(dollars)) { console.log(dim(' Invalid amount. Cancelled.')); return; }
437
+ const amount = Math.round(dollars * 100);
308
438
 
309
- const memo = await prompt(` What is this payment for? `);
439
+ const memo = await prompt(` Description ${dim('(appears on the transaction record)')}: `);
310
440
  if (!memo) { console.log(dim(' Cancelled.')); return; }
311
441
 
312
- console.log(`\n ${dim(`Sending $${(amount / 100).toFixed(2)} test payment...`)}`);
442
+ const agentMode = (config as any).agentMode;
443
+ const isAutonomous = agentMode === 'autonomous';
444
+
445
+ if (!isAutonomous) {
446
+ const userId = (config as any).endUserId;
447
+ if (!userId) {
448
+ console.log(`\n ${dim('No payment method set up yet. Run option [2] first.')}`);
449
+ return;
450
+ }
451
+ }
452
+
453
+ console.log(`\n ${dim(`Sending $${dollars.toFixed(2)} test payment...`)}`);
313
454
 
314
455
  try {
315
- const payment = await client.payments.create({
456
+ const paymentParams: Record<string, unknown> = {
316
457
  agentId,
317
458
  amount,
318
459
  currency: 'USD',
319
460
  memo,
320
461
  idempotencyKey: `cli_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
321
- } as any);
462
+ };
463
+
464
+ if (!isAutonomous) {
465
+ paymentParams.userId = (config as any).endUserId;
466
+ paymentParams.merchantUrl = 'https://test.arispay.app';
467
+ }
468
+
469
+ const payment = await client.payments.create(paymentParams as any);
322
470
  const p = payment as any;
323
471
  console.log();
324
472
  if (p.status === 'succeeded') {
@@ -387,8 +535,8 @@ ${bold('Examples:')}
387
535
  arispay init
388
536
  arispay agents create --name "BookingBot" --mode autonomous
389
537
  arispay agents list
390
- arispay pay --agent <id> --amount 2000 --memo "Dinner for 2"
391
- arispay transactions --agent <id> --from 2025-01-01
538
+ arispay pay --agent <id> --amount 20.00 --memo "Dinner for 2"
539
+ arispay transactions --agent <id> --from 2026-01-01
392
540
 
393
541
  ${bold('Environment:')}
394
542
  ARISPAY_API_KEY Override API key