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

Remove old billing code, add billing manager feature flag

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

+ 0 - 24
api/server/handlers/billing/billing_ce.go

@@ -1,24 +0,0 @@
-//go:build !ee
-// +build !ee
-
-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/config"
-)
-
-type BillingWebhookHandler struct {
-	handlers.PorterHandlerReader
-	handlers.Unavailable
-}
-
-func NewBillingWebhookHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-) http.Handler {
-	return handlers.NewUnavailable(config, "billing_webhook")
-}

+ 0 - 22
api/server/handlers/billing/billing_ee.go

@@ -1,22 +0,0 @@
-//go:build ee
-// +build ee
-
-package billing
-
-import (
-	"net/http"
-
-	"github.com/porter-dev/porter/api/server/shared"
-	"github.com/porter-dev/porter/api/server/shared/config"
-
-	"github.com/porter-dev/porter/ee/api/server/handlers/billing"
-)
-
-var NewBillingWebhookHandler func(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-) http.Handler
-
-func init() {
-	NewBillingWebhookHandler = billing.NewBillingWebhookHandler
-}

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

@@ -92,6 +92,14 @@ func (p *ProjectDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
+	err = p.Config().BillingManager.DeleteCustomer(proj)
+	if err != nil {
+		e := "error deleting project in billing provider"
+		err = telemetry.Error(ctx, span, err, e)
+		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
 	deletedProject, err := p.Repo().Project().DeleteProject(proj)
 	if err != nil {
 		e := "error deleting project"

+ 2 - 0
dashboard/src/main/home/project-settings/ProjectSettings.tsx

@@ -90,6 +90,8 @@ function ProjectSettings(props: any) {
         label: "Additional settings",
       });
 
+      console.log(currentProject);
+      console.log(currentProject?.billing_enabled);
       if (currentProject?.billing_enabled) {
         tabOpts.push({
           value: "billing",

+ 0 - 284
ee/billing/client.go

@@ -1,284 +0,0 @@
-//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"
-
-	"github.com/gorilla/schema"
-	"github.com/porter-dev/porter/api/types"
-	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
-	publicServerURL string
-	httpClient      *http.Client
-}
-
-// NewClient creates a new billing API client
-func NewClient(serverURL, publicServerURL, apiKey string) (*Client, error) {
-	httpClient := &http.Client{
-		Timeout: time.Minute,
-	}
-
-	client := &Client{apiKey, serverURL, publicServerURL, 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,
-		ProjectName: proj.Name,
-	}
-
-	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)
-}
-
-func (c *Client) GetRedirectURI(user *cemodels.User, proj *cemodels.Project) (string, error) {
-	// get an internal cookie
-	reqData := &CreateBillingCookieRequest{
-		ProjectName: proj.Name,
-		ProjectID:   proj.ID,
-		UserID:      user.ID,
-		Email:       user.Email,
-	}
-
-	createCookieVals := make(map[string][]string)
-	err := schema.NewEncoder().Encode(reqData, createCookieVals)
-	if err != nil {
-		return "", err
-	}
-
-	urlVals := url.Values(createCookieVals)
-	encodedURLVals := urlVals.Encode()
-
-	dst := &CreateBillingCookieResponse{}
-
-	err = c.postRequest("/api/v1/private/cookie", reqData, dst)
-
-	if err != nil {
-		return "", err
-	}
-
-	redirectData := &VerifyUserRequest{
-		TokenID: dst.TokenID,
-		Token:   dst.Token,
-	}
-
-	vals := make(map[string][]string)
-	err = schema.NewEncoder().Encode(redirectData, vals)
-
-	if err != nil {
-		return "", err
-	}
-
-	urlVals = url.Values(vals)
-	encodedURLVals = urlVals.Encode()
-
-	return fmt.Sprintf("%s/api/v1/verify?%s", c.publicServerURL, encodedURLVals), 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, *types.FeatureFlags, error) {
-	usageData := &APIWebhookRequest{}
-
-	err := json.Unmarshal(payload, usageData)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	return &cemodels.ProjectUsage{
-			ProjectID:      usageData.ProjectID,
-			ResourceCPU:    usageData.CPU,
-			ResourceMemory: usageData.Memory * 1000,
-			Clusters:       usageData.Clusters,
-			Users:          usageData.Users,
-		}, &types.FeatureFlags{
-			PreviewEnvironmentsEnabled: usageData.PreviewEnvironmentsEnabled,
-			ManagedInfraEnabled:        usageData.ManagedInfraEnabled,
-			StacksEnabled:              usageData.StacksEnabled,
-			ManagedDatabasesEnabled:    usageData.ManagedDatabasesEnabled,
-			CapiProvisionerEnabled:     usageData.CapiProvisionerEnabled,
-			SimplifiedViewEnabled:      usageData.SimplifiedViewEnabled,
-			AzureEnabled:               usageData.AzureEnabled,
-		}, nil
-}

+ 0 - 50
ee/billing/types.go

@@ -1,50 +0,0 @@
-//go:build ee
-// +build ee
-
-package billing
-
-type CreateCustomerRequest struct {
-	Email       string `json:"email" form:"required"`
-	UserID      uint   `json:"user_id" form:"required"`
-	ProjectID   uint   `json:"project_id" form:"required"`
-	ProjectName string `json:"project_name" form:"required"`
-}
-
-type DeleteCustomerRequest struct {
-	UserID    uint `json:"user_id" form:"required"`
-	ProjectID uint `json:"project_id" form:"required"`
-}
-
-type APIWebhookRequest struct {
-	ProjectID uint `json:"project_id" form:"required"`
-
-	Clusters uint `json:"clusters" form:"required"`
-	Users    uint `json:"users" form:"required"`
-	CPU      uint `json:"cpu" form:"required"`
-	Memory   uint `json:"memory" form:"required"`
-
-	PreviewEnvironmentsEnabled string `json:"preview_environments_enabled,omitempty"`
-	ManagedInfraEnabled        string `json:"managed_infra_enabled,omitempty"`
-	StacksEnabled              string `json:"stacks_enabled,omitempty"`
-	ManagedDatabasesEnabled    string `json:"managed_databases_enabled,omitempty"`
-	CapiProvisionerEnabled     string `json:"capi_provisioner_enabled,omitempty"`
-	SimplifiedViewEnabled      string `json:"simplified_view_enabled,omitempty"`
-	AzureEnabled               bool   `json:"azure_enabled,omitempty"`
-}
-
-type CreateBillingCookieRequest struct {
-	Email       string `json:"email" form:"required"`
-	UserID      uint   `json:"user_id" form:"required"`
-	ProjectID   uint   `json:"project_id" form:"required"`
-	ProjectName string `json:"project_name" form:"required"`
-}
-
-type CreateBillingCookieResponse struct {
-	Token   string `json:"token"`
-	TokenID string `json:"token_id"`
-}
-
-type VerifyUserRequest struct {
-	TokenID string `schema:"token_id" form:"required"`
-	Token   string `schema:"token" form:"required"`
-}

+ 3 - 0
internal/billing/billing.go

@@ -11,6 +11,9 @@ type BillingManager interface {
 	// mapping with projects and billing customers, because billing and usage are set per project.
 	CreateCustomer(userEmail string, proj *models.Project) (customerID string, err error)
 
+	// DeleteCustomer will delete the customer from the billing provider
+	DeleteCustomer(proj *models.Project) (err error)
+
 	// ListPaymentMethod will return all payment methods for the project
 	ListPaymentMethod(proj *models.Project) (paymentMethods []types.PaymentMethod, err error)
 

+ 15 - 0
internal/billing/stripe.go

@@ -41,6 +41,21 @@ func (s *StripeBillingManager) CreateCustomer(userEmail string, proj *models.Pro
 	return customerID, nil
 }
 
+// DeleteCustomer will delete the customer from the billing provider
+func (s *StripeBillingManager) DeleteCustomer(proj *models.Project) (err error) {
+	stripe.Key = s.StripeSecretKey
+
+	if proj.BillingID == "" {
+		params := &stripe.CustomerParams{}
+		_, err := customer.Del(proj.BillingID, params)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // ListPaymentMethod will return all payment methods for the project
 func (s *StripeBillingManager) ListPaymentMethod(proj *models.Project) (paymentMethods []types.PaymentMethod, err error) {
 	stripe.Key = s.StripeSecretKey

+ 4 - 1
internal/models/project.go

@@ -134,7 +134,8 @@ type Project struct {
 	Roles []Role `json:"roles"`
 
 	// BillingID corresponds to the id generated by the billing provider
-	BillingID string
+	BillingID      string
+	BillingEnabled bool
 
 	ProjectUsageID      uint
 	ProjectUsageCacheID uint
@@ -227,6 +228,8 @@ func (p *Project) GetFeatureFlag(flagName FeatureFlagLabel, launchDarklyClient *
 			return p.AzureEnabled
 		case "capi_provisioner_enabled":
 			return p.CapiProvisionerEnabled
+		case "billing_enabled":
+			return p.BillingEnabled
 		case "db_enabled":
 			return false
 		case "enable_reprovision":