瀏覽代碼

Add more error handling

Mauricio Araujo 2 年之前
父節點
當前提交
2ccb8ad05d

+ 2 - 3
api/server/handlers/billing/list.go

@@ -84,9 +84,8 @@ func (c *CheckPaymentEnabledHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 		)
 	}
 
-	if c.Config().ServerConf.MetronomeAPIKey != "" && c.Config().ServerConf.PorterCloudPlanID != "" &&
-		proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) && proj.UsageID == uuid.Nil {
-		customerID, customerPlanID, err := c.Config().BillingManager.MetronomeClient.CreateCustomerWithPlan(ctx, user.Email, proj.Name, proj.ID, proj.BillingID, c.Config().ServerConf.PorterCloudPlanID)
+	if c.Config().BillingManager.MetronomeEnabled && proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) && proj.UsageID == uuid.Nil {
+		customerID, customerPlanID, err := c.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")
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))

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

@@ -33,7 +33,7 @@ func (c *ListPlansHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	if !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
+	if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
 		c.WriteResult(w, r, "")
 
 		telemetry.WithAttributes(span,
@@ -79,7 +79,7 @@ func (c *ListCreditsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	if !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
+	if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
 		c.WriteResult(w, r, "")
 
 		telemetry.WithAttributes(span,
@@ -126,7 +126,7 @@ func (c *GetUsageDashboardHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	if !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
+	if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
 		c.WriteResult(w, r, "")
 
 		telemetry.WithAttributes(span,

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

@@ -98,8 +98,8 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	// Create Metronome customer and add to starter plan
-	if p.Config().ServerConf.MetronomeAPIKey != "" && p.Config().ServerConf.PorterCloudPlanID != "" && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
-		customerID, customerPlanID, err := p.Config().BillingManager.MetronomeClient.CreateCustomerWithPlan(ctx, user.Email, proj.Name, proj.ID, proj.BillingID, p.Config().ServerConf.PorterCloudPlanID)
+	if p.Config().BillingManager.MetronomeEnabled && 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")
 			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))

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

@@ -92,8 +92,7 @@ func (p *ProjectDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	if p.Config().ServerConf.MetronomeAPIKey != "" && p.Config().ServerConf.PorterCloudPlanID != "" &&
-		proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
+	if p.Config().BillingManager.MetronomeEnabled && 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"

+ 1 - 0
api/server/shared/config/env/envconfs.go

@@ -73,6 +73,7 @@ type ServerConf struct {
 	StripePublishableKey string `env:"STRIPE_PUBLISHABLE_KEY"`
 	MetronomeAPIKey      string `env:"METRONOME_API_KEY"`
 	PorterCloudPlanID    string `env:"PORTER_CLOUD_PLAN_ID"`
+	PorterStandardPlanID string `env:"PORTER_STANDARD_PLAN_ID"`
 
 	// This endpoint will be passed to the porter-agent so that
 	// the billing manager can query Prometheus.

+ 13 - 7
api/server/shared/config/loader/loader.go

@@ -334,8 +334,9 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 	}
 
 	var (
-		stripeClient    billing.StripeClient
-		metronomeClient billing.MetronomeClient
+		stripeClient     billing.StripeClient
+		metronomeClient  billing.MetronomeClient
+		metronomeEnabled bool
 	)
 	if sc.StripeSecretKey != "" {
 		stripeClient = billing.NewStripeClient(InstanceEnvConf.ServerConf.StripeSecretKey, InstanceEnvConf.ServerConf.StripePublishableKey)
@@ -343,16 +344,21 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 		res.Logger.Info().Msg("STRIPE_SECRET_KEY not set, all Stripe functionality will be disabled")
 	}
 
-	if sc.MetronomeAPIKey != "" {
-		metronomeClient = billing.NewMetronomeClient(InstanceEnvConf.ServerConf.MetronomeAPIKey)
+	if sc.MetronomeAPIKey != "" && sc.PorterCloudPlanID != "" && sc.PorterStandardPlanID != "" {
+		metronomeClient, err = billing.NewMetronomeClient(InstanceEnvConf.ServerConf.MetronomeAPIKey, InstanceEnvConf.ServerConf.PorterCloudPlanID, InstanceEnvConf.ServerConf.PorterStandardPlanID)
+		if err != nil {
+			return nil, fmt.Errorf("unable to create metronome client: %w", err)
+		}
+		metronomeEnabled = true
 	} else {
-		res.Logger.Info().Msg("METRONOME_API_KEY not set, all Metronome functionality will be disabled")
+		res.Logger.Info().Msg("METRONOME_API_KEY, PORTER_CLOUD_PLAN_ID, or PORTER_STANDARD_PLAN_ID not set, all Metronome functionality will be disabled")
 	}
 
 	res.Logger.Info().Msg("Creating billing manager")
 	res.BillingManager = billing.Manager{
-		StripeClient:    stripeClient,
-		MetronomeClient: metronomeClient,
+		StripeClient:     stripeClient,
+		MetronomeClient:  metronomeClient,
+		MetronomeEnabled: metronomeEnabled,
 	}
 	res.Logger.Info().Msg("Created billing manager")
 

+ 1 - 2
dashboard/src/lib/hooks/useStripe.tsx

@@ -213,7 +213,6 @@ export const useCustomerDashboard = (dashboard: string): TGetUsageDashboard => {
           project_id: currentProject?.id,
         }
       );
-      console.log(res);
       return res.data;
     },
     {
@@ -300,7 +299,7 @@ export const useCustomerPlan = (): TGetPlan => {
   );
 
   return {
-    plan: planReq.data,
+    plan: planReq.data != "" ? planReq.data : null,
   };
 };
 

+ 41 - 41
dashboard/src/main/home/project-settings/BillingPage.tsx

@@ -209,54 +209,54 @@ function BillingPage(): JSX.Element {
             </Text>
             <Spacer y={1} />
 
-            <Text>Active Plan</Text>
-            <Spacer y={0.5} />
-            {plan !== undefined ? (
-              <Fieldset>
-                <Container row spaced>
-                  <Container row>
-                    <Text color="helper">{plan.plan_name}</Text>
+            {plan != null && plan.plan_name != "" ? (
+              <div>
+                <Text>Active Plan</Text>
+                <Spacer y={0.5} />
+                <Fieldset>
+                  <Container row spaced>
+                    <Container row>
+                      <Text color="helper">{plan.plan_name}</Text>
+                    </Container>
+                    <Container row>
+                      {plan.trial_info !== undefined &&
+                      plan.trial_info.ending_before !== "" ? (
+                        <Text>
+                          Free trial ends{" "}
+                          {relativeTime(plan.trial_info.ending_before)}
+                        </Text>
+                      ) : (
+                        <Text>Started on {readableDate(plan.starting_on)}</Text>
+                      )}
+                    </Container>
                   </Container>
-                  <Container row>
-                    {plan.trial_info !== undefined ? (
-                      <Text>
-                        Free trial ends{" "}
-                        {relativeTime(plan.trial_info.ending_before)}
-                      </Text>
-                    ) : (
-                      <Text>Started on {readableDate(plan.starting_on)}</Text>
+                </Fieldset>
+                <Text size={16}>Current Usage</Text>
+                <Spacer y={1} />
+                <Text color="helper">
+                  View the current usage of this billing period.
+                </Text>
+                <Spacer y={1} />{" "}
+                <Container row style={{ width: "100%", height: "70vh" }}>
+                  <ParentSize>
+                    {({ width, height }) => (
+                      <iframe
+                        width={width}
+                        height={height}
+                        src={usageDashboard}
+                        scrolling="no"
+                        frameBorder={0}
+                        allowTransparency={true}
+                      ></iframe>
                     )}
-                  </Container>
+                  </ParentSize>
                 </Container>
-              </Fieldset>
+              </div>
             ) : (
-              <Loading></Loading>
+              <Text>This project doesn't have an active billing plan.</Text>
             )}
             <Spacer y={2} />
           </div>
-
-          <div>
-            <Text size={16}>Current Usage</Text>
-            <Spacer y={1} />
-            <Text color="helper">
-              View the current usage of this billing period.
-            </Text>
-            <Spacer y={1} />{" "}
-            <Container row style={{ width: "100%", height: "70vh" }}>
-              <ParentSize>
-                {({ width, height }) => (
-                  <iframe
-                    width={width}
-                    height={height}
-                    src={usageDashboard}
-                    scrolling="no"
-                    frameBorder={0}
-                    allowTransparency={true}
-                  ></iframe>
-                )}
-              </ParentSize>
-            </Container>
-          </div>
         </div>
       ) : (
         <div></div>

+ 3 - 2
internal/billing/billing.go

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

+ 29 - 11
internal/billing/metronome.go

@@ -25,32 +25,46 @@ const (
 
 // MetronomeClient is the client used to call the Metronome API
 type MetronomeClient struct {
-	ApiKey string
+	ApiKey               string
+	PorterCloudPlanID    uuid.UUID
+	PorterStandardPlanID uuid.UUID
 }
 
 // NewMetronomeClient returns a new Metronome client
-func NewMetronomeClient(metronomeApiKey string) MetronomeClient {
-	return MetronomeClient{
-		ApiKey: metronomeApiKey,
+func NewMetronomeClient(metronomeApiKey string, porterCloudPlanID string, porterStandardPlanID string) (client MetronomeClient, err error) {
+	porterCloudPlanUUID, err := uuid.Parse(porterCloudPlanID)
+	if err != nil {
+		return client, err
+	}
+
+	porterStandardPlanUUID, err := uuid.Parse(porterStandardPlanID)
+	if err != nil {
+		return client, err
 	}
+
+	return MetronomeClient{
+		ApiKey:               metronomeApiKey,
+		PorterCloudPlanID:    porterCloudPlanUUID,
+		PorterStandardPlanID: porterStandardPlanUUID,
+	}, nil
 }
 
 // CreateCustomerWithPlan will create the customer in Metronome and immediately add it to the plan
-func (m MetronomeClient) CreateCustomerWithPlan(ctx context.Context, userEmail string, projectName string, projectID uint, billingID string, planID string) (customerID uuid.UUID, customerPlanID uuid.UUID, err error) {
+func (m MetronomeClient) CreateCustomerWithPlan(ctx context.Context, userEmail string, projectName string, projectID uint, billingID string, sandboxEnabled bool) (customerID uuid.UUID, customerPlanID uuid.UUID, err error) {
 	ctx, span := telemetry.NewSpan(ctx, "add-metronome-customer-plan")
 	defer span.End()
 
-	porterCloudPlanID, err := uuid.Parse(planID)
-	if err != nil {
-		return customerID, customerPlanID, telemetry.Error(ctx, span, err, "error parsing starter plan id")
+	planID := m.PorterStandardPlanID
+	if sandboxEnabled {
+		planID = m.PorterCloudPlanID
 	}
 
 	customerID, err = m.createCustomer(ctx, userEmail, projectName, projectID, billingID)
 	if err != nil {
-		return customerID, customerPlanID, telemetry.Error(ctx, span, err, "error while creatinc customer with plan")
+		return customerID, customerPlanID, telemetry.Error(ctx, span, err, fmt.Sprintf("error while creating customer with plan %s", planID))
 	}
 
-	customerPlanID, err = m.addCustomerPlan(ctx, customerID, porterCloudPlanID)
+	customerPlanID, err = m.addCustomerPlan(ctx, customerID, planID)
 
 	return customerID, customerPlanID, err
 }
@@ -145,7 +159,11 @@ func (m MetronomeClient) ListCustomerPlan(ctx context.Context, customerID uuid.U
 		return plan, telemetry.Error(ctx, span, err, "failed to list customer plans")
 	}
 
-	return result.Data[0], nil
+	if len(result.Data) > 0 {
+		plan = result.Data[0]
+	}
+
+	return plan, nil
 }
 
 // EndCustomerPlan will immediately end the plan for the given customer

+ 3 - 0
zarf/helm/.serverenv

@@ -78,3 +78,6 @@ METRONOME_API_KEY=
 
 # PORTER_CLOUD_PLAN_ID is the id of the starter plan in Metronome. Only used if METRONOME_API_KEY is set
 PORTER_CLOUD_PLAN_ID=
+
+# PORTER_STANDARD_PLAN_ID is the id of the standard plan in Metronome. Only used if METRONOME_API_KEY is set
+PORTER_STANDARD_PLAN_ID=