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)
|
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(
|
return stripecoinpayments.NewService(
|
||||||
log.Named("payments.stripe:service"),
|
log.Named("payments.stripe:service"),
|
||||||
stripeClient,
|
stripeClient,
|
||||||
@ -62,9 +72,8 @@ func setupPayments(log *zap.Logger, db satellite.DB) (*stripecoinpayments.Servic
|
|||||||
db.Billing(),
|
db.Billing(),
|
||||||
db.Console().Projects(),
|
db.Console().Projects(),
|
||||||
db.ProjectAccounting(),
|
db.ProjectAccounting(),
|
||||||
pc.StorageTBPrice,
|
prices,
|
||||||
pc.EgressTBPrice,
|
priceOverrides,
|
||||||
pc.SegmentPrice,
|
|
||||||
pc.BonusRate)
|
pc.BonusRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +145,16 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m
|
|||||||
stripeClient = stripecoinpayments.NewStripeClient(log, pc.StripeCoinPayments)
|
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.Payments.Service, err = stripecoinpayments.NewService(
|
||||||
peer.Log.Named("payments.stripe:service"),
|
peer.Log.Named("payments.stripe:service"),
|
||||||
stripeClient,
|
stripeClient,
|
||||||
@ -154,9 +164,8 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m
|
|||||||
peer.DB.Billing(),
|
peer.DB.Billing(),
|
||||||
peer.DB.Console().Projects(),
|
peer.DB.Console().Projects(),
|
||||||
peer.DB.ProjectAccounting(),
|
peer.DB.ProjectAccounting(),
|
||||||
pc.StorageTBPrice,
|
prices,
|
||||||
pc.EgressTBPrice,
|
priceOverrides,
|
||||||
pc.SegmentPrice,
|
|
||||||
pc.BonusRate)
|
pc.BonusRate)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -48,7 +48,6 @@ import (
|
|||||||
"storj.io/storj/satellite/orders"
|
"storj.io/storj/satellite/orders"
|
||||||
"storj.io/storj/satellite/overlay"
|
"storj.io/storj/satellite/overlay"
|
||||||
"storj.io/storj/satellite/payments"
|
"storj.io/storj/satellite/payments"
|
||||||
"storj.io/storj/satellite/payments/paymentsconfig"
|
|
||||||
"storj.io/storj/satellite/payments/storjscan"
|
"storj.io/storj/satellite/payments/storjscan"
|
||||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||||
"storj.io/storj/satellite/reputation"
|
"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)
|
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.Payments.StripeService, err = stripecoinpayments.NewService(
|
||||||
peer.Log.Named("payments.stripe:service"),
|
peer.Log.Named("payments.stripe:service"),
|
||||||
stripeClient,
|
stripeClient,
|
||||||
@ -521,9 +530,8 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
|||||||
peer.DB.Billing(),
|
peer.DB.Billing(),
|
||||||
peer.DB.Console().Projects(),
|
peer.DB.Console().Projects(),
|
||||||
peer.DB.ProjectAccounting(),
|
peer.DB.ProjectAccounting(),
|
||||||
pc.StorageTBPrice,
|
prices,
|
||||||
pc.EgressTBPrice,
|
priceOverrides,
|
||||||
pc.SegmentPrice,
|
|
||||||
pc.BonusRate)
|
pc.BonusRate)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -591,12 +599,6 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
|||||||
return nil, errs.Combine(err, peer.Close())
|
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.Console.Endpoint = consoleweb.NewServer(
|
||||||
peer.Log.Named("console:endpoint"),
|
peer.Log.Named("console:endpoint"),
|
||||||
consoleConfig,
|
consoleConfig,
|
||||||
@ -608,7 +610,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
|||||||
peer.ABTesting.Service,
|
peer.ABTesting.Service,
|
||||||
peer.Console.Listener,
|
peer.Console.Listener,
|
||||||
config.Payments.StripeCoinPayments.StripePublicKey,
|
config.Payments.StripeCoinPayments.StripePublicKey,
|
||||||
pricing,
|
config.Payments.UsagePrice,
|
||||||
peer.URL(),
|
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
|
// TODO maybe switch this test to testplanet to avoid defining config and Stripe service
|
||||||
pc := paymentsconfig.Config{
|
pc := paymentsconfig.Config{
|
||||||
StorageTBPrice: "10",
|
UsagePrice: paymentsconfig.ProjectUsagePrice{
|
||||||
EgressTBPrice: "45",
|
StorageTB: "10",
|
||||||
SegmentPrice: "0.0000022",
|
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(
|
paymentsService, err := stripecoinpayments.NewService(
|
||||||
log.Named("payments.stripe:service"),
|
log.Named("payments.stripe:service"),
|
||||||
stripecoinpayments.NewStripeMock(
|
stripecoinpayments.NewStripeMock(
|
||||||
@ -91,9 +99,8 @@ func TestGraphqlMutation(t *testing.T) {
|
|||||||
db.Billing(),
|
db.Billing(),
|
||||||
db.Console().Projects(),
|
db.Console().Projects(),
|
||||||
db.ProjectAccounting(),
|
db.ProjectAccounting(),
|
||||||
pc.StorageTBPrice,
|
prices,
|
||||||
pc.EgressTBPrice,
|
priceOverrides,
|
||||||
pc.SegmentPrice,
|
|
||||||
pc.BonusRate)
|
pc.BonusRate)
|
||||||
require.NoError(t, err)
|
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
|
// TODO maybe switch this test to testplanet to avoid defining config and Stripe service
|
||||||
pc := paymentsconfig.Config{
|
pc := paymentsconfig.Config{
|
||||||
StorageTBPrice: "10",
|
UsagePrice: paymentsconfig.ProjectUsagePrice{
|
||||||
EgressTBPrice: "45",
|
StorageTB: "10",
|
||||||
SegmentPrice: "0.0000022",
|
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(
|
paymentsService, err := stripecoinpayments.NewService(
|
||||||
log.Named("payments.stripe:service"),
|
log.Named("payments.stripe:service"),
|
||||||
stripecoinpayments.NewStripeMock(
|
stripecoinpayments.NewStripeMock(
|
||||||
@ -75,9 +83,8 @@ func TestGraphqlQuery(t *testing.T) {
|
|||||||
db.Billing(),
|
db.Billing(),
|
||||||
db.Console().Projects(),
|
db.Console().Projects(),
|
||||||
db.ProjectAccounting(),
|
db.ProjectAccounting(),
|
||||||
pc.StorageTBPrice,
|
prices,
|
||||||
pc.EgressTBPrice,
|
priceOverrides,
|
||||||
pc.SegmentPrice,
|
|
||||||
pc.BonusRate)
|
pc.BonusRate)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ type Server struct {
|
|||||||
|
|
||||||
stripePublicKey string
|
stripePublicKey string
|
||||||
|
|
||||||
pricing paymentsconfig.PricingValues
|
usagePrice paymentsconfig.ProjectUsagePrice
|
||||||
|
|
||||||
schema graphql.Schema
|
schema graphql.Schema
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ func (a *apiAuth) RemoveAuthCookie(w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates new instance of console server.
|
// 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{
|
server := Server{
|
||||||
log: logger,
|
log: logger,
|
||||||
config: config,
|
config: config,
|
||||||
@ -220,7 +220,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
|||||||
ipRateLimiter: web.NewIPRateLimiter(config.RateLimit, logger),
|
ipRateLimiter: web.NewIPRateLimiter(config.RateLimit, logger),
|
||||||
userIDRateLimiter: NewUserIDRateLimiter(config.RateLimit, logger),
|
userIDRateLimiter: NewUserIDRateLimiter(config.RateLimit, logger),
|
||||||
nodeURL: nodeURL,
|
nodeURL: nodeURL,
|
||||||
pricing: pricing,
|
usagePrice: usagePrice,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("Starting Satellite UI.", zap.Stringer("Address", server.listener.Addr()))
|
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.PathwayOverviewEnabled = server.config.PathwayOverviewEnabled
|
||||||
data.DefaultPaidStorageLimit = server.config.UsageLimits.Storage.Paid
|
data.DefaultPaidStorageLimit = server.config.UsageLimits.Storage.Paid
|
||||||
data.DefaultPaidBandwidthLimit = server.config.UsageLimits.Bandwidth.Paid
|
data.DefaultPaidBandwidthLimit = server.config.UsageLimits.Bandwidth.Paid
|
||||||
data.StorageTBPrice = server.pricing.StorageTBPrice
|
data.StorageTBPrice = server.usagePrice.StorageTB
|
||||||
data.EgressTBPrice = server.pricing.EgressTBPrice
|
data.EgressTBPrice = server.usagePrice.EgressTB
|
||||||
data.SegmentPrice = server.pricing.SegmentPrice
|
data.SegmentPrice = server.usagePrice.Segment
|
||||||
data.RegistrationRecaptchaEnabled = server.config.Captcha.Registration.Recaptcha.Enabled
|
data.RegistrationRecaptchaEnabled = server.config.Captcha.Registration.Recaptcha.Enabled
|
||||||
data.RegistrationRecaptchaSiteKey = server.config.Captcha.Registration.Recaptcha.SiteKey
|
data.RegistrationRecaptchaSiteKey = server.config.Captcha.Registration.Recaptcha.SiteKey
|
||||||
data.RegistrationHcaptchaEnabled = server.config.Captcha.Registration.Hcaptcha.Enabled
|
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)
|
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(
|
service, err := stripecoinpayments.NewService(
|
||||||
peer.Log.Named("payments.stripe:service"),
|
peer.Log.Named("payments.stripe:service"),
|
||||||
stripeClient,
|
stripeClient,
|
||||||
@ -572,9 +582,8 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB,
|
|||||||
peer.DB.Billing(),
|
peer.DB.Billing(),
|
||||||
peer.DB.Console().Projects(),
|
peer.DB.Console().Projects(),
|
||||||
peer.DB.ProjectAccounting(),
|
peer.DB.ProjectAccounting(),
|
||||||
pc.StorageTBPrice,
|
prices,
|
||||||
pc.EgressTBPrice,
|
priceOverrides,
|
||||||
pc.SegmentPrice,
|
|
||||||
pc.BonusRate)
|
pc.BonusRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Combine(err, peer.Close())
|
return nil, errs.Combine(err, peer.Close())
|
||||||
|
@ -4,30 +4,143 @@
|
|||||||
package paymentsconfig
|
package paymentsconfig
|
||||||
|
|
||||||
import (
|
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/billing"
|
||||||
"storj.io/storj/satellite/payments/storjscan"
|
"storj.io/storj/satellite/payments/storjscan"
|
||||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Error is payments config err class.
|
||||||
|
var Error = errs.Class("payments config")
|
||||||
|
|
||||||
// Config defines global payments config.
|
// Config defines global payments config.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Provider string `help:"payments provider to use" default:""`
|
Provider string `help:"payments provider to use" default:""`
|
||||||
BillingConfig billing.Config
|
BillingConfig billing.Config
|
||||||
StripeCoinPayments stripecoinpayments.Config
|
StripeCoinPayments stripecoinpayments.Config
|
||||||
Storjscan storjscan.Config
|
Storjscan storjscan.Config
|
||||||
StorageTBPrice string `help:"price user should pay for storing TB per month" default:"4" testDefault:"10"`
|
UsagePrice ProjectUsagePrice
|
||||||
EgressTBPrice string `help:"price user should pay for each TB of egress" default:"7" testDefault:"45"`
|
BonusRate int64 `help:"amount of percents that user will earn as bonus credits by depositing in STORJ tokens" default:"10"`
|
||||||
SegmentPrice string `help:"price user should pay for each segment stored in network per month" default:"0.0000088" testDefault:"0.0000022"`
|
NodeEgressBandwidthPrice int64 `help:"price node receive for storing TB of egress in cents" default:"2000"`
|
||||||
BonusRate int64 `help:"amount of percents that user will earn as bonus credits by depositing in STORJ tokens" default:"10"`
|
NodeRepairBandwidthPrice int64 `help:"price node receive for storing TB of repair in cents" default:"1000"`
|
||||||
NodeEgressBandwidthPrice int64 `help:"price node receive for storing TB of egress in cents" default:"2000"`
|
NodeAuditBandwidthPrice int64 `help:"price node receive for storing TB of audit in cents" default:"1000"`
|
||||||
NodeRepairBandwidthPrice int64 `help:"price node receive for storing TB of repair in cents" default:"1000"`
|
NodeDiskSpacePrice int64 `help:"price node receive for storing disk space in cents/TB" default:"150"`
|
||||||
NodeAuditBandwidthPrice int64 `help:"price node receive for storing TB of audit in cents" default:"1000"`
|
UsagePriceOverrides ProjectUsagePriceOverrides `help:"semicolon-separated usage price overrides in the format partner:storage,egress,segment"`
|
||||||
NodeDiskSpacePrice int64 `help:"price node receive for storing disk space in cents/TB" default:"150"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PricingValues holds pricing model for satellite.
|
// ProjectUsagePrice holds the configuration for the satellite's project usage price model.
|
||||||
type PricingValues struct {
|
type ProjectUsagePrice struct {
|
||||||
StorageTBPrice string
|
StorageTB string `help:"price user should pay for storage per month in dollars/TB" default:"4" testDefault:"10"`
|
||||||
EgressTBPrice string
|
EgressTB string `help:"price user should pay for egress in dollars/TB" default:"7" testDefault:"45"`
|
||||||
SegmentPrice string
|
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)
|
projectUsage := accounting.NewService(db.ProjectAccounting(), cache, projectLimitCache, *sat.API.Metainfo.Metabase, 5*time.Minute, -10*time.Second)
|
||||||
|
|
||||||
pc := paymentsconfig.Config{
|
pc := paymentsconfig.Config{
|
||||||
StorageTBPrice: "10",
|
UsagePrice: paymentsconfig.ProjectUsagePrice{
|
||||||
EgressTBPrice: "45",
|
StorageTB: "10",
|
||||||
SegmentPrice: "0.0000022",
|
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(
|
paymentsService, err := stripecoinpayments.NewService(
|
||||||
log.Named("payments.stripe:service"),
|
log.Named("payments.stripe:service"),
|
||||||
stripecoinpayments.NewStripeMock(
|
stripecoinpayments.NewStripeMock(
|
||||||
@ -69,9 +77,8 @@ func TestSignupCouponCodes(t *testing.T) {
|
|||||||
db.Billing(),
|
db.Billing(),
|
||||||
db.Console().Projects(),
|
db.Console().Projects(),
|
||||||
db.ProjectAccounting(),
|
db.ProjectAccounting(),
|
||||||
pc.StorageTBPrice,
|
prices,
|
||||||
pc.EgressTBPrice,
|
priceOverrides,
|
||||||
pc.SegmentPrice,
|
|
||||||
pc.BonusRate)
|
pc.BonusRate)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -63,9 +63,8 @@ type Service struct {
|
|||||||
usageDB accounting.ProjectAccounting
|
usageDB accounting.ProjectAccounting
|
||||||
stripeClient StripeClient
|
stripeClient StripeClient
|
||||||
|
|
||||||
StorageMBMonthPriceCents decimal.Decimal
|
usagePrices ProjectUsagePriceModel
|
||||||
EgressMBPriceCents decimal.Decimal
|
usagePriceOverrides map[string]ProjectUsagePriceModel
|
||||||
SegmentMonthPriceCents decimal.Decimal
|
|
||||||
// BonusRate amount of percents
|
// BonusRate amount of percents
|
||||||
BonusRate int64
|
BonusRate int64
|
||||||
// Coupon Values
|
// Coupon Values
|
||||||
@ -78,42 +77,30 @@ type Service struct {
|
|||||||
nowFn func() time.Time
|
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.
|
// 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) {
|
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) {
|
||||||
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)
|
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
log: log,
|
log: log,
|
||||||
db: db,
|
db: db,
|
||||||
walletsDB: walletsDB,
|
walletsDB: walletsDB,
|
||||||
billingDB: billingDB,
|
billingDB: billingDB,
|
||||||
projectsDB: projectsDB,
|
projectsDB: projectsDB,
|
||||||
usageDB: usageDB,
|
usageDB: usageDB,
|
||||||
stripeClient: stripeClient,
|
stripeClient: stripeClient,
|
||||||
StorageMBMonthPriceCents: storageMBMonthPriceCents,
|
usagePrices: usagePrices,
|
||||||
EgressMBPriceCents: egressMBPriceCents,
|
usagePriceOverrides: usagePriceOverrides,
|
||||||
SegmentMonthPriceCents: segmentMonthPriceCents,
|
BonusRate: bonusRate,
|
||||||
BonusRate: bonusRate,
|
StripeFreeTierCouponID: config.StripeFreeTierCouponID,
|
||||||
StripeFreeTierCouponID: config.StripeFreeTierCouponID,
|
AutoAdvance: config.AutoAdvance,
|
||||||
AutoAdvance: config.AutoAdvance,
|
listingLimit: config.ListingLimit,
|
||||||
listingLimit: config.ListingLimit,
|
nowFn: time.Now,
|
||||||
nowFn: time.Now,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,21 +489,21 @@ func (service *Service) InvoiceItemsFromProjectRecord(projName string, record Pr
|
|||||||
projectItem := &stripe.InvoiceItemParams{}
|
projectItem := &stripe.InvoiceItemParams{}
|
||||||
projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Segment Storage (MB-Month)", projName))
|
projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Segment Storage (MB-Month)", projName))
|
||||||
projectItem.Quantity = stripe.Int64(storageMBMonthDecimal(record.Storage).IntPart())
|
projectItem.Quantity = stripe.Int64(storageMBMonthDecimal(record.Storage).IntPart())
|
||||||
storagePrice, _ := service.StorageMBMonthPriceCents.Float64()
|
storagePrice, _ := service.usagePrices.StorageMBMonthCents.Float64()
|
||||||
projectItem.UnitAmountDecimal = stripe.Float64(storagePrice)
|
projectItem.UnitAmountDecimal = stripe.Float64(storagePrice)
|
||||||
result = append(result, projectItem)
|
result = append(result, projectItem)
|
||||||
|
|
||||||
projectItem = &stripe.InvoiceItemParams{}
|
projectItem = &stripe.InvoiceItemParams{}
|
||||||
projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Egress Bandwidth (MB)", projName))
|
projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Egress Bandwidth (MB)", projName))
|
||||||
projectItem.Quantity = stripe.Int64(egressMBDecimal(record.Egress).IntPart())
|
projectItem.Quantity = stripe.Int64(egressMBDecimal(record.Egress).IntPart())
|
||||||
egressPrice, _ := service.EgressMBPriceCents.Float64()
|
egressPrice, _ := service.usagePrices.EgressMBCents.Float64()
|
||||||
projectItem.UnitAmountDecimal = stripe.Float64(egressPrice)
|
projectItem.UnitAmountDecimal = stripe.Float64(egressPrice)
|
||||||
result = append(result, projectItem)
|
result = append(result, projectItem)
|
||||||
|
|
||||||
projectItem = &stripe.InvoiceItemParams{}
|
projectItem = &stripe.InvoiceItemParams{}
|
||||||
projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Segment Fee (Segment-Month)", projName))
|
projectItem.Description = stripe.String(fmt.Sprintf("Project %s - Segment Fee (Segment-Month)", projName))
|
||||||
projectItem.Quantity = stripe.Int64(segmentMonthDecimal(record.Segments).IntPart())
|
projectItem.Quantity = stripe.Int64(segmentMonthDecimal(record.Segments).IntPart())
|
||||||
segmentPrice, _ := service.SegmentMonthPriceCents.Float64()
|
segmentPrice, _ := service.usagePrices.SegmentMonthCents.Float64()
|
||||||
projectItem.UnitAmountDecimal = stripe.Float64(segmentPrice)
|
projectItem.UnitAmountDecimal = stripe.Float64(segmentPrice)
|
||||||
result = append(result, projectItem)
|
result = append(result, projectItem)
|
||||||
service.log.Info("invoice items", zap.Any("result", result))
|
service.log.Info("invoice items", zap.Any("result", result))
|
||||||
@ -771,9 +758,9 @@ func (price projectUsagePrice) TotalInt64() int64 {
|
|||||||
// calculateProjectUsagePrice calculate project usage price.
|
// calculateProjectUsagePrice calculate project usage price.
|
||||||
func (service *Service) calculateProjectUsagePrice(egress int64, storage, segments float64) projectUsagePrice {
|
func (service *Service) calculateProjectUsagePrice(egress int64, storage, segments float64) projectUsagePrice {
|
||||||
return projectUsagePrice{
|
return projectUsagePrice{
|
||||||
Storage: service.StorageMBMonthPriceCents.Mul(storageMBMonthDecimal(storage)).Round(0),
|
Storage: service.usagePrices.StorageMBMonthCents.Mul(storageMBMonthDecimal(storage)).Round(0),
|
||||||
Egress: service.EgressMBPriceCents.Mul(egressMBDecimal(egress)).Round(0),
|
Egress: service.usagePrices.EgressMBCents.Mul(egressMBDecimal(egress)).Round(0),
|
||||||
Segments: service.SegmentMonthPriceCents.Mul(segmentMonthDecimal(segments)).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
|
# amount of percents that user will earn as bonus credits by depositing in STORJ tokens
|
||||||
# payments.bonus-rate: 10
|
# 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
|
# price node receive for storing TB of audit in cents
|
||||||
# payments.node-audit-bandwidth-price: 1000
|
# 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 to use
|
||||||
# payments.provider: ""
|
# 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
|
# basic auth identifier
|
||||||
# payments.storjscan.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
|
# stripe API secret key
|
||||||
# payments.stripe-coin-payments.stripe-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
|
# how often to remove unused project bandwidth rollups
|
||||||
# project-bw-cleanup.interval: 168h0m0s
|
# project-bw-cleanup.interval: 168h0m0s
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user