@layr-labs/ecloud-cli 1.0.0-dev.1 → 1.0.0-dev.2
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/VERSION +2 -2
- package/dist/commands/billing/__tests__/status.test.js +23 -4
- package/dist/commands/billing/__tests__/status.test.js.map +1 -1
- package/dist/commands/billing/__tests__/top-up.test.js +89 -257
- package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
- package/dist/commands/billing/status.js +23 -4
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/top-up.js +79 -145
- package/dist/commands/billing/top-up.js.map +1 -1
- package/dist/commands/compute/app/deploy.js +1 -1
- package/dist/commands/compute/app/info.js +1 -1
- package/dist/commands/compute/app/list.js +1 -1
- package/dist/commands/compute/app/logs.js +1 -1
- package/dist/commands/compute/app/profile/set.js +1 -1
- package/dist/commands/compute/app/releases.js +1 -1
- package/dist/commands/compute/app/start.js +1 -1
- package/dist/commands/compute/app/stop.js +1 -1
- package/dist/commands/compute/app/terminate.js +1 -1
- package/dist/commands/compute/app/upgrade.js +1 -1
- package/dist/commands/compute/build/info.js +1 -1
- package/dist/commands/compute/build/list.js +1 -1
- package/dist/commands/compute/build/logs.js +1 -1
- package/dist/commands/compute/build/status.js +1 -1
- package/dist/commands/compute/build/submit.js +1 -1
- package/dist/commands/compute/build/verify.js +1 -1
- package/dist/commands/compute/undelegate.js +1 -1
- package/dist/hooks/init/__tests__/version-check.test.js +1 -1
- package/dist/hooks/init/version-check.js +1 -1
- package/package.json +2 -2
|
@@ -319,8 +319,7 @@ async function createBillingClient(flags) {
|
|
|
319
319
|
// src/commands/billing/top-up.ts
|
|
320
320
|
import { formatUnits } from "viem";
|
|
321
321
|
import chalk2 from "chalk";
|
|
322
|
-
import { input as input2
|
|
323
|
-
import open from "open";
|
|
322
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
324
323
|
|
|
325
324
|
// src/telemetry.ts
|
|
326
325
|
import {
|
|
@@ -384,22 +383,12 @@ async function withTelemetry(command, action) {
|
|
|
384
383
|
var POLL_INTERVAL_MS = 5e3;
|
|
385
384
|
var POLL_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
386
385
|
var BillingTopUp = class _BillingTopUp extends Command {
|
|
387
|
-
static description = "Purchase EigenCompute credits with USDC
|
|
388
|
-
static examples = [
|
|
389
|
-
"<%= config.bin %> billing top-up",
|
|
390
|
-
"<%= config.bin %> billing top-up --method usdc --amount 50",
|
|
391
|
-
"<%= config.bin %> billing top-up --method card --amount 25"
|
|
392
|
-
];
|
|
386
|
+
static description = "Purchase EigenCompute credits with USDC";
|
|
393
387
|
static flags = {
|
|
394
388
|
...commonFlags,
|
|
395
|
-
method: Flags2.string({
|
|
396
|
-
required: false,
|
|
397
|
-
description: "Payment method: usdc (on-chain) or card (credit card)",
|
|
398
|
-
options: ["usdc", "card"]
|
|
399
|
-
}),
|
|
400
389
|
amount: Flags2.string({
|
|
401
390
|
required: false,
|
|
402
|
-
description: "Amount to spend (
|
|
391
|
+
description: "Amount of USDC to spend (e.g., '50')"
|
|
403
392
|
}),
|
|
404
393
|
account: Flags2.string({
|
|
405
394
|
required: false,
|
|
@@ -435,156 +424,101 @@ ${chalk2.bold("Purchase EigenCompute credits")}`);
|
|
|
435
424
|
const remaining = status.remainingCredits ?? 0;
|
|
436
425
|
const applied = status.creditsApplied ?? 0;
|
|
437
426
|
baselineTotal = remaining + applied;
|
|
438
|
-
|
|
427
|
+
if (status.remainingCredits !== void 0) {
|
|
428
|
+
this.log(` ${chalk2.bold("Credits:")} ${chalk2.cyan(`$${status.remainingCredits.toFixed(2)}`)}`);
|
|
429
|
+
}
|
|
439
430
|
} catch {
|
|
440
431
|
this.debug("Could not fetch current credit balance");
|
|
441
432
|
}
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
});
|
|
449
|
-
if (method === "usdc") {
|
|
450
|
-
await this.handleUsdc(billing, flags, walletAddress, targetAccount, baselineTotal);
|
|
451
|
-
} else {
|
|
452
|
-
await this.handleCard(billing, flags, baselineTotal);
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
async handleUsdc(billing, flags, walletAddress, targetAccount, baselineTotal) {
|
|
457
|
-
const onChainState = await billing.getTopUpInfo();
|
|
458
|
-
const { usdcBalance, minimumPurchase } = onChainState;
|
|
459
|
-
const balanceFormatted = formatUnits(usdcBalance, 6);
|
|
460
|
-
this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
|
|
461
|
-
if (usdcBalance === BigInt(0)) {
|
|
462
|
-
this.log(`
|
|
433
|
+
const onChainState = await billing.getTopUpInfo();
|
|
434
|
+
const { usdcBalance, minimumPurchase } = onChainState;
|
|
435
|
+
const balanceFormatted = formatUnits(usdcBalance, 6);
|
|
436
|
+
this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
|
|
437
|
+
if (usdcBalance === BigInt(0)) {
|
|
438
|
+
this.log(`
|
|
463
439
|
${chalk2.yellow(" No USDC in wallet.")}`);
|
|
464
|
-
|
|
465
|
-
|
|
440
|
+
this.log(` Send USDC on Sepolia to: ${chalk2.cyan(walletAddress)}`);
|
|
441
|
+
this.log(` Then re-run: ${chalk2.cyan("ecloud billing top-up")}
|
|
466
442
|
`);
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
const minimumFormatted = formatUnits(minimumPurchase, 6);
|
|
470
|
-
const amountStr = flags.amount ?? await input2({
|
|
471
|
-
message: `How much USDC to spend on credits? (minimum: ${minimumFormatted})`,
|
|
472
|
-
validate: (val) => {
|
|
473
|
-
const n = parseFloat(val);
|
|
474
|
-
if (isNaN(n) || n <= 0) return "Enter a positive number";
|
|
475
|
-
const raw = BigInt(Math.round(n * 1e6));
|
|
476
|
-
if (raw < minimumPurchase)
|
|
477
|
-
return `Minimum purchase is ${minimumFormatted} USDC`;
|
|
478
|
-
if (raw > usdcBalance)
|
|
479
|
-
return `Insufficient balance. You have ${balanceFormatted} USDC`;
|
|
480
|
-
return true;
|
|
443
|
+
return;
|
|
481
444
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const { txHash } = await billing.topUp({
|
|
496
|
-
amount: amountRaw,
|
|
497
|
-
account: targetAccount
|
|
498
|
-
});
|
|
499
|
-
this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${txHash}`);
|
|
500
|
-
await this.pollForCredits(billing, flags, baselineTotal, amountFloat);
|
|
501
|
-
}
|
|
502
|
-
async handleCard(billing, flags, baselineTotal) {
|
|
503
|
-
const MINIMUM_DOLLARS = 5;
|
|
504
|
-
const amountStr = flags.amount ?? await input2({
|
|
505
|
-
message: `How many dollars of credits to purchase? (minimum: $${MINIMUM_DOLLARS})`,
|
|
506
|
-
validate: (val) => {
|
|
507
|
-
const n = parseInt(val, 10);
|
|
508
|
-
if (isNaN(n) || n <= 0) return "Enter a positive whole number";
|
|
509
|
-
if (n.toString() !== val.trim()) return "Enter a whole dollar amount (no cents)";
|
|
510
|
-
if (n < MINIMUM_DOLLARS) return `Minimum purchase is $${MINIMUM_DOLLARS}`;
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
const dollars = parseInt(amountStr, 10);
|
|
515
|
-
if (isNaN(dollars) || dollars < MINIMUM_DOLLARS) {
|
|
516
|
-
this.error(`Minimum purchase is $${MINIMUM_DOLLARS}`);
|
|
517
|
-
}
|
|
518
|
-
const amountCents = dollars * 100;
|
|
519
|
-
const { paymentMethods } = await billing.getPaymentMethods();
|
|
520
|
-
let useExistingCard = false;
|
|
521
|
-
let paymentMethodId;
|
|
522
|
-
if (paymentMethods.length > 0) {
|
|
523
|
-
const card = paymentMethods[0];
|
|
524
|
-
const lastFour = card.stripePaymentMethodId.slice(-4);
|
|
525
|
-
useExistingCard = await confirm({
|
|
526
|
-
message: `Use card on file (ending in ${lastFour})?`,
|
|
527
|
-
default: true
|
|
445
|
+
const minimumFormatted = formatUnits(minimumPurchase, 6);
|
|
446
|
+
const amountStr = flags.amount ?? await input2({
|
|
447
|
+
message: `How much USDC to spend on credits? (minimum: ${minimumFormatted})`,
|
|
448
|
+
validate: (val) => {
|
|
449
|
+
const n = parseFloat(val);
|
|
450
|
+
if (isNaN(n) || n <= 0) return "Enter a positive number";
|
|
451
|
+
const raw = BigInt(Math.round(n * 1e6));
|
|
452
|
+
if (raw < minimumPurchase)
|
|
453
|
+
return `Minimum purchase is ${minimumFormatted} USDC`;
|
|
454
|
+
if (raw > usdcBalance)
|
|
455
|
+
return `Insufficient balance. You have ${balanceFormatted} USDC`;
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
528
458
|
});
|
|
529
|
-
|
|
530
|
-
|
|
459
|
+
const amountFloat = parseFloat(amountStr);
|
|
460
|
+
const amountRaw = BigInt(Math.round(amountFloat * 1e6));
|
|
461
|
+
if (amountRaw < minimumPurchase) {
|
|
462
|
+
this.error(`Minimum purchase is ${minimumFormatted} USDC`);
|
|
531
463
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const result = await billing.purchaseCredits(amountCents, paymentMethodId);
|
|
536
|
-
if (result.checkoutUrl) {
|
|
537
|
-
this.log(`
|
|
538
|
-
${chalk2.cyan(result.checkoutUrl)}`);
|
|
539
|
-
this.log(chalk2.gray(" Opening checkout in browser..."));
|
|
540
|
-
await open(result.checkoutUrl);
|
|
541
|
-
} else {
|
|
542
|
-
this.log(` ${chalk2.green("\u2713")} Payment submitted`);
|
|
543
|
-
}
|
|
544
|
-
await this.pollForCredits(billing, flags, baselineTotal, dollars);
|
|
545
|
-
}
|
|
546
|
-
async pollForCredits(billing, flags, baselineTotal, amountPurchased) {
|
|
547
|
-
this.log(chalk2.gray("\n Waiting for credits to appear..."));
|
|
548
|
-
const startTime = Date.now();
|
|
549
|
-
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
550
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
551
|
-
try {
|
|
552
|
-
const status = await billing.getStatus({
|
|
553
|
-
productId: flags.product
|
|
554
|
-
});
|
|
555
|
-
const remaining = status.remainingCredits ?? 0;
|
|
556
|
-
const applied = status.creditsApplied ?? 0;
|
|
557
|
-
const currentTotal = remaining + applied;
|
|
558
|
-
this.debug(
|
|
559
|
-
`Poll: remaining=${remaining}, applied=${applied}, total=${currentTotal}, baseline=${baselineTotal}`
|
|
464
|
+
if (amountRaw > usdcBalance) {
|
|
465
|
+
this.error(
|
|
466
|
+
`Insufficient USDC balance. You have ${balanceFormatted} USDC but requested ${amountFloat.toFixed(2)}`
|
|
560
467
|
);
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
468
|
+
}
|
|
469
|
+
this.log(`
|
|
470
|
+
Purchasing ${chalk2.bold(`$${amountFloat.toFixed(2)}`)} in credits...`);
|
|
471
|
+
const { txHash } = await billing.topUp({
|
|
472
|
+
amount: amountRaw,
|
|
473
|
+
account: targetAccount
|
|
474
|
+
});
|
|
475
|
+
this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${txHash}`);
|
|
476
|
+
this.log(chalk2.gray("\n Waiting for credits to appear..."));
|
|
477
|
+
const startTime = Date.now();
|
|
478
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
479
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
480
|
+
try {
|
|
481
|
+
const status = await billing.getStatus({
|
|
482
|
+
productId: flags.product
|
|
483
|
+
});
|
|
484
|
+
const remaining = status.remainingCredits ?? 0;
|
|
485
|
+
const applied = status.creditsApplied ?? 0;
|
|
486
|
+
const currentTotal = remaining + applied;
|
|
487
|
+
this.debug(`Poll: remaining=${remaining}, applied=${applied}, total=${currentTotal}, baseline=${baselineTotal}`);
|
|
488
|
+
if (baselineTotal === void 0 || currentTotal > baselineTotal) {
|
|
489
|
+
const creditsAdded = baselineTotal !== void 0 ? currentTotal - baselineTotal : void 0;
|
|
490
|
+
const isMatched = creditsAdded !== void 0 && Math.abs(creditsAdded - amountFloat * 2) < 0.01;
|
|
491
|
+
const appliedFromTopUp = creditsAdded !== void 0 ? creditsAdded - remaining : 0;
|
|
492
|
+
this.log(`
|
|
493
|
+
${chalk2.green("\u2713")} Credits received: ${chalk2.cyan(`$${(creditsAdded ?? amountFloat).toFixed(2)}`)}`);
|
|
494
|
+
if (isMatched) {
|
|
495
|
+
this.log(` ${chalk2.green("\u2713")} Includes $${amountFloat.toFixed(2)} match bonus!`);
|
|
496
|
+
}
|
|
497
|
+
if (remaining > 0) {
|
|
498
|
+
this.log(` Remaining balance: ${chalk2.cyan(`$${remaining.toFixed(2)}`)}`);
|
|
499
|
+
}
|
|
500
|
+
if (appliedFromTopUp > 0) {
|
|
501
|
+
this.log(` ${chalk2.gray(`$${appliedFromTopUp.toFixed(2)} applied to current bill`)}`);
|
|
502
|
+
}
|
|
503
|
+
this.log();
|
|
504
|
+
return;
|
|
569
505
|
}
|
|
570
|
-
|
|
571
|
-
|
|
506
|
+
} catch {
|
|
507
|
+
this.debug("Error polling for credit balance");
|
|
572
508
|
}
|
|
573
|
-
} catch {
|
|
574
|
-
this.debug("Error polling for credit balance");
|
|
575
509
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
`
|
|
510
|
+
this.log(
|
|
511
|
+
`
|
|
579
512
|
${chalk2.yellow("\u26A0")} Credits haven't appeared yet. This can take a few minutes.`
|
|
580
|
-
|
|
581
|
-
|
|
513
|
+
);
|
|
514
|
+
this.log(` ${chalk2.gray("Check your balance:")} ecloud billing status
|
|
582
515
|
`);
|
|
516
|
+
});
|
|
583
517
|
}
|
|
584
518
|
};
|
|
585
519
|
|
|
586
520
|
// src/commands/billing/__tests__/top-up.test.ts
|
|
587
|
-
import { input as input3
|
|
521
|
+
import { input as input3 } from "@inquirer/prompts";
|
|
588
522
|
vi.mock("../../../client", () => ({
|
|
589
523
|
createBillingClient: vi.fn()
|
|
590
524
|
}));
|
|
@@ -592,12 +526,7 @@ vi.mock("../../../telemetry", () => ({
|
|
|
592
526
|
withTelemetry: vi.fn((_cmd, fn) => fn())
|
|
593
527
|
}));
|
|
594
528
|
vi.mock("@inquirer/prompts", () => ({
|
|
595
|
-
input: vi.fn()
|
|
596
|
-
select: vi.fn(),
|
|
597
|
-
confirm: vi.fn()
|
|
598
|
-
}));
|
|
599
|
-
vi.mock("open", () => ({
|
|
600
|
-
default: vi.fn()
|
|
529
|
+
input: vi.fn()
|
|
601
530
|
}));
|
|
602
531
|
var WALLET_ADDRESS = "0x1234567890abcdef1234567890abcdef12345678";
|
|
603
532
|
var TX_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
|
|
@@ -612,9 +541,7 @@ describe("ecloud billing top-up", () => {
|
|
|
612
541
|
address: WALLET_ADDRESS,
|
|
613
542
|
getStatus: vi.fn(),
|
|
614
543
|
getTopUpInfo: vi.fn(),
|
|
615
|
-
topUp: vi.fn()
|
|
616
|
-
getPaymentMethods: vi.fn(),
|
|
617
|
-
purchaseCredits: vi.fn()
|
|
544
|
+
topUp: vi.fn()
|
|
618
545
|
};
|
|
619
546
|
createBillingClient.mockResolvedValue(mockBilling);
|
|
620
547
|
input3.mockResolvedValue("50");
|
|
@@ -659,7 +586,7 @@ describe("ecloud billing top-up", () => {
|
|
|
659
586
|
setupOnChainState();
|
|
660
587
|
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
661
588
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
662
|
-
const cmd = createCommand({ amount: "50"
|
|
589
|
+
const cmd = createCommand({ amount: "50" });
|
|
663
590
|
const promise = cmd.run();
|
|
664
591
|
for (let i = 0; i < 10; i++) {
|
|
665
592
|
await vi.advanceTimersByTimeAsync(5e3);
|
|
@@ -681,7 +608,7 @@ describe("ecloud billing top-up", () => {
|
|
|
681
608
|
it("zero USDC balance: exits with fund wallet message", async () => {
|
|
682
609
|
setupOnChainState({ usdcBalance: BigInt(0) });
|
|
683
610
|
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "inactive" });
|
|
684
|
-
const cmd = createCommand({ amount: "50"
|
|
611
|
+
const cmd = createCommand({ amount: "50" });
|
|
685
612
|
await cmd.run();
|
|
686
613
|
const fullOutput = logOutput.join("\n");
|
|
687
614
|
expect(fullOutput).toContain("No USDC in wallet");
|
|
@@ -692,7 +619,7 @@ describe("ecloud billing top-up", () => {
|
|
|
692
619
|
it("below minimum purchase: shows error", async () => {
|
|
693
620
|
setupOnChainState({ minimumPurchase: BigInt(1e7) });
|
|
694
621
|
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "inactive" });
|
|
695
|
-
const cmd = createCommand({ amount: "5"
|
|
622
|
+
const cmd = createCommand({ amount: "5" });
|
|
696
623
|
await expect(cmd.run()).rejects.toThrow("Minimum purchase is 10 USDC");
|
|
697
624
|
});
|
|
698
625
|
it("--account flag: passes different address to topUp", async () => {
|
|
@@ -700,7 +627,7 @@ describe("ecloud billing top-up", () => {
|
|
|
700
627
|
setupOnChainState();
|
|
701
628
|
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
702
629
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
703
|
-
const cmd = createCommand({ amount: "50",
|
|
630
|
+
const cmd = createCommand({ amount: "50", account: targetAccount });
|
|
704
631
|
const promise = cmd.run();
|
|
705
632
|
for (let i = 0; i < 10; i++) {
|
|
706
633
|
await vi.advanceTimersByTimeAsync(5e3);
|
|
@@ -720,7 +647,7 @@ describe("ecloud billing top-up", () => {
|
|
|
720
647
|
subscriptionStatus: "active",
|
|
721
648
|
remainingCredits: 10
|
|
722
649
|
});
|
|
723
|
-
const cmd = createCommand({ amount: "50"
|
|
650
|
+
const cmd = createCommand({ amount: "50" });
|
|
724
651
|
const promise = cmd.run();
|
|
725
652
|
await vi.advanceTimersByTimeAsync(2e5);
|
|
726
653
|
await promise;
|
|
@@ -732,7 +659,7 @@ describe("ecloud billing top-up", () => {
|
|
|
732
659
|
setupOnChainState();
|
|
733
660
|
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
734
661
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "inactive" }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 100 });
|
|
735
|
-
const cmd = createCommand({ amount: "100"
|
|
662
|
+
const cmd = createCommand({ amount: "100" });
|
|
736
663
|
const promise = cmd.run();
|
|
737
664
|
for (let i = 0; i < 10; i++) {
|
|
738
665
|
await vi.advanceTimersByTimeAsync(5e3);
|
|
@@ -744,7 +671,7 @@ describe("ecloud billing top-up", () => {
|
|
|
744
671
|
setupOnChainState();
|
|
745
672
|
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
746
673
|
mockBilling.getStatus.mockRejectedValue(new Error("API unavailable"));
|
|
747
|
-
const cmd = createCommand({ amount: "50"
|
|
674
|
+
const cmd = createCommand({ amount: "50" });
|
|
748
675
|
const promise = cmd.run();
|
|
749
676
|
await vi.advanceTimersByTimeAsync(2e5);
|
|
750
677
|
await promise;
|
|
@@ -753,100 +680,5 @@ describe("ecloud billing top-up", () => {
|
|
|
753
680
|
expect(fullOutput).toContain("Transaction confirmed");
|
|
754
681
|
expect(fullOutput).toContain("Credits haven't appeared yet");
|
|
755
682
|
});
|
|
756
|
-
it("credit card: charges existing card on file", async () => {
|
|
757
|
-
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 35 });
|
|
758
|
-
mockBilling.getPaymentMethods.mockResolvedValue({
|
|
759
|
-
paymentMethods: [
|
|
760
|
-
{
|
|
761
|
-
id: "029641fc-3e5c-11f1-986c-5601121cbf6d",
|
|
762
|
-
stripePaymentMethodId: "pm_1ABC1234",
|
|
763
|
-
createdAt: "2026-04-20T15:00:00Z"
|
|
764
|
-
}
|
|
765
|
-
]
|
|
766
|
-
});
|
|
767
|
-
mockBilling.purchaseCredits.mockResolvedValue({
|
|
768
|
-
purchaseId: "a1b2c3d4",
|
|
769
|
-
amountCents: "2500"
|
|
770
|
-
});
|
|
771
|
-
confirm2.mockResolvedValue(true);
|
|
772
|
-
const cmd = createCommand({ amount: "25", method: "card" });
|
|
773
|
-
const promise = cmd.run();
|
|
774
|
-
for (let i = 0; i < 10; i++) {
|
|
775
|
-
await vi.advanceTimersByTimeAsync(5e3);
|
|
776
|
-
}
|
|
777
|
-
await promise;
|
|
778
|
-
const fullOutput = logOutput.join("\n");
|
|
779
|
-
expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(2500, "029641fc-3e5c-11f1-986c-5601121cbf6d");
|
|
780
|
-
expect(fullOutput).toContain("Payment submitted");
|
|
781
|
-
expect(fullOutput).toContain("Credits received");
|
|
782
|
-
});
|
|
783
|
-
it("credit card: opens checkout when user declines existing card", async () => {
|
|
784
|
-
const openMock = (await import("open")).default;
|
|
785
|
-
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
|
|
786
|
-
mockBilling.getPaymentMethods.mockResolvedValue({
|
|
787
|
-
paymentMethods: [
|
|
788
|
-
{
|
|
789
|
-
id: "029641fc-3e5c-11f1-986c-5601121cbf6d",
|
|
790
|
-
stripePaymentMethodId: "pm_1ABC1234",
|
|
791
|
-
createdAt: "2026-04-20T15:00:00Z"
|
|
792
|
-
}
|
|
793
|
-
]
|
|
794
|
-
});
|
|
795
|
-
mockBilling.purchaseCredits.mockResolvedValue({
|
|
796
|
-
checkoutSessionId: "cs_test_abc123",
|
|
797
|
-
checkoutUrl: "https://checkout.stripe.com/test",
|
|
798
|
-
amountCents: "2500"
|
|
799
|
-
});
|
|
800
|
-
confirm2.mockResolvedValue(false);
|
|
801
|
-
const cmd = createCommand({ amount: "25", method: "card" });
|
|
802
|
-
const promise = cmd.run();
|
|
803
|
-
await vi.advanceTimersByTimeAsync(2e5);
|
|
804
|
-
await promise;
|
|
805
|
-
const fullOutput = logOutput.join("\n");
|
|
806
|
-
expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(2500, void 0);
|
|
807
|
-
expect(openMock).toHaveBeenCalledWith("https://checkout.stripe.com/test");
|
|
808
|
-
expect(fullOutput).toContain("https://checkout.stripe.com/test");
|
|
809
|
-
});
|
|
810
|
-
it("credit card: opens checkout when no card on file", async () => {
|
|
811
|
-
const openMock = (await import("open")).default;
|
|
812
|
-
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
|
|
813
|
-
mockBilling.getPaymentMethods.mockResolvedValue({ paymentMethods: [] });
|
|
814
|
-
mockBilling.purchaseCredits.mockResolvedValue({
|
|
815
|
-
checkoutSessionId: "cs_test_abc123",
|
|
816
|
-
checkoutUrl: "https://checkout.stripe.com/test",
|
|
817
|
-
amountCents: "5000"
|
|
818
|
-
});
|
|
819
|
-
const cmd = createCommand({ amount: "50", method: "card" });
|
|
820
|
-
const promise = cmd.run();
|
|
821
|
-
await vi.advanceTimersByTimeAsync(2e5);
|
|
822
|
-
await promise;
|
|
823
|
-
const fullOutput = logOutput.join("\n");
|
|
824
|
-
expect(confirm2).not.toHaveBeenCalled();
|
|
825
|
-
expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(5e3, void 0);
|
|
826
|
-
expect(openMock).toHaveBeenCalledWith("https://checkout.stripe.com/test");
|
|
827
|
-
expect(fullOutput).toContain("https://checkout.stripe.com/test");
|
|
828
|
-
});
|
|
829
|
-
it("credit card: rejects amount below $5 minimum", async () => {
|
|
830
|
-
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
|
|
831
|
-
const cmd = createCommand({ amount: "3", method: "card" });
|
|
832
|
-
await expect(cmd.run()).rejects.toThrow("Minimum purchase is $5");
|
|
833
|
-
});
|
|
834
|
-
it("credit card: --method and --amount flags skip prompts", async () => {
|
|
835
|
-
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
836
|
-
mockBilling.getPaymentMethods.mockResolvedValue({ paymentMethods: [] });
|
|
837
|
-
mockBilling.purchaseCredits.mockResolvedValue({
|
|
838
|
-
checkoutSessionId: "cs_test_abc123",
|
|
839
|
-
checkoutUrl: "https://checkout.stripe.com/test",
|
|
840
|
-
amountCents: "5000"
|
|
841
|
-
});
|
|
842
|
-
const cmd = createCommand({ amount: "50", method: "card" });
|
|
843
|
-
const promise = cmd.run();
|
|
844
|
-
for (let i = 0; i < 10; i++) {
|
|
845
|
-
await vi.advanceTimersByTimeAsync(5e3);
|
|
846
|
-
}
|
|
847
|
-
await promise;
|
|
848
|
-
expect(select3).not.toHaveBeenCalled();
|
|
849
|
-
expect(input3).not.toHaveBeenCalled();
|
|
850
|
-
});
|
|
851
683
|
});
|
|
852
684
|
//# sourceMappingURL=top-up.test.js.map
|