Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integrate payment card section with API #43473

2 changes: 1 addition & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3214,7 +3214,7 @@ export default {
title: 'Payment',
subtitle: 'Add a payment card to pay for your Expensify subscription.',
addCardButton: 'Add payment card',
cardNextPayment: 'Your next payment date is',
cardNextPayment: ({nextPaymentDate}) => `Your next payment date is ${nextPaymentDate}.`,
cardEnding: ({cardNumber}) => `Card ending in ${cardNumber}`,
cardInfo: ({name, expiration, currency}) => `Name: ${name}, Expiration: ${expiration}, Currency: ${currency}`,
changeCard: 'Change payment card',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3721,7 +3721,7 @@ export default {
title: 'Pago',
subtitle: 'Añade una tarjeta de pago para abonar tu suscripción a Expensify',
addCardButton: 'Añade tarjeta de pago',
cardNextPayment: 'Your next payment date is',
cardNextPayment: ({nextPaymentDate}) => `Tu próxima fecha de pago es ${nextPaymentDate}.`,
cardEnding: ({cardNumber}) => `Tarjeta terminada en ${cardNumber}`,
cardInfo: ({name, expiration, currency}) => `Nombre: ${name}, Expiración: ${expiration}, Moneda: ${currency}`,
changeCard: 'Cambiar tarjeta de pago',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,27 @@ import ONYXKEYS from '@src/ONYXKEYS';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import CardSectionActions from './CardSectionActions';
import CardSectionDataEmpty from './CardSectionDataEmpty';
import getNextBillingDate from './utils';

function CardSection() {
const {translate, preferredLocale} = useLocalize();
const styles = useThemeStyles();
const theme = useTheme();
const [fundList] = useOnyx(ONYXKEYS.FUND_LIST);
const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION);

const defaultCard = useMemo(() => Object.values(fundList ?? {}).find((card) => card.isDefault), [fundList]);

const cardMonth = useMemo(() => DateUtils.getMonthNames(preferredLocale)[(defaultCard?.accountData?.cardMonth ?? 1) - 1], [defaultCard?.accountData?.cardMonth, preferredLocale]);

const nextPaymentDate = !isEmptyObject(privateSubscription) ? getNextBillingDate(privateSubscription.startDate) : undefined;

const sectionSubtitle = defaultCard && !!nextPaymentDate ? translate('subscription.cardSection.cardNextPayment', {nextPaymentDate}) : translate('subscription.cardSection.subtitle');

return (
<Section
title={translate('subscription.cardSection.title')}
subtitle={translate('subscription.cardSection.subtitle')}
subtitle={sectionSubtitle}
isCentralPane
titleStyles={styles.textStrong}
subtitleMuted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import ThreeDotsMenu from '@components/ThreeDotsMenu';
import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types';
import useLocalize from '@hooks/useLocalize';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Navigation from '@navigation/Navigation';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

const anchorAlignment = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
Expand All @@ -24,12 +26,12 @@ function CardSectionActions() {
{
icon: Expensicons.CreditCard,
text: translate('subscription.cardSection.changeCard'),
onSelected: () => {}, // TODO: update with navigation to "add card" screen (https://github.com/Expensify/App/issues/38621)
onSelected: () => Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD),
},
{
icon: Expensicons.MoneyCircle,
text: translate('subscription.cardSection.changeCurrency'),
onSelected: () => {}, // TODO: update with navigation to "change currency" screen (https://github.com/Expensify/App/issues/38621)
onSelected: () => {}, // TODO: update with navigation to "change currency" screen (https://github.com/Expensify/App/issues/38629)
},
],
[translate],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react';
import Button from '@components/Button';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@navigation/Navigation';
import ROUTES from '@src/ROUTES';

function CardSectionDataEmpty() {
const {translate} = useLocalize();
Expand All @@ -10,7 +12,7 @@ function CardSectionDataEmpty() {
return (
<Button
text={translate('subscription.cardSection.addCardButton')}
onPress={() => {}} // TODO: update with navigation to "add card" screen (https://github.com/Expensify/App/issues/38621)
onPress={() => Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD)}
style={styles.w100}
success
large
Expand Down
25 changes: 25 additions & 0 deletions src/pages/settings/Subscription/CardSection/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {addDays, addMonths, differenceInMonths, format, isValid} from 'date-fns';
import CONST from '@src/CONST';

/**
* Get the next billing date.
*
* @param initialDate - The initial billing date in 'yyyy-MM-dd' format.
* @returns - The next billing date in 'yyyy-MM-dd' format.
*/
function getNextBillingDate(initialDate: string): string {
Copy link
Collaborator

@mananjadhav mananjadhav Jun 11, 2024

Choose a reason for hiding this comment

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

what do you think of this to get rid of while loop.

function getNextBillingDate(initialDate: string): string {
    const start = new Date(initialDate);
    const today = new Date();

    // Calculate the number of months difference and add one if start date is before today
    const monthsDiff = differenceInMonths(today, start);
    const nextBillingDate = addMonths(start, monthsDiff + 1);

    return format(nextBillingDate, CONST.DATE.MONTH_DAY_YEAR_FORMAT);
}

cc - @amyevans

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah if we can eliminate a loop that would be great!

Copy link
Member Author

Choose a reason for hiding this comment

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

updated

let start = new Date(`${initialDate}T00:00:00`);

if (!isValid(start)) {
start = new Date();
}

const current = new Date(start);
Copy link
Collaborator

@mananjadhav mananjadhav Jun 12, 2024

Choose a reason for hiding this comment

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

a little lost here, isn't current === start ? Then what's the thought of adding differenceInMonths ?

My code I had used const today = new Date().

This is what I see now. The next payment date is in the past.

image

Copy link
Member Author

Choose a reason for hiding this comment

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

Problem fixed
I also found one more edge case and fixed it

Copy link
Collaborator

Choose a reason for hiding this comment

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

I raised one more comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't reviewed the function logic closely but just glancing at the expected next billing dates in the test, I wanted to clarify expected behavior... the next billing date should always be the first of the next month (internal ref)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't have the access to the link, but does that mean we don't even need the initialDate. We should always:

     startOfMonth(addMonths(today, 1))

?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes we can simplify to that, apologies for not catching it sooner!

Copy link
Collaborator

Choose a reason for hiding this comment

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

No worries. Better we caught this now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@pasyukevich Can you take care of this change?

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated


const monthsDiff = differenceInMonths(current, start);
const nextBillingDate = addDays(addMonths(start, monthsDiff + 1), 1);

return format(nextBillingDate, CONST.DATE.MONTH_DAY_YEAR_FORMAT);
}

export default getNextBillingDate;
24 changes: 24 additions & 0 deletions tests/unit/CardsSectionUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {addDays, addMonths, format} from 'date-fns';
import getNextBillingDate from '@src/pages/settings/Subscription/CardSection/utils';

describe('getNextBillingDate', () => {
it('should return the next billing date one month after the initial date', () => {
const endDate = '2023-01-15';
const nextBillingDate = getNextBillingDate(endDate);
expect(nextBillingDate).toBe('February 16, 2023');
});

it('should handle end-of-month edge cases correctly', () => {
const initialDate = '2023-01-31';
const nextBillingDate = getNextBillingDate(initialDate);
const expectedNextBillingDate = format(addDays(addMonths(new Date('2023-01-31'), 1), 1), 'MMMM d, yyyy');
expect(nextBillingDate).toBe(expectedNextBillingDate);
});

it('should handle invalid dates correctly', () => {
const initialDate = 'invalid date';
const nextBillingDate = getNextBillingDate(initialDate);
const expectedNextBillingDate = format(addDays(addMonths(new Date(), 1), 1), 'MMMM d, yyyy');
expect(nextBillingDate).toBe(expectedNextBillingDate);
});
});
Loading