Merge pull request #1006 from automatisch/aut-73
feat: add checkout process
This commit is contained in:
@@ -39,7 +39,7 @@ type AppConfig = {
|
|||||||
smtpPassword: string;
|
smtpPassword: string;
|
||||||
fromEmail: string;
|
fromEmail: string;
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
paddleVendorId: string;
|
paddleVendorId: number;
|
||||||
paddleVendorAuthCode: string;
|
paddleVendorAuthCode: string;
|
||||||
stripeSecretKey: string;
|
stripeSecretKey: string;
|
||||||
stripeSigningSecret: string;
|
stripeSigningSecret: string;
|
||||||
@@ -113,7 +113,7 @@ const appConfig: AppConfig = {
|
|||||||
smtpPassword: process.env.SMTP_PASSWORD,
|
smtpPassword: process.env.SMTP_PASSWORD,
|
||||||
fromEmail: process.env.FROM_EMAIL,
|
fromEmail: process.env.FROM_EMAIL,
|
||||||
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
||||||
paddleVendorId: process.env.PADDLE_VENDOR_ID,
|
paddleVendorId: Number(process.env.PADDLE_VENDOR_ID),
|
||||||
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
|
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
|
||||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||||
stripeSigningSecret: process.env.STRIPE_SIGNING_SECRET,
|
stripeSigningSecret: process.env.STRIPE_SIGNING_SECRET,
|
||||||
|
@@ -485,7 +485,7 @@ type GetPaymentPortalUrl {
|
|||||||
|
|
||||||
type GetPaddleInfo {
|
type GetPaddleInfo {
|
||||||
sandbox: Boolean
|
sandbox: Boolean
|
||||||
vendorId: String
|
vendorId: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaymentPlan {
|
type PaymentPlan {
|
||||||
|
@@ -16,14 +16,26 @@ import Paper from '@mui/material/Paper';
|
|||||||
import LockIcon from '@mui/icons-material/Lock';
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
|
|
||||||
import usePaymentPlans from 'hooks/usePaymentPlans.ee';
|
import usePaymentPlans from 'hooks/usePaymentPlans.ee';
|
||||||
|
import useCurrentUser from 'hooks/useCurrentUser';
|
||||||
|
import usePaddle from 'hooks/usePaddle.ee';
|
||||||
|
|
||||||
export default function UpgradeFreeTrial() {
|
export default function UpgradeFreeTrial() {
|
||||||
const { plans, loading } = usePaymentPlans();
|
const { plans, loading } = usePaymentPlans();
|
||||||
|
const currentUser = useCurrentUser();
|
||||||
|
const { loaded: paddleLoaded } = usePaddle();
|
||||||
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
||||||
const selectedPlan = plans?.[selectedIndex];
|
const selectedPlan = plans?.[selectedIndex];
|
||||||
|
|
||||||
const updateSelection = (index: number) => setSelectedIndex(index);
|
const updateSelection = (index: number) => setSelectedIndex(index);
|
||||||
|
|
||||||
|
const handleCheckout = React.useCallback(() => {
|
||||||
|
window.Paddle.Checkout?.open({
|
||||||
|
product: selectedPlan.productId,
|
||||||
|
email: currentUser.email,
|
||||||
|
passthrough: JSON.stringify({ id: currentUser.id, email: currentUser.email })
|
||||||
|
})
|
||||||
|
}, [selectedPlan, currentUser]);
|
||||||
|
|
||||||
if (loading || !plans.length) return null;
|
if (loading || !plans.length) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -140,7 +152,13 @@ export default function UpgradeFreeTrial() {
|
|||||||
<Typography variant="subtitle2" sx={{ fontSize: '12px', mt: 0 }}>
|
<Typography variant="subtitle2" sx={{ fontSize: '12px', mt: 0 }}>
|
||||||
+ VAT if applicable
|
+ VAT if applicable
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button size="small" variant="contained" sx={{ mt: 2 }}>
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
onClick={handleCheckout}
|
||||||
|
disabled={!paddleLoaded}
|
||||||
|
>
|
||||||
<LockIcon fontSize="small" sx={{ mr: 1 }} />
|
<LockIcon fontSize="small" sx={{ mr: 1 }} />
|
||||||
Pay securely via Paddle
|
Pay securely via Paddle
|
||||||
</Button>
|
</Button>
|
||||||
|
72
packages/web/src/contexts/Paddle.ee.tsx
Normal file
72
packages/web/src/contexts/Paddle.ee.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import useCloud from 'hooks/useCloud';
|
||||||
|
import usePaddleInfo from 'hooks/usePaddleInfo.ee';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
Paddle: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaddleContextParams = {
|
||||||
|
loaded: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PaddleContext =
|
||||||
|
React.createContext<PaddleContextParams>({
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
type PaddleProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PaddleProvider = (
|
||||||
|
props: PaddleProviderProps
|
||||||
|
): React.ReactElement => {
|
||||||
|
const { children } = props;
|
||||||
|
const isCloud = useCloud();
|
||||||
|
const { sandbox, vendorId } = usePaddleInfo();
|
||||||
|
const [loaded, setLoaded] = React.useState(false);
|
||||||
|
|
||||||
|
const value = React.useMemo(() => {
|
||||||
|
return {
|
||||||
|
loaded,
|
||||||
|
};
|
||||||
|
}, [loaded]);
|
||||||
|
|
||||||
|
React.useEffect(function loadPaddleScript() {
|
||||||
|
if (!isCloud) return;
|
||||||
|
|
||||||
|
const g = document.createElement('script')
|
||||||
|
const s = document.getElementsByTagName('script')[0];
|
||||||
|
g.src = 'https://cdn.paddle.com/paddle/paddle.js';
|
||||||
|
g.defer = true;
|
||||||
|
g.async = true;
|
||||||
|
|
||||||
|
if (s.parentNode) {
|
||||||
|
s.parentNode.insertBefore(g, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.onload = function () {
|
||||||
|
setLoaded(true);
|
||||||
|
}
|
||||||
|
}, [isCloud]);
|
||||||
|
|
||||||
|
React.useEffect(function initPaddleScript() {
|
||||||
|
if (!loaded || !vendorId) return;
|
||||||
|
|
||||||
|
if (sandbox) {
|
||||||
|
window.Paddle.Environment.set('sandbox');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Paddle.Setup({ vendor: vendorId });
|
||||||
|
}, [loaded, sandbox, vendorId])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PaddleContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</PaddleContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
10
packages/web/src/graphql/queries/get-paddle-info.ee.ts
Normal file
10
packages/web/src/graphql/queries/get-paddle-info.ee.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const GET_PADDLE_INFO = gql`
|
||||||
|
query GetPaddleInfo {
|
||||||
|
getPaddleInfo {
|
||||||
|
sandbox
|
||||||
|
vendorId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
14
packages/web/src/hooks/usePaddle.ee.ts
Normal file
14
packages/web/src/hooks/usePaddle.ee.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { PaddleContext } from 'contexts/Paddle.ee';
|
||||||
|
|
||||||
|
type UsePaddleReturn = {
|
||||||
|
loaded: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function usePaddle(): UsePaddleReturn {
|
||||||
|
const paddleContext = React.useContext(PaddleContext);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loaded: paddleContext.loaded,
|
||||||
|
};
|
||||||
|
}
|
19
packages/web/src/hooks/usePaddleInfo.ee.ts
Normal file
19
packages/web/src/hooks/usePaddleInfo.ee.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
|
||||||
|
import { GET_PADDLE_INFO } from 'graphql/queries/get-paddle-info.ee';
|
||||||
|
|
||||||
|
type UsePaddleInfoReturn = {
|
||||||
|
sandbox: boolean;
|
||||||
|
vendorId: string;
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function usePaddleInfo(): UsePaddleInfoReturn {
|
||||||
|
const { data, loading } = useQuery(GET_PADDLE_INFO);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sandbox: data?.getPaddleInfo?.sandbox,
|
||||||
|
vendorId: data?.getPaddleInfo?.vendorId,
|
||||||
|
loading
|
||||||
|
};
|
||||||
|
}
|
@@ -6,6 +6,7 @@ import ApolloProvider from 'components/ApolloProvider';
|
|||||||
import SnackbarProvider from 'components/SnackbarProvider';
|
import SnackbarProvider from 'components/SnackbarProvider';
|
||||||
import { AuthenticationProvider } from 'contexts/Authentication';
|
import { AuthenticationProvider } from 'contexts/Authentication';
|
||||||
import { AutomatischInfoProvider } from 'contexts/AutomatischInfo';
|
import { AutomatischInfoProvider } from 'contexts/AutomatischInfo';
|
||||||
|
import { PaddleProvider } from 'contexts/Paddle.ee';
|
||||||
import Router from 'components/Router';
|
import Router from 'components/Router';
|
||||||
import LiveChat from 'components/LiveChat/index.ee';
|
import LiveChat from 'components/LiveChat/index.ee';
|
||||||
import routes from 'routes';
|
import routes from 'routes';
|
||||||
@@ -18,11 +19,13 @@ ReactDOM.render(
|
|||||||
<ApolloProvider>
|
<ApolloProvider>
|
||||||
<AutomatischInfoProvider>
|
<AutomatischInfoProvider>
|
||||||
<IntlProvider>
|
<IntlProvider>
|
||||||
<ThemeProvider>
|
<PaddleProvider>
|
||||||
{routes}
|
<ThemeProvider>
|
||||||
|
{routes}
|
||||||
|
|
||||||
<LiveChat />
|
<LiveChat />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
</PaddleProvider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</AutomatischInfoProvider>
|
</AutomatischInfoProvider>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
|
Reference in New Issue
Block a user