diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts
index 1550fb646f0f5..bdd6a8cef0223 100644
--- a/src/libs/CardUtils.ts
+++ b/src/libs/CardUtils.ts
@@ -942,7 +942,7 @@ function isCardPendingActivate(card?: Card) {
* Returns true if the card has fraud type 'domain' or 'individual'.
*/
function isCardWithPotentialFraud(card: Card): boolean {
- return card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN || card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL;
+ return card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN || card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL || !!card.nameValuePairs?.possibleFraud;
}
function isCardPendingReplace(card?: Card) {
diff --git a/src/pages/home/TimeSensitiveSection/index.tsx b/src/pages/home/TimeSensitiveSection/index.tsx
index b37c7f4eddc27..e39a4842d7ed2 100644
--- a/src/pages/home/TimeSensitiveSection/index.tsx
+++ b/src/pages/home/TimeSensitiveSection/index.tsx
@@ -125,13 +125,13 @@ function TimeSensitiveSection() {
{/* Priority 1: Card fraud alerts */}
{shouldShowReviewCardFraud &&
cardsWithFraud.map((card) => {
- if (!card.message?.possibleFraud) {
+ if (!card.nameValuePairs?.possibleFraud) {
return null;
}
return (
);
})}
diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts
index 189feb3fc25fa..073e2fb09880e 100644
--- a/src/types/onyx/Card.ts
+++ b/src/types/onyx/Card.ts
@@ -40,12 +40,6 @@ type PossibleFraudData = {
fraudAlertReportActionID?: number;
};
-/** Model of card message data */
-type CardMessage = {
- /** Possible fraud information */
- possibleFraud?: PossibleFraudData;
-};
-
/** Model of Expensify card */
type Card = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Card ID number */
@@ -93,9 +87,6 @@ type Card = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Current fraud state of the card */
fraud: ValueOf;
- /** Card message data containing possible fraud info and other metadata */
- message?: CardMessage;
-
/** Card name */
cardName?: string;
@@ -201,6 +192,9 @@ type Card = OnyxCommon.OnyxValueWithOfflineFeedback<{
* null/undefined if card is not frozen
*/
frozen?: FrozenCardData | null;
+
+ /** Possible fraud information */
+ possibleFraud?: PossibleFraudData;
}> &
OnyxCommon.OnyxValueWithOfflineFeedback<
/** Type of export card */
@@ -423,7 +417,6 @@ export type {
AssignableCardsList,
CardAssignmentData,
UnassignedCard,
- CardMessage,
PossibleFraudData,
FrozenCardData,
};
diff --git a/tests/unit/hooks/useTimeSensitiveCards.test.ts b/tests/unit/hooks/useTimeSensitiveCards.test.ts
index b1ae37e5665e1..96aa6f405bbd0 100644
--- a/tests/unit/hooks/useTimeSensitiveCards.test.ts
+++ b/tests/unit/hooks/useTimeSensitiveCards.test.ts
@@ -172,7 +172,11 @@ describe('useTimeSensitiveCards', () => {
});
it('should identify cards with fraud and set shouldShowReviewCardFraud to true', async () => {
- const cardWithFraud = createRandomExpensifyCard(1, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN});
+ const cardWithFraud = createRandomExpensifyCard(1, {
+ state: CONST.EXPENSIFY_CARD.STATE.OPEN,
+ fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN,
+ possibleFraud: {triggerAmount: 5663, triggerMerchant: 'WAL-MART #2366', currency: 'USD', fraudAlertReportID: 123456},
+ });
const cardList: CardList = {'1': cardWithFraud};
await Onyx.merge(ONYXKEYS.CARD_LIST, cardList);
@@ -182,10 +186,11 @@ describe('useTimeSensitiveCards', () => {
expect(result.current.cardsWithFraud).toHaveLength(1);
expect(result.current.cardsWithFraud.at(0)?.cardID).toBe(1);
+ expect(result.current.cardsWithFraud.at(0)?.nameValuePairs?.possibleFraud?.triggerAmount).toBe(5663);
expect(result.current.shouldShowReviewCardFraud).toBe(true);
});
- it('should not show fraud review for cards with fraud type NONE', async () => {
+ it('should not show fraud review for cards with fraud type NONE and no possibleFraud data', async () => {
const cardWithNoFraud = createRandomExpensifyCard(1, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.NONE});
const cardList: CardList = {'1': cardWithNoFraud};
@@ -197,4 +202,22 @@ describe('useTimeSensitiveCards', () => {
expect(result.current.cardsWithFraud).toHaveLength(0);
expect(result.current.shouldShowReviewCardFraud).toBe(false);
});
+
+ it('should show fraud review when fraud is NONE but possibleFraud data exists in nameValuePairs', async () => {
+ const cardWithPendingFraudAlert = createRandomExpensifyCard(1, {
+ state: CONST.EXPENSIFY_CARD.STATE.OPEN,
+ fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.NONE,
+ possibleFraud: {triggerAmount: 5663, triggerMerchant: 'WAL-MART #2366', currency: 'USD', fraudAlertReportID: 5230242215684213},
+ });
+ const cardList: CardList = {'1': cardWithPendingFraudAlert};
+
+ await Onyx.merge(ONYXKEYS.CARD_LIST, cardList);
+ await waitForBatchedUpdates();
+
+ const {result} = renderHook(() => useTimeSensitiveCards());
+
+ expect(result.current.cardsWithFraud).toHaveLength(1);
+ expect(result.current.cardsWithFraud.at(0)?.cardID).toBe(1);
+ expect(result.current.shouldShowReviewCardFraud).toBe(true);
+ });
});
diff --git a/tests/unit/selectors/CardTest.ts b/tests/unit/selectors/CardTest.ts
index 3bff645af2b93..b69aca649979e 100644
--- a/tests/unit/selectors/CardTest.ts
+++ b/tests/unit/selectors/CardTest.ts
@@ -376,7 +376,11 @@ describe('timeSensitiveCardsSelector', () => {
});
it('identifies cards with domain fraud', () => {
- const cardWithDomainFraud = createRandomExpensifyCard(1, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN});
+ const cardWithDomainFraud = createRandomExpensifyCard(1, {
+ state: CONST.EXPENSIFY_CARD.STATE.OPEN,
+ fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN,
+ possibleFraud: {triggerAmount: 5000, triggerMerchant: 'Test Merchant', currency: 'USD', fraudAlertReportID: 123},
+ });
const normalCard = createRandomExpensifyCard(2, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.NONE});
const cardList: CardList = {
@@ -389,10 +393,15 @@ describe('timeSensitiveCardsSelector', () => {
expect(result.cardsWithFraud).toHaveLength(1);
expect(result.cardsWithFraud.at(0)?.cardID).toBe(1);
expect(result.cardsWithFraud.at(0)?.fraud).toBe(CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN);
+ expect(result.cardsWithFraud.at(0)?.nameValuePairs?.possibleFraud?.triggerAmount).toBe(5000);
});
it('identifies cards with individual fraud', () => {
- const cardWithIndividualFraud = createRandomExpensifyCard(1, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL});
+ const cardWithIndividualFraud = createRandomExpensifyCard(1, {
+ state: CONST.EXPENSIFY_CARD.STATE.OPEN,
+ fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL,
+ possibleFraud: {triggerAmount: 3000, triggerMerchant: 'Suspicious Shop', currency: 'USD', fraudAlertReportID: 456},
+ });
const normalCard = createRandomExpensifyCard(2, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.NONE});
const cardList: CardList = {
@@ -405,13 +414,25 @@ describe('timeSensitiveCardsSelector', () => {
expect(result.cardsWithFraud).toHaveLength(1);
expect(result.cardsWithFraud.at(0)?.cardID).toBe(1);
expect(result.cardsWithFraud.at(0)?.fraud).toBe(CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL);
+ expect(result.cardsWithFraud.at(0)?.nameValuePairs?.possibleFraud?.triggerMerchant).toBe('Suspicious Shop');
});
it('detects fraud on both physical and virtual Expensify cards', () => {
- const physicalCardWithFraud = createRandomExpensifyCard(1, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN});
+ const physicalCardWithFraud = createRandomExpensifyCard(1, {
+ state: CONST.EXPENSIFY_CARD.STATE.OPEN,
+ fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN,
+ possibleFraud: {triggerAmount: 5000, triggerMerchant: 'Store A', currency: 'USD', fraudAlertReportID: 111},
+ });
const virtualCardWithFraud: Card = {
- ...createRandomExpensifyCard(2, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL}),
- nameValuePairs: {isVirtual: true} as Card['nameValuePairs'],
+ ...createRandomExpensifyCard(2, {
+ state: CONST.EXPENSIFY_CARD.STATE.OPEN,
+ fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL,
+ possibleFraud: {triggerAmount: 2000, triggerMerchant: 'Store B', currency: 'USD', fraudAlertReportID: 222},
+ }),
+ nameValuePairs: {
+ isVirtual: true,
+ possibleFraud: {triggerAmount: 2000, triggerMerchant: 'Store B', currency: 'USD', fraudAlertReportID: 222},
+ } as Card['nameValuePairs'],
};
const cardList: CardList = {
@@ -430,7 +451,11 @@ describe('timeSensitiveCardsSelector', () => {
...createRandomCompanyCard(1, {bank: 'vcf'}),
fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN,
};
- const expensifyCardWithFraud = createRandomExpensifyCard(2, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN});
+ const expensifyCardWithFraud = createRandomExpensifyCard(2, {
+ state: CONST.EXPENSIFY_CARD.STATE.OPEN,
+ fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN,
+ possibleFraud: {triggerAmount: 7500, triggerMerchant: 'Online Store', currency: 'USD', fraudAlertReportID: 789},
+ });
const cardList: CardList = {
'1': companyCardWithFraud,
@@ -444,7 +469,7 @@ describe('timeSensitiveCardsSelector', () => {
expect(result.cardsWithFraud.at(0)?.cardID).toBe(2);
});
- it('does not include cards with fraud type NONE', () => {
+ it('does not include cards with fraud type NONE and no possibleFraud data', () => {
const cardWithNoFraud = createRandomExpensifyCard(1, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.NONE});
const cardList: CardList = {
@@ -455,6 +480,26 @@ describe('timeSensitiveCardsSelector', () => {
expect(result.cardsWithFraud).toHaveLength(0);
});
+
+ it('includes cards with fraud type NONE when possibleFraud data exists in nameValuePairs', () => {
+ const cardWithPendingFraudAlert = createRandomExpensifyCard(1, {
+ state: CONST.EXPENSIFY_CARD.STATE.OPEN,
+ fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.NONE,
+ possibleFraud: {triggerAmount: 5663, triggerMerchant: 'WAL-MART #2366', currency: 'USD', fraudAlertReportID: 5230242215684213},
+ });
+ const normalCard = createRandomExpensifyCard(2, {state: CONST.EXPENSIFY_CARD.STATE.OPEN, fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.NONE});
+
+ const cardList: CardList = {
+ '1': cardWithPendingFraudAlert,
+ '2': normalCard,
+ };
+
+ const result = timeSensitiveCardsSelector(cardList);
+
+ expect(result.cardsWithFraud).toHaveLength(1);
+ expect(result.cardsWithFraud.at(0)?.cardID).toBe(1);
+ expect(result.cardsWithFraud.at(0)?.nameValuePairs?.possibleFraud?.triggerAmount).toBe(5663);
+ });
});
describe('areAllExpensifyCardsShipped', () => {
diff --git a/tests/utils/collections/card.ts b/tests/utils/collections/card.ts
index c1aeab2d79881..65106859c14ff 100644
--- a/tests/utils/collections/card.ts
+++ b/tests/utils/collections/card.ts
@@ -3,6 +3,7 @@ import {format} from 'date-fns';
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import type {Card} from '@src/types/onyx';
+import type {PossibleFraudData} from '@src/types/onyx/Card';
import type {CardFeedWithNumber} from '@src/types/onyx/CardFeeds';
export default function createRandomCard(
@@ -14,6 +15,7 @@ export default function createRandomCard(
fraud?: ValueOf;
accountID?: number;
domainName?: string;
+ possibleFraud?: PossibleFraudData;
},
): Card {
const cardID = index > 0 ? index : randNumber();
@@ -62,6 +64,7 @@ export default function createRandomCard(
scrapeMinDate: format(randPastDate(), CONST.DATE.FNS_DB_FORMAT_STRING),
errors: {},
errorFields: {},
+ ...(options?.possibleFraud ? {nameValuePairs: {possibleFraud: options.possibleFraud} as Card['nameValuePairs']} : {}),
};
}
@@ -76,6 +79,7 @@ function createRandomExpensifyCard(
fraud?: ValueOf;
accountID?: number;
domainName?: string;
+ possibleFraud?: PossibleFraudData;
},
): Card {
return createRandomCard(index, {