فهرست منبع

Add publishable key endpoint

Mauricio Araujo 2 سال پیش
والد
کامیت
34cbc92423

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

@@ -72,5 +72,32 @@ func (c *CreateBillingCustomerHandler) ServeHTTP(w http.ResponseWriter, r *http.
 		return
 	}
 
+	c.WriteResult(w, r, "")
+}
+
+// GetPublishableKeyHandler will return the configured publishable key
+type GetPublishableKeyHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+// NewCreateBillingCustomerIfNotExists will create a new CreateBillingCustomerIfNotExists
+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()
+
+	// 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)
 	c.WriteResult(w, r, publishableKey)
 }

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

@@ -451,6 +451,31 @@ func getProjectRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/billing/publishable_key -> project.NewGetPublishableKeyHandler
+	publishableKeyEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/billing/publishable_key",
+			},
+			Scopes: []types.PermissionScope{},
+		},
+	)
+
+	publishableKeyHandler := billing.NewGetPublishableKeyHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: publishableKeyEndpoint,
+		Handler:  publishableKeyHandler,
+		Router:   r,
+	})
+
 	// GET /api/projects/{project_id}/clusters -> cluster.NewClusterListHandler
 	listClusterEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 24 - 6
dashboard/src/lib/hooks/useStripe.tsx

@@ -31,7 +31,7 @@ type TCheckHasPaymentEnabled = {
   refetchPaymentEnabled: any;
 };
 
-type TCheckCustomerExists = {
+type TGetPublishableKey = {
   publishableKey: string;
 };
 
@@ -150,20 +150,38 @@ export const checkIfProjectHasPayment = (): TCheckHasPaymentEnabled => {
   };
 };
 
-export const checkBillingCustomerExists = (): TCheckCustomerExists => {
+export const checkBillingCustomerExists = () => {
+  const { user, currentProject } = useContext(Context);
+
+  useQuery(["checkCustomerExists", currentProject?.id], async () => {
+    if (!currentProject?.id || currentProject.id === -1) {
+      return;
+    }
+    const res = await api.checkBillingCustomerExists(
+      "<token>",
+      { user_email: user?.email },
+      { project_id: currentProject?.id }
+    );
+    return res.data;
+  });
+};
+
+export const usePublishableKey = (): TGetPublishableKey => {
   const { user, currentProject } = useContext(Context);
 
   // Fetch list of payment methods
   const keyReq = useQuery(
-    ["checkCustomerExists", currentProject?.id],
+    ["getPublishableKey", currentProject?.id],
     async () => {
       if (!currentProject?.id || currentProject.id === -1) {
         return;
       }
-      const res = await api.checkBillingCustomerExists(
+      const res = await api.getPublishableKey(
         "<token>",
-        { user_email: user?.email },
-        { project_id: currentProject?.id }
+        {},
+        {
+          project_id: currentProject?.id,
+        }
       );
       return res.data;
     }

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

@@ -17,6 +17,7 @@ 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";
@@ -293,6 +294,9 @@ 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

+ 2 - 2
dashboard/src/main/home/modals/BillingModal.tsx

@@ -6,7 +6,7 @@ import Link from "components/porter/Link";
 import Modal from "components/porter/Modal";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
-import { checkBillingCustomerExists } from "lib/hooks/useStripe";
+import { usePublishableKey } from "lib/hooks/useStripe";
 
 import PaymentSetupForm from "./PaymentSetupForm";
 
@@ -17,7 +17,7 @@ const BillingModal = ({
   back: (value: React.SetStateAction<boolean>) => void;
   onCreate: () => Promise<void>;
 }) => {
-  const { publishableKey } = checkBillingCustomerExists();
+  const { publishableKey } = usePublishableKey();
   const stripePromise = loadStripe(publishableKey);
 
   const appearance = {

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

@@ -10,7 +10,6 @@ import Image from "components/porter/Image";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
 import {
-  checkBillingCustomerExists,
   checkIfProjectHasPayment,
   usePaymentMethods,
   useSetDefaultPaymentMethod,
@@ -34,7 +33,6 @@ function BillingPage(): JSX.Element {
     deletingIds,
   } = usePaymentMethods();
   const { setDefaultPaymentMethod } = useSetDefaultPaymentMethod();
-  checkBillingCustomerExists();
 
   const { refetchPaymentEnabled } = checkIfProjectHasPayment();
 

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

@@ -3450,6 +3450,16 @@ const checkBillingCustomerExists = baseApi<
   }
 >("POST", ({ project_id }) => `/api/projects/${project_id}/billing/customer`);
 
+const getPublishableKey = baseApi<
+  {},
+  {
+    project_id?: number;
+  }
+>(
+  "GET",
+  ({ project_id }) => `/api/projects/${project_id}/billing/publishable_key`
+);
+
 const getHasBilling = baseApi<{}, { project_id: number }>(
   "GET",
   ({ project_id }) => `/api/projects/${project_id}/billing`
@@ -3847,6 +3857,7 @@ export default {
 
   // BILLING
   checkBillingCustomerExists,
+  getPublishableKey,
   listPaymentMethod,
   addPaymentMethod,
   setDefaultPaymentMethod,