Skip to content

Commit b3835db

Browse files
authored
refactor(billing): use fiat currency for invoices (#4774)
1 parent 7e5a848 commit b3835db

75 files changed

Lines changed: 356 additions & 279 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

openmeter/app/stripe/calculator.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@ import (
1212
)
1313

1414
// NewStripeCalculator creates a new StripeCalculator.
15-
func NewStripeCalculator(currencyCode currencyx.Code) (StripeCalculator, error) {
16-
currency, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat).
17-
WithCode(currencyCode).
18-
Build()
15+
func NewStripeCalculator(currencyCode currencyx.FiatCode) (StripeCalculator, error) {
16+
currency, err := currencyCode.AsFiatCurrency()
1917
if err != nil {
2018
return StripeCalculator{}, fmt.Errorf("failed to get stripe calculator: %w", err)
2119
}

openmeter/app/stripe/client/invoice.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ type CreateInvoiceInput struct {
144144
InvoiceID string
145145
AutomaticTaxEnabled bool
146146
CollectionMethod billing.CollectionMethod
147-
Currency currencyx.Code
147+
Currency currencyx.FiatCode
148148
DaysUntilDue *int64
149149
StatementDescriptor *string
150150
StripeCustomerID string

openmeter/billing/charges/creditpurchase/service/create.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,8 @@ func (s *service) buildInvoiceCreditPurchaseGatheringLine(charge creditpurchase.
9595

9696
// Total cost = credit amount * cost basis (e.g., 100 credits * $0.5 = $50)
9797
totalCost := intent.CreditAmount.Mul(invoiceSettlement.CostBasis)
98-
calc, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat).
99-
WithCode(invoiceSettlement.Currency).
100-
Build()
98+
invoiceCurrency := currencyx.FiatCode(invoiceSettlement.Currency)
99+
calc, err := invoiceCurrency.AsFiatCurrency()
101100
if err != nil {
102101
return billing.GatheringLine{}, fmt.Errorf("creating currency calculator: %w", err)
103102
}
@@ -134,7 +133,7 @@ func (s *service) buildInvoiceCreditPurchaseGatheringLine(charge creditpurchase.
134133
},
135134
),
136135
),
137-
Currency: invoiceSettlement.Currency,
136+
Currency: invoiceCurrency,
138137
ServicePeriod: intent.ServicePeriod,
139138
InvoiceAt: intent.CalculateEffectiveAt(),
140139
TaxConfig: lo.ToPtr(intent.TaxConfig.ToTaxConfig()),

openmeter/billing/charges/flatfee/charge.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/openmeterio/openmeter/openmeter/currencies"
1616
"github.com/openmeterio/openmeter/openmeter/customer"
1717
"github.com/openmeterio/openmeter/openmeter/productcatalog"
18+
"github.com/openmeterio/openmeter/pkg/currencyx"
1819
"github.com/openmeterio/openmeter/pkg/models"
1920
"github.com/openmeterio/openmeter/pkg/timeutil"
2021
)
@@ -81,6 +82,25 @@ func (c ChargeBase) GetCurrency() currencies.Currency {
8182
return c.Intent.GetCurrency()
8283
}
8384

