Skip to content

Commit

Permalink
fix: progressive billing billed tier 0 multiple times
Browse files Browse the repository at this point in the history
  • Loading branch information
turip committed Jan 20, 2025
1 parent a2ade61 commit 167a934
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 95 deletions.
20 changes: 17 additions & 3 deletions openmeter/billing/service/lineservice/usagebasedline.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ func (l usageBasedLine) calculateGraduatedTieredPriceDetailedLines(usage *featur

tierIndex := in.TierIndex + 1

if in.Tier.UnitPrice != nil {
if in.Tier.UnitPrice != nil && in.Quantity.IsPositive() {
newLine := newDetailedLineInput{
Name: fmt.Sprintf("%s: usage price for tier %d", l.line.Name, tierIndex),
Quantity: in.Quantity,
Expand All @@ -474,8 +474,11 @@ func (l usageBasedLine) calculateGraduatedTieredPriceDetailedLines(usage *featur
out = append(out, newLine)
}

// If have already billed this flat price for the previous split line, so we can skip it
shouldFirstFlatLineBeBilled := in.TierIndex > 0 || l.IsFirstInPeriod()

// Flat price is always billed for the whole tier when we are crossing the tier boundary
if in.Tier.FlatPrice != nil && in.AtTierBoundary {
if in.Tier.FlatPrice != nil && in.AtTierBoundary && shouldFirstFlatLineBeBilled {
newLine := newDetailedLineInput{
Name: fmt.Sprintf("%s: flat price for tier %d", l.line.Name, tierIndex),
Quantity: alpacadecimal.NewFromFloat(1),
Expand Down Expand Up @@ -650,7 +653,7 @@ func tieredPriceCalculator(in tieredPriceCalculatorInput) error {

previousTierQty := alpacadecimal.Zero
for idx, tier := range in.TieredPrice.WithSortedTiers().Tiers {
if previousTierQty.GreaterThan(in.ToQty) {
if previousTierQty.GreaterThanOrEqual(in.ToQty) {
// We already have enough data to bill for this tiered price
break
}
Expand All @@ -675,6 +678,17 @@ func tieredPriceCalculator(in tieredPriceCalculatorInput) error {
previousTierQty = tierUpperBound
}

if in.ToQty.Equal(alpacadecimal.Zero) {
// We need to add the first range, in case there's a flat price component
qtyRanges = append(qtyRanges, tierRange{
Tier: in.TieredPrice.Tiers[0],
TierIndex: 0,
AtTierBoundary: true,
FromQty: alpacadecimal.Zero,
ToQty: alpacadecimal.Zero,
})
}

if in.IntrospectRangesFn != nil {
in.IntrospectRangesFn(qtyRanges)
}
Expand Down
21 changes: 4 additions & 17 deletions openmeter/billing/service/lineservice/usagebasedline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -839,15 +839,7 @@ func TestTieredGraduatedCalculation(t *testing.T) {
usage: featureUsageResponse{
LinePeriodQty: alpacadecimal.NewFromFloat(0),
},
expect: newDetailedLinesInput{
{
Name: "feature: flat price for tier 1",
PerUnitAmount: alpacadecimal.NewFromFloat(100),
Quantity: alpacadecimal.NewFromFloat(1),
ChildUniqueReferenceID: "graduated-tiered-1-flat-price",
PaymentTerm: productcatalog.InArrearsPaymentTerm,
},
},
expect: newDetailedLinesInput{},
})
})

Expand Down Expand Up @@ -940,14 +932,9 @@ func TestTieredGraduatedCalculation(t *testing.T) {
},
expect: newDetailedLinesInput{
{
Name: "feature: flat price for tier 1",
PerUnitAmount: alpacadecimal.NewFromFloat(100),
Quantity: alpacadecimal.NewFromFloat(1),
ChildUniqueReferenceID: "graduated-tiered-1-flat-price",
PaymentTerm: productcatalog.InArrearsPaymentTerm,
},
{
Name: "feature: minimum spend",
Name: "feature: minimum spend",
// We have a flat fee of 100 for tier 1, and given that it was invoiced as part of the previous split we need to remove
// that from the charge.
PerUnitAmount: alpacadecimal.NewFromFloat(900),
Quantity: alpacadecimal.NewFromFloat(1),
ChildUniqueReferenceID: GraduatedMinSpendChildUniqueReferenceID,
Expand Down
Loading

0 comments on commit 167a934

Please sign in to comment.