File size: 6,613 Bytes
5a22efd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package usagebased

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/alpacahq/alpacadecimal"

	"github.com/openmeterio/openmeter/openmeter/billing/charges/lineage"
	"github.com/openmeterio/openmeter/openmeter/billing/charges/models/creditrealization"
	"github.com/openmeterio/openmeter/openmeter/billing/charges/models/ledgertransaction"
	"github.com/openmeterio/openmeter/pkg/currencyx"
	"github.com/openmeterio/openmeter/pkg/models"
	"github.com/openmeterio/openmeter/pkg/timeutil"
)

type CreditsOnlyUsageAccruedInput struct {
	Charge           Charge                `json:"charge"`
	Run              RealizationRun        `json:"run"`
	BookedAt         time.Time             `json:"bookedAt"`
	AmountToAllocate alpacadecimal.Decimal `json:"amountToAllocate"`
}

func (i CreditsOnlyUsageAccruedInput) Validate() error {
	var errs []error

	if err := i.Charge.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("charge: %w", err))
	}

	if err := i.Run.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("run: %w", err))
	}

	if i.BookedAt.IsZero() {
		errs = append(errs, fmt.Errorf("booked at is required"))
	}

	if !i.AmountToAllocate.IsPositive() {
		errs = append(errs, fmt.Errorf("amount to allocate must be positive"))
	}

	return models.NewNillableGenericValidationError(errors.Join(errs...))
}

type CreditsOnlyUsageAccruedCorrectionInput struct {
	Charge   Charge         `json:"charge"`
	Run      RealizationRun `json:"run"`
	BookedAt time.Time      `json:"bookedAt"`

	Corrections                  creditrealization.CorrectionRequest   `json:"corrections"`
	LineageSegmentsByRealization lineage.ActiveSegmentsByRealizationID `json:"-"`
}

func (i CreditsOnlyUsageAccruedCorrectionInput) Validate() error {
	var errs []error

	if err := i.Charge.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("charge: %w", err))
	}

	if err := i.Run.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("run: %w", err))
	}

	if i.BookedAt.IsZero() {
		errs = append(errs, fmt.Errorf("booked at is required"))
	}

	return models.NewNillableGenericValidationError(errors.Join(errs...))
}

func (i CreditsOnlyUsageAccruedCorrectionInput) ValidateWith(currency currencyx.Currency) error {
	var errs []error

	if err := i.Validate(); err != nil {
		return err
	}

	if currency == nil {
		errs = append(errs, fmt.Errorf("currency is required"))
	}

	if currency != nil {
		if err := i.Corrections.ValidateWith(currency); err != nil {
			errs = append(errs, fmt.Errorf("corrections: %w", err))
		}
	}

	return models.NewNillableGenericValidationError(errors.Join(errs...))
}

type OnInvoiceUsageAccruedInput struct {
	Charge        Charge                `json:"charge"`
	Run           RealizationRun        `json:"run"`
	ServicePeriod timeutil.ClosedPeriod `json:"servicePeriod"`
	BookedAt      time.Time             `json:"bookedAt"`
	Amount        alpacadecimal.Decimal `json:"amount"`
}

func (i OnInvoiceUsageAccruedInput) Validate() error {
	var errs []error

	if err := i.Charge.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("charge: %w", err))
	}

	if err := i.Run.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("run: %w", err))
	}

	if err := i.ServicePeriod.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("service period: %w", err))
	}

	if i.BookedAt.IsZero() {
		errs = append(errs, fmt.Errorf("booked at is required"))
	}

	if i.Amount.IsNegative() {
		errs = append(errs, fmt.Errorf("amount cannot be negative"))
	}

	return models.NewNillableGenericValidationError(errors.Join(errs...))
}

type RunEventInput struct {
	Charge  Charge         `json:"charge"`
	Run     RealizationRun `json:"run"`
	EventAt time.Time      `json:"eventAt"`
}

func (i RunEventInput) Validate() error {
	var errs []error

	if err := i.Charge.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("charge: %w", err))
	}

	if err := i.Run.Validate(); err != nil {
		errs = append(errs, fmt.Errorf("run: %w", err))
	}

	if i.EventAt.IsZero() {
		errs = append(errs, fmt.Errorf("event at is required"))
	}

	return models.NewNillableGenericValidationError(errors.Join(errs...))
}

type (
	OnPaymentAuthorizedInput = RunEventInput
	OnPaymentSettledInput    = RunEventInput
)

type Handler interface {
	// OnInvoiceUsageAccrued is called when invoice-settled usage-based usage is sent to the customer.
	OnInvoiceUsageAccrued(ctx context.Context, input OnInvoiceUsageAccruedInput) (ledgertransaction.GroupReference, error)

	// OnPaymentAuthorized is called when an invoice-backed usage-based run receives payment authorization.
	OnPaymentAuthorized(ctx context.Context, input OnPaymentAuthorizedInput) (ledgertransaction.GroupReference, error)

	// OnPaymentSettled is called when an invoice-backed usage-based run payment is settled.
	OnPaymentSettled(ctx context.Context, input OnPaymentSettledInput) (ledgertransaction.GroupReference, error)

	// OnCreditsOnlyUsageAccrued is called when a credit-only usage-based charge needs to be allocated as credits fully.
	OnCreditsOnlyUsageAccrued(ctx context.Context, input CreditsOnlyUsageAccruedInput) (creditrealization.CreateAllocationInputs, error)

	// OnCreditsOnlyUsageAccruedCorrection is called when a credit-only usage-based charge needs to be corrected.
	OnCreditsOnlyUsageAccruedCorrection(ctx context.Context, input CreditsOnlyUsageAccruedCorrectionInput) (creditrealization.CreateCorrectionInputs, error)
}

type UnimplementedHandler struct{}

var _ Handler = (*UnimplementedHandler)(nil)

func (h UnimplementedHandler) OnInvoiceUsageAccrued(ctx context.Context, input OnInvoiceUsageAccruedInput) (ledgertransaction.GroupReference, error) {
	return ledgertransaction.GroupReference{}, errors.New("not implemented")
}

func (h UnimplementedHandler) OnPaymentAuthorized(ctx context.Context, input OnPaymentAuthorizedInput) (ledgertransaction.GroupReference, error) {
	return ledgertransaction.GroupReference{}, errors.New("not implemented")
}

func (h UnimplementedHandler) OnPaymentSettled(ctx context.Context, input OnPaymentSettledInput) (ledgertransaction.GroupReference, error) {
	return ledgertransaction.GroupReference{}, errors.New("not implemented")
}

func (h UnimplementedHandler) OnCreditsOnlyUsageAccrued(ctx context.Context, input CreditsOnlyUsageAccruedInput) (creditrealization.CreateAllocationInputs, error) {
	return nil, errors.New("not implemented")
}

func (h UnimplementedHandler) OnCreditsOnlyUsageAccruedCorrection(ctx context.Context, input CreditsOnlyUsageAccruedCorrectionInput) (creditrealization.CreateCorrectionInputs, error) {
	return nil, errors.New("not implemented")
}