Skip to content

Commit 9329939

Browse files
easyCZroboquat
authored andcommitted
[stripe] Use attributionId in stripe queries
1 parent 1b64527 commit 9329939

File tree

3 files changed

+44
-50
lines changed

3 files changed

+44
-50
lines changed

components/usage/pkg/apiv1/billing.go

+3-13
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,9 @@ func (s *BillingService) ReconcileInvoices(ctx context.Context, in *v1.Reconcile
4343
return nil, status.Errorf(codes.Internal, "Failed to reconcile invoices.")
4444
}
4545

46-
creditSummaryForTeams := map[string]stripe.CreditSummary{}
46+
creditSummaryForTeams := map[db.AttributionID]int64{}
4747
for _, balance := range balances {
48-
entity, id := balance.AttributionID.Values()
49-
50-
// TODO: Support updating of user attribution IDs
51-
if entity != db.AttributionEntity_Team {
52-
continue
53-
}
54-
55-
creditSummaryForTeams[id] = stripe.CreditSummary{
56-
Credits: int64(math.Ceil(balance.CreditCents.ToCredits())),
57-
ReportID: "no-report",
58-
}
48+
creditSummaryForTeams[balance.AttributionID] = int64(math.Ceil(balance.CreditCents.ToCredits()))
5949
}
6050

6151
err = s.stripeClient.UpdateUsage(ctx, creditSummaryForTeams)
@@ -86,7 +76,7 @@ func (s *BillingService) FinalizeInvoice(ctx context.Context, in *v1.FinalizeInv
8676
return nil, status.Errorf(codes.Internal, "Failed to retrieve subscription details from invoice.")
8777
}
8878

