satellite/payments: remove migare-credits billing command

This was a one-time command and it has been already executed on all
production satellites.

Change-Id: Ic58a151c3e5da9c139df875f6517f37275a1ebf5
This commit is contained in:
Kaloyan Raev 2020-07-07 11:47:20 +03:00
parent fd740295ec
commit 12a15e5a6a
3 changed files with 0 additions and 411 deletions

View File

@ -225,12 +225,6 @@ var (
Long: "Ensures that we have a stripe customer for every satellite user.",
RunE: cmdStripeCustomer,
}
migrateCreditsCmd = &cobra.Command{
Use: "migrate-credits",
Short: "Migrates credits to Stripe",
Long: "Migrates credits received for STORJ token deposits from Satellite DB to Stripe balance.",
RunE: cmdMigrateCredits,
}
runCfg Satellite
setupCfg Satellite
@ -301,7 +295,6 @@ func init() {
billingCmd.AddCommand(createCustomerInvoicesCmd)
billingCmd.AddCommand(finalizeCustomerInvoicesCmd)
billingCmd.AddCommand(stripeCustomerCmd)
billingCmd.AddCommand(migrateCreditsCmd)
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(runMigrationCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(runAPICmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
@ -324,7 +317,6 @@ 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(migrateCreditsCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {
@ -723,14 +715,6 @@ func cmdStripeCustomer(cmd *cobra.Command, args []string) (err error) {
return generateStripeCustomers(ctx)
}
func cmdMigrateCredits(cmd *cobra.Command, args []string) (err error) {
ctx, _ := process.Ctx(cmd)
return runBillingCmd(func(payments *stripecoinpayments.Service, _ *dbx.DB) error {
return payments.MigrateCredits(ctx)
})
}
func main() {
process.ExecCustomDebug(rootCmd)
}

View File

@ -1,245 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package stripecoinpayments
import (
"context"
"encoding/json"
"time"
"github.com/stripe/stripe-go"
"go.uber.org/zap"
"storj.io/common/uuid"
)
type migrationStats struct {
processedCustomers int
customersWithCreditRecords int
customersWithPositiveBalance int
migratedBalanceAmount int64
migratedHistoryAmount int64
migratedCreditRecords int
}
// MigrateCredits migrates credits from Satellite DB to Stripe balance.
func (service *Service) MigrateCredits(ctx context.Context) (err error) {
defer mon.Task()(&ctx)(&err)
now := time.Now().UTC()
stats := &migrationStats{}
cusPage, err := service.db.Customers().List(ctx, 0, service.listingLimit, now)
if err != nil {
return Error.Wrap(err)
}
for _, cus := range cusPage.Customers {
if err = ctx.Err(); err != nil {
return Error.Wrap(err)
}
if err = service.migrateCredits(ctx, cus, stats); err != nil {
return Error.Wrap(err)
}
}
for cusPage.Next {
if err = ctx.Err(); err != nil {
return Error.Wrap(err)
}
cusPage, err = service.db.Customers().List(ctx, cusPage.NextOffset, service.listingLimit, now)
if err != nil {
return Error.Wrap(err)
}
for _, cus := range cusPage.Customers {
if err = ctx.Err(); err != nil {
return Error.Wrap(err)
}
if err = service.migrateCredits(ctx, cus, stats); err != nil {
return Error.Wrap(err)
}
}
}
service.log.Info("Migration complete.",
zap.Int("Processed Customers", stats.processedCustomers),
zap.Int("Customers With Credit Records", stats.customersWithCreditRecords),
zap.Int("Customers With Positive Credit Balance", stats.customersWithPositiveBalance),
zap.Int64("Balance Amount in Cents Migrated to Stripe", stats.migratedBalanceAmount),
zap.Int("Credit History Records Migrated to Stripe", stats.migratedCreditRecords),
zap.Int64("Credit History Amount in Cents Migrated to Stripe", stats.migratedHistoryAmount),
)
return nil
}
func (service *Service) migrateCredits(ctx context.Context, customer Customer, stats *migrationStats) error {
stats.processedCustomers++
err := service.migrateCreditsBalance(ctx, customer, stats)
if err != nil {
return Error.Wrap(err)
}
err = service.migrateCreditsHistory(ctx, customer, stats)
return Error.Wrap(err)
}
func (service *Service) migrateCreditsBalance(ctx context.Context, customer Customer, stats *migrationStats) error {
balance, err := service.db.Credits().Balance(ctx, customer.UserID)
if err != nil {
return Error.Wrap(err)
}
if balance <= 0 {
return nil
}
stats.customersWithPositiveBalance++
service.log.Info("Found positive credit balance.",
zap.Int64("Balance", balance),
zap.Stringer("User ID", customer.UserID),
zap.String("Customer ID", customer.ID),
)
// Check for Stripe balance transactions created from previous failed attempt
var txDone bool
it := service.stripeClient.CustomerBalanceTransactions().List(&stripe.CustomerBalanceTransactionListParams{Customer: stripe.String(customer.ID)})
for it.Next() {
cbt := it.CustomerBalanceTransaction()
if cbt.Type != stripe.CustomerBalanceTransactionTypeAdjustment {
continue
}
if cbt.Description != StripeMigratedDepositBonusTransactionDescription {
continue
}
if cbt.Amount != -balance {
return Error.New("amount mismatch in found balance transaction, want: %d, got: %d", -balance, cbt.Amount)
}
service.log.Warn("Found balance transaction in Stripe from previous attempt.",
zap.Int64("Amount", cbt.Amount),
zap.Time("Created At", time.Unix(cbt.Created, 0)),
zap.Stringer("User ID", customer.UserID),
zap.String("Customer ID", customer.ID),
)
txDone = true
}
// Add the unspent credits to Stripe balance
if !txDone {
params := &stripe.CustomerBalanceTransactionParams{
Amount: stripe.Int64(-balance),
Customer: stripe.String(customer.ID),
Currency: stripe.String(string(stripe.CurrencyUSD)),
Description: stripe.String(StripeMigratedDepositBonusTransactionDescription),
}
stats.migratedBalanceAmount += balance
service.log.Info("Crediting Stripe balance.",
zap.Int64("Amount", *params.Amount),
zap.Stringer("User ID", customer.UserID),
zap.String("Customer ID", customer.ID),
)
_, err = service.stripeClient.CustomerBalanceTransactions().New(params)
if err != nil {
return Error.Wrap(err)
}
}
// Clear the credits balance in the satellite DB
creditSpendingID, err := uuid.New()
if err != nil {
return Error.Wrap(err)
}
service.log.Info("Issuing a credit spending to clear balance in satellite DB.",
zap.Int64("Amount", balance),
zap.Stringer("User ID", customer.UserID),
zap.String("Customer ID", customer.ID),
)
err = service.db.Credits().InsertCreditsSpending(ctx, CreditsSpending{
ID: creditSpendingID,
Amount: balance,
UserID: customer.UserID,
Status: CreditsSpendingStatusApplied,
Period: time.Now().UTC(),
})
return Error.Wrap(err)
}
func (service *Service) migrateCreditsHistory(ctx context.Context, customer Customer, stats *migrationStats) error {
credits, err := service.db.Credits().ListCredits(ctx, customer.UserID)
if err != nil {
return Error.Wrap(err)
}
if len(credits) == 0 {
return nil
}
stats.customersWithCreditRecords++
service.log.Info("Found credit records in satellite DB.",
zap.Int("Count", len(credits)),
zap.Stringer("User ID", customer.UserID),
zap.String("Customer ID", customer.ID),
)
stripeCustomer, err := service.stripeClient.Customers().Get(customer.ID, nil)
if err != nil {
return Error.Wrap(err)
}
metadata := stripeCustomer.Metadata
for _, credit := range credits {
metadataKey := "credit_" + credit.TransactionID.String()
_, ok := metadata[metadataKey]
if ok {
// the credit record already exist in metadata
continue
}
b, err := json.Marshal(credit)
if err != nil {
return Error.Wrap(err)
}
metadataValue := string(b)
stats.migratedCreditRecords++
stats.migratedHistoryAmount += credit.Amount
service.log.Info("Adding credit record to the metadata of Stripe customer.",
zap.String("Key", metadataKey),
zap.String("Value", metadataValue),
zap.Stringer("User ID", customer.UserID),
zap.String("Customer ID", customer.ID),
)
if metadata == nil {
metadata = make(map[string]string)
}
metadata[metadataKey] = metadataValue
}
params := stripe.CustomerParams{}
params.Metadata = metadata
_, err = service.stripeClient.Customers().Update(customer.ID, &params)
return Error.Wrap(err)
}

View File

@ -1,150 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package stripecoinpayments_test
import (
"fmt"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stripe/stripe-go"
"storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/storj/private/testplanet"
"storj.io/storj/satellite/payments"
"storj.io/storj/satellite/payments/coinpayments"
"storj.io/storj/satellite/payments/stripecoinpayments"
)
func TestService_MigrateCredits(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
satellite := planet.Satellites[0]
for i, tt := range []struct {
credits []int64
spedings []int64
}{
{ // user with no credits and spendings
},
{ // user with a single credit, but no spendings
credits: []int64{1000},
},
{ // user with a single credit and spending, some remaining balances
credits: []int64{1000},
spedings: []int64{700},
},
{ // user with a single credit and spending, no remaining balances
credits: []int64{1000},
spedings: []int64{1000},
},
{ // user with a single credit and spending, negative balances
credits: []int64{1000},
spedings: []int64{2000},
},
{ // user with a few credits and spendings, some remaining balance
credits: []int64{100, 200, 300},
spedings: []int64{50, 150},
},
{ // user with a few credits and spendings, no remaining balance
credits: []int64{100, 200, 300},
spedings: []int64{50, 150, 400},
},
{ // user with a few credits and spendings, negative remaining balance
credits: []int64{100, 200},
spedings: []int64{50, 150, 200},
},
} {
errTag := fmt.Sprintf("%d. %+v", i, tt)
user, err := satellite.AddUser(ctx, "testuser"+strconv.Itoa(i), "test@test"+strconv.Itoa(i), 1)
require.NoError(t, err, errTag)
project, err := satellite.AddProject(ctx, user.ID, "testproject-"+strconv.Itoa(i))
require.NoError(t, err, errTag)
// Keep track of the balance when adding all credits and spendings to the database
var initialBalance int64
// Add the credits to the database
for i, credit := range tt.credits {
initialBalance += credit
err = satellite.DB.StripeCoinPayments().Credits().InsertCredit(ctx, payments.Credit{
UserID: user.ID,
TransactionID: coinpayments.TransactionID(user.ID.String() + "-" + strconv.Itoa(i)),
Amount: credit,
Created: time.Now().UTC(),
})
require.NoError(t, err, errTag)
}
// Add the credit spendings to the database
for _, spending := range tt.spedings {
initialBalance -= spending
err = satellite.DB.StripeCoinPayments().Credits().InsertCreditsSpending(ctx, stripecoinpayments.CreditsSpending{
ID: testrand.UUID(),
ProjectID: project.ID,
UserID: user.ID,
Amount: spending,
Status: stripecoinpayments.CreditsSpendingStatusApplied,
Period: time.Now().UTC(),
Created: time.Now().UTC(),
})
require.NoError(t, err, errTag)
}
// Check that the initial credits balance is as expected
balance, err := satellite.DB.StripeCoinPayments().Credits().Balance(ctx, user.ID)
require.NoError(t, err, errTag)
require.EqualValues(t, initialBalance, balance, errTag)
// Migrate the credits to Stripe
err = satellite.API.Payments.Service.MigrateCredits(ctx)
require.NoError(t, err, errTag)
// Check that the credits balance in database is now zero.
balance, err = satellite.DB.StripeCoinPayments().Credits().Balance(ctx, user.ID)
require.NoError(t, err, errTag)
if initialBalance > 0 {
require.Zero(t, balance, errTag)
} else {
// If the initial balance was negative (should not be possible in reality) then after migration it remains the same
require.Equal(t, initialBalance, balance, errTag)
}
customerID, err := satellite.DB.StripeCoinPayments().Customers().GetCustomerID(ctx, user.ID)
require.NoError(t, err, errTag)
it := satellite.API.Payments.Stripe.CustomerBalanceTransactions().List(&stripe.CustomerBalanceTransactionListParams{Customer: stripe.String(customerID)})
if initialBalance > 0 {
// Check that we have only one balance transaction in Stripe, which is the one adding the unspent credits balance
require.True(t, it.Next(), errTag)
cbt := it.CustomerBalanceTransaction()
require.Equal(t, stripe.CustomerBalanceTransactionTypeAdjustment, cbt.Type, errTag)
require.Equal(t, stripecoinpayments.StripeMigratedDepositBonusTransactionDescription, cbt.Description, errTag)
require.EqualValues(t, -initialBalance, cbt.Amount, errTag)
}
require.False(t, it.Next(), errTag)
customer, err := satellite.API.Payments.Stripe.Customers().Get(customerID, nil)
require.NoError(t, err, errTag)
if len(tt.credits) == 0 {
require.Nil(t, customer.Metadata, errTag)
} else {
// Check that the Stripe customer metadata contains the history of added credits
require.NotNil(t, customer.Metadata, errTag)
require.Len(t, customer.Metadata, len(tt.credits), errTag)
for i, credit := range tt.credits {
txID := user.ID.String() + "-" + strconv.Itoa(i)
require.Contains(t, customer.Metadata, "credit_"+txID, errTag)
require.Contains(t, customer.Metadata["credit_"+txID], `"credit":`+strconv.FormatInt(credit, 10), errTag)
}
}
}
})
}