From 601e07bbf19bf7f547c89011aa1261904220ece2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 14 Nov 2024 16:19:26 +0100 Subject: [PATCH 1/4] Update amex cards format --- src/libs/CardUtils.ts | 11 +++++++++-- src/pages/settings/Wallet/PaymentMethodList.tsx | 2 +- .../companyCards/WorkspaceCompanyCardDetailsPage.tsx | 2 +- .../companyCards/WorkspaceCompanyCardsList.tsx | 2 +- .../companyCards/assignCard/CardSelectionStep.tsx | 2 +- .../companyCards/assignCard/ConfirmationStep.tsx | 2 +- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 2e31ffa808b9..709fcc8965fa 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -135,14 +135,21 @@ function maskCard(lastFour = ''): string { * Converts given 'X' to '•' for the entire card string. * * @param cardName - card name with XXXX in the middle. + * @param bank - card bank. * @returns - The masked card string. */ -function maskCardNumber(cardName: string): string { - if (!cardName || cardName === '') { +function maskCardNumber(cardName: string, bank: string): string { + if (!cardName || cardName === '' || bank === '') { return ''; } const hasSpace = /\s/.test(cardName); const maskedString = cardName.replace(/X/g, '•'); + const isAmexBank = [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX_DIRECT].some((value) => value === bank); + + if (isAmexBank && maskedString.length === 15) { + return maskedString.replace(/(.{4})(.{6})(.{5})/, '$1 $2 $3'); + } + return hasSpace ? cardName : maskedString.replace(/(.{4})/g, '$1 ').trim(); } diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index f409241d7f3e..729aa150928d 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -226,7 +226,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, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx index 9fdfa7bec7b3..bee196031bda 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx @@ -112,7 +112,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag /> diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx index 9e9e2bf5208f..75b4f44fc843 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx @@ -60,7 +60,7 @@ function WorkspaceCompanyCardsList({cardsList, policyID}: WorkspaceCompanyCardsL > diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index f9e552ac5afe..4b07e7a220b8 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -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: ( editStep(CONST.COMPANY_CARD.STEP.CARD)} /> From fc97fdcefdaef2c03742e3fb8def8f486794a108 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 15 Nov 2024 11:15:22 +0100 Subject: [PATCH 2/4] Add unit tests --- src/libs/CardUtils.ts | 4 +- .../assignCard/ConfirmationStep.tsx | 2 +- tests/unit/CardUtilsTest.ts | 37 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 0abefdbd1a60..dcf2926460cd 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -138,8 +138,8 @@ function maskCard(lastFour = ''): string { * @param bank - card bank. * @returns - The masked card string. */ -function maskCardNumber(cardName: string, bank: string): string { - if (!cardName || cardName === '' || bank === '') { +function maskCardNumber(cardName: string, bank: string | undefined): string { + if (!cardName || cardName === '' || !bank) { return ''; } const hasSpace = /\s/.test(cardName); diff --git a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx index 655f0908cf5a..6520d8ac0fc7 100644 --- a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx @@ -76,7 +76,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { /> editStep(CONST.COMPANY_CARD.STEP.CARD)} /> diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 09a36fcfb562..1917833df710 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -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's provided in the 'CREDIT CARD...6607' format", () => { + 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 '•'", () => { + 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 '•'", () => { + const cardNumber = '211944XXXXX6557'; + const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX); + expect(maskedCardNumber).toBe('2119 44•••• •6557'); + }); + + it('Should return empty string if undefined bank was provided', () => { + const cardNumber = '480801XXXXXX2554'; + const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, undefined); + expect(maskedCardNumber).toBe(''); + }); + + 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(''); + }); + }); }); From 9bcec51d49d45b52a3845642f6056205fc3aefb0 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 18 Nov 2024 10:52:31 +0100 Subject: [PATCH 3/4] Minor improvements --- src/libs/CardUtils.ts | 14 +++++++++----- tests/unit/CardUtilsTest.ts | 8 ++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index dcf2926460cd..029906ce2a3a 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -135,22 +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 bank - card bank. + * @param feed - card feed. * @returns - The masked card string. */ -function maskCardNumber(cardName: string, bank: string | undefined): string { - if (!cardName || cardName === '' || !bank) { +function maskCardNumber(cardName: string, feed: string | undefined): string { + if (!cardName || cardName === '' || !feed) { return ''; } const hasSpace = /\s/.test(cardName); const maskedString = cardName.replace(/X/g, '•'); - const isAmexBank = [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX_DIRECT].some((value) => value === bank); + 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 hasSpace ? cardName : maskedString.replace(/(.{4})/g, '$1 ').trim(); + return maskedString.replace(/(.{4})/g, '$1 ').trim(); } /** diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 1917833df710..58dda1f3566f 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -223,25 +223,25 @@ describe('CardUtils', () => { expect(maskedCardNumber).toBe('4808 01•• •••• 2554'); }); - it("Should return card number without changes if it's provided in the 'CREDIT CARD...6607' format", () => { + 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 '•'", () => { + 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 '•'", () => { + 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 empty string if undefined bank was provided', () => { + it('Should return empty string if undefined feed was provided', () => { const cardNumber = '480801XXXXXX2554'; const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, undefined); expect(maskedCardNumber).toBe(''); From 1836991c5cf891cba01977ad631b8e5617d498c7 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 18 Nov 2024 12:28:52 +0100 Subject: [PATCH 4/4] Remove check for feed --- src/libs/CardUtils.ts | 2 +- tests/unit/CardUtilsTest.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 029906ce2a3a..77aeb8e0ecc3 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -139,7 +139,7 @@ function maskCard(lastFour = ''): string { * @returns - The masked card string. */ function maskCardNumber(cardName: string, feed: string | undefined): string { - if (!cardName || cardName === '' || !feed) { + if (!cardName || cardName === '') { return ''; } const hasSpace = /\s/.test(cardName); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 58dda1f3566f..9d4af5aa3760 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -241,10 +241,10 @@ describe('CardUtils', () => { expect(maskedCardNumber).toBe('2119 44•••• •6557'); }); - it('Should return empty string if undefined feed was provided', () => { + it('Should return masked card number even if undefined feed was provided', () => { const cardNumber = '480801XXXXXX2554'; const maskedCardNumber = CardUtils.maskCardNumber(cardNumber, undefined); - expect(maskedCardNumber).toBe(''); + expect(maskedCardNumber).toBe('4808 01•• •••• 2554'); }); it('Should return empty string if invalid card name was provided', () => {