diff --git a/frontend/package.json b/frontend/package.json index af9d6e6..ae7d13b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,7 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", + "@stripe/stripe-js": "^2.2.2", "class-variance-authority": "^0.7.0", "classnames": "^2.3.2", "clsx": "^2.0.0", diff --git a/frontend/src/components/payments/PricingDialog.tsx b/frontend/src/components/payments/PricingDialog.tsx index da03daa..3ad06ad 100644 --- a/frontend/src/components/payments/PricingDialog.tsx +++ b/frontend/src/components/payments/PricingDialog.tsx @@ -8,10 +8,14 @@ import { DialogTrigger, } from "../ui/dialog"; import { FaCheckCircle } from "react-icons/fa"; +import Spinner from "../custom-ui/Spinner"; +import useStripeCheckout from "./useStripeCheckout"; const LOGOS = ["microsoft", "amazon", "mit", "stanford", "bytedance", "baidu"]; const PricingDialog: React.FC = () => { + const { checkout, isLoadingCheckout } = useStripeCheckout(); + return ( { + + {/* Economy Plan */} +
+

Hobby

+

Higher limits

+
+ $15 + / month +
+ + + +
    +
  • + + 300 credits +
  • +
  • + + Slack support +
  • +
+

diff --git a/frontend/src/components/payments/useStripeCheckout.ts b/frontend/src/components/payments/useStripeCheckout.ts new file mode 100644 index 0000000..23e266c --- /dev/null +++ b/frontend/src/components/payments/useStripeCheckout.ts @@ -0,0 +1,66 @@ +import { useEffect, useState } from "react"; +import toast from "react-hot-toast"; +import { SAAS_BACKEND_URL, STRIPE_PUBLISHABLE_KEY } from "../../config"; +import { addEvent } from "../../lib/analytics"; +import { Stripe, loadStripe } from "@stripe/stripe-js"; +import { useAuthenticatedFetch } from "../hosted/useAuthenticatedFetch"; + +interface CreateCheckoutSessionResponse { + sessionId: string; +} + +export default function useStripeCheckout() { + const authenticatedFetch = useAuthenticatedFetch(); + + const [stripe, setStripe] = useState(null); + const [isLoadingCheckout, setIsLoadingCheckout] = useState(false); + + const checkout = async (priceLookupKey: string) => { + const rewardfulReferralId = "xxx"; // TODO: Use later with Rewardful + + if (!stripe) { + addEvent("StripeNotLoaded"); + return; + } + + try { + setIsLoadingCheckout(true); + + // Create a Checkout Session + const res: CreateCheckoutSessionResponse = await authenticatedFetch( + `${SAAS_BACKEND_URL}/create_checkout_session` + + `?price_lookup_key=${priceLookupKey}` + + `&rewardful_referral_id=${rewardfulReferralId}`, + "POST" + ); + + // Redirect to Stripe Checkout + const { error } = await stripe.redirectToCheckout({ + sessionId: res.sessionId, + }); + if (error) { + throw new Error(error.message); + } + } catch (e) { + toast.error("Error directing you to checkout. Please contact support."); + addEvent("StripeCheckoutError"); + } finally { + setIsLoadingCheckout(false); + } + }; + + // Load Stripe when the component mounts + useEffect(() => { + async function load() { + try { + setStripe(await loadStripe(STRIPE_PUBLISHABLE_KEY)); + } catch (e) { + console.error(e); + addEvent("StripeFailedToLoad"); + } + } + load(); + }, []); + + return { checkout, isLoadingCheckout }; +} diff --git a/frontend/src/config.ts b/frontend/src/config.ts index e4ddaf2..c9a7272 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -15,3 +15,8 @@ export const PICO_BACKEND_FORM_SECRET = export const CLERK_PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY || null; + +export const STRIPE_PUBLISHABLE_KEY = + import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY || null; + +export const SAAS_BACKEND_URL = import.meta.env.VITE_SAAS_BACKEND_URL || null; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b229fc0..4722580 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1287,6 +1287,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@stripe/stripe-js@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-2.2.2.tgz#9c1318a3e09a6df0a667d54d7f6edb917670109d" + integrity sha512-LvFZRZEBoMe6vXC6RoOAIbXWo/0JDdndq43ekL9M6affcM7PtF5KALmwt91BazW7q49sbSl0l7TunWhhSwEW4w== + "@types/babel__core@^7.20.3": version "7.20.4" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz"