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

implement createteam and update endpoints for new internal billing

Alexander Belanger 3 лет назад
Родитель
Сommit
dfeeb30b11

+ 1 - 25
api/server/handlers/billing/billing_ce.go

@@ -1,3 +1,4 @@
+//go:build !ee
 // +build !ee
 
 package billing
@@ -10,19 +11,6 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 )
 
-type BillingGetTokenHandler struct {
-	handlers.PorterHandlerReader
-	handlers.Unavailable
-}
-
-func NewBillingGetTokenHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-	writer shared.ResultWriter,
-) http.Handler {
-	return handlers.NewUnavailable(config, "billing_get_token")
-}
-
 type BillingWebhookHandler struct {
 	handlers.PorterHandlerReader
 	handlers.Unavailable
@@ -34,15 +22,3 @@ func NewBillingWebhookHandler(
 ) http.Handler {
 	return handlers.NewUnavailable(config, "billing_webhook")
 }
-
-type BillingAddProjectHandler struct {
-	handlers.PorterHandlerReader
-	handlers.Unavailable
-}
-
-func NewBillingAddProjectHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-) http.Handler {
-	return handlers.NewUnavailable(config, "billing_add_project")
-}

+ 1 - 13
api/server/handlers/billing/billing_ee.go

@@ -1,3 +1,4 @@
+//go:build ee
 // +build ee
 
 package billing
@@ -11,24 +12,11 @@ import (
 	"github.com/porter-dev/porter/ee/api/server/handlers/billing"
 )
 
-var NewBillingGetTokenHandler func(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-	writer shared.ResultWriter,
-) http.Handler
-
 var NewBillingWebhookHandler func(
 	config *config.Config,
 	decoderValidator shared.RequestDecoderValidator,
 ) http.Handler
 
-var NewBillingAddProjectHandler func(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-) http.Handler
-
 func init() {
-	NewBillingGetTokenHandler = billing.NewBillingGetTokenHandler
 	NewBillingWebhookHandler = billing.NewBillingWebhookHandler
-	NewBillingAddProjectHandler = billing.NewBillingAddProjectHandler
 }

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

@@ -44,7 +44,7 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	var err error
-	proj, role, err := CreateProjectWithUser(p.Repo().Project(), proj, user)
+	proj, _, err = CreateProjectWithUser(p.Repo().Project(), proj, user)
 
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
@@ -79,7 +79,7 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	p.WriteResult(w, r, proj.ToProjectType())
 
 	// add project to billing team
-	teamID, err := p.Config().BillingManager.CreateTeam(proj)
+	_, err = p.Config().BillingManager.CreateTeam(proj)
 
 	if err != nil {
 		// we do not write error response, since setting up billing error can be
@@ -87,16 +87,6 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		p.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
 	}
 
-	if teamID != "" {
-		err = p.Config().BillingManager.AddUserToTeam(teamID, user, role)
-
-		if err != nil {
-			// we do not write error response, since setting up billing error can be
-			// resolved later and may not be fatal
-			p.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
-		}
-	}
-
 	p.Config().AnalyticsClient.Track(analytics.ProjectCreateTrack(&analytics.ProjectCreateTrackOpts{
 		ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(user.ID, proj.ID),
 	}))

+ 0 - 8
api/server/handlers/project/delete_role.go

@@ -54,12 +54,4 @@ func (p *RoleDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	p.WriteResult(w, r, res)
-
-	err = p.Config().BillingManager.RemoveUserFromTeam(role)
-
-	if err != nil {
-		// we do not write error response, since setting up billing error can be
-		// resolved later and may not be fatal
-		p.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
-	}
 }

+ 0 - 8
api/server/handlers/project/update_role.go

@@ -57,12 +57,4 @@ func (p *RoleUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	p.WriteResult(w, r, res)
-
-	err = p.Config().BillingManager.UpdateUserInTeam(role)
-
-	if err != nil {
-		// we do not write error response, since setting up billing error can be
-		// resolved later and may not be fatal
-		p.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
-	}
 }

+ 0 - 25
api/server/router/base.go

@@ -4,7 +4,6 @@ import (
 	"fmt"
 
 	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/api/server/handlers/billing"
 	"github.com/porter-dev/porter/api/server/handlers/credentials"
 	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"github.com/porter-dev/porter/api/server/handlers/healthcheck"
@@ -516,30 +515,6 @@ func GetBaseRoutes(
 		Router:   r,
 	})
 
-	// POST /api/internal/billing -> billing.NewBillingAddProjectHandler
-	addProjectBillingEndpoint := factory.NewAPIEndpoint(
-		&types.APIRequestMetadata{
-			Verb:   types.APIVerbCreate,
-			Method: types.HTTPVerbPost,
-			Path: &types.Path{
-				Parent:       basePath,
-				RelativePath: "/internal/billing",
-			},
-			Scopes: []types.PermissionScope{},
-		},
-	)
-
-	addProjectBillingHandler := billing.NewBillingAddProjectHandler(
-		config,
-		factory.GetDecoderValidator(),
-	)
-
-	routes = append(routes, &router.Route{
-		Endpoint: addProjectBillingEndpoint,
-		Handler:  addProjectBillingHandler,
-		Router:   r,
-	})
-
 	if config.ServerConf.GithubIncomingWebhookSecret != "" {
 		// POST /api/github/incoming_webhook/{webhook_id} -> webhook.NewGithubIncomingWebhook
 		githubIncomingWebhookEndpoint := factory.NewAPIEndpoint(

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

@@ -281,35 +281,6 @@ func getProjectRoutes(
 		Router:   r,
 	})
 
-	// GET /api/projects/{project_id}/billing/token -> billing.NewBillingGetTokenEndpoint
-	getBillingTokenEndpoint := factory.NewAPIEndpoint(
-		&types.APIRequestMetadata{
-			Verb:   types.APIVerbGet,
-			Method: types.HTTPVerbGet,
-			Path: &types.Path{
-				Parent:       basePath,
-				RelativePath: relPath + "/billing/token",
-			},
-			Scopes: []types.PermissionScope{
-				types.UserScope,
-				types.ProjectScope,
-				types.SettingsScope,
-			},
-		},
-	)
-
-	getBillingTokenHandler := billing.NewBillingGetTokenHandler(
-		config,
-		factory.GetDecoderValidator(),
-		factory.GetResultWriter(),
-	)
-
-	routes = append(routes, &router.Route{
-		Endpoint: getBillingTokenEndpoint,
-		Handler:  getBillingTokenHandler,
-		Router:   r,
-	})
-
 	// GET /api/billing_webhook -> billing.NewBillingWebhookHandler
 	getBillingWebhookEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 2 - 4
api/server/shared/config/loader/init_ee.go

@@ -1,3 +1,4 @@
+//go:build ee
 // +build ee
 
 package loader
@@ -6,7 +7,6 @@ import (
 	eeBilling "github.com/porter-dev/porter/ee/billing"
 	"github.com/porter-dev/porter/ee/integrations/vault"
 	"github.com/porter-dev/porter/ee/models"
-	eeGorm "github.com/porter-dev/porter/ee/repository/gorm"
 	"github.com/porter-dev/porter/internal/billing"
 )
 
@@ -24,14 +24,12 @@ func init() {
 		key[i] = b
 	}
 
-	eeRepo := eeGorm.NewEERepository(InstanceDB, &key)
-
 	if InstanceEnvConf.ServerConf.IronPlansAPIKey != "" && InstanceEnvConf.ServerConf.IronPlansServerURL != "" {
 		serverURL := InstanceEnvConf.ServerConf.IronPlansServerURL
 		apiKey := InstanceEnvConf.ServerConf.IronPlansAPIKey
 		var err error
 
-		InstanceBillingManager, err = eeBilling.NewClient(serverURL, apiKey, eeRepo)
+		InstanceBillingManager, err = eeBilling.NewClient(serverURL, apiKey)
 
 		if err != nil {
 			panic(err)

+ 7 - 1
dashboard/src/main/home/project-settings/ProjectSettings.tsx

@@ -119,7 +119,13 @@ class ProjectSettings extends Component<PropsType, StateType> {
       return (
         <Placeholder>
           <Helper>
-          Please contact <a href="mailto:support@porter.run">support@porter.run</a> to upgrade your project's usage limits.
+            Visit the{" "}
+            <a
+              href={`/api/projects/${this.context.currentProject?.id}/billing/redirect`}
+            >
+              billing portal
+            </a>{" "}
+            to view plans.
           </Helper>
         </Placeholder>
       );

+ 0 - 163
ee/api/server/handlers/billing/add_project.go

@@ -1,163 +0,0 @@
-package billing
-
-import (
-	"errors"
-	"fmt"
-	"net/http"
-	"strings"
-
-	"github.com/porter-dev/porter/api/server/authz"
-	"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"
-	"gorm.io/gorm"
-)
-
-type BillingAddProjectHandler struct {
-	handlers.PorterHandlerReadWriter
-	authz.KubernetesAgentGetter
-}
-
-func NewBillingAddProjectHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-) http.Handler {
-	return &BillingAddProjectHandler{
-		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, nil),
-	}
-}
-
-// Adds a project to a billing team in IronPlans. Takes the following steps:
-// 1. Looks for project billing data for the given project.
-// 2. Checks for project billing data. If the project already has billing data, move to step 3b, otherwise 3a.
-// 3a. Creates a new team in IronPlans, and creates a custom plan in IronPlans. Subscribes the team to the plan.
-// 3b. Finds the relevant team in IronPlans, creates a custom plan, and updates the subscription for the team.
-// 4. If team was created, creates ProjectBilling object.
-// 5. If team was created, finds all roles in the team. Adds all roles as a team member to the project billing. Updates UserBilling models.
-func (c *BillingAddProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	// validation for internal token
-	// if internal token is empty, throw forbidden error; this server is misconfigured
-	if c.Config().ServerConf.RetoolToken == "" {
-		c.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("internal retool token does not exist: re-configure the server")))
-		return
-	}
-
-	reqToken := r.Header.Get("Authorization")
-	splitToken := strings.Split(reqToken, "Bearer")
-
-	if len(splitToken) != 2 {
-		c.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("no token found")))
-		return
-	}
-
-	reqToken = strings.TrimSpace(splitToken[1])
-
-	if reqToken != c.Config().ServerConf.RetoolToken {
-		c.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("passed retool token does not match env")))
-		return
-	}
-
-	request := &types.AddProjectBillingRequest{}
-
-	if ok := c.DecodeAndValidate(w, r, request); !ok {
-		return
-	}
-
-	// make sure the project exists; if it does not exist, throw forbidden error
-	proj, err := c.Repo().Project().ReadProject(request.ProjectID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
-			return
-		}
-
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	// look for project billing data for the given project
-	teamID, err := c.Config().BillingManager.GetTeamID(proj)
-	isNotFound := err != nil && errors.Is(err, gorm.ErrRecordNotFound)
-
-	// if the error is not nil and is not "ErrRecordNotFound", throw error
-	if err != nil && !isNotFound {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	// if the team is not found, create a new team
-	if isNotFound {
-		teamID, err = c.Config().BillingManager.CreateTeam(proj)
-
-		if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
-		}
-	}
-
-	// determine whether to place the team on a custom plan or an existing plan
-	if request.ExistingPlanName != "" {
-		err = addToExistingPlan(c.Config(), request.ExistingPlanName, teamID)
-	} else {
-		err = addToCustomPlan(c.Config(), teamID, proj, request)
-	}
-
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	// add users in project to the plan
-	projRoles, err := c.Repo().Project().ListProjectRoles(proj.ID)
-
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	for _, role := range projRoles {
-		user, err := c.Repo().User().ReadUser(role.UserID)
-
-		if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
-		}
-
-		err = c.Config().BillingManager.AddUserToTeam(teamID, user, &role)
-
-		if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
-		}
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-func addToCustomPlan(c *config.Config, teamID string, proj *models.Project, req *types.AddProjectBillingRequest) error {
-	// create a new plan in IronPlans
-	planID, err := c.BillingManager.CreatePlan(teamID, proj, req)
-
-	if err != nil {
-		return err
-	}
-
-	// create a new subscription to this plan in IronPlans
-	return c.BillingManager.CreateOrUpdateSubscription(teamID, planID)
-}
-
-func addToExistingPlan(c *config.Config, existingPlanName, teamID string) error {
-	// look for existing plans in IronPlans
-	planID, err := c.BillingManager.GetExistingPublicPlan(existingPlanName)
-
-	if err != nil {
-		return err
-	}
-
-	// create a new subscription to this plan in IronPlans
-	return c.BillingManager.CreateOrUpdateSubscription(teamID, planID)
-}

