satellite/payments: More Cleanup and Satellite command to ensure we have stripe customers (#3805)
This commit is contained in:
parent
3d6518081a
commit
52590197c2
@ -120,7 +120,6 @@ var (
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: cmdGracefulExit,
|
||||
}
|
||||
|
||||
verifyGracefulExitReceiptCmd = &cobra.Command{
|
||||
Use: "verify-exit-receipt [storage node ID] [receipt]",
|
||||
Short: "Verify a graceful exit receipt",
|
||||
@ -128,6 +127,12 @@ var (
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: cmdVerifyGracefulExitReceipt,
|
||||
}
|
||||
stripeCustomerCmd = &cobra.Command{
|
||||
Use: "ensure-stripe-customer",
|
||||
Short: "Ensures that we have a stripe customer for every user",
|
||||
Long: "Ensures that we have a stripe customer for every satellite user",
|
||||
RunE: cmdStripeCustomer,
|
||||
}
|
||||
|
||||
runCfg Satellite
|
||||
setupCfg Satellite
|
||||
@ -173,6 +178,7 @@ func init() {
|
||||
reportsCmd.AddCommand(partnerAttributionCmd)
|
||||
reportsCmd.AddCommand(gracefulExitCmd)
|
||||
reportsCmd.AddCommand(verifyGracefulExitReceiptCmd)
|
||||
reportsCmd.AddCommand(stripeCustomerCmd)
|
||||
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))
|
||||
@ -183,6 +189,7 @@ func init() {
|
||||
process.Bind(nodeUsageCmd, &nodeUsageCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(gracefulExitCmd, &gracefulExitCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(verifyGracefulExitReceiptCmd, &verifyGracefulExitReceiptCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(stripeCustomerCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(partnerAttributionCmd, &partnerAttribtionCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
}
|
||||
|
||||
@ -415,6 +422,12 @@ func cmdNodeUsage(cmd *cobra.Command, args []string) (err error) {
|
||||
return generateNodeUsageCSV(ctx, start, end, file)
|
||||
}
|
||||
|
||||
func cmdStripeCustomer(cmd *cobra.Command, args []string) (err error) {
|
||||
ctx, _ := process.Ctx(cmd)
|
||||
|
||||
return generateStripeCustomers(ctx)
|
||||
}
|
||||
|
||||
func cmdValueAttribution(cmd *cobra.Command, args []string) (err error) {
|
||||
ctx, _ := process.Ctx(cmd)
|
||||
log := zap.L().Named("satellite-cli")
|
||||
|
109
cmd/satellite/stripe.go
Normal file
109
cmd/satellite/stripe.go
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/storj/private/dbutil"
|
||||
"storj.io/storj/satellite"
|
||||
"storj.io/storj/satellite/payments"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
"storj.io/storj/satellite/satellitedb"
|
||||
"storj.io/storj/satellite/satellitedb/dbx"
|
||||
)
|
||||
|
||||
// UserData contains the uuid and email of a satellite user.
|
||||
type UserData struct {
|
||||
ID uuid.UUID
|
||||
Email string
|
||||
}
|
||||
|
||||
// generateStripeCustomers creates missing stripe-customers for users in our database.
|
||||
func generateStripeCustomers(ctx context.Context) (err error) {
|
||||
//Open SatelliteDB for the Payment Service
|
||||
db, err := satellitedb.New(zap.L().Named("db"), runCfg.Database, satellitedb.Options{})
|
||||
if err != nil {
|
||||
return errs.New("error connecting to master database on satellite: %+v", err)
|
||||
}
|
||||
defer func() {
|
||||
err = errs.Combine(err, db.Close())
|
||||
}()
|
||||
|
||||
//Open direct DB connection to execute custom queries
|
||||
driver, source, implementation, err := dbutil.SplitConnStr(runCfg.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if implementation != dbutil.Postgres && implementation != dbutil.Cockroach {
|
||||
return errs.New("unsupported driver %q", driver)
|
||||
}
|
||||
|
||||
dbxDB, err := dbx.Open(driver, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("Connected to:", zap.String("db source", source))
|
||||
defer func() {
|
||||
err = errs.Combine(err, dbxDB.Close())
|
||||
}()
|
||||
|
||||
handler, err := setupPayments(zap.L().Named("payments"), db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := dbxDB.Query(ctx, "SELECT id, email FROM users WHERE id NOT IN (SELECT user_id from stripe_customers)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err = errs.Combine(err, rows.Close())
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
var user UserData
|
||||
err := rows.Scan(&user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.Setup(ctx, user.ID, user.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func setupPayments(log *zap.Logger, db satellite.DB) (handler payments.Accounts, err error) {
|
||||
pc := runCfg.Payments
|
||||
service, err := stripecoinpayments.NewService(
|
||||
log.Named("payments.stripe:service"),
|
||||
pc.StripeCoinPayments,
|
||||
db.StripeCoinPayments(),
|
||||
db.Console().Projects(),
|
||||
db.ProjectAccounting(),
|
||||
pc.StorageTBPrice,
|
||||
pc.EgressTBPrice,
|
||||
pc.ObjectPrice,
|
||||
pc.BonusRate,
|
||||
pc.CouponValue,
|
||||
pc.CouponDuration,
|
||||
pc.CouponProjectLimit,
|
||||
pc.MinCoinPayment)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler = service.Accounts()
|
||||
|
||||
return handler, err
|
||||
}
|
3
go.sum
3
go.sum
@ -35,7 +35,9 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
|
||||
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
|
||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alessio/shellescape v0.0.0-20190409004728-b115ca0f9053 h1:H/GMMKYPkEIC3DF/JWQz8Pdd+Feifov2EIgGfNpeogI=
|
||||
github.com/alessio/shellescape v0.0.0-20190409004728-b115ca0f9053/go.mod h1:xW8sBma2LE3QxFSzCnH9qe6gAE2yO9GvQaWwX89HxbE=
|
||||
@ -583,6 +585,7 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
|
@ -529,7 +529,11 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
pc.StorageTBPrice,
|
||||
pc.EgressTBPrice,
|
||||
pc.ObjectPrice,
|
||||
pc.BonusRate)
|
||||
pc.BonusRate,
|
||||
pc.CouponValue,
|
||||
pc.CouponDuration,
|
||||
pc.CouponProjectLimit,
|
||||
pc.MinCoinPayment)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
@ -583,6 +587,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
peer.Marketing.PartnersService,
|
||||
peer.Payments.Accounts,
|
||||
consoleConfig.Config,
|
||||
config.Payments.MinCoinPayment,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
"storj.io/storj/satellite/console/consoleauth"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
"storj.io/storj/satellite/payments/mockpayments"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
||||
"storj.io/storj/storage/redis/redisserver"
|
||||
@ -57,16 +57,6 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
payments, err := stripecoinpayments.NewService(
|
||||
log.Named("payments"),
|
||||
stripecoinpayments.Config{},
|
||||
db.StripeCoinPayments(),
|
||||
db.Console().Projects(),
|
||||
db.ProjectAccounting(),
|
||||
"0", "0", "0", 10,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
redis, err := redisserver.Mini()
|
||||
require.NoError(t, err)
|
||||
defer ctx.Check(redis.Close)
|
||||
@ -84,8 +74,9 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
projectUsage,
|
||||
db.Rewards(),
|
||||
partnersService,
|
||||
payments.Accounts(),
|
||||
mockpayments.Accounts(),
|
||||
console.Config{PasswordCost: console.TestPasswordCost},
|
||||
5000,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"storj.io/storj/satellite/console/consoleauth"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
"storj.io/storj/satellite/payments/mockpayments"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
||||
"storj.io/storj/storage/redis/redisserver"
|
||||
@ -42,16 +42,6 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
payments, err := stripecoinpayments.NewService(
|
||||
log.Named("payments"),
|
||||
stripecoinpayments.Config{},
|
||||
db.StripeCoinPayments(),
|
||||
db.Console().Projects(),
|
||||
db.ProjectAccounting(),
|
||||
"0", "0", "0", 10,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
redis, err := redisserver.Mini()
|
||||
require.NoError(t, err)
|
||||
defer ctx.Check(redis.Close)
|
||||
@ -69,8 +59,9 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
projectUsage,
|
||||
db.Rewards(),
|
||||
partnersService,
|
||||
payments.Accounts(),
|
||||
mockpayments.Accounts(),
|
||||
console.Config{PasswordCost: console.TestPasswordCost},
|
||||
5000,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -87,6 +87,8 @@ type Service struct {
|
||||
accounts payments.Accounts
|
||||
|
||||
config Config
|
||||
|
||||
minCoinPayment int64
|
||||
}
|
||||
|
||||
// Config keeps track of core console service configuration parameters
|
||||
@ -101,7 +103,7 @@ type PaymentsService struct {
|
||||
}
|
||||
|
||||
// NewService returns new instance of Service.
|
||||
func NewService(log *zap.Logger, signer Signer, store DB, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, rewards rewards.DB, partners *rewards.PartnersService, accounts payments.Accounts, config Config) (*Service, error) {
|
||||
func NewService(log *zap.Logger, signer Signer, store DB, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, rewards rewards.DB, partners *rewards.PartnersService, accounts payments.Accounts, config Config, minCoinPayment int64) (*Service, error) {
|
||||
if signer == nil {
|
||||
return nil, errs.New("signer can't be nil")
|
||||
}
|
||||
@ -125,6 +127,7 @@ func NewService(log *zap.Logger, signer Signer, store DB, projectAccounting acco
|
||||
partners: partners,
|
||||
accounts: accounts,
|
||||
config: config,
|
||||
minCoinPayment: minCoinPayment,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -369,7 +372,7 @@ func (paymentService PaymentsService) AddPromotionalCoupon(ctx context.Context,
|
||||
return errs.New("user don't have a payment method")
|
||||
}
|
||||
|
||||
return paymentService.service.accounts.Coupons().AddPromotionalCoupon(ctx, userID, duration, amount, limit)
|
||||
return paymentService.service.accounts.Coupons().AddPromotionalCoupon(ctx, userID)
|
||||
}
|
||||
|
||||
// checkRegistrationSecret returns a RegistrationToken if applicable (nil if not), and an error
|
||||
@ -864,6 +867,24 @@ func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p
|
||||
return nil, ErrProjLimit.Wrap(err)
|
||||
}
|
||||
|
||||
cards, err := s.accounts.CreditCards().List(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
s.log.Debug(fmt.Sprintf("could not add promotional coupon for user %s", auth.User.ID.String()), zap.Error(Error.Wrap(err)))
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
balance, err := s.accounts.Balance(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
s.log.Debug(fmt.Sprintf("could not add promotional coupon for user %s", auth.User.ID.String()), zap.Error(Error.Wrap(err)))
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
if len(cards) == 0 && balance < s.minCoinPayment {
|
||||
err = errs.New("no valid payment methods found")
|
||||
s.log.Debug(fmt.Sprintf("could not add promotional coupon for user %s", auth.User.ID.String()), zap.Error(Error.Wrap(err)))
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error {
|
||||
p, err = tx.Projects().Insert(ctx,
|
||||
&Project{
|
||||
@ -884,21 +905,11 @@ func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
cards, err := s.accounts.CreditCards().List(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
s.log.Debug(fmt.Sprintf("could not add promotional coupon for user %s", auth.User.ID.String()), zap.Error(Error.Wrap(err)))
|
||||
return p, nil
|
||||
}
|
||||
if len(cards) == 0 {
|
||||
s.log.Debug(fmt.Sprintf("could not add promotional coupon for user %s - no payment methods", auth.User.ID.String()), zap.Error(Error.Wrap(err)))
|
||||
return p, nil
|
||||
}
|
||||
err = s.accounts.Coupons().AddPromotionalCoupon(ctx, auth.User.ID, 2, 5500, memory.TB)
|
||||
err = s.accounts.Coupons().AddPromotionalCoupon(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
s.log.Debug(fmt.Sprintf("could not add promotional coupon for user %s", auth.User.ID.String()), zap.Error(Error.Wrap(err)))
|
||||
}
|
||||
|
@ -432,7 +432,11 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
pc.StorageTBPrice,
|
||||
pc.EgressTBPrice,
|
||||
pc.ObjectPrice,
|
||||
pc.BonusRate)
|
||||
pc.BonusRate,
|
||||
pc.CouponValue,
|
||||
pc.CouponDuration,
|
||||
pc.CouponProjectLimit,
|
||||
pc.MinCoinPayment)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
|
@ -25,7 +25,7 @@ type Coupons interface {
|
||||
// AddPromotionalCoupon is used to add a promotional coupon for specified users who already have
|
||||
// a project and do not have a promotional coupon yet.
|
||||
// And updates project limits to selected size.
|
||||
AddPromotionalCoupon(ctx context.Context, userID uuid.UUID, duration int, amount int64, projectLimit memory.Size) error
|
||||
AddPromotionalCoupon(ctx context.Context, userID uuid.UUID) error
|
||||
|
||||
// PopulatePromotionalCoupons is used to populate promotional coupons through all active users who already have
|
||||
// a project, payment method and do not have a promotional coupon yet.
|
||||
|
@ -128,7 +128,14 @@ func (accounts accounts) Charges(ctx context.Context, userID uuid.UUID) (_ []pay
|
||||
func (creditCards *creditCards) List(ctx context.Context, userID uuid.UUID) (_ []payments.CreditCard, err error) {
|
||||
defer mon.Task()(&ctx, userID)(&err)
|
||||
|
||||
return []payments.CreditCard{}, nil
|
||||
return []payments.CreditCard{{
|
||||
ID: "pm_card_mastercard",
|
||||
ExpMonth: 12,
|
||||
ExpYear: 2050,
|
||||
Brand: "Mastercard",
|
||||
Last4: "4444",
|
||||
IsDefault: true,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// Add is used to save new credit card, attach it to payment account and make it default.
|
||||
@ -198,8 +205,8 @@ func (coupons *coupons) PopulatePromotionalCoupons(ctx context.Context, duration
|
||||
// AddPromotionalCoupon is used to add a promotional coupon for specified users who already have
|
||||
// a project and do not have a promotional coupon yet.
|
||||
// And updates project limits to selected size.
|
||||
func (coupons *coupons) AddPromotionalCoupon(ctx context.Context, userID uuid.UUID, duration int, amount int64, projectLimit memory.Size) (err error) {
|
||||
defer mon.Task()(&ctx, userID, duration, amount, projectLimit)(&err)
|
||||
func (coupons *coupons) AddPromotionalCoupon(ctx context.Context, userID uuid.UUID) (err error) {
|
||||
defer mon.Task()(&ctx, userID)(&err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
package paymentsconfig
|
||||
|
||||
import (
|
||||
"storj.io/common/memory"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
)
|
||||
|
||||
@ -11,8 +12,12 @@ import (
|
||||
type Config struct {
|
||||
Provider string `help:"payments provider to use" default:""`
|
||||
StripeCoinPayments stripecoinpayments.Config
|
||||
StorageTBPrice string `help:"price user should pay for storing TB per month" default:"10"`
|
||||
EgressTBPrice string `help:"price user should pay for each TB of egress" default:"45"`
|
||||
ObjectPrice string `help:"price user should pay for each object stored in network per month" default:"0.0000022"`
|
||||
BonusRate int64 `help:"amount of percents that user will earn as bonus credits by depositing in STORJ tokens" default:"10"`
|
||||
StorageTBPrice string `help:"price user should pay for storing TB per month" default:"10"`
|
||||
EgressTBPrice string `help:"price user should pay for each TB of egress" default:"45"`
|
||||
ObjectPrice string `help:"price user should pay for each object stored in network per month" default:"0.0000022"`
|
||||
BonusRate int64 `help:"amount of percents that user will earn as bonus credits by depositing in STORJ tokens" default:"10"`
|
||||
CouponValue int64 `help:"coupon value in cents" default:"5500"`
|
||||
CouponDuration int64 `help:"duration a new coupon is valid in months/billing cycles" default:"2"`
|
||||
CouponProjectLimit memory.Size `help:"project limit to which increase to after applying the coupon, 0 B means not changing it from the default" default:"0 B"`
|
||||
MinCoinPayment int64 `help:"minimum value of coin payments in cents before coupon is applied" default:"5000"`
|
||||
}
|
||||
|
@ -187,8 +187,8 @@ func (coupons *coupons) PopulatePromotionalCoupons(ctx context.Context, duration
|
||||
// AddPromotionalCoupon is used to add a promotional coupon for specified users who already have
|
||||
// a project and do not have a promotional coupon yet.
|
||||
// And updates project limits to selected size.
|
||||
func (coupons *coupons) AddPromotionalCoupon(ctx context.Context, userID uuid.UUID, duration int, amount int64, projectLimit memory.Size) (err error) {
|
||||
defer mon.Task()(&ctx, userID, duration, amount, projectLimit)(&err)
|
||||
func (coupons *coupons) AddPromotionalCoupon(ctx context.Context, userID uuid.UUID) (err error) {
|
||||
defer mon.Task()(&ctx, userID)(&err)
|
||||
|
||||
return Error.Wrap(coupons.service.db.Coupons().PopulatePromotionalCoupons(ctx, []uuid.UUID{userID}, duration, amount, projectLimit))
|
||||
return Error.Wrap(coupons.service.db.Coupons().PopulatePromotionalCoupons(ctx, []uuid.UUID{userID}, int(coupons.service.CouponDuration), coupons.service.CouponValue, coupons.service.CouponProjectLimit))
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ var (
|
||||
mon = monkit.Package()
|
||||
)
|
||||
|
||||
// fetchLimit sets the maximum amount of items before we start paging on requests
|
||||
const fetchLimit = 100
|
||||
|
||||
// Config stores needed information for payment service initialization.
|
||||
type Config struct {
|
||||
StripeSecretKey string `help:"stripe API secret key" default:""`
|
||||
@ -62,6 +65,12 @@ type Service struct {
|
||||
ObjectHourCents decimal.Decimal
|
||||
// BonusRate amount of percents
|
||||
BonusRate int64
|
||||
// Coupon Values
|
||||
CouponValue int64
|
||||
CouponDuration int64
|
||||
CouponProjectLimit memory.Size
|
||||
// Minimum CoinPayment to create a coupon
|
||||
MinCoinPayment int64
|
||||
|
||||
//Stripe Extended Features
|
||||
AutoAdvance bool
|
||||
@ -72,7 +81,7 @@ type Service struct {
|
||||
}
|
||||
|
||||
// NewService creates a Service instance.
|
||||
func NewService(log *zap.Logger, config Config, db DB, projectsDB console.Projects, usageDB accounting.ProjectAccounting, storageTBPrice, egressTBPrice, objectPrice string, bonusRate int64) (*Service, error) {
|
||||
func NewService(log *zap.Logger, config Config, db DB, projectsDB console.Projects, usageDB accounting.ProjectAccounting, storageTBPrice, egressTBPrice, objectPrice string, bonusRate, couponValue, couponDuration int64, couponProjectLimit memory.Size, minCoinPayment int64) (*Service, error) {
|
||||
backendConfig := &stripe.BackendConfig{
|
||||
LeveledLogger: log.Sugar(),
|
||||
}
|
||||
@ -121,17 +130,21 @@ func NewService(log *zap.Logger, config Config, db DB, projectsDB console.Projec
|
||||
egressByteCents := egressTBCents.Div(decimal.New(1000000000000, 0))
|
||||
|
||||
return &Service{
|
||||
log: log,
|
||||
db: db,
|
||||
projectsDB: projectsDB,
|
||||
usageDB: usageDB,
|
||||
stripeClient: stripeClient,
|
||||
coinPayments: coinPaymentsClient,
|
||||
ByteHourCents: byteHourCents,
|
||||
EgressByteCents: egressByteCents,
|
||||
ObjectHourCents: objectHourCents,
|
||||
BonusRate: bonusRate,
|
||||
AutoAdvance: config.AutoAdvance,
|
||||
log: log,
|
||||
db: db,
|
||||
projectsDB: projectsDB,
|
||||
usageDB: usageDB,
|
||||
stripeClient: stripeClient,
|
||||
coinPayments: coinPaymentsClient,
|
||||
ByteHourCents: byteHourCents,
|
||||
EgressByteCents: egressByteCents,
|
||||
ObjectHourCents: objectHourCents,
|
||||
BonusRate: bonusRate,
|
||||
CouponValue: couponValue,
|
||||
CouponDuration: couponDuration,
|
||||
CouponProjectLimit: couponProjectLimit,
|
||||
MinCoinPayment: minCoinPayment,
|
||||
AutoAdvance: config.AutoAdvance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -144,10 +157,9 @@ func (service *Service) Accounts() payments.Accounts {
|
||||
func (service *Service) updateTransactionsLoop(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
const limit = 100
|
||||
before := time.Now()
|
||||
|
||||
txsPage, err := service.db.Transactions().ListPending(ctx, 0, limit, before)
|
||||
txsPage, err := service.db.Transactions().ListPending(ctx, 0, fetchLimit, before)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -161,7 +173,7 @@ func (service *Service) updateTransactionsLoop(ctx context.Context) (err error)
|
||||
return err
|
||||
}
|
||||
|
||||
txsPage, err = service.db.Transactions().ListPending(ctx, txsPage.NextOffset, limit, before)
|
||||
txsPage, err = service.db.Transactions().ListPending(ctx, txsPage.NextOffset, fetchLimit, before)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -218,8 +230,8 @@ func (service *Service) updateTransactions(ctx context.Context, ids TransactionA
|
||||
|
||||
cents := convertToCents(rate, &info.Received)
|
||||
|
||||
if cents >= 5000 {
|
||||
err = service.Accounts().Coupons().AddPromotionalCoupon(ctx, userID, 2, 5500, memory.TB)
|
||||
if cents >= service.MinCoinPayment {
|
||||
err = service.Accounts().Coupons().AddPromotionalCoupon(ctx, userID)
|
||||
if err != nil {
|
||||
service.log.Error(fmt.Sprintf("could not add promotional coupon for user %s", userID.String()), zap.Error(err))
|
||||
continue
|
||||
@ -235,10 +247,9 @@ func (service *Service) updateTransactions(ctx context.Context, ids TransactionA
|
||||
func (service *Service) updateAccountBalanceLoop(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
const limit = 100
|
||||
before := time.Now()
|
||||
|
||||
txsPage, err := service.db.Transactions().ListUnapplied(ctx, 0, limit, before)
|
||||
txsPage, err := service.db.Transactions().ListUnapplied(ctx, 0, fetchLimit, before)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -258,7 +269,7 @@ func (service *Service) updateAccountBalanceLoop(ctx context.Context) (err error
|
||||
return err
|
||||
}
|
||||
|
||||
txsPage, err = service.db.Transactions().ListUnapplied(ctx, txsPage.NextOffset, limit, before)
|
||||
txsPage, err = service.db.Transactions().ListUnapplied(ctx, txsPage.NextOffset, fetchLimit, before)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -364,8 +375,6 @@ func (service *Service) GetRate(ctx context.Context, curr1, curr2 coinpayments.C
|
||||
func (service *Service) PrepareInvoiceProjectRecords(ctx context.Context, period time.Time) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
const limit = 25
|
||||
|
||||
now := time.Now().UTC()
|
||||
utc := period.UTC()
|
||||
|
||||
@ -376,7 +385,7 @@ func (service *Service) PrepareInvoiceProjectRecords(ctx context.Context, period
|
||||
return Error.New("prepare is for past periods only")
|
||||
}
|
||||
|
||||
projsPage, err := service.projectsDB.List(ctx, 0, limit, end)
|
||||
projsPage, err := service.projectsDB.List(ctx, 0, fetchLimit, end)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -390,7 +399,7 @@ func (service *Service) PrepareInvoiceProjectRecords(ctx context.Context, period
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
projsPage, err = service.projectsDB.List(ctx, projsPage.NextOffset, limit, end)
|
||||
projsPage, err = service.projectsDB.List(ctx, projsPage.NextOffset, fetchLimit, end)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -517,10 +526,9 @@ func (service *Service) createProjectRecords(ctx context.Context, projects []con
|
||||
func (service *Service) InvoiceApplyProjectRecords(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
const limit = 25
|
||||
before := time.Now().UTC()
|
||||
|
||||
recordsPage, err := service.db.ProjectRecords().ListUnapplied(ctx, 0, limit, before)
|
||||
recordsPage, err := service.db.ProjectRecords().ListUnapplied(ctx, 0, fetchLimit, before)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -534,7 +542,7 @@ func (service *Service) InvoiceApplyProjectRecords(ctx context.Context) (err err
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
recordsPage, err = service.db.ProjectRecords().ListUnapplied(ctx, recordsPage.NextOffset, limit, before)
|
||||
recordsPage, err = service.db.ProjectRecords().ListUnapplied(ctx, recordsPage.NextOffset, fetchLimit, before)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -623,10 +631,9 @@ func (service *Service) createInvoiceItems(ctx context.Context, cusID, projName
|
||||
func (service *Service) InvoiceApplyCoupons(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
const limit = 25
|
||||
before := time.Now().UTC()
|
||||
|
||||
usagePage, err := service.db.Coupons().ListUnapplied(ctx, 0, limit, before)
|
||||
usagePage, err := service.db.Coupons().ListUnapplied(ctx, 0, fetchLimit, before)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -640,7 +647,7 @@ func (service *Service) InvoiceApplyCoupons(ctx context.Context) (err error) {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
usagePage, err = service.db.Coupons().ListUnapplied(ctx, usagePage.NextOffset, limit, before)
|
||||
usagePage, err = service.db.Coupons().ListUnapplied(ctx, usagePage.NextOffset, fetchLimit, before)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -728,10 +735,9 @@ func (service *Service) createInvoiceCouponItems(ctx context.Context, coupon pay
|
||||
func (service *Service) InvoiceApplyCredits(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
const limit = 25
|
||||
before := time.Now().UTC()
|
||||
|
||||
spendingsPage, err := service.db.Credits().ListCreditsSpendingsPaged(ctx, int(CreditsSpendingStatusUnapplied), 0, limit, before)
|
||||
spendingsPage, err := service.db.Credits().ListCreditsSpendingsPaged(ctx, int(CreditsSpendingStatusUnapplied), 0, fetchLimit, before)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -745,7 +751,7 @@ func (service *Service) InvoiceApplyCredits(ctx context.Context) (err error) {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
spendingsPage, err = service.db.Credits().ListCreditsSpendingsPaged(ctx, int(CreditsSpendingStatusUnapplied), spendingsPage.NextOffset, limit, before)
|
||||
spendingsPage, err = service.db.Credits().ListCreditsSpendingsPaged(ctx, int(CreditsSpendingStatusUnapplied), spendingsPage.NextOffset, fetchLimit, before)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -808,10 +814,9 @@ func (service *Service) createInvoiceCreditItem(ctx context.Context, spending Cr
|
||||
func (service *Service) CreateInvoices(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
const limit = 25
|
||||
before := time.Now()
|
||||
|
||||
cusPage, err := service.db.Customers().List(ctx, 0, limit, before)
|
||||
cusPage, err := service.db.Customers().List(ctx, 0, fetchLimit, before)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -831,7 +836,7 @@ func (service *Service) CreateInvoices(ctx context.Context) (err error) {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
cusPage, err = service.db.Customers().List(ctx, cusPage.NextOffset, limit, before)
|
||||
cusPage, err = service.db.Customers().List(ctx, cusPage.NextOffset, fetchLimit, before)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
@ -385,12 +385,15 @@ func (coupons *coupons) PopulatePromotionalCoupons(ctx context.Context, users []
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = coupons.db.Update_Project_By_Id(ctx,
|
||||
dbx.Project_Id(id.ProjectID[:]),
|
||||
dbx.Project_Update_Fields{
|
||||
UsageLimit: dbx.Project_UsageLimit(projectLimit.Int64()),
|
||||
},
|
||||
)
|
||||
// if projectLimit specified, set it, else omit change the existing value
|
||||
if projectLimit.Int64() > 0 {
|
||||
_, err = coupons.db.Update_Project_By_Id(ctx,
|
||||
dbx.Project_Id(id.ProjectID[:]),
|
||||
dbx.Project_Update_Fields{
|
||||
UsageLimit: dbx.Project_UsageLimit(projectLimit.Int64()),
|
||||
},
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
12
scripts/testdata/satellite-config.yaml.lock
vendored
12
scripts/testdata/satellite-config.yaml.lock
vendored
@ -397,9 +397,21 @@ identity.key-path: /root/.local/share/storj/identity/satellite/identity.key
|
||||
# amount of percents that user will earn as bonus credits by depositing in STORJ tokens
|
||||
# payments.bonus-rate: 10
|
||||
|
||||
# duration a new coupon is valid in months/billing cycles
|
||||
# payments.coupon-duration: 2
|
||||
|
||||
# project limit to which increase to after applying the coupon, 0 B means not changing it from the default
|
||||
# payments.coupon-project-limit: 0 B
|
||||
|
||||
# coupon value in cents
|
||||
# payments.coupon-value: 5500
|
||||
|
||||
# price user should pay for each TB of egress
|
||||
# payments.egress-tb-price: "45"
|
||||
|
||||
# minimum value of coin payments in cents before coupon is applied
|
||||
# payments.min-coin-payment: 5000
|
||||
|
||||
# price user should pay for each object stored in network per month
|
||||
# payments.object-price: "0.0000022"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user