Преглед изворни кода

Fix credits and end plan calls

Mauricio Araujo пре 2 година
родитељ
комит
d2da63b44a

+ 37 - 0
.github/workflows/porter_stack_porter-ui copy.yml

@@ -0,0 +1,37 @@
+'on':
+  push:
+    branches:
+      - metronome-integration
+name: Deploy Porter to Internal Tooling
+jobs:
+  build-go:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+      - name: build-go
+        uses: ./.github/actions/build-go
+
+  build-npm:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+      - name: build-npm
+        uses: ./.github/actions/build-npm
+
+  porter-deploy:
+    runs-on: ubuntu-latest
+    needs: [build-go, build-npm]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+      - name: porter-deploy
+        timeout-minutes: 30
+        uses: ./.github/actions/porter-deploy
+        with:
+          app: porter-ui
+          cluster: '37'
+          host: https://dashboard.internal-tools.porter.run
+          project: '18'
+          token: ${{ secrets.PORTER_STAGING_DEPLOYMENT }}

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

@@ -99,8 +99,7 @@ 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 != "" &&
-		p.Config().ServerConf.EnableSandbox {
+	if p.Config().ServerConf.MetronomeAPIKey != "" && p.Config().ServerConf.PorterCloudPlanID != "" {
 		// Create Metronome Customer
 		if p.Config().ServerConf.MetronomeAPIKey != "" {
 			usageID, err := p.Config().BillingManager.MetronomeClient.CreateCustomer(user.CompanyName, proj.Name, proj.ID, proj.BillingID)

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

@@ -4,7 +4,6 @@ import (
 	"net/http"
 
 	"connectrpc.com/connect"
-	"github.com/google/uuid"
 	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
@@ -94,16 +93,9 @@ func (p *ProjectDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	if p.Config().ServerConf.MetronomeAPIKey != "" && p.Config().ServerConf.PorterCloudPlanID != "" {
-		porterCloudPlanID, err := uuid.Parse(p.Config().ServerConf.PorterCloudPlanID)
+		err = p.Config().BillingManager.MetronomeClient.EndCustomerPlan(proj.UsageID, proj.UsagePlanID)
 		if err != nil {
-			err = telemetry.Error(ctx, span, err, "error parsing starter plan id")
-			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
-		}
-
-		err = p.Config().BillingManager.MetronomeClient.EndCustomerPlan(proj.UsageID, porterCloudPlanID)
-		if err != nil {
-			e := "error deleting project in usage provider"
+			e := "error ending billing plan"
 			err = telemetry.Error(ctx, span, err, e)
 			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
 			return

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

@@ -341,6 +341,33 @@ func getProjectRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/billing/credits -> project.NewGetCreditsHandler
+	getCreditsEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/billing/credits",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+			},
+		},
+	)
+
+	getCreditsHandler := billing.NewGetCreditsHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: getCreditsEndpoint,
+		Handler:  getCreditsHandler,
+		Router:   r,
+	})
+
 	// POST /api/projects/{project_id}/billing/payment_method -> project.NewCreateBillingHandler
 	createBillingEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 9 - 5
api/types/billing.go

@@ -65,18 +65,22 @@ type EndCustomerPlanRequest struct {
 type ListCreditGrantsRequest struct {
 	// An array of credit type IDs. This must not be specified if
 	// credit_grant_ids is specified.
-	CreditTypeIDs []uuid.UUID `json:"credit_type_ids"`
+	CreditTypeIDs []uuid.UUID `json:"credit_type_ids,omitempty"`
 	// An array of Metronome customer IDs. This must not be specified if
 	// credit_grant_ids is specified.
-	CustomerIDs []uuid.UUID `json:"customer_ids"`
+	CustomerIDs []uuid.UUID `json:"customer_ids,omitempty"`
 	// An array of credit grant IDs. If this is specified, neither
 	// credit_type_ids nor customer_ids may be specified.
-	CreditGrantIDs []uuid.UUID `json:"credit_grant_ids"`
+	CreditGrantIDs []uuid.UUID `json:"credit_grant_ids,omitempty"`
 	// Only return credit grants that expire at or after this RFC 3339 timestamp.
-	NotExpiringBefore string `json:"not_expiring_before"`
+	NotExpiringBefore string `json:"not_expiring_before,omitempty"`
 	// Only return credit grants that are effective before this RFC 3339 timestamp
 	// (exclusive).
-	EffectiveBefore string `json:"effective_before"`
+	EffectiveBefore string `json:"effective_before,omitempty"`
+}
+
+type ListCreditGrantsResponse struct {
+	Data []CreditGrant `json:"data"`
 }
 
 type CreditType struct {

+ 9 - 5
dashboard/src/lib/hooks/useStripe.tsx

@@ -35,6 +35,10 @@ type TGetPublishableKey = {
   publishableKey: string;
 };
 
+type TGetCredits = {
+  credits: number;
+};
+
 export const usePaymentMethods = (): TUsePaymentMethod => {
   const { currentProject } = useContext(Context);
 
@@ -176,12 +180,12 @@ export const usePublishableKey = (): TGetPublishableKey => {
   };
 };
 
-export const usePorterCredits = (): TGetPublishableKey => {
+export const usePorterCredits = (): TGetCredits => {
   const { currentProject } = useContext(Context);
 
-  // Fetch list of payment methods
-  const keyReq = useQuery(
-    ["getPublishableKey", currentProject?.id],
+  // Fetch available credits
+  const creditsReq = useQuery(
+    ["getPorterCredits", currentProject?.id],
     async () => {
       if (!currentProject?.id || currentProject.id === -1) {
         return;
@@ -198,7 +202,7 @@ export const usePorterCredits = (): TGetPublishableKey => {
   );
 
   return {
-    publishableKey: keyReq.data,
+    credits: creditsReq.data,
   };
 };
 

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

@@ -12,6 +12,7 @@ import Text from "components/porter/Text";
 import {
   checkIfProjectHasPayment,
   usePaymentMethods,
+  usePorterCredits,
   useSetDefaultPaymentMethod,
 } from "lib/hooks/useStripe";
 
@@ -36,6 +37,12 @@ function BillingPage(): JSX.Element {
 
   const { refetchPaymentEnabled } = checkIfProjectHasPayment();
 
+  const { credits } = usePorterCredits();
+
+  const formatCredits = (credits: number): string => {
+    return (credits / 100).toFixed(2);
+  };
+
   const onCreate = async () => {
     await refetchPaymentMethods();
     setShouldCreate(false);
@@ -66,7 +73,7 @@ function BillingPage(): JSX.Element {
         <Image src={gift} style={{ marginTop: "-2px" }} />
         <Spacer inline x={1} />
         <Text size={20}>
-          {paymentMethodList?.length > 0 ? "$ 5.00" : "$ 0.00"}
+          {credits > 0 ? `$${formatCredits(credits)}` : "$ 0.00"}
         </Text>
       </Container>
       <Spacer y={2} />

+ 5 - 5
internal/billing/metronome.go

@@ -77,7 +77,7 @@ func (m *MetronomeClient) AddCustomerPlan(customerID uuid.UUID, planID uuid.UUID
 
 	var result types.AddCustomerPlanResponse
 
-	err = post(path, http.MethodPost, m.ApiKey, req, result)
+	err = post(path, http.MethodPost, m.ApiKey, req, &result)
 	if err != nil {
 		return customerPlanID, err
 	}
@@ -114,7 +114,7 @@ func (m *MetronomeClient) GetCustomerCredits(customerID uuid.UUID) (credits int6
 		return credits, fmt.Errorf("customer id empty")
 	}
 
-	path := fmt.Sprintf("credits/listGrants")
+	path := "credits/listGrants"
 
 	req := types.ListCreditGrantsRequest{
 		CustomerIDs: []uuid.UUID{
@@ -122,13 +122,13 @@ func (m *MetronomeClient) GetCustomerCredits(customerID uuid.UUID) (credits int6
 		},
 	}
 
-	var result types.CreditGrant
-	err = post(path, http.MethodPost, m.ApiKey, req, result)
+	var result types.ListCreditGrantsResponse
+	err = post(path, http.MethodPost, m.ApiKey, req, &result)
 	if err != nil {
 		return credits, err
 	}
 
-	return result.Balance.ExcludingPending, nil
+	return result.Data[0].Balance.IncludingPending, nil
 }
 
 func post(path string, method string, apiKey string, body interface{}, data interface{}) (err error) {