Skip to content

Commit 74fbec0

Browse files
committed
feat: update billing commands for per-environment subscriptions
- Refactored subscription management to handle subscriptions per environment instead of globally - Updated status command to show subscription details and app usage for current environment only - Modified cancel command to suspend apps only on current environment when canceling - Added improved handling of different subscription states (past_due, unpaid, etc.) with portal links - Enhanced status display formatting with clearer subscription
1 parent c1cc182 commit 74fbec0

File tree

7 files changed

+143
-241
lines changed

7 files changed

+143
-241
lines changed

pkg/commands/billing/cancel.go

Lines changed: 31 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ var CancelCommand = &cli.Command{
1717
Action: func(cCtx *cli.Context) error {
1818
ctx := cCtx.Context
1919
logger := common.LoggerFromContext(cCtx)
20+
environmentConfig, err := utils.GetEnvironmentConfig(cCtx)
21+
if err != nil {
22+
return fmt.Errorf("failed to get environment config: %w", err)
23+
}
24+
envName := environmentConfig.Name
2025

2126
// Get API client
22-
apiClient, err := utils.NewBillingApiClient(cCtx)
27+
apiClient, err := utils.NewUserApiClient(cCtx)
2328
if err != nil {
2429
return fmt.Errorf("failed to create API client: %w", err)
2530
}
@@ -30,45 +35,32 @@ var CancelCommand = &cli.Command{
3035
return fmt.Errorf("failed to check subscription status: %w", err)
3136
}
3237

33-
if subscription.Status != "active" {
34-
logger.Info("You don't have an active subscription.")
38+
if !isSubscriptionActive(subscription.Status) {
39+
logger.Info("You don't have an active subscription on %s.", envName)
3540
return nil
3641
}
3742

38-
// Get developer address
39-
developerAddr, err := utils.GetDeveloperAddress(cCtx)
43+
// Get contract caller for current environment
44+
caller, err := utils.GetContractCaller(cCtx)
4045
if err != nil {
41-
return fmt.Errorf("failed to get developer address: %w", err)
46+
return fmt.Errorf("failed to get contract caller: %w", err)
4247
}
4348

44-
// Check active apps across all networks
45-
// Get private key for contract calls
46-
privateKey, err := utils.GetPrivateKeyOrFail(cCtx)
49+
// Get developer address
50+
developerAddr, err := utils.GetDeveloperAddress(cCtx)
4751
if err != nil {
48-
return fmt.Errorf("failed to get private key: %w", err)
52+
return fmt.Errorf("failed to get developer address: %w", err)
4953
}
5054

51-
// Query all networks for active app counts
52-
results, err := GetActiveAppCountsForAllNetworks(ctx, cCtx, developerAddr, privateKey, true)
55+
// Check active apps on current environment
56+
activeAppCount, err := caller.GetActiveAppCount(ctx, developerAddr)
5357
if err != nil {
54-
return fmt.Errorf("failed to query networks: %w", err)
55-
}
56-
57-
// Calculate total active apps
58-
totalActiveApps := uint32(0)
59-
for _, info := range results {
60-
totalActiveApps += info.Count
58+
return fmt.Errorf("failed to get active app count: %w", err)
6159
}
6260

63-
// If apps exist, show per-network breakdown and get confirmation
64-
if totalActiveApps > 0 {
65-
logger.Info("You have active apps that will be suspended:")
66-
for env, info := range results {
67-
if info.Count > 0 {
68-
displayName := utils.GetEnvironmentDescription(env, env, false)
69-
logger.Info(" • %s: %d app(s)", displayName, info.Count)
70-
}
71-
}
61+
// If apps exist, show warning and get confirmation
62+
if activeAppCount > 0 {
63+
logger.Info("You have %d active app(s) on %s that will be suspended.", activeAppCount, envName)
7264
logger.Info("")
7365

7466
confirmed, err := output.Confirm("Continue?")
@@ -81,37 +73,26 @@ var CancelCommand = &cli.Command{
8173
return nil
8274
}
8375

84-
// Suspend apps on each network that has active apps
85-
for env, info := range results {
86-
if info.Count == 0 {
87-
continue
88-
}
89-
90-
caller := info.Caller
91-
logger.Info("Suspending apps on %s...", env)
92-
93-
// Get only active apps for this developer on this network
94-
activeApps, err := getActiveAppsByCreator(ctx, caller, developerAddr)
95-
if err != nil {
96-
return fmt.Errorf("failed to get active apps for %s: %w", env, err)
97-
}
76+
// Get only active apps for this developer
77+
activeApps, err := getActiveAppsByCreator(ctx, caller, developerAddr)
78+
if err != nil {
79+
return fmt.Errorf("failed to get active apps: %w", err)
80+
}
9881

99-
if len(activeApps) == 0 {
100-
logger.Info("No active apps to suspend on %s", env)
101-
continue
102-
}
82+
if len(activeApps) > 0 {
83+
logger.Info("Suspending apps...")
10384

10485
// Suspend only active apps
10586
err = caller.Suspend(ctx, developerAddr, activeApps)
10687
if err != nil {
107-
return fmt.Errorf("failed to suspend apps on %s: %w", env, err)
88+
return fmt.Errorf("failed to suspend apps: %w", err)
10889
}
10990

110-
logger.Info("✓ Apps suspended on %s", env)
91+
logger.Info("✓ Apps suspended")
11192
}
11293
} else {
11394
// No active apps, just confirm cancellation
114-
logger.Warn("Canceling your subscription will prevent you from deploying new apps.")
95+
logger.Warn("Canceling your subscription on %s will prevent you from deploying new apps.", envName)
11596
confirmed, err := output.Confirm("Are you sure you want to cancel your subscription?")
11697
if err != nil {
11798
return fmt.Errorf("failed to get confirmation: %w", err)
@@ -129,7 +110,7 @@ var CancelCommand = &cli.Command{
129110
return fmt.Errorf("failed to cancel subscription: %w", err)
130111
}
131112

132-
logger.Info("\n✓ Subscription canceled successfully.")
113+
logger.Info("\n✓ Subscription canceled successfully for %s.", envName)
133114
return nil
134115
},
135116
}

pkg/commands/billing/status.go

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@ var StatusCommand = &cli.Command{
1414
Usage: "Show subscription status and usage",
1515
Action: func(cCtx *cli.Context) error {
1616
logger := common.LoggerFromContext(cCtx)
17+
environmentConfig, err := utils.GetEnvironmentConfig(cCtx)
18+
if err != nil {
19+
return fmt.Errorf("failed to get environment config: %w", err)
20+
}
21+
envName := environmentConfig.Name
1722

1823
// Get API client
19-
apiClient, err := utils.NewBillingApiClient(cCtx)
24+
apiClient, err := utils.NewUserApiClient(cCtx)
2025
if err != nil {
2126
return fmt.Errorf("failed to create API client: %w", err)
2227
}
@@ -34,13 +39,8 @@ var StatusCommand = &cli.Command{
3439
statusDisplay := formatStatus(subscription.Status)
3540
logger.Info("Status: %s", statusDisplay)
3641

37-
if subscription.Status == "inactive" {
38-
logger.Info("\nYou don't have an active subscription.")
39-
logger.Info("Run 'eigenx billing subscribe' to get started.")
40-
return nil
41-
}
42-
43-
if subscription.Status == "canceled" {
42+
// Show historical details if canceled
43+
if subscription.Status == utils.StatusCanceled {
4444
logger.Info("\nYour subscription has been canceled.")
4545
if subscription.CurrentPeriodEnd != nil && *subscription.CurrentPeriodEnd > 0 {
4646
endDate := time.Unix(*subscription.CurrentPeriodEnd, 0)
@@ -57,46 +57,54 @@ var StatusCommand = &cli.Command{
5757
return nil
5858
}
5959

60+
// Handle payment issues (past_due, unpaid) - show portal to fix
61+
if subscription.Status == utils.StatusPastDue || subscription.Status == utils.StatusUnpaid {
62+
logger.Warn("\nYour subscription has a payment issue.")
63+
logger.Info("Please update your payment method to restore access.")
64+
65+
if subscription.PortalURL != nil && *subscription.PortalURL != "" {
66+
logger.Info("\nUpdate payment method:")
67+
logger.Info(" %s", *subscription.PortalURL)
68+
}
69+
70+
return nil
71+
}
72+
73+
// Handle all other inactive statuses (incomplete, expired, paused, inactive)
74+
if !isSubscriptionActive(subscription.Status) {
75+
logger.Info("\nYou don't have an active subscription on %s.", envName)
76+
logger.Info("Run 'eigenx billing subscribe' to get started.")
77+
return nil
78+
}
79+
6080
// Plan details
6181
if subscription.PlanPrice != nil && subscription.Currency != nil && *subscription.PlanPrice > 0 {
6282
logger.Info("Plan: $%.2f/%s", *subscription.PlanPrice, *subscription.Currency)
6383
} else {
6484
logger.Info("Plan: Standard")
6585
}
66-
logger.Info(" 1 app per network")
86+
logger.Info(" - 1 app deployed on %s", envName)
6787
logger.Info("")
6888

69-
// Network-specific usage
70-
logger.Info("Network capacity:")
89+
// Current environment usage
90+
logger.Info("Usage:")
7191

72-
// Get developer address
73-
developerAddr, err := utils.GetDeveloperAddress(cCtx)
92+
// Get contract caller for current environment
93+
caller, err := utils.GetContractCaller(cCtx)
7494
if err != nil {
95+
logger.Warn(" Unable to fetch usage: %v", err)
96+
logger.Info("")
97+
} else if developerAddr, err := utils.GetDeveloperAddress(cCtx); err != nil {
7598
logger.Warn(" Unable to fetch usage: failed to get developer address")
99+
logger.Info("")
100+
} else if count, err := caller.GetActiveAppCount(cCtx.Context, developerAddr); err != nil {
101+
logger.Warn(" Unable to fetch usage: %v", err)
102+
logger.Info("")
76103
} else {
77-
// Get private key for contract calls
78-
privateKey, err := utils.GetPrivateKeyOrFail(cCtx)
79-
if err != nil {
80-
logger.Warn(" Unable to fetch usage: failed to get private key")
81-
} else {
82-
ctx := cCtx.Context
83-
84-
// Query all networks for active app counts
85-
results, err := GetActiveAppCountsForAllNetworks(ctx, cCtx, developerAddr, privateKey, false)
86-
if err != nil {
87-
logger.Warn(" Unable to fetch usage: %v", err)
88-
} else {
89-
// Display results for each network
90-
for env, info := range results {
91-
displayName := utils.GetEnvironmentDescription(env, env, false)
92-
logger.Info(" %s: %d / 1 apps deployed", displayName, info.Count)
93-
}
94-
}
95-
}
104+
logger.Info(" %d / 1 apps deployed on %s", count, envName)
105+
logger.Info("")
96106
}
97107

98-
logger.Info("")
99-
100108
// Billing information
101109
logger.Info("Billing:")
102110

@@ -131,17 +139,32 @@ var StatusCommand = &cli.Command{
131139
},
132140
}
133141

134-
func formatStatus(status string) string {
142+
// isSubscriptionActive returns true if the subscription status allows deploying apps
143+
func isSubscriptionActive(status utils.SubscriptionStatus) bool {
144+
return status == utils.StatusActive || status == utils.StatusTrialing
145+
}
146+
147+
func formatStatus(status utils.SubscriptionStatus) string {
135148
switch status {
136-
case "active":
149+
case utils.StatusActive:
137150
return "✓ Active"
138-
case "past_due":
151+
case utils.StatusTrialing:
152+
return "✓ Trial"
153+
case utils.StatusPastDue:
139154
return "⚠ Past Due"
140-
case "canceled":
155+
case utils.StatusCanceled:
141156
return "✗ Canceled"
142-
case "inactive":
157+
case utils.StatusInactive:
143158
return "✗ Inactive"
159+
case utils.StatusIncomplete:
160+
return "⚠ Incomplete"
161+
case utils.StatusIncompleteExpired:
162+
return "✗ Expired"
163+
case utils.StatusUnpaid:
164+
return "⚠ Unpaid"
165+
case utils.StatusPaused:
166+
return "⚠ Paused"
144167
default:
145-
return status
168+
return string(status)
146169
}
147170
}

pkg/commands/billing/subscribe.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ var SubscribeCommand = &cli.Command{
1515
Usage: "Subscribe to start deploying apps",
1616
Action: func(cCtx *cli.Context) error {
1717
logger := common.LoggerFromContext(cCtx)
18+
environmentConfig, err := utils.GetEnvironmentConfig(cCtx)
19+
if err != nil {
20+
return fmt.Errorf("failed to get environment config: %w", err)
21+
}
22+
envName := environmentConfig.Name
1823

1924
// Check if already subscribed
20-
client, err := utils.NewBillingApiClient(cCtx)
25+
client, err := utils.NewUserApiClient(cCtx)
2126
if err != nil {
2227
return fmt.Errorf("failed to create API client: %w", err)
2328
}
@@ -27,13 +32,26 @@ var SubscribeCommand = &cli.Command{
2732
return fmt.Errorf("failed to check subscription status: %w", err)
2833
}
2934

30-
if subscription.Status == "active" {
31-
logger.Info("You're already subscribed. Run 'eigenx billing status' for details.")
35+
if isSubscriptionActive(subscription.Status) {
36+
logger.Info("You're already subscribed to %s. Run 'eigenx billing status' for details.", envName)
37+
return nil
38+
}
39+
40+
// Handle payment issues - direct to portal instead of creating new subscription
41+
if subscription.Status == utils.StatusPastDue || subscription.Status == utils.StatusUnpaid {
42+
logger.Info("You already have a subscription on %s, but it has a payment issue.", envName)
43+
logger.Info("Please update your payment method to restore access.")
44+
45+
if subscription.PortalURL != nil && *subscription.PortalURL != "" {
46+
logger.Info("\nUpdate payment method:")
47+
logger.Info(" %s", *subscription.PortalURL)
48+
}
49+
3250
return nil
3351
}
3452

3553
// Create checkout session
36-
logger.Info("Creating checkout session...")
54+
logger.Info("Creating checkout session for %s...", envName)
3755
session, err := client.CreateCheckoutSession(cCtx)
3856
if err != nil {
3957
return fmt.Errorf("failed to create checkout session: %w", err)
@@ -64,11 +82,9 @@ var SubscribeCommand = &cli.Command{
6482
continue
6583
}
6684

67-
if subscription.Status == "active" {
68-
logger.Info("\n✓ Subscription activated successfully!")
69-
logger.Info("\nYou now have access to:")
70-
logger.Info(" • 1 app on testnet (sepolia)")
71-
logger.Info(" • 1 app on mainnet (mainnet-alpha)")
85+
if isSubscriptionActive(subscription.Status) {
86+
logger.Info("\n✓ Subscription activated successfully for %s!", envName)
87+
logger.Info("\nYou now have access to deploy 1 app on %s", envName)
7288
logger.Info("\nStart deploying with: eigenx app deploy")
7389
return nil
7490
}

0 commit comments

Comments
 (0)