+ 0 - 66
ee/api/server/handlers/billing/get_token.go

@@ -1,66 +0,0 @@
-package billing
-
-import (
-	"fmt"
-	"net/http"
-
-	"github.com/porter-dev/porter/api/server/authz"
-	"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"
-)
-
-type BillingGetTokenHandler struct {
-	handlers.PorterHandlerReadWriter
-	authz.KubernetesAgentGetter
-}
-
-func NewBillingGetTokenHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-	writer shared.ResultWriter,
-) http.Handler {
-	return &BillingGetTokenHandler{
-		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
-	}
-}
-
-func (c *BillingGetTokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	user, _ := r.Context().Value(types.UserScope).(*models.User)
-	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
-
-	// we double-check that the user is an admin the project
-	roles, err := c.Repo().Project().ListProjectRoles(proj.ID)
-
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	for _, role := range roles {
-		if role.UserID != 0 && role.UserID == user.ID {
-			if role.Kind != types.RoleAdmin {
-				c.HandleAPIError(w, r, apierrors.NewErrForbidden(
-					fmt.Errorf("user %d is not an admin in project %d", user.ID, proj.ID),
-				))
-
-				return
-			}
-		}
-	}
-
-	token, teamID, err := c.Config().BillingManager.GetIDToken(proj, user)
-
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	c.WriteResult(w, r, &types.GetBillingTokenResponse{
-		Token:  token,
-		TeamID: teamID,
-	})
-}

+ 1 - 19
ee/api/server/handlers/invite/accept.go

@@ -1,3 +1,4 @@
+//go:build ee
 // +build ee
 
 package invite
@@ -104,24 +105,5 @@ func (c *InviteAcceptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	// add project to billing team
-	teamID, err := c.Config().BillingManager.GetTeamID(proj)
-
-	if err != nil {
-		// we do not write error response, since setting up billing error can be
-		// resolved later and may not be fatal
-		c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
-	}
-
-	if teamID != "" {
-		err = c.Config().BillingManager.AddUserToTeam(teamID, user, role)
-
-		if err != nil {
-			// we do not write error response, since setting up billing error can be
-			// resolved later and may not be fatal
-			c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
-		}
-	}
-
 	http.Redirect(w, r, "/dashboard", 302)
 }

+ 267 - 0
ee/billing/client.go

@@ -0,0 +1,267 @@
+//go:build ee
+// +build ee
+
+package billing
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	cemodels "github.com/porter-dev/porter/internal/models"
+)
+
+// Client contains an API client for the internal billing engine
+type Client struct {
+	apiKey     string
+	serverURL  string
+	httpClient *http.Client
+}
+
+// NewClient creates a new billing API client
+func NewClient(serverURL, apiKey string) (*Client, error) {
+	httpClient := &http.Client{
+		Timeout: time.Minute,
+	}
+
+	client := &Client{apiKey, serverURL, httpClient}
+
+	return client, nil
+}
+
+func (c *Client) CreateTeam(user *cemodels.User, proj *cemodels.Project) (string, error) {
+	// call the internal billing endpoint to create a new customer in the database
+	reqData := &CreateCustomerRequest{
+		Email:     user.Email,
+		UserID:    user.ID,
+		ProjectID: proj.ID,
+	}
+
+	err := c.postRequest("/api/v1/private/customer", reqData, nil)
+
+	if err != nil {
+		return "", err
+	}
+
+	return fmt.Sprintf("%d-%d", proj.ID, user.ID), nil
+}
+
+func (c *Client) DeleteTeam(user *cemodels.User, proj *cemodels.Project) error {
+	// call delete customer
+	reqData := &DeleteCustomerRequest{
+		UserID:    user.ID,
+		ProjectID: proj.ID,
+	}
+
+	return c.deleteRequest("/api/v1/private/customer", reqData, nil)
+}
+
+// VerifySignature verifies a webhook signature based on hmac protocol
+func (c *Client) VerifySignature(signature string, body []byte) bool {
+	if len(signature) != 71 || !strings.HasPrefix(signature, "sha256=") {
+		return false
+	}
+
+	actual := make([]byte, 32)
+	_, err := hex.Decode(actual, []byte(signature[7:]))
+
+	if err != nil {
+		return false
+	}
+
+	computed := hmac.New(sha256.New, []byte(c.apiKey))
+	_, err = computed.Write(body)
+
+	if err != nil {
+		return false
+	}
+
+	return hmac.Equal(computed.Sum(nil), actual)
+}
+
+func (c *Client) postRequest(path string, data interface{}, dst interface{}) error {
+	return c.writeRequest("POST", path, data, dst)
+}
+
+func (c *Client) putRequest(path string, data interface{}, dst interface{}) error {
+	return c.writeRequest("PUT", path, data, dst)
+}
+
+func (c *Client) deleteRequest(path string, data interface{}, dst interface{}) error {
+	return c.writeRequest("DELETE", path, data, dst)
+}
+
+func (c *Client) getRequest(path string, dst interface{}, query ...map[string]string) error {
+	reqURL, err := url.Parse(c.serverURL)
+
+	if err != nil {
+		return nil
+	}
+
+	reqURL.Path = path
+
+	q := reqURL.Query()
+	for _, queryGroup := range query {
+		for key, val := range queryGroup {
+			q.Add(key, val)
+		}
+	}
+
+	reqURL.RawQuery = q.Encode()
+
+	req, err := http.NewRequest(
+		"GET",
+		reqURL.String(),
+		nil,
+	)
+
+	if err != nil {
+		return err
+	}
+
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	req.Header.Set("Accept", "application/json; charset=utf-8")
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
+
+	res, err := c.httpClient.Do(req)
+
+	if err != nil {
+		return err
+	}
+
+	defer res.Body.Close()
+
+	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
+		resBytes, err := ioutil.ReadAll(res.Body)
+
+		if err != nil {
+			return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
+		}
+
+		return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
+	}
+
+	if dst != nil {
+		return json.NewDecoder(res.Body).Decode(dst)
+	}
+
+	return nil
+}
+
+func (c *Client) writeRequest(method, path string, data interface{}, dst interface{}) error {
+	reqURL, err := url.Parse(c.serverURL)
+
+	if err != nil {
+		return nil
+	}
+
+	reqURL.Path = path
+
+	var strData []byte
+
+	if data != nil {
+		strData, err = json.Marshal(data)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	req, err := http.NewRequest(
+		method,
+		reqURL.String(),
+		strings.NewReader(string(strData)),
+	)
+
+	if err != nil {
+		return err
+	}
+
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	req.Header.Set("Accept", "application/json; charset=utf-8")
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
+
+	res, err := c.httpClient.Do(req)
+
+	if err != nil {
+		return err
+	}
+
+	defer res.Body.Close()
+
+	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
+		resBytes, err := ioutil.ReadAll(res.Body)
+
+		if err != nil {
+			return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
+		}
+
+		return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
+	}
+
+	if dst != nil {
+		return json.NewDecoder(res.Body).Decode(dst)
+	}
+
+	return nil
+}
+
+const (
+	FeatureSlugCPU      string = "cpu"
+	FeatureSlugMemory   string = "memory"
+	FeatureSlugClusters string = "clusters"
+	FeatureSlugUsers    string = "users"
+)
+
+func (c *Client) ParseProjectUsageFromWebhook(payload []byte) (*cemodels.ProjectUsage, error) {
+	// TODO: parse webhook model
+	return nil, nil
+
+	// subscription := &SubscriptionWebhookRequest{}
+
+	// err := json.Unmarshal(payload, subscription)
+
+	// if err != nil {
+	// 	return nil, err
+	// }
+
+	// // if event type is not subscription, return wrong webhook event type error
+	// if subscription.EventType != "subscription" {
+	// 	return nil, nil
+	// }
+
+	// // get the project id linked to that team
+	// projBilling, err := c.repo.ProjectBilling().ReadProjectBillingByTeamID(subscription.TeamID)
+
+	// if err != nil {
+	// 	return nil, err
+	// }
+
+	// usage := &cemodels.ProjectUsage{
+	// 	ProjectID: projBilling.ProjectID,
+	// }
+
+	// for _, feature := range subscription.Plan.Features {
+	// 	// look for slug of "cpus" and "memory"
+	// 	maxLimit := uint(feature.FeatureSpec.MaxLimit)
+	// 	switch feature.Feature.Slug {
+	// 	case FeatureSlugCPU:
+	// 		usage.ResourceCPU = maxLimit
+	// 	case FeatureSlugMemory:
+	// 		usage.ResourceMemory = 1000 * maxLimit
+	// 	case FeatureSlugClusters:
+	// 		usage.Clusters = maxLimit
+	// 	case FeatureSlugUsers:
+	// 		usage.Users = maxLimit
+	// 	}
+	// }
+
+	// return usage, nil
+}

