| package service |
|
|
| import ( |
| "context" |
| "fmt" |
| "time" |
|
|
| "github.com/samber/lo" |
|
|
| "github.com/openmeterio/openmeter/openmeter/billing" |
| "github.com/openmeterio/openmeter/openmeter/billing/charges/meta" |
| "github.com/openmeterio/openmeter/openmeter/billing/charges/usagebased" |
| "github.com/openmeterio/openmeter/openmeter/productcatalog" |
| "github.com/openmeterio/openmeter/pkg/framework/transaction" |
| "github.com/openmeterio/openmeter/pkg/models" |
| "github.com/openmeterio/openmeter/pkg/slicesx" |
| "github.com/openmeterio/openmeter/pkg/timeutil" |
| ) |
|
|
| func (s *service) Create(ctx context.Context, input usagebased.CreateInput) ([]usagebased.ChargeWithGatheringLine, error) { |
| if err := input.Validate(); err != nil { |
| return nil, err |
| } |
|
|
| if len(input.Intents) == 0 { |
| return nil, nil |
| } |
|
|
| return transaction.Run(ctx, s.adapter, func(ctx context.Context) ([]usagebased.ChargeWithGatheringLine, error) { |
| createIntents, err := slicesx.MapWithErr(input.Intents, func(intent usagebased.Intent) (usagebased.CreateIntent, error) { |
| chargeIntent := intent.Normalized() |
|
|
| featureMeter, err := input.FeatureMeters.Get(chargeIntent.FeatureKey, false) |
| if err != nil { |
| return usagebased.CreateIntent{}, fmt.Errorf("resolve usage based feature for key %s: %w", chargeIntent.FeatureKey, err) |
| } |
|
|
| return usagebased.CreateIntent{ |
| Intent: chargeIntent.AsOverridableIntent(), |
| Annotations: chargeIntent.Annotations, |
| FeatureID: featureMeter.Feature.ID, |
| RatingEngine: s.rater.GetPreferredRatingEngineFor(chargeIntent), |
| }, nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
|
|
| |
| charges, err := s.adapter.CreateCharges(ctx, usagebased.CreateChargesInput{ |
| Namespace: input.Namespace, |
| Intents: createIntents, |
| }) |
| if err != nil { |
| return nil, err |
| } |
|
|
| err = s.metaAdapter.RegisterCharges(ctx, meta.RegisterChargesInput{ |
| Namespace: input.Namespace, |
| Type: meta.ChargeTypeUsageBased, |
| Charges: lo.Map(charges, func(charge usagebased.Charge, idx int) meta.IDWithUniqueReferenceID { |
| return meta.IDWithUniqueReferenceID{ |
| ID: charge.ID, |
| UniqueReferenceID: charge.Intent.GetUniqueReferenceID(), |
| } |
| }), |
| }) |
| if err != nil { |
| return nil, err |
| } |
|
|
| return slicesx.MapWithErr(charges, func(charge usagebased.Charge) (usagebased.ChargeWithGatheringLine, error) { |
| |
| if charge.Intent.GetSettlementMode() == productcatalog.CreditOnlySettlementMode { |
| return usagebased.ChargeWithGatheringLine{ |
| Charge: charge, |
| }, nil |
| } |
|
|
| return gatheringLineFromUsageBasedChargeForPeriod(charge, charge.Intent.GetEffectiveServicePeriod(), charge.Intent.GetEffectiveInvoiceAt()) |
| }) |
| }) |
| } |
|
|
| func gatheringLineFromUsageBasedChargeForPeriod(charge usagebased.Charge, servicePeriod timeutil.ClosedPeriod, invoiceAt time.Time) (usagebased.ChargeWithGatheringLine, error) { |
| intent := charge.Intent.GetEffectiveIntent() |
|
|
| var subscription *billing.SubscriptionReference |
| if intent.Subscription != nil { |
| subscription = &billing.SubscriptionReference{ |
| SubscriptionID: intent.Subscription.SubscriptionID, |
| PhaseID: intent.Subscription.PhaseID, |
| ItemID: intent.Subscription.ItemID, |
| BillingPeriod: timeutil.ClosedPeriod{ |
| From: intent.BillingPeriod.From, |
| To: intent.BillingPeriod.To, |
| }, |
| } |
| } |
|
|
| clonedAnnotations, err := intent.Annotations.Clone() |
| if err != nil { |
| return usagebased.ChargeWithGatheringLine{}, fmt.Errorf("cloning annotations: %w", err) |
| } |
|
|
| var unitConfig *productcatalog.UnitConfig |
| if intent.UnitConfig != nil { |
| unitConfig = lo.ToPtr(intent.UnitConfig.Clone()) |
| } |
|
|
| gatheringLine := billing.GatheringLine{ |
| GatheringLineBase: billing.GatheringLineBase{ |
| ManagedResource: models.NewManagedResource(models.ManagedResourceInput{ |
| Namespace: charge.Namespace, |
| Name: intent.Name, |
| Description: intent.Description, |
| }), |
|
|
| Metadata: intent.Metadata.Clone(), |
| Annotations: clonedAnnotations, |
| ManagedBy: intent.ManagedBy, |
|
|
| Price: intent.Price, |
| FeatureKey: intent.FeatureKey, |
| UnitConfig: unitConfig, |
|
|
| Currency: intent.Currency, |
| ServicePeriod: servicePeriod, |
| InvoiceAt: invoiceAt, |
|
|
| TaxConfig: lo.ToPtr(intent.TaxConfig.ToTaxConfig()), |
|
|
| ChargeID: lo.ToPtr(charge.ID), |
| Engine: billing.LineEngineTypeChargeUsageBased, |
| Subscription: subscription, |
|
|
| RateCardDiscounts: intent.Discounts.Clone(), |
| }, |
| } |
|
|
| return usagebased.ChargeWithGatheringLine{ |
| Charge: charge, |
| GatheringLineToCreate: &gatheringLine, |
| }, nil |
| } |
|
|