diff --git a/unionflow/unionflow-mobile-apps/lib/core/utils/error_formatter.dart b/unionflow/unionflow-mobile-apps/lib/core/utils/error_formatter.dart new file mode 100644 index 0000000..628f9db --- /dev/null +++ b/unionflow/unionflow-mobile-apps/lib/core/utils/error_formatter.dart @@ -0,0 +1,95 @@ +/// Utilitaire pour formater les messages d'erreur venant du backend. +/// Gère notamment les erreurs LCB-FT (anti-blanchiment). +class ErrorFormatter { + /// Formate une erreur en message utilisateur convivial. + /// + /// Détecte et formate spécialement les erreurs LCB-FT (origine des fonds manquante). + /// Supprime les préfixes techniques comme "Exception: " ou "DioException: ". + static String format(dynamic error) { + if (error == null) return 'Une erreur inconnue est survenue'; + + final errorString = error.toString(); + + // Erreur LCB-FT : origine des fonds manquante + if (errorString.contains('origine des fonds') || + errorString.contains('LCB-FT') || + errorString.contains('au-dessus du seuil')) { + return '🛡️ L\'origine des fonds est obligatoire pour cette opération (conformité LCB-FT anti-blanchiment).\n\nVeuillez préciser d\'où proviennent les fonds.'; + } + + // Erreur KYC + if (errorString.contains('KYC') || errorString.contains('vérification identité')) { + return '🛡️ Votre identité doit être vérifiée pour cette opération (conformité KYC).\n\nContactez votre administrateur.'; + } + + // Erreur solde insuffisant + if (errorString.contains('solde') && errorString.contains('insuffisant')) { + return '💳 Solde insuffisant pour effectuer cette opération.'; + } + + // Erreur réseau / timeout + if (errorString.contains('SocketException') || + errorString.contains('timeout') || + errorString.contains('network')) { + return '📡 Erreur de connexion. Vérifiez votre connexion internet et réessayez.'; + } + + // Erreur 400 générique (validation backend) + if (errorString.contains('400') || errorString.contains('Bad Request')) { + // Essayer d'extraire le message du backend + final match = RegExp(r'message["\s:]+([^"}\n]+)', caseSensitive: false) + .firstMatch(errorString); + if (match != null && match.group(1) != null) { + return match.group(1)!.trim(); + } + return 'Données invalides. Vérifiez les informations saisies.'; + } + + // Erreur 401 / 403 (authentification / autorisation) + if (errorString.contains('401') || errorString.contains('403')) { + return '🔒 Vous n\'avez pas les autorisations nécessaires pour cette opération.'; + } + + // Erreur 404 (ressource non trouvée) + if (errorString.contains('404')) { + return 'Ressource non trouvée. Elle a peut-être été supprimée.'; + } + + // Erreur 500 (erreur serveur) + if (errorString.contains('500') || errorString.contains('Internal Server')) { + return '🔧 Erreur serveur. Nos équipes ont été notifiées. Réessayez plus tard.'; + } + + // Nettoyer les préfixes techniques + String cleaned = errorString + .replaceFirst('Exception: ', '') + .replaceFirst('DioException: ', '') + .replaceFirst('DioError: ', '') + .replaceFirst('Error: ', '') + .trim(); + + // Si le message est trop long, le tronquer + if (cleaned.length > 200) { + cleaned = '${cleaned.substring(0, 197)}...'; + } + + return cleaned.isNotEmpty ? cleaned : 'Une erreur est survenue'; + } + + /// Détermine si une erreur est critique (nécessite intervention admin). + static bool isCritical(dynamic error) { + final errorString = error.toString().toLowerCase(); + return errorString.contains('kyc') || + errorString.contains('vérification identité') || + errorString.contains('401') || + errorString.contains('403'); + } + + /// Détermine si une erreur est liée au LCB-FT. + static bool isLcbFtError(dynamic error) { + final errorString = error.toString().toLowerCase(); + return errorString.contains('origine des fonds') || + errorString.contains('lcb-ft') || + errorString.contains('anti-blanchiment'); + } +} diff --git a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/depot_epargne_dialog.dart b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/depot_epargne_dialog.dart index c9dfe3d..c76ba87 100644 --- a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/depot_epargne_dialog.dart +++ b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/depot_epargne_dialog.dart @@ -4,6 +4,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../../../core/constants/lcb_ft_constants.dart'; import '../../../../core/data/repositories/parametres_lcb_ft_repository.dart'; +import '../../../../core/utils/error_formatter.dart'; import '../../data/models/transaction_epargne_request.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; @@ -113,7 +114,10 @@ class _DepotEpargneDialogState extends State { } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Wave: ${e.toString().replaceFirst('Exception: ', '')}')), + SnackBar( + content: Text(ErrorFormatter.format(e)), + duration: ErrorFormatter.isLcbFtError(e) ? const Duration(seconds: 6) : const Duration(seconds: 3), + ), ); } finally { if (mounted) setState(() => _waveLoading = false); @@ -158,7 +162,10 @@ class _DepotEpargneDialogState extends State { } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erreur: $e')), + SnackBar( + content: Text(ErrorFormatter.format(e)), + duration: ErrorFormatter.isLcbFtError(e) ? const Duration(seconds: 6) : const Duration(seconds: 3), + ), ); } finally { if (mounted) setState(() => _loading = false); diff --git a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/retrait_epargne_dialog.dart b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/retrait_epargne_dialog.dart index 84a0b22..eafbee8 100644 --- a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/retrait_epargne_dialog.dart +++ b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/retrait_epargne_dialog.dart @@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart'; import '../../../../core/constants/lcb_ft_constants.dart'; import '../../../../core/data/repositories/parametres_lcb_ft_repository.dart'; +import '../../../../core/utils/error_formatter.dart'; import '../../data/models/transaction_epargne_request.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; @@ -105,17 +106,21 @@ class _RetraitEpargneDialogState extends State { _showSnack('Retrait enregistré', isError: false); } catch (e) { if (!mounted) return; - _showSnack('Erreur: ${e.toString().replaceFirst('Exception: ', '')}'); + _showSnack( + ErrorFormatter.format(e), + duration: ErrorFormatter.isLcbFtError(e) ? 6 : 3, + ); } finally { if (mounted) setState(() => _loading = false); } } - void _showSnack(String msg, {bool isError = true}) { + void _showSnack(String msg, {bool isError = true, int duration = 3}) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(msg), backgroundColor: isError ? ColorTokens.error : ColorTokens.success, + duration: Duration(seconds: duration), ), ); } diff --git a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/transfert_epargne_dialog.dart b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/transfert_epargne_dialog.dart index 24e0ef3..10ba13c 100644 --- a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/transfert_epargne_dialog.dart +++ b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/transfert_epargne_dialog.dart @@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart'; import '../../../../core/constants/lcb_ft_constants.dart'; import '../../../../core/data/repositories/parametres_lcb_ft_repository.dart'; +import '../../../../core/utils/error_formatter.dart'; import '../../data/models/compte_epargne_model.dart'; import '../../data/models/transaction_epargne_request.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; @@ -130,8 +131,9 @@ class _TransfertEpargneDialogState extends State { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Erreur: ${e.toString().replaceFirst('Exception: ', '')}'), + content: Text(ErrorFormatter.format(e)), backgroundColor: ColorTokens.error, + duration: ErrorFormatter.isLcbFtError(e) ? const Duration(seconds: 6) : const Duration(seconds: 3), ), ); } finally {