vibecash 0.1.3 → 0.2.1

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/index.js +788 -832
  2. package/package.json +12 -27
  3. package/README.md +0 -191
package/dist/index.js CHANGED
@@ -1,1012 +1,968 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command9 } from "commander";
5
-
6
- // src/commands/wallet.ts
7
4
  import { Command } from "commander";
8
5
 
9
6
  // src/config.ts
10
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
11
8
  import { homedir } from "os";
12
9
  import { join } from "path";
13
-
14
- // ../shared/src/constants.ts
15
- var PRODUCT_NAME = "vibecash";
16
- var DOMAINS = {
17
- api: `api.${PRODUCT_NAME}.dev`,
18
- app: `${PRODUCT_NAME}.dev`,
19
- pay: `pay.${PRODUCT_NAME}.dev`,
20
- js: `js.${PRODUCT_NAME}.dev`
21
- };
22
- var API_BASE_URL = `https://${DOMAINS.api}/v1`;
23
- var EXPIRATION = {
24
- checkoutSession: 24 * 60 * 60 * 1e3,
25
- // 24 hours
26
- claimToken: 7 * 24 * 60 * 60 * 1e3,
27
- // 7 days
28
- portalSession: 60 * 60 * 1e3
29
- // 1 hour
30
- };
31
-
32
- // ../shared/src/utils-browser.ts
33
- function formatAmount(amountCents, currency) {
34
- const amount = amountCents / 100;
35
- return new Intl.NumberFormat("en-US", {
36
- style: "currency",
37
- currency
38
- }).format(amount);
39
- }
40
- function parseAmountToCents(amount) {
41
- const parsed = typeof amount === "string" ? parseFloat(amount) : amount;
42
- return Math.round(parsed * 100);
43
- }
44
-
45
- // ../shared/src/utils-node.ts
46
- import { createHash, randomBytes, timingSafeEqual } from "crypto";
47
-
48
- // src/config.ts
49
- var CONFIG_DIR = join(homedir(), `.${PRODUCT_NAME}`);
10
+ var CONFIG_DIR = join(homedir(), ".vibecash");
50
11
  var CONFIG_FILE = join(CONFIG_DIR, "config.json");
51
12
  function getConfig() {
52
- if (!existsSync(CONFIG_FILE)) {
53
- return {};
54
- }
55
- try {
56
- const content = readFileSync(CONFIG_FILE, "utf-8");
57
- return JSON.parse(content);
58
- } catch {
59
- return {};
60
- }
13
+ if (!existsSync(CONFIG_FILE)) return {};
14
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
61
15
  }
62
16
  function saveConfig(config) {
63
- if (!existsSync(CONFIG_DIR)) {
64
- mkdirSync(CONFIG_DIR, { recursive: true });
65
- }
66
- const existing = getConfig();
67
- const merged = { ...existing, ...config };
68
- writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
17
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
18
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
69
19
  }
70
20
  function getSecret() {
71
- const envSecret = process.env.AIPAY_SECRET;
72
- if (envSecret) {
73
- return envSecret;
74
- }
21
+ return process.env.VIBECASH_SECRET || getConfig().secret;
22
+ }
23
+ function setSecret(secret) {
75
24
  const config = getConfig();
76
- return config.secret;
25
+ config.secret = secret;
26
+ saveConfig(config);
77
27
  }
78
28
  function getApiUrl() {
79
- const envUrl = process.env.AIPAY_API_URL;
80
- if (envUrl) {
81
- return envUrl;
82
- }
83
- const config = getConfig();
84
- return config.apiUrl || "https://api.vibecash.dev";
29
+ return process.env.VIBECASH_API_URL || getConfig().apiUrl || "https://api.vibecash.dev";
30
+ }
31
+ var _overrideFormat;
32
+ function setOutputFormat(format) {
33
+ _overrideFormat = format;
85
34
  }
86
35
  function getOutputFormat() {
87
- const envFormat = process.env.AIPAY_OUTPUT;
88
- if (envFormat === "human" || envFormat === "json") {
89
- return envFormat;
90
- }
91
- const config = getConfig();
92
- return config.outputFormat || "json";
36
+ if (_overrideFormat) return _overrideFormat;
37
+ const env = process.env.VIBECASH_OUTPUT;
38
+ if (env === "json" || env === "human") return env;
39
+ return getConfig().outputFormat || "human";
93
40
  }
94
41
 
95
42
  // src/api-client.ts