+ 0 - 677
ee/billing/ironplans.go

@@ -1,677 +0,0 @@
-// +build ee
-
-package billing
-
-import (
-	"crypto/hmac"
-	"crypto/sha256"
-	"encoding/base64"
-	"encoding/hex"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"strings"
-	"time"
-
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/ee/models"
-	"github.com/porter-dev/porter/ee/repository"
-	"gorm.io/gorm"
-
-	cemodels "github.com/porter-dev/porter/internal/models"
-)
-
-// Client contains an API client for IronPlans
-type Client struct {
-	apiKey    string
-	serverURL string
-	repo      repository.EERepository
-
-	httpClient *http.Client
-
-	defaultPlanID string
-	customPlanID  string
-}
-
-// NewClient creates a new billing API client
-func NewClient(serverURL, apiKey string, repo repository.EERepository) (*Client, error) {
-	httpClient := &http.Client{
-		Timeout: time.Minute,
-	}
-
-	client := &Client{apiKey, serverURL, repo, httpClient, "", ""}
-
-	// get the default plans from the IronPlans API server
-	defPlanID, err := client.GetExistingPublicPlan("Free")
-
-	if err != nil {
-		return nil, err
-	}
-
-	customPlanID, err := client.GetExistingPublicPlan("Enterprise")
-
-	if err != nil {
-		return nil, err
-	}
-
-	client.defaultPlanID = defPlanID
-	client.customPlanID = customPlanID
-
-	return client, nil
-}
-
-func (c *Client) CreateTeam(proj *cemodels.Project) (string, error) {
-	resp := &Team{}
-	err := c.postRequest("/teams/v1", &CreateTeamRequest{
-		Name: proj.Name,
-	}, resp)
-
-	if err != nil {
-		return "", err
-	}
-
-	// put the user on the free plan, as the default behavior, if there is a default plan
-	if c.defaultPlanID != "" {
-		err = c.CreateOrUpdateSubscription(resp.ID, c.defaultPlanID)
-
-		if err != nil {
-			return "", fmt.Errorf("subscription creation failed: %s", err)
-		}
-	}
-
-	_, err = c.repo.ProjectBilling().CreateProjectBilling(&models.ProjectBilling{
-		ProjectID:     proj.ID,
-		BillingTeamID: resp.ID,
-	})
-
-	if err != nil {
-		return "", err
-	}
-
-	return resp.ID, err
-}
-
-func (c *Client) DeleteTeam(proj *cemodels.Project) error {
-	projBilling, err := c.repo.ProjectBilling().ReadProjectBillingByProjectID(proj.ID)
-
-	if err != nil {
-		return err
-	}
-
-	return c.deleteRequest(fmt.Sprintf("/teams/v1/%s", projBilling.BillingTeamID), nil, nil)
-}
-
-func (c *Client) GetTeamID(proj *cemodels.Project) (teamID string, err error) {
-	projBilling, err := c.repo.ProjectBilling().ReadProjectBillingByProjectID(proj.ID)
-
-	if err != nil {
-		return "", err
-	}
-
-	return projBilling.BillingTeamID, nil
-}
-
-func (c *Client) CreatePlan(teamID string, proj *cemodels.Project, planSpec *types.AddProjectBillingRequest) (string, error) {
-	// construct basic plan object
-	planFeatures := make([]*CreatePlanFeature, 0)
-
-	userDisplay := fmt.Sprintf("Up to %d users", planSpec.Users)
-
-	if planSpec.Users == 0 {
-		userDisplay = fmt.Sprintf("Unlimited users")
-	}
-
-	clusterDisplay := fmt.Sprintf("Up to %d clusters", planSpec.Clusters)
-
-	if planSpec.Clusters == 0 {
-		clusterDisplay = fmt.Sprintf("Unlimited clusters")
-	}
-
-	cpuDisplay := fmt.Sprintf("Up to %d CPUs", planSpec.CPU)
-
-	if planSpec.CPU == 0 {
-		cpuDisplay = fmt.Sprintf("Unlimited CPU")
-	}
-
-	ramDisplay := fmt.Sprintf("Up to %d GB RAM", planSpec.Memory)
-
-	if planSpec.Memory == 0 {
-		ramDisplay = fmt.Sprintf("Unlimited RAM")
-	}
-
-	planFeatures = append(planFeatures, &CreatePlanFeature{
-		Display: userDisplay,
-	})
-	planFeatures = append(planFeatures, &CreatePlanFeature{
-		Display: clusterDisplay,
-	})
-	planFeatures = append(planFeatures, &CreatePlanFeature{
-		Display: cpuDisplay,
-	})
-	planFeatures = append(planFeatures, &CreatePlanFeature{
-		Display: ramDisplay,
-	})
-
-	var customPlanID *string
-
-	if c.customPlanID != "" {
-		customPlanID = &c.customPlanID
-	}
-
-	createPlanReq := &CreatePlanRequest{
-		Name:               proj.Name,
-		IsActive:           true,
-		IsPublic:           false,
-		IsTrialAllowed:     true,
-		ReplacePlanID:      customPlanID,
-		PerMonthPriceCents: planSpec.Price,
-		PerYearPriceCents:  12 * planSpec.Price,
-		Features:           planFeatures,
-		TeamsAccess: []*CreatePlanTeamsAccess{
-			{
-				TeamID: teamID,
-				Revoke: false,
-			},
-		},
-	}
-
-	// find all relevant feature IDs
-	listResp := &ListFeaturesResponse{}
-	err := c.getRequest("/features/v1", listResp)
-
-	if err != nil {
-		return "", err
-	}
-
-	// create a feature spec per feature ID, and add to features array for plan
-	for _, feature := range listResp.Results {
-		featureSpec := &CreateFeatureSpecRequest{
-			Name:         "unnamed",
-			RecordPeriod: "monthly",
-			Aggregation:  "sum",
-			UnitPrice:    0,
-		}
-
-		switch feature.Slug {
-		case FeatureSlugUsers:
-			featureSpec.MaxLimit = planSpec.Users
-			featureSpec.UnitsIncluded = planSpec.Users
-		case FeatureSlugClusters:
-			featureSpec.MaxLimit = planSpec.Clusters
-			featureSpec.UnitsIncluded = planSpec.Clusters
-		case FeatureSlugCPU:
-			featureSpec.MaxLimit = planSpec.CPU
-			featureSpec.UnitsIncluded = planSpec.CPU
-		case FeatureSlugMemory:
-			featureSpec.MaxLimit = planSpec.Memory
-			featureSpec.UnitsIncluded = planSpec.Memory
-		// continue on default behavior so that feature spec is not created for
-		// features that don't match a slug
-		default:
-			continue
-		}
-
-		// create the feature spec
-		resp := &CreateFeaturespecResponse{}
-		err = c.postRequest("/featurespecs/v1/", featureSpec, resp)
-
-		if err != nil {
-			return "", err
-		}
-
-		var index int
-		switch feature.Slug {
-		case FeatureSlugUsers:
-			index = 0
-		case FeatureSlugClusters:
-			index = 1
-		case FeatureSlugCPU:
-			index = 2
-		case FeatureSlugMemory:
-			index = 3
-		}
-
-		createPlanReq.Features[index].FeatureID = feature.ID
-		createPlanReq.Features[index].SpecID = resp.ID
-	}
-
-	// create the plan and return the plan ID
-	planResp := &Plan{}
-
-	err = c.postRequest("/plans/v1/", createPlanReq, planResp)
-
-	if err != nil {
-		return "", err
-	}
-
-	return planResp.ID, nil
-}
-
-func (c *Client) CreateOrUpdateSubscription(teamID, planID string) error {
-	// determine if subscription already exists by reading the team ID and seeing if the subscription
-	// field has an ID attached
-	teamResp := &Team{}
-	err := c.getRequest(fmt.Sprintf("/teams/v1/%s", teamID), teamResp)
-
-	if err != nil {
-		return err
-	}
-
-	subReq := &CreateSubscriptionRequest{
-		PlanID:     planID,
-		NextPlanID: c.defaultPlanID,
-		TeamID:     teamID,
-		IsPaused:   false,
-	}
-
-	// if subscription ID is not empty, perform a PUT request to update the subscription
-	if teamResp.Subscription.ID != "" {
-		// delete the subscription
-		err = c.deleteRequest(fmt.Sprintf("/subscriptions/v1/%s/purge/", teamResp.Subscription.ID), nil, nil)
-
-		if err != nil {
-			return err
-		}
-	}
-
-	return c.postRequest("/subscriptions/v1", subReq, nil)
-}
-
-func (c *Client) GetExistingPublicPlan(planName string) (string, error) {
-	listResp := &ListPlansResponse{}
-	err := c.getRequest("/plans/v1/", listResp, map[string]string{"is_public": "true"})
-
-	if err != nil {
-		return "", err
-	}
-
-	for _, plan := range listResp.Results {
-		if plan.Name == planName {
-			return plan.ID, nil
-		}
-	}
-
-	return "", fmt.Errorf("plan not found")
-}
-
-func (c *Client) AddUserToTeam(teamID string, user *cemodels.User, role *cemodels.Role) error {
-	// determine if user is already in team/has user billing
-	userBilling, err := c.repo.UserBilling().ReadUserBilling(role.ProjectID, user.ID)
-
-	if userBilling != nil {
-		return nil
-	}
-
-	roleEnum := RoleEnumMember
-
-	// if user's role is admin, add them to the team as an owner
-	if role.Kind == types.RoleAdmin {
-		roleEnum = RoleEnumOwner
-	}
-
-	req := &AddTeammateRequest{
-		TeamID:   teamID,
-		Role:     roleEnum,
-		Email:    user.Email,
-		SourceID: fmt.Sprintf("%d-%d", role.ProjectID, user.ID),
-	}
-
-	resp := &Teammate{}
-
-	err = c.postRequest("/team_memberships/v1", req, resp)
-
-	if err != nil {
-		return err
-	}
-
-	_, err = c.repo.UserBilling().CreateUserBilling(&models.UserBilling{
-		ProjectID:  role.ProjectID,
-		UserID:     user.ID,
-		TeammateID: resp.ID,
-		Token:      []byte(""),
-	})
-
-	return err
-}
-
-func (c *Client) UpdateUserInTeam(role *cemodels.Role) error {
-	// get the user billing information to get the membership id
-	userBilling, err := c.repo.UserBilling().ReadUserBilling(role.ProjectID, role.UserID)
-
-	if err != nil {
-		return err
-	}
-
-	roleEnum := RoleEnumMember
-
-	// if user's role is admin, add them to the team as an owner
-	if role.Kind == types.RoleAdmin {
-		roleEnum = RoleEnumOwner
-	}
-
-	req := &UpdateTeammateRequest{
-		Role: roleEnum,
-	}
-
-	resp := &Teammate{}
-
-	return c.putRequest(fmt.Sprintf("/team_memberships/v1/%s", userBilling.TeammateID), req, resp)
-}
-
-func (c *Client) RemoveUserFromTeam(role *cemodels.Role) error {
-	// get the user billing information to get the membership id
-	userBilling, err := c.repo.UserBilling().ReadUserBilling(role.ProjectID, role.UserID)
-
-	if err != nil {
-		return err
-	}
-
-	return c.deleteRequest(fmt.Sprintf("/team_memberships/v1/%s", userBilling.TeammateID), nil, nil)
-}
-
-// GetIDToken gets an id token for a user in a project, creating the ID token if necessary
-func (c *Client) GetIDToken(proj *cemodels.Project, user *cemodels.User) (token string, teamID string, err error) {
-	// attempt to get a team ID for the project
-	teamID, err = c.GetTeamID(proj)
-
-	// attempt to read the user billing data from the project
-	userBilling, err := c.repo.UserBilling().ReadUserBilling(proj.ID, user.ID)
-	notFound := errors.Is(err, gorm.ErrRecordNotFound)
-
-	if !notFound && err != nil {
-		return "", "", err
-	}
-
-	if !notFound {
-		token = string(userBilling.Token)
-
-		if token != "" {
-			// check if the JWT token has expired
-			isTokExpired := isExpired(token)
-
-			// if JWT token has not expired, return the token
-			if !isTokExpired {
-				return token, teamID, nil
-			}
-		}
-	}
-
-	req := &CreateIDTokenRequest{
-		Email:  user.Email,
-		UserID: fmt.Sprintf("%d-%d", proj.ID, user.ID),
-	}
-
-	resp := &CreateIDTokenResponse{}
-
-	err = c.postRequest("/customers/v1/token", req, resp)
-
-	if err != nil {
-		return "", "", err
-	}
-
-	token = resp.Token
-
-	if notFound {
-		_, err := c.repo.UserBilling().CreateUserBilling(&models.UserBilling{
-			ProjectID: proj.ID,
-			UserID:    user.ID,
-			Token:     []byte(token),
-		})
-
-		if err != nil {
-			return "", "", err
-		}
-	} else {
-		_, err := c.repo.UserBilling().UpdateUserBilling(&models.UserBilling{
-			Model: &gorm.Model{
-				ID: userBilling.ID,
-			},
-			ProjectID:  proj.ID,
-			UserID:     user.ID,
-			Token:      []byte(token),
-			TeammateID: userBilling.TeammateID,
-		})
-
-		if err != nil {
-			return "", "", err
-		}
-	}
-
-	return token, teamID, nil
-}
-
-// VerifySignature verifies a webhook signature based on hmac protocol
-// https://docs.ironplans.com/webhook-events/webhook-events
-func (c *Client) VerifySignature(signature string, body []byte) bool {
-	if len(signature) != 71 || !strings.HasPrefix(signature, "sha256=") {
-		return false
-	}
-
-	actual := make([]byte, 32)
-	_, err := hex.Decode(actual, []byte(signature[7:]))
-
-	if err != nil {
-		return false
-	}
-
-	computed := hmac.New(sha256.New, []byte(c.apiKey))
-	_, err = computed.Write(body)
-
-	if err != nil {
-		return false
-	}
-
-	return hmac.Equal(computed.Sum(nil), actual)
-}
-
-func (c *Client) postRequest(path string, data interface{}, dst interface{}) error {
-	return c.writeRequest("POST", path, data, dst)
-}
-
-func (c *Client) putRequest(path string, data interface{}, dst interface{}) error {
-	return c.writeRequest("PUT", path, data, dst)
-}
-
-func (c *Client) deleteRequest(path string, data interface{}, dst interface{}) error {
-	return c.writeRequest("DELETE", path, data, dst)
-}
-
-func (c *Client) getRequest(path string, dst interface{}, query ...map[string]string) error {
-	reqURL, err := url.Parse(c.serverURL)
-
-	if err != nil {
-		return nil
-	}
-
-	reqURL.Path = path
-
-	q := reqURL.Query()
-	for _, queryGroup := range query {
-		for key, val := range queryGroup {
-			q.Add(key, val)
-		}
-	}
-
-	reqURL.RawQuery = q.Encode()
-
-	req, err := http.NewRequest(
-		"GET",
-		reqURL.String(),
-		nil,
-	)
-
-	if err != nil {
-		return err
-	}
-
-	req.Header.Set("Content-Type", "application/json; charset=utf-8")
-	req.Header.Set("Accept", "application/json; charset=utf-8")
-	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
-
-	res, err := c.httpClient.Do(req)
-
-	if err != nil {
-		return err
-	}
-
-	defer res.Body.Close()
-
-	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
-		resBytes, err := ioutil.ReadAll(res.Body)
-
-		if err != nil {
-			return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
-		}
-
-		return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
-	}
-
-	if dst != nil {
-		return json.NewDecoder(res.Body).Decode(dst)
-	}
-
-	return nil
-}
-
-func (c *Client) writeRequest(method, path string, data interface{}, dst interface{}) error {
-	reqURL, err := url.Parse(c.serverURL)
-
-	if err != nil {
-		return nil
-	}
-
-	reqURL.Path = path
-
-	var strData []byte
-
-	if data != nil {
-		strData, err = json.Marshal(data)
-
-		if err != nil {
-			return err
-		}
-	}
-
-	req, err := http.NewRequest(
-		method,
-		reqURL.String(),
-		strings.NewReader(string(strData)),
-	)
-
-	if err != nil {
-		return err
-	}
-
-	req.Header.Set("Content-Type", "application/json; charset=utf-8")
-	req.Header.Set("Accept", "application/json; charset=utf-8")
-	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
-
-	res, err := c.httpClient.Do(req)
-
-	if err != nil {
-		return err
-	}
-
-	defer res.Body.Close()
-
-	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
-		resBytes, err := ioutil.ReadAll(res.Body)
-
-		if err != nil {
-			return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
-		}
-
-		return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
-	}
-
-	if dst != nil {
-		return json.NewDecoder(res.Body).Decode(dst)
-	}
-
-	return nil
-}
-
-const (
-	FeatureSlugCPU      string = "cpu"
-	FeatureSlugMemory   string = "memory"
-	FeatureSlugClusters string = "clusters"
-	FeatureSlugUsers    string = "users"
-)
-
-func (c *Client) ParseProjectUsageFromWebhook(payload []byte) (*cemodels.ProjectUsage, error) {
-	subscription := &SubscriptionWebhookRequest{}
-
-	err := json.Unmarshal(payload, subscription)
-
-	if err != nil {
-		return nil, err
-	}
-
-	// if event type is not subscription, return wrong webhook event type error
-	if subscription.EventType != "subscription" {
-		return nil, nil
-	}
-
-	// get the project id linked to that team
-	projBilling, err := c.repo.ProjectBilling().ReadProjectBillingByTeamID(subscription.TeamID)
-
-	if err != nil {
-		return nil, err
-	}
-
-	usage := &cemodels.ProjectUsage{
-		ProjectID: projBilling.ProjectID,
-	}
-
-	for _, feature := range subscription.Plan.Features {
-		// look for slug of "cpus" and "memory"
-		maxLimit := uint(feature.FeatureSpec.MaxLimit)
-		switch feature.Feature.Slug {
-		case FeatureSlugCPU:
-			usage.ResourceCPU = maxLimit
-		case FeatureSlugMemory:
-			usage.ResourceMemory = 1000 * maxLimit
-		case FeatureSlugClusters:
-			usage.Clusters = maxLimit
-		case FeatureSlugUsers:
-			usage.Users = maxLimit
-		}
-	}
-
-	return usage, nil
-}
-
-type expiryJWT struct {
-	ExpiresAt int64 `json:"exp"`
-}
-
-func isExpired(token string) bool {
-	var encoded string
-
-	if tokenSplit := strings.Split(token, "."); len(tokenSplit) != 3 {
-		return true
-	} else {
-		encoded = tokenSplit[1]
-	}
-
-	decodedBytes, err := base64.RawStdEncoding.DecodeString(encoded)
-
-	if err != nil {
-		return true
-	}
-
-	expiryData := &expiryJWT{}
-
-	err = json.Unmarshal(decodedBytes, expiryData)
-
-	if err != nil {
-		return true
-	}
-
-	expiryTime := time.Unix(expiryData.ExpiresAt, 0)
-
-	return expiryTime.Before(time.Now())
-}

