From f00b11279227d160c7429056d921e404a2851e80 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Fri, 14 Apr 2023 17:56:16 -0400 Subject: [PATCH] Change Stripe `` to `` (#8268) * Use Stripe PaymentElement * Only show needs confirmation, if the booking needs it * Clean up * Type fix * More type fixes --- apps/web/pages/booking/[uid].tsx | 2 +- .../ee/payments/components/Payment.tsx | 108 ++++++++++-------- .../ee/payments/components/PaymentPage.tsx | 26 ++--- 3 files changed, 72 insertions(+), 64 deletions(-) diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx index 311d736d18..928daf900d 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/pages/booking/[uid].tsx @@ -177,7 +177,7 @@ export default function Success(props: SuccessProps) { // - Event Type has require confirmation option enabled always // - EventType has conditionally enabled confirmation option based on how far the booking is scheduled. // - It's a paid event and payment is pending. - const needsConfirmation = bookingInfo.status === BookingStatus.PENDING; + const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation; const userIsOwner = !!(session?.user?.id && eventType.owner?.id === session.user.id); const isCancelled = diff --git a/packages/features/ee/payments/components/Payment.tsx b/packages/features/ee/payments/components/Payment.tsx index c2cfb0248b..a81f5e7fc8 100644 --- a/packages/features/ee/payments/components/Payment.tsx +++ b/packages/features/ee/payments/components/Payment.tsx @@ -1,37 +1,20 @@ import type { Payment } from "@prisma/client"; -import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js"; -import type { StripeCardElementChangeEvent, StripeElementLocale } from "@stripe/stripe-js"; +import { useElements, useStripe, PaymentElement, Elements } from "@stripe/react-stripe-js"; import type stripejs from "@stripe/stripe-js"; +import type { StripeElementLocale } from "@stripe/stripe-js"; import { useRouter } from "next/router"; import type { SyntheticEvent } from "react"; import { useEffect, useState } from "react"; +import getStripe from "@calcom/app-store/stripepayment/lib/client"; import type { StripePaymentData, StripeSetupIntentData } from "@calcom/app-store/stripepayment/lib/server"; import { bookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect"; +import { CAL_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, Checkbox } from "@calcom/ui"; import type { EventType } from ".prisma/client"; -const CARD_OPTIONS: stripejs.StripeCardElementOptions = { - iconStyle: "solid" as const, - classes: { - base: "block p-2 w-full border-solid border-2 border-default rounded-md dark:bg-black dark:text-inverted dark:border-black focus-within:ring-black focus-within:border-black text-sm", - }, - style: { - base: { - color: "#666", - iconColor: "#666", - fontFamily: "ui-sans-serif, system-ui", - fontSmoothing: "antialiased", - fontSize: "16px", - "::placeholder": { - color: "#888888", - }, - }, - }, -} as const; - type Props = { payment: Omit & { data: StripePaymentData | StripeSetupIntentData; @@ -49,7 +32,7 @@ type States = | { status: "error"; error: Error } | { status: "ok" }; -export default function PaymentComponent(props: Props) { +const PaymentForm = (props: Props) => { const { t, i18n } = useLocale(); const router = useRouter(); const [state, setState] = useState({ status: "idle" }); @@ -62,20 +45,10 @@ export default function PaymentComponent(props: Props) { elements?.update({ locale: i18n.language as StripeElementLocale }); }, [elements, i18n.language]); - const handleChange = async (event: StripeCardElementChangeEvent) => { - // Listen for changes in the CardElement - // and display any errors as the customer types their card details - setState({ status: "idle" }); - if (event.error) - setState({ status: "error", error: new Error(event.error?.message || t("missing_card_fields")) }); - }; - const handleSubmit = async (ev: SyntheticEvent) => { ev.preventDefault(); if (!stripe || !elements || !router.isReady) return; - const card = elements.getElement(CardElement); - if (!card) return; setState({ status: "processing" }); let payload; @@ -85,16 +58,18 @@ export default function PaymentComponent(props: Props) { }; if (paymentOption === "HOLD" && "setupIntent" in props.payment.data) { const setupIntentData = props.payment.data as unknown as StripeSetupIntentData; - payload = await stripe.confirmCardSetup(setupIntentData.setupIntent.client_secret!, { - payment_method: { - card, + payload = await stripe.confirmSetup({ + elements, + confirmParams: { + return_url: `${CAL_URL}/booking/${props.bookingUid}`, }, }); } else if (paymentOption === "ON_BOOKING") { const paymentData = props.payment.data as unknown as StripePaymentData; - payload = await stripe.confirmCardPayment(paymentData.client_secret!, { - payment_method: { - card, + payload = await stripe.confirmPayment({ + elements, + confirmParams: { + return_url: `${CAL_URL}/booking/${props.bookingUid}`, }, }); } @@ -120,15 +95,12 @@ export default function PaymentComponent(props: Props) { }); } }; + return ( - {t("card_details")} - + + setState({ status: "idle" })} /> + {paymentOption === "HOLD" && ( {t("cancel")} + color="secondary"> {state.status === "processing" ? ( @@ -173,4 +144,47 @@ export default function PaymentComponent(props: Props) { )} ); +}; + +const ELEMENT_STYLES: stripejs.Appearance = { + theme: "none", +}; + +const ELEMENT_STYLES_DARK: stripejs.Appearance = { + theme: "night", + variables: { + colorText: "#d6d6d6", + fontWeightNormal: "600", + borderRadius: "6px", + colorBackground: "#101010", + colorPrimary: "#d6d6d6", + }, +}; + +export default function PaymentComponent(props: Props) { + const stripePromise = getStripe((props.payment.data as StripePaymentData).stripe_publishable_key); + const paymentOption = props.payment.paymentOption; + const [darkMode, setDarkMode] = useState(false); + let clientSecret: string | null; + + useEffect(() => { + setDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches); + }, []); + + if (paymentOption === "HOLD" && "setupIntent" in props.payment.data) { + clientSecret = props.payment.data.setupIntent.client_secret; + } else if (!("setupIntent" in props.payment.data)) { + clientSecret = props.payment.data.client_secret; + } + + return ( + + + + ); } diff --git a/packages/features/ee/payments/components/PaymentPage.tsx b/packages/features/ee/payments/components/PaymentPage.tsx index f37e690f8a..d551d16b56 100644 --- a/packages/features/ee/payments/components/PaymentPage.tsx +++ b/packages/features/ee/payments/components/PaymentPage.tsx @@ -1,4 +1,3 @@ -import { Elements } from "@stripe/react-stripe-js"; import classNames from "classnames"; import Head from "next/head"; import type { FC } from "react"; @@ -6,8 +5,6 @@ import { useEffect, useState } from "react"; import { FormattedNumber, IntlProvider } from "react-intl"; import { getSuccessPageLocationMessage } from "@calcom/app-store/locations"; -import getStripe from "@calcom/app-store/stripepayment/lib/client"; -import type { StripePaymentData } from "@calcom/app-store/stripepayment/lib/server"; import dayjs from "@calcom/dayjs"; import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants"; @@ -65,9 +62,9 @@ const PaymentPage: FC = (props) => { - + - + @@ -126,17 +123,14 @@ const PaymentPage: FC = (props) => { {t("paid")} )} {props.payment.appId === "stripe" && !props.payment.success && ( - - - + )} {props.payment.refunded && ( {t("refunded")}
{t("card_details")}