96
- var ApiClient = class {
97
- baseUrl;
98
- secret;
99
- constructor(options) {
100
- this.baseUrl = options?.baseUrl || getApiUrl();
101
- this.secret = options?.secret || getSecret();
102
- }
103
- async request(method, path, body, requiresAuth = true) {
104
- const url = `${this.baseUrl}${path}`;
105
- const headers = {
106
- "Content-Type": "application/json"
107
- };
108
- if (requiresAuth) {
109
- if (!this.secret) {
110
- throw new Error(
111
- "No API secret found. Set AIPAY_SECRET environment variable or run `aipay wallet create`"
112
- );
113
- }
114
- headers["Authorization"] = `Bearer ${this.secret}`;
115
- }
116
- const response = await fetch(url, {
117
- method,
118
- headers,
119
- body: body ? JSON.stringify(body) : void 0
120
- });
121
- const json = await response.json();
122
- if (!response.ok) {
123
- const errorMessage = json.error?.message || `API error: ${response.status}`;
124
- throw new Error(errorMessage);
125
- }
126
- return json;
127
- }
128
- // Wallets
129
- async createWallet() {
130
- return this.request("POST", "/v1/wallets", void 0, false);
131
- }
132
- async getWallet() {
133
- return this.request("GET", "/v1/wallets/current");
134
- }
135
- async createClaimLink() {
136
- return this.request("POST", "/v1/wallets/current/claim_link");
137
- }
138
- // Products
139
- async createProduct(data) {
140
- return this.request("POST", "/v1/products", data);
141
- }
142
- async listProducts() {
143
- return this.request("GET", "/v1/products");
144
- }
145
- async getProduct(id) {
146
- return this.request("GET", `/v1/products/${id}`);
147
- }
148
- // Prices
149
- async createPrice(data) {
150
- return this.request("POST", "/v1/prices", {
151
- productId: data.productId,
152
- amount: data.amount,
153
- currency: data.currency,
154
- type: data.type,
155
- interval: data.interval,
156
- intervalCount: data.intervalCount,
157
- trialPeriodDays: data.trialPeriodDays
158
- });
159
- }
160
- async listPrices(productId) {
161
- const query = productId ? `?product_id=${productId}` : "";
162
- return this.request("GET", `/v1/prices${query}`);
163
- }
164
- // Checkout
165
- async createCheckoutSession(data) {
166
- return this.request("POST", "/v1/checkout/sessions", data);
167
- }
168
- async getCheckoutSession(id) {
169
- return this.request("GET", `/v1/checkout/sessions/${id}`, void 0, false);
170
- }
171
- // Subscriptions
172
- async listSubscriptions(params) {
173
- const query = new URLSearchParams();
174
- if (params?.customerId) query.set("customer_id", params.customerId);
175
- if (params?.status) query.set("status", params.status);
176
- const queryStr = query.toString() ? `?${query.toString()}` : "";
177
- return this.request("GET", `/v1/subscriptions${queryStr}`);
178
- }
179
- async getSubscription(id) {
180
- return this.request("GET", `/v1/subscriptions/${id}`);
181
- }
182
- async cancelSubscription(id, immediately = false) {
183
- return this.request("POST", `/v1/subscriptions/${id}/cancel`, {
184
- cancel_at_period_end: !immediately
185
- });
186
- }
187
- async resumeSubscription(id) {
188
- return this.request("POST", `/v1/subscriptions/${id}/resume`);
189
- }
190
- // Customers
191
- async createCustomer(data) {
192
- return this.request("POST", "/v1/customers", data);
193
- }
194
- async listCustomers() {
195
- return this.request("GET", "/v1/customers");
196
- }
197
- async getCustomer(id) {
198
- return this.request("GET", `/v1/customers/${id}`);
199
- }
200
- async createPortalSession(customerId, returnUrl) {
201
- return this.request("POST", `/v1/customers/${customerId}/portal_sessions`, {
202
- return_url: returnUrl
203
- });
204
- }
205
- // Payment Links
206
- async createPaymentLink(data) {
207
- return this.request("POST", "/v1/payment_links", data);
208
- }
209
- async listPaymentLinks() {
210
- return this.request("GET", "/v1/payment_links");
211
- }
212
- async getPaymentLink(id) {
213
- return this.request("GET", `/v1/payment_links/${id}`, void 0, false);
43
+ async function apiRequest(method, path, body) {
44
+ const secret = getSecret();
45
+ if (!secret) {
46
+ console.error("No API key found. Set VIBECASH_SECRET or run `vibecash wallet create`");
47
+ process.exit(1);
48
+ }
49
+ const url = `${getApiUrl()}/v1${path}`;
50
+ const res = await fetch(url, {
51
+ method,
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ "Authorization": `Bearer ${secret}`
55
+ },
56
+ body: body ? JSON.stringify(body) : void 0
57
+ });
58
+ const data = await res.json();
59
+ if (!res.ok) {
60
+ const msg = data.error?.message || "Unknown error";
61
+ throw new Error(`API Error (${res.status}): ${msg}`);
214
62
  }
215
- async updatePaymentLink(id, data) {
216
- return this.request("PATCH", `/v1/payment_links/${id}`, data);
63
+ return data;
64
+ }
65
+ async function apiRequestNoAuth(method, path, body) {
66
+ const url = `${getApiUrl()}/v1${path}`;
67
+ const res = await fetch(url, {
68
+ method,
69
+ headers: { "Content-Type": "application/json" },
70
+ body: body ? JSON.stringify(body) : void 0
71
+ });
72
+ const data = await res.json();
73
+ if (!res.ok) {
74
+ const msg = data.error?.message || "Unknown error";
75
+ throw new Error(`API Error (${res.status}): ${msg}`);
217
76
  }
218
- };
77
+ return data;
78
+ }
219
79
 
220
80
  // src/utils/output.ts
221
- function formatAmount2(amountCents, currency = "USD") {
222
- return formatAmount(amountCents, currency);
223
- }
224
81
  function output(data, humanFormatter) {
225
82
  const format = getOutputFormat();
226
- if (format === "human" && humanFormatter) {
83
+ if (format === "json") {
84
+ console.log(JSON.stringify(data, null, 2));
85
+ } else if (humanFormatter) {
227
86
  console.log(humanFormatter(data));
228
87
  } else {
229
88
  console.log(JSON.stringify(data, null, 2));
230
89
  }
231
90
  }
232
- function formatDate(timestamp) {
233
- return new Date(timestamp).toLocaleString();
91
+ function success(message) {
92
+ console.log(`\u2713 ${message}`);
234
93
  }
235
- function formatCurrency(amountCents, currency = "USD") {
236
- return formatAmount(amountCents, currency);
94
+ function table(headers, rows) {
95
+ const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || "").length)));
96
+ const top = "\u250C" + colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u2510";
97
+ const bot = "\u2514" + colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u2518";
98
+ const row = (cells) => "\u2502" + cells.map((c, i) => ` ${(c || "").padEnd(colWidths[i])} `).join("\u2502") + "\u2502";
99
+ console.log(top);
100
+ console.log(row(headers));
101
+ console.log("\u251C" + colWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C") + "\u2524");
102
+ rows.forEach((r) => console.log(row(r)));
103
+ console.log(bot);
237
104
  }
238
- function formatStatus(status) {
239
- const colors = {
240
- active: "\x1B[32m",
241
- // green
242
- trialing: "\x1B[33m",
243
- // yellow
244
- past_due: "\x1B[31m",
245
- // red
246
- canceled: "\x1B[90m",
247
- // gray
248
- succeeded: "\x1B[32m",
249
- failed: "\x1B[31m",
250
- pending: "\x1B[33m",
251
- open: "\x1B[33m",
252
- complete: "\x1B[32m",
253
- expired: "\x1B[90m",
254
- created: "\x1B[36m",
255
- // cyan
256
- claimed: "\x1B[32m",
257
- verified: "\x1B[32m"
258
- };
259
- const reset = "\x1B[0m";
260
- const color = colors[status] || "";
261
- return `${color}${status}${reset}`;
105
+ function fmtAmount(cents, currency) {
106
+ if (cents == null) return "N/A";
107
+ const n = typeof cents === "string" ? parseInt(cents, 10) : cents;
108
+ if (isNaN(n)) return String(cents);
109
+ const dec = (n / 100).toFixed(2);
110
+ return currency ? `${dec} ${currency}` : dec;
262
111
  }
263
- function formatTable(rows) {
264
- if (rows.length === 0) return "";
265
- const colWidths = [];
266
- for (const row of rows) {
267
- row.forEach((cell, i) => {
268
- const cellLen = stripAnsi(cell).length;
269
- colWidths[i] = Math.max(colWidths[i] || 0, cellLen);
270
- });
271
- }
272
- return rows.map(
273
- (row) => row.map((cell, i) => cell.padEnd(colWidths[i] + (cell.length - stripAnsi(cell).length))).join(" ")
274
- ).join("\n");
275
- }
276
- function stripAnsi(str) {
277
- return str.replace(/\x1b\[[0-9;]*m/g, "");
278
- }
279
- function error(message) {
280
- console.error(`\x1B[31mError:\x1B[0m ${message}`);
281
- process.exit(1);
282
- }
283
- function info(message) {
284
- console.log(`\x1B[36m\u2139\x1B[0m ${message}`);
112
+ function fmtDate(ts) {
113
+ if (ts == null || ts === "") return "N/A";
114
+ const n = typeof ts === "string" ? parseInt(ts, 10) : ts;
115
+ if (isNaN(n) || n === 0) return "N/A";
116
+ const d = new Date(n);
117
+ const pad = (v) => String(v).padStart(2, "0");
118
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
285
119
  }
286
120
 
287
121
  // src/commands/wallet.ts
288
- function createWalletCommand() {
289
- const wallet = new Command("wallet").description("Manage wallets");
122
+ function registerWalletCommands(program2) {
123
+ const wallet = program2.command("wallet").description("Manage your wallet");
290
124
  wallet.command("create").description("Create a new wallet").action(async () => {
291
125
  try {
292
- const client = new ApiClient();
293
- const result = await client.createWallet();
294
- saveConfig({ secret: result.secret });
295
- output(result, (data) => {
296
- const d = data;
126
+ const data = await apiRequestNoAuth("POST", "/wallets");
127
+ if (data.secret) {
128
+ setSecret(data.secret);
129
+ success("Wallet created! API key saved to ~/.vibecash/config.json");
130
+ }
131
+ output(data, (d) => {
132
+ const w = d.wallet || d;
297
133
  return [
298
- `Wallet created successfully!`,
299
- ``,
300
- ` ID: ${d.id}`,
301
- ` Secret: ${d.secret}`,
302
- ` Status: ${formatStatus(d.status)}`,
303
- ``,
304
- `Your secret has been saved to ~/.aipay/config.json`,
305
- `You can also set it as an environment variable:`,
306
- ` export AIPAY_SECRET=${d.secret}`
134
+ `Wallet ID: ${w.id}`,
135
+ `Status: ${w.status || "active"}`,
136
+ `Currency: ${w.currency || "SGD"}`
307
137
  ].join("\n");
308
138
  });
309
139
  } catch (err) {
310
- error(err instanceof Error ? err.message : "Failed to create wallet");
140
+ console.error(`Error: ${err.message}`);
141
+ process.exit(1);
311
142
  }
312
143
  });
313
- wallet.command("status").description("Get current wallet status").action(async () => {
144
+ wallet.command("info").description("Get current wallet info").action(async () => {
314
145
  try {
315
- const client = new ApiClient();
316
- const result = await client.getWallet();
317
- output(result, (data) => {
318
- const d = data;
146
+ const data = await apiRequest("GET", "/wallets/current");
147
+ output(data, (d) => {
319
148
  return [
320
- `Wallet: ${d.id}`,
321
- ``,
322
- ` Status: ${formatStatus(d.status)}`,
323
- ` Balance: ${formatCurrency(d.balance, d.currency)}`,
324
- ` Pending Balance: ${formatCurrency(d.pendingBalance, d.currency)}`,
325
- ` Created: ${formatDate(d.createdAt)}`
149
+ `Wallet ID: ${d.id}`,
150
+ `Status: ${d.status || "N/A"}`,
151
+ `Currency: ${d.currency || "N/A"}`,
152
+ `Available: ${fmtAmount(d.availableBalance, d.currency)}`,
153
+ `Pending: ${fmtAmount(d.pendingBalance, d.currency)}`,
154
+ `Withdrawn: ${fmtAmount(d.withdrawnBalance, d.currency)}`,
155
+ `Webhook: ${d.webhookUrl || "not configured"}`,
156
+ `Created: ${fmtDate(d.createdAt)}`
326
157
  ].join("\n");
327
158
  });
328
159
  } catch (err) {
329
- error(err instanceof Error ? err.message : "Failed to get wallet status");
160
+ console.error(`Error: ${err.message}`);
161
+ process.exit(1);
330
162
  }
331
163
  });
332
- wallet.command("claim").description("Generate a claim link for this wallet").action(async () => {
164
+ wallet.command("claim").description("Generate a claim link for your wallet").action(async () => {
333
165
  try {
334
- const client = new ApiClient();
335
- const wallet2 = await client.getWallet();
336
- if (wallet2.status === "claimed" || wallet2.status === "verified") {
337
- error("This wallet has already been claimed and cannot be claimed again.");
338
- return;
339
- }
340
- const result = await client.createClaimLink();
341
- output(result, (data) => {
342
- const d = data;
343
- return [
344
- `Claim link generated!`,
345
- ``,
346
- ` URL: ${d.claimUrl}`,
347
- ` Expires: ${formatDate(d.expiresAt)}`,
348
- ``,
349
- `Share this link to allow someone to claim ownership of this wallet.`
350
- ].join("\n");
166
+ const data = await apiRequest("POST", "/wallets/current/claim_link");
167
+ output(data, (d) => {
168
+ return `Claim Link: ${d.url || d.claimLink || d.link || JSON.stringify(d)}`;
351
169
  });
352
170
  } catch (err) {
353
- error(err instanceof Error ? err.message : "Failed to generate claim link");
171
+ console.error(`Error: ${err.message}`);
172
+ process.exit(1);
354
173
  }
355
174
  });
356
- return wallet;
357
175
  }
358
176
 
359
177
  // src/commands/product.ts
360
- import { Command as Command2 } from "commander";
361
- function createProductCommand() {
362
- const product = new Command2("product").description("Manage products");
363
- product.command("create").description("Create a new product").argument("<name>", "Product name").option("-d, --description <description>", "Product description").action(async (name, options) => {
364
- try {
365
- const client = new ApiClient();
366
- const result = await client.createProduct({
367
- name,
368
- description: options.description
369
- });
370
- output(result, (data) => {
371
- const d = data;
372
- return [
373
- `Product created!`,
374
- ``,
375
- ` ID: ${d.id}`,
376
- ` Name: ${d.name}`,
377
- d.description ? ` Description: ${d.description}` : null,
378
- ` Active: ${d.active}`,
379
- ` Created: ${formatDate(d.createdAt)}`
380
- ].filter(Boolean).join("\n");
381
- });
178
+ function registerProductCommands(program2) {
179
+ const product = program2.command("product").description("Manage products");
180
+ product.command("create <name>").description("Create a new product").option("-d, --description <description>", "Product description").action(async (name, opts) => {
181
+ try {
182
+ const body = { name };
183
+ if (opts.description) body.description = opts.description;
184
+ const data = await apiRequest("POST", "/products", body);
185
+ success("Product created");
186
+ output(data, (d) => `Product ID: ${d.id}
187
+ Name: ${d.name}`);
382
188
  } catch (err) {
383
- error(err instanceof Error ? err.message : "Failed to create product");
189
+ console.error(`Error: ${err.message}`);
190
+ process.exit(1);
384
191
  }
385
192
  });
386
193
  product.command("list").description("List all products").action(async () => {
387
194
  try {
388
- const client = new ApiClient();
389
- const result = await client.listProducts();
390
- output(result, (data) => {
391
- const d = data;
392
- if (d.data.length === 0) {
393
- return "No products found.";
394
- }
395
- const rows = [
396
- ["ID", "NAME", "ACTIVE", "CREATED"],
397
- ...d.data.map((p) => [p.id, p.name, p.active ? "yes" : "no", formatDate(p.createdAt)])
398
- ];
399
- return formatTable(rows);
195
+ const data = await apiRequest("GET", "/products");
196
+ output(data, (d) => {
197
+ const items = d.data || d.products || d;
198
+ if (!Array.isArray(items) || items.length === 0) return "No products found.";
199
+ table(
200
+ ["ID", "Name", "Created"],
201
+ items.map((p) => [p.id, p.name, fmtDate(p.createdAt)])
202
+ );
203
+ return "";
400
204
  });
401
205
  } catch (err) {
402
- error(err instanceof Error ? err.message : "Failed to list products");
206
+ console.error(`Error: ${err.message}`);
207
+ process.exit(1);
403
208
  }
404
209
  });
405
- product.command("get").description("Get product details").argument("<id>", "Product ID").action(async (id) => {
210
+ product.command("get <id>").description("Get a product by ID").action(async (id) => {
406
211
  try {
407
- const client = new ApiClient();
408
- const result = await client.getProduct(id);
409
- output(result, (data) => {
410
- const d = data;
411
- return [
412
- `Product: ${d.id}`,
413
- ``,
414
- ` Name: ${d.name}`,
415
- d.description ? ` Description: ${d.description}` : null,
416
- ` Active: ${d.active}`,
417
- ` Created: ${formatDate(d.createdAt)}`
418
- ].filter(Boolean).join("\n");
419
- });
212
+ const data = await apiRequest("GET", `/products/${id}`);
213
+ output(data, (d) => [
214
+ `Product ID: ${d.id}`,
215
+ `Name: ${d.name}`,
216
+ `Description: ${d.description || "N/A"}`,
217
+ `Created: ${fmtDate(d.createdAt)}`
218
+ ].join("\n"));
219
+ } catch (err) {
220
+ console.error(`Error: ${err.message}`);
221
+ process.exit(1);
222
+ }
223
+ });
224
+ product.command("update <id>").description("Update a product").option("-n, --name <name>", "New product name").option("-d, --description <description>", "New product description").action(async (id, opts) => {
225
+ try {
226
+ const body = {};
227
+ if (opts.name) body.name = opts.name;
228
+ if (opts.description) body.description = opts.description;
229
+ await apiRequest("PATCH", `/products/${id}`, body);
230
+ success("Product updated");
420
231
  } catch (err) {
421
- error(err instanceof Error ? err.message : "Failed to get product");
232
+ console.error(`Error: ${err.message}`);
233
+ process.exit(1);
234
+ }
235
+ });
236
+ product.command("delete <id>").description("Delete a product").action(async (id) => {
237
+ try {
238
+ await apiRequest("DELETE", `/products/${id}`);
239
+ success(`Product ${id} deleted`);
240
+ } catch (err) {
241
+ console.error(`Error: ${err.message}`);
242
+ process.exit(1);
422
243
  }
423
244
  });
424
- return product;
425
245
  }
426
246
 
427
247
  // src/commands/price.ts
428
- import { Command as Command3 } from "commander";
429
- function createPriceCommand() {
430
- const price = new Command3("price").description("Manage prices");
431
- price.command("create").description("Create a new price").argument("<product_id>", "Product ID").requiredOption("-a, --amount <amount>", "Price amount (e.g., 9.99)").requiredOption("-t, --type <type>", "Price type (one_time or recurring)").option("-c, --currency <currency>", "Currency code", "USD").option("-i, --interval <interval>", "Billing interval (day, week, month, year)").option("--interval-count <count>", "Number of intervals between billings", "1").option("--trial-days <days>", "Trial period in days").action(
432
- async (productId, options) => {
433
- try {
434
- const type = options.type;
435
- if (!["one_time", "recurring"].includes(type)) {
436
- error("Type must be one_time or recurring");
437
- return;
438
- }
439
- if (type === "recurring" && !options.interval) {
440
- error("Interval is required for recurring prices");
441
- return;
442
- }
443
- const client = new ApiClient();
444
- const result = await client.createPrice({
445
- productId,
446
- amount: parseAmountToCents(options.amount),
447
- currency: options.currency,
448
- type,
449
- interval: options.interval,
450
- intervalCount: parseInt(options.intervalCount, 10),
451
- trialPeriodDays: options.trialDays ? parseInt(options.trialDays, 10) : void 0
452
- });
453
- output(result, (data) => {
454
- const d = data;
455
- const interval = d.type === "recurring" && d.interval ? d.intervalCount === 1 ? `/${d.interval}` : `/${d.intervalCount} ${d.interval}s` : "";
456
- return [
457
- `Price created!`,
458
- ``,
459
- ` ID: ${d.id}`,
460
- ` Amount: ${formatCurrency(d.amount, d.currency)}${interval}`,
461
- ` Type: ${d.type}`,
462
- d.interval ? ` Interval: ${d.interval}` : null,
463
- d.trialPeriodDays ? ` Trial: ${d.trialPeriodDays} days` : null,
464
- ` Created: ${formatDate(d.createdAt)}`
465
- ].filter(Boolean).join("\n");
466
- });
467
- } catch (err) {
468
- error(err instanceof Error ? err.message : "Failed to create price");
469
- }
248
+ function registerPriceCommands(program2) {
249
+ const price = program2.command("price").description("Manage prices");
250
+ price.command("create <productId>").description("Create a new price for a product").requiredOption("-a, --amount <amount>", "Price amount in cents (e.g. 1000 = $10.00)").requiredOption("-t, --type <type>", "Price type (one_time or recurring)").option("-c, --currency <currency>", "Currency code", "SGD").option("-i, --interval <interval>", "Billing interval (month, year) \u2014 required for recurring").action(async (productId, opts) => {
251
+ try {
252
+ const body = {
253
+ productId,
254
+ unitAmount: parseInt(opts.amount, 10),
255
+ type: opts.type,
256
+ currency: opts.currency.toUpperCase()
257
+ };
258
+ if (opts.interval) body.interval = opts.interval;
259
+ const data = await apiRequest("POST", "/prices", body);
260
+ success("Price created");
261
+ output(data, (d) => [
262
+ `Price ID: ${d.id}`,
263
+ `Amount: ${fmtAmount(d.unitAmount ?? d.amount, d.currency)}`,
264
+ `Type: ${d.type}`,
265
+ d.interval ? `Interval: ${d.interval}` : null
266
+ ].filter(Boolean).join("\n"));
267
+ } catch (err) {
268
+ console.error(`Error: ${err.message}`);
269
+ process.exit(1);
470
270
  }
471
- );
472
- price.command("list").description("List prices").option("-p, --product <id>", "Filter by product ID").action(async (options) => {
271
+ });
272
+ price.command("list").description("List all prices").action(async () => {
473
273
  try {
474
- const client = new ApiClient();
475
- const result = await client.listPrices(options.product);
476
- output(result, (data) => {
477
- const d = data;
478
- if (d.data.length === 0) {
479
- return "No prices found.";
480
- }
481
- const rows = [
482
- ["ID", "PRODUCT", "AMOUNT", "TYPE", "INTERVAL"],
483
- ...d.data.map((p) => [
274
+ const data = await apiRequest("GET", "/prices");
275
+ output(data, (d) => {
276
+ const items = d.data || d.prices || d;
277
+ if (!Array.isArray(items) || items.length === 0) return "No prices found.";
278
+ table(
279
+ ["ID", "Amount", "Type", "Interval"],
280
+ items.map((p) => [
484
281
  p.id,
485
- p.productId,
486
- formatCurrency(p.amount, p.currency),
282
+ fmtAmount(p.unitAmount ?? p.amount, p.currency),
487
283
  p.type,
488
284
  p.interval || "-"
489
285
  ])
490
- ];
491
- return formatTable(rows);
286
+ );
287
+ return "";
492
288
  });
493
289
  } catch (err) {
494
- error(err instanceof Error ? err.message : "Failed to list prices");
290
+ console.error(`Error: ${err.message}`);
291
+ process.exit(1);
495
292
  }
496
293
  });
497
- return price;
498
294
  }
499
295
 
500
296
  // src/commands/checkout.ts
501
- import { Command as Command4 } from "commander";
502
- function createCheckoutCommand() {
503
- const checkout = new Command4("checkout").description("Manage checkout sessions");
504
- checkout.command("create").description("Create a checkout session").option("-p, --price <id>", "Price ID").option("-a, --amount <amount>", "Amount for inline price (e.g., 9.99)").option("-d, --description <desc>", "Description for inline price").option("-m, --mode <mode>", "Checkout mode (payment, subscription)", "payment").option("--success-url <url>", "Success redirect URL").option("--cancel-url <url>", "Cancel redirect URL").option("--email <email>", "Customer email").option("-c, --currency <currency>", "Currency code (default: USD)", "USD").option("--trial-days <days>", "Trial period days (for subscriptions)").action(
505
- async (options) => {
506
- try {
507
- if (!options.price && !options.amount) {
508
- error("Either --price or --amount is required");
509
- return;
510
- }
511
- const mode = options.mode;
512
- if (!["payment", "subscription", "setup"].includes(mode)) {
513
- error("Mode must be payment, subscription, or setup");
514
- return;
515
- }
516
- const client = new ApiClient();
517
- const params = {
518
- mode,
519
- successUrl: options.successUrl,
520
- cancelUrl: options.cancelUrl,
521
- customerEmail: options.email,
522
- trialPeriodDays: options.trialDays ? parseInt(options.trialDays, 10) : void 0
523
- };
524
- if (options.price) {
525
- params.priceId = options.price;
526
- } else if (options.amount) {
527
- params.lineItems = [
528
- {
529
- description: options.description,
530
- amount: parseAmountToCents(options.amount),
531
- currency: options.currency.toUpperCase(),
532
- quantity: 1
533
- }
534
- ];
535
- }
536
- const result = await client.createCheckoutSession(params);
537
- output(result, (data) => {
538
- const d = data;
539
- return [
540
- `Checkout session created!`,
541
- ``,
542
- ` ID: ${d.id}`,
543
- ` Mode: ${d.mode}`,
544
- ` Status: ${formatStatus(d.status)}`,
545
- d.amountTotal ? ` Amount: ${formatCurrency(d.amountTotal, d.currency || "USD")}` : null,
546
- ` URL: ${d.url}`,
547
- ` Expires: ${formatDate(d.expiresAt)}`
548
- ].filter(Boolean).join("\n");
549
- });
550
- } catch (err) {
551
- error(err instanceof Error ? err.message : "Failed to create checkout session");
552
- }
297
+ function registerCheckoutCommands(program2) {
298
+ const checkout = program2.command("checkout").description("Manage checkout sessions");
299
+ checkout.command("create").description("Create a new checkout session").requiredOption("-p, --price-id <priceId>", "Price ID to checkout").requiredOption("--success-url <url>", "URL to redirect on success").requiredOption("--cancel-url <url>", "URL to redirect on cancel").option("-m, --mode <mode>", "payment or subscription", "payment").option("-q, --quantity <quantity>", "Quantity", "1").option("--methods <methods>", "Payment methods (comma-separated: card,alipay,paynow,wechat)", "card,alipay,paynow,wechat").action(async (opts) => {
300
+ try {
301
+ const body = {
302
+ mode: opts.mode,
303
+ lineItems: [{ priceId: opts.priceId, quantity: parseInt(opts.quantity, 10) }],
304
+ successUrl: opts.successUrl,
305
+ cancelUrl: opts.cancelUrl,
306
+ paymentMethodTypes: opts.methods.split(",").map((m) => m.trim())
307
+ };
308
+ const data = await apiRequest("POST", "/checkout/sessions", body);
309
+ success("Checkout session created");
310
+ output(data, (d) => {
311
+ return [
312
+ `Session ID: ${d.id}`,
313
+ `Checkout URL: ${d.url || d.checkoutUrl || "N/A"}`
314
+ ].join("\n");
315
+ });
316
+ } catch (err) {
317
+ console.error(`Error: ${err.message}`);
318
+ process.exit(1);
553
319
  }
554
- );
555
- checkout.command("status").description("Get checkout session status").argument("<id>", "Checkout session ID").option("-w, --wait", "Wait for session to complete").option("--timeout <ms>", "Timeout in milliseconds when waiting", "300000").action(async (id, options) => {
556
- try {
557
- const client = new ApiClient();
558
- if (options.wait) {
559
- const timeout = parseInt(options.timeout, 10);
560
- const startTime = Date.now();
561
- info("Waiting for checkout to complete...");
562
- while (Date.now() - startTime < timeout) {
563
- const result = await client.getCheckoutSession(id);
564
- if (result.session.status === "complete") {
565
- output(result, formatCheckoutSession);
566
- return;
567
- }
568
- if (result.session.status === "expired") {
569
- error("Checkout session expired");
570
- return;
571
- }
572
- await new Promise((resolve) => setTimeout(resolve, 2e3));
320
+ });
321
+ checkout.command("get <id>").description("Get a checkout session by ID").option("-w, --watch", 'Poll until status changes from "open" (every 5s)').option("-i, --interval <seconds>", "Poll interval in seconds", "5").action(async (id, opts) => {
322
+ try {
323
+ const formatSession = (d) => {
324
+ return [
325
+ `Session ID: ${d.id}`,
326
+ `Status: ${d.status || "N/A"}`,
327
+ `Mode: ${d.mode || "N/A"}`,
328
+ d.amountTotal != null ? `Amount: ${fmtAmount(d.amountTotal, d.currency)}` : null,
329
+ d.paymentId ? `Payment ID: ${d.paymentId}` : null,
330
+ d.url ? `URL: ${d.url}` : null,
331
+ d.successUrl ? `Success: ${d.successUrl}` : null
332
+ ].filter(Boolean).join("\n");
333
+ };
334
+ if (!opts.watch) {
335
+ const data = await apiRequest("GET", `/checkout/sessions/${id}`);
336
+ output(data, formatSession);
337
+ return;
338
+ }
339
+ const intervalMs = parseInt(opts.interval || "5", 10) * 1e3;
340
+ console.log(`Watching session ${id} (every ${intervalMs / 1e3}s)...
341
+ `);
342
+ while (true) {
343
+ const data = await apiRequest("GET", `/checkout/sessions/${id}`);
344
+ const status = data.status || "unknown";
345
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
346
+ process.stdout.write(`\r[${ts}] Status: ${status} `);
347
+ if (status !== "open") {
348
+ console.log("\n");
349
+ output(data, formatSession);
350
+ break;
573
351
  }
574
- error("Timeout waiting for checkout to complete");
575
- } else {
576
- const result = await client.getCheckoutSession(id);
577
- output(result, formatCheckoutSession);
352
+ await new Promise((r) => setTimeout(r, intervalMs));
578
353
  }
579
354
  } catch (err) {
580
- error(err instanceof Error ? err.message : "Failed to get checkout session");
355
+ console.error(`Error: ${err.message}`);
356
+ process.exit(1);
581
357
  }
582
358
  });
583
- return checkout;
584
- }
585
- function formatCheckoutSession(data) {
586
- const d = data;
587
- const lines = [
588
- `Checkout Session: ${d.session.id}`,
589
- ``,
590
- ` Status: ${formatStatus(d.session.status)}`,
591
- ` Mode: ${d.session.mode}`
592
- ];
593
- if (d.product) {
594
- lines.push(` Product: ${d.product.name} (${d.product.id})`);
595
- }
596
- if (d.price) {
597
- lines.push(` Price: ${formatCurrency(d.price.amount, d.price.currency)} (${d.price.id})`);
598
- } else if (d.session.amountTotal) {
599
- lines.push(` Amount: ${formatCurrency(d.session.amountTotal, d.session.currency || "USD")}`);
600
- }
601
- lines.push(` URL: ${d.session.url}`);
602
- lines.push(` Created: ${formatDate(d.session.createdAt)}`);
603
- if (d.session.completedAt) {
604
- lines.push(` Completed: ${formatDate(d.session.completedAt)}`);
605
- }
606
- return lines.join("\n");
607
359
  }
608
360
 
609
361
  // src/commands/subscription.ts
610
- import { Command as Command5 } from "commander";
611
- function createSubscriptionCommand() {
612
- const subscription = new Command5("subscription").description("Manage subscriptions");
613
- subscription.command("list").description("List subscriptions").option("-c, --customer <id>", "Filter by customer ID").option("-s, --status <status>", "Filter by status").action(async (options) => {
614
- try {
615
- const client = new ApiClient();
616
- const result = await client.listSubscriptions({
617
- customerId: options.customer,
618
- status: options.status
619
- });
620
- output(result, (data) => {
621
- const d = data;
622
- if (d.data.length === 0) {
623
- return "No subscriptions found.";
624
- }
625
- const rows = [
626
- ["ID", "CUSTOMER", "STATUS", "PERIOD END", "CANCEL"],
627
- ...d.data.map((s) => [
628
- s.id,
629
- s.customerId,
630
- formatStatus(s.status),
631
- formatDate(s.currentPeriodEnd),
632
- s.cancelAtPeriodEnd ? "yes" : "no"
633
- ])
634
- ];
635
- return formatTable(rows);
362
+ function registerSubscriptionCommands(program2) {
363
+ const subscription = program2.command("subscription").description("Manage subscriptions");
364
+ subscription.command("list").description("List all subscriptions").action(async () => {
365
+ try {
366
+ const data = await apiRequest("GET", "/subscriptions");
367
+ output(data, (d) => {
368
+ const items = d.data || d.subscriptions || d;
369
+ if (!Array.isArray(items) || items.length === 0) return "No subscriptions found.";
370
+ table(
371
+ ["ID", "Status", "Customer", "Price", "Created"],
372
+ items.map((s) => [s.id, s.status, s.customerId || "N/A", s.priceId || "N/A", fmtDate(s.createdAt)])
373
+ );
374
+ return "";
636
375
  });
637
376
  } catch (err) {
638
- error(err instanceof Error ? err.message : "Failed to list subscriptions");
377
+ console.error(`Error: ${err.message}`);
378
+ process.exit(1);
639
379
  }
640
380
  });
641
- subscription.command("get").description("Get subscription details").argument("<id>", "Subscription ID").action(async (id) => {
381
+ subscription.command("get <id>").description("Get a subscription by ID").action(async (id) => {
642
382
  try {
643
- const client = new ApiClient();
644
- const result = await client.getSubscription(id);
645
- output(result, (data) => {
646
- const d = data;
647
- return [
648
- `Subscription: ${d.id}`,
649
- ``,
650
- ` Customer: ${d.customerId}`,
651
- ` Price: ${d.priceId}`,
652
- ` Status: ${formatStatus(d.status)}`,
653
- ` Period Start: ${formatDate(d.currentPeriodStart)}`,
654
- ` Period End: ${formatDate(d.currentPeriodEnd)}`,
655
- d.trialEnd ? ` Trial End: ${formatDate(d.trialEnd)}` : null,
656
- ` Cancel: ${d.cancelAtPeriodEnd ? "at period end" : "no"}`,
657
- ` Created: ${formatDate(d.createdAt)}`
658
- ].filter(Boolean).join("\n");
659
- });
383
+ const data = await apiRequest("GET", `/subscriptions/${id}`);
384
+ output(data, (d) => [
385
+ `Subscription ID: ${d.id}`,
386
+ `Status: ${d.status}`,
387
+ `Customer: ${d.customerId || "N/A"}`,
388
+ `Price: ${d.priceId || "N/A"}`,
389
+ `Current Period End: ${fmtDate(d.currentPeriodEnd)}`,
390
+ `Created: ${fmtDate(d.createdAt)}`
391
+ ].join("\n"));
660
392
  } catch (err) {
661
- error(err instanceof Error ? err.message : "Failed to get subscription");
393
+ console.error(`Error: ${err.message}`);
394
+ process.exit(1);
662
395
  }
663
396
  });
664
- subscription.command("cancel").description("Cancel a subscription").argument("<id>", "Subscription ID").option("--immediately", "Cancel immediately instead of at period end").action(async (id, options) => {
397
+ subscription.command("cancel <id>").description("Cancel a subscription").action(async (id) => {
665
398
  try {
666
- const client = new ApiClient();
667
- const result = await client.cancelSubscription(id, options.immediately);
668
- output(result, (data) => {
669
- const d = data;
670
- const when = options.immediately ? "immediately" : "at period end";
671
- return [
672
- `Subscription ${d.id} will be canceled ${when}.`,
673
- ``,
674
- ` Status: ${formatStatus(d.status)}`,
675
- ` Cancel: ${d.cancelAtPeriodEnd ? "at period end" : "immediately"}`,
676
- d.canceledAt ? ` Canceled At: ${formatDate(d.canceledAt)}` : null
677
- ].filter(Boolean).join("\n");
678
- });
399
+ await apiRequest("POST", `/subscriptions/${id}/cancel`);
400
+ success(`Subscription ${id} cancelled`);
679
401
  } catch (err) {
680
- error(err instanceof Error ? err.message : "Failed to cancel subscription");
402
+ console.error(`Error: ${err.message}`);
403
+ process.exit(1);
681
404
  }
682
405
  });
683
- subscription.command("resume").description("Resume a canceled subscription").argument("<id>", "Subscription ID").action(async (id) => {
406
+ subscription.command("resume <id>").description("Resume a cancelled subscription").action(async (id) => {
684
407
  try {
685
- const client = new ApiClient();
686
- const result = await client.resumeSubscription(id);
687
- output(result, (data) => {
688
- const d = data;
689
- return [
690
- `Subscription ${d.id} has been resumed.`,
691
- ``,
692
- ` Status: ${formatStatus(d.status)}`,
693
- ` Cancel: ${d.cancelAtPeriodEnd ? "at period end" : "no"}`
694
- ].join("\n");
695
- });
408
+ await apiRequest("POST", `/subscriptions/${id}/resume`);
409
+ success(`Subscription ${id} resumed`);
696
410
  } catch (err) {
697
- error(err instanceof Error ? err.message : "Failed to resume subscription");
411
+ console.error(`Error: ${err.message}`);
412
+ process.exit(1);
698
413
  }
699
414
  });
700
- return subscription;
701
415
  }
702
416
 
703
417
  // src/commands/customer.ts
704
- import { Command as Command6 } from "commander";
705
- function createCustomerCommand() {
706
- const customer = new Command6("customer").description("Manage customers");
707
- customer.command("create").description("Create a new customer").requiredOption("-e, --email <email>", "Customer email").option("-n, --name <name>", "Customer name").action(async (options) => {
708
- try {
709
- const client = new ApiClient();
710
- const result = await client.createCustomer({
711
- email: options.email,
712
- name: options.name
713
- });
714
- output(result, (data) => {
715
- const d = data;
716
- return [
717
- `Customer created!`,
718
- ``,
719
- ` ID: ${d.id}`,
720
- ` Email: ${d.email}`,
721
- d.name ? ` Name: ${d.name}` : null,
722
- ` Created: ${formatDate(d.createdAt)}`
723
- ].filter(Boolean).join("\n");
724
- });
418
+ function registerCustomerCommands(program2) {
419
+ const customer = program2.command("customer").description("Manage customers");
420
+ customer.command("create").description("Create a new customer").requiredOption("-e, --email <email>", "Customer email").option("-n, --name <name>", "Customer name").action(async (opts) => {
421
+ try {
422
+ const body = { email: opts.email };
423
+ if (opts.name) body.name = opts.name;
424
+ const data = await apiRequest("POST", "/customers", body);
425
+ success("Customer created");
426
+ output(data, (d) => [
427
+ `Customer ID: ${d.id}`,
428
+ `Email: ${d.email}`,
429
+ `Name: ${d.name || "N/A"}`
430
+ ].join("\n"));
725
431
  } catch (err) {
726
- error(err instanceof Error ? err.message : "Failed to create customer");
432
+ console.error(`Error: ${err.message}`);
433
+ process.exit(1);
727
434
  }
728
435
  });
729
436
  customer.command("list").description("List all customers").action(async () => {
730
437
  try {
731
- const client = new ApiClient();
732
- const result = await client.listCustomers();
733
- output(result, (data) => {
734
- const d = data;
735
- if (d.data.length === 0) {
736
- return "No customers found.";
737
- }
738
- const rows = [
739
- ["ID", "EMAIL", "NAME", "CREATED"],
740
- ...d.data.map((c) => [c.id, c.email, c.name || "-", formatDate(c.createdAt)])
741
- ];
742
- return formatTable(rows);
438
+ const data = await apiRequest("GET", "/customers");
439
+ output(data, (d) => {
440
+ const items = d.data || d.customers || d;
441
+ if (!Array.isArray(items) || items.length === 0) return "No customers found.";
442
+ table(
443
+ ["ID", "Email", "Name", "Created"],
444
+ items.map((c) => [c.id, c.email, c.name || "", fmtDate(c.createdAt)])
445
+ );
446
+ return "";
743
447
  });
744
448
  } catch (err) {
745
- error(err instanceof Error ? err.message : "Failed to list customers");
449
+ console.error(`Error: ${err.message}`);
450
+ process.exit(1);
451
+ }
452
+ });
453
+ customer.command("get <id>").description("Get a customer by ID").action(async (id) => {
454
+ try {
455
+ const data = await apiRequest("GET", `/customers/${id}`);
456
+ output(data, (d) => [
457
+ `Customer ID: ${d.id}`,
458
+ `Email: ${d.email}`,
459
+ `Name: ${d.name || "N/A"}`,
460
+ `Created: ${fmtDate(d.createdAt)}`
461
+ ].join("\n"));
462
+ } catch (err) {
463
+ console.error(`Error: ${err.message}`);
464
+ process.exit(1);
746
465
  }
747
466
  });
748
- customer.command("get").description("Get customer details").argument("<id>", "Customer ID").action(async (id) => {
467
+ customer.command("portal <id>").description("Create a customer portal session").action(async (id) => {
749
468
  try {
750
- const client = new ApiClient();
751
- const result = await client.getCustomer(id);
752
- output(result, (data) => {
753
- const d = data;
469
+ const data = await apiRequest("POST", `/customers/${id}/portal-session`);
470
+ success("Portal session created");
471
+ output(data, (d) => `Portal URL: ${d.url || d.portalUrl || JSON.stringify(d)}`);
472
+ } catch (err) {
473
+ console.error(`Error: ${err.message}`);
474
+ process.exit(1);
475
+ }
476
+ });
477
+ }
478
+
479
+ // src/commands/link.ts
480
+ function registerLinkCommands(program2) {
481
+ const link = program2.command("link").description("Manage payment links");
482
+ link.command("create <amount>").description("Create a new payment link (amount in cents, e.g. 1000 = $10.00)").option("-c, --currency <currency>", "Currency code", "SGD").option("-n, --name <name>", "Link name").option("-d, --description <description>", "Link description").option("--success-url <url>", "URL to redirect after successful payment").option("--cancel-url <url>", "URL to redirect on cancel").option("--methods <methods>", "Payment methods (comma-separated: card,alipay,paynow,wechat)").action(async (amount, opts) => {
483
+ try {
484
+ const body = {
485
+ amount: parseInt(amount, 10),
486
+ currency: opts.currency
487
+ };
488
+ if (opts.name) body.name = opts.name;
489
+ if (opts.description) body.description = opts.description;
490
+ if (opts.successUrl) body.successUrl = opts.successUrl;
491
+ if (opts.cancelUrl) body.cancelUrl = opts.cancelUrl;
492
+ if (opts.methods) body.paymentMethodTypes = opts.methods.split(",").map((m) => m.trim());
493
+ const data = await apiRequest("POST", "/payment_links", body);
494
+ success("Payment link created");
495
+ output(data, (d) => {
754
496
  return [
755
- `Customer: ${d.id}`,
756
- ``,
757
- ` Email: ${d.email}`,
758
- d.name ? ` Name: ${d.name}` : null,
759
- ` Created: ${formatDate(d.createdAt)}`
497
+ `Link ID: ${d.id}`,
498
+ `URL: ${d.url || "N/A"}`,
499
+ `Amount: ${fmtAmount(d.amount, d.currency)}`,
500
+ d.description ? `Desc: ${d.description}` : null
760
501
  ].filter(Boolean).join("\n");
761
502
  });
762
503
  } catch (err) {
763
- error(err instanceof Error ? err.message : "Failed to get customer");
504
+ console.error(`Error: ${err.message}`);
505
+ process.exit(1);
764
506
  }
765
507
  });
766
- customer.command("portal").description("Create a customer portal session").argument("<id>", "Customer ID").option("-r, --return-url <url>", "Return URL after portal session").action(async (id, options) => {
508
+ link.command("list").description("List all payment links").action(async () => {
767
509
  try {
768
- const client = new ApiClient();
769
- const result = await client.createPortalSession(id, options.returnUrl);
770
- output(result, (data) => {
771
- const d = data;
510
+ const data = await apiRequest("GET", "/payment_links");
511
+ output(data, (d) => {
512
+ const items = d.data || d.paymentLinks || d;
513
+ if (!Array.isArray(items) || items.length === 0) return "No payment links found.";
514
+ table(
515
+ ["ID", "Amount", "Active", "Description", "Created"],
516
+ items.map((l) => [
517
+ l.id,
518
+ fmtAmount(l.amount, l.currency),
519
+ l.active ? "yes" : "no",
520
+ l.description || "",
521
+ fmtDate(l.createdAt)
522
+ ])
523
+ );
524
+ return "";
525
+ });
526
+ } catch (err) {
527
+ console.error(`Error: ${err.message}`);
528
+ process.exit(1);
529
+ }
530
+ });
531
+ link.command("get <id>").description("Get a payment link by ID").action(async (id) => {
532
+ try {
533
+ const data = await apiRequest("GET", `/payment_links/${id}`);
534
+ output(data, (d) => {
772
535
  return [
773
- `Portal session created!`,
774
- ``,
775
- ` ID: ${d.id}`,
776
- ` URL: ${d.url}`,
777
- ` Expires: ${formatDate(d.expiresAt)}`
536
+ `Link ID: ${d.id}`,
537
+ `URL: ${d.url || "N/A"}`,
538
+ `Amount: ${fmtAmount(d.amount, d.currency)}`,
539
+ `Active: ${d.active ? "yes" : "no"}`,
540
+ `Desc: ${d.description || "N/A"}`,
541
+ `Created: ${fmtDate(d.createdAt)}`
778
542
  ].join("\n");
779
543
  });
780
544
  } catch (err) {
781
- error(err instanceof Error ? err.message : "Failed to create portal session");
545
+ console.error(`Error: ${err.message}`);
546
+ process.exit(1);
547
+ }
548
+ });
549
+ link.command("deactivate <id>").description("Deactivate a payment link").action(async (id) => {
550
+ try {
551
+ await apiRequest("PATCH", `/payment_links/${id}`, { active: false });
552
+ success(`Payment link ${id} deactivated`);
553
+ } catch (err) {
554
+ console.error(`Error: ${err.message}`);
555
+ process.exit(1);
556
+ }
557
+ });
558
+ link.command("activate <id>").description("Activate a payment link").action(async (id) => {
559
+ try {
560
+ await apiRequest("PATCH", `/payment_links/${id}`, { active: true });
561
+ success(`Payment link ${id} activated`);
562
+ } catch (err) {
563
+ console.error(`Error: ${err.message}`);
564
+ process.exit(1);
782
565
  }
783
566
  });
784
- return customer;
785
567
  }
786
568
 
787
569
  // src/commands/create.ts
788
- import { Command as Command7 } from "commander";
789
- function createQuickCreateCommand() {
790
- const create = new Command7("create").description("Quick command to create a checkout session").argument("<amount>", "Amount to charge (e.g., 9.99)").option("-d, --description <desc>", "Product/payment description").option("--monthly", "Create a monthly subscription").option("--yearly", "Create a yearly subscription").option("--weekly", "Create a weekly subscription").option("--trial-days <days>", "Trial period in days (for subscriptions)").option("--success-url <url>", "Success redirect URL").option("--cancel-url <url>", "Cancel redirect URL").option("--email <email>", "Customer email").action(
791
- async (amountStr, options) => {
792
- try {
793
- const client = new ApiClient();
794
- const amount = parseAmountToCents(amountStr);
795
- let mode = "payment";
796
- let interval;
797
- if (options.monthly || options.yearly || options.weekly) {
798
- mode = "subscription";
799
- if (options.monthly) interval = "month";
800
- else if (options.yearly) interval = "year";
801
- else if (options.weekly) interval = "week";
802
- }
803
- if (mode === "subscription") {
804
- const product = await client.createProduct({
805
- name: options.description || "Subscription"
806
- });
807
- const price = await client.createPrice({
808
- productId: product.id,
809
- amount,
810
- currency: "USD",
811
- type: "recurring",
812
- interval,
813
- trialPeriodDays: options.trialDays ? parseInt(options.trialDays, 10) : void 0
814
- });
815
- const session = await client.createCheckoutSession({
816
- mode: "subscription",
817
- priceId: price.id,
818
- successUrl: options.successUrl,
819
- cancelUrl: options.cancelUrl,
820
- customerEmail: options.email
821
- });
822
- output(
823
- {
824
- product,
825
- price,
826
- session
827
- },
828
- (data) => {
829
- const d = data;
830
- return [
831
- `Subscription checkout created!`,
832
- ``,
833
- ` Product: ${d.product.name} (${d.product.id})`,
834
- ` Price: ${formatCurrency(d.price.amount, d.price.currency)}/${d.price.interval} (${d.price.id})`,
835
- ``,
836
- ` Session: ${d.session.id}`,
837
- ` Status: ${formatStatus(d.session.status)}`,
838
- ` URL: ${d.session.url}`,
839
- ` Expires: ${formatDate(d.session.expiresAt)}`
840
- ].join("\n");
841
- }
842
- );
843
- } else {
844
- const session = await client.createCheckoutSession({
845
- mode: "payment",
846
- lineItems: [
847
- {
848
- description: options.description,
849
- amount,
850
- currency: "USD",
851
- quantity: 1
852
- }
853
- ],
854
- successUrl: options.successUrl,
855
- cancelUrl: options.cancelUrl,
856
- customerEmail: options.email
857
- });
858
- output(session, (data) => {
859
- const d = data;
860
- return [
861
- `Payment checkout created!`,
862
- ``,
863
- ` Session: ${d.id}`,
864
- ` Amount: ${formatCurrency(d.amountTotal || 0, d.currency || "USD")}`,
865
- ` Status: ${formatStatus(d.status)}`,
866
- ` URL: ${d.url}`,
867
- ` Expires: ${formatDate(d.expiresAt)}`
868
- ].join("\n");
869
- });
570
+ import { createInterface } from "readline";
571
+ function prompt(question) {
572
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
573
+ return new Promise((resolve) => {
574
+ rl.question(question, (answer) => {
575
+ rl.close();
576
+ resolve(answer.trim());
577
+ });
578
+ });
579
+ }
580
+ function registerCreateCommand(program2) {
581
+ program2.command("create").description("Quick create: product + price + checkout session in one go").action(async () => {
582
+ try {
583
+ const productName = await prompt("Product name: ");
584
+ if (!productName) {
585
+ console.error("Product name is required.");
586
+ process.exit(1);
587
+ }
588
+ const amountStr = await prompt("Amount (in smallest currency unit, e.g. 1000 for $10.00): ");
589
+ const amount = parseInt(amountStr, 10);
590
+ if (isNaN(amount) || amount <= 0) {
591
+ console.error("Invalid amount.");
592
+ process.exit(1);
593
+ }
594
+ const currency = await prompt("Currency (default: SGD): ") || "SGD";
595
+ const type = await prompt("Type (one_time or recurring): ");
596
+ if (type !== "one_time" && type !== "recurring") {
597
+ console.error('Type must be "one_time" or "recurring".');
598
+ process.exit(1);
599
+ }
600
+ let interval;
601
+ if (type === "recurring") {
602
+ interval = await prompt("Billing interval (month or year): ");
603
+ if (interval !== "month" && interval !== "year") {
604
+ console.error('Interval must be "month" or "year".');
605
+ process.exit(1);
870
606
  }
871
- } catch (err) {
872
- error(err instanceof Error ? err.message : "Failed to create checkout");
873
607
  }
608
+ const successUrl = await prompt("Success URL (default: https://example.com/success): ") || "https://example.com/success";
609
+ const cancelUrl = await prompt("Cancel URL (default: https://example.com/cancel): ") || "https://example.com/cancel";
610
+ console.log("\nCreating product...");
611
+ const product = await apiRequest("POST", "/products", { name: productName });
612
+ success(`Product created: ${product.id}`);
613
+ console.log("Creating price...");
614
+ const priceBody = {
615
+ productId: product.id,
616
+ amount,
617
+ type,
618
+ currency
619
+ };
620
+ if (interval) priceBody.interval = interval;
621
+ const price = await apiRequest("POST", "/prices", priceBody);
622
+ success(`Price created: ${price.id}`);
623
+ console.log("Creating checkout session...");
624
+ const session = await apiRequest("POST", "/checkout/sessions", {
625
+ priceId: price.id,
626
+ successUrl,
627
+ cancelUrl
628
+ });
629
+ success(`Checkout session created: ${session.id}`);
630
+ const checkoutUrl = session.url || session.checkoutUrl;
631
+ if (checkoutUrl) {
632
+ console.log(`
633
+ Checkout URL: ${checkoutUrl}`);
634
+ }
635
+ output({
636
+ product: { id: product.id, name: productName },
637
+ price: { id: price.id, amount, currency, type, interval },
638
+ checkout: { id: session.id, url: checkoutUrl }
639
+ });
640
+ } catch (err) {
641
+ console.error(`Error: ${err.message}`);
642
+ process.exit(1);
874
643
  }
875
- );
876
- return create;
644
+ });
877
645
  }
878
646
 
879
- // src/commands/link.ts
880
- import { Command as Command8 } from "commander";
881
- function createLinkCommand() {
882
- const link = new Command8("link").description("Manage payment links (reusable payment URLs)");
883
- link.command("create").description("Create a new payment link").argument("<amount>", "Amount in dollars (e.g., 9.99)").option("-c, --currency <currency>", "Currency code (default: USD)", "USD").option("-n, --name <name>", "Product/payment name").option("-d, --description <description>", "Description").option("--success-url <url>", "Redirect URL after successful payment").option("--cancel-url <url>", "Redirect URL if customer cancels").action(
884
- async (amountStr, options) => {
885
- try {
886
- const amount = parseAmountToCents(amountStr);
887
- const client = new ApiClient();
888
- const result = await client.createPaymentLink({
889
- amount,
890
- currency: options.currency,
891
- name: options.name,
892
- description: options.description,
893
- successUrl: options.successUrl,
894
- cancelUrl: options.cancelUrl
895
- });
896
- output(result, (data) => {
897
- const d = data;
898
- return [
899
- `Payment Link created!`,
900
- ``,
901
- ` ID: ${d.id}`,
902
- d.name ? ` Name: ${d.name}` : null,
903
- d.description ? ` Description: ${d.description}` : null,
904
- ` Amount: ${formatAmount2(d.amount, d.currency)}`,
905
- ` URL: ${d.url}`,
906
- ` Active: ${d.active}`,
907
- ` Created: ${formatDate(d.createdAt)}`,
908
- ``,
909
- `Share this URL with customers - each visitor gets their own checkout session.`
910
- ].filter(Boolean).join("\n");
911
- });
912
- } catch (err) {
913
- error(err instanceof Error ? err.message : "Failed to create payment link");
914
- }
647
+ // src/commands/payout.ts
648
+ function registerPayoutCommands(program2) {
649
+ const payout = program2.command("payout").description("Manage payouts");
650
+ payout.command("list").description("List all payouts").action(async () => {
651
+ try {
652
+ const data = await apiRequest("GET", "/payouts");
653
+ output(data, (d) => {
654
+ const items = d.data || d.payouts || d;
655
+ if (!Array.isArray(items) || items.length === 0) return "No payouts found.";
656
+ table(
657
+ ["ID", "Amount", "Status", "Created"],
658
+ items.map((p) => [p.id, fmtAmount(p.amount, p.currency), p.status, fmtDate(p.createdAt)])
659
+ );
660
+ return "";
661
+ });
662
+ } catch (err) {
663
+ console.error(`Error: ${err.message}`);
664
+ process.exit(1);
915
665
  }
916
- );
917
- link.command("list").description("List all payment links").action(async () => {
666
+ });
667
+ payout.command("create").description("Create a new payout").requiredOption("-a, --amount <amount>", "Payout amount in cents (e.g. 1000 = $10.00)").requiredOption("-b, --bank-account-id <bankAccountId>", "Bank account ID").action(async (opts) => {
918
668
  try {
919
- const client = new ApiClient();
920
- const result = await client.listPaymentLinks();
921
- output(result, (data) => {
922
- const d = data;
923
- if (d.data.length === 0) {
924
- return "No payment links found.";
925
- }
926
- const rows = [
927
- ["ID", "NAME", "AMOUNT", "ACTIVE", "URL"],
928
- ...d.data.map((l) => [
929
- l.id,
930
- l.name || "-",
931
- formatAmount2(l.amount, l.currency),
932
- l.active ? "yes" : "no",
933
- l.url
669
+ const body = {
670
+ amount: parseInt(opts.amount, 10),
671
+ bankAccountId: opts.bankAccountId
672
+ };
673
+ const data = await apiRequest("POST", "/payouts", body);
674
+ success("Payout created");
675
+ output(data, (d) => [
676
+ `Payout ID: ${d.id}`,
677
+ `Amount: ${fmtAmount(d.amount, d.currency)}`,
678
+ `Status: ${d.status}`
679
+ ].join("\n"));
680
+ } catch (err) {
681
+ console.error(`Error: ${err.message}`);
682
+ process.exit(1);
683
+ }
684
+ });
685
+ payout.command("cancel <id>").description("Cancel a payout").action(async (id) => {
686
+ try {
687
+ await apiRequest("POST", `/payouts/${id}/cancel`);
688
+ success(`Payout ${id} cancelled`);
689
+ } catch (err) {
690
+ console.error(`Error: ${err.message}`);
691
+ process.exit(1);
692
+ }
693
+ });
694
+ payout.command("status").description("Check payout eligibility").action(async () => {
695
+ try {
696
+ const data = await apiRequest("GET", "/payouts/eligibility");
697
+ output(data, (d) => [
698
+ `Eligible: ${d.eligible ?? "N/A"}`,
699
+ `Available Balance: ${fmtAmount(d.availableBalance)}`,
700
+ `Minimum Payout: ${fmtAmount(d.minimumPayout)}`
701
+ ].join("\n"));
702
+ } catch (err) {
703
+ console.error(`Error: ${err.message}`);
704
+ process.exit(1);
705
+ }
706
+ });
707
+ }
708
+
709
+ // src/commands/bank.ts
710
+ import { createInterface as createInterface2 } from "readline";
711
+ function prompt2(question) {
712
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
713
+ return new Promise((resolve) => {
714
+ rl.question(question, (answer) => {
715
+ rl.close();
716
+ resolve(answer.trim());
717
+ });
718
+ });
719
+ }
720
+ function registerBankCommands(program2) {
721
+ const bank = program2.command("bank").description("Manage bank accounts");
722
+ bank.command("list").description("List all bank accounts").action(async () => {
723
+ try {
724
+ const data = await apiRequest("GET", "/bank-accounts");
725
+ output(data, (d) => {
726
+ const items = d.data || d.bankAccounts || d;
727
+ if (!Array.isArray(items) || items.length === 0) return "No bank accounts found.";
728
+ table(
729
+ ["ID", "Bank Name", "Last 4", "Default", "Created"],
730
+ items.map((b) => [
731
+ b.id,
732
+ b.bankName || "N/A",
733
+ b.last4 || "N/A",
734
+ b.isDefault ? "yes" : "no",
735
+ fmtDate(b.createdAt)
934
736
  ])
935
- ];
936
- return formatTable(rows);
737
+ );
738
+ return "";
739
+ });
740
+ } catch (err) {
741
+ console.error(`Error: ${err.message}`);
742
+ process.exit(1);
743
+ }
744
+ });
745
+ bank.command("add").description("Add a new bank account").action(async () => {
746
+ try {
747
+ const accountHolderName = await prompt2("Account holder name: ");
748
+ const bankName = await prompt2("Bank name: ");
749
+ const routingNumber = await prompt2("Routing number: ");
750
+ const accountNumber = await prompt2("Account number: ");
751
+ const accountType = await prompt2("Account type (checking/savings, default: checking): ") || "checking";
752
+ const body = {
753
+ accountHolderName,
754
+ bankName,
755
+ routingNumber,
756
+ accountNumber,
757
+ accountType
758
+ };
759
+ const data = await apiRequest("POST", "/bank-accounts", body);
760
+ success("Bank account added");
761
+ output(data, (d) => [
762
+ `Bank Account ID: ${d.id}`,
763
+ `Bank: ${d.bankName || bankName}`,
764
+ `Last 4: ${d.last4 || "N/A"}`
765
+ ].join("\n"));
766
+ } catch (err) {
767
+ console.error(`Error: ${err.message}`);
768
+ process.exit(1);
769
+ }
770
+ });
771
+ bank.command("remove <id>").description("Remove a bank account").action(async (id) => {
772
+ try {
773
+ await apiRequest("DELETE", `/bank-accounts/${id}`);
774
+ success(`Bank account ${id} removed`);
775
+ } catch (err) {
776
+ console.error(`Error: ${err.message}`);
777
+ process.exit(1);
778
+ }
779
+ });
780
+ bank.command("set-default <id>").description("Set a bank account as default").action(async (id) => {
781
+ try {
782
+ await apiRequest("PATCH", `/bank-accounts/${id}`, { isDefault: true });
783
+ success(`Bank account ${id} set as default`);
784
+ } catch (err) {
785
+ console.error(`Error: ${err.message}`);
786
+ process.exit(1);
787
+ }
788
+ });
789
+ }
790
+
791
+ // src/commands/kyc.ts
792
+ import { createInterface as createInterface3 } from "readline";
793
+ function prompt3(question) {
794
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
795
+ return new Promise((resolve) => {
796
+ rl.question(question, (answer) => {
797
+ rl.close();
798
+ resolve(answer.trim());
799
+ });
800
+ });
801
+ }
802
+ function registerKycCommands(program2) {
803
+ const kyc = program2.command("kyc").description("Manage KYC verification");
804
+ kyc.command("submit").description("Submit KYC verification details").action(async () => {
805
+ try {
806
+ const fullName = await prompt3("Full name: ");
807
+ const dateOfBirth = await prompt3("Date of birth (YYYY-MM-DD): ");
808
+ const address = await prompt3("Street address: ");
809
+ const city = await prompt3("City: ");
810
+ const state = await prompt3("State/Province: ");
811
+ const postalCode = await prompt3("Postal code: ");
812
+ const country = await prompt3("Country (2-letter code, e.g. SG): ");
813
+ const body = {
814
+ fullName,
815
+ dateOfBirth,
816
+ address: {
817
+ line1: address,
818
+ city,
819
+ state,
820
+ postalCode,
821
+ country
822
+ }
823
+ };
824
+ const data = await apiRequest("POST", "/kyc", body);
825
+ success("KYC submission received");
826
+ output(data, (d) => {
827
+ return [
828
+ `KYC ID: ${d.id || "N/A"}`,
829
+ `Status: ${d.status || "pending"}`
830
+ ].join("\n");
937
831
  });
938
832
  } catch (err) {
939
- error(err instanceof Error ? err.message : "Failed to list payment links");
833
+ console.error(`Error: ${err.message}`);
834
+ process.exit(1);
940
835
  }
941
836
  });
942
- link.command("get").description("Get payment link details").argument("<id>", "Payment Link ID").action(async (id) => {
837
+ kyc.command("status").description("Check KYC verification status").action(async () => {
943
838
  try {
944
- const client = new ApiClient();
945
- const result = await client.getPaymentLink(id);
946
- output(result, (data) => {
947
- const d = data;
839
+ const data = await apiRequest("GET", "/kyc/status");
840
+ output(data, (d) => {
948
841
  return [
949
- `Payment Link: ${d.id}`,
950
- ``,
951
- d.name ? ` Name: ${d.name}` : null,
952
- d.description ? ` Description: ${d.description}` : null,
953
- ` Amount: ${formatAmount2(d.amount, d.currency)}`,
954
- ` URL: ${d.url}`,
955
- ` Active: ${d.active}`,
956
- d.successUrl ? ` Success URL: ${d.successUrl}` : null,
957
- ` Created: ${formatDate(d.createdAt)}`
958
- ].filter(Boolean).join("\n");
842
+ `Status: ${d.status || "N/A"}`,
843
+ `Verified: ${d.verified ?? "N/A"}`,
844
+ `Requirements: ${d.requirements ? JSON.stringify(d.requirements) : "None"}`
845
+ ].join("\n");
959
846
  });
960
847
  } catch (err) {
961
- error(err instanceof Error ? err.message : "Failed to get payment link");
848
+ console.error(`Error: ${err.message}`);
849
+ process.exit(1);
962
850
  }
963
851
  });
964
- link.command("deactivate").description("Deactivate a payment link").argument("<id>", "Payment Link ID").action(async (id) => {
852
+ }
853
+
854
+ // src/commands/webhook.ts
855
+ function registerWebhookCommands(program2) {
856
+ const webhook = program2.command("webhook").description("Manage webhook configuration");
857
+ webhook.command("set <url>").description("Set webhook URL for your wallet (must be HTTPS)").action(async (url) => {
965
858
  try {
966
- const client = new ApiClient();
967
- const result = await client.updatePaymentLink(id, { active: false });
968
- output(result, (data) => {
969
- const d = data;
970
- return `Payment link ${d.id} has been deactivated.`;
859
+ if (!url.startsWith("https://")) {
860
+ console.error("Error: webhook URL must start with https://");
861
+ process.exit(1);
862
+ }
863
+ const data = await apiRequest("PATCH", "/wallets/current", {
864
+ webhookUrl: url
971
865
  });
866
+ success("Webhook configured");
867
+ console.log(` URL: ${data.webhookUrl}`);
868
+ if (data.webhookSecret) {
869
+ console.log(` Secret: ${data.webhookSecret}`);
870
+ console.log("\nSave the secret \u2014 it will not be shown again.");
871
+ console.log("Use client.webhooks.constructEvent(body, signature, secret) in your server.");
872
+ }
972
873
  } catch (err) {
973
- error(err instanceof Error ? err.message : "Failed to deactivate payment link");
874
+ console.error(`Error: ${err.message}`);
875
+ process.exit(1);
974
876
  }
975
877
  });
976
- link.command("activate").description("Activate a payment link").argument("<id>", "Payment Link ID").action(async (id) => {
878
+ webhook.command("info").description("Show current webhook configuration").action(async () => {
977
879
  try {
978
- const client = new ApiClient();
979
- const result = await client.updatePaymentLink(id, { active: true });
980
- output(result, (data) => {
981
- const d = data;
982
- return `Payment link ${d.id} has been activated.`;
880
+ const data = await apiRequest("GET", "/wallets/current");
881
+ if (data.webhookUrl) {
882
+ console.log(`Webhook URL: ${data.webhookUrl}`);
883
+ console.log("Status: configured");
884
+ } else {
885
+ console.log("No webhook configured.");
886
+ console.log("Run: vibecash webhook set <url>");
887
+ }
888
+ } catch (err) {
889
+ console.error(`Error: ${err.message}`);
890
+ process.exit(1);
891
+ }
892
+ });
893
+ webhook.command("remove").description("Remove webhook configuration").action(async () => {
894
+ try {
895
+ await apiRequest("PATCH", "/wallets/current", {
896
+ webhookUrl: null
983
897
  });
898
+ success("Webhook removed");
984
899
  } catch (err) {
985
- error(err instanceof Error ? err.message : "Failed to activate payment link");
900
+ console.error(`Error: ${err.message}`);
901
+ process.exit(1);
986
902
  }
987
903
  });
988
- return link;
904
+ webhook.command("roll-secret").description("Generate a new webhook signing secret (invalidates the old one)").action(async () => {
905
+ try {
906
+ const wallet = await apiRequest("GET", "/wallets/current");
907
+ if (!wallet.webhookUrl) {
908
+ console.error("No webhook configured. Run: vibecash webhook set <url>");
909
+ process.exit(1);
910
+ }
911
+ const data = await apiRequest("PATCH", "/wallets/current", {
912
+ webhookUrl: wallet.webhookUrl
913
+ });
914
+ success("New secret generated");
915
+ console.log(` URL: ${data.webhookUrl}`);
916
+ if (data.webhookSecret) {
917
+ console.log(` Secret: ${data.webhookSecret}`);
918
+ console.log("\nSave the new secret \u2014 it will not be shown again.");
919
+ console.log("The old secret is now invalid.");
920
+ }
921
+ } catch (err) {
922
+ console.error(`Error: ${err.message}`);
923
+ process.exit(1);
924
+ }
925
+ });
926
+ webhook.command("events").description("Webhook event types reference").action(() => {
927
+ console.log("VibeCash Webhook Event Types\n");
928
+ console.log("Payment Events:");
929
+ console.log(" payment.succeeded \u2014 Customer payment completed successfully");
930
+ console.log(" payment.failed \u2014 Customer payment failed\n");
931
+ console.log("Subscription Events:");
932
+ console.log(" subscription.renewed \u2014 Subscription auto-renewed");
933
+ console.log(" subscription.past_due \u2014 Subscription payment overdue");
934
+ console.log(" subscription.unpaid \u2014 Subscription marked unpaid");
935
+ console.log(" subscription.trial_ending \u2014 Trial period ending soon\n");
936
+ console.log("Checkout Events:");
937
+ console.log(" checkout.session.expired \u2014 Checkout session expired\n");
938
+ console.log("Payload Format:");
939
+ console.log(" {");
940
+ console.log(' "id": "evt_xxx",');
941
+ console.log(' "type": "payment.succeeded",');
942
+ console.log(' "created": 1710000000,');
943
+ console.log(' "data": { "object": { ...full resource... } }');
944
+ console.log(" }\n");
945
+ console.log("Signature Header:");
946
+ console.log(" VibeCash-Signature: t={timestamp},v1={hmac-sha256-hex}");
947
+ });
989
948
  }
990
949
 
991
950
  // src/index.ts
992
- var program = new Command9();
993
- program.name("vibecash").description("VibeCash - Payment infrastructure for AI agents").version("0.1.3");
994
- program.addCommand(createWalletCommand());
995
- program.addCommand(createProductCommand());
996
- program.addCommand(createPriceCommand());
997
- program.addCommand(createCheckoutCommand());
998
- program.addCommand(createSubscriptionCommand());
999
- program.addCommand(createCustomerCommand());
1000
- program.addCommand(createQuickCreateCommand());
1001
- program.addCommand(createLinkCommand());
1002
- program.option("--human", "Output in human-readable format");
1003
- program.option("--json", "Output in JSON format (default)");
1004
- program.hook("preAction", (thisCommand) => {
951
+ var program = new Command();
952
+ program.name("vibecash").description("VibeCash CLI - manage payments from the command line").version("0.2.1").option("--json", "Output in JSON format").hook("preAction", (thisCommand) => {
1005
953
  const opts = thisCommand.opts();
1006
- if (opts.human) {
1007
- process.env.AIPAY_OUTPUT = "human";
1008
- } else if (opts.json) {
1009
- process.env.AIPAY_OUTPUT = "json";
1010
- }
954
+ if (opts.json) setOutputFormat("json");
1011
955
  });
956
+ registerWalletCommands(program);
957
+ registerProductCommands(program);
958
+ registerPriceCommands(program);
959
+ registerCheckoutCommands(program);
960
+ registerSubscriptionCommands(program);
961
+ registerCustomerCommands(program);
962
+ registerLinkCommands(program);
963
+ registerCreateCommand(program);
964
+ registerPayoutCommands(program);
965
+ registerBankCommands(program);
966
+ registerKycCommands(program);
967
+ registerWebhookCommands(program);
1012
968
  program.parse();