mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
feat: Stripe payment integration
This commit is contained in:
@@ -78,7 +78,7 @@ export function PaymentPortal() {
|
||||
|
||||
<Group position={'apart'} className={styles.totalItem}>
|
||||
<Text>Total</Text>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
<Text style={{ fontWeight: 500 }}>
|
||||
{sharableLinkMeta?.totalFormatted}
|
||||
</Text>
|
||||
</Group>
|
||||
@@ -96,7 +96,7 @@ export function PaymentPortal() {
|
||||
className={clsx(styles.totalItem, styles.borderBottomDark)}
|
||||
>
|
||||
<Text>Due Amount</Text>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
<Text style={{ fontWeight: 500 }}>
|
||||
{sharableLinkMeta?.dueAmountFormatted}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { StripeIntegration } from '@/containers/StripePayment/StripeIntegration';
|
||||
import { StripeIntegration2 } from '@/containers/StripePayment/StripeIntegration';
|
||||
|
||||
export default function IntegrationsPage() {
|
||||
return <StripeIntegration />
|
||||
return <StripeIntegration2 />
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
import { useGetPaymentServicesState } from '@/hooks/query/payment-services';
|
||||
|
||||
type PaymentMethodsContextType = {
|
||||
isPaymentMethodsStateLoading: boolean;
|
||||
paymentMethodsState: any;
|
||||
};
|
||||
|
||||
const PaymentMethodsContext = createContext<PaymentMethodsContextType>(
|
||||
{} as PaymentMethodsContextType,
|
||||
);
|
||||
|
||||
type PaymentMethodsProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const PaymentMethodsBoot = ({ children }: PaymentMethodsProviderProps) => {
|
||||
const { data: paymentMethodsState, isLoading: isPaymentMethodsStateLoading } =
|
||||
useGetPaymentServicesState();
|
||||
|
||||
const value = { isPaymentMethodsStateLoading, paymentMethodsState };
|
||||
|
||||
return (
|
||||
<PaymentMethodsContext.Provider value={value}>
|
||||
{children}
|
||||
</PaymentMethodsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const usePaymentMethodsBoot = () => {
|
||||
const context = useContext<PaymentMethodsContextType>(PaymentMethodsContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
'usePaymentMethods must be used within a PaymentMethodsProvider',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export { PaymentMethodsBoot, usePaymentMethodsBoot };
|
||||
@@ -1,20 +1,23 @@
|
||||
// @ts-nocheck
|
||||
import styled from 'styled-components';
|
||||
import { Button, Classes, Intent, Text } from '@blueprintjs/core';
|
||||
import { Box, Card, Group, Stack } from '@/components';
|
||||
import { StripeLogo } from '@/icons/StripeLogo';
|
||||
import { Button, Classes, Intent, Text } from '@blueprintjs/core';
|
||||
import { PaymentMethodsBoot } from './PreferencesPaymentMethodsBoot';
|
||||
|
||||
export default function PreferencesPaymentMethodsPage() {
|
||||
return (
|
||||
<PaymentMethodsRoot>
|
||||
<Text className={Classes.TEXT_MUTED} style={{ marginBottom: 20 }}>
|
||||
Accept payments from all the major debit and credit card networks
|
||||
through the supported payment gateways.
|
||||
</Text>
|
||||
<PaymentMethodsBoot>
|
||||
<Text className={Classes.TEXT_MUTED} style={{ marginBottom: 20 }}>
|
||||
Accept payments from all the major debit and credit card networks
|
||||
through the supported payment gateways.
|
||||
</Text>
|
||||
|
||||
<Stack>
|
||||
<StripePaymentMethod />
|
||||
</Stack>
|
||||
<Stack>
|
||||
<StripePaymentMethod />
|
||||
</Stack>
|
||||
</PaymentMethodsBoot>
|
||||
</PaymentMethodsRoot>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,17 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
ConnectAccountOnboarding,
|
||||
ConnectComponentsProvider,
|
||||
} from '@stripe/react-connect-js';
|
||||
import { useStripeConnect } from './use-stripe-connect';
|
||||
import { useCreateStripeAccount } from '@/hooks/query/stripe-integration';
|
||||
useCreateStripeAccount,
|
||||
useCreateStripeAccountLink,
|
||||
} from '@/hooks/query/stripe-integration';
|
||||
|
||||
export function StripeIntegration() {
|
||||
const [accountCreatePending, setAccountCreatePending] =
|
||||
useState<boolean>(false);
|
||||
const [onboardingExited, setOnboardingExited] = useState<boolean>(false);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
const [connectedAccountId, setConnectedAccountId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const stripeConnectInstance = useStripeConnect(connectedAccountId || '');
|
||||
const { mutateAsync: createAccount } = useCreateStripeAccount();
|
||||
|
||||
const handleSignupBtnClick = () => {
|
||||
setAccountCreatePending(true);
|
||||
setError(false);
|
||||
|
||||
createAccount({})
|
||||
.then((account) => {
|
||||
setConnectedAccountId(account.account_id);
|
||||
})
|
||||
.catch(() => {
|
||||
setError(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setAccountCreatePending(false);
|
||||
});
|
||||
};
|
||||
export const StripeIntegration2 = () => {
|
||||
const [accountCreatePending, setAccountCreatePending] = useState(false);
|
||||
const [accountLinkCreatePending, setAccountLinkCreatePending] =
|
||||
useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [connectedAccountId, setConnectedAccountId] = useState<string>();
|
||||
const { mutateAsync: createStripeAccount } = useCreateStripeAccount();
|
||||
const { mutateAsync: createStripeAccountLink } = useCreateStripeAccountLink();
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
@@ -40,29 +20,70 @@ export function StripeIntegration() {
|
||||
</div>
|
||||
<div className="content">
|
||||
{!connectedAccountId && <h2>Get ready for take off</h2>}
|
||||
{connectedAccountId && !stripeConnectInstance && (
|
||||
<h2>Add information to start accepting money</h2>
|
||||
)}
|
||||
{!connectedAccountId && (
|
||||
<p>
|
||||
Bigcapital Technology, Inc. is the world's leading air travel
|
||||
platform: join our team of pilots to help people travel faster.
|
||||
</p>
|
||||
)}
|
||||
{!accountCreatePending && !connectedAccountId && (
|
||||
<div>
|
||||
<button onClick={handleSignupBtnClick}>Sign up</button>
|
||||
</div>
|
||||
{connectedAccountId && (
|
||||
<h2>Add information to start accepting money</h2>
|
||||
)}
|
||||
{stripeConnectInstance && (
|
||||
<ConnectComponentsProvider connectInstance={stripeConnectInstance}>
|
||||
<ConnectAccountOnboarding
|
||||
onExit={() => setOnboardingExited(true)}
|
||||
/>
|
||||
</ConnectComponentsProvider>
|
||||
{connectedAccountId && (
|
||||
<p>
|
||||
Matt's Mats partners with Stripe to help you receive payments and
|
||||
keep your personal bank and details secure.
|
||||
</p>
|
||||
)}
|
||||
{!accountCreatePending && !connectedAccountId && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
setAccountCreatePending(true);
|
||||
setError(false);
|
||||
createStripeAccount({}).then((response) => {
|
||||
const { account_id: accountId } = response;
|
||||
setAccountCreatePending(false);
|
||||
|
||||
if (accountId) {
|
||||
setConnectedAccountId(accountId);
|
||||
}
|
||||
if (error) {
|
||||
setError(true);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Create an account!
|
||||
</button>
|
||||
)}
|
||||
{connectedAccountId && !accountLinkCreatePending && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setAccountLinkCreatePending(true);
|
||||
setError(false);
|
||||
createStripeAccountLink({
|
||||
stripeAccountId: connectedAccountId,
|
||||
})
|
||||
.then((res) => {
|
||||
const { clientSecret } = res;
|
||||
setAccountLinkCreatePending(false);
|
||||
|
||||
if (clientSecret.url) {
|
||||
window.location.href = clientSecret.url;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Add information
|
||||
</button>
|
||||
)}
|
||||
{error && <p className="error">Something went wrong!</p>}
|
||||
{(connectedAccountId || accountCreatePending || onboardingExited) && (
|
||||
{(connectedAccountId ||
|
||||
accountCreatePending ||
|
||||
accountLinkCreatePending) && (
|
||||
<div className="dev-callout">
|
||||
{connectedAccountId && (
|
||||
<p>
|
||||
@@ -71,17 +92,14 @@ export function StripeIntegration() {
|
||||
</p>
|
||||
)}
|
||||
{accountCreatePending && <p>Creating a connected account...</p>}
|
||||
{onboardingExited && (
|
||||
<p>The Account Onboarding component has exited</p>
|
||||
)}
|
||||
{accountLinkCreatePending && <p>Creating a new Account Link...</p>}
|
||||
</div>
|
||||
)}
|
||||
<div className="info-callout">
|
||||
<p>
|
||||
This is a sample app for Connect onboarding using the Account
|
||||
Onboarding embedded component.{' '}
|
||||
This is a sample app for Stripe-hosted Connect onboarding.{' '}
|
||||
<a
|
||||
href="https://docs.stripe.com/connect/onboarding/quickstart?connect-onboarding-surface=embedded"
|
||||
href="https://docs.stripe.com/connect/onboarding/quickstart?connect-onboarding-surface=hosted"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -92,4 +110,4 @@ export function StripeIntegration() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
69
packages/webapp/src/hooks/query/payment-methods.ts
Normal file
69
packages/webapp/src/hooks/query/payment-methods.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
} from 'react-query';
|
||||
import useApiRequest from '../useRequest';
|
||||
|
||||
|
||||
// # Delete payment method
|
||||
// -----------------------------------------
|
||||
interface DeletePaymentMethodValues {
|
||||
paymentMethodId: number;
|
||||
}
|
||||
export const useDeletePaymentMethod = (
|
||||
options?: UseMutationOptions<void, Error, DeletePaymentMethodValues>,
|
||||
): UseMutationResult<void, Error, DeletePaymentMethodValues> => {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation<void, Error, DeletePaymentMethodValues>(
|
||||
({ paymentMethodId }) => {
|
||||
return apiRequest
|
||||
.delete(`/payment-methods/${paymentMethodId}`)
|
||||
.then((res) => res.data);
|
||||
},
|
||||
{ ...options },
|
||||
);
|
||||
};
|
||||
|
||||
// # Edit payment method
|
||||
// -----------------------------------------
|
||||
interface EditPaymentMethodValues {
|
||||
paymentMethodId: number;
|
||||
name?: string;
|
||||
bankAccountId?: number;
|
||||
clearningAccountId?: number;
|
||||
showVisa?: boolean;
|
||||
showMasterCard?: boolean;
|
||||
showDiscover?: boolean;
|
||||
showAmer?: boolean;
|
||||
showJcb?: boolean;
|
||||
showDiners?: boolean;
|
||||
}
|
||||
interface EditPaymentMethodResponse {
|
||||
id: number;
|
||||
message: string;
|
||||
}
|
||||
export const useEditPaymentMethod = (
|
||||
options?: UseMutationOptions<
|
||||
EditPaymentMethodResponse,
|
||||
Error,
|
||||
EditPaymentMethodValues
|
||||
>,
|
||||
): UseMutationResult<
|
||||
EditPaymentMethodResponse,
|
||||
Error,
|
||||
EditPaymentMethodValues
|
||||
> => {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation<EditPaymentMethodResponse, Error, EditPaymentMethodValues>(
|
||||
({ paymentMethodId, ...editData }) => {
|
||||
return apiRequest
|
||||
.put(`/payment-methods/${paymentMethodId}`, editData)
|
||||
.then((res) => res.data);
|
||||
},
|
||||
{ ...options },
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,6 @@ import { transformToCamelCase } from '@/utils';
|
||||
const PaymentServicesQueryKey = 'PaymentServices';
|
||||
|
||||
export interface GetPaymentServicesResponse {}
|
||||
|
||||
/**
|
||||
* Retrieves the integrated payment services.
|
||||
* @param {UseQueryOptions<GetPaymentServicesResponse, Error>} options
|
||||
@@ -33,3 +32,31 @@ export const useGetPaymentServices = (
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export interface GetPaymentServicesStateResponse {}
|
||||
/**
|
||||
* Retrieves the state of payment services.
|
||||
* @param {UseQueryOptions<GetPaymentServicesStateResponse, Error>} options
|
||||
* @returns {UseQueryResult<GetPaymentServicesStateResponse, Error>}
|
||||
*/
|
||||
export const useGetPaymentServicesState = (
|
||||
options?: UseQueryOptions<GetPaymentServicesStateResponse, Error>,
|
||||
): UseQueryResult<GetPaymentServicesStateResponse, Error> => {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery<GetPaymentServicesStateResponse, Error>(
|
||||
['PaymentServicesState'],
|
||||
() =>
|
||||
apiRequest
|
||||
.get('/payment-services/state')
|
||||
.then(
|
||||
(response) =>
|
||||
transformToCamelCase(
|
||||
response.data?.paymentServicesState,
|
||||
) as GetPaymentServicesStateResponse,
|
||||
),
|
||||
{
|
||||
...options,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,49 @@ import {
|
||||
import useApiRequest from '../useRequest';
|
||||
import { transformToCamelCase } from '@/utils';
|
||||
|
||||
|
||||
// Create Stripe Account Link.
|
||||
// ------------------------------------
|
||||
interface StripeAccountLinkResponse {
|
||||
clientSecret: {
|
||||
created: number;
|
||||
expiresAt: number;
|
||||
object: string;
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
interface StripeAccountLinkValues {
|
||||
stripeAccountId: string;
|
||||
}
|
||||
|
||||
export const useCreateStripeAccountLink = (
|
||||
options?: UseMutationOptions<
|
||||
StripeAccountLinkResponse,
|
||||
Error,
|
||||
StripeAccountLinkValues
|
||||
>,
|
||||
): UseMutationResult<
|
||||
StripeAccountLinkResponse,
|
||||
Error,
|
||||
StripeAccountLinkValues
|
||||
> => {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
(values: StripeAccountLinkValues) => {
|
||||
return apiRequest
|
||||
.post('/stripe_integration/account_link', {
|
||||
stripe_account_id: values?.stripeAccountId,
|
||||
})
|
||||
.then((res) => transformToCamelCase(res.data));
|
||||
},
|
||||
{ ...options },
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Create Stripe Account Session.
|
||||
// ------------------------------------
|
||||
interface AccountSessionValues {
|
||||
connectedAccountId?: string;
|
||||
}
|
||||
@@ -40,6 +83,8 @@ export const useCreateStripeAccountSession = (
|
||||
);
|
||||
};
|
||||
|
||||
// Create Stripe Account.
|
||||
// ------------------------------------
|
||||
interface CreateStripeAccountValues {}
|
||||
interface CreateStripeAccountResponse {
|
||||
account_id: string;
|
||||
@@ -64,6 +109,8 @@ export const useCreateStripeAccount = (
|
||||
);
|
||||
};
|
||||
|
||||
// Create Stripe Checkout Session.
|
||||
// ------------------------------------
|
||||
interface CreateCheckoutSessionValues {
|
||||
linkId: string;
|
||||
}
|
||||
|
||||
@@ -9,20 +9,22 @@ export const ArrowBottomLeft: React.FC<ArrowBottomLeftProps> = ({
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14 3C14 2.45 13.55 2 13 2C12.72 2 12.47 2.11 12.29 2.29L4 10.59V6C4 5.45 3.55 5 3 5S2 5.45 2 6V13C2 13.55 2.45 14 3 14H10C10.55 14 11 13.55 11 13C11 12.45 10.55 12 10 12H5.41L13.7 3.71C13.89 3.53 14 3.28 14 3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span className={'bp4-icon bp4-icon-arrow-bottom-left'}>
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14 3C14 2.45 13.55 2 13 2C12.72 2 12.47 2.11 12.29 2.29L4 10.59V6C4 5.45 3.55 5 3 5S2 5.45 2 6V13C2 13.55 2.45 14 3 14H10C10.55 14 11 13.55 11 13C11 12.45 10.55 12 10 12H5.41L13.7 3.71C13.89 3.53 14 3.28 14 3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user