feat: Onboard accounts to Stripe Connect

This commit is contained in:
Ahmed Bouhuolia
2024-09-08 11:42:26 +02:00
parent 6d24474162
commit a183666df6
14 changed files with 360 additions and 3 deletions

View File

@@ -54,6 +54,11 @@ export default [
disabled: false,
href: '/preferences/items',
},
{
text: 'Integrations',
disabled: false,
href: '/preferences/integrations'
},
// {
// text: <T id={'sms_integration.label'} />,
// disabled: false,

View File

@@ -0,0 +1,5 @@
import { StripeIntegration } from '@/containers/StripePayment/StripeIntegration';
export default function IntegrationsPage() {
return <StripeIntegration />
}

View File

@@ -0,0 +1,96 @@
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';
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);
});
};
return (
<div className="container">
<div className="banner">
<h2>Bigcapital Technology, Inc.</h2>
</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>
)}
{stripeConnectInstance && (
<ConnectComponentsProvider connectInstance={stripeConnectInstance}>
<ConnectAccountOnboarding
onExit={() => setOnboardingExited(true)}
/>
</ConnectComponentsProvider>
)}
{error && <p className="error">Something went wrong!</p>}
{(connectedAccountId || accountCreatePending || onboardingExited) && (
<div className="dev-callout">
{connectedAccountId && (
<p>
Your connected account ID is:{' '}
<code className="bold">{connectedAccountId}</code>
</p>
)}
{accountCreatePending && <p>Creating a connected account...</p>}
{onboardingExited && (
<p>The Account Onboarding component has exited</p>
)}
</div>
)}
<div className="info-callout">
<p>
This is a sample app for Connect onboarding using the Account
Onboarding embedded component.{' '}
<a
href="https://docs.stripe.com/connect/onboarding/quickstart?connect-onboarding-surface=embedded"
target="_blank"
rel="noopener noreferrer"
>
View docs
</a>
</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,47 @@
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<StripeConnectInstance | null>();
const { mutateAsync: createAccountSession } = useCreateStripeAccountSession();
useEffect(() => {
if (connectedAccountId) {
const fetchClientSecret = async (): Promise<string> => {
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;
};

View File

@@ -0,0 +1,59 @@
// @ts-nocheck
import {
useMutation,
UseMutationOptions,
UseMutationResult,
} from 'react-query';
import useApiRequest from '../useRequest';
interface AccountSessionValues {
connectedAccountId?: string;
}
interface AccountSessionResponse {
client_secret: string;
}
export const useCreateStripeAccountSession = (
options?: UseMutationOptions<
AccountSessionResponse,
Error,
AccountSessionValues
>,
): UseMutationResult<AccountSessionResponse, Error, AccountSessionValues> => {
const apiRequest = useApiRequest();
return useMutation(
(values: AccountSessionValues) => {
return apiRequest
.post('/stripe_integration/account_session', {
account: values?.connectedAccountId,
})
.then((res) => res.data);
},
{ ...options },
);
};
interface CreateStripeAccountValues {}
interface CreateStripeAccountResponse {
account_id: string;
}
export const useCreateStripeAccount = (
options?: UseMutationOptions<
CreateStripeAccountResponse,
Error,
CreateStripeAccountValues
>,
) => {
const apiRequest = useApiRequest();
return useMutation(
(values: CreateStripeAccountValues) => {
return apiRequest
.post('/stripe_integration/account')
.then((res) => res.data);
},
{ ...options },
);
};

View File

@@ -103,6 +103,13 @@ 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')),