Skip to content

Commit

Permalink
Merge pull request #52578 from callstack-internal/VickyStash/feature/…
Browse files Browse the repository at this point in the history
…52551-amex-cards-format

[Company Cards] Update the Amex cards format
  • Loading branch information
mountiny authored Nov 19, 2024
2 parents 51a83bf + 1836991 commit 9268594
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 7 deletions.
15 changes: 13 additions & 2 deletions src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,26 @@ function maskCard(lastFour = ''): string {
* Converts given 'X' to '•' for the entire card string.
*
* @param cardName - card name with XXXX in the middle.
* @param feed - card feed.
* @returns - The masked card string.
*/
function maskCardNumber(cardName: string): string {
function maskCardNumber(cardName: string, feed: string | undefined): string {
if (!cardName || cardName === '') {
return '';
}
const hasSpace = /\s/.test(cardName);
const maskedString = cardName.replace(/X/g, '•');
return hasSpace ? cardName : maskedString.replace(/(.{4})/g, '$1 ').trim();
const isAmexBank = [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX_DIRECT].some((value) => value === feed);

if (hasSpace) {
return cardName;
}

if (isAmexBank && maskedString.length === 15) {
return maskedString.replace(/(.{4})(.{6})(.{5})/, '$1 $2 $3');
}

return maskedString.replace(/(.{4})/g, '$1 ').trim();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/pages/settings/Wallet/PaymentMethodList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ function PaymentMethodList({
if (!CardUtils.isExpensifyCard(card.cardID)) {
assignedCardsGrouped.push({
key: card.cardID.toString(),
title: CardUtils.maskCardNumber(card.cardName ?? ''),
title: CardUtils.maskCardNumber(card.cardName ?? '', card.bank),
description: getDescriptionForPolicyDomainCard(card.domainName),
shouldShowRightIcon: false,
interactive: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
/>
<MenuItemWithTopDescription
description={translate('workspace.moreFeatures.companyCards.cardNumber')}
title={CardUtils.maskCardNumber(card?.cardName ?? '')}
title={CardUtils.maskCardNumber(card?.cardName ?? '', bank)}
interactive={false}
titleStyle={styles.walletCardNumber}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function WorkspaceCompanyCardsList({cardsList, policyID}: WorkspaceCompanyCardsL
>
<WorkspaceCompanyCardsListRow
cardholder={personalDetails?.[item.accountID ?? '-1']}
cardNumber={CardUtils.maskCardNumber(cardName)}
cardNumber={CardUtils.maskCardNumber(cardName, item.bank)}
name={customCardNames?.[item.cardID] ?? ''}
/>
</PressableWithFeedback>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) {
const cardListOptions = Object.entries(filteredCardList).map(([cardNumber, encryptedCardNumber]) => ({
keyForList: encryptedCardNumber,
value: encryptedCardNumber,
text: CardUtils.maskCardNumber(cardNumber),
text: CardUtils.maskCardNumber(cardNumber, feed),
isSelected: cardSelected === encryptedCardNumber,
leftElement: (
<Icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) {
/>
<MenuItemWithTopDescription
description={translate('workspace.companyCards.card')}
title={CardUtils.maskCardNumber(data?.cardNumber ?? '')}
title={CardUtils.maskCardNumber(data?.cardNumber ?? '', data?.bankName)}
shouldShowRightIcon
onPress={() => editStep(CONST.COMPANY_CARD.STEP.CARD)}
/>
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/CardUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,41 @@ describe('CardUtils', () => {
expect(feedName).toBe(undefined);
});
});

describe('maskCardNumber', () => {
it("Should return the card number divided into chunks of 4, with 'X' replaced by '•' if it's provided in the '480801XXXXXX2554' format", () => {
const cardNumber = '480801XXXXXX2554';
const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD);
expect(maskedCardNumber).toBe('4808 01•• •••• 2554');
});

it('Should return card number without changes if it has empty space', () => {
const cardNumber = 'CREDIT CARD...6607';
const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE);
expect(maskedCardNumber).toBe(cardNumber);
});

it("Should return the Amex direct feed card number divided into 4/6/5 chunks, with 'X' replaced by '•' if it's provided in '211944XXXXX6557' format", () => {
const cardNumber = '211944XXXXX6557';
const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX_DIRECT);
expect(maskedCardNumber).toBe('2119 44•••• •6557');
});

it("Should return the Amex custom feed card number divided into 4/6/5 chunks, with 'X' replaced by '•' if it's provided in '211944XXXXX6557' format", () => {
const cardNumber = '211944XXXXX6557';
const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX);
expect(maskedCardNumber).toBe('2119 44•••• •6557');
});

it('Should return masked card number even if undefined feed was provided', () => {
const cardNumber = '480801XXXXXX2554';
const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, undefined);
expect(maskedCardNumber).toBe('4808 01•• •••• 2554');
});

it('Should return empty string if invalid card name was provided', () => {
const maskedCardNumber = CardUtils.maskCardNumber('', CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD);
expect(maskedCardNumber).toBe('');
});
});
});

0 comments on commit 9268594

Please sign in to comment.