@forjio/storlaunch-cli 0.1.0
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/LICENSE +21 -0
- package/bin/storlaunch.js +2 -0
- package/dist/commands/auth.d.ts +4 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +78 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +86 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/payment.d.ts +4 -0
- package/dist/commands/payment.d.ts.map +1 -0
- package/dist/commands/payment.js +738 -0
- package/dist/commands/payment.js.map +1 -0
- package/dist/commands/storefront.d.ts +4 -0
- package/dist/commands/storefront.d.ts.map +1 -0
- package/dist/commands/storefront.js +433 -0
- package/dist/commands/storefront.js.map +1 -0
- package/dist/commands/webhook.d.ts +4 -0
- package/dist/commands/webhook.d.ts.map +1 -0
- package/dist/commands/webhook.js +249 -0
- package/dist/commands/webhook.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +18 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +62 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.d.ts +23 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +58 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/output.d.ts +21 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +66 -0
- package/dist/lib/output.js.map +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { apiRequest, ApiClientError } from "../lib/api.js";
|
|
6
|
+
import { output } from "../lib/output.js";
|
|
7
|
+
function getExitCode(err) {
|
|
8
|
+
if (err instanceof ApiClientError) {
|
|
9
|
+
if (err.status === 401 || err.status === 403)
|
|
10
|
+
return 2;
|
|
11
|
+
if (err.status === 429)
|
|
12
|
+
return 3;
|
|
13
|
+
if (err.code === "QUOTA_EXCEEDED")
|
|
14
|
+
return 4;
|
|
15
|
+
}
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
function handleError(err, json) {
|
|
19
|
+
if (json && err instanceof ApiClientError) {
|
|
20
|
+
console.error(JSON.stringify({ data: null, error: { code: err.code, message: err.message } }, null, 2));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
24
|
+
}
|
|
25
|
+
process.exit(getExitCode(err));
|
|
26
|
+
}
|
|
27
|
+
function parseMetadata(raw) {
|
|
28
|
+
if (!raw)
|
|
29
|
+
return undefined;
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
console.error(chalk.red("Error: --metadata must be a valid JSON string"));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function parseCommaSeparated(raw) {
|
|
39
|
+
if (!raw)
|
|
40
|
+
return undefined;
|
|
41
|
+
return raw.split(",").map((s) => s.trim());
|
|
42
|
+
}
|
|
43
|
+
// ─── Checkout ────────────────────────────────────────────────
|
|
44
|
+
const checkout = new Command("checkout").description("Manage checkout sessions");
|
|
45
|
+
checkout
|
|
46
|
+
.command("create")
|
|
47
|
+
.description("Create a checkout session")
|
|
48
|
+
.requiredOption("--amount <amount>", "Amount in smallest currency unit", parseInt)
|
|
49
|
+
.requiredOption("--currency <code>", "Currency code (IDR, USD)")
|
|
50
|
+
.option("--description <text>", "Shown on checkout page")
|
|
51
|
+
.option("--customer <id>", "Customer ID")
|
|
52
|
+
.option("--customer-email <email>", "Pre-fill email")
|
|
53
|
+
.option("--success-url <url>", "Redirect URL after payment")
|
|
54
|
+
.option("--cancel-url <url>", "Redirect URL on cancel")
|
|
55
|
+
.option("--expires-in <minutes>", "Session expiry in minutes", parseInt)
|
|
56
|
+
.option("--payment-methods <methods>", "Comma-separated: qris,ewallet,va,card,paypal")
|
|
57
|
+
.option("--metadata <json>", "JSON key-value pairs")
|
|
58
|
+
.action(async (opts, cmd) => {
|
|
59
|
+
const g = cmd.optsWithGlobals();
|
|
60
|
+
try {
|
|
61
|
+
const body = {
|
|
62
|
+
amount: opts.amount,
|
|
63
|
+
currency: opts.currency,
|
|
64
|
+
};
|
|
65
|
+
if (opts.description)
|
|
66
|
+
body["description"] = opts.description;
|
|
67
|
+
if (opts.customer)
|
|
68
|
+
body["customerId"] = opts.customer;
|
|
69
|
+
if (opts.customerEmail)
|
|
70
|
+
body["customerEmail"] = opts.customerEmail;
|
|
71
|
+
if (opts.successUrl)
|
|
72
|
+
body["successUrl"] = opts.successUrl;
|
|
73
|
+
if (opts.cancelUrl)
|
|
74
|
+
body["cancelUrl"] = opts.cancelUrl;
|
|
75
|
+
if (opts.expiresIn)
|
|
76
|
+
body["expiresInMinutes"] = opts.expiresIn;
|
|
77
|
+
if (opts.paymentMethods)
|
|
78
|
+
body["paymentMethods"] = parseCommaSeparated(opts.paymentMethods);
|
|
79
|
+
if (opts.metadata)
|
|
80
|
+
body["metadata"] = parseMetadata(opts.metadata);
|
|
81
|
+
const result = await apiRequest("/payment/checkout-sessions", {
|
|
82
|
+
method: "POST",
|
|
83
|
+
body,
|
|
84
|
+
sandbox: g.sandbox,
|
|
85
|
+
});
|
|
86
|
+
if (g.json) {
|
|
87
|
+
output(result, { json: true });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log(`Checkout session created: ${chalk.bold(String(result["id"]))}`);
|
|
91
|
+
if (result["url"])
|
|
92
|
+
console.log(`URL: ${result["url"]}`);
|
|
93
|
+
console.log(`Status: ${result["status"]}`);
|
|
94
|
+
if (result["expiresAt"])
|
|
95
|
+
console.log(`Expires: ${result["expiresAt"]}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
handleError(err, g.json);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
checkout
|
|
103
|
+
.command("list")
|
|
104
|
+
.description("List checkout sessions")
|
|
105
|
+
.option("--status <status>", "Filter: open, completed, expired")
|
|
106
|
+
.option("--customer <id>", "Filter by customer ID")
|
|
107
|
+
.option("--limit <n>", "Items per page", parseInt)
|
|
108
|
+
.option("--cursor <cursor>", "Pagination cursor")
|
|
109
|
+
.action(async (opts, cmd) => {
|
|
110
|
+
const g = cmd.optsWithGlobals();
|
|
111
|
+
try {
|
|
112
|
+
const result = await apiRequest("/payment/checkout-sessions", {
|
|
113
|
+
query: { status: opts.status, customer: opts.customer, limit: opts.limit, cursor: opts.cursor },
|
|
114
|
+
sandbox: g.sandbox,
|
|
115
|
+
});
|
|
116
|
+
if (g.json) {
|
|
117
|
+
output(result, { json: true });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const cols = [
|
|
121
|
+
{ key: "id", header: "ID", width: 24 },
|
|
122
|
+
{ key: "amount", header: "Amount" },
|
|
123
|
+
{ key: "currency", header: "Currency" },
|
|
124
|
+
{ key: "status", header: "Status" },
|
|
125
|
+
{ key: "createdAt", header: "Created" },
|
|
126
|
+
];
|
|
127
|
+
output((result["data"] ?? result), { columns: cols });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
handleError(err, g.json);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
checkout
|
|
135
|
+
.command("get <id>")
|
|
136
|
+
.description("Get a checkout session")
|
|
137
|
+
.action(async (id, _, cmd) => {
|
|
138
|
+
const g = cmd.optsWithGlobals();
|
|
139
|
+
try {
|
|
140
|
+
const result = await apiRequest(`/payment/checkout-sessions/${id}`, {
|
|
141
|
+
sandbox: g.sandbox,
|
|
142
|
+
});
|
|
143
|
+
output(result, { json: g.json });
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
handleError(err, g.json);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
// ─── Plans ───────────────────────────────────────────────────
|
|
150
|
+
const plans = new Command("plans").description("Manage subscription plans");
|
|
151
|
+
plans
|
|
152
|
+
.command("create")
|
|
153
|
+
.description("Create a subscription plan")
|
|
154
|
+
.requiredOption("--name <name>", "Plan display name")
|
|
155
|
+
.requiredOption("--amount <amount>", "Amount per interval", parseInt)
|
|
156
|
+
.requiredOption("--currency <code>", "Currency code")
|
|
157
|
+
.requiredOption("--interval <interval>", "weekly, monthly, or yearly")
|
|
158
|
+
.option("--description <text>", "Plan description")
|
|
159
|
+
.option("--trial-days <days>", "Free trial duration", parseInt)
|
|
160
|
+
.option("--features <list>", "Comma-separated feature list")
|
|
161
|
+
.option("--metadata <json>", "JSON string")
|
|
162
|
+
.action(async (opts, cmd) => {
|
|
163
|
+
const g = cmd.optsWithGlobals();
|
|
164
|
+
try {
|
|
165
|
+
const body = {
|
|
166
|
+
name: opts.name,
|
|
167
|
+
amount: opts.amount,
|
|
168
|
+
currency: opts.currency,
|
|
169
|
+
interval: opts.interval,
|
|
170
|
+
};
|
|
171
|
+
if (opts.description)
|
|
172
|
+
body["description"] = opts.description;
|
|
173
|
+
if (opts.trialDays !== undefined)
|
|
174
|
+
body["trialDays"] = opts.trialDays;
|
|
175
|
+
if (opts.features)
|
|
176
|
+
body["features"] = parseCommaSeparated(opts.features);
|
|
177
|
+
if (opts.metadata)
|
|
178
|
+
body["metadata"] = parseMetadata(opts.metadata);
|
|
179
|
+
const result = await apiRequest("/payment/plans", {
|
|
180
|
+
method: "POST",
|
|
181
|
+
body,
|
|
182
|
+
sandbox: g.sandbox,
|
|
183
|
+
});
|
|
184
|
+
if (g.json) {
|
|
185
|
+
output(result, { json: true });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
console.log(`Plan created: ${chalk.bold(String(result["id"]))}`);
|
|
189
|
+
console.log(`Name: ${result["name"]}`);
|
|
190
|
+
console.log(`Amount: ${result["amount"]} ${result["currency"]}`);
|
|
191
|
+
console.log(`Interval: ${result["interval"]}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
handleError(err, g.json);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
plans
|
|
199
|
+
.command("list")
|
|
200
|
+
.description("List subscription plans")
|
|
201
|
+
.option("--active", "Filter by active status")
|
|
202
|
+
.option("--limit <n>", "Items per page", parseInt)
|
|
203
|
+
.option("--cursor <cursor>", "Pagination cursor")
|
|
204
|
+
.action(async (opts, cmd) => {
|
|
205
|
+
const g = cmd.optsWithGlobals();
|
|
206
|
+
try {
|
|
207
|
+
const result = await apiRequest("/payment/plans", {
|
|
208
|
+
query: { active: opts.active ? "true" : undefined, limit: opts.limit, cursor: opts.cursor },
|
|
209
|
+
sandbox: g.sandbox,
|
|
210
|
+
});
|
|
211
|
+
if (g.json) {
|
|
212
|
+
output(result, { json: true });
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const cols = [
|
|
216
|
+
{ key: "id", header: "ID", width: 20 },
|
|
217
|
+
{ key: "name", header: "Name" },
|
|
218
|
+
{ key: "amount", header: "Amount" },
|
|
219
|
+
{ key: "currency", header: "Currency" },
|
|
220
|
+
{ key: "interval", header: "Interval" },
|
|
221
|
+
{ key: "active", header: "Active" },
|
|
222
|
+
];
|
|
223
|
+
output((result["data"] ?? result), { columns: cols });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
handleError(err, g.json);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
plans
|
|
231
|
+
.command("get <id>")
|
|
232
|
+
.description("Get a plan")
|
|
233
|
+
.action(async (id, _, cmd) => {
|
|
234
|
+
const g = cmd.optsWithGlobals();
|
|
235
|
+
try {
|
|
236
|
+
const result = await apiRequest(`/payment/plans/${id}`, {
|
|
237
|
+
sandbox: g.sandbox,
|
|
238
|
+
});
|
|
239
|
+
output(result, { json: g.json });
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
handleError(err, g.json);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
plans
|
|
246
|
+
.command("update <id>")
|
|
247
|
+
.description("Update a plan")
|
|
248
|
+
.option("--name <name>", "New plan name")
|
|
249
|
+
.option("--amount <amount>", "New amount", parseInt)
|
|
250
|
+
.option("--description <text>", "New description")
|
|
251
|
+
.option("--trial-days <days>", "New trial duration", parseInt)
|
|
252
|
+
.option("--features <list>", "Comma-separated feature list")
|
|
253
|
+
.option("--active", "Enable the plan")
|
|
254
|
+
.option("--no-active", "Disable the plan")
|
|
255
|
+
.action(async (id, opts, cmd) => {
|
|
256
|
+
const g = cmd.optsWithGlobals();
|
|
257
|
+
try {
|
|
258
|
+
const body = {};
|
|
259
|
+
if (opts.name)
|
|
260
|
+
body["name"] = opts.name;
|
|
261
|
+
if (opts.amount !== undefined)
|
|
262
|
+
body["amount"] = opts.amount;
|
|
263
|
+
if (opts.description)
|
|
264
|
+
body["description"] = opts.description;
|
|
265
|
+
if (opts.trialDays !== undefined)
|
|
266
|
+
body["trialDays"] = opts.trialDays;
|
|
267
|
+
if (opts.features)
|
|
268
|
+
body["features"] = parseCommaSeparated(opts.features);
|
|
269
|
+
if (opts.active !== undefined)
|
|
270
|
+
body["active"] = opts.active;
|
|
271
|
+
const result = await apiRequest(`/payment/plans/${id}`, {
|
|
272
|
+
method: "PATCH",
|
|
273
|
+
body,
|
|
274
|
+
sandbox: g.sandbox,
|
|
275
|
+
});
|
|
276
|
+
if (g.json) {
|
|
277
|
+
output(result, { json: true });
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
console.log(chalk.green(`Plan ${id} updated.`));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
handleError(err, g.json);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
plans
|
|
288
|
+
.command("delete <id>")
|
|
289
|
+
.description("Archive a plan")
|
|
290
|
+
.action(async (id, _, cmd) => {
|
|
291
|
+
const g = cmd.optsWithGlobals();
|
|
292
|
+
try {
|
|
293
|
+
await apiRequest(`/payment/plans/${id}`, { method: "DELETE", sandbox: g.sandbox });
|
|
294
|
+
if (g.json) {
|
|
295
|
+
output({ deleted: true, id }, { json: true });
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
console.log(chalk.green(`Plan ${id} archived.`));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
handleError(err, g.json);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
// ─── Subscriptions ───────────────────────────────────────────
|
|
306
|
+
const subscriptions = new Command("subscriptions").description("Manage subscriptions");
|
|
307
|
+
subscriptions
|
|
308
|
+
.command("create")
|
|
309
|
+
.description("Create a subscription")
|
|
310
|
+
.requiredOption("--customer <id>", "Customer ID")
|
|
311
|
+
.requiredOption("--plan <id>", "Plan ID")
|
|
312
|
+
.option("--payment-method <method>", "Preferred payment method")
|
|
313
|
+
.option("--trial-end <date>", "Override trial period (ISO 8601)")
|
|
314
|
+
.option("--metadata <json>", "JSON string")
|
|
315
|
+
.action(async (opts, cmd) => {
|
|
316
|
+
const g = cmd.optsWithGlobals();
|
|
317
|
+
try {
|
|
318
|
+
const body = {
|
|
319
|
+
customerId: opts.customer,
|
|
320
|
+
planId: opts.plan,
|
|
321
|
+
};
|
|
322
|
+
if (opts.paymentMethod)
|
|
323
|
+
body["paymentMethod"] = opts.paymentMethod;
|
|
324
|
+
if (opts.trialEnd)
|
|
325
|
+
body["trialEnd"] = opts.trialEnd;
|
|
326
|
+
if (opts.metadata)
|
|
327
|
+
body["metadata"] = parseMetadata(opts.metadata);
|
|
328
|
+
const result = await apiRequest("/payment/subscriptions", {
|
|
329
|
+
method: "POST",
|
|
330
|
+
body,
|
|
331
|
+
sandbox: g.sandbox,
|
|
332
|
+
});
|
|
333
|
+
if (g.json) {
|
|
334
|
+
output(result, { json: true });
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
console.log(`Subscription created: ${chalk.bold(String(result["id"]))}`);
|
|
338
|
+
console.log(`Status: ${result["status"]}`);
|
|
339
|
+
console.log(`Plan: ${result["planId"]}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
handleError(err, g.json);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
subscriptions
|
|
347
|
+
.command("list")
|
|
348
|
+
.description("List subscriptions")
|
|
349
|
+
.option("--customer <id>", "Filter by customer ID")
|
|
350
|
+
.option("--plan <id>", "Filter by plan ID")
|
|
351
|
+
.option("--status <status>", "Filter: trialing, active, past_due, paused, canceled")
|
|
352
|
+
.option("--limit <n>", "Items per page", parseInt)
|
|
353
|
+
.option("--cursor <cursor>", "Pagination cursor")
|
|
354
|
+
.action(async (opts, cmd) => {
|
|
355
|
+
const g = cmd.optsWithGlobals();
|
|
356
|
+
try {
|
|
357
|
+
const result = await apiRequest("/payment/subscriptions", {
|
|
358
|
+
query: { customer: opts.customer, plan: opts.plan, status: opts.status, limit: opts.limit, cursor: opts.cursor },
|
|
359
|
+
sandbox: g.sandbox,
|
|
360
|
+
});
|
|
361
|
+
if (g.json) {
|
|
362
|
+
output(result, { json: true });
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
const cols = [
|
|
366
|
+
{ key: "id", header: "ID", width: 20 },
|
|
367
|
+
{ key: "customerId", header: "Customer", width: 20 },
|
|
368
|
+
{ key: "planId", header: "Plan", width: 20 },
|
|
369
|
+
{ key: "status", header: "Status" },
|
|
370
|
+
{ key: "currentPeriodEnd", header: "Period End" },
|
|
371
|
+
];
|
|
372
|
+
output((result["data"] ?? result), { columns: cols });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
handleError(err, g.json);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
subscriptions
|
|
380
|
+
.command("get <id>")
|
|
381
|
+
.description("Get a subscription")
|
|
382
|
+
.action(async (id, _, cmd) => {
|
|
383
|
+
const g = cmd.optsWithGlobals();
|
|
384
|
+
try {
|
|
385
|
+
const result = await apiRequest(`/payment/subscriptions/${id}`, {
|
|
386
|
+
sandbox: g.sandbox,
|
|
387
|
+
});
|
|
388
|
+
output(result, { json: g.json });
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
handleError(err, g.json);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
subscriptions
|
|
395
|
+
.command("update <id>")
|
|
396
|
+
.description("Update a subscription plan")
|
|
397
|
+
.option("--plan <id>", "New plan ID")
|
|
398
|
+
.option("--prorate", "Apply proration immediately")
|
|
399
|
+
.action(async (id, opts, cmd) => {
|
|
400
|
+
const g = cmd.optsWithGlobals();
|
|
401
|
+
try {
|
|
402
|
+
const body = {};
|
|
403
|
+
if (opts.plan)
|
|
404
|
+
body["planId"] = opts.plan;
|
|
405
|
+
if (opts.prorate)
|
|
406
|
+
body["prorate"] = true;
|
|
407
|
+
const result = await apiRequest(`/payment/subscriptions/${id}`, {
|
|
408
|
+
method: "PATCH",
|
|
409
|
+
body,
|
|
410
|
+
sandbox: g.sandbox,
|
|
411
|
+
});
|
|
412
|
+
if (g.json) {
|
|
413
|
+
output(result, { json: true });
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
console.log(chalk.green(`Subscription ${id} updated.`));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
handleError(err, g.json);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
subscriptions
|
|
424
|
+
.command("cancel <id>")
|
|
425
|
+
.description("Cancel a subscription")
|
|
426
|
+
.option("--immediate", "Cancel now instead of at period end")
|
|
427
|
+
.action(async (id, opts, cmd) => {
|
|
428
|
+
const g = cmd.optsWithGlobals();
|
|
429
|
+
try {
|
|
430
|
+
await apiRequest(`/payment/subscriptions/${id}`, {
|
|
431
|
+
method: "DELETE",
|
|
432
|
+
body: opts.immediate ? { immediate: true } : undefined,
|
|
433
|
+
sandbox: g.sandbox,
|
|
434
|
+
});
|
|
435
|
+
if (g.json) {
|
|
436
|
+
output({ canceled: true, id, immediate: !!opts.immediate }, { json: true });
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
const mode = opts.immediate ? "immediately" : "at period end";
|
|
440
|
+
console.log(chalk.green(`Subscription ${id} canceled ${mode}.`));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch (err) {
|
|
444
|
+
handleError(err, g.json);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
subscriptions
|
|
448
|
+
.command("pause <id>")
|
|
449
|
+
.description("Pause a subscription")
|
|
450
|
+
.action(async (id, _, cmd) => {
|
|
451
|
+
const g = cmd.optsWithGlobals();
|
|
452
|
+
try {
|
|
453
|
+
const result = await apiRequest(`/payment/subscriptions/${id}`, {
|
|
454
|
+
method: "PATCH",
|
|
455
|
+
body: { status: "paused" },
|
|
456
|
+
sandbox: g.sandbox,
|
|
457
|
+
});
|
|
458
|
+
if (g.json) {
|
|
459
|
+
output(result, { json: true });
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
console.log(chalk.green(`Subscription ${id} paused.`));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
handleError(err, g.json);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
subscriptions
|
|
470
|
+
.command("resume <id>")
|
|
471
|
+
.description("Resume a paused subscription")
|
|
472
|
+
.action(async (id, _, cmd) => {
|
|
473
|
+
const g = cmd.optsWithGlobals();
|
|
474
|
+
try {
|
|
475
|
+
const result = await apiRequest(`/payment/subscriptions/${id}`, {
|
|
476
|
+
method: "PATCH",
|
|
477
|
+
body: { status: "active" },
|
|
478
|
+
sandbox: g.sandbox,
|
|
479
|
+
});
|
|
480
|
+
if (g.json) {
|
|
481
|
+
output(result, { json: true });
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
console.log(chalk.green(`Subscription ${id} resumed.`));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
handleError(err, g.json);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
// ─── Invoices ────────────────────────────────────────────────
|
|
492
|
+
const invoices = new Command("invoices").description("Manage invoices");
|
|
493
|
+
invoices
|
|
494
|
+
.command("list")
|
|
495
|
+
.description("List invoices")
|
|
496
|
+
.option("--customer <id>", "Filter by customer ID")
|
|
497
|
+
.option("--subscription <id>", "Filter by subscription ID")
|
|
498
|
+
.option("--status <status>", "Filter: draft, open, paid, overdue, void")
|
|
499
|
+
.option("--limit <n>", "Items per page", parseInt)
|
|
500
|
+
.option("--cursor <cursor>", "Pagination cursor")
|
|
501
|
+
.action(async (opts, cmd) => {
|
|
502
|
+
const g = cmd.optsWithGlobals();
|
|
503
|
+
try {
|
|
504
|
+
const result = await apiRequest("/payment/invoices", {
|
|
505
|
+
query: { customer: opts.customer, subscription: opts.subscription, status: opts.status, limit: opts.limit, cursor: opts.cursor },
|
|
506
|
+
sandbox: g.sandbox,
|
|
507
|
+
});
|
|
508
|
+
if (g.json) {
|
|
509
|
+
output(result, { json: true });
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
const cols = [
|
|
513
|
+
{ key: "id", header: "ID", width: 20 },
|
|
514
|
+
{ key: "amount", header: "Amount" },
|
|
515
|
+
{ key: "currency", header: "Currency" },
|
|
516
|
+
{ key: "status", header: "Status" },
|
|
517
|
+
{ key: "createdAt", header: "Created" },
|
|
518
|
+
];
|
|
519
|
+
output((result["data"] ?? result), { columns: cols });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
handleError(err, g.json);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
invoices
|
|
527
|
+
.command("get <id>")
|
|
528
|
+
.description("Get an invoice")
|
|
529
|
+
.action(async (id, _, cmd) => {
|
|
530
|
+
const g = cmd.optsWithGlobals();
|
|
531
|
+
try {
|
|
532
|
+
const result = await apiRequest(`/payment/invoices/${id}`, {
|
|
533
|
+
sandbox: g.sandbox,
|
|
534
|
+
});
|
|
535
|
+
output(result, { json: g.json });
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
handleError(err, g.json);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
invoices
|
|
542
|
+
.command("download <id>")
|
|
543
|
+
.description("Download invoice as PDF")
|
|
544
|
+
.option("--output <path>", "Output file path")
|
|
545
|
+
.action(async (id, opts, cmd) => {
|
|
546
|
+
const g = cmd.optsWithGlobals();
|
|
547
|
+
try {
|
|
548
|
+
// Get invoice details first to determine filename
|
|
549
|
+
const invoice = await apiRequest(`/payment/invoices/${id}`, {
|
|
550
|
+
sandbox: g.sandbox,
|
|
551
|
+
});
|
|
552
|
+
const outputPath = opts.output ?? `./${invoice["number"] ?? id}.pdf`;
|
|
553
|
+
// Download the PDF
|
|
554
|
+
const { resolveApiKey, resolveApiUrl } = await import("../lib/config.js");
|
|
555
|
+
const token = resolveApiKey({ sandbox: g.sandbox });
|
|
556
|
+
const baseUrl = resolveApiUrl();
|
|
557
|
+
const url = `${baseUrl.replace(/\/$/, "")}/payment/invoices/${id}/pdf`;
|
|
558
|
+
const response = await fetch(url, {
|
|
559
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
560
|
+
});
|
|
561
|
+
if (!response.ok) {
|
|
562
|
+
throw new ApiClientError({
|
|
563
|
+
status: response.status,
|
|
564
|
+
message: `Failed to download invoice: ${response.statusText}`,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
568
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
569
|
+
writeFileSync(outputPath, buffer);
|
|
570
|
+
if (g.json) {
|
|
571
|
+
output({ id, path: outputPath, size: buffer.length }, { json: true });
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
console.log(chalk.green(`Invoice downloaded: ${outputPath} (${buffer.length} bytes)`));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch (err) {
|
|
578
|
+
handleError(err, g.json);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
// ─── Customers ───────────────────────────────────────────────
|
|
582
|
+
const customers = new Command("customers").description("Manage customers");
|
|
583
|
+
customers
|
|
584
|
+
.command("create")
|
|
585
|
+
.description("Create a customer")
|
|
586
|
+
.requiredOption("--email <email>", "Customer email")
|
|
587
|
+
.option("--name <name>", "Display name")
|
|
588
|
+
.option("--metadata <json>", "JSON string")
|
|
589
|
+
.action(async (opts, cmd) => {
|
|
590
|
+
const g = cmd.optsWithGlobals();
|
|
591
|
+
try {
|
|
592
|
+
const body = { email: opts.email };
|
|
593
|
+
if (opts.name)
|
|
594
|
+
body["name"] = opts.name;
|
|
595
|
+
if (opts.metadata)
|
|
596
|
+
body["metadata"] = parseMetadata(opts.metadata);
|
|
597
|
+
const result = await apiRequest("/payment/customers", {
|
|
598
|
+
method: "POST",
|
|
599
|
+
body,
|
|
600
|
+
sandbox: g.sandbox,
|
|
601
|
+
});
|
|
602
|
+
if (g.json) {
|
|
603
|
+
output(result, { json: true });
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
console.log(`Customer created: ${chalk.bold(String(result["id"]))}`);
|
|
607
|
+
console.log(`Email: ${result["email"]}`);
|
|
608
|
+
if (result["name"])
|
|
609
|
+
console.log(`Name: ${result["name"]}`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
catch (err) {
|
|
613
|
+
handleError(err, g.json);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
customers
|
|
617
|
+
.command("list")
|
|
618
|
+
.description("List customers")
|
|
619
|
+
.option("--email <email>", "Filter by exact email")
|
|
620
|
+
.option("--limit <n>", "Items per page", parseInt)
|
|
621
|
+
.option("--cursor <cursor>", "Pagination cursor")
|
|
622
|
+
.action(async (opts, cmd) => {
|
|
623
|
+
const g = cmd.optsWithGlobals();
|
|
624
|
+
try {
|
|
625
|
+
const result = await apiRequest("/payment/customers", {
|
|
626
|
+
query: { email: opts.email, limit: opts.limit, cursor: opts.cursor },
|
|
627
|
+
sandbox: g.sandbox,
|
|
628
|
+
});
|
|
629
|
+
if (g.json) {
|
|
630
|
+
output(result, { json: true });
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
const cols = [
|
|
634
|
+
{ key: "id", header: "ID", width: 20 },
|
|
635
|
+
{ key: "email", header: "Email", width: 30 },
|
|
636
|
+
{ key: "name", header: "Name" },
|
|
637
|
+
{ key: "createdAt", header: "Created" },
|
|
638
|
+
];
|
|
639
|
+
output((result["data"] ?? result), { columns: cols });
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
catch (err) {
|
|
643
|
+
handleError(err, g.json);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
customers
|
|
647
|
+
.command("get <id>")
|
|
648
|
+
.description("Get a customer")
|
|
649
|
+
.action(async (id, _, cmd) => {
|
|
650
|
+
const g = cmd.optsWithGlobals();
|
|
651
|
+
try {
|
|
652
|
+
const result = await apiRequest(`/payment/customers/${id}`, {
|
|
653
|
+
sandbox: g.sandbox,
|
|
654
|
+
});
|
|
655
|
+
output(result, { json: g.json });
|
|
656
|
+
}
|
|
657
|
+
catch (err) {
|
|
658
|
+
handleError(err, g.json);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
customers
|
|
662
|
+
.command("update <id>")
|
|
663
|
+
.description("Update a customer")
|
|
664
|
+
.option("--name <name>", "New display name")
|
|
665
|
+
.option("--email <email>", "New email")
|
|
666
|
+
.option("--metadata <json>", "JSON string")
|
|
667
|
+
.action(async (id, opts, cmd) => {
|
|
668
|
+
const g = cmd.optsWithGlobals();
|
|
669
|
+
try {
|
|
670
|
+
const body = {};
|
|
671
|
+
if (opts.name)
|
|
672
|
+
body["name"] = opts.name;
|
|
673
|
+
if (opts.email)
|
|
674
|
+
body["email"] = opts.email;
|
|
675
|
+
if (opts.metadata)
|
|
676
|
+
body["metadata"] = parseMetadata(opts.metadata);
|
|
677
|
+
const result = await apiRequest(`/payment/customers/${id}`, {
|
|
678
|
+
method: "PATCH",
|
|
679
|
+
body,
|
|
680
|
+
sandbox: g.sandbox,
|
|
681
|
+
});
|
|
682
|
+
if (g.json) {
|
|
683
|
+
output(result, { json: true });
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
console.log(chalk.green(`Customer ${id} updated.`));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
catch (err) {
|
|
690
|
+
handleError(err, g.json);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
// ─── Portal ──────────────────────────────────────────────────
|
|
694
|
+
const portal = new Command("portal").description("Manage billing portal sessions");
|
|
695
|
+
portal
|
|
696
|
+
.command("create")
|
|
697
|
+
.description("Create a billing portal session")
|
|
698
|
+
.requiredOption("--customer <id>", "Customer ID")
|
|
699
|
+
.option("--return-url <url>", "Redirect when customer is done")
|
|
700
|
+
.option("--expires-in <minutes>", "Session duration in minutes", parseInt)
|
|
701
|
+
.action(async (opts, cmd) => {
|
|
702
|
+
const g = cmd.optsWithGlobals();
|
|
703
|
+
try {
|
|
704
|
+
const body = { customerId: opts.customer };
|
|
705
|
+
if (opts.returnUrl)
|
|
706
|
+
body["returnUrl"] = opts.returnUrl;
|
|
707
|
+
if (opts.expiresIn)
|
|
708
|
+
body["expiresInMinutes"] = opts.expiresIn;
|
|
709
|
+
const result = await apiRequest("/payment/portal-sessions", {
|
|
710
|
+
method: "POST",
|
|
711
|
+
body,
|
|
712
|
+
sandbox: g.sandbox,
|
|
713
|
+
});
|
|
714
|
+
if (g.json) {
|
|
715
|
+
output(result, { json: true });
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
console.log(`Portal session created: ${chalk.bold(String(result["id"]))}`);
|
|
719
|
+
if (result["url"])
|
|
720
|
+
console.log(`URL: ${result["url"]}`);
|
|
721
|
+
if (result["expiresAt"])
|
|
722
|
+
console.log(`Expires: ${result["expiresAt"]}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
catch (err) {
|
|
726
|
+
handleError(err, g.json);
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
// ─── Top-level payment command ───────────────────────────────
|
|
730
|
+
const payment = new Command("payment").description("Manage payments, plans, subscriptions, and customers");
|
|
731
|
+
payment.addCommand(checkout);
|
|
732
|
+
payment.addCommand(plans);
|
|
733
|
+
payment.addCommand(subscriptions);
|
|
734
|
+
payment.addCommand(invoices);
|
|
735
|
+
payment.addCommand(customers);
|
|
736
|
+
payment.addCommand(portal);
|
|
737
|
+
export { payment };
|
|
738
|
+
//# sourceMappingURL=payment.js.map
|