85+
func (c ChargeBase) GetInvoiceCurrency() (currencyx.FiatCode, error) {
86+
currency := c.GetCurrency()
87+
if currency.IsFiat() {
88+
return currencyx.FiatCode(currency.GetCode()), nil
89+
}
90+
91+
costBasisIntent := c.Intent.GetCostBasisIntent()
92+
if costBasisIntent == nil {
93+
return "", errors.New("cost basis intent is required for a custom-currency invoice")
94+
}
95+
96+
fiatCurrency, err := costBasisIntent.GetFiatCurrency()
97+
if err != nil {
98+
return "", fmt.Errorf("getting cost basis fiat currency: %w", err)
99+
}
100+
101+
return fiatCurrency.GetFiatCode(), nil
102+
}
103+
84104
func (c ChargeBase) ErrorAttributes() models.Attributes {
85105
return models.Attributes{
86106
"charge_id": c.ID,

openmeter/billing/charges/flatfee/service/create.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ func buildFlatFeeGatheringLine(input buildFlatFeeGatheringLineInput) (billing.Ga
195195
managedBy = billing.ManuallyManagedLine
196196
}
197197

198+
invoiceCurrency, err := flatFee.GetInvoiceCurrency()
199+
if err != nil {
200+
return billing.GatheringLine{}, fmt.Errorf("getting invoice currency: %w", err)
201+
}
202+
198203
gatheringLine := billing.GatheringLine{
199204
GatheringLineBase: billing.GatheringLineBase{
200205
ManagedResource: models.NewManagedResource(models.ManagedResourceInput{
@@ -217,7 +222,7 @@ func buildFlatFeeGatheringLine(input buildFlatFeeGatheringLineInput) (billing.Ga
217222
),
218223
FeatureKey: lo.FromPtr(lineIntent.FeatureKey),
219224

220-
Currency: lineIntent.Currency.GetCode(),
225+
Currency: invoiceCurrency,
221226
ServicePeriod: lineIntent.ServicePeriod,
222227
InvoiceAt: lineIntent.InvoiceAt,
223228

openmeter/billing/charges/flatfee/service/linemapper.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,10 @@ import (
88

99
"github.com/openmeterio/openmeter/openmeter/billing"
1010
"github.com/openmeterio/openmeter/openmeter/billing/charges/flatfee"
11-
"github.com/openmeterio/openmeter/pkg/currencyx"
1211
)
1312

1413
func populateFlatFeeStandardLineFromRun(stdLine *billing.StandardLine, run flatfee.RealizationRun) error {
15-
currency, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat).
16-
WithCode(stdLine.Currency).
17-
Build()
14+
currency, err := stdLine.Currency.AsFiatCurrency()
1815
if err != nil {
1916
return fmt.Errorf("creating currency calculator: %w", err)
2017
}

openmeter/billing/charges/flatfee/service/manualedit.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,11 @@ func intentFromManualCreatedLine(
7878
return flatfee.Intent{}, fmt.Errorf("line id is required")
7979
}
8080

81-
currency, err := currencies.NewFiatCurrency(line.GetCurrency())
81+
fiatCurrency, err := line.GetCurrency().AsFiatCurrency()
8282
if err != nil {
8383
return flatfee.Intent{}, fmt.Errorf("resolving fiat currency %q: %w", line.GetCurrency(), err)
8484
}
85+
currency := currencies.Currency{Currency: fiatCurrency}
8586

8687
if chargeID := line.GetChargeID(); chargeID != nil && *chargeID != "" {
8788
return flatfee.Intent{}, fmt.Errorf("line[%s]: charge id must be empty for manual create", line.GetID())

openmeter/billing/charges/invoiceupdater/invoiceupdate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ func (u *updater) provisionUpcomingLines(ctx context.Context, customerID custome
320320
return nil
321321
}
322322

323-
linesByCurrency := lo.GroupBy(lines, func(l billing.GatheringLine) currencyx.Code {
323+
linesByCurrency := lo.GroupBy(lines, func(l billing.GatheringLine) currencyx.FiatCode {
324324
return l.Currency
325325
})
326326

openmeter/billing/charges/models/costbasis/intent.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,36 @@ func (i Intent) AsDynamic() (DynamicIntent, error) {
130130
return *i.dynamic, nil
131131
}
132132

133+
// GetFiatCurrency returns the fiat currency in which the custom-currency cost
134+
// basis is expressed, regardless of the selected resolution mode.
135+
func (i Intent) GetFiatCurrency() (*currencyx.FiatCurrency, error) {
136+
switch i.kind {
137+
case ModeDynamic:
138+
intent, err := i.AsDynamic()
139+
if err != nil {
140+
return nil, err
141+
}
142+
143+
return intent.FiatCurrency, nil
144+
case ModePinned:
145+
intent, err := i.AsPinned()
146+
if err != nil {
147+
return nil, err
148+
}
149+
150+
return intent.FiatCurrency, nil
151+
case ModeManual:
152+
intent, err := i.AsManual()
153+
if err != nil {
154+
return nil, err
155+
}
156+
157+
return intent.FiatCurrency, nil
158+
default:
159+
return nil, models.NewGenericValidationError(fmt.Errorf("invalid intent kind: %s", i.kind))
160+
}
161+
}
162+
133163
type DynamicIntent struct {
134164
FiatCurrency *currencyx.FiatCurrency
135165
}

openmeter/billing/charges/service/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ type currencyAndCustomerID struct {
394394
}
395395

396396
type currencyCodeAndCustomerID struct {
397-
currency currencyx.Code
397+
currency currencyx.FiatCode
398398
customerID customer.CustomerID
399399
}
400400

0 commit comments

Comments
 (0)