satellite/payments: add egress discount ratio to price override config

This change allows for specifying the ratio of free egress per unit
of storage within a price override configuration.

References storj/storj-private#215
References storj/storj-private#224

Change-Id: Ib1c79f77ec8bb11dd5b2f9dace13800b0b3ce942
This commit is contained in:
Jeremy Wharton 2023-04-03 08:24:34 -05:00 committed by Storj Robot
parent 64c798e912
commit 6e9dae06d5
4 changed files with 42 additions and 28 deletions

View File

@ -36,15 +36,16 @@ type Config struct {
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"`
UsagePriceOverrides ProjectUsagePriceOverrides `help:"semicolon-separated usage price overrides in the format partner:storage,egress,segment,egress_discount_ratio. The egress discount ratio is the ratio of free egress per unit-month of storage"`
PackagePlans PackagePlans `help:"semicolon-separated partner package plans in the format partner:price,credit. Price and credit are in cents USD."`
}
// 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"`
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"`
EgressDiscountRatio float64 `internal:"true"`
}
// ToModel returns the payments.ProjectUsagePriceModel representation of the project usage price.
@ -67,6 +68,7 @@ func (p ProjectUsagePrice) ToModel() (model payments.ProjectUsagePriceModel, err
StorageMBMonthCents: storageTBMonthDollars.Shift(-6).Shift(2),
EgressMBCents: egressTBDollars.Shift(-6).Shift(2),
SegmentMonthCents: segmentMonthDollars.Shift(2),
EgressDiscountRatio: p.EgressDiscountRatio,
}, nil
}
@ -89,7 +91,8 @@ func (p *ProjectUsagePriceOverrides) String() string {
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))
egressDiscount := strconv.FormatFloat(prices.EgressDiscountRatio, 'f', -1, 64)
s.WriteString(fmt.Sprintf("%s:%s,%s,%s,%s", partner, prices.StorageTB, prices.EgressTB, prices.Segment, egressDiscount))
left--
if left > 0 {
s.WriteRune(';')
@ -116,22 +119,28 @@ func (p *ProjectUsagePriceOverrides) Set(s string) error {
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)
valuesStr := info[1]
values := strings.Split(valuesStr, ",")
if len(values) != 4 {
return Error.New("Invalid values (expected format storage,egress,segment,egress_discount_ratio, got %s)", valuesStr)
}
for _, price := range prices {
if _, err := decimal.NewFromString(price); err != nil {
return Error.New("Invalid price (%s)", err)
for i := 0; i < 3; i++ {
if _, err := decimal.NewFromString(values[i]); err != nil {
return Error.New("Invalid price '%s' (%s)", values[i], err)
}
}
egressDiscount, err := strconv.ParseFloat(values[3], 64)
if err != nil {
return Error.New("Invalid egress discount ratio '%s' (%s)", values[3], err)
}
overrideMap[info[0]] = ProjectUsagePrice{
StorageTB: prices[0],
EgressTB: prices[1],
Segment: prices[2],
StorageTB: values[0],
EgressTB: values[1],
Segment: values[2],
EgressDiscountRatio: egressDiscount,
}
}
p.overrideMap = overrideMap

View File

@ -29,44 +29,47 @@ func TestProjectUsagePriceOverrides(t *testing.T) {
configValue: "",
expectedModel: Prices{},
}, {
testID: "missing prices",
testID: "missing values",
configValue: "partner",
}, {
testID: "missing partner",
configValue: ":1,2,3",
configValue: ":1,2,3,4",
}, {
testID: "too few prices",
testID: "too few values",
configValue: "partner:1",
}, {
testID: "single price override",
configValue: "partner:1,2,3",
configValue: "partner:1,2,3,4",
expectedModel: Prices{
// Shift is to change the precision from TB dollars to MB cents
"partner": payments.ProjectUsagePriceModel{
StorageMBMonthCents: decimal.NewFromInt(1).Shift(-4),
EgressMBCents: decimal.NewFromInt(2).Shift(-4),
SegmentMonthCents: decimal.NewFromInt(3).Shift(2),
EgressDiscountRatio: 4,
},
},
}, {
testID: "too many prices",
configValue: "partner:1,2,3,4",
testID: "too many values",
configValue: "partner:1,2,3,4,5",
}, {
testID: "invalid decimal",
configValue: "partner:0.0.1,2,3",
testID: "invalid price",
configValue: "partner:0.0.1,2,3,4",
}, {
testID: "multiple price overrides",
configValue: "partner1:1,2,3;partner2:4,5,6",
configValue: "partner1:1,2,3,4;partner2:5,6,7,8",
expectedModel: Prices{
"partner1": payments.ProjectUsagePriceModel{
StorageMBMonthCents: decimal.NewFromInt(1).Shift(-4),
EgressMBCents: decimal.NewFromInt(2).Shift(-4),
SegmentMonthCents: decimal.NewFromInt(3).Shift(2),
EgressDiscountRatio: 4,
},
"partner2": payments.ProjectUsagePriceModel{
StorageMBMonthCents: decimal.NewFromInt(4).Shift(-4),
EgressMBCents: decimal.NewFromInt(5).Shift(-4),
SegmentMonthCents: decimal.NewFromInt(6).Shift(2),
StorageMBMonthCents: decimal.NewFromInt(5).Shift(-4),
EgressMBCents: decimal.NewFromInt(6).Shift(-4),
SegmentMonthCents: decimal.NewFromInt(7).Shift(2),
EgressDiscountRatio: 8,
},
},
},
@ -96,6 +99,7 @@ func TestProjectUsagePriceOverrides(t *testing.T) {
require.Equal(t, price.StorageMBMonthCents, model.StorageMBMonthCents)
require.Equal(t, price.EgressMBCents, model.EgressMBCents)
require.Equal(t, price.SegmentMonthCents, model.SegmentMonthCents)
require.Equal(t, price.EgressDiscountRatio, model.EgressDiscountRatio)
}
})
}

View File

@ -34,4 +34,5 @@ type ProjectUsagePriceModel struct {
StorageMBMonthCents decimal.Decimal `json:"storageMBMonthCents"`
EgressMBCents decimal.Decimal `json:"egressMBCents"`
SegmentMonthCents decimal.Decimal `json:"segmentMonthCents"`
EgressDiscountRatio float64 `json:"egressDiscountRatio"`
}

View File

@ -889,7 +889,7 @@ 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
# semicolon-separated usage price overrides in the format partner:storage,egress,segment,egress_discount_ratio. The egress discount ratio is the ratio of free egress per unit-month of storage
# payments.usage-price-overrides: ""
# price user should pay for egress in dollars/TB