From 66d7baa1267d1154d5ce86ee05cc43f0228697e6 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Mon, 20 Mar 2023 23:23:44 +0000 Subject: [PATCH 1/2] fix: make Paddle vendor id number --- packages/backend/src/config/app.ts | 4 ++-- packages/backend/src/graphql/schema.graphql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/config/app.ts b/packages/backend/src/config/app.ts index 3364b834..a18038b1 100644 --- a/packages/backend/src/config/app.ts +++ b/packages/backend/src/config/app.ts @@ -39,7 +39,7 @@ type AppConfig = { smtpPassword: string; fromEmail: string; isCloud: boolean; - paddleVendorId: string; + paddleVendorId: number; paddleVendorAuthCode: string; stripeSecretKey: string; stripeSigningSecret: string; @@ -113,7 +113,7 @@ const appConfig: AppConfig = { smtpPassword: process.env.SMTP_PASSWORD, fromEmail: process.env.FROM_EMAIL, 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, stripeSecretKey: process.env.STRIPE_SECRET_KEY, stripeSigningSecret: process.env.STRIPE_SIGNING_SECRET, diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 872a0556..36f7de89 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -485,7 +485,7 @@ type GetPaymentPortalUrl { type GetPaddleInfo { sandbox: Boolean - vendorId: String + vendorId: Int } type PaymentPlan { From b5ed984f058097564b8e80574f00a5142acbb64f Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Mon, 20 Mar 2023 23:24:04 +0000 Subject: [PATCH 2/2] feat: add checkout process --- .../components/UpgradeFreeTrial/index.ee.tsx | 20 +++++- packages/web/src/contexts/Paddle.ee.tsx | 72 +++++++++++++++++++ .../src/graphql/queries/get-paddle-info.ee.ts | 10 +++ packages/web/src/hooks/usePaddle.ee.ts | 14 ++++ packages/web/src/hooks/usePaddleInfo.ee.ts | 19 +++++ packages/web/src/index.tsx | 11 +-- 6 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 packages/web/src/contexts/Paddle.ee.tsx create mode 100644 packages/web/src/graphql/queries/get-paddle-info.ee.ts create mode 100644 packages/web/src/hooks/usePaddle.ee.ts create mode 100644 packages/web/src/hooks/usePaddleInfo.ee.ts diff --git a/packages/web/src/components/UpgradeFreeTrial/index.ee.tsx b/packages/web/src/components/UpgradeFreeTrial/index.ee.tsx index 4fca59a0..9a0cfa90 100644 --- a/packages/web/src/components/UpgradeFreeTrial/index.ee.tsx +++ b/packages/web/src/components/UpgradeFreeTrial/index.ee.tsx @@ -16,14 +16,26 @@ import Paper from '@mui/material/Paper'; import LockIcon from '@mui/icons-material/Lock'; import usePaymentPlans from 'hooks/usePaymentPlans.ee'; +import useCurrentUser from 'hooks/useCurrentUser'; +import usePaddle from 'hooks/usePaddle.ee'; export default function UpgradeFreeTrial() { const { plans, loading } = usePaymentPlans(); + const currentUser = useCurrentUser(); + const { loaded: paddleLoaded } = usePaddle(); const [selectedIndex, setSelectedIndex] = React.useState(0); const selectedPlan = plans?.[selectedIndex]; 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; return ( @@ -140,7 +152,13 @@ export default function UpgradeFreeTrial() { + VAT if applicable - diff --git a/packages/web/src/contexts/Paddle.ee.tsx b/packages/web/src/contexts/Paddle.ee.tsx new file mode 100644 index 00000000..e4c0d5b7 --- /dev/null +++ b/packages/web/src/contexts/Paddle.ee.tsx @@ -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({ + 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 ( + + {children} + + ); +}; diff --git a/packages/web/src/graphql/queries/get-paddle-info.ee.ts b/packages/web/src/graphql/queries/get-paddle-info.ee.ts new file mode 100644 index 00000000..4e801865 --- /dev/null +++ b/packages/web/src/graphql/queries/get-paddle-info.ee.ts @@ -0,0 +1,10 @@ +import { gql } from '@apollo/client'; + +export const GET_PADDLE_INFO = gql` + query GetPaddleInfo { + getPaddleInfo { + sandbox + vendorId + } + } +`; diff --git a/packages/web/src/hooks/usePaddle.ee.ts b/packages/web/src/hooks/usePaddle.ee.ts new file mode 100644 index 00000000..747f0661 --- /dev/null +++ b/packages/web/src/hooks/usePaddle.ee.ts @@ -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, + }; +} diff --git a/packages/web/src/hooks/usePaddleInfo.ee.ts b/packages/web/src/hooks/usePaddleInfo.ee.ts new file mode 100644 index 00000000..85ff5905 --- /dev/null +++ b/packages/web/src/hooks/usePaddleInfo.ee.ts @@ -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 + }; +} diff --git a/packages/web/src/index.tsx b/packages/web/src/index.tsx index c2cd7099..bc657214 100644 --- a/packages/web/src/index.tsx +++ b/packages/web/src/index.tsx @@ -6,6 +6,7 @@ import ApolloProvider from 'components/ApolloProvider'; import SnackbarProvider from 'components/SnackbarProvider'; import { AuthenticationProvider } from 'contexts/Authentication'; import { AutomatischInfoProvider } from 'contexts/AutomatischInfo'; +import { PaddleProvider } from 'contexts/Paddle.ee'; import Router from 'components/Router'; import LiveChat from 'components/LiveChat/index.ee'; import routes from 'routes'; @@ -18,11 +19,13 @@ ReactDOM.render( - - {routes} + + + {routes} - - + + +