diff --git a/src/pages/PlanPage/PlanPage.jsx b/src/pages/PlanPage/PlanPage.jsx index 8ae321a08c..8adf8f6fca 100644 --- a/src/pages/PlanPage/PlanPage.jsx +++ b/src/pages/PlanPage/PlanPage.jsx @@ -8,6 +8,7 @@ import config from 'config' import { SentryRoute } from 'sentry' +import { useStripeSetupIntent } from 'services/account/useStripeSetupIntent' import LoadingLogo from 'ui/LoadingLogo' import { PlanProvider } from './context' @@ -37,6 +38,7 @@ function PlanPage() { const { data: ownerData } = useSuspenseQueryV5( PlanPageDataQueryOpts({ owner, provider }) ) + const { data: setupIntent } = useStripeSetupIntent({ owner, provider }) if (config.IS_SELF_HOSTED || !ownerData?.isCurrentUserPartOfOrg) { return @@ -45,7 +47,10 @@ function PlanPage() { return (
- + }> diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx index 6cb32dc984..10952b5f7a 100644 --- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx +++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx @@ -7,7 +7,7 @@ import PaymentMethod from './PaymentMethod' import Button from 'ui/Button' import { useState } from 'react' import A from 'ui/A' -import EditablePaymentMethod from './EditPaymentMethod' +import EditPaymentMethod from './EditPaymentMethod' interface URLParams { provider: string @@ -29,8 +29,6 @@ function BillingDetails() { return null } - console.log('iseditmode', isEditMode) - return (
{/* Billing Details Section */} @@ -68,7 +66,12 @@ function BillingDetails() { )}
{isEditMode ? ( - + ) : ( <> diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethod/EditPaymentMethod.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethod/EditPaymentMethod.tsx index c7142559c7..23db86d014 100644 --- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethod/EditPaymentMethod.tsx +++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethod/EditPaymentMethod.tsx @@ -1,121 +1,22 @@ -import { - Elements, - PaymentElement, - useElements, - useStripe, -} from '@stripe/react-stripe-js' -import { loadStripe } from '@stripe/stripe-js' import React, { useState } from 'react' -import Button from 'ui/Button' - import AddressForm from '../Address/AddressForm' -// TODO - fetch from API -const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY || '' -const MANUALLY_FETCHED_CLIENT_SECRET = process.env.STRIPE_CLIENT_SECRET || '' - -const stripePromise = loadStripe(STRIPE_PUBLISHABLE_KEY) - -interface PaymentFormProps { - clientSecret: string -} - -const PaymentForm: React.FC = () => { - const stripe = useStripe() - const elements = useElements() - const [isSubmitting, setIsSubmitting] = useState(false) - const [errorMessage, setErrorMessage] = useState(null) - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault() - setIsSubmitting(true) - setErrorMessage(null) - - if (!stripe || !elements) { - setErrorMessage('Stripe has not loaded yet. Please try again.') - setIsSubmitting(false) - return - } - - const { error } = await stripe.confirmPayment({ - elements, - confirmParams: { - // eslint-disable-next-line camelcase - return_url: 'https://codecov.io', - }, - }) - - if (error) { - setErrorMessage(error.message || 'An unexpected error occurred.') - setIsSubmitting(false) - } else { - setIsSubmitting(false) - } - } - - return ( -
- -
- - -
- - {errorMessage &&
{errorMessage}
} -
- ) -} - -const PaymentPage: React.FC<{ clientSecret: string }> = ({ clientSecret }) => { - const options = { - clientSecret, - appearance: { - theme: 'stripe' as const, - }, - } +import EditPaymentMethodForm from './EditPaymentMethodForm' - return ( - - - - ) +interface EditPaymentMethodProps { + isEditMode: boolean + setEditMode: (isEditMode: boolean) => void + provider: string + owner: string } -interface EditablePaymentMethodProps { - clientSecret: string -} - -const EditPaymentMethod: React.FC = () => { - const clientSecret = MANUALLY_FETCHED_CLIENT_SECRET // TODO - fetch from API - +const EditPaymentMethod = ({ + isEditMode, + setEditMode, + provider, + owner, +}: EditPaymentMethodProps) => { const [activeTab, setActiveTab] = useState<'primary' | 'secondary'>('primary') return ( @@ -143,13 +44,23 @@ const EditPaymentMethod: React.FC = () => {
{activeTab === 'primary' && (
- + {}} provider={''} owner={''} />
)} {activeTab === 'secondary' && (
- + {}} provider={''} owner={''} />
)} diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethod/EditPaymentMethodForm.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethod/EditPaymentMethodForm.tsx new file mode 100644 index 0000000000..69fe90fdb5 --- /dev/null +++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/EditPaymentMethod/EditPaymentMethodForm.tsx @@ -0,0 +1,90 @@ +import { PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js" +import { useState } from "react" +import { Button } from "react-day-picker" + +interface PaymentMethodFormProps { + isEditMode: boolean + setEditMode: (isEditMode: boolean) => void + provider: string + owner: string +} + +const EditPaymentMethodForm = ({ + isEditMode, + setEditMode, + provider, + owner, +}: PaymentMethodFormProps) => { + const stripe = useStripe() + const elements = useElements() + const [isSubmitting, setIsSubmitting] = useState(false) + const [errorMessage, setErrorMessage] = useState(null) + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault() + setIsSubmitting(true) + setErrorMessage(null) + + if (!stripe || !elements) { + setErrorMessage('Stripe has not loaded yet. Please try again.') + setIsSubmitting(false) + return + } + + const { error } = await stripe.confirmPayment({ + elements, + confirmParams: { + // eslint-disable-next-line camelcase + return_url: 'https://codecov.io', + }, + }) + + if (error) { + setErrorMessage(error.message || 'An unexpected error occurred.') + setIsSubmitting(false) + } else { + setIsSubmitting(false) + } + } + + return ( +
+ +
+ + +
+ + {errorMessage &&
{errorMessage}
} +
+ ) +} + +export default EditPaymentMethodForm diff --git a/src/services/account/useStripeSetupIntent.ts b/src/services/account/useStripeSetupIntent.ts new file mode 100644 index 0000000000..37c07b55ed --- /dev/null +++ b/src/services/account/useStripeSetupIntent.ts @@ -0,0 +1,55 @@ +import { useQuery } from '@tanstack/react-query' +import { z } from 'zod' + +import Api from 'shared/api' +import { NetworkErrorObject } from 'shared/api/helpers' + +export const StripeSetupIntentSchema = z.object({ + clientSecret: z.string(), +}) + +export interface UseStripeSetupIntentArgs { + provider: string + owner: string + opts?: { + enabled?: boolean + } +} + +function fetchStripeSetupIntent({ + provider, + owner, + signal, +}: { + provider: string + owner: string + signal?: AbortSignal +}) { + const path = `/${provider}/${owner}/account-details/setup_intent` + return Api.get({ path, provider, signal }) +} + +export function useStripeSetupIntent({ + provider, + owner, + opts = {}, +}: UseStripeSetupIntentArgs) { + return useQuery({ + queryKey: ['setupIntent', provider, owner], + queryFn: ({ signal }) => + fetchStripeSetupIntent({ provider, owner, signal }).then((res) => { + const parsedRes = StripeSetupIntentSchema.safeParse(res) + + if (!parsedRes.success) { + return Promise.reject({ + status: 404, + data: {}, + dev: 'useStripeSetupIntent - 404 failed to parse', + } satisfies NetworkErrorObject) + } + + return parsedRes.data + }), + ...opts, + }) +}