mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat: Delete Stripe payment method
This commit is contained in:
@@ -27,7 +27,7 @@ export class PaymentServicesController extends BaseController {
|
||||
param('paymentMethodId').exists(),
|
||||
body('name').optional().isString(),
|
||||
body('options.bankAccountId').optional().isNumeric(),
|
||||
body('options.clearningAccountId').optional().isNumeric(),
|
||||
body('options.clearingAccountId').optional().isNumeric(),
|
||||
body('options.showVisa').optional().isBoolean(),
|
||||
body('options.showMasterCard').optional().isBoolean(),
|
||||
body('options.showDiscover').optional().isBoolean(),
|
||||
|
||||
@@ -26,6 +26,7 @@ export class GetPaymentMethodsStateService {
|
||||
const isStripeAccountCreated = !!stripePayment;
|
||||
const isStripePaymentActive = !!(stripePayment?.active || null);
|
||||
|
||||
const stripePaymentMethodId = stripePayment?.id || null;
|
||||
const stripeAccountId = stripePayment?.accountId || null;
|
||||
const stripePublishableKey = config.stripePayment.publishableKey;
|
||||
const stripeCurrencies = ['USD', 'EUR'];
|
||||
@@ -36,6 +37,7 @@ export class GetPaymentMethodsStateService {
|
||||
isStripeAccountCreated,
|
||||
isStripePaymentActive,
|
||||
stripeAccountId,
|
||||
stripePaymentMethodId,
|
||||
stripePublishableKey,
|
||||
stripeCurrencies,
|
||||
stripeRedirectUrl,
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface GetPaymentMethodsPOJO {
|
||||
isStripeAccountCreated: boolean;
|
||||
isStripePaymentActive: boolean;
|
||||
stripeAccountId: string | null;
|
||||
stripePaymentMethodId: number | null;
|
||||
stripePublishableKey: string | null;
|
||||
stripeCurrencies: Array<string>;
|
||||
stripeRedirectUrl: string | null;
|
||||
|
||||
@@ -30,6 +30,7 @@ import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts';
|
||||
import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts';
|
||||
import { BankAccountAlerts } from '@/containers/CashFlow/AccountTransactions/alerts';
|
||||
import { BrandingTemplatesAlerts } from '../BrandingTemplates/alerts/BrandingTemplatesAlerts';
|
||||
import { PaymentMethodsAlerts } from '../Preferences/PaymentMethods/alerts/PaymentMethodsAlerts';
|
||||
|
||||
export default [
|
||||
...AccountsAlerts,
|
||||
@@ -63,4 +64,5 @@ export default [
|
||||
...SubscriptionAlerts,
|
||||
...BankAccountAlerts,
|
||||
...BrandingTemplatesAlerts,
|
||||
...PaymentMethodsAlerts,
|
||||
];
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import styled from 'styled-components';
|
||||
import { Button, Classes, Intent, Text } from '@blueprintjs/core';
|
||||
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 {
|
||||
@@ -9,10 +18,15 @@ import {
|
||||
} from './PreferencesPaymentMethodsBoot';
|
||||
import { StripePreSetupDialog } from './dialogs/StripePreSetupDialog/StripePreSetupDialog';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import { useDialogActions, useDrawerActions } from '@/hooks/state';
|
||||
import {
|
||||
useAlertActions,
|
||||
useDialogActions,
|
||||
useDrawerActions,
|
||||
} from '@/hooks/state';
|
||||
import { useCreateStripeAccountLink } from '@/hooks/query/stripe-integration';
|
||||
import { StripeIntegrationEditDrawer } from './drawers/StripeIntegrationEditDrawer';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
import { MoreIcon } from '@/icons/More';
|
||||
|
||||
export default function PreferencesPaymentMethodsPage() {
|
||||
return (
|
||||
@@ -26,12 +40,12 @@ export default function PreferencesPaymentMethodsPage() {
|
||||
<Stack>
|
||||
<StripePaymentMethod />
|
||||
</Stack>
|
||||
</PaymentMethodsBoot>
|
||||
|
||||
<StripePreSetupDialog dialogName={DialogsName.StripeSetup} />
|
||||
<StripeIntegrationEditDrawer
|
||||
name={DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT}
|
||||
/>
|
||||
<StripePreSetupDialog dialogName={DialogsName.StripeSetup} />
|
||||
<StripeIntegrationEditDrawer
|
||||
name={DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT}
|
||||
/>
|
||||
</PaymentMethodsBoot>
|
||||
</PaymentMethodsRoot>
|
||||
);
|
||||
}
|
||||
@@ -39,12 +53,15 @@ 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,
|
||||
@@ -78,10 +95,24 @@ function StripePaymentMethod() {
|
||||
openDrawer(DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT);
|
||||
};
|
||||
|
||||
const handleDeleteConnectionClick = () => {
|
||||
openAlert('delete-stripe-payment-method', {
|
||||
paymentMethodId: stripePaymentMethodId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card style={{ margin: 0 }}>
|
||||
<Group position="apart">
|
||||
<StripeLogo />
|
||||
<Group>
|
||||
<StripeLogo />
|
||||
|
||||
{isAccountActive && (
|
||||
<Tag minimal intent={Intent.SUCCESS}>
|
||||
Active
|
||||
</Tag>
|
||||
)}
|
||||
</Group>
|
||||
<Group spacing={10}>
|
||||
<Button small onClick={handleEditBtnClick}>
|
||||
Edit
|
||||
@@ -102,6 +133,22 @@ function StripePaymentMethod() {
|
||||
Complete Stripe Set Up
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{isAccountCreated && (
|
||||
<Popover
|
||||
content={
|
||||
<Menu>
|
||||
<MenuItem
|
||||
intent={Intent.DANGER}
|
||||
text={'Delete Connection'}
|
||||
onClick={handleDeleteConnectionClick}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button small icon={<MoreIcon size={16} />} />
|
||||
</Popover>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
|
||||
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
import { useDeletePaymentMethod } from '@/hooks/query/payment-methods';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Delete Stripe connection alert.
|
||||
*/
|
||||
function DeleteStripeAccountAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { paymentMethodId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { isLoading, mutateAsync: deletePaymentMethod } =
|
||||
useDeletePaymentMethod();
|
||||
|
||||
// Handle cancel open bill alert.
|
||||
const handleCancelOpenBill = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// Handle confirm bill open.
|
||||
const handleConfirmBillOpen = () => {
|
||||
deletePaymentMethod({ paymentMethodId })
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The Stripe payment account has been deleted.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeAlert(name);
|
||||
})
|
||||
.catch((error) => {
|
||||
closeAlert(name);
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={'Delete Account'}
|
||||
intent={Intent.DANGER}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelOpenBill}
|
||||
onConfirm={handleConfirmBillOpen}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>Are you sure want to delete your Stripe account connection?</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(DeleteStripeAccountAlert);
|
||||
@@ -0,0 +1,13 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
const DeleteStripeConnectionAlert = React.lazy(
|
||||
() => import('./DeleteStripeConnectionAlert'),
|
||||
);
|
||||
|
||||
export const PaymentMethodsAlerts = [
|
||||
{
|
||||
name: 'delete-stripe-payment-method',
|
||||
component: DeleteStripeConnectionAlert,
|
||||
},
|
||||
];
|
||||
@@ -1,6 +1,12 @@
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import { Formik, FormikHelpers } from 'formik';
|
||||
import { useUpdatePaymentMethod } from '@/hooks/query/payment-services';
|
||||
import { AppToaster } from '@/components';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { usePaymentMethodsBoot } from '../PreferencesPaymentMethodsBoot';
|
||||
import { useDrawerActions } from '@/hooks/state';
|
||||
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||
|
||||
interface StripeIntegrationFormValues {
|
||||
paymentAccountId: string;
|
||||
@@ -24,10 +30,34 @@ interface StripeIntegrationEditFormProps {
|
||||
export function StripeIntegrationEditForm({
|
||||
children,
|
||||
}: StripeIntegrationEditFormProps) {
|
||||
const { closeDrawer } = useDrawerActions();
|
||||
const { name } = useDrawerContext();
|
||||
const { mutateAsync: updatePaymentMethod } = useUpdatePaymentMethod();
|
||||
const { paymentMethodsState } = usePaymentMethodsBoot();
|
||||
const stripePaymentState = paymentMethodsState?.stripe;
|
||||
const paymentMethodId = stripePaymentState?.stripePaymentMethodId;
|
||||
|
||||
const onSubmit = (
|
||||
values: StripeIntegrationFormValues,
|
||||
{ setSubmitting }: FormikHelpers<StripeIntegrationFormValues>,
|
||||
) => {
|
||||
setSubmitting(true);
|
||||
updatePaymentMethod({ paymentMethodId, values })
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The Stripe settings have been updated.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
closeDrawer(name);
|
||||
})
|
||||
.catch(() => {
|
||||
setSubmitting(false);
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -44,7 +44,7 @@ export function StripeIntegrationEditFormContent() {
|
||||
export function StripeIntegrationEditFormFooter() {
|
||||
const { name } = useDrawerContext();
|
||||
const { closeDrawer } = useDrawerActions();
|
||||
const { submitForm } = useFormikContext();
|
||||
const { submitForm, isSubmitting } = useFormikContext();
|
||||
|
||||
const handleSubmitBtnClick = () => {
|
||||
submitForm();
|
||||
@@ -56,7 +56,11 @@ export function StripeIntegrationEditFormFooter() {
|
||||
return (
|
||||
<>
|
||||
<Group spacing={10}>
|
||||
<Button intent={Intent.PRIMARY} onClick={handleSubmitBtnClick}>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
loading={isSubmitting}
|
||||
onClick={handleSubmitBtnClick}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button onClick={handleCancelBtnClick}>Cancel</Button>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
// @ts-nocheck
|
||||
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
import useApiRequest from '../useRequest';
|
||||
import { transformToCamelCase } from '@/utils';
|
||||
import { transformToCamelCase, transfromToSnakeCase } from '@/utils';
|
||||
|
||||
const PaymentServicesQueryKey = 'PaymentServices';
|
||||
|
||||
@@ -37,7 +42,8 @@ export interface GetPaymentServicesStateResponse {
|
||||
stripe: {
|
||||
isStripeAccountCreated: boolean;
|
||||
isStripePaymentActive: boolean;
|
||||
stripeAccountId: string;
|
||||
stripeAccountId: string | null;
|
||||
stripePaymentMethodId: number | null;
|
||||
stripeCurrencies: string[];
|
||||
stripePublishableKey: string;
|
||||
stripeRedirectUrl: string;
|
||||
@@ -69,3 +75,42 @@ export const useGetPaymentServicesState = (
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
interface UpdatePaymentMethodResponse {
|
||||
id: number;
|
||||
message: string;
|
||||
}
|
||||
interface UpdatePaymentMethodValues {
|
||||
paymentMethodId: string | number;
|
||||
values: {
|
||||
name: string;
|
||||
bankAccountId: number;
|
||||
clearingAccountId: number;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Updates a payment method.
|
||||
* @returns {UseMutationResult<UpdatePaymentMethodResponse, Error, UpdatePaymentMethodValues, unknown>}
|
||||
*/
|
||||
export const useUpdatePaymentMethod = (): UseMutationResult<
|
||||
UpdatePaymentMethodResponse,
|
||||
Error,
|
||||
UpdatePaymentMethodValues,
|
||||
unknown
|
||||
> => {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation<
|
||||
UpdatePaymentMethodResponse,
|
||||
Error,
|
||||
UpdatePaymentMethodValues,
|
||||
unknown
|
||||
>((data: UpdatePaymentMethodValues) =>
|
||||
apiRequest
|
||||
.post(
|
||||
`/payment-services/${data.paymentMethodId}`,
|
||||
transfromToSnakeCase(data.values),
|
||||
)
|
||||
.then((response) => response.data),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
closeDialog,
|
||||
openDrawer,
|
||||
closeDrawer,
|
||||
openAlert,
|
||||
closeAlert,
|
||||
} from '@/store/dashboard/dashboard.actions';
|
||||
|
||||
export const useDispatchAction = (action) => {
|
||||
@@ -86,3 +88,10 @@ export const useDrawerActions = () => {
|
||||
closeDrawer: useDispatchAction(closeDrawer),
|
||||
};
|
||||
};
|
||||
|
||||
export const useAlertActions = () => {
|
||||
return {
|
||||
openAlert: useDispatchAction(openAlert),
|
||||
closeAlert: useDispatchAction(closeAlert),
|
||||
};
|
||||
};
|
||||
|
||||
26
packages/webapp/src/icons/More.tsx
Normal file
26
packages/webapp/src/icons/More.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
|
||||
interface MoreIconProps extends React.SVGProps<SVGSVGElement> {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const MoreIcon: React.FC<MoreIconProps> = ({ size = 16, ...props }) => (
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
enableBackground={`new 0 0 ${size} ${size}`}
|
||||
xmlSpace="preserve"
|
||||
{...props}
|
||||
>
|
||||
<g id="more_3_">
|
||||
<circle cx={size / 8} cy={size / 2.00625} r={size / 8} />
|
||||
<circle cx={size - size / 8} cy={size / 2.00625} r={size / 8} />
|
||||
<circle cx={size / 2} cy={size / 2.00625} r={size / 8} />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
Reference in New Issue
Block a user