Skip to content

Commit 5c4583a

Browse files
feat(statemachine): add promotional credit statemachine
1 parent 90370f3 commit 5c4583a

3 files changed

Lines changed: 381 additions & 18 deletions

File tree

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,23 @@ func (s *service) Create(ctx context.Context, input creditpurchase.CreateInput)
3030
// Let's activate the state machine for the credit purchase charge
3131
switch charge.Intent.Settlement.Type() {
3232
case creditpurchase.SettlementTypePromotional:
33-
charge, err = s.onPromotionalCreditPurchase(ctx, charge)
33+
stateMachine, stateMachineErr := NewPromotionalCreditPurchaseStateMachine(StateMachineConfig{
34+
Charge: charge,
35+
Adapter: s.adapter,
36+
Service: s,
37+
})
38+
if stateMachineErr != nil {
39+
return creditpurchase.ChargeWithGatheringLine{}, fmt.Errorf("new promotional state machine: %w", stateMachineErr)
40+
}
41+
42+
advancedCharge, stateMachineErr := stateMachine.AdvanceUntilStateStable(ctx)
43+
if stateMachineErr != nil {
44+
return creditpurchase.ChargeWithGatheringLine{}, fmt.Errorf("advance promotional state machine: %w", stateMachineErr)
45+
}
46+
47+
if advancedCharge != nil {
48+
charge = *advancedCharge
49+
}
3450
case creditpurchase.SettlementTypeInvoice:
3551
// noop, as we will transition to active state when the invoice is created, as
3652
// - invocing based charges are driven by the invoice state machine

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

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,15 @@ package service
22

33
import (
44
"context"
5-
"slices"
5+
"fmt"
66

77
"github.com/openmeterio/openmeter/openmeter/billing/charges/creditpurchase"
88
"github.com/openmeterio/openmeter/openmeter/billing/charges/lineage"
9+
"github.com/openmeterio/openmeter/openmeter/billing/charges/meta"
910
"github.com/openmeterio/openmeter/pkg/clock"
1011
)
1112

12-
var activePromotionalCreditPurchaseStatuses = []creditpurchase.Status{
13-
creditpurchase.StatusCreated,
14-
creditpurchase.StatusActive,
15-
}
16-
17-
func (s *service) onPromotionalCreditPurchase(ctx context.Context, charge creditpurchase.Charge) (creditpurchase.Charge, error) {
18-
// Prevent re-processing of the charge
19-
if !slices.Contains(activePromotionalCreditPurchaseStatuses, charge.Status) {
20-
return creditpurchase.Charge{}, creditpurchase.ErrCreditPurchaseChargeNotActive.WithAttrs(charge.ErrorAttributes())
21-
}
22-
13+
func (s *service) grantPromotionalCredit(ctx context.Context, charge creditpurchase.Charge) (creditpurchase.Charge, error) {
2314
ledgerTransactionGroupReference, err := s.handler.OnPromotionalCreditPurchase(ctx, charge)
2415
if err != nil {
2516
return creditpurchase.Charge{}, err
@@ -47,14 +38,54 @@ func (s *service) onPromotionalCreditPurchase(ctx context.Context, charge credit
4738
}
4839
}
4940

50-
charge.Status = creditpurchase.StatusFinal
41+
return charge, nil
42+
}
43+
44+
type PromotionalCreditpurchaseStateMachine struct {
45+
*stateMachine
46+
}
47+
48+
func NewPromotionalCreditPurchaseStateMachine(config StateMachineConfig) (*PromotionalCreditpurchaseStateMachine, error) {
49+
if err := config.Validate(); err != nil {
50+
return nil, fmt.Errorf("validate: %w", err)
51+
}
52+
53+
if config.Charge.Intent.Settlement.Type() != creditpurchase.SettlementTypePromotional {
54+
return nil, fmt.Errorf("charge %s is not promotional", config.Charge.ID)
55+
}
5156

52-
updatedBase, err := s.adapter.UpdateCharge(ctx, charge.ChargeBase)
57+
stateMachine, err := newStateMachineBase(config)
5358
if err != nil {
54-
return creditpurchase.Charge{}, err
59+
return nil, fmt.Errorf("failed to create promotional credit purchase state machine: %w", err)
5560
}
5661

57-
charge.ChargeBase = updatedBase
62+
out := &PromotionalCreditpurchaseStateMachine{
63+
stateMachine: stateMachine,
64+
}
65+
out.configureStates()
5866

59-
return charge, nil
67+
return out, nil
68+
}
69+
70+
func (s *PromotionalCreditpurchaseStateMachine) configureStates() {
71+
s.Configure(creditpurchase.StatusCreated).
72+
Permit(meta.TriggerNext, creditpurchase.StatusFinal)
73+
74+
s.Configure(creditpurchase.StatusActive).
75+
Permit(meta.TriggerNext, creditpurchase.StatusFinal)
76+
77+
s.Configure(creditpurchase.StatusFinal).
78+
OnEntry(func(ctx context.Context, _ ...any) error {
79+
return s.GrantPromotionalCredit(ctx)
80+
})
81+
}
82+
83+
func (s *PromotionalCreditpurchaseStateMachine) GrantPromotionalCredit(ctx context.Context) error {
84+
charge, err := s.Service.grantPromotionalCredit(ctx, s.Charge)
85+
if err != nil {
86+
return fmt.Errorf("grant promotional credit: %w", err)
87+
}
88+
89+
s.Charge = charge
90+
return nil
6091
}

0 commit comments

Comments
 (0)