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:
Jeremy Wharton 2022-12-01 01:40:52 -06:00 committed by Storj Robot
parent 99f4b2a26e
commit ba7d2c2dbe
12 changed files with 363 additions and 109 deletions

View File

@ -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)
}

View File

@ -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 {

View File

@ -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(),
)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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())

View File

@ -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
}

View 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)
}
})
}
}

View File

@ -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)

View File

@ -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),
}
}

View File

@ -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