Fix race condition on stripe subscription (#9629)
Fixes https://github.com/twentyhq/core-team-issues/issues/191 - remove automatic redirection on payment success page when subscription status is undefined - add an effect component to refresh the subscription status on payment success page Observation: Locally, I had to delay the stripe webhook subscription created endpoint by 7s to see race condition issue https://github.com/user-attachments/assets/463e1816-34fd-4c4f-b590-3994a3a3e91a
This commit is contained in:
@ -146,7 +146,7 @@ const testCases = [
|
|||||||
{ loc: AppPath.PlanRequired, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.InviteTeam, res: AppPath.InviteTeam },
|
{ loc: AppPath.PlanRequired, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.InviteTeam, res: AppPath.InviteTeam },
|
||||||
{ loc: AppPath.PlanRequired, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.Completed, res: defaultHomePagePath },
|
{ loc: AppPath.PlanRequired, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.Completed, res: defaultHomePagePath },
|
||||||
|
|
||||||
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, subscriptionStatus: undefined, onboardingStatus: OnboardingStatus.PlanRequired, res: AppPath.PlanRequired },
|
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, subscriptionStatus: undefined, onboardingStatus: OnboardingStatus.PlanRequired, res: undefined },
|
||||||
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Canceled, onboardingStatus: OnboardingStatus.Completed, res: '/settings/billing' },
|
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Canceled, onboardingStatus: OnboardingStatus.Completed, res: '/settings/billing' },
|
||||||
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Unpaid, onboardingStatus: OnboardingStatus.Completed, res: '/settings/billing' },
|
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Unpaid, onboardingStatus: OnboardingStatus.Completed, res: '/settings/billing' },
|
||||||
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.PastDue, onboardingStatus: OnboardingStatus.Completed, res: defaultHomePagePath },
|
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.PastDue, onboardingStatus: OnboardingStatus.Completed, res: defaultHomePagePath },
|
||||||
|
|||||||
@ -42,7 +42,8 @@ export const usePageChangeEffectNavigateLocation = () => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
onboardingStatus === OnboardingStatus.PlanRequired &&
|
onboardingStatus === OnboardingStatus.PlanRequired &&
|
||||||
!isMatchingLocation(AppPath.PlanRequired)
|
!isMatchingLocation(AppPath.PlanRequired) &&
|
||||||
|
!isMatchingLocation(AppPath.PlanRequiredSuccess)
|
||||||
) {
|
) {
|
||||||
return AppPath.PlanRequired;
|
return AppPath.PlanRequired;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
import {
|
import {
|
||||||
AnimatedEaseIn,
|
AnimatedEaseIn,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
|
isDefined,
|
||||||
MainButton,
|
MainButton,
|
||||||
RGBA,
|
RGBA,
|
||||||
UndecoratedLink,
|
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { SubTitle } from '@/auth/components/SubTitle';
|
import { SubTitle } from '@/auth/components/SubTitle';
|
||||||
import { Title } from '@/auth/components/Title';
|
import { Title } from '@/auth/components/Title';
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
|
import {
|
||||||
|
OnboardingStatus,
|
||||||
|
useGetCurrentUserLazyQuery,
|
||||||
|
} from '~/generated/graphql';
|
||||||
|
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||||
|
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { OnboardingStatus } from '~/generated/graphql';
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const StyledCheckContainer = styled.div`
|
const StyledCheckContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -34,11 +41,38 @@ const StyledButtonContainer = styled.div`
|
|||||||
|
|
||||||
export const PaymentSuccess = () => {
|
export const PaymentSuccess = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
const navigate = useNavigate();
|
||||||
|
const subscriptionStatus = useSubscriptionStatus();
|
||||||
|
const onboardingStatus = useOnboardingStatus();
|
||||||
|
const [getCurrentUser] = useGetCurrentUserLazyQuery();
|
||||||
|
const setCurrentUser = useSetRecoilState(currentUserState);
|
||||||
const color =
|
const color =
|
||||||
theme.name === 'light' ? theme.grayScale.gray90 : theme.grayScale.gray10;
|
theme.name === 'light' ? theme.grayScale.gray90 : theme.grayScale.gray10;
|
||||||
|
|
||||||
if (currentUser?.onboardingStatus === OnboardingStatus.Completed) {
|
const navigateWithSubscriptionCheck = async () => {
|
||||||
|
if (isDefined(subscriptionStatus)) {
|
||||||
|
navigate(AppPath.CreateWorkspace);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await getCurrentUser({ fetchPolicy: 'network-only' });
|
||||||
|
const currentUser = result.data?.currentUser;
|
||||||
|
const refreshedSubscriptionStatus =
|
||||||
|
currentUser?.currentWorkspace?.currentBillingSubscription?.status;
|
||||||
|
|
||||||
|
if (isDefined(currentUser) && isDefined(refreshedSubscriptionStatus)) {
|
||||||
|
setCurrentUser(currentUser);
|
||||||
|
navigate(AppPath.CreateWorkspace);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
"We're waiting for a confirmation from our payment provider (Stripe).\n" +
|
||||||
|
'Please try again in a few seconds, sorry.',
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (onboardingStatus === OnboardingStatus.Completed) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,9 +86,11 @@ export const PaymentSuccess = () => {
|
|||||||
<Title>All set!</Title>
|
<Title>All set!</Title>
|
||||||
<SubTitle>Your account has been activated.</SubTitle>
|
<SubTitle>Your account has been activated.</SubTitle>
|
||||||
<StyledButtonContainer>
|
<StyledButtonContainer>
|
||||||
<UndecoratedLink to={AppPath.CreateWorkspace}>
|
<MainButton
|
||||||
<MainButton title="Start" width={200} />
|
title="Start"
|
||||||
</UndecoratedLink>
|
width={200}
|
||||||
|
onClick={navigateWithSubscriptionCheck}
|
||||||
|
/>
|
||||||
</StyledButtonContainer>
|
</StyledButtonContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user