arispay 0.1.4 → 0.1.7

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 +61 -65
  2. package/package.json +1 -1
  3. package/src/cli.ts +71 -56
package/dist/cli.js CHANGED
@@ -110,75 +110,69 @@ ${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
- }
113
+ async function setupCardViaBrowser(endUserId, agentId) {
114
+ const config = loadConfig(BRAND);
115
+ const { apiKey, environment, baseUrl } = config;
116
+ const defaultUrl = environment === "production" ? "https://api.arispay.app" : "https://api-production-79ea.up.railway.app";
117
+ const apiUrl = baseUrl || defaultUrl;
138
118
  console.log(`
139
- ${dim("Tokenizing and verifying card...")}`);
119
+ ${dim("Creating secure card setup link...")}`);
140
120
  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
121
+ const res = await fetch(`${apiUrl}/v1/card-setup-sessions`, {
122
+ method: "POST",
123
+ headers: {
124
+ "Content-Type": "application/json",
125
+ Authorization: `Bearer ${apiKey}`
126
+ },
127
+ body: JSON.stringify({ endUserId, ...agentId ? { agentId } : {} }),
128
+ signal: AbortSignal.timeout(15e3)
148
129
  });
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;
130
+ if (!res.ok) {
131
+ const body = await res.json().catch(() => ({}));
132
+ throw new Error(body.error?.message || `Failed to create card setup (HTTP ${res.status})`);
170
133
  }
134
+ const data = await res.json();
171
135
  console.log();
172
- success(`Card verified!
136
+ console.log(` ${bold("Open this link to securely add and verify your card:")}
137
+ `);
138
+ console.log(` ${cyan(data.setupUrl)}
139
+ `);
140
+ console.log(` ${dim("Your card details are entered on a secure page hosted by ArisPay.")}`);
141
+ console.log(` ${dim("3D Secure verification is handled automatically in the browser.")}`);
142
+ console.log(` ${dim("This link expires in 15 minutes.")}
143
+ `);
144
+ console.log(` ${dim("Waiting for you to complete card setup...")}`);
145
+ const deadline = Date.now() + 15 * 60 * 1e3;
146
+ while (Date.now() < deadline) {
147
+ await new Promise((r) => setTimeout(r, 3e3));
148
+ try {
149
+ const statusRes = await fetch(`${apiUrl}/v1/card-setup-sessions/${data.token}/status`, {
150
+ signal: AbortSignal.timeout(1e4)
151
+ });
152
+ const statusData = await statusRes.json();
153
+ if (statusData.status === "completed") {
154
+ console.log();
155
+ success("Card verified!\n");
156
+ console.log(` ${bold("Card:")} ${statusData.cardBrand?.toUpperCase() || "Card"} ending in ${statusData.cardLast4 || "****"}`);
157
+ console.log(`
158
+ ${dim("Your card is linked and verified. All payments will process instantly.")}
173
159
  `);
174
- console.log(` ${bold("Card:")} ${r.cardBrand?.toUpperCase() || "Card"} ending in ${r.cardLast4 || "****"}`);
160
+ return true;
161
+ }
162
+ if (statusData.status === "failed") {
163
+ console.log();
164
+ console.error(` \x1B[31m\u2717\x1B[0m Card verification failed. Please try again.`);
165
+ return false;
166
+ }
167
+ } catch {
168
+ }
169
+ }
175
170
  console.log(`
176
- ${dim("Your card is now linked and verified. You can make payments.")}
177
- `);
178
- return true;
171
+ ${dim("Card setup link expired. Run option [2] to try again.")}`);
172
+ return false;
179
173
  } catch (e) {
180
174
  console.log();
181
- console.error(` \x1B[31m\u2717\x1B[0m ${e.message || "Failed to tokenize card"}`);
175
+ console.error(` \x1B[31m\u2717\x1B[0m ${e.message || "Failed to start card setup"}`);
182
176
  return false;
183
177
  }
184
178
  }
@@ -197,10 +191,11 @@ ${bold("Connect your agent to ArisPay")}`);
197
191
  ${dim("How does your agent make payments?")}
198
192
  `);
199
193
  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")}
194
+ console.log(` ${cyan("[2]")} Autonomous ${dim("\u2014 The agent decides when to pay, within spend limits you set")}`);
195
+ console.log(` ${cyan("[3]")} Both ${dim("\u2014 Platform and autonomous, depending on the transaction")}
201
196
  `);
202
197
  const modeChoice = await prompt(` Enter choice ${dim("[1]")}: `);
203
- const mode = modeChoice === "2" ? "autonomous" : "platform";
198
+ const mode = modeChoice === "2" ? "autonomous" : modeChoice === "3" ? "both" : "platform";
204
199
  console.log(`
205
200
  ${dim("Connecting agent...")}`);
206
201
  try {
@@ -225,7 +220,7 @@ ${bold("Connect your agent to ArisPay")}`);
225
220
  console.log(` ${bold("Mode:")} ${a.mode || mode}`);
226
221
  console.log(` ${bold("Status:")} ${a.status}
227
222
  `);
228
- if (mode === "autonomous") {
223
+ if (mode === "autonomous" || mode === "both") {
229
224
  console.log(` ${bold("Set spend limits")}`);
230
225
  console.log(` ${dim("Autonomous agents need spend limits so they can only spend within bounds you control.")}
231
226
  `);
@@ -284,7 +279,7 @@ ${bold("Connect your agent to ArisPay")}`);
284
279
  return;
285
280
  }
286
281
  }
287
- const cardOk = await collectAndTokenizeCard(client, endUserId);
282
+ const cardOk = await setupCardViaBrowser(endUserId, a.id);
288
283
  if (!cardOk) return;
289
284
  const topupStr = await prompt(`
290
285
  Amount to load ${dim("(e.g. 50.00)")}: $`);
@@ -351,7 +346,8 @@ ${bold("Add a payment method")}`);
351
346
  `);
352
347
  const methodChoice = await prompt(` Enter choice: `);
353
348
  if (methodChoice === "1") {
354
- await collectAndTokenizeCard(client, userId);
349
+ const storedAgentId = config.agentId;
350
+ await setupCardViaBrowser(userId, storedAgentId);
355
351
  } else if (methodChoice === "2") {
356
352
  console.log();
357
353
  const chain = await prompt(` Chain ${dim("(ethereum, base, polygon, solana)")}: `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arispay",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
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,69 +130,82 @@ async function postInitMenu(config: { apiKey?: string; environment?: string; bas
130
130
  }
131
131
  }
132
132
 
133
- // ── Card Tokenization + 3DS Helper ──────────────────────────────────
133
+ // ── Card Setup Helper (browser-based with 3DS) ─────────────────────
134
134
 
135
- async function collectAndTokenizeCard(
136
- client: ArisPayClient,
137
- userId: string,
135
+ async function setupCardViaBrowser(
136
+ endUserId: string,
137
+ agentId?: string,
138
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`);
139
+ const config = loadConfig(BRAND);
140
+ const { apiKey, environment, baseUrl } = config;
141
+ const defaultUrl = environment === 'production'
142
+ ? 'https://api.arispay.app'
143
+ : 'https://api-production-79ea.up.railway.app';
144
+ const apiUrl = baseUrl || defaultUrl;
141
145
 
142
- const cardNumber = await promptSecret(` Card number: `);
143
- if (!cardNumber) { console.log(dim(' Cancelled.')); return false; }
146
+ console.log(`\n ${dim('Creating secure card setup link...')}`);
144
147
 
145
- const expiry = await prompt(` Expiry ${dim('(MM/YY)')}: `);
146
- if (!expiry) { console.log(dim(' Cancelled.')); return false; }
148
+ try {
149
+ // Create card setup session
150
+ const res = await fetch(`${apiUrl}/v1/card-setup-sessions`, {
151
+ method: 'POST',
152
+ headers: {
153
+ 'Content-Type': 'application/json',
154
+ Authorization: `Bearer ${apiKey}`,
155
+ },
156
+ body: JSON.stringify({ endUserId, ...(agentId ? { agentId } : {}) }),
157
+ signal: AbortSignal.timeout(15_000),
158
+ });
147
159
 
148
- const securityCode = await promptSecret(` CVV: `);
149
- if (!securityCode) { console.log(dim(' Cancelled.')); return false; }
160
+ if (!res.ok) {
161
+ const body = await res.json().catch(() => ({}));
162
+ throw new Error((body as any).error?.message || `Failed to create card setup (HTTP ${res.status})`);
163
+ }
150
164
 
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
- }
165
+ const data = await res.json() as { token: string; setupUrl: string; expiresAt: string };
156
166
 
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');
167
+ console.log();
168
+ console.log(` ${bold('Open this link to securely add and verify your card:')}\n`);
169
+ console.log(` ${cyan(data.setupUrl)}\n`);
170
+ console.log(` ${dim('Your card details are entered on a secure page hosted by ArisPay.')}`);
171
+ console.log(` ${dim('3D Secure verification is handled automatically in the browser.')}`);
172
+ console.log(` ${dim('This link expires in 15 minutes.')}\n`);
173
+
174
+ // Poll for completion
175
+ console.log(` ${dim('Waiting for you to complete card setup...')}`);
176
+ const deadline = Date.now() + 15 * 60 * 1000; // 15 min
177
+ while (Date.now() < deadline) {
178
+ await new Promise(r => setTimeout(r, 3000));
179
+
180
+ try {
181
+ const statusRes = await fetch(`${apiUrl}/v1/card-setup-sessions/${data.token}/status`, {
182
+ signal: AbortSignal.timeout(10_000),
183
+ });
184
+ const statusData = await statusRes.json() as any;
185
+
186
+ if (statusData.status === 'completed') {
187
+ console.log();
188
+ success('Card verified!\n');
189
+ console.log(` ${bold('Card:')} ${statusData.cardBrand?.toUpperCase() || 'Card'} ending in ${statusData.cardLast4 || '****'}`);
190
+ console.log(`\n ${dim('Your card is linked and verified. All payments will process instantly.')}\n`);
191
+ return true;
192
+ }
193
+
194
+ if (statusData.status === 'failed') {
195
+ console.log();
196
+ console.error(` \x1b[31m✗\x1b[0m Card verification failed. Please try again.`);
197
+ return false;
198
+ }
199
+ } catch {
200
+ // Network error — keep polling
183
201
  }
184
- console.log(` ${bold('Card:')} ${r.cardBrand?.toUpperCase() || 'Card'} ending in ${r.cardLast4 || '****'}\n`);
185
- return true;
186
202
  }
187
203
 
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;
204
+ console.log(`\n ${dim('Card setup link expired. Run option [2] to try again.')}`);
205
+ return false;
193
206
  } catch (e: any) {
194
207
  console.log();
195
- console.error(` \x1b[31m✗\x1b[0m ${e.message || 'Failed to tokenize card'}`);
208
+ console.error(` \x1b[31m✗\x1b[0m ${e.message || 'Failed to start card setup'}`);
196
209
  return false;
197
210
  }
198
211
  }
@@ -209,10 +222,11 @@ async function wizardConnectAgent(client: ArisPayClient): Promise<void> {
209
222
 
210
223
  console.log(`\n ${dim('How does your agent make payments?')}\n`);
211
224
  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`);
225
+ console.log(` ${cyan('[2]')} Autonomous ${dim('— The agent decides when to pay, within spend limits you set')}`);
226
+ console.log(` ${cyan('[3]')} Both ${dim('— Platform and autonomous, depending on the transaction')}\n`);
213
227
 
214
228
  const modeChoice = await prompt(` Enter choice ${dim('[1]')}: `);
215
- const mode = modeChoice === '2' ? 'autonomous' : 'platform';
229
+ const mode = modeChoice === '2' ? 'autonomous' : modeChoice === '3' ? 'both' : 'platform';
216
230
 
217
231
  console.log(`\n ${dim('Connecting agent...')}`);
218
232
 
@@ -240,7 +254,7 @@ async function wizardConnectAgent(client: ArisPayClient): Promise<void> {
240
254
  console.log(` ${bold('Status:')} ${a.status}\n`);
241
255
 
242
256
  // Prompt for spend limits when autonomous
243
- if (mode === 'autonomous') {
257
+ if (mode === 'autonomous' || mode === 'both') {
244
258
  console.log(` ${bold('Set spend limits')}`);
245
259
  console.log(` ${dim('Autonomous agents need spend limits so they can only spend within bounds you control.')}\n`);
246
260
 
@@ -305,7 +319,7 @@ async function wizardConnectAgent(client: ArisPayClient): Promise<void> {
305
319
  }
306
320
 
307
321
  // Collect card details and verify via 3DS
308
- const cardOk = await collectAndTokenizeCard(client, endUserId);
322
+ const cardOk = await setupCardViaBrowser(endUserId, a.id);
309
323
  if (!cardOk) return;
310
324
 
311
325
  // Top up
@@ -373,7 +387,8 @@ async function wizardAddPaymentMethod(client: ArisPayClient): Promise<void> {
373
387
  const methodChoice = await prompt(` Enter choice: `);
374
388
 
375
389
  if (methodChoice === '1') {
376
- await collectAndTokenizeCard(client, userId);
390
+ const storedAgentId = (config as any).agentId;
391
+ await setupCardViaBrowser(userId, storedAgentId);
377
392
  } else if (methodChoice === '2') {
378
393
  console.log();
379
394
  const chain = await prompt(` Chain ${dim('(ethereum, base, polygon, solana)')}: `);