Przeglądaj źródła

Remove endpoint for creating customers, improve error handling

Mauricio Araujo 2 lat temu
rodzic
commit
2b5debbb06

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

@@ -42,7 +42,7 @@ func (c *CreateBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	clientSecret, err := c.Config().BillingManager.CreatePaymentMethod(ctx, proj)
+	clientSecret, err := c.Config().BillingManager.CreatePaymentMethod(ctx, proj.BillingID)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error creating payment method")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error creating payment method: %w", err)))
@@ -81,7 +81,7 @@ func (c *SetDefaultBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	err := c.Config().BillingManager.SetDefaultPaymentMethod(ctx, paymentMethodID, proj)
+	err := c.Config().BillingManager.SetDefaultPaymentMethod(ctx, paymentMethodID, proj.BillingID)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error setting default payment method")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error setting default payment method: %w", err)))

+ 0 - 103
api/server/handlers/billing/customer.go

@@ -1,103 +0,0 @@
-package billing
-
-import (
-	"fmt"
-	"net/http"
-
-	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/shared"
-	"github.com/porter-dev/porter/api/server/shared/apierrors"
-	"github.com/porter-dev/porter/api/server/shared/config"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/telemetry"
-)
-
-// CreateBillingCustomerHandler will create a new handler
-// for creating customers in the billing provider
-type CreateBillingCustomerHandler struct {
-	handlers.PorterHandlerReadWriter
-}
-
-// NewCreateBillingCustomerIfNotExists will create a new CreateBillingCustomerIfNotExists
-func NewCreateBillingCustomerIfNotExists(
-	config *config.Config,
-	writer shared.ResultWriter,
-) *CreateBillingCustomerHandler {
-	return &CreateBillingCustomerHandler{
-		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
-	}
-}
-
-func (c *CreateBillingCustomerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	ctx, span := telemetry.NewSpan(r.Context(), "create-billing-customer-endpoint")
-	defer span.End()
-
-	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
-	user, _ := r.Context().Value(types.UserScope).(*models.User)
-
-	if proj.BillingID != "" {
-		c.WriteResult(w, r, "")
-		return
-	}
-
-	// Create customer in Stripe
-	customerID, err := c.Config().BillingManager.CreateCustomer(ctx, user.Email, proj)
-	if err != nil {
-		err := telemetry.Error(ctx, span, err, "error creating billing customer")
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error creating billing customer: %w", err)))
-		return
-	}
-
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
-		telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
-		telemetry.AttributeKV{Key: "user-email", Value: user.Email},
-	)
-
-	// Update the project record with the customer ID
-	proj.BillingID = customerID
-	_, err = c.Repo().Project().UpdateProject(proj)
-	if err != nil {
-		err := telemetry.Error(ctx, span, err, "error updating project")
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error updating project: %w", err)))
-		return
-	}
-
-	c.WriteResult(w, r, "")
-}
-
-// GetPublishableKeyHandler will return the configured publishable key
-type GetPublishableKeyHandler struct {
-	handlers.PorterHandlerReadWriter
-}
-
-// NewGetPublishableKeyHandler will return the publishable key
-func NewGetPublishableKeyHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-	writer shared.ResultWriter,
-) *GetPublishableKeyHandler {
-	return &GetPublishableKeyHandler{
-		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
-	}
-}
-
-func (c *GetPublishableKeyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	ctx, span := telemetry.NewSpan(r.Context(), "get-publishable-key-endpoint")
-	defer span.End()
-
-	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
-
-	// There is no easy way to pass environment variables to the frontend,
-	// so for now pass via the backend. This is acceptable because the key is
-	// meant to be public
-	publishableKey := c.Config().BillingManager.GetPublishableKey(ctx)
-
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
-		telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
-	)
-
-	c.WriteResult(w, r, publishableKey)
-}

+ 67 - 0
api/server/handlers/billing/key.go

