@@ -12,8 +12,11 @@ import (
12
12
"math"
13
13
"time"
14
14
15
+ "github.com/bufbuild/connect-go"
15
16
"github.com/gitpod-io/gitpod/common-go/log"
16
17
db "github.com/gitpod-io/gitpod/components/gitpod-db/go"
18
+ experimental_v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
19
+ "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"
17
20
v1 "github.com/gitpod-io/gitpod/usage-api/v1"
18
21
"github.com/gitpod-io/gitpod/usage/pkg/stripe"
19
22
"github.com/google/uuid"
@@ -38,6 +41,8 @@ type BillingService struct {
38
41
ccManager * db.CostCenterManager
39
42
stripePrices stripe.StripePrices
40
43
44
+ teamsService v1connect.TeamsServiceClient
45
+
41
46
v1.UnimplementedBillingServiceServer
42
47
}
43
48
@@ -408,6 +413,73 @@ func (s *BillingService) CancelSubscription(ctx context.Context, in *v1.CancelSu
408
413
return & v1.CancelSubscriptionResponse {}, nil
409
414
}
410
415
416
+ func (s * BillingService ) OnChargeDispute (ctx context.Context , req * v1.OnChargeDisputeRequest ) (* v1.OnChargeDisputeResponse , error ) {
417
+ if req .DisputeId == "" {
418
+ return nil , status .Errorf (codes .InvalidArgument , "dispute ID is required" )
419
+ }
420
+
421
+ logger := log .WithContext (ctx ).WithField ("disputeId" , req .DisputeId )
422
+
423
+ dispute , err := s .stripeClient .GetDispute (ctx , req .DisputeId )
424
+ if err != nil {
425
+ return nil , status .Errorf (codes .Internal , "failed to retrieve dispute ID %s from stripe" , req .DisputeId )
426
+ }
427
+
428
+ if dispute .PaymentIntent == nil || dispute .PaymentIntent .Customer == nil {
429
+ return nil , status .Errorf (codes .Internal , "dispute did not contain customer of payment intent in expanded fields" )
430
+ }
431
+
432
+ customer := dispute .PaymentIntent .Customer
433
+ logger = logger .WithField ("customerId" , customer .ID )
434
+
435
+ attributionIDValue , ok := customer .Metadata [stripe .AttributionIDMetadataKey ]
436
+ if ! ok {
437
+ return nil , status .Errorf (codes .Internal , "Customer %s object did not contain attribution ID in metadata" , customer .ID )
438
+ }
439
+
440
+ logger = logger .WithField ("attributionId" , attributionIDValue )
441
+
442
+ attributionID , err := db .ParseAttributionID (attributionIDValue )
443
+ if err != nil {
444
+ log .WithError (err ).Errorf ("Failed to parse attribution ID from customer metadata." )
445
+ return nil , status .Errorf (codes .Internal , "failed to parse attribution ID from customer metadata" )
446
+ }
447
+
448
+ var userIDsToBlock []string
449
+ entity , id := attributionID .Values ()
450
+ switch entity {
451
+ case db .AttributionEntity_User :
452
+ // legacy for cases where we've not migrated the user to a team
453
+ // because we attribute to the user directly, we can just block the user directly
454
+ userIDsToBlock = append (userIDsToBlock , id )
455
+
456
+ case db .AttributionEntity_Team :
457
+ team , err := s .teamsService .GetTeam (ctx , connect .NewRequest (& experimental_v1.GetTeamRequest {
458
+ TeamId : id ,
459
+ }))
460
+ if err != nil {
461
+ return nil , status .Errorf (codes .Internal , "failed to lookup team details for team ID: %s" , id )
462
+ }
463
+
464
+ for _ , member := range team .Msg .GetTeam ().GetMembers () {
465
+ if member .GetRole () != experimental_v1 .TeamRole_TEAM_ROLE_OWNER {
466
+ continue
467
+ }
468
+ userIDsToBlock = append (userIDsToBlock , member .GetUserId ())
469
+ }
470
+
471
+ default :
472
+ return nil , status .Errorf (codes .Internal , "unknown attribution entity for %s" , attributionIDValue )
473
+ }
474
+
475
+ logger = logger .WithField ("teamOwners" , userIDsToBlock )
476
+
477
+ logger .Infof ("Identified %d users to block based on charge dispute" , len (userIDsToBlock ))
478
+ // TODO: actually block users
479
+
480
+ return & v1.OnChargeDisputeResponse {}, nil
481
+ }
482
+
411
483
func (s * BillingService ) getPriceId (ctx context.Context , attributionId string ) string {
412
484
defaultPriceId := s .stripePrices .TeamUsagePriceIDs .USD
413
485
attributionID , err := db .ParseAttributionID (attributionId )
0 commit comments