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.
- package/dist/cli.js +204 -68
- package/package.json +1 -1
- 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
|
-
|
|
127
|
-
console.log(` ${dim("
|
|
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
|
|
130
|
-
const mode =
|
|
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
|
|
146
|
-
console.log(` ${dim("
|
|
147
|
-
console.log(` ${dim("
|
|
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
|
-
|
|
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
|
|
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
|
|
305
|
-
const
|
|
306
|
-
if (!
|
|
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
|
|
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 $${
|
|
447
|
+
${dim(`Sending $${dollars.toFixed(2)} test payment...`)}`);
|
|
317
448
|
try {
|
|
318
|
-
const
|
|
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
|
|
390
|
-
arispay transactions --agent <id> --from
|
|
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
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('
|
|
145
|
-
console.log(` ${dim('
|
|
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
|
|
148
|
-
const mode =
|
|
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
|
|
167
|
-
console.log(` ${dim('
|
|
168
|
-
console.log(` ${dim('
|
|
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
|
-
|
|
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.')}
|
|
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
|
|
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
|
|
306
|
-
const
|
|
307
|
-
if (!
|
|
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(`
|
|
439
|
+
const memo = await prompt(` Description ${dim('(appears on the transaction record)')}: `);
|
|
310
440
|
if (!memo) { console.log(dim(' Cancelled.')); return; }
|
|
311
441
|
|
|
312
|
-
|
|
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
|
|
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
|
-
}
|
|
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
|
|
391
|
-
arispay transactions --agent <id> --from
|
|
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
|