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
30 changes: 30 additions & 0 deletions src/pages/settings/Subscription/CardSection/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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 today = new Date();

const monthsDiff = differenceInMonths(today, start);

let nextBillingDate = addDays(addMonths(start, monthsDiff), 1);

if (nextBillingDate.toUTCString() < today.toUTCString()) {
nextBillingDate = addMonths(nextBillingDate, 1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this fix to always ensure that the nextBillingDate is in the future? In this case should this be

Suggested change
nextBillingDate = addMonths(nextBillingDate, 1);
nextBillingDate = addMonths(today, 1);

?

Copy link
Member Author

@pasyukevich pasyukevich Jun 13, 2024

Choose a reason for hiding this comment

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

yes, but this update is not doing same as now

In this case, we will have today's as a billing date

}

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

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

describe('getNextBillingDate', () => {
beforeAll(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date(2024, 6, 5));
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB, maybe we can add a comment that month is zero-indexed? It tripped me up for a sec 😄

Copy link
Contributor

@JKobrynski JKobrynski Jun 18, 2024

Choose a reason for hiding this comment

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

Done!

CC: @blimpich

Copy link
Contributor

@blimpich blimpich Jun 19, 2024

Choose a reason for hiding this comment

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

Where is the comment? I think what Amy meant was something like this

Suggested change
jest.setSystemTime(new Date(2024, 6, 5));
// Month is zero indexed, so this is July 5th 2024
jest.setSystemTime(new Date(2024, 6, 5));

});

afterAll(() => {
jest.useRealTimers();
});

it('should return the next billing date when initial date is valid', () => {
const initialDate = '2024-06-01';
const expectedNextBillingDate = 'July 2, 2024';

expect(getNextBillingDate(initialDate)).toEqual(expectedNextBillingDate);
});

it('should handle end-of-month edge cases correctly', () => {
const initialDate = '2024-01-31';
const nextBillingDate = getNextBillingDate(initialDate);
const expectedNextBillingDate = 'July 1, 2024';
expect(nextBillingDate).toBe(expectedNextBillingDate);
});

it('should handle date when it at the current month', () => {
const initialDate = '2024-06-06';
const nextBillingDate = getNextBillingDate(initialDate);
const expectedNextBillingDate = 'June 7, 2024';
expect(nextBillingDate).toBe(expectedNextBillingDate);
});

it('should return the next billing date when initial date is invalid', () => {
const initialDate = 'invalid-date';
const expectedNextBillingDate = 'July 6, 2024';

expect(getNextBillingDate(initialDate)).toEqual(expectedNextBillingDate);
});
});
Loading