Merge pull request #1006 from automatisch/aut-73

feat: add checkout process
This commit is contained in:
Ömer Faruk Aydın
2023-03-21 11:34:16 +03:00
committed by GitHub
8 changed files with 144 additions and 8 deletions

View File

@@ -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,

View File

@@ -485,7 +485,7 @@ type GetPaymentPortalUrl {
type GetPaddleInfo { type GetPaddleInfo {
sandbox: Boolean sandbox: Boolean
vendorId: String vendorId: Int
} }
type PaymentPlan { type PaymentPlan {

View File

@@ -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>

View 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>
);
};

View File

@@ -0,0 +1,10 @@
import { gql } from '@apollo/client';
export const GET_PADDLE_INFO = gql`
query GetPaddleInfo {
getPaddleInfo {
sandbox
vendorId
}
}
`;

View 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,
};
}

View 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
};
}

View File

@@ -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>