satellite/payments: allow floating point numbers for pricing
Change-Id: I78b60134cf043746efef5371b761939a10f75aaf
This commit is contained in:
parent
a0c9f7f3b0
commit
083b396c16
1
go.mod
1
go.mod
@ -81,6 +81,7 @@ require (
|
||||
github.com/pkg/profile v1.2.1 // indirect
|
||||
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e // indirect
|
||||
github.com/rs/cors v1.5.0 // indirect
|
||||
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114
|
||||
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e
|
||||
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
|
||||
|
2
go.sum
2
go.sum
@ -375,6 +375,8 @@ github.com/segmentio/go-prompt v1.2.1-0.20161017233205-f0d19b6901ad h1:EqOdoSJGI
|
||||
github.com/segmentio/go-prompt v1.2.1-0.20161017233205-f0d19b6901ad/go.mod h1:B3ehdD1xPoWDKgrQgUaGk+m8H1xb1J5TyYDfKpKNeEE=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114 h1:Pm6R878vxWWWR+Sa3ppsLce/Zq+JNTs6aVvRu13jv9A=
|
||||
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
|
@ -479,15 +479,19 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
default:
|
||||
peer.Payments.Accounts = mockpayments.Accounts()
|
||||
case "stripecoinpayments":
|
||||
service := stripecoinpayments.NewService(
|
||||
service, err := stripecoinpayments.NewService(
|
||||
peer.Log.Named("payments.stripe:service"),
|
||||
pc.StripeCoinPayments,
|
||||
peer.DB.StripeCoinPayments(),
|
||||
peer.DB.Console().Projects(),
|
||||
peer.DB.ProjectAccounting(),
|
||||
pc.PerObjectPrice,
|
||||
pc.EgressPrice,
|
||||
pc.TbhPrice)
|
||||
pc.StorageTBPrice,
|
||||
pc.EgressTBPrice,
|
||||
pc.ObjectPrice)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
|
||||
peer.Payments.Accounts = service.Accounts()
|
||||
peer.Payments.Inspector = stripecoinpayments.NewEndpoint(service)
|
||||
|
@ -57,14 +57,15 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
payments := stripecoinpayments.NewService(
|
||||
payments, err := stripecoinpayments.NewService(
|
||||
log.Named("payments"),
|
||||
stripecoinpayments.Config{},
|
||||
db.StripeCoinPayments(),
|
||||
db.Console().Projects(),
|
||||
db.ProjectAccounting(),
|
||||
0, 0, 0,
|
||||
"0", "0", "0",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
miniredis := redisserver.NewMini()
|
||||
addr, cleanup, err := miniredis.Run()
|
||||
|
@ -42,14 +42,15 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
payments := stripecoinpayments.NewService(
|
||||
payments, err := stripecoinpayments.NewService(
|
||||
log.Named("payments"),
|
||||
stripecoinpayments.Config{},
|
||||
db.StripeCoinPayments(),
|
||||
db.Console().Projects(),
|
||||
db.ProjectAccounting(),
|
||||
0, 0, 0,
|
||||
"0", "0", "0",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
miniredis := redisserver.NewMini()
|
||||
addr, cleanup, err := miniredis.Run()
|
||||
|
@ -372,15 +372,19 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
default:
|
||||
peer.Payments.Accounts = mockpayments.Accounts()
|
||||
case "stripecoinpayments":
|
||||
service := stripecoinpayments.NewService(
|
||||
service, err := stripecoinpayments.NewService(
|
||||
peer.Log.Named("payments.stripe:service"),
|
||||
pc.StripeCoinPayments,
|
||||
peer.DB.StripeCoinPayments(),
|
||||
peer.DB.Console().Projects(),
|
||||
peer.DB.ProjectAccounting(),
|
||||
pc.PerObjectPrice,
|
||||
pc.EgressPrice,
|
||||
pc.TbhPrice)
|
||||
pc.StorageTBPrice,
|
||||
pc.EgressTBPrice,
|
||||
pc.ObjectPrice)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
|
||||
peer.Payments.Accounts = service.Accounts()
|
||||
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
type Config struct {
|
||||
Provider string `help:"payments provider to use" default:""`
|
||||
StripeCoinPayments stripecoinpayments.Config
|
||||
PerObjectPrice int64 `help:"price in cents user should pay for each object storing in network" devDefault:"0" releaseDefault:"0"`
|
||||
EgressPrice int64 `help:"price in cents user should pay for each TB of egress" devDefault:"0" releaseDefault:"0"`
|
||||
TbhPrice int64 `help:"price in cents user should pay for storing each TB per hour" devDefault:"0" releaseDefault:"0"`
|
||||
StorageTBPrice string `help:"price user should pay for storing TB per month" default:"10"`
|
||||
EgressTBPrice string `help:"price user should pay for each TB of egress" default:"45"`
|
||||
ObjectPrice string `help:"price user should pay for each object stored in network per month" default:"0.0000022"`
|
||||
}
|
||||
|
@ -127,12 +127,13 @@ func (accounts *accounts) ProjectCharges(ctx context.Context, userID uuid.UUID)
|
||||
return charges, Error.Wrap(err)
|
||||
}
|
||||
|
||||
projectPrice := accounts.service.calculateProjectUsagePrice(usage.Egress, usage.Storage, usage.ObjectCount)
|
||||
|
||||
charges = append(charges, payments.ProjectCharge{
|
||||
ProjectID: project.ID,
|
||||
// TODO: check precision
|
||||
Egress: usage.Egress * accounts.service.EgressPrice / int64(memory.TB),
|
||||
ObjectCount: int64(usage.ObjectCount * float64(accounts.service.PerObjectPrice)),
|
||||
StorageGbHrs: int64(usage.Storage*float64(accounts.service.TBhPrice)) / int64(memory.TB),
|
||||
ProjectID: project.ID,
|
||||
Egress: projectPrice.Egress.IntPart(),
|
||||
ObjectCount: projectPrice.Objects.IntPart(),
|
||||
StorageGbHrs: projectPrice.Storage.IntPart(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"github.com/stripe/stripe-go"
|
||||
"github.com/stripe/stripe-go/client"
|
||||
@ -17,7 +18,6 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/spacemonkeygo/monkit.v2"
|
||||
|
||||
"storj.io/common/memory"
|
||||
"storj.io/storj/satellite/accounting"
|
||||
"storj.io/storj/satellite/console"
|
||||
"storj.io/storj/satellite/payments"
|
||||
@ -55,9 +55,9 @@ type Service struct {
|
||||
stripeClient *client.API
|
||||
coinPayments *coinpayments.Client
|
||||
|
||||
PerObjectPrice int64
|
||||
EgressPrice int64
|
||||
TBhPrice int64
|
||||
ByteHourCents decimal.Decimal
|
||||
EgressByteCents decimal.Decimal
|
||||
ObjectHourCents decimal.Decimal
|
||||
|
||||
mu sync.Mutex
|
||||
rates coinpayments.CurrencyRateInfos
|
||||
@ -65,7 +65,7 @@ type Service struct {
|
||||
}
|
||||
|
||||
// NewService creates a Service instance.
|
||||
func NewService(log *zap.Logger, config Config, db DB, projectsDB console.Projects, usageDB accounting.ProjectAccounting, perObjectPrice, egressPrice, tbhPrice int64) *Service {
|
||||
func NewService(log *zap.Logger, config Config, db DB, projectsDB console.Projects, usageDB accounting.ProjectAccounting, storageTBPrice, egressTBPrice, objectPrice string) (*Service, error) {
|
||||
backendConfig := &stripe.BackendConfig{
|
||||
LeveledLogger: log.Sugar(),
|
||||
}
|
||||
@ -85,17 +85,44 @@ func NewService(log *zap.Logger, config Config, db DB, projectsDB console.Projec
|
||||
},
|
||||
)
|
||||
|
||||
return &Service{
|
||||
log: log,
|
||||
db: db,
|
||||
projectsDB: projectsDB,
|
||||
usageDB: usageDB,
|
||||
stripeClient: stripeClient,
|
||||
coinPayments: coinPaymentsClient,
|
||||
TBhPrice: tbhPrice,
|
||||
PerObjectPrice: perObjectPrice,
|
||||
EgressPrice: egressPrice,
|
||||
tbMonthDollars, err := decimal.NewFromString(storageTBPrice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
egressByteDollars, err := decimal.NewFromString(egressTBPrice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objectMonthDollars, err := decimal.NewFromString(objectPrice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// change the precision from dollars to cents
|
||||
tbMonthCents := tbMonthDollars.Shift(2)
|
||||
egressByteCents := egressByteDollars.Shift(2)
|
||||
objectHourCents := objectMonthDollars.Shift(2)
|
||||
|
||||
// get per hour prices from storage and objects
|
||||
hoursPerMonth := decimal.New(30*24, 0)
|
||||
|
||||
tbHourCents := tbMonthCents.Div(hoursPerMonth)
|
||||
objectHourCents = objectHourCents.Div(hoursPerMonth)
|
||||
|
||||
// convert tb to bytes for storage price
|
||||
byteHourCents := tbHourCents.Div(decimal.New(1000000000000, 0))
|
||||
|
||||
return &Service{
|
||||
log: log,
|
||||
db: db,
|
||||
projectsDB: projectsDB,
|
||||
usageDB: usageDB,
|
||||
stripeClient: stripeClient,
|
||||
coinPayments: coinPaymentsClient,
|
||||
ByteHourCents: byteHourCents,
|
||||
EgressByteCents: egressByteCents,
|
||||
ObjectHourCents: objectHourCents,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Accounts exposes all needed functionality to manage payment accounts.
|
||||
@ -394,11 +421,7 @@ func (service *Service) createProjectRecords(ctx context.Context, projects []con
|
||||
return err
|
||||
}
|
||||
|
||||
egressPrice := usage.Egress * service.EgressPrice / int64(memory.TB)
|
||||
objectCountPrice := int64(usage.ObjectCount * float64(service.PerObjectPrice))
|
||||
storageGbHrsPrice := int64(usage.Storage*float64(service.TBhPrice)) / int64(memory.TB)
|
||||
|
||||
currentUsagePrice := egressPrice + objectCountPrice + storageGbHrsPrice
|
||||
currentUsagePrice := service.calculateProjectUsagePrice(usage.Egress, usage.Storage, usage.ObjectCount).TotalInt64()
|
||||
|
||||
// TODO: only for 1 coupon per project
|
||||
for _, coupon := range coupons {
|
||||
@ -506,15 +529,10 @@ func (service *Service) createInvoiceItems(ctx context.Context, cusID, projName
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: reuse this code fragment.
|
||||
egressPrice := record.Egress * service.EgressPrice / int64(memory.TB)
|
||||
objectCountPrice := int64(record.Objects * float64(service.PerObjectPrice))
|
||||
storageGbHrsPrice := int64(record.Storage*float64(service.TBhPrice)) / int64(memory.TB)
|
||||
|
||||
currentUsageAmount := egressPrice + objectCountPrice + storageGbHrsPrice
|
||||
projectPrice := service.calculateProjectUsagePrice(record.Egress, record.Storage, record.Objects)
|
||||
|
||||
projectItem := &stripe.InvoiceItemParams{
|
||||
Amount: stripe.Int64(currentUsageAmount),
|
||||
Amount: stripe.Int64(projectPrice.TotalInt64()),
|
||||
Currency: stripe.String(string(stripe.CurrencyUSD)),
|
||||
Customer: stripe.String(cusID),
|
||||
Description: stripe.String(fmt.Sprintf("project %s", projName)),
|
||||
@ -706,3 +724,29 @@ func (service *Service) createInvoice(ctx context.Context, cusID string) (err er
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// projectUsagePrice represents pricing for project usage.
|
||||
type projectUsagePrice struct {
|
||||
Storage decimal.Decimal
|
||||
Egress decimal.Decimal
|
||||
Objects decimal.Decimal
|
||||
}
|
||||
|
||||
// Total returns project usage price total.
|
||||
func (price projectUsagePrice) Total() decimal.Decimal {
|
||||
return price.Storage.Add(price.Egress).Add(price.Objects)
|
||||
}
|
||||
|
||||
// Total returns project usage price total.
|
||||
func (price projectUsagePrice) TotalInt64() int64 {
|
||||
return price.Storage.Add(price.Egress).Add(price.Objects).IntPart()
|
||||
}
|
||||
|
||||
// calculateProjectUsagePrice calculate project usage price.
|
||||
func (service *Service) calculateProjectUsagePrice(egress int64, storage, objects float64) projectUsagePrice {
|
||||
return projectUsagePrice{
|
||||
Storage: service.ByteHourCents.Mul(decimal.NewFromFloat(storage)),
|
||||
Egress: service.EgressByteCents.Mul(decimal.New(egress, 0)),
|
||||
Objects: service.ObjectHourCents.Mul(decimal.NewFromFloat(objects)),
|
||||
}
|
||||
}
|
||||
|
14
scripts/testdata/satellite-config.yaml.lock
vendored
14
scripts/testdata/satellite-config.yaml.lock
vendored
@ -367,15 +367,18 @@ identity.key-path: /root/.local/share/storj/identity/satellite/identity.key
|
||||
# number of update requests to process per transaction
|
||||
# overlay.update-stats-batch-size: 100
|
||||
|
||||
# price in cents user should pay for each TB of egress
|
||||
# payments.egress-price: 0
|
||||
# price user should pay for each TB of egress
|
||||
# payments.egress-tb-price: "45"
|
||||
|
||||
# price in cents user should pay for each object storing in network
|
||||
# payments.per-object-price: 0
|
||||
# price user should pay for each object stored in network per month
|
||||
# payments.object-price: "0.0000022"
|
||||
|
||||
# payments provider to use
|
||||
# payments.provider: ""
|
||||
|
||||
# price user should pay for storing TB per month
|
||||
# payments.storage-tb-price: "10"
|
||||
|
||||
# amount of time we wait before running next account balance update loop
|
||||
# payments.stripe-coin-payments.account-balance-update-interval: 1h30m0s
|
||||
|
||||
@ -397,9 +400,6 @@ identity.key-path: /root/.local/share/storj/identity/satellite/identity.key
|
||||
# amount of time we wait before running next transaction update loop
|
||||
# payments.stripe-coin-payments.transaction-update-interval: 30m0s
|
||||
|
||||
# price in cents user should pay for storing each TB per hour
|
||||
# payments.tbh-price: 0
|
||||
|
||||
# referrals.referral-manager-url: ""
|
||||
|
||||
# time limit for downloading pieces from a node for repair
|
||||
|
Loading…
Reference in New Issue
Block a user