89-
teamID, found := subscription.Metadata[stripe.TeamIDMetadataKey]
79+
teamID, found := subscription.Metadata[stripe.AttributionIDMetadataKey]
9080
if !found {
9181
logger.Error("Failed to find teamID from subscription metadata.")
9282
return nil, status.Errorf(codes.Internal, "Failed to extra teamID from Stripe subscription.")

components/usage/pkg/stripe/stripe.go

+27-24
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"encoding/json"
1010
"fmt"
11+
"github.com/gitpod-io/gitpod/usage/pkg/db"
1112
"os"
1213
"strings"
1314

@@ -17,8 +18,8 @@ import (
1718
)
1819

1920
const (
20-
ReportIDMetadataKey = "reportId"
21-
TeamIDMetadataKey = "teamId"
21+
ReportIDMetadataKey = "reportId"
22+
AttributionIDMetadataKey = "attributionId"
2223
)
2324

2425
type Client struct {
@@ -63,19 +64,14 @@ type Invoice struct {
6364
Credits int64
6465
}
6566

66-
type CreditSummary struct {
67-
Credits int64
68-
ReportID string
69-
}
70-
7167
// UpdateUsage updates teams' Stripe subscriptions with usage data
7268
// `usageForTeam` is a map from team name to total workspace seconds used within a billing period.
73-
func (c *Client) UpdateUsage(ctx context.Context, creditsPerTeam map[string]CreditSummary) error {
74-
teamIds := make([]string, 0, len(creditsPerTeam))
75-
for k := range creditsPerTeam {
76-
teamIds = append(teamIds, k)
69+
func (c *Client) UpdateUsage(ctx context.Context, creditsPerAttributionID map[db.AttributionID]int64) error {
70+
attributionIDs := make([]db.AttributionID, 0, len(creditsPerAttributionID))
71+
for k := range creditsPerAttributionID {
72+
attributionIDs = append(attributionIDs, k)
7773
}
78-
queries := queriesForCustomersWithTeamIds(teamIds)
74+
queries := queriesForCustomersWithAttributionIDs(attributionIDs)
7975

8076
for _, query := range queries {
8177
log.Infof("Searching customers in Stripe with query: %q", query)
@@ -86,14 +82,21 @@ func (c *Client) UpdateUsage(ctx context.Context, creditsPerTeam map[string]Cred
8682
}
8783

8884
for _, customer := range customers {
89-
teamID := customer.Metadata["teamId"]
90-
log.Infof("Found customer %q for teamId %q", customer.Name, teamID)
85+
attributionIDRaw := customer.Metadata[AttributionIDMetadataKey]
86+
log.Infof("Found customer %q for attribution ID %q", customer.Name, attributionIDRaw)
87+
88+
attributionID, err := db.ParseAttributionID(attributionIDRaw)
89+
if err != nil {
90+
log.WithError(err).Error("Failed to parse attribution ID from Stripe metadata.")
91+
continue
92+
}
9193

92-
_, err := c.updateUsageForCustomer(ctx, customer, creditsPerTeam[teamID])
94+
_, err = c.updateUsageForCustomer(ctx, customer, creditsPerAttributionID[attributionID])
9395
if err != nil {
9496
log.WithField("customer_id", customer.ID).
9597
WithField("customer_name", customer.Name).
9698
WithField("subscriptions", customer.Subscriptions).
99+
WithField("attribution_id", attributionID).
97100
WithError(err).
98101
Errorf("Failed to update usage.")
99102

@@ -127,7 +130,7 @@ func (c *Client) findCustomers(ctx context.Context, query string) ([]*stripe.Cus
127130
return customers, nil
128131
}
129132

130-
func (c *Client) updateUsageForCustomer(ctx context.Context, customer *stripe.Customer, summary CreditSummary) (*UsageRecord, error) {
133+
func (c *Client) updateUsageForCustomer(ctx context.Context, customer *stripe.Customer, credits int64) (*UsageRecord, error) {
131134
subscriptions := customer.Subscriptions.Data
132135
if len(subscriptions) != 1 {
133136
return nil, fmt.Errorf("customer has an unexpected number of subscriptions %v (expected 1, got %d)", subscriptions, len(subscriptions))
@@ -146,15 +149,15 @@ func (c *Client) updateUsageForCustomer(ctx context.Context, customer *stripe.Cu
146149
Context: ctx,
147150
},
148151
SubscriptionItem: stripe.String(subscriptionItemId),
149-
Quantity: stripe.Int64(summary.Credits),
152+
Quantity: stripe.Int64(credits),
150153
})
151154
if err != nil {
152155
return nil, fmt.Errorf("failed to register usage for customer %q on subscription item %s", customer.Name, subscriptionItemId)
153156
}
154157

155158
return &UsageRecord{
156159
SubscriptionItemID: subscriptionItemId,
157-
Quantity: summary.Credits,
160+
Quantity: credits,
158161
}, nil
159162
}
160163

@@ -246,19 +249,19 @@ func (c *Client) GetInvoice(ctx context.Context, invoiceID string) (*stripe.Invo
246249
return invoice, nil
247250
}
248251

249-
// queriesForCustomersWithTeamIds constructs Stripe query strings to find the Stripe Customer for each teamId
252+
// queriesForCustomersWithAttributionIDs constructs Stripe query strings to find the Stripe Customer for each teamId
250253
// It returns multiple queries, each being a big disjunction of subclauses so that we can process multiple teamIds in one query.
251254
// `clausesPerQuery` is a limit enforced by the Stripe API.
252-
func queriesForCustomersWithTeamIds(teamIds []string) []string {
255+
func queriesForCustomersWithAttributionIDs(attributionIDs []db.AttributionID) []string {
253256
const clausesPerQuery = 10
254257
var queries []string
255258
sb := strings.Builder{}
256259

257-
for i := 0; i < len(teamIds); i += clausesPerQuery {
260+
for i := 0; i < len(attributionIDs); i += clausesPerQuery {
258261
sb.Reset()
259-
for j := 0; j < clausesPerQuery && i+j < len(teamIds); j++ {
260-
sb.WriteString(fmt.Sprintf("metadata['%s']:'%s'", TeamIDMetadataKey, teamIds[i+j]))
261-
if j < clausesPerQuery-1 && i+j < len(teamIds)-1 {
262+
for j := 0; j < clausesPerQuery && i+j < len(attributionIDs); j++ {
263+
sb.WriteString(fmt.Sprintf("metadata['%s']:'%s'", AttributionIDMetadataKey, attributionIDs[i+j]))
264+
if j < clausesPerQuery-1 && i+j < len(attributionIDs)-1 {
262265
sb.WriteString(" OR ")
263266
}
264267
}

components/usage/pkg/stripe/stripe_test.go

+14-13
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,33 @@ package stripe
66

77
import (
88
"fmt"
9+
"github.com/gitpod-io/gitpod/usage/pkg/db"
910
"testing"
1011

1112
"github.com/stretchr/testify/require"
1213
)
1314

14-
func TestCustomerQueriesForTeamIds_SingleQuery(t *testing.T) {
15+
func TestQueriesForCustomersWithAttributionID_Single(t *testing.T) {
1516
testCases := []struct {
1617
Name string
17-
TeamIds []string
18+
AttributionIDs []db.AttributionID
1819
ExpectedQueries []string
1920
}{
2021
{
2122
Name: "1 team id",
22-
TeamIds: []string{"abcd-123"},
23-
ExpectedQueries: []string{"metadata['teamId']:'abcd-123'"},
23+
AttributionIDs: []db.AttributionID{db.NewTeamAttributionID("abcd-123")},
24+
ExpectedQueries: []string{"metadata['attributionId']:'team:abcd-123'"},
2425
},
2526
{
26-
Name: "2 team ids",
27-
TeamIds: []string{"abcd-123", "abcd-456"},
28-
ExpectedQueries: []string{"metadata['teamId']:'abcd-123' OR metadata['teamId']:'abcd-456'"},
27+
Name: "1 team id, 1 user id",
28+
AttributionIDs: []db.AttributionID{db.NewTeamAttributionID("abcd-123"), db.NewUserAttributionID("abcd-456")},
29+
ExpectedQueries: []string{"metadata['attributionId']:'team:abcd-123' OR metadata['attributionId']:'user:abcd-456'"},
2930
},
3031
}
3132

3233
for _, tc := range testCases {
3334
t.Run(tc.Name, func(t *testing.T) {
34-
actualQueries := queriesForCustomersWithTeamIds(tc.TeamIds)
35+
actualQueries := queriesForCustomersWithAttributionIDs(tc.AttributionIDs)
3536

3637
require.Equal(t, tc.ExpectedQueries, actualQueries)
3738
})
@@ -66,18 +67,18 @@ func TestCustomerQueriesForTeamIds_MultipleQueries(t *testing.T) {
6667
},
6768
}
6869

69-
buildTeamIds := func(numberOfTeamIds int) []string {
70-
var teamIds []string
70+
buildTeamIds := func(numberOfTeamIds int) []db.AttributionID {
71+
var attributionIDs []db.AttributionID
7172
for i := 0; i < numberOfTeamIds; i++ {
72-
teamIds = append(teamIds, fmt.Sprintf("abcd-%d", i))
73+
attributionIDs = append(attributionIDs, db.NewTeamAttributionID(fmt.Sprintf("abcd-%d", i)))
7374
}
74-
return teamIds
75+
return attributionIDs
7576
}
7677

7778
for _, tc := range testCases {
7879
t.Run(tc.Name, func(t *testing.T) {
7980
teamIds := buildTeamIds(tc.NumberOfTeamIds)
80-
actualQueries := queriesForCustomersWithTeamIds(teamIds)
81+
actualQueries := queriesForCustomersWithAttributionIDs(teamIds)
8182

8283
require.Equal(t, tc.ExpectedNumberOfQueries, len(actualQueries))
8384
})

0 commit comments

Comments
 (0)