Skip to content

Commit

Permalink
fix: one-time fee support (#2103)
Browse files Browse the repository at this point in the history
  • Loading branch information
turip authored Jan 17, 2025
1 parent 3bdf817 commit 4dbee0f
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 42 deletions.
50 changes: 39 additions & 11 deletions openmeter/billing/worker/subscription/phaseiterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,13 @@ func (it *PhaseIterator) Generate(iterationEnd time.Time) ([]subscriptionItemWit
if err != nil {
return nil, err
}
out = append(out, generatedItem)

if generatedItem == nil {
// One time item is not billable yet, let's skip it
break
}

out = append(out, *generatedItem)
continue
}

Expand Down Expand Up @@ -281,21 +287,43 @@ func (it *PhaseIterator) truncateItemsIfNeeded(in []subscriptionItemWithPeriod)
return out
}

func (it *PhaseIterator) generateOneTimeItem(item subscription.SubscriptionItemView, versionID int) (subscriptionItemWithPeriod, error) {
func (it *PhaseIterator) generateOneTimeItem(item subscription.SubscriptionItemView, versionID int) (*subscriptionItemWithPeriod, error) {
period := billing.Period{
Start: item.SubscriptionItem.ActiveFrom,
}

end := lo.CoalesceOrEmpty(item.SubscriptionItem.ActiveTo, it.phaseCadence.ActiveTo)
if end == nil {
// TODO[later]: implement open ended gathering line items, as that's a valid use case to for example:
// Have a plan, that has an open ended billing item for flat fee, then the end user uses progressive billing
// to bill the end user if the usage gets above $1000. Non-gathering lines must have a period end.
return subscriptionItemWithPeriod{}, fmt.Errorf("cannot determine phase end for item %s", item.Spec.ItemKey)
}
// One time items are not usage based, so the price object will be a flat price
price := item.SubscriptionItem.RateCard.Price

period := billing.Period{
Start: item.SubscriptionItem.ActiveFrom,
End: *end,
if price == nil {
// If an item has no price it is not in scope for line generation
return nil, nil
}

if price.Type() != productcatalog.FlatPriceType {
return nil, fmt.Errorf("cannot determine period end for one-time item %s", item.Spec.ItemKey)
}

flatFee, err := item.SubscriptionItem.RateCard.Price.AsFlat()
if err != nil {
return nil, err
}

if flatFee.PaymentTerm == productcatalog.InArrearsPaymentTerm {
// If the item is InArrears but we cannot determine when that time is, let's just skip this item until we
// can determine the end of period
return nil, nil
}

// For in-advance fees we just specify an empty period, which is fine for non UBP items
period.End = item.SubscriptionItem.ActiveFrom
} else {
period.End = *end
}

return subscriptionItemWithPeriod{
return &subscriptionItemWithPeriod{
SubscriptionItemView: item,
Period: period,
NonTruncatedPeriod: period,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ func (s *PhaseIteratorTestSuite) TestPhaseIterator() {
Type: productcatalog.FlatPriceType,
},
},
expectError: true,
expectError: false,
},
}

Expand Down
Loading

0 comments on commit 4dbee0f

Please sign in to comment.