cmd/satellite: Add billing command for converting customers to paid tier
We have implemented the paid tier, but it currently only handles new users entering paid tier. It does not convert users who have already added a credit card previously. We still want to convert these users' project limits. This billing command can be run once to convert all old customers with a credti card. Afterwards, we should be able to safely remove it. Change-Id: Ia496580b8e72ef436375b74f590fe57cca704fa8
This commit is contained in:
parent
8855c0dff7
commit
5870502589
@ -5,6 +5,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/memory"
|
||||
"storj.io/common/storj"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/satellite"
|
||||
@ -137,3 +139,115 @@ func generateStripeCustomers(ctx context.Context) (err error) {
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// checkPaidTier ensures that all customers with a credit card are in the paid tier.
|
||||
func checkPaidTier(ctx context.Context) (err error) {
|
||||
usageLimitsConfig := runCfg.Console.UsageLimits
|
||||
|
||||
fmt.Println("This command will do the following:\nFor every user who has added a credit card and is not already in the paid tier:")
|
||||
fmt.Printf("Move this user to the paid tier and change their current project limits to:\n\tStorage: %s\n\tBandwidth: %s\n", usageLimitsConfig.Storage.Paid.String(), usageLimitsConfig.Bandwidth.Paid.String())
|
||||
fmt.Printf("Do you really want to run this command? (confirm with 'yes') ")
|
||||
|
||||
var confirm string
|
||||
n, err := fmt.Scanln(&confirm)
|
||||
if err != nil {
|
||||
if n != 0 {
|
||||
return err
|
||||
}
|
||||
// fmt.Scanln cannot handle empty input
|
||||
confirm = "n"
|
||||
}
|
||||
|
||||
if strings.ToLower(confirm) != "yes" {
|
||||
fmt.Println("Aborted - no users or projects have been modified")
|
||||
return nil
|
||||
}
|
||||
|
||||
return runBillingCmd(ctx, func(ctx context.Context, payments *stripecoinpayments.Service, db satellite.DB) error {
|
||||
customers := db.StripeCoinPayments().Customers()
|
||||
creditCards := payments.Accounts().CreditCards()
|
||||
users := db.Console().Users()
|
||||
projects := db.Console().Projects()
|
||||
|
||||
usersUpgraded := 0
|
||||
projectsUpgraded := 0
|
||||
failedUsers := make(map[uuid.UUID]bool)
|
||||
morePages := true
|
||||
nextOffset := int64(0)
|
||||
listingLimit := 100
|
||||
end := time.Now()
|
||||
for morePages {
|
||||
if err = ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
customersPage, err := customers.List(ctx, nextOffset, listingLimit, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
morePages = customersPage.Next
|
||||
nextOffset = customersPage.NextOffset
|
||||
|
||||
for _, c := range customersPage.Customers {
|
||||
user, err := users.Get(ctx, c.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.PaidTier {
|
||||
// already in paid tier; go to next customer
|
||||
continue
|
||||
}
|
||||
cards, err := creditCards.List(ctx, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cards) == 0 {
|
||||
// no card added, so no paid tier; go to next customer
|
||||
continue
|
||||
}
|
||||
|
||||
// convert user to paid tier
|
||||
err = users.UpdatePaidTier(ctx, user.ID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usersUpgraded++
|
||||
|
||||
// increase limits of existing projects to paid tier
|
||||
userProjects, err := projects.GetOwn(ctx, user.ID)
|
||||
if err != nil {
|
||||
failedUsers[user.ID] = true
|
||||
fmt.Printf("Error getting user's projects; skipping: %v\n", err)
|
||||
continue
|
||||
}
|
||||
for _, project := range userProjects {
|
||||
if project.StorageLimit == nil || *project.StorageLimit < usageLimitsConfig.Storage.Paid {
|
||||
project.StorageLimit = new(memory.Size)
|
||||
*project.StorageLimit = usageLimitsConfig.Storage.Paid
|
||||
}
|
||||
if project.BandwidthLimit == nil || *project.BandwidthLimit < usageLimitsConfig.Bandwidth.Paid {
|
||||
project.BandwidthLimit = new(memory.Size)
|
||||
*project.BandwidthLimit = usageLimitsConfig.Bandwidth.Paid
|
||||
}
|
||||
err = projects.Update(ctx, &project)
|
||||
if err != nil {
|
||||
failedUsers[user.ID] = true
|
||||
fmt.Printf("Error updating user's project; skipping: %v\n", err)
|
||||
continue
|
||||
}
|
||||
projectsUpgraded++
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("Finished. Upgraded %d users and %d projects.\n", usersUpgraded, projectsUpgraded)
|
||||
|
||||
if len(failedUsers) > 0 {
|
||||
fmt.Println("Failed to upgrade some users' projects to paid tier:")
|
||||
for id := range failedUsers {
|
||||
fmt.Println(id.String())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -225,6 +225,12 @@ var (
|
||||
Long: "Ensures that we have a stripe customer for every satellite user.",
|
||||
RunE: cmdStripeCustomer,
|
||||
}
|
||||
checkPaidTierCmd = &cobra.Command{
|
||||
Use: "check-paid-tier",
|
||||
Short: "Ensures that all customers with a credit card are in the paid tier.",
|
||||
Long: "Ensures that all customers with a credit card are in the paid tier.",
|
||||
RunE: cmdCheckPaidTier,
|
||||
}
|
||||
consistencyCmd = &cobra.Command{
|
||||
Use: "consistency",
|
||||
Short: "Readdress DB consistency issues",
|
||||
@ -319,6 +325,7 @@ func init() {
|
||||
billingCmd.AddCommand(createCustomerInvoicesCmd)
|
||||
billingCmd.AddCommand(finalizeCustomerInvoicesCmd)
|
||||
billingCmd.AddCommand(stripeCustomerCmd)
|
||||
billingCmd.AddCommand(checkPaidTierCmd)
|
||||
consistencyCmd.AddCommand(consistencyGECleanupCmd)
|
||||
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(runMigrationCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
@ -342,6 +349,7 @@ func init() {
|
||||
process.Bind(createCustomerInvoicesCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(finalizeCustomerInvoicesCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(stripeCustomerCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(checkPaidTierCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(consistencyGECleanupCmd, &consistencyGECleanupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
|
||||
if err := consistencyGECleanupCmd.MarkFlagRequired("before"); err != nil {
|
||||
@ -743,6 +751,12 @@ func cmdStripeCustomer(cmd *cobra.Command, args []string) (err error) {
|
||||
return generateStripeCustomers(ctx)
|
||||
}
|
||||
|
||||
func cmdCheckPaidTier(cmd *cobra.Command, args []string) (err error) {
|
||||
ctx, _ := process.Ctx(cmd)
|
||||
|
||||
return checkPaidTier(ctx)
|
||||
}
|
||||
|
||||
func cmdConsistencyGECleanup(cmd *cobra.Command, args []string) error {
|
||||
ctx, _ := process.Ctx(cmd)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user