Skip to content

Commit 65aaa25

Browse files
committed
[usage] Implement BillingService
1 parent 8d604b7 commit 65aaa25

File tree

4 files changed

+120
-3
lines changed

4 files changed

+120
-3
lines changed

components/usage/pkg/apiv1/billing.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package apiv1
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"github.com/gitpod-io/gitpod/common-go/log"
11+
v1 "github.com/gitpod-io/gitpod/usage-api/v1"
12+
"github.com/gitpod-io/gitpod/usage/pkg/db"
13+
"github.com/gitpod-io/gitpod/usage/pkg/stripe"
14+
"google.golang.org/grpc/codes"
15+
"google.golang.org/grpc/status"
16+
"math"
17+
)
18+
19+
func NewBillingService(stripeClient *stripe.Client) *BillingService {
20+
return &BillingService{
21+
stripeClient: stripeClient,
22+
}
23+
}
24+
25+
type BillingService struct {
26+
stripeClient *stripe.Client
27+
28+
v1.UnimplementedBillingServiceServer
29+
}
30+
31+
func (s *BillingService) UpdateInvoices(ctx context.Context, in *v1.UpdateInvoicesRequest) (*v1.UpdateInvoicesResponse, error) {
32+
credits, err := creditSummaryForTeams(in.GetSessions())
33+
if err != nil {
34+
log.Log.WithError(err).Errorf("Failed to compute credit summary.")
35+
return nil, status.Errorf(codes.InvalidArgument, "failed to compute credit summary")
36+
}
37+
38+
err = s.stripeClient.UpdateUsage(credits)
39+
if err != nil {
40+
log.Log.WithError(err).Errorf("Failed to update stripe invoices.")
41+
return nil, status.Errorf(codes.Internal, "failed to update stripe invoices")
42+
}
43+
44+
return &v1.UpdateInvoicesResponse{}, nil
45+
}
46+
47+
func creditSummaryForTeams(sessions []*v1.BilledSession) (map[string]int64, error) {
48+
creditsPerTeamID := map[string]int64{}
49+
50+
for _, session := range sessions {
51+
attributionID, err := db.ParseAttributionID(session.AttributionId)
52+
if err != nil {
53+
return nil, fmt.Errorf("failed to parse attribution ID: %w", err)
54+
}
55+
56+
entity, id := attributionID.Values()
57+
if entity != db.AttributionEntity_Team {
58+
continue
59+
}
60+
61+
if _, ok := creditsPerTeamID[id]; !ok {
62+
creditsPerTeamID[id] = 0
63+
}
64+
65+
creditsPerTeamID[id] += session.GetCredits()
66+
}
67+
68+
for teamID, credits := range creditsPerTeamID {
69+
creditsPerTeamID[teamID] = int64(math.Ceil(float64(credits)))
70+
}
71+
72+
return creditsPerTeamID, nil
73+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package apiv1
6+
7+
import (
8+
"context"
9+
"github.com/gitpod-io/gitpod/common-go/log"
10+
v1 "github.com/gitpod-io/gitpod/usage-api/v1"
11+
)
12+
13+
// BillingServiceNoop is used for Self-Hosted installations
14+
type BillingServiceNoop struct {
15+
v1.UnimplementedBillingServiceServer
16+
}
17+
18+
func (s *BillingServiceNoop) UpdateInvoices(_ context.Context, _ *v1.UpdateInvoicesRequest) (*v1.UpdateInvoicesResponse, error) {
19+
log.Log.Infof("UpdateInvoices RPC invoked in no-op mode, no invoices will be updated.")
20+
return &v1.UpdateInvoicesResponse{}, nil
21+
}

components/usage/pkg/db/workspace_instance.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ func (a AttributionID) Values() (entity string, identifier string) {
120120
return tokens[0], tokens[1]
121121
}
122122

123+
func ParseAttributionID(s string) (AttributionID, error) {
124+
tokens := strings.Split(s, ":")
125+
if len(tokens) != 2 {
126+
return "", fmt.Errorf("attribution ID (%s) does not have two parts", s)
127+
}
128+
129+
switch tokens[0] {
130+
case AttributionEntity_Team:
131+
return NewTeamAttributionID(tokens[1]), nil
132+
case AttributionEntity_User:
133+
return NewUserAttributionID(tokens[1]), nil
134+
default:
135+
return "", fmt.Errorf("unknown attribution ID type: %s", s)
136+
}
137+
}
138+
123139
const (
124140
WorkspaceClass_Default = "default"
125141
)

components/usage/pkg/server/server.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func Start(cfg Config) error {
5454

5555
var billingController controller.BillingController = &controller.NoOpBillingController{}
5656

57+
var stripeClient *stripe.Client
5758
if cfg.StripeCredentialsFile != "" {
5859
config, err := stripe.ReadConfigFromFile(cfg.StripeCredentialsFile)
5960
if err != nil {
@@ -65,7 +66,8 @@ func Start(cfg Config) error {
6566
return fmt.Errorf("failed to initialize stripe client: %w", err)
6667
}
6768

68-
billingController = controller.NewStripeBillingController(c)
69+
stripeClient = c
70+
billingController = controller.NewStripeBillingController(c, pricer)
6971
}
7072

7173
schedule, err := time.ParseDuration(cfg.ControllerSchedule)
@@ -97,7 +99,7 @@ func Start(cfg Config) error {
9799
if err != nil {
98100
return fmt.Errorf("failed to initialize usage server: %w", err)
99101
}
100-
err = registerGRPCServices(srv, conn)
102+
err = registerGRPCServices(srv, conn, stripeClient)
101103
if err != nil {
102104
return fmt.Errorf("failed to register gRPC services: %w", err)
103105
}
@@ -115,7 +117,12 @@ func Start(cfg Config) error {
115117
return nil
116118
}
117119

118-
func registerGRPCServices(srv *baseserver.Server, conn *gorm.DB) error {
120+
func registerGRPCServices(srv *baseserver.Server, conn *gorm.DB, stripeClient *stripe.Client) error {
119121
v1.RegisterUsageServiceServer(srv.GRPC(), apiv1.NewUsageService(conn))
122+
if stripeClient == nil {
123+
v1.RegisterBillingServiceServer(srv.GRPC(), &apiv1.BillingServiceNoop{})
124+
} else {
125+
v1.RegisterBillingServiceServer(srv.GRPC(), apiv1.NewBillingService(stripeClient))
126+
}
120127
return nil
121128
}

0 commit comments

Comments
 (0)