diff --git a/src/CONST.ts b/src/CONST.ts index d93fb53be181..db29c1195e48 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3913,6 +3913,7 @@ const CONST = { }, STRIPE_GBP_AUTH_STATUSES: { SUCCEEDED: 'succeeded', + CARD_AUTHENTICATION_REQUIRED: 'authentication_required', }, TAB: { NEW_CHAT_TAB_ID: 'NewChatTab', diff --git a/src/languages/en.ts b/src/languages/en.ts index 1684ed3057da..58296f1f3a3e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4274,6 +4274,7 @@ export default { changeCurrency: 'Change payment currency', cardNotFound: 'No payment card added', retryPaymentButton: 'Retry payment', + authenticatePayment: 'Authenticate payment', requestRefund: 'Request refund', requestRefundModal: { phrase1: 'Getting a refund is easy, just downgrade your account before your next billing date and you’ll receive a refund.', diff --git a/src/languages/es.ts b/src/languages/es.ts index b4d071ba4a08..4cc0cc174c15 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4794,6 +4794,7 @@ export default { changeCurrency: 'Cambiar moneda de pago', cardNotFound: 'No se ha añadido ninguna tarjeta de pago', retryPaymentButton: 'Reintentar el pago', + authenticatePayment: 'Autenticar el pago', requestRefund: 'Solicitar reembolso', requestRefundModal: { phrase1: 'Obtener un reembolso es fácil, simplemente baja tu cuenta de categoría antes de la próxima fecha de facturación y recibirás un reembolso.', diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index cd5904e4a82d..6df0b2f2c205 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -174,7 +174,7 @@ function hasAmountOwed(): boolean { * @returns Whether there is a card authentication error. */ function hasCardAuthenticatedError() { - return stripeCustomerId?.status === 'authentication_required' && amountOwed === 0; + return stripeCustomerId?.status === 'authentication_required' && getAmountOwed() === 0; } /** @@ -457,6 +457,7 @@ export { getAmountOwed, getOverdueGracePeriodDate, getCardForSubscriptionBilling, + hasCardAuthenticatedError, hasSubscriptionGreenDotInfo, hasRetryBillingError, PAYMENT_STATUS, diff --git a/src/pages/settings/Subscription/CardAuthenticationModal/index.tsx b/src/pages/settings/Subscription/CardAuthenticationModal/index.tsx index 80cde4cb862c..b6dee7ee82a5 100644 --- a/src/pages/settings/Subscription/CardAuthenticationModal/index.tsx +++ b/src/pages/settings/Subscription/CardAuthenticationModal/index.tsx @@ -20,20 +20,21 @@ function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) { const styles = useThemeStyles(); const [authenticationLink] = useOnyx(ONYXKEYS.VERIFY_3DS_SUBSCRIPTION); const [session] = useOnyx(ONYXKEYS.SESSION); - const [privateStripeCustomerID] = useOnyx(ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID); const [isLoading, setIsLoading] = useState(true); + const [isVisible, setIsVisible] = useState(false); - const onModalClose = () => { + const onModalClose = useCallback(() => { + setIsVisible(false); PaymentMethods.clearPaymentCard3dsVerification(); - }; + Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION); + }, []); useEffect(() => { - if (privateStripeCustomerID?.status !== CONST.STRIPE_GBP_AUTH_STATUSES.SUCCEEDED) { + if (!authenticationLink) { return; } - PaymentMethods.clearPaymentCard3dsVerification(); - Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION); - }, [privateStripeCustomerID]); + setIsVisible(!!authenticationLink); + }, [authenticationLink]); const handleGBPAuthentication = useCallback( (event: MessageEvent) => { @@ -43,7 +44,7 @@ function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) { onModalClose(); } }, - [session?.accountID], + [onModalClose, session?.accountID], ); useEffect(() => { @@ -56,7 +57,7 @@ function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) { return ( diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 25a201bf6887..48caeed2bb96 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -18,6 +18,7 @@ import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getPaymentMethodDescription} from '@libs/PaymentUtils'; import * as SubscriptionUtils from '@libs/SubscriptionUtils'; +import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Subscription from '@userActions/Subscription'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -40,6 +41,9 @@ function CardSection() { const theme = useTheme(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); + const [privateStripeCustomerID] = useOnyx(ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID); + const [authenticationLink] = useOnyx(ONYXKEYS.VERIFY_3DS_SUBSCRIPTION); + const [session] = useOnyx(ONYXKEYS.SESSION); const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); const subscriptionPlan = useSubscriptionPlan(); const [subscriptionRetryBillingStatusPending] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_PENDING); @@ -47,7 +51,6 @@ function CardSection() { const [subscriptionRetryBillingStatusFailed] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED); const {isOffline} = useNetwork(); const defaultCard = useMemo(() => Object.values(fundList ?? {}).find((card) => card.accountData?.additionalData?.isBillingCard), [fundList]); - const cardMonth = useMemo(() => DateUtils.getMonthNames(preferredLocale)[(defaultCard?.accountData?.cardMonth ?? 1) - 1], [defaultCard?.accountData?.cardMonth, preferredLocale]); const requestRefund = useCallback(() => { @@ -64,12 +67,23 @@ function CardSection() { useEffect(() => { setBillingStatus(CardSectionUtils.getBillingStatus(translate, defaultCard?.accountData ?? {})); - }, [subscriptionRetryBillingStatusPending, subscriptionRetryBillingStatusSuccessful, subscriptionRetryBillingStatusFailed, translate, defaultCard?.accountData]); + }, [subscriptionRetryBillingStatusPending, subscriptionRetryBillingStatusSuccessful, subscriptionRetryBillingStatusFailed, translate, defaultCard?.accountData, privateStripeCustomerID]); const handleRetryPayment = () => { Subscription.clearOutstandingBalance(); }; + useEffect(() => { + if (!authenticationLink || privateStripeCustomerID?.status !== CONST.STRIPE_GBP_AUTH_STATUSES.CARD_AUTHENTICATION_REQUIRED) { + return; + } + Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); + }, [authenticationLink, privateStripeCustomerID?.status]); + + const handleAuthenticatePayment = () => { + PaymentMethods.verifySetupIntent(session?.accountID ?? -1, false); + }; + const handleBillingBannerClose = () => { setBillingStatus(undefined); }; @@ -81,7 +95,8 @@ function CardSection() { BillingBanner = ; } else if (SubscriptionUtils.hasUserFreeTrialEnded()) { BillingBanner = ; - } else if (billingStatus) { + } + if (billingStatus) { BillingBanner = ( )} + {SubscriptionUtils.hasCardAuthenticatedError() && ( +