From 8109236e72bc8f7f171b3788b1a5d704b6f46d05 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 23 Sep 2024 13:21:54 +0200 Subject: [PATCH] fix: Stripe payment integration --- .../server/src/interfaces/StripePayment.ts | 3 +- .../PaymentServices/GetPaymentMethodsState.ts | 3 + .../src/services/PaymentServices/types.ts | 1 + .../src/services/PaymentServices/utils.ts | 9 + .../CreateStripeAccountService.ts | 8 +- .../Integrations/IntegrationsPage.tsx | 5 - .../PreferencesPaymentMethodsPage.tsx | 168 +---------------- .../PaymentMethods/StripePaymentMethod.tsx | 172 ++++++++++++++++++ .../alerts/DeleteStripeConnectionAlert.tsx | 3 +- .../Preferences/PaymentMethods/constants.ts | 1 + .../drawers/StripeIntegrationEditBoot.tsx | 10 +- .../drawers/StripeIntegrationEditContent.tsx | 5 +- .../InvoiceForm/InvoiceFormFooterLeft.tsx | 26 ++- .../StripePayment/StripeIntegration.tsx | 113 ------------ .../StripePayment/use-stripe-connect.ts | 47 ----- .../webapp/src/hooks/query/payment-methods.ts | 19 -- .../src/hooks/query/payment-services.ts | 38 ++++ packages/webapp/src/hooks/state/dashboard.tsx | 18 +- packages/webapp/src/routes/preferences.tsx | 7 - 19 files changed, 287 insertions(+), 369 deletions(-) create mode 100644 packages/server/src/services/PaymentServices/utils.ts delete mode 100644 packages/webapp/src/containers/Preferences/Integrations/IntegrationsPage.tsx create mode 100644 packages/webapp/src/containers/Preferences/PaymentMethods/StripePaymentMethod.tsx create mode 100644 packages/webapp/src/containers/Preferences/PaymentMethods/constants.ts delete mode 100644 packages/webapp/src/containers/StripePayment/StripeIntegration.tsx delete mode 100644 packages/webapp/src/containers/StripePayment/use-stripe-connect.ts diff --git a/packages/server/src/interfaces/StripePayment.ts b/packages/server/src/interfaces/StripePayment.ts index a8777cf40..41b1d6e23 100644 --- a/packages/server/src/interfaces/StripePayment.ts +++ b/packages/server/src/interfaces/StripePayment.ts @@ -15,7 +15,6 @@ export interface StripeInvoiceCheckoutSessionPOJO { redirectTo: string; } - export interface StripeWebhookEventPayload { event: any; -} \ No newline at end of file +} diff --git a/packages/server/src/services/PaymentServices/GetPaymentMethodsState.ts b/packages/server/src/services/PaymentServices/GetPaymentMethodsState.ts index 06ab5466a..edcb6a987 100644 --- a/packages/server/src/services/PaymentServices/GetPaymentMethodsState.ts +++ b/packages/server/src/services/PaymentServices/GetPaymentMethodsState.ts @@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi'; import HasTenancyService from '../Tenancy/TenancyService'; import { GetPaymentMethodsPOJO } from './types'; import config from '@/config'; +import { isStripePaymentConfigured } from './utils'; @Service() export class GetPaymentMethodsStateService { @@ -31,11 +32,13 @@ export class GetPaymentMethodsStateService { const stripePublishableKey = config.stripePayment.publishableKey; const stripeCurrencies = ['USD', 'EUR']; const stripeRedirectUrl = 'https://your-stripe-redirect-url.com'; + const isStripeServerConfigured = isStripePaymentConfigured(); const paymentMethodPOJO: GetPaymentMethodsPOJO = { stripe: { isStripeAccountCreated, isStripePaymentActive, + isStripeServerConfigured, stripeAccountId, stripePaymentMethodId, stripePublishableKey, diff --git a/packages/server/src/services/PaymentServices/types.ts b/packages/server/src/services/PaymentServices/types.ts index f23041b16..663d3c638 100644 --- a/packages/server/src/services/PaymentServices/types.ts +++ b/packages/server/src/services/PaymentServices/types.ts @@ -17,6 +17,7 @@ export interface GetPaymentMethodsPOJO { stripe: { isStripeAccountCreated: boolean; isStripePaymentActive: boolean; + isStripeServerConfigured: boolean; stripeAccountId: string | null; stripePaymentMethodId: number | null; stripePublishableKey: string | null; diff --git a/packages/server/src/services/PaymentServices/utils.ts b/packages/server/src/services/PaymentServices/utils.ts new file mode 100644 index 000000000..c8ddbc844 --- /dev/null +++ b/packages/server/src/services/PaymentServices/utils.ts @@ -0,0 +1,9 @@ +import config from '@/config'; + +export const isStripePaymentConfigured = () => { + return ( + config.stripePayment.secretKey && + config.stripePayment.publishableKey && + config.stripePayment.webhooksSecret + ); +}; diff --git a/packages/server/src/services/StripePayment/CreateStripeAccountService.ts b/packages/server/src/services/StripePayment/CreateStripeAccountService.ts index 19a7b1492..243cb0ee4 100644 --- a/packages/server/src/services/StripePayment/CreateStripeAccountService.ts +++ b/packages/server/src/services/StripePayment/CreateStripeAccountService.ts @@ -1,6 +1,6 @@ +import { Inject, Service } from 'typedi'; import { StripePaymentService } from '@/services/StripePayment/StripePaymentService'; import HasTenancyService from '@/services/Tenancy/TenancyService'; -import { Inject, Service } from 'typedi'; import { CreateStripeAccountDTO } from './types'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import events from '@/subscribers/events'; @@ -8,17 +8,17 @@ import events from '@/subscribers/events'; @Service() export class CreateStripeAccountService { @Inject() - private stripePaymentService: StripePaymentService; + private tenancy: HasTenancyService; @Inject() - private tenancy: HasTenancyService; + private stripePaymentService: StripePaymentService; @Inject() private eventPublisher: EventPublisher; /** * Creates a new Stripe account. - * @param {number} tenantId + * @param {number} tenantI * @param {CreateStripeAccountDTO} stripeAccountDTO * @returns {Promise} */ diff --git a/packages/webapp/src/containers/Preferences/Integrations/IntegrationsPage.tsx b/packages/webapp/src/containers/Preferences/Integrations/IntegrationsPage.tsx deleted file mode 100644 index 6b909000a..000000000 --- a/packages/webapp/src/containers/Preferences/Integrations/IntegrationsPage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { StripeIntegration2 } from '@/containers/StripePayment/StripeIntegration'; - -export default function IntegrationsPage() { - return -} diff --git a/packages/webapp/src/containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage.tsx b/packages/webapp/src/containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage.tsx index b23bc278a..38fd8e0f7 100644 --- a/packages/webapp/src/containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage.tsx +++ b/packages/webapp/src/containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage.tsx @@ -1,35 +1,20 @@ // @ts-nocheck import React, { useEffect } from 'react'; import styled from 'styled-components'; -import { - Button, - Classes, - Intent, - Menu, - MenuItem, - Popover, - Tag, - Text, -} from '@blueprintjs/core'; -import { AppToaster, Box, Card, Group, Stack } from '@/components'; -import { StripeLogo } from '@/icons/StripeLogo'; -import { - PaymentMethodsBoot, - usePaymentMethodsBoot, -} from './PreferencesPaymentMethodsBoot'; +import { Classes, Text } from '@blueprintjs/core'; +import { Box, Stack } from '@/components'; +import { PaymentMethodsBoot } from './PreferencesPaymentMethodsBoot'; import { StripePreSetupDialog } from './dialogs/StripePreSetupDialog/StripePreSetupDialog'; -import { DialogsName } from '@/constants/dialogs'; -import { - useAlertActions, - useChangePreferencesPageTitle, - useDialogActions, - useDrawerActions, -} from '@/hooks/state'; -import { useCreateStripeAccountLink } from '@/hooks/query/stripe-integration'; +import { useChangePreferencesPageTitle } from '@/hooks/state'; import { StripeIntegrationEditDrawer } from './drawers/StripeIntegrationEditDrawer'; +import { StripePaymentMethod } from './StripePaymentMethod'; +import { DialogsName } from '@/constants/dialogs'; import { DRAWERS } from '@/constants/drawers'; -import { MoreIcon } from '@/icons/More'; +/** + * Payment methods page. + * @returns {JSX.Element} + */ export default function PreferencesPaymentMethodsPage() { const changePageTitle = useChangePreferencesPageTitle(); @@ -58,141 +43,8 @@ export default function PreferencesPaymentMethodsPage() { ); } -function StripePaymentMethod() { - const { openDialog } = useDialogActions(); - const { openDrawer } = useDrawerActions(); - const { openAlert } = useAlertActions(); - - const { paymentMethodsState } = usePaymentMethodsBoot(); - const stripeState = paymentMethodsState?.stripe; - - const isAccountCreated = stripeState?.isStripeAccountCreated; - const isAccountActive = stripeState?.isStripePaymentActive; - const stripeAccountId = stripeState?.stripeAccountId; - const stripePaymentMethodId = stripeState?.stripePaymentMethodId; - - const { - mutateAsync: createStripeAccountLink, - isLoading: isCreateStripeLinkLoading, - } = useCreateStripeAccountLink(); - - // Handle Stripe setup button click. - const handleSetUpBtnClick = () => { - openDialog(DialogsName.StripeSetup); - }; - - // Handle complete Stripe setup button click. - const handleCompleteSetUpBtnClick = () => { - createStripeAccountLink({ stripeAccountId }) - .then((res) => { - const { clientSecret } = res; - - if (clientSecret.url) { - window.open(clientSecret.url, '_blank'); - } - }) - .catch(() => { - AppToaster.show({ - message: 'Something went wrong.', - intent: Intent.DANGER, - }); - }); - }; - - // Handle edit button click. - const handleEditBtnClick = () => { - openDrawer(DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT); - }; - - // Handle delete connection button click. - const handleDeleteConnectionClick = () => { - openAlert('delete-stripe-payment-method', { - paymentMethodId: stripePaymentMethodId, - }); - }; - - return ( - - - - - - {isAccountActive && ( - - Active - - )} - - - {isAccountActive && ( - - )} - {!isAccountCreated && ( - - )} - {isAccountCreated && !isAccountActive && ( - - )} - {isAccountCreated && ( - - - - } - > - + )} + {!isAccountCreated && ( + + )} + {isAccountCreated && !isAccountActive && ( + + )} + {isAccountCreated && ( + + + + } + > + - )} - {connectedAccountId && !accountLinkCreatePending && ( - - )} - {error &&

