11import AsyncStorage from '@react-native-async-storage/async-storage' ;
2- import type {
2+ import {
33 FraudDetection ,
44 FraudAlert ,
55 FraudAnalytics ,
66 FraudInvestigation ,
77 FraudRule ,
8- FraudReport ,
8+ FraudGeneratedReport ,
99 FraudCheckRequest ,
1010 FraudCheckResponse ,
1111 FraudIndicator ,
@@ -15,6 +15,7 @@ import type {
1515 FraudIndicatorType ,
1616 FraudFilters ,
1717 RealTimeMonitoring ,
18+ DetectionStats ,
1819} from '../types/fraud' ;
1920
2021const STORAGE_KEYS = {
@@ -58,13 +59,18 @@ export async function performFraudCheck(request: FraudCheckRequest): Promise<Fra
5859
5960 // Check 3: Location mismatch
6061 if ( request . metadata . location ) {
61- const locationCheck = await checkLocationAnomaly ( request . userId , request . metadata . location ) ;
62+ const loc = typeof request . metadata . location === 'string'
63+ ? { country : request . metadata . location }
64+ : request . metadata . location ;
65+ const locationCheck = await checkLocationAnomaly ( request . userId , loc ) ;
6266 if ( locationCheck . isSuspicious ) {
6367 indicators . push ( {
6468 type : FraudIndicatorType . LOCATION_MISMATCH ,
6569 severity : 'medium' ,
6670 description : locationCheck . reason ,
67- value : request . metadata . location ,
71+ value : typeof request . metadata . location === 'string'
72+ ? request . metadata . location
73+ : request . metadata . location . country ,
6874 } ) ;
6975 riskScore += 20 ;
7076 }
@@ -462,7 +468,7 @@ export async function getAllInvestigations(): Promise<FraudInvestigation[]> {
462468export async function generateFraudReport (
463469 reportType : 'daily' | 'weekly' | 'monthly' | 'custom' ,
464470 period : { start : Date ; end : Date }
465- ) : Promise < FraudReport > {
471+ ) : Promise < FraudGeneratedReport > {
466472 const analytics = await getFraudAnalytics ( ) ;
467473 const detections = await getAllDetections ( {
468474 dateFrom : period . start ,
@@ -540,7 +546,7 @@ export async function getMonitoringStatus(): Promise<RealTimeMonitoring> {
540546 const monitoring : RealTimeMonitoring = JSON . parse ( data ) ;
541547 return {
542548 ...monitoring ,
543- lastCheckTimestamp : new Date ( monitoring . lastCheckTimestamp ) ,
549+ lastCheckTimestamp : monitoring . lastCheckTimestamp ? new Date ( monitoring . lastCheckTimestamp ) : undefined ,
544550 } ;
545551 } catch ( error ) {
546552 console . error ( 'Failed to load monitoring status:' , error ) ;
@@ -552,7 +558,7 @@ async function updateMonitoringStats(): Promise<void> {
552558 const monitoring = await getMonitoringStatus ( ) ;
553559 const detections = await getAllDetections ( ) ;
554560
555- monitoring . transactionsMonitored ++ ;
561+ monitoring . transactionsMonitored = ( monitoring . transactionsMonitored ?? 0 ) + 1 ;
556562 monitoring . activeDetections = detections . filter ( d => d . status === FraudStatus . PENDING ) . length ;
557563 monitoring . lastCheckTimestamp = new Date ( ) ;
558564
@@ -620,19 +626,21 @@ async function checkLocationAnomaly(
620626 return { isSuspicious : false , reason : '' } ;
621627 }
622628
623- const recentLocation = detections [ detections . length - 1 ] . metadata . location ;
624- if ( ! recentLocation ) {
629+ const rawLocation = detections [ detections . length - 1 ] . metadata . location ;
630+ if ( ! rawLocation ) {
625631 return { isSuspicious : false , reason : '' } ;
626632 }
627633
628- if ( recentLocation . country !== location . country ) {
634+ const recentCountry = typeof rawLocation === 'string' ? rawLocation : rawLocation . country ;
635+
636+ if ( recentCountry !== location . country ) {
629637 const timeDiff = Date . now ( ) - detections [ detections . length - 1 ] . timestamp . getTime ( ) ;
630638 const hoursDiff = timeDiff / ( 1000 * 60 * 60 ) ;
631639
632640 if ( hoursDiff < 2 ) {
633641 return {
634642 isSuspicious : true ,
635- reason : `Location changed from ${ recentLocation . country } to ${ location . country } in ${ hoursDiff . toFixed ( 1 ) } hours` ,
643+ reason : `Location changed from ${ recentCountry } to ${ location . country } in ${ hoursDiff . toFixed ( 1 ) } hours` ,
636644 } ;
637645 }
638646 }
@@ -773,11 +781,11 @@ function generateReportRecommendations(
773781 recommendations . push ( 'Average risk score is rising. Consider implementing additional verification steps.' ) ;
774782 }
775783
776- if ( analytics . falsePositiveRate > 20 ) {
784+ if ( analytics . falsePositiveRate && analytics . falsePositiveRate > 20 ) {
777785 recommendations . push ( `False positive rate is ${ analytics . falsePositiveRate . toFixed ( 1 ) } %. Review and adjust fraud detection thresholds.` ) ;
778786 }
779787
780- if ( analytics . preventedLoss > 1000 ) {
788+ if ( analytics . preventedLoss && analytics . preventedLoss > 1000 ) {
781789 recommendations . push ( `Successfully prevented $${ analytics . preventedLoss . toFixed ( 2 ) } in potential fraud.` ) ;
782790 }
783791
@@ -787,3 +795,41 @@ function generateReportRecommendations(
787795
788796 return recommendations ;
789797}
798+
799+ // ── Synchronous service singleton ──────────────────────────────────────────────
800+ // Provides a lightweight synchronous facade for hooks that need immediate values.
801+
802+ class FraudDetectionService {
803+ private stats : DetectionStats = {
804+ total : 0 ,
805+ blocked : 0 ,
806+ flagged : 0 ,
807+ approved : 0 ,
808+ avgRiskScore : 0 ,
809+ } ;
810+
811+ getDetectionStats ( ) : DetectionStats {
812+ return { ...this . stats } ;
813+ }
814+
815+ updateStats ( partial : Partial < DetectionStats > ) : void {
816+ this . stats = { ...this . stats , ...partial } ;
817+ }
818+ }
819+
820+ export const fraudDetectionService = new FraudDetectionService ( ) ;
821+
822+ // ── Prevention recommendation type ─────────────────────────────────────────────
823+
824+ export interface PreventionRecommendation {
825+ id : string ;
826+ category : 'velocity' | 'geo' | 'device' | 'chargeback' | 'account' | 'monitoring' ;
827+ severity : 'critical' | 'high' | 'medium' | 'low' ;
828+ title : string ;
829+ description : string ;
830+ impactScore : number ;
831+ effort : 'low' | 'medium' | 'high' ;
832+ }
833+
834+ // Re-export DetectionStats so callers can import it from this module
835+ export type { DetectionStats } ;
0 commit comments