Просмотр исходного кода

Fix porter cloud ingest aliases (#4580)

Mauricio Araujo 2 лет назад
Родитель
Сommit
e0790e56c2

+ 11 - 2
api/server/handlers/billing/ingest.go

@@ -2,6 +2,7 @@
 package billing
 
 import (
+	"fmt"
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -35,12 +36,13 @@ func (c *IngestEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
+	if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
 		c.WriteResult(w, r, "")
 
 		telemetry.WithAttributes(span,
-			telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeEnabled},
+			telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
 			telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
+			telemetry.AttributeKV{Key: "porter-cloud-enabled", Value: proj.EnableSandbox},
 		)
 		return
 	}
@@ -64,6 +66,13 @@ func (c *IngestEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		telemetry.AttributeKV{Key: "usage-events-count", Value: len(ingestEventsRequest.Events)},
 	)
 
+	// For Porter Cloud events, we apend a prefix to avoid collisions before sending to Metronome
+	if proj.EnableSandbox {
+		for i := range ingestEventsRequest.Events {
+			ingestEventsRequest.Events[i].CustomerID = fmt.Sprintf("porter-cloud-%s", ingestEventsRequest.Events[i].CustomerID)
+		}
+	}
+
 	err := c.Config().BillingManager.MetronomeClient.IngestEvents(ctx, ingestEventsRequest.Events)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error ingesting events")

+ 24 - 25
api/server/handlers/billing/list.go

@@ -115,25 +115,16 @@ func (c *CheckPaymentEnabledHandler) ensureBillingSetup(ctx context.Context, pro
 		}
 
 		// Create billing customer for project and set the billing ID if it doesn't exist
-		shouldUpdateBilling, err := c.ensureStripeCustomerExists(ctx, adminUser.Email, proj)
+		err = c.ensureStripeCustomerExists(ctx, adminUser.Email, proj)
 		if err != nil {
 			return telemetry.Error(ctx, span, err, "error ensuring Stripe customer exists")
 		}
 
 		// Create usage customer for project and set the usage ID if it doesn't exist
-		shouldUpdateUsage, err := c.ensureMetronomeCustomerExists(ctx, adminUser.Email, proj)
+		err = c.ensureMetronomeCustomerExists(ctx, adminUser.Email, proj)
 		if err != nil {
 			return telemetry.Error(ctx, span, err, "error ensuring Metronome customer exists")
 		}
-
-		if !shouldUpdateBilling && !shouldUpdateUsage {
-			return nil
-		}
-
-		_, err = c.Repo().Project().UpdateProject(proj)
-		if err != nil {
-			return telemetry.Error(ctx, span, err, "error updating project")
-		}
 	}
 
 	return nil
@@ -171,17 +162,17 @@ func (c *CheckPaymentEnabledHandler) getAdminUser(ctx context.Context, projectID
 	return adminUser, nil
 }
 
