diff --git a/src/components/StatefulButton/StatefulProvisioningButton.tsx b/src/components/StatefulButton/StatefulProvisioningButton.tsx index fe8c809..241ed88 100644 --- a/src/components/StatefulButton/StatefulProvisioningButton.tsx +++ b/src/components/StatefulButton/StatefulProvisioningButton.tsx @@ -23,7 +23,7 @@ const StatefulProvisioningButton = () => { const queryClient = useQueryClient(); const { authenticatedUser }: AppContextValue = useContext(AppContext); const { data: polledCheckoutIntent } = usePolledCheckoutIntent(); - const { data: successContext, refetch } = useBFFSuccess(); + const { data: successContext, refetch } = useBFFSuccess(authenticatedUser.userId ?? null); const { checkoutIntent } = successContext || {}; const [buttonState, setButtonState] = useState('pending'); const intl = useIntl(); diff --git a/src/components/StatefulButton/StatefulSubscribeButton.tsx b/src/components/StatefulButton/StatefulSubscribeButton.tsx index 113d19f..321aeac 100644 --- a/src/components/StatefulButton/StatefulSubscribeButton.tsx +++ b/src/components/StatefulButton/StatefulSubscribeButton.tsx @@ -5,11 +5,12 @@ import { StatefulButton } from '@openedx/paragon'; import { CheckoutContextValue, useCheckout } from '@stripe/react-stripe-js'; import { StripeCheckoutStatus } from '@stripe/stripe-js'; import { useQueryClient } from '@tanstack/react-query'; -import { useContext, useEffect, useState } from 'react'; +import { useCallback, useContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useCheckoutIntent, usePolledCheckoutIntent } from '@/components/app/data'; import { termsAndConditions } from '@/components/app/data/constants'; +import { queryBffContext, queryBffSuccess } from '@/components/app/data/queries/queries'; import { patchCheckoutIntent } from '@/components/app/data/services/checkout-intent'; import { determineExistingSuccessfulCheckoutIntent } from '@/components/app/data/services/context'; import { CheckoutPageRoute, CheckoutSubstepKey, DataStoreKey } from '@/constants/checkout'; @@ -73,17 +74,12 @@ const StatefulSubscribeButton = () => { confirm: CheckoutContextValue['confirm'], } = useCheckout(); const billingDetailsData = useCheckoutFormStore((state) => state.formData[DataStoreKey.BillingDetails]); - const setCheckoutSessionStatus = useCheckoutFormStore((state) => state.setCheckoutSessionStatus); + const { setCheckoutSessionStatus, setCheckoutSessionClientSecret } = useCheckoutFormStore((state) => state); const hasInvalidTerms = Object.values(billingDetailsData).some((value) => !value); const isFormValid = canConfirm && !hasInvalidTerms; - const onClickHandler = async () => { - // Sets the button to pending state and then calls confirm() - setStatefulButtonState('pending'); - - // Calls confirm() to start the Stripe checkout flow. - let response; + const patchCheckoutIntentCallback = useCallback(async () => { try { if (checkoutIntent) { const { id, country, state } = checkoutIntent; @@ -95,12 +91,20 @@ const StatefulSubscribeButton = () => { }; await patchCheckoutIntent(tncCheckoutUpdateRequest); } + } catch (patchError) { + logError(patchError); + } + }, [checkoutIntent]); + + const purchaseStripeCheckout = useCallback(async () => { + let response; + try { response = await confirm({ redirect: 'if_required', returnUrl: `${window.location.href}/${CheckoutSubstepKey.Success}`, }); - } catch (error) { - response = error; + } catch (stripeError) { + response = stripeError; } // Set the button to the appropriate state based on the response. // Stripe responses map 1:1 to button states except for 'default' which is the initial state. @@ -110,7 +114,16 @@ const StatefulSubscribeButton = () => { `[BillingDetails] Error during self service purchasing Stripe checkout for checkoutIntent: ${JSON.stringify(checkoutIntent)}, ${JSON.stringify(response.error)}`, ); } - }; + }, [checkoutIntent, confirm]); + + const onClickHandler = useCallback(async () => { + // Sets the button to pending state and then calls confirm() + setStatefulButtonState('pending'); + + // Calls confirm() to start the Stripe checkout flow. + await patchCheckoutIntentCallback(); + await purchaseStripeCheckout(); + }, [patchCheckoutIntentCallback, purchaseStripeCheckout]); // Visually alter the Subscribe button to a "successful" appearance if the polled intent state becomes successful. useEffect(() => { @@ -124,6 +137,13 @@ const StatefulSubscribeButton = () => { // If the payment succeeded from the stripe API, update the checkout session status. if (status.type === 'complete' && status.paymentStatus === 'paid') { setCheckoutSessionStatus(status); + setCheckoutSessionClientSecret(''); + queryClient.invalidateQueries({ + queryKey: queryBffSuccess(authenticatedUser.userId ?? null).queryKey, + }).then(data => data).catch(error => logError(error)); + queryClient.invalidateQueries({ + queryKey: queryBffContext(authenticatedUser.userId ?? null).queryKey, + }).then(data => data).catch(error => logError(error)); sendEnterpriseCheckoutTrackingEvent({ checkoutIntentId: checkoutIntent?.id ?? null, eventName: EVENT_NAMES.SUBSCRIPTION_CHECKOUT.PAYMENT_PROCESSED_SUCCESSFULLY, @@ -140,10 +160,10 @@ const StatefulSubscribeButton = () => { statefulButtonState, status, setCheckoutSessionStatus, - queryClient, - authenticatedUser, navigate, checkoutIntent?.id, + queryClient, + authenticatedUser.userId, ]); const props = { diff --git a/src/components/Stepper/StepperContent/BillingDetailsSuccessContent.tsx b/src/components/Stepper/StepperContent/BillingDetailsSuccessContent.tsx index e45585f..36bbff0 100644 --- a/src/components/Stepper/StepperContent/BillingDetailsSuccessContent.tsx +++ b/src/components/Stepper/StepperContent/BillingDetailsSuccessContent.tsx @@ -27,13 +27,13 @@ const determineBannerMessage = (state?: string | null) => { const BillingDetailsSuccessContent = () => { const { authenticatedUser }:AppContextValue = useContext(AppContext); - const { data: successBFFContext } = useBFFSuccess(authenticatedUser?.id); + const { data: successBFFContext, refetch } = useBFFSuccess(authenticatedUser?.userId || null); const { checkoutIntent } = successBFFContext || {}; const bannerElement = useMemo( () => determineBannerMessage(checkoutIntent?.state), [checkoutIntent?.state], ); - + refetch().catch(error => error); return ( <> diff --git a/src/components/Stepper/Steps/BillingDetails.tsx b/src/components/Stepper/Steps/BillingDetails.tsx index 1298235..5369a1d 100644 --- a/src/components/Stepper/Steps/BillingDetails.tsx +++ b/src/components/Stepper/Steps/BillingDetails.tsx @@ -1,27 +1,34 @@ +import { AppContext } from '@edx/frontend-platform/react'; import { Stack, Stepper } from '@openedx/paragon'; +import { useContext } from 'react'; import { Helmet } from 'react-helmet'; -import { useCheckoutSessionClientSecret } from '@/components/app/data'; +import { useBFFSuccess, useCheckoutSessionClientSecret } from '@/components/app/data'; import { BillingDetailsPage } from '@/components/billing-details-pages'; -import { useStepperContent } from '@/components/Stepper/Steps/hooks'; +import { BillingDetailsSuccessContent } from '@/components/Stepper/StepperContent'; import { StripeProvider } from '@/components/StripeProvider'; import { CheckoutStepKey } from '@/constants/checkout'; +import { useCheckoutFormStore } from '@/hooks/useCheckoutFormStore'; const BillingDetails = () => { + const { authenticatedUser }: AppContextValue = useContext(AppContext); const checkoutSessionClientSecret = useCheckoutSessionClientSecret(); - const StepperContent = useStepperContent(); - - if (!checkoutSessionClientSecret) { + const { data: successContext } = useBFFSuccess(authenticatedUser.userId || null); + const state = useCheckoutFormStore(s => s.checkoutSessionStatus); + const successfulCheckout = state.type === 'complete' && state.paymentStatus === 'paid'; + console.log({ + successfulCheckout, successContext, checkoutSessionClientSecret, state, + }); + if ((successfulCheckout && successContext?.checkoutIntent?.existingSuccessfulCheckoutIntent) || !checkoutSessionClientSecret) { return ( <> - + - ); } return ( diff --git a/src/components/app/routes/loaders/checkoutStepperLoader.ts b/src/components/app/routes/loaders/checkoutStepperLoader.ts index b2918d0..1480fb5 100644 --- a/src/components/app/routes/loaders/checkoutStepperLoader.ts +++ b/src/components/app/routes/loaders/checkoutStepperLoader.ts @@ -142,7 +142,7 @@ async function billingDetailsSuccessLoader(queryClient: QueryClient): Promise