@@ -0,0 +1,67 @@
+package billing
+
+import (
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/telemetry"
+)
+
+// GetPublishableKeyHandler will return the configured publishable key
+type GetPublishableKeyHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+// NewGetPublishableKeyHandler will return the publishable key
+func NewGetPublishableKeyHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *GetPublishableKeyHandler {
+	return &GetPublishableKeyHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *GetPublishableKeyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	ctx, span := telemetry.NewSpan(r.Context(), "get-publishable-key-endpoint")
+	defer span.End()
+
+	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
+	user, _ := ctx.Value(types.UserScope).(*models.User)
+
+	// Create billing customer for project and set the billing ID if it doesn't exist
+	if proj.BillingID == "" {
+		billingID, err := c.Config().BillingManager.CreateCustomer(ctx, user.Email, proj.ID, proj.Name)
+		if err != nil {
+			err = telemetry.Error(ctx, span, err, "error creating billing customer")
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+		proj.BillingID = billingID
+
+		_, err = c.Repo().Project().UpdateProject(proj)
+		if err != nil {
+			err := telemetry.Error(ctx, span, err, "error updating project")
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
+	// There is no easy way to pass environment variables to the frontend,
+	// so for now pass via the backend. This is acceptable because the key is
+	// meant to be public
+	publishableKey := c.Config().BillingManager.GetPublishableKey(ctx)
+
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
+		telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
+	)
+
+	c.WriteResult(w, r, publishableKey)
+}

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

@@ -38,8 +38,27 @@ func (c *ListBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	defer span.End()
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
+	user, _ := ctx.Value(types.UserScope).(*models.User)
 
-	paymentMethods, err := c.Config().BillingManager.ListPaymentMethod(ctx, proj)
+	// Create billing customer for project and set the billing ID if it doesn't exist
+	if proj.BillingID == "" {
+		billingID, err := c.Config().BillingManager.CreateCustomer(ctx, user.Email, proj.ID, proj.Name)
+		if err != nil {
+			err = telemetry.Error(ctx, span, err, "error creating billing customer")
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+		proj.BillingID = billingID
+
+		_, err = c.Repo().Project().UpdateProject(proj)
+		if err != nil {
+			err := telemetry.Error(ctx, span, err, "error updating project")
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
+	paymentMethods, err := c.Config().BillingManager.ListPaymentMethod(ctx, proj.BillingID)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error listing payment method")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error listing payment method: %w", err)))
@@ -65,7 +84,7 @@ func (c *CheckPaymentEnabledHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	paymentEnabled, err := c.Config().BillingManager.CheckPaymentEnabled(ctx, proj)
+	paymentEnabled, err := c.Config().BillingManager.CheckPaymentEnabled(ctx, proj.BillingID)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error checking if payment enabled")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error checking if payment enabled: %w", err)))

+ 18 - 17
api/server/handlers/project/create.go

@@ -55,23 +55,6 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	var err error
 
-	if p.Config().ServerConf.StripeSecretKey != "" && p.Config().ServerConf.StripePublishableKey != "" {
-		// Create billing customer for project and set the billing ID
-		billingID, err := p.Config().BillingManager.CreateCustomer(ctx, user.Email, proj)
-		if err != nil {
-			err = telemetry.Error(ctx, span, err, "error creating billing customer")
-			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
-		}
-		proj.BillingID = billingID
-
-		telemetry.WithAttributes(span,
-			telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
-			telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
-			telemetry.AttributeKV{Key: "user-email", Value: user.Email},
-		)
-	}
-
 	proj, _, err = CreateProjectWithUser(p.Repo().Project(), proj, user)
 	if err != nil {
 		err = telemetry.Error(ctx, span, err, "error creating project with user")
@@ -96,6 +79,24 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
+	if p.Config().ServerConf.StripeSecretKey != "" && p.Config().ServerConf.StripePublishableKey != "" {
+		// Create billing customer for project and set the billing ID
+		billingID, err := p.Config().BillingManager.CreateCustomer(ctx, user.Email, proj.ID, proj.Name)
+		if err != nil {
+			err = telemetry.Error(ctx, span, err, "error creating billing customer")
+			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+		proj.BillingID = billingID
+
+		_, err = p.Repo().Project().UpdateProject(proj)
+		if err != nil {
+			err := telemetry.Error(ctx, span, err, "error updating project")
+			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
 	// create default project usage restriction
 	_, err = p.Repo().ProjectUsage().CreateProjectUsage(&models.ProjectUsage{
 		ProjectID:      proj.ID,

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

@@ -92,7 +92,7 @@ func (p *ProjectDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	err = p.Config().BillingManager.DeleteCustomer(ctx, proj)
+	err = p.Config().BillingManager.DeleteCustomer(ctx, proj.BillingID)
 	if err != nil {
 		e := "error deleting project in billing provider"
 		err = telemetry.Error(ctx, span, err, e)

+ 0 - 27
api/server/router/project.go

@@ -423,33 +423,6 @@ func getProjectRoutes(
 		Router:   r,
 	})
 
-	// POST /api/projects/{project_id}/billing/customer/ -> project.NewGetOrCreateCustomerHandler
-	getOrCreateBillingCustomerEndpoint := factory.NewAPIEndpoint(
-		&types.APIRequestMetadata{
-			Verb:   types.APIVerbCreate,
-			Method: types.HTTPVerbPost,
-			Path: &types.Path{
-				Parent:       basePath,
-				RelativePath: relPath + "/billing/customer",
-			},
-			Scopes: []types.PermissionScope{
-				types.UserScope,
-				types.ProjectScope,
-			},
-		},
-	)
-
-	getOrCreateBillingCustomerHandler := billing.NewCreateBillingCustomerIfNotExists(
-		config,
-		factory.GetResultWriter(),
-	)
-
-	routes = append(routes, &router.Route{
-		Endpoint: getOrCreateBillingCustomerEndpoint,
-		Handler:  getOrCreateBillingCustomerHandler,
-		Router:   r,
-	})
-
 	// GET /api/projects/{project_id}/billing/publishable_key -> project.NewGetPublishableKeyHandler
 	publishableKeyEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 0 - 21
dashboard/src/lib/hooks/useStripe.tsx

@@ -150,27 +150,6 @@ export const checkIfProjectHasPayment = (): TCheckHasPaymentEnabled => {
   };
 };
 
-export const checkBillingCustomerExists = () => {
-  const { currentProject } = useContext(Context);
-
-  useQuery(["checkCustomerExists", currentProject?.id], async () => {
-    if (!currentProject?.id || currentProject.id === -1) {
-      return;
-    }
-
-    if (!currentProject?.billing_enabled) {
-      return;
-    }
-
-    const res = await api.checkBillingCustomerExists(
-      "<token>",
-      {},
-      { project_id: currentProject?.id }
-    );
-    return res.data;
-  });
-};
-
 export const usePublishableKey = (): TGetPublishableKey => {
   const { user, currentProject } = useContext(Context);
 

+ 0 - 4
dashboard/src/main/home/Home.tsx

@@ -17,7 +17,6 @@ import Modal from "components/porter/Modal";
 import ShowIntercomButton from "components/porter/ShowIntercomButton";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
-import { checkBillingCustomerExists } from "lib/hooks/useStripe";
 
 import api from "shared/api";
 import { withAuth, type WithAuthProps } from "shared/auth/AuthorizationHoc";
@@ -294,9 +293,6 @@ const Home: React.FC<Props> = (props) => {
     prevCurrentCluster.current = props.currentCluster;
   }, [props.currentCluster]);
 
-  // Create Stripe customer if it doesn't exists already
-  checkBillingCustomerExists();
-
   const projectOverlayCall = async () => {
     try {
       const projectList = await api

+ 0 - 8
dashboard/src/shared/api.tsx

@@ -3441,13 +3441,6 @@ const removeStackEnvGroup = baseApi<
 );
 
 // Billing
-const checkBillingCustomerExists = baseApi<
-  {},
-  {
-    project_id?: number;
-  }
->("POST", ({ project_id }) => `/api/projects/${project_id}/billing/customer`);
-
 const getPublishableKey = baseApi<
   {},
   {
@@ -3854,7 +3847,6 @@ export default {
   removeStackEnvGroup,
 
   // BILLING
-  checkBillingCustomerExists,
   getPublishableKey,
   listPaymentMethod,
   addPaymentMethod,

+ 12 - 13
internal/billing/billing.go

@@ -4,29 +4,28 @@ import (
 	"context"
 
 	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/models"
 )
 
 // BillingManager contains methods for managing billing for a project
 type BillingManager interface {
 	// CreateCustomer registers a project in the billing provider. This is currently a one-to-one
 	// mapping with projects and billing customers, because billing and usage are set per project.
-	CreateCustomer(ctx context.Context, userEmail string, proj *models.Project) (customerID string, err error)
+	CreateCustomer(ctx context.Context, userEmail string, projectID uint, projectName string) (customerID string, err error)
 
 	// DeleteCustomer will delete the customer from the billing provider
-	DeleteCustomer(ctx context.Context, proj *models.Project) (err error)
+	DeleteCustomer(ctx context.Context, customerID string) (err error)
 
 	// CheckPaymentEnabled will check if the project has a payment method configured
-	CheckPaymentEnabled(ctx context.Context, proj *models.Project) (paymentEnabled bool, err error)
+	CheckPaymentEnabled(ctx context.Context, customerID string) (paymentEnabled bool, err error)
 
 	// ListPaymentMethod will return all payment methods for the project
-	ListPaymentMethod(ctx context.Context, proj *models.Project) (paymentMethods []types.PaymentMethod, err error)
+	ListPaymentMethod(ctx context.Context, customerID string) (paymentMethods []types.PaymentMethod, err error)
 
 	// CreatePaymentMethod will add a new payment method to the project in Stripe
-	CreatePaymentMethod(ctx context.Context, proj *models.Project) (clientSecret string, err error)
+	CreatePaymentMethod(ctx context.Context, customerID string) (clientSecret string, err error)
 
 	// SetDefaultPaymentMethod will set the payment method as default in the customer invoice settings
-	SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, proj *models.Project) (err error)
+	SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, customerID string) (err error)
 
 	// DeletePaymentMethod will remove a payment method for the project in Stripe
 	DeletePaymentMethod(ctx context.Context, paymentMethodID string) (err error)
@@ -39,32 +38,32 @@ type BillingManager interface {
 type NoopBillingManager struct{}
 
 // CreateCustomer is a no-op
-func (s *NoopBillingManager) CreateCustomer(ctx context.Context, userEmail string, proj *models.Project) (customerID string, err error) {
+func (s *NoopBillingManager) CreateCustomer(ctx context.Context, userEmail string, projectID uint, projectName string) (customerID string, err error) {
 	return "", nil
 }
 
 // DeleteCustomer is a no-op
-func (s *NoopBillingManager) DeleteCustomer(ctx context.Context, proj *models.Project) (err error) {
+func (s *NoopBillingManager) DeleteCustomer(ctx context.Context, customerID string) (err error) {
 	return nil
 }
 
 // CheckPaymentEnabled is a  no-op
-func (s *NoopBillingManager) CheckPaymentEnabled(ctx context.Context, proj *models.Project) (paymentEnabled bool, err error) {
+func (s *NoopBillingManager) CheckPaymentEnabled(ctx context.Context, customerID string) (paymentEnabled bool, err error) {
 	return false, nil
 }
 
 // ListPaymentMethod is a no-op
-func (s *NoopBillingManager) ListPaymentMethod(ctx context.Context, proj *models.Project) (paymentMethods []types.PaymentMethod, err error) {
+func (s *NoopBillingManager) ListPaymentMethod(ctx context.Context, customerID string) (paymentMethods []types.PaymentMethod, err error) {
 	return []types.PaymentMethod{}, nil
 }
 
 // CreatePaymentMethod is a no-op
-func (s *NoopBillingManager) CreatePaymentMethod(ctx context.Context, proj *models.Project) (clientSecret string, err error) {
+func (s *NoopBillingManager) CreatePaymentMethod(ctx context.Context, customerID string) (clientSecret string, err error) {
 	return "", nil
 }
 
 // SetDefaultPaymentMethod is a no-op
-func (s *NoopBillingManager) SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, proj *models.Project) (err error) {
+func (s *NoopBillingManager) SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, customerID string) (err error) {
 	return nil
 }
 

+ 66 - 41
internal/billing/stripe.go

@@ -6,7 +6,6 @@ import (
 	"strconv"
 
 	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/telemetry"
 	"github.com/stripe/stripe-go/v76"
 	"github.com/stripe/stripe-go/v76/customer"
@@ -22,71 +21,81 @@ type StripeBillingManager struct {
 }
 
 // CreateCustomer will create a customer in Stripe only if the project doesn't have a BillingID
-func (s *StripeBillingManager) CreateCustomer(ctx context.Context, userEmail string, proj *models.Project) (customerID string, err error) {
+func (s *StripeBillingManager) CreateCustomer(ctx context.Context, userEmail string, projectID uint, projectName string) (customerID string, err error) {
 	ctx, span := telemetry.NewSpan(ctx, "create-stripe-customer")
 	defer span.End()
 
+	if projectID == 0 || projectName == "" {
+		return "", fmt.Errorf("invalid project id or name")
+	}
+
 	stripe.Key = s.StripeSecretKey
 
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "billing-id", Value: proj.BillingID},
-	)
+	// Create customer if not exists
+	customerName := fmt.Sprintf("project_%s", projectName)
+	projectIDStr := strconv.FormatUint(uint64(projectID), 10)
+	params := &stripe.CustomerParams{
+		Name:  stripe.String(customerName),
+		Email: stripe.String(userEmail),
+		Metadata: map[string]string{
+			"porter_project_id": projectIDStr,
+		},
+	}
 
-	if proj.BillingID == "" {
-		// Create customer if not exists
-		customerName := fmt.Sprintf("project_%s", proj.Name)
-		projectIDStr := strconv.FormatUint(uint64(proj.ID), 10)
-		params := &stripe.CustomerParams{
-			Name:  stripe.String(customerName),
-			Email: stripe.String(userEmail),
-			Metadata: map[string]string{
-				"porter_project_id": projectIDStr,
-			},
-		}
+	// Create in Stripe
+	customer, err := customer.New(params)
+	if err != nil {
+		return "", telemetry.Error(ctx, span, err, "failed to create Stripe customer")
+	}
 
-		// Create in Stripe
-		customer, err := customer.New(params)
-		if err != nil {
-			return "", telemetry.Error(ctx, span, err, "failed to create Stripe customer")
-		}
+	customerID = customer.ID
 
-		customerID = customer.ID
-	}
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: projectIDStr},
+		telemetry.AttributeKV{Key: "customer-id", Value: customerID},
+		telemetry.AttributeKV{Key: "user-email", Value: userEmail},
+	)
 
 	return customerID, nil
 }
 
 // DeleteCustomer will delete the customer from the billing provider
-func (s *StripeBillingManager) DeleteCustomer(ctx context.Context, proj *models.Project) (err error) {
+func (s *StripeBillingManager) DeleteCustomer(ctx context.Context, customerID string) (err error) {
 	ctx, span := telemetry.NewSpan(ctx, "delete-stripe-customer")
 	defer span.End()
 
+	if customerID == "" {
+		return nil
+	}
+
 	stripe.Key = s.StripeSecretKey
 
 	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "billing-id", Value: proj.BillingID},
+		telemetry.AttributeKV{Key: "billing-id", Value: customerID},
 	)
 
-	if proj.BillingID != "" {
-		params := &stripe.CustomerParams{}
-		_, err := customer.Del(proj.BillingID, params)
-		if err != nil {
-			return telemetry.Error(ctx, span, err, "failed to delete Stripe customer")
-		}
+	params := &stripe.CustomerParams{}
+	_, err = customer.Del(customerID, params)
+	if err != nil {
+		return telemetry.Error(ctx, span, err, "failed to delete Stripe customer")
 	}
 
 	return nil
 }
 
 // CheckPaymentEnabled will return true if the project has a payment method added, false otherwise
-func (s *StripeBillingManager) CheckPaymentEnabled(ctx context.Context, proj *models.Project) (paymentEnabled bool, err error) {
+func (s *StripeBillingManager) CheckPaymentEnabled(ctx context.Context, customerID string) (paymentEnabled bool, err error) {
 	_, span := telemetry.NewSpan(ctx, "check-stripe-payment-enabled")
 	defer span.End()
 
+	if customerID == "" {
+		return false, fmt.Errorf("customer id cannot be empty")
+	}
+
 	stripe.Key = s.StripeSecretKey
 
 	params := &stripe.PaymentMethodListParams{
-		Customer: stripe.String(proj.BillingID),
+		Customer: stripe.String(customerID),
 		Type:     stripe.String(string(stripe.PaymentMethodTypeCard)),
 	}
 	result := paymentmethod.List(params)
@@ -95,20 +104,24 @@ func (s *StripeBillingManager) CheckPaymentEnabled(ctx context.Context, proj *mo
 }
 
 // ListPaymentMethod will return all payment methods for the project
-func (s *StripeBillingManager) ListPaymentMethod(ctx context.Context, proj *models.Project) (paymentMethods []types.PaymentMethod, err error) {
+func (s *StripeBillingManager) ListPaymentMethod(ctx context.Context, customerID string) (paymentMethods []types.PaymentMethod, err error) {
 	ctx, span := telemetry.NewSpan(ctx, "list-stripe-payment-method")
 	defer span.End()
 
+	if customerID == "" {
+		return paymentMethods, fmt.Errorf("customer id cannot be empty")
+	}
+
 	stripe.Key = s.StripeSecretKey
 
 	// Get configured payment methods
 	params := &stripe.PaymentMethodListParams{
-		Customer: stripe.String(proj.BillingID),
+		Customer: stripe.String(customerID),
 		Type:     stripe.String(string(stripe.PaymentMethodTypeCard)),
 	}
 	result := paymentmethod.List(params)
 
-	defaultPaymentExists, defaultPaymentID, err := s.checkDefaultPaymentMethod(proj.BillingID)
+	defaultPaymentExists, defaultPaymentID, err := s.checkDefaultPaymentMethod(customerID)
 	if err != nil {
 		return paymentMethods, telemetry.Error(ctx, span, err, "failed to list Stripe payment method")
 	}
@@ -134,7 +147,7 @@ func (s *StripeBillingManager) ListPaymentMethod(ctx context.Context, proj *mode
 	// Set default payment method when project has payment methods enabled but
 	// no default setup
 	if len(paymentMethods) > 0 && !defaultPaymentExists {
-		err = s.SetDefaultPaymentMethod(ctx, paymentMethods[len(paymentMethods)-1].ID, proj)
+		err = s.SetDefaultPaymentMethod(ctx, paymentMethods[len(paymentMethods)-1].ID, customerID)
 		if err != nil {
 			return paymentMethods, telemetry.Error(ctx, span, err, "failed to list Stripe payment method")
 		}
@@ -144,14 +157,18 @@ func (s *StripeBillingManager) ListPaymentMethod(ctx context.Context, proj *mode
 }
 
 // CreatePaymentMethod will add a new payment method to the project in Stripe
-func (s *StripeBillingManager) CreatePaymentMethod(ctx context.Context, proj *models.Project) (clientSecret string, err error) {
+func (s *StripeBillingManager) CreatePaymentMethod(ctx context.Context, customerID string) (clientSecret string, err error) {
 	ctx, span := telemetry.NewSpan(ctx, "create-stripe-payment-method")
 	defer span.End()
 
+	if customerID == "" {
+		return "", fmt.Errorf("customer id cannot be empty")
+	}
+
 	stripe.Key = s.StripeSecretKey
 
 	params := &stripe.SetupIntentParams{
-		Customer: stripe.String(proj.BillingID),
+		Customer: stripe.String(customerID),
 		AutomaticPaymentMethods: &stripe.SetupIntentAutomaticPaymentMethodsParams{
 			Enabled: stripe.Bool(false),
 		},
@@ -167,10 +184,14 @@ func (s *StripeBillingManager) CreatePaymentMethod(ctx context.Context, proj *mo
 }
 
 // SetDefaultPaymentMethod will add a new payment method to the project in Stripe
-func (s *StripeBillingManager) SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, proj *models.Project) (err error) {
+func (s *StripeBillingManager) SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, customerID string) (err error) {
 	ctx, span := telemetry.NewSpan(ctx, "set-default-stripe-payment-method")
 	defer span.End()
 
+	if customerID == "" || paymentMethodID == "" {
+		return fmt.Errorf("empty customer id or payment method id")
+	}
+
 	stripe.Key = s.StripeSecretKey
 
 	params := &stripe.CustomerParams{
@@ -179,7 +200,7 @@ func (s *StripeBillingManager) SetDefaultPaymentMethod(ctx context.Context, paym
 		},
 	}
 
-	_, err = customer.Update(proj.BillingID, params)
+	_, err = customer.Update(customerID, params)
 	if err != nil {
 		return telemetry.Error(ctx, span, err, "failed to set default Stripe payment method")
 	}
@@ -192,6 +213,10 @@ func (s *StripeBillingManager) DeletePaymentMethod(ctx context.Context, paymentM
 	ctx, span := telemetry.NewSpan(ctx, "delete-stripe-payment-method")
 	defer span.End()
 
+	if paymentMethodID == "" {
+		return fmt.Errorf("payment method id cannot be empty")
+	}
+
 	stripe.Key = s.StripeSecretKey
 
 	_, err = paymentmethod.Detach(paymentMethodID, nil)