openmeter / ledger /chargeadapter /usagebased.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 5)
cee2387 verified
Raw
History Blame Contribute Delete
9.56 kB
package chargeadapter
import (
"context"
"fmt"
"github.com/alpacahq/alpacadecimal"
"github.com/samber/lo"
"github.com/openmeterio/openmeter/openmeter/billing/charges/models/creditrealization"
"github.com/openmeterio/openmeter/openmeter/billing/charges/models/ledgertransaction"
"github.com/openmeterio/openmeter/openmeter/billing/charges/usagebased"
"github.com/openmeterio/openmeter/openmeter/customer"
"github.com/openmeterio/openmeter/openmeter/ledger"
"github.com/openmeterio/openmeter/openmeter/ledger/collector"
"github.com/openmeterio/openmeter/openmeter/ledger/transactions"
"github.com/openmeterio/openmeter/openmeter/productcatalog"
"github.com/openmeterio/openmeter/pkg/currencyx"
)
// usageBasedHandler maps usage-based credit lifecycle events to ledger transaction templates.
type usageBasedHandler struct {
ledger ledger.Ledger
deps transactions.ResolverDependencies
collector collector.Service
}
var _ usagebased.Handler = (*usageBasedHandler)(nil)
func NewUsageBasedHandler(
ledger ledger.Ledger,
deps transactions.ResolverDependencies,
collectorService collector.Service,
) usagebased.Handler {
return &usageBasedHandler{
ledger: ledger,
deps: deps,
collector: collectorService,
}
}
func (h *usageBasedHandler) OnInvoiceUsageAccrued(ctx context.Context, input usagebased.OnInvoiceUsageAccruedInput) (ledgertransaction.GroupReference, error) {
if err := input.Validate(); err != nil {
return ledgertransaction.GroupReference{}, err
}
amount := input.Amount
if amount.IsZero() {
return ledgertransaction.GroupReference{}, nil
}
intent := input.Charge.Intent
taxConfig := intent.GetTaxConfig()
if err := validateSettlementMode(
intent.GetSettlementMode(),
productcatalog.CreditThenInvoiceSettlementMode,
); err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("invoice usage accrued: %w", err)
}
customerID := customer.CustomerID{
Namespace: input.Charge.Namespace,
ID: intent.GetCustomerID(),
}
inputs, err := transactions.ResolveTransactions(
ctx,
h.deps,
transactions.ResolutionScope{
CustomerID: customerID,
Namespace: input.Charge.Namespace,
},
transactions.TransferCustomerReceivableToAccruedTemplate{
At: input.BookedAt,
Amount: amount,
Currency: intent.GetCurrency(),
TaxCode: lo.ToPtr(taxConfig.TaxCodeID),
TaxBehavior: (*ledger.TaxBehavior)(taxConfig.Behavior),
CostBasis: invoiceCostBasis,
SpendChargeID: &input.Charge.ID,
},
)
if err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("resolve transactions: %w", err)
}
transactionGroup, err := h.ledger.CommitGroup(ctx, transactions.GroupInputs(
input.Charge.Namespace,
chargeAnnotationsForUsageBasedCharge(input.Charge),
inputs...,
))
if err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("commit ledger transaction group: %w", err)
}
return ledgertransaction.GroupReference{
TransactionGroupID: transactionGroup.ID().ID,
}, nil
}
func (h *usageBasedHandler) OnPaymentAuthorized(ctx context.Context, input usagebased.OnPaymentAuthorizedInput) (ledgertransaction.GroupReference, error) {
if err := input.Validate(); err != nil {
return ledgertransaction.GroupReference{}, err
}
intent := input.Charge.Intent
if err := validateSettlementMode(
intent.GetSettlementMode(),
productcatalog.CreditThenInvoiceSettlementMode,
); err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("payment authorized: %w", err)
}
receivableReplenishment := alpacadecimal.Zero
if input.Run.InvoiceUsage != nil {
receivableReplenishment = input.Run.InvoiceUsage.Totals.Total
}
if receivableReplenishment.IsZero() {
return ledgertransaction.GroupReference{}, nil
}
customerID := customer.CustomerID{
Namespace: input.Charge.Namespace,
ID: intent.GetCustomerID(),
}
annotations := chargeAnnotationsForUsageBasedCharge(input.Charge)
inputs, err := transactions.ResolveTransactions(
ctx,
h.deps,
transactions.ResolutionScope{
CustomerID: customerID,
Namespace: input.Charge.Namespace,
},
transactions.AuthorizeCustomerReceivablePaymentTemplate{
At: input.EventAt,
Amount: receivableReplenishment,
Currency: intent.GetCurrency(),
CostBasis: invoiceCostBasis,
SpendChargeID: &input.Charge.ID,
},
)
if err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("resolve transactions: %w", err)
}
for i, txInput := range inputs {
if txInput != nil {
inputs[i] = transactions.WithAnnotations(txInput, annotations)
}
}
transactionGroup, err := h.ledger.CommitGroup(ctx, transactions.GroupInputs(
input.Charge.Namespace,
annotations,
inputs...,
))
if err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("commit ledger transaction group: %w", err)
}
return ledgertransaction.GroupReference{
TransactionGroupID: transactionGroup.ID().ID,
}, nil
}
func (h *usageBasedHandler) OnPaymentSettled(ctx context.Context, input usagebased.OnPaymentSettledInput) (ledgertransaction.GroupReference, error) {
if err := input.Validate(); err != nil {
return ledgertransaction.GroupReference{}, err
}
intent := input.Charge.Intent
if err := validateSettlementMode(
intent.GetSettlementMode(),
productcatalog.CreditThenInvoiceSettlementMode,
); err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("payment settled: %w", err)
}
if input.Run.InvoiceUsage == nil || !input.Run.InvoiceUsage.Totals.Total.IsPositive() {
return ledgertransaction.GroupReference{}, nil
}
customerID := customer.CustomerID{
Namespace: input.Charge.Namespace,
ID: intent.GetCustomerID(),
}
annotations := chargeAnnotationsForUsageBasedCharge(input.Charge)
inputs, err := transactions.ResolveTransactions(
ctx,
h.deps,
transactions.ResolutionScope{
CustomerID: customerID,
Namespace: input.Charge.Namespace,
},
transactions.SettleCustomerReceivableFromPaymentTemplate{
At: input.EventAt,
Amount: input.Run.InvoiceUsage.Totals.Total,
Currency: intent.GetCurrency(),
CostBasis: invoiceCostBasis,
SpendChargeID: &input.Charge.ID,
},
)
if err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("resolve transactions: %w", err)
}
for i, txInput := range inputs {
if txInput != nil {
inputs[i] = transactions.WithAnnotations(txInput, annotations)
}
}
transactionGroup, err := h.ledger.CommitGroup(ctx, transactions.GroupInputs(
input.Charge.Namespace,
annotations,
inputs...,
))
if err != nil {
return ledgertransaction.GroupReference{}, fmt.Errorf("commit ledger transaction group: %w", err)
}
return ledgertransaction.GroupReference{
TransactionGroupID: transactionGroup.ID().ID,
}, nil
}
func (h *usageBasedHandler) OnCreditsOnlyUsageAccrued(ctx context.Context, input usagebased.CreditsOnlyUsageAccruedInput) (creditrealization.CreateAllocationInputs, error) {
if err := input.Validate(); err != nil {
return nil, err
}
if input.AmountToAllocate.IsZero() {
return nil, nil
}
intent := input.Charge.Intent
taxConfig := intent.GetTaxConfig()
if err := validateSettlementMode(
intent.GetSettlementMode(),
productcatalog.CreditOnlySettlementMode,
productcatalog.CreditThenInvoiceSettlementMode,
); err != nil {
return nil, fmt.Errorf("credits only usage accrued: %w", err)
}
realizations, err := h.collector.CollectToAccrued(ctx, collector.CollectToAccruedInput{
Namespace: input.Charge.Namespace,
ChargeID: input.Charge.ID,
CustomerID: intent.GetCustomerID(),
Annotations: chargeAnnotationsForUsageBasedCharge(input.Charge),
BookedAt: input.BookedAt,
SourceBalanceAsOf: input.BookedAt,
Currency: intent.GetCurrency(),
FeatureKey: intent.GetFeatureKey(),
TaxCode: lo.ToPtr(taxConfig.TaxCodeID),
TaxBehavior: (*ledger.TaxBehavior)(taxConfig.Behavior),
SettlementMode: intent.GetSettlementMode(),
ServicePeriod: intent.GetEffectiveServicePeriod(),
Amount: input.AmountToAllocate,
})
if err != nil {
return nil, err
}
if len(realizations) == 0 {
return nil, nil
}
return realizations, nil
}
func (h *usageBasedHandler) OnCreditsOnlyUsageAccruedCorrection(ctx context.Context, input usagebased.CreditsOnlyUsageAccruedCorrectionInput) (creditrealization.CreateCorrectionInputs, error) {
intent := input.Charge.Intent
if err := validateSettlementMode(
intent.GetSettlementMode(),
productcatalog.CreditOnlySettlementMode,
productcatalog.CreditThenInvoiceSettlementMode,
); err != nil {
return nil, fmt.Errorf("credits only usage accrued correction: %w", err)
}
currency, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat).
WithCode(intent.GetCurrency()).
Build()
if err != nil {
return nil, fmt.Errorf("get currency calculator: %w", err)
}
if err := input.ValidateWith(currency); err != nil {
return nil, err
}
return h.collector.CorrectCollectedAccrued(ctx, collector.CorrectCollectedAccruedInput{
Namespace: input.Charge.Namespace,
ChargeID: input.Charge.ID,
CustomerID: intent.GetCustomerID(),
Annotations: chargeAnnotationsForUsageBasedCharge(input.Charge),
AllocateAt: input.BookedAt,
Corrections: input.Corrections,
LineageSegmentsByRealization: input.LineageSegmentsByRealization,
})
}