+ 8 - 135
ee/billing/types.go

@@ -1,142 +1,15 @@
+//go:build ee
 // +build ee
 
 package billing
 
-type Team struct {
-	ID           string       `json:"id"`
-	ProviderID   string       `json:"provider_id"`
-	Name         string       `json:"name"`
-	Members      []Teammate   `json:"members"`
-	Subscription Subscription `json:"subscription"`
+type CreateCustomerRequest struct {
+	Email     string `json:"email" form:"required"`
+	UserID    uint   `json:"user_id" form:"required"`
+	ProjectID uint   `json:"project_id" form:"required"`
 }
 
-type RoleEnum string
-
-const (
-	RoleEnumOwner  RoleEnum = "owner"
-	RoleEnumMember RoleEnum = "member"
-)
-
-type Teammate struct {
-	ID         string   `json:"id"`
-	CustomerID string   `json:"customer_id"`
-	Role       RoleEnum `json:"role"`
-	Email      string   `json:"email"`
-}
-
-type Subscription struct {
-	ID       string `json:"id"`
-	Plan     Plan   `json:"plan"`
-	IsActive bool   `json:"is_active"`
-}
-
-type Plan struct {
-	ID         string        `json:"id"`
-	ProviderID string        `json:"string"`
-	Name       string        `json:"name"`
-	IsActive   bool          `json:"is_active"`
-	Features   []PlanFeature `json:"features"`
-}
-
-type CreatePlanRequest struct {
-	Name               string                   `json:"name"`
-	IsActive           bool                     `json:"is_active"`
-	IsPublic           bool                     `json:"is_public"`
-	IsTrialAllowed     bool                     `json:"is_trial_allowed"`
-	PerMonthPriceCents uint                     `json:"per_month_price_cents"`
-	PerYearPriceCents  uint                     `json:"per_year_price_cents"`
-	ReplacePlanID      *string                  `json:"replace_plan_id"`
-	Features           []*CreatePlanFeature     `json:"features"`
-	TeamsAccess        []*CreatePlanTeamsAccess `json:"teams_access"`
-}
-
-type CreatePlanFeature struct {
-	FeatureID string `json:"feature_id"`
-	SpecID    string `json:"spec_id"`
-	Display   string `json:"display"`
-	Sort      uint   `json:"sort"`
-	IsActive  bool   `json:"is_active"`
-}
-
-type CreatePlanTeamsAccess struct {
-	TeamID string `json:"team_id"`
-	Revoke bool   `json:"revoke"`
-}
-
-type CreateFeatureSpecRequest struct {
-	Name          string `json:"name"`
-	RecordPeriod  string `json:"record_period"`
-	Aggregation   string `json:"aggregation"`
-	MaxLimit      uint   `json:"max_limit"`
-	UnitPrice     uint   `json:"unit_price"`
-	UnitsIncluded uint   `json:"units_included"`
-}
-
-type CreateFeaturespecResponse struct {
-	*CreateFeatureSpecRequest
-	ID string `json:"id"`
-}
-
-type ListFeaturesResponse struct {
-	Results []Feature `json:"results"`
-}
-
-type ListPlansResponse struct {
-	Results []Plan `json:"results"`
-}
-
-type PlanFeature struct {
-	ID          string      `json:"id"`
-	IsActive    bool        `json:"is_active"`
-	Feature     Feature     `json:"feature"`
-	FeatureSpec FeatureSpec `json:"spec"`
-}
-
-type Feature struct {
-	ID   string `json:"id"`
-	Slug string `json:"slug"`
-}
-
-type FeatureSpec struct {
-	ID         string `json:"id"`
-	Name       string `json:"name"`
-	MaxLimit   int64  `json:"max_limit"`
-	ProviderID string `json:"provider_id"`
-}
-
-type CreateTeamRequest struct {
-	Name string `json:"name"`
-}
-
-type AddTeammateRequest struct {
-	Role     RoleEnum `json:"role"`
-	Email    string   `json:"email"`
-	SourceID string   `json:"source_id"`
-	TeamID   string   `json:"team_id"`
-}
-
-type UpdateTeammateRequest struct {
-	Role RoleEnum `json:"role"`
-}
-
-type CreateIDTokenRequest struct {
-	Email  string `json:"customer_email"`
-	UserID string `json:"customer_source_id"`
-}
-
-type CreateIDTokenResponse struct {
-	Token string `json:"token"`
-}
-
-type SubscriptionWebhookRequest struct {
-	EventType string `json:"event_type"`
-	TeamID    string `json:"team_id"`
-	Plan      Plan   `json:"plan"`
-}
-
-type CreateSubscriptionRequest struct {
-	PlanID     string `json:"plan_id"`
-	TeamID     string `json:"team_id"`
-	IsPaused   bool   `json:"is_paused"`
-	NextPlanID string `json:"next_plan_id"`
+type DeleteCustomerRequest struct {
+	UserID    uint `json:"user_id" form:"required"`
+	ProjectID uint `json:"project_id" form:"required"`
 }

+ 0 - 60
internal/billing/billing.go

@@ -3,7 +3,6 @@ package billing
 import (
 	"fmt"
 
-	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 )
 
@@ -17,33 +16,6 @@ type BillingManager interface {
 	// DeleteTeam deletes a billing team.
 	DeleteTeam(proj *models.Project) (err error)
 
-	// GetTeamID gets the billing team id for a project
-	GetTeamID(proj *models.Project) (teamID string, err error)
-
-	// CreatePlan creates a new plan based on the requested limits
-	CreatePlan(teamID string, proj *models.Project, planSpec *types.AddProjectBillingRequest) (string, error)
-
-	// CreateOrUpdateSubscription creates or updates a new subscription to a plan, based on a team and plan ID
-	CreateOrUpdateSubscription(teamID, planID string) error
-
-	// GetExistingPublicPlan returns an existing public plan based on a name
-	GetExistingPublicPlan(planName string) (string, error)
-
-	// AddUserToTeam adds a user to a team, and cases on whether the user can view
-	// billing based on the role.
-	AddUserToTeam(teamID string, user *models.User, role *models.Role) error
-
-	// UpdateUserInTeam updates a user's role in a team, and cases on whether the user can view
-	// billing based on the role.
-	UpdateUserInTeam(role *models.Role) error
-
-	// RemoveUserFromTeam removes a user from a team
-	RemoveUserFromTeam(role *models.Role) error
-
-	// GetIDToken retrieves a billing token for a user. The billing token can be exchanged
-	// to view billing information.
-	GetIDToken(proj *models.Project, user *models.User) (token string, teamID string, err error)
-
 	// ParseProjectUsageFromWebhook parses the project usage from a webhook payload sent
 	// from a billing agent
 	ParseProjectUsageFromWebhook(payload []byte) (*models.ProjectUsage, error)
@@ -63,38 +35,6 @@ func (n *NoopBillingManager) DeleteTeam(proj *models.Project) (err error) {
 	return nil
 }
 
-func (n *NoopBillingManager) GetTeamID(proj *models.Project) (teamID string, err error) {
-	return fmt.Sprintf("%d", proj.ID), nil
-}
-
-func (n *NoopBillingManager) CreatePlan(teamID string, proj *models.Project, planSpec *types.AddProjectBillingRequest) (string, error) {
-	return "", nil
-}
-
-func (n *NoopBillingManager) CreateOrUpdateSubscription(teamID, planID string) error {
-	return nil
-}
-
-func (n *NoopBillingManager) GetExistingPublicPlan(planName string) (string, error) {
-	return "", nil
-}
-
-func (n *NoopBillingManager) AddUserToTeam(teamID string, user *models.User, role *models.Role) error {
-	return nil
-}
-
-func (n *NoopBillingManager) UpdateUserInTeam(role *models.Role) error {
-	return nil
-}
-
-func (n *NoopBillingManager) RemoveUserFromTeam(role *models.Role) error {
-	return nil
-}
-
-func (n *NoopBillingManager) GetIDToken(proj *models.Project, user *models.User) (token string, teamID string, err error) {
-	return "", "", nil
-}
-
 func (n *NoopBillingManager) ParseProjectUsageFromWebhook(payload []byte) (*models.ProjectUsage, error) {
 	return nil, nil
 }