mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-23 00:00:31 +00:00
feat: Onboard accounts to Stripe Connect
This commit is contained in:
@@ -93,3 +93,7 @@ S3_BUCKET=
|
|||||||
# PostHog
|
# PostHog
|
||||||
POSTHOG_API_KEY=
|
POSTHOG_API_KEY=
|
||||||
POSTHOG_HOST=
|
POSTHOG_HOST=
|
||||||
|
|
||||||
|
# Stripe Payment
|
||||||
|
STRIPE_PAYMENT_SECRET_KEY=
|
||||||
|
STRIPE_PAYMENT_PUBLISHABLE_KEY=
|
||||||
@@ -109,6 +109,7 @@
|
|||||||
"rtl-detect": "^1.0.4",
|
"rtl-detect": "^1.0.4",
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
"source-map-loader": "^4.0.1",
|
"source-map-loader": "^4.0.1",
|
||||||
|
"stripe": "^16.10.0",
|
||||||
"tmp-promise": "^3.0.3",
|
"tmp-promise": "^3.0.3",
|
||||||
"ts-transformer-keys": "^0.4.2",
|
"ts-transformer-keys": "^0.4.2",
|
||||||
"tsyringe": "^4.3.0",
|
"tsyringe": "^4.3.0",
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { NextFunction, Request, Response, Router } from 'express';
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { StripePaymentService } from '@/services/StripePayment/StripePaymentService';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class StripeIntegrationController {
|
||||||
|
@Inject()
|
||||||
|
private stripePaymentService: StripePaymentService;
|
||||||
|
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post('/account', asyncMiddleware(this.createAccount.bind(this)));
|
||||||
|
router.post(
|
||||||
|
'/account_session',
|
||||||
|
asyncMiddleware(this.createAccountSession.bind(this))
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createAccount(req: Request, res: Response, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const accountId = await this.stripePaymentService.createAccount();
|
||||||
|
res.status(201).json({ accountId });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createAccountSession(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { account } = req.body;
|
||||||
|
try {
|
||||||
|
const clientSecret = await this.stripePaymentService.createAccountSession(
|
||||||
|
account
|
||||||
|
);
|
||||||
|
res.status(200).json({ clientSecret });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,7 @@ import { Webhooks } from './controllers/Webhooks/Webhooks';
|
|||||||
import { ExportController } from './controllers/Export/ExportController';
|
import { ExportController } from './controllers/Export/ExportController';
|
||||||
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
||||||
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
||||||
|
import { StripeIntegrationController } from './controllers/StripeIntegration/StripeIntegrationController';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
@@ -147,6 +148,7 @@ export default () => {
|
|||||||
dashboard.use('/import', Container.get(ImportController).router());
|
dashboard.use('/import', Container.get(ImportController).router());
|
||||||
dashboard.use('/export', Container.get(ExportController).router());
|
dashboard.use('/export', Container.get(ExportController).router());
|
||||||
dashboard.use('/attachments', Container.get(AttachmentsController).router());
|
dashboard.use('/attachments', Container.get(AttachmentsController).router());
|
||||||
|
dashboard.use('/stripe_integration', Container.get(StripeIntegrationController).router());
|
||||||
|
|
||||||
dashboard.use('/', Container.get(ProjectTasksController).router());
|
dashboard.use('/', Container.get(ProjectTasksController).router());
|
||||||
dashboard.use('/', Container.get(ProjectTimesController).router());
|
dashboard.use('/', Container.get(ProjectTimesController).router());
|
||||||
|
|||||||
@@ -259,6 +259,14 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
posthog: {
|
posthog: {
|
||||||
apiKey: process.env.POSTHOG_API_KEY,
|
apiKey: process.env.POSTHOG_API_KEY,
|
||||||
host: process.env.POSTHOG_HOST
|
host: process.env.POSTHOG_HOST,
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stripe Payment Integration.
|
||||||
|
*/
|
||||||
|
stripePayment: {
|
||||||
|
secretKey: process.env.STRIPE_PAYMENT_SECRET_KEY || '',
|
||||||
|
publishableKey: process.env.STRIPE_PAYMENT_PUBLISHABLE_KEY || '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Service } from 'typedi';
|
||||||
|
import stripe from 'stripe';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class StripePaymentService {
|
||||||
|
private stripe;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.stripe = new stripe(config.stripePayment.secretKey, {
|
||||||
|
apiVersion: '2023-10-16',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createAccountSession(accountId: string) {
|
||||||
|
try {
|
||||||
|
const accountSession = await this.stripe.accountSessions.create({
|
||||||
|
account: accountId,
|
||||||
|
components: {
|
||||||
|
account_onboarding: { enabled: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return accountSession.client_secret;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
'An error occurred when calling the Stripe API to create an account session'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createAccount() {
|
||||||
|
try {
|
||||||
|
const account = await this.stripe.accounts.create({});
|
||||||
|
|
||||||
|
return account.id;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
'An error occurred when calling the Stripe API to create an account'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
"@casl/react": "^2.3.0",
|
"@casl/react": "^2.3.0",
|
||||||
"@craco/craco": "^5.9.0",
|
"@craco/craco": "^5.9.0",
|
||||||
"@reduxjs/toolkit": "^1.2.5",
|
"@reduxjs/toolkit": "^1.2.5",
|
||||||
|
"@stripe/connect-js": "^3.3.12",
|
||||||
|
"@stripe/react-connect-js": "^3.3.13",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.4.0",
|
"@testing-library/react": "^9.4.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ export default [
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/items',
|
href: '/preferences/items',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Integrations',
|
||||||
|
disabled: false,
|
||||||
|
href: '/preferences/integrations'
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// text: <T id={'sms_integration.label'} />,
|
// text: <T id={'sms_integration.label'} />,
|
||||||
// disabled: false,
|
// disabled: false,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { StripeIntegration } from '@/containers/StripePayment/StripeIntegration';
|
||||||
|
|
||||||
|
export default function IntegrationsPage() {
|
||||||
|
return <StripeIntegration />
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
59
packages/webapp/src/hooks/query/stripe-integration.ts
Normal file
59
packages/webapp/src/hooks/query/stripe-integration.ts
Normal 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 },
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -103,6 +103,13 @@ export const getPreferenceRoutes = () => [
|
|||||||
component: lazy(() => import('@/containers/Subscriptions/BillingPage')),
|
component: lazy(() => import('@/containers/Subscriptions/BillingPage')),
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `${BASE_URL}/integrations`,
|
||||||
|
component: lazy(
|
||||||
|
() => import('@/containers/Preferences/Integrations/IntegrationsPage'),
|
||||||
|
),
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `${BASE_URL}/`,
|
path: `${BASE_URL}/`,
|
||||||
component: lazy(() => import('../containers/Preferences/DefaultRoute')),
|
component: lazy(() => import('../containers/Preferences/DefaultRoute')),
|
||||||
|
|||||||
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@@ -302,6 +302,9 @@ importers:
|
|||||||
source-map-loader:
|
source-map-loader:
|
||||||
specifier: ^4.0.1
|
specifier: ^4.0.1
|
||||||
version: 4.0.2(webpack@5.91.0)
|
version: 4.0.2(webpack@5.91.0)
|
||||||
|
stripe:
|
||||||
|
specifier: ^16.10.0
|
||||||
|
version: 16.10.0
|
||||||
tmp-promise:
|
tmp-promise:
|
||||||
specifier: ^3.0.3
|
specifier: ^3.0.3
|
||||||
version: 3.0.3
|
version: 3.0.3
|
||||||
@@ -507,6 +510,12 @@ importers:
|
|||||||
'@reduxjs/toolkit':
|
'@reduxjs/toolkit':
|
||||||
specifier: ^1.2.5
|
specifier: ^1.2.5
|
||||||
version: 1.9.7(react-redux@7.2.9)(react@18.3.1)
|
version: 1.9.7(react-redux@7.2.9)(react@18.3.1)
|
||||||
|
'@stripe/connect-js':
|
||||||
|
specifier: ^3.3.12
|
||||||
|
version: 3.3.12
|
||||||
|
'@stripe/react-connect-js':
|
||||||
|
specifier: ^3.3.13
|
||||||
|
version: 3.3.13(@stripe/connect-js@3.3.12)(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@testing-library/jest-dom':
|
'@testing-library/jest-dom':
|
||||||
specifier: ^4.2.4
|
specifier: ^4.2.4
|
||||||
version: 4.2.4
|
version: 4.2.4
|
||||||
@@ -5710,6 +5719,22 @@ packages:
|
|||||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@stripe/connect-js@3.3.12:
|
||||||
|
resolution: {integrity: sha512-hXbgvGq9Lb6BYgsb8lcbjL76Yqsxr0yAj6T9ZFTfUK0O4otI5GSEWum9do9rf/E5OfYy6fR1FG/77Jve2w1o6Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@stripe/react-connect-js@3.3.13(@stripe/connect-js@3.3.12)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-kMxYjeQUcl/ixu/mSeX5QGIr/MuP+YxFSEBdb8j6w+tbK82tmcjyFDgoQTQwVXNqUV6jI66Kks3XcfpPRfeiJA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@stripe/connect-js': '>=3.3.11'
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
'@stripe/connect-js': 3.3.12
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@supercharge/promise-pool@3.2.0:
|
/@supercharge/promise-pool@3.2.0:
|
||||||
resolution: {integrity: sha512-pj0cAALblTZBPtMltWOlZTQSLT07jIaFNeM8TWoJD1cQMgDB9mcMlVMoetiB35OzNJpqQ2b+QEtwiR9f20mADg==}
|
resolution: {integrity: sha512-pj0cAALblTZBPtMltWOlZTQSLT07jIaFNeM8TWoJD1cQMgDB9mcMlVMoetiB35OzNJpqQ2b+QEtwiR9f20mADg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -24010,6 +24035,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
/stripe@16.10.0:
|
||||||
|
resolution: {integrity: sha512-H0qeSCkZVvk4fVchUbg0rNNviwOyw3Rsr9X6MKe84ajBeMz4ogEOZykaUcb/n0GSdvWlXAtbnB1gxl3xOlH+ZA==}
|
||||||
|
engines: {node: '>=12.*'}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 14.18.63
|
||||||
|
qs: 6.12.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/strnum@1.0.5:
|
/strnum@1.0.5:
|
||||||
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
|
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|||||||
Reference in New Issue
Block a user