Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 52 additions & 22 deletions app/common/billing.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/openmeterio/openmeter/openmeter/billing/charges/flatfee"
"github.com/openmeterio/openmeter/openmeter/billing/charges/usagebased"
chargesworkeradvance "github.com/openmeterio/openmeter/openmeter/billing/charges/worker/advance"
billinglineengine "github.com/openmeterio/openmeter/openmeter/billing/lineengine"
"github.com/openmeterio/openmeter/openmeter/billing/rating"
billingratingservice "github.com/openmeterio/openmeter/openmeter/billing/rating/service"
billingsequence "github.com/openmeterio/openmeter/openmeter/billing/sequence"
Expand Down Expand Up @@ -48,9 +49,10 @@ import (
// BillingRegistry bundles the billing and charges services. External callers that need
// billing or charges should depend on BillingRegistry rather than individual services.
type BillingRegistry struct {
Billing billing.Service
Sequence billingsequence.Service
Charges *ChargesRegistry
Billing billing.Service
LegacyBillingLineEngine *billinglineengine.Engine
Sequence billingsequence.Service
Charges *ChargesRegistry
}

func (r BillingRegistry) ChargesServiceOrNil() charges.Service {
Expand Down Expand Up @@ -109,18 +111,34 @@ func NewBillingSequenceService(
})
}

func NewLegacyBillingLineEngine(
billingAdapter billing.Adapter,
billingRatingService rating.Service,
featureConnector feature.FeatureConnector,
streamingConnector streaming.Connector,
billingConfig config.BillingConfiguration,
) (*billinglineengine.Engine, error) {
return billinglineengine.New(billinglineengine.Config{
SplitLineGroupAdapter: billingAdapter,
RatingService: billingRatingService,
FeatureService: featureConnector,
StreamingConnector: streamingConnector,
MaxParallelQuantitySnapshots: billingConfig.MaxParallelQuantitySnapshots,
})
}

