@tarout/cli 0.3.0 → 0.4.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/dist/{api-QAKANRFX.js → api-QVUO7EWV.js} +2 -2
- package/dist/{chunk-CJMIX35A.js → chunk-DI66W4S5.js} +1 -1
- package/dist/{chunk-KL3JNPAY.js → chunk-K7JK5HIL.js} +6 -1
- package/dist/{chunk-NHNK5ZQ5.js → chunk-Y6TWR3XZ.js} +1 -1
- package/dist/index.js +1429 -217
- package/dist/{prompts-QQ2FZKQT.js → prompts-JH6YBHHV.js} +2 -2
- package/package.json +1 -1
- package/dist/billing-GUA4S2Y4.js +0 -12
- package/dist/chunk-BS6DFVSU.js +0 -998
package/dist/chunk-BS6DFVSU.js
DELETED
|
@@ -1,998 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AuthError,
|
|
3
|
-
getApiClient,
|
|
4
|
-
handleError
|
|
5
|
-
} from "./chunk-NHNK5ZQ5.js";
|
|
6
|
-
import {
|
|
7
|
-
isLoggedIn
|
|
8
|
-
} from "./chunk-5DAFGMBH.js";
|
|
9
|
-
import {
|
|
10
|
-
confirm,
|
|
11
|
-
select
|
|
12
|
-
} from "./chunk-CJMIX35A.js";
|
|
13
|
-
import {
|
|
14
|
-
ExitCode,
|
|
15
|
-
box,
|
|
16
|
-
colors,
|
|
17
|
-
exit,
|
|
18
|
-
isJsonMode,
|
|
19
|
-
log,
|
|
20
|
-
outputData,
|
|
21
|
-
outputError,
|
|
22
|
-
outputJsonLine,
|
|
23
|
-
shouldSkipConfirmation,
|
|
24
|
-
table
|
|
25
|
-
} from "./chunk-KL3JNPAY.js";
|
|
26
|
-
|
|
27
|
-
// src/commands/billing.ts
|
|
28
|
-
import { InvalidArgumentError } from "commander";
|
|
29
|
-
import open from "open";
|
|
30
|
-
|
|
31
|
-
// src/utils/spinner.ts
|
|
32
|
-
import ora from "ora";
|
|
33
|
-
var currentSpinner = null;
|
|
34
|
-
function jsonMode() {
|
|
35
|
-
try {
|
|
36
|
-
return isJsonMode();
|
|
37
|
-
} catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function startSpinner(text) {
|
|
42
|
-
if (jsonMode()) return null;
|
|
43
|
-
if (currentSpinner) {
|
|
44
|
-
currentSpinner.stop();
|
|
45
|
-
}
|
|
46
|
-
currentSpinner = ora(text).start();
|
|
47
|
-
return currentSpinner;
|
|
48
|
-
}
|
|
49
|
-
function succeedSpinner(text) {
|
|
50
|
-
if (jsonMode()) return;
|
|
51
|
-
if (currentSpinner) {
|
|
52
|
-
currentSpinner.succeed(text);
|
|
53
|
-
currentSpinner = null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function failSpinner(text) {
|
|
57
|
-
if (jsonMode()) return;
|
|
58
|
-
if (currentSpinner) {
|
|
59
|
-
currentSpinner.fail(text);
|
|
60
|
-
currentSpinner = null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function stopSpinner() {
|
|
64
|
-
if (jsonMode()) return;
|
|
65
|
-
if (currentSpinner) {
|
|
66
|
-
currentSpinner.stop();
|
|
67
|
-
currentSpinner = null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function updateSpinner(text) {
|
|
71
|
-
if (jsonMode()) return;
|
|
72
|
-
if (currentSpinner) {
|
|
73
|
-
currentSpinner.text = text;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// src/commands/billing.ts
|
|
78
|
-
function registerBillingCommands(program) {
|
|
79
|
-
const billing = program.command("billing").description("Manage subscription and billing");
|
|
80
|
-
billing.command("status").description("Show current subscription and entitlements").action(async () => {
|
|
81
|
-
try {
|
|
82
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
83
|
-
const client = getApiClient();
|
|
84
|
-
const _spinner = startSpinner("Fetching subscription...");
|
|
85
|
-
const subscription = await client.subscription.getCurrent.query();
|
|
86
|
-
succeedSpinner();
|
|
87
|
-
if (isJsonMode()) {
|
|
88
|
-
outputData(subscription);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
log("");
|
|
92
|
-
log(colors.bold("Current Subscription"));
|
|
93
|
-
log("");
|
|
94
|
-
if (!subscription || !subscription.planKey) {
|
|
95
|
-
log(` Plan: ${colors.dim("No active subscription (free tier)")}`);
|
|
96
|
-
} else {
|
|
97
|
-
log(` Plan: ${colors.cyan(subscription.planKey)}`);
|
|
98
|
-
if (subscription.planQuantity && subscription.planQuantity > 1) {
|
|
99
|
-
log(` Quantity: ${subscription.planQuantity}`);
|
|
100
|
-
}
|
|
101
|
-
log(` Status: ${formatSubStatus(subscription.status || "active")}`);
|
|
102
|
-
if (subscription.currentPeriodEnd) {
|
|
103
|
-
log(` Renews: ${formatDate(subscription.currentPeriodEnd)}`);
|
|
104
|
-
}
|
|
105
|
-
if (subscription.cancelAtPeriodEnd) {
|
|
106
|
-
log(` ${colors.warn("\u26A0 Cancels at end of billing period")}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (subscription?.items && subscription.items.length > 0) {
|
|
110
|
-
log("");
|
|
111
|
-
log(colors.bold("Add-ons"));
|
|
112
|
-
table(
|
|
113
|
-
["ADDON", "QUANTITY"],
|
|
114
|
-
subscription.items.map((item) => [
|
|
115
|
-
colors.cyan(item.addonKey || item.key || ""),
|
|
116
|
-
String(item.quantity || 1)
|
|
117
|
-
])
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
log("");
|
|
121
|
-
log(`To view available plans: ${colors.dim("tarout billing plans")}`);
|
|
122
|
-
} catch (err) {
|
|
123
|
-
handleError(err);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
billing.command("plans").description("List available subscription plans").action(async () => {
|
|
127
|
-
try {
|
|
128
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
129
|
-
const client = getApiClient();
|
|
130
|
-
const _spinner = startSpinner("Fetching plans...");
|
|
131
|
-
const catalog = await client.subscription.getCatalog.query();
|
|
132
|
-
succeedSpinner();
|
|
133
|
-
if (isJsonMode()) {
|
|
134
|
-
outputData(catalog);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
log("");
|
|
138
|
-
log(colors.bold("Available Plans"));
|
|
139
|
-
log("");
|
|
140
|
-
const plans = catalog?.plans || catalog || [];
|
|
141
|
-
if (!Array.isArray(plans) || plans.length === 0) {
|
|
142
|
-
log("No plans available.");
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
table(
|
|
146
|
-
["PLAN", "PRICE", "DESCRIPTION"],
|
|
147
|
-
plans.map((p) => [
|
|
148
|
-
colors.cyan(p.planKey || p.key || p.name || ""),
|
|
149
|
-
p.priceHalalas ? `${(p.priceHalalas / 100).toFixed(2)} SAR/mo` : colors.dim("Free"),
|
|
150
|
-
p.description || ""
|
|
151
|
-
])
|
|
152
|
-
);
|
|
153
|
-
log("");
|
|
154
|
-
log(`To upgrade: ${colors.dim("tarout billing upgrade <plan>")}`);
|
|
155
|
-
} catch (err) {
|
|
156
|
-
handleError(err);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
billing.command("upgrade").argument("[plan]", "Plan key to switch to (alias: --plan)").description("Upgrade or change subscription plan").option(
|
|
160
|
-
"--plan <key>",
|
|
161
|
-
"Plan key (alias for the positional argument; useful for agent invocations)"
|
|
162
|
-
).option(
|
|
163
|
-
"-q, --quantity <n>",
|
|
164
|
-
"Plan quantity (for multi-slot plans)",
|
|
165
|
-
Number.parseInt
|
|
166
|
-
).option(
|
|
167
|
-
"--billing-period <period>",
|
|
168
|
-
"Billing period: monthly or yearly (yearly = 10\xD7 monthly, 2 months free)",
|
|
169
|
-
parseBillingPeriod
|
|
170
|
-
).option(
|
|
171
|
-
"--addon <key[:qty]>",
|
|
172
|
-
"Bundled addon to purchase with the plan change (repeatable, e.g. --addon db.standard:2)",
|
|
173
|
-
collectAddon,
|
|
174
|
-
[]
|
|
175
|
-
).option(
|
|
176
|
-
"-w, --wait",
|
|
177
|
-
"After hosted-checkout opens, poll status until the payment is confirmed"
|
|
178
|
-
).option(
|
|
179
|
-
"--timeout <seconds>",
|
|
180
|
-
"Maximum wait time in seconds (default 600)",
|
|
181
|
-
(v) => Number.parseInt(v, 10),
|
|
182
|
-
600
|
|
183
|
-
).option(
|
|
184
|
-
"--no-open",
|
|
185
|
-
"Do not auto-open the payment URL in the default browser"
|
|
186
|
-
).action(async (planKey, options) => {
|
|
187
|
-
try {
|
|
188
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
189
|
-
const client = getApiClient();
|
|
190
|
-
let targetPlan = planKey || options.plan;
|
|
191
|
-
const billingPeriod = options.billingPeriod;
|
|
192
|
-
const addons = Array.isArray(options.addon) && options.addon.length > 0 ? options.addon : void 0;
|
|
193
|
-
if (!targetPlan) {
|
|
194
|
-
const _spinner = startSpinner("Fetching plans...");
|
|
195
|
-
const catalog = await client.subscription.getCatalog.query();
|
|
196
|
-
succeedSpinner();
|
|
197
|
-
const plans = catalog?.plans || catalog || [];
|
|
198
|
-
if (!Array.isArray(plans) || plans.length === 0) {
|
|
199
|
-
log("No plans available.");
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
targetPlan = await select(
|
|
203
|
-
"Select a plan:",
|
|
204
|
-
plans.map((p) => ({
|
|
205
|
-
name: `${p.planKey || p.key || p.name} ${p.priceHalalas ? `(${(p.priceHalalas / 100).toFixed(2)} SAR/mo)` : "(Free)"}`,
|
|
206
|
-
value: p.planKey || p.key || p.name
|
|
207
|
-
})),
|
|
208
|
-
{
|
|
209
|
-
field: "plan",
|
|
210
|
-
flag: "--plan",
|
|
211
|
-
context: {
|
|
212
|
-
available: plans.map((p) => ({
|
|
213
|
-
key: p.planKey || p.key || p.name,
|
|
214
|
-
priceHalalas: p.priceHalalas ?? 0
|
|
215
|
-
}))
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
if (!targetPlan) {
|
|
221
|
-
throw new Error("No plan selected");
|
|
222
|
-
}
|
|
223
|
-
const _previewSpinner = startSpinner("Calculating change...");
|
|
224
|
-
let preview;
|
|
225
|
-
try {
|
|
226
|
-
preview = await client.subscription.previewPlanChange.query({
|
|
227
|
-
planKey: targetPlan,
|
|
228
|
-
planQuantity: options.quantity,
|
|
229
|
-
billingPeriod,
|
|
230
|
-
addons
|
|
231
|
-
});
|
|
232
|
-
succeedSpinner();
|
|
233
|
-
} catch {
|
|
234
|
-
failSpinner();
|
|
235
|
-
preview = null;
|
|
236
|
-
}
|
|
237
|
-
if (!shouldSkipConfirmation()) {
|
|
238
|
-
log("");
|
|
239
|
-
log(`Plan: ${colors.cyan(targetPlan)}`);
|
|
240
|
-
if (options.quantity) log(`Quantity: ${options.quantity}`);
|
|
241
|
-
if (billingPeriod) log(`Billing period: ${billingPeriod}`);
|
|
242
|
-
if (addons && addons.length > 0) {
|
|
243
|
-
log(
|
|
244
|
-
`Addons: ${addons.map((a) => `${a.addonKey}\xD7${a.quantity}`).join(", ")}`
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
if (preview?.amountDue !== void 0) {
|
|
248
|
-
log(
|
|
249
|
-
`Amount due now: ${colors.bold(`${(preview.amountDue / 100).toFixed(2)} SAR`)}`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
log("");
|
|
253
|
-
const confirmed = await confirm(
|
|
254
|
-
`Switch to plan "${targetPlan}"?`,
|
|
255
|
-
false,
|
|
256
|
-
{
|
|
257
|
-
field: "confirm_upgrade",
|
|
258
|
-
flag: "--yes",
|
|
259
|
-
context: {
|
|
260
|
-
plan: targetPlan,
|
|
261
|
-
quantity: options.quantity,
|
|
262
|
-
billingPeriod,
|
|
263
|
-
addons,
|
|
264
|
-
amountDueHalalas: preview?.amountDue
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
);
|
|
268
|
-
if (!confirmed) {
|
|
269
|
-
log("Cancelled.");
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
const _changeSpinner = startSpinner("Changing plan...");
|
|
274
|
-
const result = await client.subscription.changePlan.mutate({
|
|
275
|
-
planKey: targetPlan,
|
|
276
|
-
planQuantity: options.quantity,
|
|
277
|
-
billingPeriod,
|
|
278
|
-
addons
|
|
279
|
-
});
|
|
280
|
-
succeedSpinner("Plan changed!");
|
|
281
|
-
if (result?.applied) {
|
|
282
|
-
if (isJsonMode()) {
|
|
283
|
-
outputData({ ...result, status: "applied" });
|
|
284
|
-
} else {
|
|
285
|
-
box("Plan Changed", [
|
|
286
|
-
`Plan: ${colors.cyan(targetPlan)}`,
|
|
287
|
-
colors.success("Applied immediately")
|
|
288
|
-
]);
|
|
289
|
-
}
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
if (!result?.paymentUrl || !result?.orderId) {
|
|
293
|
-
if (isJsonMode()) {
|
|
294
|
-
outputData({ ...result, status: "deferred" });
|
|
295
|
-
} else {
|
|
296
|
-
box("Plan Change Staged", [
|
|
297
|
-
`Plan: ${colors.cyan(targetPlan)}`,
|
|
298
|
-
"Will apply at the end of the current billing period."
|
|
299
|
-
]);
|
|
300
|
-
}
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
const orderId = result.orderId;
|
|
304
|
-
const paymentUrl = result.paymentUrl;
|
|
305
|
-
if (!options.wait) {
|
|
306
|
-
if (isJsonMode()) {
|
|
307
|
-
outputData({
|
|
308
|
-
...result,
|
|
309
|
-
status: "payment_required",
|
|
310
|
-
hint: "Re-run with --wait to poll until checkout completes, or call `tarout billing confirm <orderId>` after the browser flow."
|
|
311
|
-
});
|
|
312
|
-
} else {
|
|
313
|
-
box("Payment Required", [
|
|
314
|
-
`Plan: ${colors.cyan(targetPlan)}`,
|
|
315
|
-
`Open: ${colors.cyan(paymentUrl)}`,
|
|
316
|
-
`Order ID: ${colors.dim(orderId)}`,
|
|
317
|
-
`Then: ${colors.dim(`tarout billing confirm ${orderId.slice(0, 8)}`)} or rerun upgrade with --wait`
|
|
318
|
-
]);
|
|
319
|
-
}
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
if (options.open !== false) {
|
|
323
|
-
try {
|
|
324
|
-
await open(paymentUrl);
|
|
325
|
-
} catch {
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (isJsonMode()) {
|
|
329
|
-
outputJsonLine({
|
|
330
|
-
type: "event",
|
|
331
|
-
event: "checkout_started",
|
|
332
|
-
orderId,
|
|
333
|
-
paymentUrl
|
|
334
|
-
});
|
|
335
|
-
} else {
|
|
336
|
-
log("");
|
|
337
|
-
log(`Open this URL to complete payment:`);
|
|
338
|
-
log(` ${colors.cyan(paymentUrl)}`);
|
|
339
|
-
log(`Order ID: ${colors.dim(orderId)}`);
|
|
340
|
-
log(`Polling for confirmation (up to ${options.timeout}s)...`);
|
|
341
|
-
}
|
|
342
|
-
const final = await pollCheckoutUntilTerminal(client, orderId, {
|
|
343
|
-
timeoutMs: options.timeout * 1e3,
|
|
344
|
-
intervalMs: 4e3
|
|
345
|
-
});
|
|
346
|
-
if (final.status === "PAID") {
|
|
347
|
-
if (isJsonMode()) {
|
|
348
|
-
outputData({
|
|
349
|
-
applied: true,
|
|
350
|
-
orderId,
|
|
351
|
-
status: "paid",
|
|
352
|
-
paidAt: final.paidAt
|
|
353
|
-
});
|
|
354
|
-
} else {
|
|
355
|
-
box("Payment Confirmed", [
|
|
356
|
-
`Plan: ${colors.cyan(targetPlan)}`,
|
|
357
|
-
colors.success("Subscription is active. Retry your last action.")
|
|
358
|
-
]);
|
|
359
|
-
}
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
if (final.status === "FAILED" || final.status === "EXPIRED") {
|
|
363
|
-
outputError(
|
|
364
|
-
final.status === "EXPIRED" ? "CHECKOUT_EXPIRED" : "CHECKOUT_FAILED",
|
|
365
|
-
final.failureReason ?? `Checkout ${final.status.toLowerCase()}`,
|
|
366
|
-
{ orderId, status: final.status, failedAt: final.failedAt }
|
|
367
|
-
);
|
|
368
|
-
if (!isJsonMode()) {
|
|
369
|
-
box("Payment Failed", [
|
|
370
|
-
`Order ID: ${colors.dim(orderId)}`,
|
|
371
|
-
`Reason: ${final.failureReason ?? final.status}`
|
|
372
|
-
]);
|
|
373
|
-
}
|
|
374
|
-
exit(ExitCode.GENERAL_ERROR);
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
outputError(
|
|
378
|
-
"CHECKOUT_TIMEOUT",
|
|
379
|
-
`Checkout still PENDING after ${options.timeout}s; the agent should keep polling or surface the URL.`,
|
|
380
|
-
{ orderId, status: final.status, paymentUrl }
|
|
381
|
-
);
|
|
382
|
-
if (!isJsonMode()) {
|
|
383
|
-
box("Still Waiting", [
|
|
384
|
-
`Order ID: ${colors.dim(orderId)}`,
|
|
385
|
-
`Run: ${colors.dim(`tarout billing wait ${orderId.slice(0, 8)} --timeout 600`)}`
|
|
386
|
-
]);
|
|
387
|
-
}
|
|
388
|
-
exit(ExitCode.GENERAL_ERROR);
|
|
389
|
-
} catch (err) {
|
|
390
|
-
handleError(err);
|
|
391
|
-
}
|
|
392
|
-
});
|
|
393
|
-
billing.command("confirm").argument("<orderId>", "Order ID returned by `billing upgrade`").description("Manually confirm a pending checkout (skips browser flow)").action(async (orderId) => {
|
|
394
|
-
try {
|
|
395
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
396
|
-
const client = getApiClient();
|
|
397
|
-
const _s = startSpinner("Confirming checkout...");
|
|
398
|
-
const result = await client.subscription.confirmCheckout.mutate({
|
|
399
|
-
orderId
|
|
400
|
-
});
|
|
401
|
-
succeedSpinner("Checkout confirmed.");
|
|
402
|
-
if (isJsonMode()) {
|
|
403
|
-
outputData(result);
|
|
404
|
-
} else {
|
|
405
|
-
box("Confirmed", [
|
|
406
|
-
`Order ID: ${colors.dim(orderId)}`,
|
|
407
|
-
colors.success("Subscription is active.")
|
|
408
|
-
]);
|
|
409
|
-
}
|
|
410
|
-
} catch (err) {
|
|
411
|
-
handleError(err);
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
billing.command("wait").argument("<orderId>", "Order ID to wait on").description("Poll a pending checkout until it resolves").option(
|
|
415
|
-
"--timeout <seconds>",
|
|
416
|
-
"Maximum wait time in seconds (default 600)",
|
|
417
|
-
(v) => Number.parseInt(v, 10),
|
|
418
|
-
600
|
|
419
|
-
).action(async (orderId, options) => {
|
|
420
|
-
try {
|
|
421
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
422
|
-
const client = getApiClient();
|
|
423
|
-
if (isJsonMode()) {
|
|
424
|
-
outputJsonLine({
|
|
425
|
-
type: "event",
|
|
426
|
-
event: "checkout_polling_started",
|
|
427
|
-
orderId,
|
|
428
|
-
timeoutSeconds: options.timeout
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
const final = await pollCheckoutUntilTerminal(client, orderId, {
|
|
432
|
-
timeoutMs: options.timeout * 1e3,
|
|
433
|
-
intervalMs: 4e3
|
|
434
|
-
});
|
|
435
|
-
if (final.status === "PAID") {
|
|
436
|
-
if (isJsonMode()) {
|
|
437
|
-
outputData({ orderId, status: "paid", paidAt: final.paidAt });
|
|
438
|
-
} else {
|
|
439
|
-
box("Confirmed", [
|
|
440
|
-
`Order ID: ${colors.dim(orderId)}`,
|
|
441
|
-
colors.success("Payment confirmed.")
|
|
442
|
-
]);
|
|
443
|
-
}
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (final.status === "PENDING") {
|
|
447
|
-
outputError("CHECKOUT_TIMEOUT", "Still pending after timeout", {
|
|
448
|
-
orderId
|
|
449
|
-
});
|
|
450
|
-
exit(ExitCode.GENERAL_ERROR);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
outputError(
|
|
454
|
-
final.status === "EXPIRED" ? "CHECKOUT_EXPIRED" : "CHECKOUT_FAILED",
|
|
455
|
-
final.failureReason ?? `Checkout ${final.status.toLowerCase()}`,
|
|
456
|
-
{ orderId, status: final.status }
|
|
457
|
-
);
|
|
458
|
-
exit(ExitCode.GENERAL_ERROR);
|
|
459
|
-
} catch (err) {
|
|
460
|
-
handleError(err);
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
billing.command("cancel").description("Cancel current subscription (at period end)").action(async () => {
|
|
464
|
-
try {
|
|
465
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
466
|
-
if (!shouldSkipConfirmation()) {
|
|
467
|
-
log("");
|
|
468
|
-
log(
|
|
469
|
-
colors.warn(
|
|
470
|
-
"Cancelling your subscription will downgrade to free tier at the end of the billing period."
|
|
471
|
-
)
|
|
472
|
-
);
|
|
473
|
-
log("");
|
|
474
|
-
const confirmed = await confirm(
|
|
475
|
-
"Are you sure you want to cancel your subscription?",
|
|
476
|
-
false,
|
|
477
|
-
{ field: "confirm_cancel", flag: "--yes" }
|
|
478
|
-
);
|
|
479
|
-
if (!confirmed) {
|
|
480
|
-
log("Cancelled.");
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
const client = getApiClient();
|
|
485
|
-
const _spinner = startSpinner("Cancelling subscription...");
|
|
486
|
-
await client.subscription.cancel.mutate();
|
|
487
|
-
succeedSpinner("Subscription scheduled for cancellation");
|
|
488
|
-
if (isJsonMode()) {
|
|
489
|
-
outputData({ cancelled: true });
|
|
490
|
-
} else {
|
|
491
|
-
log("");
|
|
492
|
-
log(
|
|
493
|
-
"Your subscription will remain active until the end of the current billing period."
|
|
494
|
-
);
|
|
495
|
-
log(`To undo: ${colors.dim("tarout billing resume")}`);
|
|
496
|
-
log("");
|
|
497
|
-
}
|
|
498
|
-
} catch (err) {
|
|
499
|
-
handleError(err);
|
|
500
|
-
}
|
|
501
|
-
});
|
|
502
|
-
billing.command("resume").description("Resume a cancelled subscription").action(async () => {
|
|
503
|
-
try {
|
|
504
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
505
|
-
const client = getApiClient();
|
|
506
|
-
const _spinner = startSpinner("Resuming subscription...");
|
|
507
|
-
await client.subscription.resume.mutate();
|
|
508
|
-
succeedSpinner("Subscription resumed!");
|
|
509
|
-
if (isJsonMode()) {
|
|
510
|
-
outputData({ resumed: true });
|
|
511
|
-
} else {
|
|
512
|
-
log("");
|
|
513
|
-
log(
|
|
514
|
-
colors.success(
|
|
515
|
-
"Your subscription has been resumed and will continue normally."
|
|
516
|
-
)
|
|
517
|
-
);
|
|
518
|
-
log("");
|
|
519
|
-
}
|
|
520
|
-
} catch (err) {
|
|
521
|
-
handleError(err);
|
|
522
|
-
}
|
|
523
|
-
});
|
|
524
|
-
billing.command("addon:add").argument("<addon>", "Addon key to add").option("-q, --quantity <n>", "Addon quantity", Number.parseInt).description("Purchase an addon slot").action(async (addonKey, options) => {
|
|
525
|
-
try {
|
|
526
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
527
|
-
const quantity = options.quantity || 1;
|
|
528
|
-
if (!shouldSkipConfirmation()) {
|
|
529
|
-
log("");
|
|
530
|
-
log(`Addon: ${colors.cyan(addonKey)}`);
|
|
531
|
-
log(`Quantity: ${quantity}`);
|
|
532
|
-
log("");
|
|
533
|
-
const confirmed = await confirm(
|
|
534
|
-
`Add addon "${addonKey}" \xD7 ${quantity}?`,
|
|
535
|
-
false,
|
|
536
|
-
{
|
|
537
|
-
field: "confirm_addon_add",
|
|
538
|
-
flag: "--yes",
|
|
539
|
-
context: { addonKey, quantity }
|
|
540
|
-
}
|
|
541
|
-
);
|
|
542
|
-
if (!confirmed) {
|
|
543
|
-
log("Cancelled.");
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
const client = getApiClient();
|
|
548
|
-
const _spinner = startSpinner("Adding addon...");
|
|
549
|
-
const result = await client.subscription.addAddon.mutate({
|
|
550
|
-
addonKey,
|
|
551
|
-
quantity
|
|
552
|
-
});
|
|
553
|
-
succeedSpinner("Addon added!");
|
|
554
|
-
if (isJsonMode()) {
|
|
555
|
-
outputData(result);
|
|
556
|
-
} else {
|
|
557
|
-
box("Addon Added", [
|
|
558
|
-
`Addon: ${colors.cyan(addonKey)}`,
|
|
559
|
-
`Quantity: ${quantity}`,
|
|
560
|
-
result?.paymentUrl ? `Payment: ${colors.cyan(result.paymentUrl)}` : colors.success("Applied immediately")
|
|
561
|
-
]);
|
|
562
|
-
}
|
|
563
|
-
} catch (err) {
|
|
564
|
-
handleError(err);
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
billing.command("addon:remove").argument("<addon>", "Addon key to remove").description("Remove an addon").action(async (addonKey) => {
|
|
568
|
-
try {
|
|
569
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
570
|
-
if (!shouldSkipConfirmation()) {
|
|
571
|
-
const confirmed = await confirm(
|
|
572
|
-
`Remove addon "${addonKey}"?`,
|
|
573
|
-
false,
|
|
574
|
-
{
|
|
575
|
-
field: "confirm_addon_remove",
|
|
576
|
-
flag: "--yes",
|
|
577
|
-
context: { addonKey }
|
|
578
|
-
}
|
|
579
|
-
);
|
|
580
|
-
if (!confirmed) {
|
|
581
|
-
log("Cancelled.");
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
const client = getApiClient();
|
|
586
|
-
const _spinner = startSpinner("Removing addon...");
|
|
587
|
-
await client.subscription.removeAddon.mutate({ addonKey });
|
|
588
|
-
succeedSpinner("Addon removed!");
|
|
589
|
-
if (isJsonMode()) {
|
|
590
|
-
outputData({ removed: true, addonKey });
|
|
591
|
-
}
|
|
592
|
-
} catch (err) {
|
|
593
|
-
handleError(err);
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
billing.command("plan:quantity").argument("<quantity>", "New plan quantity", Number.parseInt).description("Set quantity for a multi-slot plan").action(async (quantity) => {
|
|
597
|
-
try {
|
|
598
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
599
|
-
const client = getApiClient();
|
|
600
|
-
const _spinner = startSpinner("Updating plan quantity...");
|
|
601
|
-
const result = await client.subscription.setPlanQuantity.mutate({
|
|
602
|
-
quantity
|
|
603
|
-
});
|
|
604
|
-
succeedSpinner("Plan quantity updated!");
|
|
605
|
-
if (isJsonMode()) outputData(result);
|
|
606
|
-
} catch (err) {
|
|
607
|
-
handleError(err);
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
billing.command("addon:quantity").argument("<addon>", "Addon key").argument("<quantity>", "New quantity", Number.parseInt).description("Update quantity for an existing addon").action(async (addonKey, quantity) => {
|
|
611
|
-
try {
|
|
612
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
613
|
-
const client = getApiClient();
|
|
614
|
-
const _spinner = startSpinner("Updating addon quantity...");
|
|
615
|
-
const result = await client.subscription.updateAddonQuantity.mutate({
|
|
616
|
-
addonKey,
|
|
617
|
-
quantity
|
|
618
|
-
});
|
|
619
|
-
succeedSpinner("Addon quantity updated!");
|
|
620
|
-
if (isJsonMode()) outputData(result);
|
|
621
|
-
} catch (err) {
|
|
622
|
-
handleError(err);
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
billing.command("addon:preview").argument("<addon>", "Addon key").option("-q, --quantity <n>", "Quantity", Number.parseInt).description("Preview cost of purchasing addons").action(async (addonKey, options) => {
|
|
626
|
-
try {
|
|
627
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
628
|
-
const client = getApiClient();
|
|
629
|
-
const _spinner = startSpinner("Calculating preview...");
|
|
630
|
-
const preview = await client.subscription.previewAddonsPurchase.query({
|
|
631
|
-
addons: [{ addonKey, quantity: options.quantity || 1 }]
|
|
632
|
-
});
|
|
633
|
-
succeedSpinner();
|
|
634
|
-
if (isJsonMode()) {
|
|
635
|
-
outputData(preview);
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
const p = preview;
|
|
639
|
-
log("");
|
|
640
|
-
log(colors.bold("Addon Purchase Preview"));
|
|
641
|
-
log(` Addon: ${colors.cyan(addonKey)}`);
|
|
642
|
-
log(` Quantity: ${options.quantity || 1}`);
|
|
643
|
-
if (p.amountDue !== void 0) {
|
|
644
|
-
log(
|
|
645
|
-
` Amount Due: ${colors.bold(`${(p.amountDue / 100).toFixed(2)} SAR`)}`
|
|
646
|
-
);
|
|
647
|
-
}
|
|
648
|
-
log("");
|
|
649
|
-
} catch (err) {
|
|
650
|
-
handleError(err);
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
billing.command("addon:buy").argument("<addon>", "Addon key").option("-q, --quantity <n>", "Quantity", Number.parseInt).description("Purchase addon slots").action(async (addonKey, options) => {
|
|
654
|
-
try {
|
|
655
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
656
|
-
const quantity = options.quantity || 1;
|
|
657
|
-
if (!shouldSkipConfirmation()) {
|
|
658
|
-
log(`
|
|
659
|
-
Purchase ${quantity}\xD7 ${colors.cyan(addonKey)}?`);
|
|
660
|
-
const confirmed = await confirm("Proceed?", false, {
|
|
661
|
-
field: "confirm_addon_buy",
|
|
662
|
-
flag: "--yes",
|
|
663
|
-
context: { addonKey, quantity }
|
|
664
|
-
});
|
|
665
|
-
if (!confirmed) {
|
|
666
|
-
log("Cancelled.");
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
const client = getApiClient();
|
|
671
|
-
const _spinner = startSpinner("Purchasing addon...");
|
|
672
|
-
const result = await client.subscription.purchaseAddons.mutate({
|
|
673
|
-
addons: [{ addonKey, quantity }]
|
|
674
|
-
});
|
|
675
|
-
succeedSpinner("Addon purchased!");
|
|
676
|
-
if (isJsonMode()) outputData(result);
|
|
677
|
-
else if (result?.paymentUrl) {
|
|
678
|
-
log(
|
|
679
|
-
`
|
|
680
|
-
Payment required: ${colors.cyan(result.paymentUrl)}
|
|
681
|
-
`
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
} catch (err) {
|
|
685
|
-
handleError(err);
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
billing.command("plan:cancel-pending").description("Cancel a pending plan change (keeps current plan)").action(async () => {
|
|
689
|
-
try {
|
|
690
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
691
|
-
if (!shouldSkipConfirmation()) {
|
|
692
|
-
const confirmed = await confirm(
|
|
693
|
-
"Cancel the pending plan change?",
|
|
694
|
-
false,
|
|
695
|
-
{ field: "confirm_pending_cancel", flag: "--yes" }
|
|
696
|
-
);
|
|
697
|
-
if (!confirmed) {
|
|
698
|
-
log("Cancelled.");
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
const client = getApiClient();
|
|
703
|
-
const _spinner = startSpinner("Cancelling pending change...");
|
|
704
|
-
await client.subscription.cancelPendingPlanChange.mutate();
|
|
705
|
-
succeedSpinner("Pending plan change cancelled!");
|
|
706
|
-
if (isJsonMode()) outputData({ cancelled: true });
|
|
707
|
-
} catch (err) {
|
|
708
|
-
handleError(err);
|
|
709
|
-
}
|
|
710
|
-
});
|
|
711
|
-
billing.command("checkout:confirm").argument("<session-id>", "Checkout session ID").description("Confirm a pending checkout session").action(async (sessionId) => {
|
|
712
|
-
try {
|
|
713
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
714
|
-
const client = getApiClient();
|
|
715
|
-
const _spinner = startSpinner("Confirming checkout...");
|
|
716
|
-
const result = await client.subscription.confirmCheckout.mutate({
|
|
717
|
-
sessionId
|
|
718
|
-
});
|
|
719
|
-
succeedSpinner("Checkout confirmed!");
|
|
720
|
-
if (isJsonMode()) outputData(result);
|
|
721
|
-
} catch (err) {
|
|
722
|
-
handleError(err);
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
billing.command("checkout:get").argument("<session-id>", "Checkout session ID").description("Get details of a checkout session").action(async (sessionId) => {
|
|
726
|
-
try {
|
|
727
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
728
|
-
const client = getApiClient();
|
|
729
|
-
const _spinner = startSpinner("Fetching checkout...");
|
|
730
|
-
const data = await client.payment.getCheckout.query({
|
|
731
|
-
sessionId
|
|
732
|
-
});
|
|
733
|
-
succeedSpinner();
|
|
734
|
-
if (isJsonMode()) outputData(data);
|
|
735
|
-
else {
|
|
736
|
-
const d = data;
|
|
737
|
-
log("");
|
|
738
|
-
log(colors.bold("Checkout Session"));
|
|
739
|
-
log(` ID: ${d.sessionId || sessionId}`);
|
|
740
|
-
log(` Status: ${d.status || "-"}`);
|
|
741
|
-
log(
|
|
742
|
-
` Amount: ${d.amount !== void 0 ? `${(d.amount / 100).toFixed(2)} SAR` : "-"}`
|
|
743
|
-
);
|
|
744
|
-
log("");
|
|
745
|
-
}
|
|
746
|
-
} catch (err) {
|
|
747
|
-
handleError(err);
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
billing.command("checkout:pay").argument("<session-id>", "Checkout session ID").description("Submit card payment for a checkout session").option("--card-number <n>", "Card number").option("--exp-month <m>", "Expiry month").option("--exp-year <y>", "Expiry year").option("--cvv <cvv>", "CVV").option("--name <name>", "Cardholder name").action(async (sessionId, options) => {
|
|
751
|
-
try {
|
|
752
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
753
|
-
const client = getApiClient();
|
|
754
|
-
const _spinner = startSpinner("Processing payment...");
|
|
755
|
-
const result = await client.payment.submitCardPayment.mutate({
|
|
756
|
-
sessionId,
|
|
757
|
-
cardNumber: options.cardNumber,
|
|
758
|
-
expMonth: options.expMonth,
|
|
759
|
-
expYear: options.expYear,
|
|
760
|
-
cvv: options.cvv,
|
|
761
|
-
cardholderName: options.name
|
|
762
|
-
});
|
|
763
|
-
succeedSpinner("Payment submitted!");
|
|
764
|
-
if (isJsonMode()) outputData(result);
|
|
765
|
-
} catch (err) {
|
|
766
|
-
handleError(err);
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
billing.command("usage").description("Show resource usage breakdown").action(async () => {
|
|
770
|
-
try {
|
|
771
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
772
|
-
const client = getApiClient();
|
|
773
|
-
const _spinner = startSpinner("Fetching usage...");
|
|
774
|
-
const data = await client.billing.getUsageBreakdown.query();
|
|
775
|
-
succeedSpinner();
|
|
776
|
-
if (isJsonMode()) {
|
|
777
|
-
outputData(data);
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
const d = data;
|
|
781
|
-
log("");
|
|
782
|
-
log(colors.bold("Usage Breakdown"));
|
|
783
|
-
log(` Apps: ${d.apps ?? "-"}`);
|
|
784
|
-
log(` Databases: ${d.databases ?? "-"}`);
|
|
785
|
-
log(` Storage: ${d.storage ?? "-"}`);
|
|
786
|
-
log(` Domains: ${d.domains ?? "-"}`);
|
|
787
|
-
log("");
|
|
788
|
-
} catch (err) {
|
|
789
|
-
handleError(err);
|
|
790
|
-
}
|
|
791
|
-
});
|
|
792
|
-
billing.command("resources").description("Show detailed resource usage and costs").action(async () => {
|
|
793
|
-
try {
|
|
794
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
795
|
-
const client = getApiClient();
|
|
796
|
-
const _spinner = startSpinner("Fetching resource usage...");
|
|
797
|
-
const data = await client.billing.getDetailedResourceUsage.query();
|
|
798
|
-
succeedSpinner();
|
|
799
|
-
if (isJsonMode()) {
|
|
800
|
-
outputData(data);
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
const list = Array.isArray(data) ? data : data?.resources || [];
|
|
804
|
-
if (!list.length) {
|
|
805
|
-
log("\nNo active resources.\n");
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
log("");
|
|
809
|
-
table(
|
|
810
|
-
["TYPE", "NAME", "COST (SAR)"],
|
|
811
|
-
list.map((r) => [
|
|
812
|
-
r.type || "-",
|
|
813
|
-
r.name || "-",
|
|
814
|
-
r.cost !== void 0 ? (r.cost / 100).toFixed(2) : "-"
|
|
815
|
-
])
|
|
816
|
-
);
|
|
817
|
-
log("");
|
|
818
|
-
} catch (err) {
|
|
819
|
-
handleError(err);
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
billing.command("trend").description("Show daily cost trend").option("--days <n>", "Number of days", "30").action(async (options) => {
|
|
823
|
-
try {
|
|
824
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
825
|
-
const client = getApiClient();
|
|
826
|
-
const _spinner = startSpinner("Fetching cost trend...");
|
|
827
|
-
const data = await client.billing.getDailyCostTrend.query({
|
|
828
|
-
days: Number.parseInt(options.days || "30")
|
|
829
|
-
});
|
|
830
|
-
succeedSpinner();
|
|
831
|
-
if (isJsonMode()) {
|
|
832
|
-
outputData(data);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
const list = Array.isArray(data) ? data : data?.trend || [];
|
|
836
|
-
if (!list.length) {
|
|
837
|
-
log("\nNo cost data found.\n");
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
log("");
|
|
841
|
-
log(colors.bold("Daily Cost Trend"));
|
|
842
|
-
table(
|
|
843
|
-
["DATE", "COST (SAR)"],
|
|
844
|
-
list.map((d) => [
|
|
845
|
-
d.date || "-",
|
|
846
|
-
d.cost !== void 0 ? (d.cost / 100).toFixed(2) : "-"
|
|
847
|
-
])
|
|
848
|
-
);
|
|
849
|
-
log("");
|
|
850
|
-
} catch (err) {
|
|
851
|
-
handleError(err);
|
|
852
|
-
}
|
|
853
|
-
});
|
|
854
|
-
billing.command("estimate").description("Estimate cost for a resource type").option("--type <type>", "Resource type (app, database, storage, domain)").option("--plan <plan>", "Plan key").action(async (options) => {
|
|
855
|
-
try {
|
|
856
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
857
|
-
const client = getApiClient();
|
|
858
|
-
const _spinner = startSpinner("Calculating estimate...");
|
|
859
|
-
const data = await client.billing.getResourceEstimate.query({
|
|
860
|
-
resourceType: options.type,
|
|
861
|
-
planKey: options.plan
|
|
862
|
-
});
|
|
863
|
-
succeedSpinner();
|
|
864
|
-
if (isJsonMode()) {
|
|
865
|
-
outputData(data);
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
const d = data;
|
|
869
|
-
log("");
|
|
870
|
-
log(colors.bold("Resource Estimate"));
|
|
871
|
-
log(
|
|
872
|
-
` Monthly: ${d.monthly !== void 0 ? `${(d.monthly / 100).toFixed(2)} SAR` : "-"}`
|
|
873
|
-
);
|
|
874
|
-
log(
|
|
875
|
-
` Hourly: ${d.hourly !== void 0 ? `${(d.hourly / 100).toFixed(4)} SAR` : "-"}`
|
|
876
|
-
);
|
|
877
|
-
log("");
|
|
878
|
-
} catch (err) {
|
|
879
|
-
handleError(err);
|
|
880
|
-
}
|
|
881
|
-
});
|
|
882
|
-
billing.command("active-resources").description("List all active billable resources").action(async () => {
|
|
883
|
-
try {
|
|
884
|
-
if (!isLoggedIn()) throw new AuthError();
|
|
885
|
-
const client = getApiClient();
|
|
886
|
-
const _spinner = startSpinner("Fetching active resources...");
|
|
887
|
-
const data = await client.billing.getActiveResources.query();
|
|
888
|
-
succeedSpinner();
|
|
889
|
-
if (isJsonMode()) {
|
|
890
|
-
outputData(data);
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
const list = Array.isArray(data) ? data : data?.resources || [];
|
|
894
|
-
if (!list.length) {
|
|
895
|
-
log("\nNo active billable resources.\n");
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
log("");
|
|
899
|
-
table(
|
|
900
|
-
["TYPE", "NAME", "STATUS", "PLAN"],
|
|
901
|
-
list.map((r) => [
|
|
902
|
-
r.type || "-",
|
|
903
|
-
r.name || "-",
|
|
904
|
-
r.status || "-",
|
|
905
|
-
r.plan || r.planKey || "-"
|
|
906
|
-
])
|
|
907
|
-
);
|
|
908
|
-
log("");
|
|
909
|
-
} catch (err) {
|
|
910
|
-
handleError(err);
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
async function pollCheckoutUntilTerminal(client, orderId, opts) {
|
|
915
|
-
const deadline = Date.now() + opts.timeoutMs;
|
|
916
|
-
let lastStatus = "";
|
|
917
|
-
while (Date.now() < deadline) {
|
|
918
|
-
const r2 = await client.subscription.pollCheckoutStatus.query({ orderId });
|
|
919
|
-
if (r2.status !== lastStatus) {
|
|
920
|
-
if (isJsonMode()) {
|
|
921
|
-
outputJsonLine({
|
|
922
|
-
type: "event",
|
|
923
|
-
event: "checkout_status",
|
|
924
|
-
orderId,
|
|
925
|
-
status: r2.status
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
lastStatus = r2.status;
|
|
929
|
-
}
|
|
930
|
-
if (r2.status !== "PENDING") {
|
|
931
|
-
return {
|
|
932
|
-
status: r2.status,
|
|
933
|
-
paidAt: r2.paidAt,
|
|
934
|
-
failedAt: r2.failedAt,
|
|
935
|
-
failureReason: r2.failureReason
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
await new Promise((res) => setTimeout(res, opts.intervalMs));
|
|
939
|
-
}
|
|
940
|
-
const r = await client.subscription.pollCheckoutStatus.query({ orderId });
|
|
941
|
-
return {
|
|
942
|
-
status: r.status,
|
|
943
|
-
paidAt: r.paidAt,
|
|
944
|
-
failedAt: r.failedAt,
|
|
945
|
-
failureReason: r.failureReason
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
function collectAddon(value, previous) {
|
|
949
|
-
const [rawKey, rawQty] = value.split(":");
|
|
950
|
-
const addonKey = (rawKey || "").trim();
|
|
951
|
-
if (!addonKey) {
|
|
952
|
-
throw new InvalidArgumentError(
|
|
953
|
-
`Invalid --addon value "${value}". Expected key[:qty] (e.g. db.standard:2).`
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
const quantity = rawQty === void 0 ? 1 : Number.parseInt(rawQty, 10);
|
|
957
|
-
if (!Number.isFinite(quantity) || quantity <= 0) {
|
|
958
|
-
throw new InvalidArgumentError(
|
|
959
|
-
`Invalid --addon quantity in "${value}". Expected a positive integer.`
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
return [...previous, { addonKey, quantity }];
|
|
963
|
-
}
|
|
964
|
-
function parseBillingPeriod(raw) {
|
|
965
|
-
const v = raw.toLowerCase();
|
|
966
|
-
if (v === "monthly" || v === "yearly") return v;
|
|
967
|
-
throw new InvalidArgumentError(
|
|
968
|
-
`Invalid --billing-period "${raw}". Expected "monthly" or "yearly".`
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
function formatSubStatus(status) {
|
|
972
|
-
const map = {
|
|
973
|
-
active: colors.success("active"),
|
|
974
|
-
trialing: colors.info("trialing"),
|
|
975
|
-
past_due: colors.warn("past due"),
|
|
976
|
-
cancelled: colors.error("cancelled"),
|
|
977
|
-
none: colors.dim("none")
|
|
978
|
-
};
|
|
979
|
-
return map[status] || status;
|
|
980
|
-
}
|
|
981
|
-
function formatDate(date) {
|
|
982
|
-
if (!date) return colors.dim("-");
|
|
983
|
-
return new Date(date).toLocaleDateString("en-US", {
|
|
984
|
-
month: "short",
|
|
985
|
-
day: "numeric",
|
|
986
|
-
year: "numeric"
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
export {
|
|
991
|
-
startSpinner,
|
|
992
|
-
succeedSpinner,
|
|
993
|
-
failSpinner,
|
|
994
|
-
stopSpinner,
|
|
995
|
-
updateSpinner,
|
|
996
|
-
registerBillingCommands,
|
|
997
|
-
pollCheckoutUntilTerminal
|
|
998
|
-
};
|