Skip to content
Merged
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3913,6 +3913,7 @@ const CONST = {
},
STRIPE_GBP_AUTH_STATUSES: {
SUCCEEDED: 'succeeded',
CARD_AUTHENTICATION_REQUIRED: 'authentication_required',
},
TAB: {
NEW_CHAT_TAB_ID: 'NewChatTab',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
3 changes: 2 additions & 1 deletion src/libs/SubscriptionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -457,6 +457,7 @@ export {
getAmountOwed,
getOverdueGracePeriodDate,
getCardForSubscriptionBilling,
hasCardAuthenticatedError,
hasSubscriptionGreenDotInfo,
hasRetryBillingError,
PAYMENT_STATUS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>) => {
Expand All @@ -43,7 +44,7 @@ function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) {
onModalClose();
}
},
[session?.accountID],
[onModalClose, session?.accountID],
);

useEffect(() => {
Expand All @@ -56,7 +57,7 @@ function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) {
return (
<Modal
type={CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE}
isVisible={!!authenticationLink}
isVisible={isVisible}
onClose={onModalClose}
onModalHide={onModalClose}
>
Expand Down
31 changes: 28 additions & 3 deletions src/pages/settings/Subscription/CardSection/CardSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -40,14 +41,16 @@ 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);
const [subscriptionRetryBillingStatusSuccessful] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL);
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(() => {
Expand All @@ -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);
};
Expand All @@ -81,7 +95,8 @@ function CardSection() {
BillingBanner = <TrialStartedBillingBanner />;
} else if (SubscriptionUtils.hasUserFreeTrialEnded()) {
BillingBanner = <TrialEndedBillingBanner />;
} else if (billingStatus) {
}
if (billingStatus) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this else if --> if intentional?

Are we saying along with Trial/Trial ended banner we would show the authenticate payment button?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I got the change now.

BillingBanner = (
<SubscriptionBillingBanner
title={billingStatus.title}
Expand Down Expand Up @@ -140,6 +155,16 @@ function CardSection() {
large
/>
)}
{SubscriptionUtils.hasCardAuthenticatedError() && (
<Button
text={translate('subscription.cardSection.authenticatePayment')}
isDisabled={isOffline || !billingStatus?.isAuthenticationRequired}
isLoading={subscriptionRetryBillingStatusPending}
onPress={handleAuthenticatePayment}
style={[styles.w100, styles.mt5]}
large
/>
)}

{!!account?.hasPurchases && (
<MenuItem
Expand Down