@@ -12,6 +12,7 @@ import (
1212 "github.com/openmeterio/openmeter/openmeter/billing/charges/models/creditrealization"
1313 "github.com/openmeterio/openmeter/openmeter/billing/charges/models/ledgertransaction"
1414 "github.com/openmeterio/openmeter/openmeter/billing/models/totals"
15+ "github.com/openmeterio/openmeter/openmeter/currencies"
1516 "github.com/openmeterio/openmeter/pkg/currencyx"
1617 "github.com/openmeterio/openmeter/pkg/models"
1718 "github.com/openmeterio/openmeter/pkg/timeutil"
@@ -77,6 +78,85 @@ func (i OnInvoiceUsageAccruedInput) Validate() error {
7778 return models .NewNillableGenericValidationError (errors .Join (errs ... ))
7879}
7980
81+ type OnCustomCurrencyOverageAccruedInput struct {
82+ Charge Charge `json:"charge"`
83+ Run RealizationRun `json:"run"`
84+ }
85+
86+ func (i OnCustomCurrencyOverageAccruedInput ) CustomCurrency () currencies.Currency {
87+ return i .Charge .Intent .GetCurrency ()
88+ }
89+
90+ func (i OnCustomCurrencyOverageAccruedInput ) GetFiatCurrency () (* currencyx.FiatCurrency , error ) {
91+ costBasisIntent := i .Charge .Intent .GetCostBasisIntent ()
92+ if costBasisIntent == nil {
93+ return nil , fmt .Errorf ("cost basis intent is required" )
94+ }
95+
96+ return costBasisIntent .GetFiatCurrency ()
97+ }
98+
99+ func (i OnCustomCurrencyOverageAccruedInput ) GetCostBasis () (alpacadecimal.Decimal , error ) {
100+ if i .Charge .State .ResolvedCostBasis == nil {
101+ return alpacadecimal.Decimal {}, fmt .Errorf ("cost basis is not resolved" )
102+ }
103+
104+ return i .Charge .State .ResolvedCostBasis .CostBasis , nil
105+ }
106+
107+ func (i OnCustomCurrencyOverageAccruedInput ) GetCustomCurrencyAmountAccrued () alpacadecimal.Decimal {
108+ return i .Run .Totals .Total
109+ }
110+
111+ func (i OnCustomCurrencyOverageAccruedInput ) Validate () error {
112+ var errs []error
113+
114+ if err := i .Charge .Validate (); err != nil {
115+ errs = append (errs , fmt .Errorf ("charge: %w" , err ))
116+ }
117+
118+ if err := i .Run .Validate (); err != nil {
119+ errs = append (errs , fmt .Errorf ("run: %w" , err ))
120+ }
121+
122+ if err := i .CustomCurrency ().Validate (); err != nil {
123+ errs = append (errs , fmt .Errorf ("custom currency: %w" , err ))
124+ }
125+
126+ if ! i .CustomCurrency ().IsCustom () {
127+ errs = append (errs , fmt .Errorf ("custom currency must be custom typed currency" ))
128+ }
129+
130+ if ! i .GetCustomCurrencyAmountAccrued ().IsPositive () {
131+ errs = append (errs , fmt .Errorf ("amount must be positive" ))
132+ }
133+
134+ if _ , err := i .GetCostBasis (); err != nil {
135+ errs = append (errs , fmt .Errorf ("cost basis: %w" , err ))
136+ }
137+
138+ return models .NewNillableGenericValidationError (errors .Join (errs ... ))
139+ }
140+
141+ type OnCustomCurrencyOverageAccruedResult struct {
142+ TransactionGroup ledgertransaction.GroupReference `json:"transactionGroup"`
143+ TotalFiatAmount alpacadecimal.Decimal `json:"totalFiatAmount"`
144+ }
145+
146+ func (r OnCustomCurrencyOverageAccruedResult ) Validate () error {
147+ var errs []error
148+
149+ if err := r .TransactionGroup .Validate (); err != nil {
150+ errs = append (errs , fmt .Errorf ("transaction group: %w" , err ))
151+ }
152+
153+ if r .TotalFiatAmount .IsNegative () {
154+ errs = append (errs , fmt .Errorf ("total fiat amount cannot be negative" ))
155+ }
156+
157+ return models .NewNillableGenericValidationError (errors .Join (errs ... ))
158+ }
159+
80160type CorrectCreditAllocationsInput struct {
81161 Charge Charge `json:"charge"`
82162 BookedAt time.Time `json:"bookedAt"`
@@ -114,9 +194,10 @@ func (i CorrectCreditAllocationsInput) ValidateWith(currencyCalculator currencyx
114194}
115195
116196type PaymentEventInput struct {
117- Charge Charge `json:"charge"`
118- EventAt time.Time `json:"eventAt"`
119- Amount alpacadecimal.Decimal `json:"amount"`
197+ Charge Charge `json:"charge"`
198+ Run RealizationRun `json:"run"`
199+ EventAt time.Time `json:"eventAt"`
200+ FiatAmount alpacadecimal.Decimal `json:"fiatAmount"`
120201}
121202
122203func (i PaymentEventInput ) Validate () error {
@@ -126,12 +207,16 @@ func (i PaymentEventInput) Validate() error {
126207 errs = append (errs , fmt .Errorf ("charge: %w" , err ))
127208 }
128209
210+ if err := i .Run .Validate (); err != nil {
211+ errs = append (errs , fmt .Errorf ("run: %w" , err ))
212+ }
213+
129214 if i .EventAt .IsZero () {
130215 errs = append (errs , fmt .Errorf ("event at is required" ))
131216 }
132217
133- if i . Amount . IsNegative () {
134- errs = append (errs , fmt .Errorf ("amount cannot be negative " ))
218+ if ! i . FiatAmount . IsPositive () {
219+ errs = append (errs , fmt .Errorf ("fiat amount must be positive " ))
135220 }
136221
137222 return models .NewNillableGenericValidationError (errors .Join (errs ... ))
@@ -149,6 +234,10 @@ type Handler interface {
149234 // OnFlatFeeStandardInvoiceUsageAccrued is called when the remaining usage is sent to the customer on a standard invoice.
150235 OnInvoiceUsageAccrued (ctx context.Context , input OnInvoiceUsageAccruedInput ) (ledgertransaction.GroupReference , error )
151236
237+ // OnCustomCurrencyOverageAccrued is called when uncovered custom-currency flat-fee value is accrued in fiat.
238+ // This must be modeled as a credit purchase flow from the ledger point of view.
239+ OnCustomCurrencyOverageAccrued (ctx context.Context , input OnCustomCurrencyOverageAccruedInput ) (OnCustomCurrencyOverageAccruedResult , error )
240+
152241 // OnCorrectCreditAllocations is called when a credit allocation needs to be corrected.
153242 OnCorrectCreditAllocations (ctx context.Context , input CorrectCreditAllocationsInput ) (creditrealization.CreateCorrectionInputs , error )
154243
0 commit comments