diff --git a/cmd/satellite/billing.go b/cmd/satellite/billing.go index 73e3f03bc..1bb4c8bd2 100644 --- a/cmd/satellite/billing.go +++ b/cmd/satellite/billing.go @@ -53,6 +53,16 @@ func setupPayments(log *zap.Logger, db satellite.DB) (*stripecoinpayments.Servic stripeClient = stripecoinpayments.NewStripeClient(log, pc.StripeCoinPayments) } + prices, err := pc.UsagePrice.ToModel() + if err != nil { + return nil, err + } + + priceOverrides, err := pc.UsagePriceOverrides.ToModels() + if err != nil { + return nil, err + } + return stripecoinpayments.NewService( log.Named("payments.stripe:service"), stripeClient, @@ -62,9 +72,8 @@ func setupPayments(log *zap.Logger, db satellite.DB) (*stripecoinpayments.Servic db.Billing(), db.Console().Projects(), db.ProjectAccounting(), - pc.StorageTBPrice, - pc.EgressTBPrice, - pc.SegmentPrice, + prices, + priceOverrides, pc.BonusRate) } diff --git a/satellite/admin.go b/satellite/admin.go index 794c7b237..f5d23cec8 100644 --- a/satellite/admin.go +++ b/satellite/admin.go @@ -145,6 +145,16 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m stripeClient = stripecoinpayments.NewStripeClient(log, pc.StripeCoinPayments) } + prices, err := pc.UsagePrice.ToModel() + if err != nil { + return nil, errs.Combine(err, peer.Close()) + } + + priceOverrides, err := pc.UsagePriceOverrides.ToModels() + if err != nil { + return nil, errs.Combine(err, peer.Close()) + } + peer.Payments.Service, err = stripecoinpayments.NewService( peer.Log.Named("payments.stripe:service"), stripeClient, @@ -154,9 +164,8 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m peer.DB.Billing(), peer.DB.Console().Projects(), peer.DB.ProjectAccounting(), - pc.StorageTBPrice, - pc.EgressTBPrice, - pc.SegmentPrice, + prices, + priceOverrides, pc.BonusRate) if err != nil { diff --git a/satellite/api.go b/satellite/api.go index 334f78d81..f301df5bf 100644 --- a/satellite/api.go +++ b/satellite/api.go @@ -48,7 +48,6 @@ import ( "storj.io/storj/satellite/orders" "storj.io/storj/satellite/overlay" "storj.io/storj/satellite/payments" - "storj.io/storj/satellite/payments/paymentsconfig" "storj.io/storj/satellite/payments/storjscan" "storj.io/storj/satellite/payments/stripecoinpayments" "storj.io/storj/satellite/reputation" @@ -512,6 +511,16 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, stripeClient = stripecoinpayments.NewStripeClient(log, pc.StripeCoinPayments) } + prices, err := pc.UsagePrice.ToModel() + if err != nil { + return nil, errs.Combine(err, peer.Close()) + } + + priceOverrides, err := pc.UsagePriceOverrides.ToModels() + if err != nil { + return nil, errs.Combine(err, peer.Close()) + } + peer.Payments.StripeService, err = stripecoinpayments.NewService( peer.Log.Named("payments.stripe:service"), stripeClient, @@ -521,9 +530,8 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, peer.DB.Billing(), peer.DB.Console().Projects(), peer.DB.ProjectAccounting(), - pc.StorageTBPrice, - pc.EgressTBPrice, - pc.SegmentPrice, + prices, + priceOverrides, pc.BonusRate) if err != nil { @@ -591,12 +599,6 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, return nil, errs.Combine(err, peer.Close()) } - pricing := paymentsconfig.PricingValues{ - StorageTBPrice: config.Payments.StorageTBPrice, - EgressTBPrice: config.Payments.EgressTBPrice, - SegmentPrice: config.Payments.SegmentPrice, - } - peer.Console.Endpoint = consoleweb.NewServer( peer.Log.Named("console:endpoint"), consoleConfig, @@ -608,7 +610,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, peer.ABTesting.Service, peer.Console.Listener, config.Payments.StripeCoinPayments.StripePublicKey, - pricing, + config.Payments.UsagePrice, peer.URL(), ) diff --git a/satellite/console/consoleweb/consoleql/mutation_test.go b/satellite/console/consoleweb/consoleql/mutation_test.go index f3ef2ba43..cfd8550d2 100644 --- a/satellite/console/consoleweb/consoleql/mutation_test.go +++ b/satellite/console/consoleweb/consoleql/mutation_test.go @@ -73,11 +73,19 @@ func TestGraphqlMutation(t *testing.T) { // TODO maybe switch this test to testplanet to avoid defining config and Stripe service pc := paymentsconfig.Config{ - StorageTBPrice: "10", - EgressTBPrice: "45", - SegmentPrice: "0.0000022", + UsagePrice: paymentsconfig.ProjectUsagePrice{ + StorageTB: "10", + EgressTB: "45", + Segment: "0.0000022", + }, } + prices, err := pc.UsagePrice.ToModel() + require.NoError(t, err) + + priceOverrides, err := pc.UsagePriceOverrides.ToModels() + require.NoError(t, err) + paymentsService, err := stripecoinpayments.NewService( log.Named("payments.stripe:service"), stripecoinpayments.NewStripeMock( @@ -91,9 +99,8 @@ func TestGraphqlMutation(t *testing.T) { db.Billing(), db.Console().Projects(), db.ProjectAccounting(), - pc.StorageTBPrice, - pc.EgressTBPrice, - pc.SegmentPrice, + prices, + priceOverrides, pc.BonusRate) require.NoError(t, err) diff --git a/satellite/console/consoleweb/consoleql/query_test.go b/satellite/console/consoleweb/consoleql/query_test.go index e529bd4f7..24ba99e5c 100644 --- a/satellite/console/consoleweb/consoleql/query_test.go +++ b/satellite/console/consoleweb/consoleql/query_test.go @@ -57,11 +57,19 @@ func TestGraphqlQuery(t *testing.T) { // TODO maybe switch this test to testplanet to avoid defining config and Stripe service pc := paymentsconfig.Config{ - StorageTBPrice: "10", - EgressTBPrice: "45", - SegmentPrice: "0.0000022", + UsagePrice: paymentsconfig.ProjectUsagePrice{ + StorageTB: "10", + EgressTB: "45", + Segment: "0.0000022", + }, } + prices, err := pc.UsagePrice.ToModel() + require.NoError(t, err) + + priceOverrides, err := pc.UsagePriceOverrides.ToModels() + require.NoError(t, err) + paymentsService, err := stripecoinpayments.NewService( log.Named("payments.stripe:service"), stripecoinpayments.NewStripeMock( @@ -75,9 +83,8 @@ func TestGraphqlQuery(t *testing.T) { db.Billing(), db.Console().Projects(), db.ProjectAccounting(), - pc.StorageTBPrice, - pc.EgressTBPrice, - pc.SegmentPrice, + prices, + priceOverrides, pc.BonusRate) require.NoError(t, err) diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index bcf03ca05..16c478a6b 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -135,7 +135,7 @@ type Server struct { stripePublicKey string - pricing paymentsconfig.PricingValues + usagePrice paymentsconfig.ProjectUsagePrice schema graphql.Schema @@ -206,7 +206,7 @@ func (a *apiAuth) RemoveAuthCookie(w http.ResponseWriter) { } // NewServer creates new instance of console server. -func NewServer(logger *zap.Logger, config Config, service *console.Service, oidcService *oidc.Service, mailService *mailservice.Service, partners *rewards.PartnersService, analytics *analytics.Service, abTesting *abtesting.Service, listener net.Listener, stripePublicKey string, pricing paymentsconfig.PricingValues, nodeURL storj.NodeURL) *Server { +func NewServer(logger *zap.Logger, config Config, service *console.Service, oidcService *oidc.Service, mailService *mailservice.Service, partners *rewards.PartnersService, analytics *analytics.Service, abTesting *abtesting.Service, listener net.Listener, stripePublicKey string, usagePrice paymentsconfig.ProjectUsagePrice, nodeURL storj.NodeURL) *Server { server := Server{ log: logger, config: config, @@ -220,7 +220,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc ipRateLimiter: web.NewIPRateLimiter(config.RateLimit, logger), userIDRateLimiter: NewUserIDRateLimiter(config.RateLimit, logger), nodeURL: nodeURL, - pricing: pricing, + usagePrice: usagePrice, } logger.Debug("Starting Satellite UI.", zap.Stringer("Address", server.listener.Addr())) @@ -500,9 +500,9 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) { data.PathwayOverviewEnabled = server.config.PathwayOverviewEnabled data.DefaultPaidStorageLimit = server.config.UsageLimits.Storage.Paid data.DefaultPaidBandwidthLimit = server.config.UsageLimits.Bandwidth.Paid - data.StorageTBPrice = server.pricing.StorageTBPrice - data.EgressTBPrice = server.pricing.EgressTBPrice - data.SegmentPrice = server.pricing.SegmentPrice + data.StorageTBPrice = server.usagePrice.StorageTB + data.EgressTBPrice = server.usagePrice.EgressTB + data.SegmentPrice = server.usagePrice.Segment data.RegistrationRecaptchaEnabled = server.config.Captcha.Registration.Recaptcha.Enabled data.RegistrationRecaptchaSiteKey = server.config.Captcha.Registration.Recaptcha.SiteKey data.RegistrationHcaptchaEnabled = server.config.Captcha.Registration.Hcaptcha.Enabled diff --git a/satellite/core.go b/satellite/core.go index 04a5bcd5d..e706e2051 100644 --- a/satellite/core.go +++ b/satellite/core.go @@ -563,6 +563,16 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, stripeClient = stripecoinpayments.NewStripeClient(log, pc.StripeCoinPayments) } + prices, err := pc.UsagePrice.ToModel() + if err != nil { + return nil, errs.Combine(err, peer.Close()) + } + + priceOverrides, err := pc.UsagePriceOverrides.ToModels() + if err != nil { + return nil, errs.Combine(err, peer.Close()) + } + service, err := stripecoinpayments.NewService( peer.Log.Named("payments.stripe:service"), stripeClient, @@ -572,9 +582,8 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, peer.DB.Billing(), peer.DB.Console().Projects(), peer.DB.ProjectAccounting(), - pc.StorageTBPrice, - pc.EgressTBPrice, - pc.SegmentPrice, + prices, + priceOverrides, pc.BonusRate) if err != nil { return nil, errs.Combine(err, peer.Close()) diff --git a/satellite/payments/paymentsconfig/config.go b/satellite/payments/paymentsconfig/config.go index 8aeaf1586..d19f6fbf0 100644 --- a/satellite/payments/paymentsconfig/config.go +++ b/satellite/payments/paymentsconfig/config.go @@ -4,30 +4,143 @@ package paymentsconfig import ( + "fmt" + "strings" + + "github.com/shopspring/decimal" + "github.com/spf13/pflag" + "github.com/zeebo/errs" + "storj.io/storj/satellite/payments/billing" "storj.io/storj/satellite/payments/storjscan" "storj.io/storj/satellite/payments/stripecoinpayments" ) +// Error is payments config err class. +var Error = errs.Class("payments config") + // Config defines global payments config. type Config struct { Provider string `help:"payments provider to use" default:""` BillingConfig billing.Config StripeCoinPayments stripecoinpayments.Config Storjscan storjscan.Config - StorageTBPrice string `help:"price user should pay for storing TB per month" default:"4" testDefault:"10"` - EgressTBPrice string `help:"price user should pay for each TB of egress" default:"7" testDefault:"45"` - SegmentPrice string `help:"price user should pay for each segment stored in network per month" default:"0.0000088" testDefault:"0.0000022"` - BonusRate int64 `help:"amount of percents that user will earn as bonus credits by depositing in STORJ tokens" default:"10"` - NodeEgressBandwidthPrice int64 `help:"price node receive for storing TB of egress in cents" default:"2000"` - NodeRepairBandwidthPrice int64 `help:"price node receive for storing TB of repair in cents" default:"1000"` - NodeAuditBandwidthPrice int64 `help:"price node receive for storing TB of audit in cents" default:"1000"` - NodeDiskSpacePrice int64 `help:"price node receive for storing disk space in cents/TB" default:"150"` + UsagePrice ProjectUsagePrice + BonusRate int64 `help:"amount of percents that user will earn as bonus credits by depositing in STORJ tokens" default:"10"` + NodeEgressBandwidthPrice int64 `help:"price node receive for storing TB of egress in cents" default:"2000"` + NodeRepairBandwidthPrice int64 `help:"price node receive for storing TB of repair in cents" default:"1000"` + NodeAuditBandwidthPrice int64 `help:"price node receive for storing TB of audit in cents" default:"1000"` + NodeDiskSpacePrice int64 `help:"price node receive for storing disk space in cents/TB" default:"150"` + UsagePriceOverrides ProjectUsagePriceOverrides `help:"semicolon-separated usage price overrides in the format partner:storage,egress,segment"` } -// PricingValues holds pricing model for satellite. -type PricingValues struct { - StorageTBPrice string - EgressTBPrice string - SegmentPrice string +// ProjectUsagePrice holds the configuration for the satellite's project usage price model. +type ProjectUsagePrice struct { + StorageTB string `help:"price user should pay for storage per month in dollars/TB" default:"4" testDefault:"10"` + EgressTB string `help:"price user should pay for egress in dollars/TB" default:"7" testDefault:"45"` + Segment string `help:"price user should pay for segments stored on network per month in dollars/segment" default:"0.0000088" testDefault:"0.0000022"` +} + +// ToModel returns the stripecoinpayments.ProjectUsagePriceModel representation of the project usage price. +func (p ProjectUsagePrice) ToModel() (model stripecoinpayments.ProjectUsagePriceModel, err error) { + storageTBMonthDollars, err := decimal.NewFromString(p.StorageTB) + if err != nil { + return model, Error.Wrap(err) + } + egressTBDollars, err := decimal.NewFromString(p.EgressTB) + if err != nil { + return model, Error.Wrap(err) + } + segmentMonthDollars, err := decimal.NewFromString(p.Segment) + if err != nil { + return model, Error.Wrap(err) + } + + // Shift is to change the precision from TB dollars to MB cents + return stripecoinpayments.ProjectUsagePriceModel{ + StorageMBMonthCents: storageTBMonthDollars.Shift(-6).Shift(2), + EgressMBCents: egressTBDollars.Shift(-6).Shift(2), + SegmentMonthCents: segmentMonthDollars.Shift(2), + }, nil +} + +// Ensure that ProjectUsagePriceOverrides implements pflag.Value. +var _ pflag.Value = (*ProjectUsagePriceOverrides)(nil) + +// ProjectUsagePriceOverrides represents a mapping between partners and project usage price overrides. +type ProjectUsagePriceOverrides struct { + overrideMap map[string]ProjectUsagePrice +} + +// Type returns the type of the pflag.Value. +func (ProjectUsagePriceOverrides) Type() string { return "paymentsconfig.ProjectUsagePriceOverrides" } + +// String returns the string representation of the price overrides. +func (p *ProjectUsagePriceOverrides) String() string { + if p == nil { + return "" + } + var s strings.Builder + left := len(p.overrideMap) + for partner, prices := range p.overrideMap { + s.WriteString(fmt.Sprintf("%s:%s,%s,%s", partner, prices.StorageTB, prices.EgressTB, prices.Segment)) + left-- + if left > 0 { + s.WriteRune(';') + } + } + return s.String() +} + +// Set sets the list of price overrides to the parsed string. +func (p *ProjectUsagePriceOverrides) Set(s string) error { + overrideMap := make(map[string]ProjectUsagePrice) + for _, overrideStr := range strings.Split(s, ";") { + if overrideStr == "" { + continue + } + + info := strings.Split(overrideStr, ":") + if len(info) != 2 { + return Error.New("Invalid price override (expected format partner:storage,egress,segment, got %s)", overrideStr) + } + + partner := strings.TrimSpace(info[0]) + if len(partner) == 0 { + return Error.New("Price override partner must not be empty") + } + + pricesStr := info[1] + prices := strings.Split(pricesStr, ",") + if len(prices) != 3 { + return Error.New("Invalid prices (expected format storage,egress,segment, got %s)", pricesStr) + } + + for _, price := range prices { + if _, err := decimal.NewFromString(price); err != nil { + return Error.New("Invalid price (%s)", err) + } + } + + overrideMap[info[0]] = ProjectUsagePrice{ + StorageTB: prices[0], + EgressTB: prices[1], + Segment: prices[2], + } + } + p.overrideMap = overrideMap + return nil +} + +// ToModels returns the price overrides represented as a mapping between partners and project usage price models. +func (p ProjectUsagePriceOverrides) ToModels() (map[string]stripecoinpayments.ProjectUsagePriceModel, error) { + models := make(map[string]stripecoinpayments.ProjectUsagePriceModel) + for partner, prices := range p.overrideMap { + model, err := prices.ToModel() + if err != nil { + return nil, err + } + models[partner] = model + } + return models, nil } diff --git a/satellite/payments/paymentsconfig/config_test.go b/satellite/payments/paymentsconfig/config_test.go new file mode 100644 index 000000000..e6b6435a5 --- /dev/null +++ b/satellite/payments/paymentsconfig/config_test.go @@ -0,0 +1,101 @@ +// Copyright (C) 2022 Storj Labs, Inc. +// See LICENSE for copying information. + +package paymentsconfig_test + +import ( + "sort" + "strings" + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/require" + + "storj.io/storj/satellite/payments/paymentsconfig" + "storj.io/storj/satellite/payments/stripecoinpayments" +) + +func TestProjectUsagePriceOverrides(t *testing.T) { + type Prices map[string]stripecoinpayments.ProjectUsagePriceModel + + cases := []struct { + testID string + configValue string + expectedModel Prices + }{ + { + testID: "empty", + configValue: "", + expectedModel: Prices{}, + }, { + testID: "missing prices", + configValue: "partner", + }, { + testID: "missing partner", + configValue: ":1,2,3", + }, { + testID: "too few prices", + configValue: "partner:1", + }, { + testID: "single price override", + configValue: "partner:1,2,3", + expectedModel: Prices{ + // Shift is to change the precision from TB dollars to MB cents + "partner": stripecoinpayments.ProjectUsagePriceModel{ + StorageMBMonthCents: decimal.NewFromInt(1).Shift(-4), + EgressMBCents: decimal.NewFromInt(2).Shift(-4), + SegmentMonthCents: decimal.NewFromInt(3).Shift(2), + }, + }, + }, { + testID: "too many prices", + configValue: "partner:1,2,3,4", + }, { + testID: "invalid decimal", + configValue: "partner:0.0.1,2,3", + }, { + testID: "multiple price overrides", + configValue: "partner1:1,2,3;partner2:4,5,6", + expectedModel: Prices{ + "partner1": stripecoinpayments.ProjectUsagePriceModel{ + StorageMBMonthCents: decimal.NewFromInt(1).Shift(-4), + EgressMBCents: decimal.NewFromInt(2).Shift(-4), + SegmentMonthCents: decimal.NewFromInt(3).Shift(2), + }, + "partner2": stripecoinpayments.ProjectUsagePriceModel{ + StorageMBMonthCents: decimal.NewFromInt(4).Shift(-4), + EgressMBCents: decimal.NewFromInt(5).Shift(-4), + SegmentMonthCents: decimal.NewFromInt(6).Shift(2), + }, + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.testID, func(t *testing.T) { + price := &paymentsconfig.ProjectUsagePriceOverrides{} + err := price.Set(c.configValue) + if c.expectedModel == nil { + require.Error(t, err) + return + } + require.NoError(t, err) + + strParts := strings.Split(price.String(), ";") + sort.Strings(strParts) + require.Equal(t, c.configValue, strings.Join(strParts, ";")) + + models, err := price.ToModels() + require.NoError(t, err) + require.Len(t, models, len(c.expectedModel)) + for partner, price := range c.expectedModel { + model := models[partner] + require.Contains(t, models, partner) + require.Equal(t, price.StorageMBMonthCents, model.StorageMBMonthCents) + require.Equal(t, price.EgressMBCents, model.EgressMBCents) + require.Equal(t, price.SegmentMonthCents, model.SegmentMonthCents) + } + }) + } +} diff --git a/satellite/payments/stripecoinpayments/accounts_test.go b/satellite/payments/stripecoinpayments/accounts_test.go index 2e2ad1e3d..7107a807a 100644 --- a/satellite/payments/stripecoinpayments/accounts_test.go +++ b/satellite/payments/stripecoinpayments/accounts_test.go @@ -51,11 +51,19 @@ func TestSignupCouponCodes(t *testing.T) { projectUsage := accounting.NewService(db.ProjectAccounting(), cache, projectLimitCache, *sat.API.Metainfo.Metabase, 5*time.Minute, -10*time.Second) pc := paymentsconfig.Config{ - StorageTBPrice: "10", - EgressTBPrice: "45", - SegmentPrice: "0.0000022", + UsagePrice: paymentsconfig.ProjectUsagePrice{ + StorageTB: "10", + EgressTB: "45", + Segment: "0.0000022", + }, } + prices, err := pc.UsagePrice.ToModel() + require.NoError(t, err) + + priceOverrides, err := pc.UsagePriceOverrides.ToModels() + require.NoError(t, err) + paymentsService, err := stripecoinpayments.NewService( log.Named("payments.stripe:service"), stripecoinpayments.NewStripeMock( @@ -69,9 +77,8 @@ func TestSignupCouponCodes(t *testing.T) { db.Billing(), db.Console().Projects(), db.ProjectAccounting(), - pc.StorageTBPrice, - pc.EgressTBPrice, - pc.SegmentPrice, + prices, + priceOverrides, pc.BonusRate) require.NoError(t, err) diff --git a/satellite/payments/stripecoinpayments/service.go b/satellite/payments/stripecoinpayments/service.go index 9c722d3a5..ecb609d98 100644 --- a/satellite/payments/stripecoinpayments/service.go +++ b/satellite/payments/stripecoinpayments/service.go @@ -63,9 +63,8 @@ type Service struct { usageDB accounting.ProjectAccounting stripeClient StripeClient - StorageMBMonthPriceCents decimal.Decimal - EgressMBPriceCents decimal.Decimal - SegmentMonthPriceCents decimal.Decimal + usagePrices ProjectUsagePriceModel + usagePriceOverrides map[string]ProjectUsagePriceModel // BonusRate amount of percents BonusRate int64 // Coupon Values @@ -78,42 +77,30 @@ type Service struct { nowFn func() time.Time } +// ProjectUsagePriceModel represents price model for project usage. +type ProjectUsagePriceModel struct { + StorageMBMonthCents decimal.Decimal + EgressMBCents decimal.Decimal + SegmentMonthCents decimal.Decimal +} + // NewService creates a Service instance. -func NewService(log *zap.Logger, stripeClient StripeClient, config Config, db DB, walletsDB storjscan.WalletsDB, billingDB billing.TransactionsDB, projectsDB console.Projects, usageDB accounting.ProjectAccounting, storageTBPrice, egressTBPrice, segmentPrice string, bonusRate int64) (*Service, error) { - storageTBMonthDollars, err := decimal.NewFromString(storageTBPrice) - if err != nil { - return nil, err - } - egressTBDollars, err := decimal.NewFromString(egressTBPrice) - if err != nil { - return nil, err - } - segmentMonthDollars, err := decimal.NewFromString(segmentPrice) - if err != nil { - return nil, err - } - - // change the precision from TB dollars to MB cents - storageMBMonthPriceCents := storageTBMonthDollars.Shift(-6).Shift(2) - egressMBPriceCents := egressTBDollars.Shift(-6).Shift(2) - segmentMonthPriceCents := segmentMonthDollars.Shift(2) - +func NewService(log *zap.Logger, stripeClient StripeClient, config Config, db DB, walletsDB storjscan.WalletsDB, billingDB billing.TransactionsDB, projectsDB console.Projects, usageDB accounting.ProjectAccounting, usagePrices ProjectUsagePriceModel, usagePriceOverrides map[string]ProjectUsagePriceModel, bonusRate int64) (*Service, error) { return &Service{ - log: log, - db: db, - walletsDB: walletsDB, - billingDB: billingDB, - projectsDB: projectsDB, - usageDB: usageDB, - stripeClient: stripeClient, - StorageMBMonthPriceCents: storageMBMonthPriceCents, - EgressMBPriceCents: egressMBPriceCents, - SegmentMonthPriceCents: segmentMonthPriceCents, - BonusRate: bonusRate, - StripeFreeTierCouponID: config.StripeFreeTierCouponID, - AutoAdvance: config.AutoAdvance, - listingLimit: config.ListingLimit, - nowFn: time.Now, + log: log, + db: db, + walletsDB: walletsDB, + billingDB: billingDB, + projectsDB: projectsDB, + usageDB: usageDB, + stripeClient: stripeClient, + usagePrices: usagePrices, + usagePriceOverrides: usagePriceOverrides, + BonusRate: bonusRate, + StripeFreeTierCouponID: config.StripeFreeTierCouponID, + AutoAdvance: config.AutoAdvance, + listingLimit: config.ListingLimit, + nowFn: time.Now, }, nil } @@ -502,21 +489,21 @@ func (service *Service) InvoiceItemsFromProjectRecord(projName string, record Pr projectItem := &stripe.InvoiceItemParams{} projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Segment Storage (MB-Month)", projName)) projectItem.Quantity = stripe.Int64(storageMBMonthDecimal(record.Storage).IntPart()) - storagePrice, _ := service.StorageMBMonthPriceCents.Float64() + storagePrice, _ := service.usagePrices.StorageMBMonthCents.Float64() projectItem.UnitAmountDecimal = stripe.Float64(storagePrice) result = append(result, projectItem) projectItem = &stripe.InvoiceItemParams{} projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Egress Bandwidth (MB)", projName)) projectItem.Quantity = stripe.Int64(egressMBDecimal(record.Egress).IntPart()) - egressPrice, _ := service.EgressMBPriceCents.Float64() + egressPrice, _ := service.usagePrices.EgressMBCents.Float64() projectItem.UnitAmountDecimal = stripe.Float64(egressPrice) result = append(result, projectItem) projectItem = &stripe.InvoiceItemParams{} projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Segment Fee (Segment-Month)", projName)) projectItem.Quantity = stripe.Int64(segmentMonthDecimal(record.Segments).IntPart()) - segmentPrice, _ := service.SegmentMonthPriceCents.Float64() + segmentPrice, _ := service.usagePrices.SegmentMonthCents.Float64() projectItem.UnitAmountDecimal = stripe.Float64(segmentPrice) result = append(result, projectItem) service.log.Info("invoice items", zap.Any("result", result)) @@ -771,9 +758,9 @@ func (price projectUsagePrice) TotalInt64() int64 { // calculateProjectUsagePrice calculate project usage price. func (service *Service) calculateProjectUsagePrice(egress int64, storage, segments float64) projectUsagePrice { return projectUsagePrice{ - Storage: service.StorageMBMonthPriceCents.Mul(storageMBMonthDecimal(storage)).Round(0), - Egress: service.EgressMBPriceCents.Mul(egressMBDecimal(egress)).Round(0), - Segments: service.SegmentMonthPriceCents.Mul(segmentMonthDecimal(segments)).Round(0), + Storage: service.usagePrices.StorageMBMonthCents.Mul(storageMBMonthDecimal(storage)).Round(0), + Egress: service.usagePrices.EgressMBCents.Mul(egressMBDecimal(egress)).Round(0), + Segments: service.usagePrices.SegmentMonthCents.Mul(segmentMonthDecimal(segments)).Round(0), } } diff --git a/scripts/testdata/satellite-config.yaml.lock b/scripts/testdata/satellite-config.yaml.lock index 3d11a7cde..fe08c2438 100755 --- a/scripts/testdata/satellite-config.yaml.lock +++ b/scripts/testdata/satellite-config.yaml.lock @@ -790,9 +790,6 @@ 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 -# price user should pay for each TB of egress -# payments.egress-tb-price: "7" - # price node receive for storing TB of audit in cents # payments.node-audit-bandwidth-price: 1000 @@ -808,12 +805,6 @@ identity.key-path: /root/.local/share/storj/identity/satellite/identity.key # payments provider to use # payments.provider: "" -# price user should pay for each segment stored in network per month -# payments.segment-price: "0.0000088" - -# price user should pay for storing TB per month -# payments.storage-tb-price: "4" - # basic auth identifier # payments.storjscan.auth.identifier: "" @@ -844,6 +835,18 @@ identity.key-path: /root/.local/share/storj/identity/satellite/identity.key # stripe API secret key # payments.stripe-coin-payments.stripe-secret-key: "" +# semicolon-separated usage price overrides in the format partner:storage,egress,segment +# payments.usage-price-overrides: "" + +# price user should pay for egress in dollars/TB +# payments.usage-price.egress-tb: "7" + +# price user should pay for segments stored on network per month in dollars/segment +# payments.usage-price.segment: "0.0000088" + +# price user should pay for storage per month in dollars/TB +# payments.usage-price.storage-tb: "4" + # how often to remove unused project bandwidth rollups # project-bw-cleanup.interval: 168h0m0s