Something went wrong!

} - {(connectedAccountId || - accountCreatePending || - accountLinkCreatePending) && ( -
- {connectedAccountId && ( -

- Your connected account ID is:{' '} - {connectedAccountId} -

- )} - {accountCreatePending &&

Creating a connected account...

} - {accountLinkCreatePending &&

Creating a new Account Link...

} -
- )} -
-

- This is a sample app for Stripe-hosted Connect onboarding.{' '} - - View docs - -

-
- - - ); -}; diff --git a/packages/webapp/src/containers/StripePayment/use-stripe-connect.ts b/packages/webapp/src/containers/StripePayment/use-stripe-connect.ts deleted file mode 100644 index 44cbd5ab6..000000000 --- a/packages/webapp/src/containers/StripePayment/use-stripe-connect.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useState, useEffect } from 'react'; -import { - loadConnectAndInitialize, - StripeConnectInstance, -} from '@stripe/connect-js'; -import { useCreateStripeAccountSession } from '@/hooks/query/stripe-integration'; - -export const useStripeConnect = (connectedAccountId?: string) => { - const [stripeConnectInstance, setStripeConnectInstance] = - useState(); - const { mutateAsync: createAccountSession } = useCreateStripeAccountSession(); - - useEffect(() => { - if (connectedAccountId) { - const fetchClientSecret = async (): Promise => { - try { - const clientSecret = await createAccountSession({ - connectedAccountId, - }); - return clientSecret?.client_secret as string; - } catch (error) { - // Handle errors on the client side here - if (error instanceof Error) { - throw new Error(`An error occurred: ${error.message}`); - } else { - throw new Error('An unknown error occurred'); - } - } - }; - - setStripeConnectInstance( - loadConnectAndInitialize({ - publishableKey: 'pk_test_51PRck9BW396nDn7gxEw1uvkoGwl5BXDWnrhntQIWReiDnH2Zdm7uL0RSvzKN6SR6ELHDK99dF9UbVEumgTu8k0oN00pP0J91Lx', - fetchClientSecret, - appearance: { - overlays: 'dialog', - variables: { - colorPrimary: '#ffffff', - }, - }, - }), - ); - } - }, [connectedAccountId, createAccountSession]); - - return stripeConnectInstance; -}; diff --git a/packages/webapp/src/hooks/query/payment-methods.ts b/packages/webapp/src/hooks/query/payment-methods.ts index e796fcbef..80ffa3b90 100644 --- a/packages/webapp/src/hooks/query/payment-methods.ts +++ b/packages/webapp/src/hooks/query/payment-methods.ts @@ -7,25 +7,6 @@ import { import useApiRequest from '../useRequest'; -// # Delete payment method -// ----------------------------------------- -interface DeletePaymentMethodValues { - paymentMethodId: number; -} -export const useDeletePaymentMethod = ( - options?: UseMutationOptions, -): UseMutationResult => { - const apiRequest = useApiRequest(); - - return useMutation( - ({ paymentMethodId }) => { - return apiRequest - .delete(`/payment-services/${paymentMethodId}`) - .then((res) => res.data); - }, - { ...options }, - ); -}; // # Edit payment method // ----------------------------------------- diff --git a/packages/webapp/src/hooks/query/payment-services.ts b/packages/webapp/src/hooks/query/payment-services.ts index 8c2b3d27f..ac4859575 100644 --- a/packages/webapp/src/hooks/query/payment-services.ts +++ b/packages/webapp/src/hooks/query/payment-services.ts @@ -12,6 +12,9 @@ import { transformToCamelCase, transfromToSnakeCase } from '@/utils'; const PaymentServicesQueryKey = 'PaymentServices'; const PaymentServicesStateQueryKey = 'PaymentServicesState'; + +// # Get payment services. +// ----------------------------------------- export interface GetPaymentServicesResponse {} /** * Retrieves the integrated payment services. @@ -40,10 +43,13 @@ export const useGetPaymentServices = ( ); }; +// # Get payment services state. +// ----------------------------------------- export interface GetPaymentServicesStateResponse { stripe: { isStripeAccountCreated: boolean; isStripePaymentActive: boolean; + isStripeServerConfigured: boolean; stripeAccountId: string | null; stripePaymentMethodId: number | null; stripeCurrencies: string[]; @@ -78,6 +84,8 @@ export const useGetPaymentServicesState = ( ); }; +// # Update payment method +// ----------------------------------------- interface UpdatePaymentMethodResponse { id: number; message: string; @@ -125,6 +133,8 @@ export const useUpdatePaymentMethod = (): UseMutationResult< ); }; +// # Get payment method +// ----------------------------------------- interface GetPaymentMethodResponse {} /** * Retrieves a specific payment method. @@ -133,6 +143,7 @@ interface GetPaymentMethodResponse {} */ export const useGetPaymentMethod = ( paymentMethodId: number, + options?: UseQueryOptions, ): UseQueryResult => { const apiRequest = useApiRequest(); @@ -145,5 +156,32 @@ export const useGetPaymentMethod = ( (res) => transformToCamelCase(res.data?.data) as GetPaymentMethodResponse, ), + options, + ); +}; + +// # Delete payment method +// ----------------------------------------- +interface DeletePaymentMethodValues { + paymentMethodId: number; +} +export const useDeletePaymentMethod = ( + options?: UseMutationOptions, +): UseMutationResult => { + const apiRequest = useApiRequest(); + const queryClient = useQueryClient(); + + return useMutation( + ({ paymentMethodId }) => { + return apiRequest + .delete(`/payment-services/${paymentMethodId}`) + .then((res) => res.data); + }, + { + onSuccess: () => { + queryClient.invalidateQueries(PaymentServicesStateQueryKey); + }, + ...options, + }, ); }; diff --git a/packages/webapp/src/hooks/state/dashboard.tsx b/packages/webapp/src/hooks/state/dashboard.tsx index dadcc9666..ec1f6f6e3 100644 --- a/packages/webapp/src/hooks/state/dashboard.tsx +++ b/packages/webapp/src/hooks/state/dashboard.tsx @@ -83,19 +83,29 @@ export const useDialogActions = () => { }; }; +/** + * Drawer actions. + * @returns + */ export const useDrawerActions = () => { + const dispatch = useDispatch(); + return { - openDrawer: useDispatchAction(openDrawer), - closeDrawer: useDispatchAction(closeDrawer), + openDrawer: (name, payload?: {}) => dispatch(openDrawer(name, payload)), + closeDrawer: (name, payload?: {}) => dispatch(closeDrawer(name, payload)), }; }; +/** + * Alert actions. + * @returns + */ export const useAlertActions = () => { const dispatch = useDispatch(); return { - openAlert: (name, payload) => dispatch(openAlert(name, payload)), - closeAlert: (name, payload) => dispatch(closeAlert(name, payload)), + openAlert: (name, payload?: {}) => dispatch(openAlert(name, payload)), + closeAlert: (name, payload?: {}) => dispatch(closeAlert(name, payload)), }; }; diff --git a/packages/webapp/src/routes/preferences.tsx b/packages/webapp/src/routes/preferences.tsx index e644b64ce..926913982 100644 --- a/packages/webapp/src/routes/preferences.tsx +++ b/packages/webapp/src/routes/preferences.tsx @@ -110,13 +110,6 @@ export const getPreferenceRoutes = () => [ component: lazy(() => import('@/containers/Subscriptions/BillingPage')), exact: true, }, - { - path: `${BASE_URL}/integrations`, - component: lazy( - () => import('@/containers/Preferences/Integrations/IntegrationsPage'), - ), - exact: true, - }, { path: `${BASE_URL}/`, component: lazy(() => import('../containers/Preferences/DefaultRoute')),