// newBillingService creates the billing service and registers validators/hooks.
// Downstream consumers should use BillingRegistry.
func newBillingService(
logger *slog.Logger,
appService app.Service,
billingAdapter billing.Adapter,
billingRatingService rating.Service,
legacyBillingLineEngine *billinglineengine.Engine,
sequenceService billingsequence.Service,
customerService customer.Service,
featureConnector feature.FeatureConnector,
meterService meter.Service,
streamingConnector streaming.Connector,
eventPublisher eventbus.Publisher,
billingConfig config.BillingConfiguration,
subscriptionServices SubscriptionServiceWithWorkflow,
Expand All @@ -130,20 +148,19 @@ func newBillingService(
taxCodeService taxcode.Service,
) (billing.Service, error) {
service, err := billingservice.New(billingservice.Config{
Adapter: billingAdapter,
SequenceService: sequenceService,
RatingService: billingRatingService,
AppService: appService,
CustomerService: customerService,
TaxCodeService: taxCodeService,
FeatureService: featureConnector,
Logger: logger,
MeterService: meterService,
StreamingConnector: streamingConnector,
Publisher: eventPublisher,
AdvancementStrategy: billingConfig.AdvancementStrategy,
FSNamespaceLockdown: fsConfig.NamespaceLockdown,
MaxParallelQuantitySnapshots: billingConfig.MaxParallelQuantitySnapshots,
Adapter: billingAdapter,
SequenceService: sequenceService,
RatingService: billingRatingService,
LegacyBillingLineEngine: legacyBillingLineEngine,
AppService: appService,
CustomerService: customerService,
TaxCodeService: taxCodeService,
FeatureService: featureConnector,
Logger: logger,
MeterService: meterService,
Publisher: eventPublisher,
AdvancementStrategy: billingConfig.AdvancementStrategy,
FSNamespaceLockdown: fsConfig.NamespaceLockdown,
})
if err != nil {
return nil, err
Expand Down Expand Up @@ -184,16 +201,27 @@ func NewBillingRegistry(
return BillingRegistry{}, err
}

legacyBillingLineEngine, err := NewLegacyBillingLineEngine(
billingAdapter,
billingRatingService,
featureConnector,
streamingConnector,
billingConfig,
)
if err != nil {
return BillingRegistry{}, err
}

billingService, err := newBillingService(
logger,
appService,
billingAdapter,
billingRatingService,
legacyBillingLineEngine,
sequenceService,
customerService,
featureConnector,
meterService,
streamingConnector,
eventPublisher,
billingConfig,
subscriptionServices,
Expand Down Expand Up @@ -233,9 +261,10 @@ func NewBillingRegistry(
}

billingRegistry := BillingRegistry{
Billing: billingService,
Sequence: sequenceService,
Charges: chargesRegistry,
Billing: billingService,
LegacyBillingLineEngine: legacyBillingLineEngine,
Sequence: sequenceService,
Charges: chargesRegistry,
}

// Hook registration
Expand Down Expand Up @@ -330,6 +359,7 @@ func NewBillingSubscriptionSyncService(logger *slog.Logger, subsServices Subscri
return subscriptionsyncservice.New(subscriptionsyncservice.Config{
SubscriptionService: subsServices.Service,
BillingService: billingRegistry.Billing,
LegacyBillingLineEngine: billingRegistry.LegacyBillingLineEngine,
ChargesService: billingRegistry.ChargesServiceOrNil(),
SubscriptionSyncAdapter: subscriptionSyncAdapter,
FeatureFlags: subscriptionsyncservice.FeatureFlags{
Expand Down
46 changes: 31 additions & 15 deletions openmeter/billing/lineengine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/openmeterio/openmeter/openmeter/billing"
"github.com/openmeterio/openmeter/openmeter/billing/rating"
"github.com/openmeterio/openmeter/openmeter/productcatalog"
"github.com/openmeterio/openmeter/openmeter/productcatalog/feature"
"github.com/openmeterio/openmeter/openmeter/streaming"
"github.com/openmeterio/openmeter/pkg/clock"
"github.com/openmeterio/openmeter/pkg/equal"
"github.com/openmeterio/openmeter/pkg/slicesx"
Expand All @@ -20,31 +22,43 @@ var (
)

type Config struct {
SplitLineGroupAdapter SplitLineGroupAdapter
QuantitySnapshotter QuantitySnapshotter
RatingService rating.Service
SplitLineGroupAdapter SplitLineGroupAdapter
RatingService rating.Service
FeatureService feature.FeatureConnector
StreamingConnector streaming.Connector
MaxParallelQuantitySnapshots int
}

func (c Config) Validate() error {
if c.SplitLineGroupAdapter == nil {
return fmt.Errorf("split line group adapter is required")
}

if c.QuantitySnapshotter == nil {
return fmt.Errorf("quantity snapshotter is required")
}

if c.RatingService == nil {
return fmt.Errorf("rating service is required")
}

if c.FeatureService == nil {
return fmt.Errorf("feature service is required")
}

if c.StreamingConnector == nil {
return fmt.Errorf("streaming connector is required")
}

if c.MaxParallelQuantitySnapshots < 1 {
return fmt.Errorf("max parallel quantity snapshots must be greater than 0")
}

return nil
}

type Engine struct {
adapter SplitLineGroupAdapter
quantitySnapshotter QuantitySnapshotter
ratingService rating.Service
adapter SplitLineGroupAdapter
ratingService rating.Service
featureService feature.FeatureConnector
streamingConnector streaming.Connector
maxParallelQuantitySnapshots int
}

func New(config Config) (*Engine, error) {
Expand All @@ -53,9 +67,11 @@ func New(config Config) (*Engine, error) {
}

return &Engine{
adapter: config.SplitLineGroupAdapter,
quantitySnapshotter: config.QuantitySnapshotter,
ratingService: config.RatingService,
adapter: config.SplitLineGroupAdapter,
ratingService: config.RatingService,
featureService: config.FeatureService,
streamingConnector: config.StreamingConnector,
maxParallelQuantitySnapshots: config.MaxParallelQuantitySnapshots,
}, nil
}

Expand All @@ -79,7 +95,7 @@ func (e *Engine) OnCollectionCompleted(ctx context.Context, input billing.OnColl
return input.Lines, nil
}

if err := e.quantitySnapshotter.SnapshotLineQuantities(ctx, input.Invoice, input.Lines); err != nil {
if err := e.SnapshotLineQuantities(ctx, input.Invoice, input.Lines); err != nil {
if _, isInvalidDatabaseState := lo.ErrorsAs[*billing.ErrSnapshotInvalidDatabaseState](err); isInvalidDatabaseState {
return nil, billing.ValidationIssue{
Severity: billing.ValidationIssueSeverityCritical,
Expand Down Expand Up @@ -174,7 +190,7 @@ func (e *Engine) snapshotManualStandardLineOverrideIfNeeded(ctx context.Context,
return nil, fmt.Errorf("getting standard line: %w", err)
}

if err := e.quantitySnapshotter.SnapshotLineQuantities(ctx, standardInvoice, billing.StandardLines{&standardLine}); err != nil {
if err := e.SnapshotLineQuantities(ctx, standardInvoice, billing.StandardLines{&standardLine}); err != nil {
return nil, fmt.Errorf("snapshotting line quantity: %w", err)
}

Expand Down
Loading
Loading