-func (c *CheckPaymentEnabledHandler) ensureStripeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (shouldUpdate bool, err error) {
+func (c *CheckPaymentEnabledHandler) ensureStripeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (err error) {
 	ctx, span := telemetry.NewSpan(ctx, "ensure-stripe-customer-exists")
 	defer span.End()
 
-	if proj.BillingID != "" {
-		return false, nil
+	if !c.Config().BillingManager.StripeConfigLoaded || !proj.GetFeatureFlag(models.BillingEnabled, c.Config().LaunchDarklyClient) || proj.BillingID != "" {
+		return nil
 	}
 
 	billingID, err := c.Config().BillingManager.StripeClient.CreateCustomer(ctx, adminUserEmail, proj.ID, proj.Name)
 	if err != nil {
-		return false, telemetry.Error(ctx, span, err, "error creating billing customer")
+		return telemetry.Error(ctx, span, err, "error creating billing customer")
 	}
 
 	telemetry.WithAttributes(span,
@@ -189,24 +180,26 @@ func (c *CheckPaymentEnabledHandler) ensureStripeCustomerExists(ctx context.Cont
 	)
 
 	proj.BillingID = billingID
-	return true, nil
+
+	_, err = c.Repo().Project().UpdateProject(proj)
+	if err != nil {
+		return telemetry.Error(ctx, span, err, "error updating project")
+	}
+
+	return nil
 }
 
-func (c *CheckPaymentEnabledHandler) ensureMetronomeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (shouldUpdate bool, err error) {
+func (c *CheckPaymentEnabledHandler) ensureMetronomeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (err error) {
 	ctx, span := telemetry.NewSpan(ctx, "ensure-metronome-customer-exists")
 	defer span.End()
 
-	if proj.UsageID != uuid.Nil {
-		return false, nil
-	}
-
-	if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) || proj.UsageID != uuid.Nil {
-		return false, nil
+	if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) || proj.UsageID != uuid.Nil {
+		return nil
 	}
 
 	customerID, customerPlanID, err := c.Config().BillingManager.MetronomeClient.CreateCustomerWithPlan(ctx, adminUserEmail, proj.Name, proj.ID, proj.BillingID, proj.EnableSandbox)
 	if err != nil {
-		return false, telemetry.Error(ctx, span, err, "error creating Metronome customer")
+		return telemetry.Error(ctx, span, err, "error creating Metronome customer")
 	}
 
 	telemetry.WithAttributes(span,
@@ -216,5 +209,11 @@ func (c *CheckPaymentEnabledHandler) ensureMetronomeCustomerExists(ctx context.C
 
 	proj.UsageID = customerID
 	proj.UsagePlanID = customerPlanID
-	return true, nil
+
+	_, err = c.Repo().Project().UpdateProject(proj)
+	if err != nil {
+		return telemetry.Error(ctx, span, err, "error updating project")
+	}
+
+	return nil
 }

+ 6 - 6
api/server/handlers/billing/plan.go

@@ -33,11 +33,11 @@ func (c *ListPlansHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
+	if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
 		c.WriteResult(w, r, "")
 
 		telemetry.WithAttributes(span,
-			telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeEnabled},
+			telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
 			telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
 		)
 		return
@@ -79,11 +79,11 @@ func (c *ListCreditsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
+	if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
 		c.WriteResult(w, r, "")
 
 		telemetry.WithAttributes(span,
-			telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeEnabled},
+			telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
 			telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
 		)
 		return
@@ -127,12 +127,12 @@ func (c *ListCustomerUsageHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
 	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeEnabled},
+		telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
 		telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
 		telemetry.AttributeKV{Key: "usage-id", Value: proj.UsageID},
 	)
 
-	if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
+	if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
 		c.WriteResult(w, r, "")
 		return
 	}

+ 2 - 2
api/server/handlers/project/create.go

@@ -81,7 +81,7 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	// Create Stripe Customer
-	if p.Config().ServerConf.StripeSecretKey != "" && p.Config().ServerConf.StripePublishableKey != "" {
+	if p.Config().BillingManager.StripeConfigLoaded && proj.GetFeatureFlag(models.BillingEnabled, p.Config().LaunchDarklyClient) {
 		// Create billing customer for project and set the billing ID
 		billingID, err := p.Config().BillingManager.StripeClient.CreateCustomer(ctx, user.Email, proj.ID, proj.Name)
 		if err != nil {
@@ -98,7 +98,7 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	// Create Metronome customer and add to starter plan
-	if p.Config().BillingManager.MetronomeEnabled && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
+	if p.Config().BillingManager.MetronomeConfigLoaded && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
 		customerID, customerPlanID, err := p.Config().BillingManager.MetronomeClient.CreateCustomerWithPlan(ctx, user.Email, proj.Name, proj.ID, proj.BillingID, proj.EnableSandbox)
 		if err != nil {
 			err = telemetry.Error(ctx, span, err, "error creating Metronome customer")

+ 1 - 1
api/server/handlers/project/delete.go

@@ -92,7 +92,7 @@ func (p *ProjectDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	if p.Config().BillingManager.MetronomeEnabled && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
+	if p.Config().BillingManager.MetronomeConfigLoaded && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
 		err = p.Config().BillingManager.MetronomeClient.EndCustomerPlan(ctx, proj.UsageID, proj.UsagePlanID)
 		if err != nil {
 			e := "error ending billing plan"

+ 6 - 3
api/server/shared/config/loader/loader.go

@@ -335,11 +335,13 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 
 	var (
 		stripeClient     billing.StripeClient
+		stripeEnabled    bool
 		metronomeClient  billing.MetronomeClient
 		metronomeEnabled bool
 	)
 	if sc.StripeSecretKey != "" {
 		stripeClient = billing.NewStripeClient(InstanceEnvConf.ServerConf.StripeSecretKey, InstanceEnvConf.ServerConf.StripePublishableKey)
+		stripeEnabled = true
 		res.Logger.Info().Msg("Stripe configuration loaded")
 	} else {
 		res.Logger.Info().Msg("STRIPE_SECRET_KEY not set, all Stripe functionality will be disabled")
@@ -358,9 +360,10 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 
 	res.Logger.Info().Msg("Creating billing manager")
 	res.BillingManager = billing.Manager{
-		StripeClient:     stripeClient,
-		MetronomeClient:  metronomeClient,
-		MetronomeEnabled: metronomeEnabled,
+		StripeClient:          stripeClient,
+		StripeConfigLoaded:    stripeEnabled,
+		MetronomeClient:       metronomeClient,
+		MetronomeConfigLoaded: metronomeEnabled,
 	}
 	res.Logger.Info().Msg("Created billing manager")
 

+ 2 - 2
dashboard/src/main/home/Home.tsx

@@ -215,7 +215,7 @@ const Home: React.FC<Props> = (props) => {
       } else {
         setHasFinishedOnboarding(true);
       }
-    } catch (error) {}
+    } catch (error) { }
   };
 
   useEffect(() => {
@@ -423,7 +423,7 @@ const Home: React.FC<Props> = (props) => {
                     >
                       connect a valid payment method
                     </Link>
-                    . Your free trial is ending in{" "}
+                    . Your free trial is ending {" "}
                     {relativeDate(plan.trial_info.ending_before, true)}.
                   </GlobalBanner>
                 )}

+ 4 - 3
internal/billing/billing.go

@@ -2,7 +2,8 @@ package billing
 
 // Manager contains methods for managing billing for a project
 type Manager struct {
-	StripeClient     StripeClient
-	MetronomeClient  MetronomeClient
-	MetronomeEnabled bool
+	StripeClient          StripeClient
+	StripeConfigLoaded    bool
+	MetronomeClient       MetronomeClient
+	MetronomeConfigLoaded bool
 }

+ 8 - 5
internal/billing/metronome.go

@@ -54,11 +54,15 @@ func (m MetronomeClient) CreateCustomerWithPlan(ctx context.Context, userEmail s
 	defer span.End()
 
 	planID := m.PorterStandardPlanID
+	projID := strconv.FormatUint(uint64(projectID), 10)
 	if sandboxEnabled {
 		planID = m.PorterCloudPlanID
+
+		// This is necessary to avoid conflicts with Porter standard projects
+		projID = fmt.Sprintf("porter-cloud-%s", projID)
 	}
 
-	customerID, err = m.createCustomer(ctx, userEmail, projectName, projectID, billingID)
+	customerID, err = m.createCustomer(ctx, userEmail, projectName, projID, billingID)
 	if err != nil {
 		return customerID, customerPlanID, telemetry.Error(ctx, span, err, fmt.Sprintf("error while creating customer with plan %s", planID))
 	}
@@ -69,17 +73,16 @@ func (m MetronomeClient) CreateCustomerWithPlan(ctx context.Context, userEmail s
 }
 
 // createCustomer will create the customer in Metronome
-func (m MetronomeClient) createCustomer(ctx context.Context, userEmail string, projectName string, projectID uint, billingID string) (customerID uuid.UUID, err error) {
+func (m MetronomeClient) createCustomer(ctx context.Context, userEmail string, projectName string, projectID string, billingID string) (customerID uuid.UUID, err error) {
 	ctx, span := telemetry.NewSpan(ctx, "create-metronome-customer")
 	defer span.End()
 
 	path := "customers"
-	projIDStr := strconv.FormatUint(uint64(projectID), 10)
 
 	customer := types.Customer{
 		Name: projectName,
 		Aliases: []string{
-			projIDStr,
+			projectID,
 		},
 		BillingConfig: types.BillingConfig{
 			BillingProviderType:       "stripe",
@@ -87,7 +90,7 @@ func (m MetronomeClient) createCustomer(ctx context.Context, userEmail string, p
 			StripeCollectionMethod:    defaultCollectionMethod,
 		},
 		CustomFields: map[string]string{
-			"project_id": projIDStr,
+			"project_id": projectID,
 			"user_email": userEmail,
 		},
 	}