satellite/payments/stripecoinpayments: add config for price overrides
This change adds configuration flags for defining partner-specific project usage price overrides. Resolves https://github.com/storj/storj-private/issues/61 Change-Id: Ia535ac22576382211d045f9ff2c9b983a07e86f3
This commit is contained in:
parent
99f4b2a26e
commit
ba7d2c2dbe
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
|
101
satellite/payments/paymentsconfig/config_test.go
Normal file
101
satellite/payments/paymentsconfig/config_test.go
Normal file
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
21
scripts/testdata/satellite-config.yaml.lock
vendored
21
scripts/testdata/satellite-config.yaml.lock
vendored
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user