Skip to content

Commit

Permalink
[billing] Report metrics on updated subscriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
easyCZ authored and roboquat committed Aug 4, 2022
1 parent 5c1074b commit 6390f20
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 23 deletions.
5 changes: 5 additions & 0 deletions components/usage/pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ func Start(cfg Config) error {
return fmt.Errorf("failed to register controller metrics: %w", err)
}

err = stripe.RegisterMetrics(srv.MetricsRegistry())
if err != nil {
return fmt.Errorf("failed to register stripe metrics: %w", err)
}

err = srv.ListenAndServe()
if err != nil {
return fmt.Errorf("failed to listen and serve: %w", err)
Expand Down
41 changes: 41 additions & 0 deletions components/usage/pkg/stripe/reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package stripe

import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
)

var (
stripeUsageUpdateTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "gitpod",
Subsystem: "stripe",
Name: "usage_records_updated_total",
Help: "Counter of usage records updated",
}, []string{"outcome"})
)

func RegisterMetrics(reg *prometheus.Registry) error {
metrics := []prometheus.Collector{
stripeUsageUpdateTotal,
}
for _, metric := range metrics {
err := reg.Register(metric)
if err != nil {
return fmt.Errorf("failed to register metric: %w", err)
}
}

return nil
}

func reportStripeUsageUpdate(err error) {
outcome := "success"
if err != nil {
outcome = "fail"
}
stripeUsageUpdateTotal.WithLabelValues(outcome).Inc()
}
72 changes: 49 additions & 23 deletions components/usage/pkg/stripe/stripe.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ func New(config ClientConfig) (*Client, error) {
return &Client{sc: client.New(config.SecretKey, nil)}, nil
}

type UsageRecord struct {
SubscriptionItemID string
Quantity int64
}

// UpdateUsage updates teams' Stripe subscriptions with usage data
// `usageForTeam` is a map from team name to total workspace seconds used within a billing period.
func (c *Client) UpdateUsage(ctx context.Context, creditsPerTeam map[string]int64) error {
Expand All @@ -55,7 +60,7 @@ func (c *Client) UpdateUsage(ctx context.Context, creditsPerTeam map[string]int6
queries := queriesForCustomersWithTeamIds(teamIds)

for _, query := range queries {
log.Infof("about to make query %q", query)
log.Infof("Searching customers in Stripe with query: %q", query)
params := &stripe.CustomerSearchParams{
SearchParams: stripe.SearchParams{
Query: query,
Expand All @@ -66,36 +71,57 @@ func (c *Client) UpdateUsage(ctx context.Context, creditsPerTeam map[string]int6
iter := c.sc.Customers.Search(params)
for iter.Next() {
customer := iter.Customer()
log.Infof("found customer %q for teamId %q", customer.Name, customer.Metadata["teamId"])
subscriptions := customer.Subscriptions.Data
if len(subscriptions) != 1 {
log.Errorf("customer has an unexpected number of subscriptions (expected 1, got %d)", len(subscriptions))
continue
}
subscription := customer.Subscriptions.Data[0]

log.Infof("customer has subscription: %q", subscription.ID)
if len(subscription.Items.Data) != 1 {
log.Errorf("this subscription has an unexpected number of subscriptionItems (expected 1, got %d)", len(subscription.Items.Data))
continue
}

creditsUsed := creditsPerTeam[customer.Metadata["teamId"]]
teamID := customer.Metadata["teamId"]
log.Infof("Found customer %q for teamId %q", customer.Name, teamID)

subscriptionItemId := subscription.Items.Data[0].ID
log.Infof("registering usage against subscriptionItem %q", subscriptionItemId)
_, err := c.sc.UsageRecords.New(&stripe.UsageRecordParams{
SubscriptionItem: stripe.String(subscriptionItemId),
Quantity: stripe.Int64(creditsUsed),
})
_, err := c.updateUsageForCustomer(ctx, customer, creditsPerTeam[teamID])
if err != nil {
log.WithError(err).Errorf("failed to register usage for customer %q", customer.Name)
log.WithField("customer_id", customer.ID).
WithField("customer_name", customer.Name).
WithField("subscriptions", customer.Subscriptions).
WithError(err).
Errorf("Failed to update usage.")

reportStripeUsageUpdate(err)
continue
}
reportStripeUsageUpdate(nil)
}
}
return nil
}

func (c *Client) updateUsageForCustomer(ctx context.Context, customer *stripe.Customer, credits int64) (*UsageRecord, error) {
subscriptions := customer.Subscriptions.Data
if len(subscriptions) != 1 {
return nil, fmt.Errorf("customer has an unexpected number of subscriptions %v (expected 1, got %d)", subscriptions, len(subscriptions))
}
subscription := customer.Subscriptions.Data[0]

log.Infof("Customer has subscription: %q", subscription.ID)
if len(subscription.Items.Data) != 1 {
return nil, fmt.Errorf("subscription %s has an unexpected number of subscriptionItems (expected 1, got %d)", subscription.ID, len(subscription.Items.Data))
}

subscriptionItemId := subscription.Items.Data[0].ID
log.Infof("Registering usage against subscriptionItem %q", subscriptionItemId)
_, err := c.sc.UsageRecords.New(&stripe.UsageRecordParams{
Params: stripe.Params{
Context: ctx,
},
SubscriptionItem: stripe.String(subscriptionItemId),
Quantity: stripe.Int64(credits),
})
if err != nil {
return nil, fmt.Errorf("failed to register usage for customer %q on subscription item %s", customer.Name, subscriptionItemId)
}

return &UsageRecord{
SubscriptionItemID: subscriptionItemId,
Quantity: credits,
}, nil
}

// queriesForCustomersWithTeamIds constructs Stripe query strings to find the Stripe Customer for each teamId
// It returns multiple queries, each being a big disjunction of subclauses so that we can process multiple teamIds in one query.
// `clausesPerQuery` is a limit enforced by the Stripe API.
Expand Down

0 comments on commit 6390f20

Please sign in to comment.