Versione OK Pour l'onglet événements.

This commit is contained in:
DahoudG
2025-09-15 20:15:34 +00:00
parent 8a619ee1bf
commit 12d514d866
73 changed files with 11508 additions and 674 deletions

View File

@@ -0,0 +1,428 @@
import 'package:injectable/injectable.dart';
import '../models/payment_model.dart';
import '../models/cotisation_model.dart';
import 'api_service.dart';
import 'cache_service.dart';
import 'wave_payment_service.dart';
import 'orange_money_service.dart';
import 'moov_money_service.dart';
/// Service de gestion des paiements
/// Gère les transactions de paiement avec différents opérateurs
@LazySingleton()
class PaymentService {
final ApiService _apiService;
final CacheService _cacheService;
final WavePaymentService _waveService;
final OrangeMoneyService _orangeService;
final MoovMoneyService _moovService;
PaymentService(
this._apiService,
this._cacheService,
this._waveService,
this._orangeService,
this._moovService,
);
/// Initie un paiement pour une cotisation
Future<PaymentModel> initiatePayment({
required String cotisationId,
required double montant,
required String methodePaiement,
required String numeroTelephone,
String? nomPayeur,
String? emailPayeur,
}) async {
try {
PaymentModel payment;
// Déléguer au service spécialisé selon la méthode de paiement
switch (methodePaiement) {
case 'WAVE':
payment = await _waveService.initiatePayment(
cotisationId: cotisationId,
montant: montant,
numeroTelephone: numeroTelephone,
nomPayeur: nomPayeur,
emailPayeur: emailPayeur,
);
break;
case 'ORANGE_MONEY':
payment = await _orangeService.initiatePayment(
cotisationId: cotisationId,
montant: montant,
numeroTelephone: numeroTelephone,
nomPayeur: nomPayeur,
emailPayeur: emailPayeur,
);
break;
case 'MOOV_MONEY':
payment = await _moovService.initiatePayment(
cotisationId: cotisationId,
montant: montant,
numeroTelephone: numeroTelephone,
nomPayeur: nomPayeur,
emailPayeur: emailPayeur,
);
break;
default:
throw PaymentException('Méthode de paiement non supportée: $methodePaiement');
}
// Sauvegarder en cache
await _cachePayment(payment);
return payment;
} catch (e) {
if (e is PaymentException) rethrow;
throw PaymentException('Erreur lors de l\'initiation du paiement: ${e.toString()}');
}
}
/// Vérifie le statut d'un paiement
Future<PaymentModel> checkPaymentStatus(String paymentId) async {
try {
// Essayer le cache d'abord
final cachedPayment = await _getCachedPayment(paymentId);
// Si le paiement est déjà terminé (succès ou échec), retourner le cache
if (cachedPayment != null &&
(cachedPayment.isSuccessful || cachedPayment.isFailed)) {
return cachedPayment;
}
// Déterminer le service à utiliser selon la méthode de paiement
PaymentModel payment;
if (cachedPayment != null) {
switch (cachedPayment.methodePaiement) {
case 'WAVE':
payment = await _waveService.checkPaymentStatus(paymentId);
break;
case 'ORANGE_MONEY':
payment = await _orangeService.checkPaymentStatus(paymentId);
break;
case 'MOOV_MONEY':
payment = await _moovService.checkPaymentStatus(paymentId);
break;
default:
throw PaymentException('Méthode de paiement inconnue: ${cachedPayment.methodePaiement}');
}
} else {
// Si pas de cache, essayer tous les services (peu probable)
throw PaymentException('Paiement non trouvé en cache');
}
// Mettre à jour le cache
await _cachePayment(payment);
return payment;
} catch (e) {
// En cas d'erreur réseau, retourner le cache si disponible
final cachedPayment = await _getCachedPayment(paymentId);
if (cachedPayment != null) {
return cachedPayment;
}
throw PaymentException('Erreur lors de la vérification du paiement: ${e.toString()}');
}
}
/// Annule un paiement en cours
Future<bool> cancelPayment(String paymentId) async {
try {
// Récupérer le paiement en cache pour connaître la méthode
final cachedPayment = await _getCachedPayment(paymentId);
if (cachedPayment == null) {
throw PaymentException('Paiement non trouvé');
}
// Déléguer au service approprié
bool cancelled = false;
switch (cachedPayment.methodePaiement) {
case 'WAVE':
cancelled = await _waveService.cancelPayment(paymentId);
break;
case 'ORANGE_MONEY':
cancelled = await _orangeService.cancelPayment(paymentId);
break;
case 'MOOV_MONEY':
cancelled = await _moovService.cancelPayment(paymentId);
break;
default:
throw PaymentException('Méthode de paiement non supportée pour l\'annulation');
}
return cancelled;
} catch (e) {
if (e is PaymentException) rethrow;
throw PaymentException('Erreur lors de l\'annulation du paiement: ${e.toString()}');
}
}
/// Retente un paiement échoué
Future<PaymentModel> retryPayment(String paymentId) async {
try {
// Récupérer le paiement original
final originalPayment = await _getCachedPayment(paymentId);
if (originalPayment == null) {
throw PaymentException('Paiement original non trouvé');
}
// Réinitier le paiement avec les mêmes paramètres
return await initiatePayment(
cotisationId: originalPayment.cotisationId,
montant: originalPayment.montant,
methodePaiement: originalPayment.methodePaiement,
numeroTelephone: originalPayment.numeroTelephone ?? '',
nomPayeur: originalPayment.nomPayeur,
emailPayeur: originalPayment.emailPayeur,
);
} catch (e) {
if (e is PaymentException) rethrow;
throw PaymentException('Erreur lors de la nouvelle tentative de paiement: ${e.toString()}');
}
}
/// Récupère l'historique des paiements d'une cotisation
Future<List<PaymentModel>> getPaymentHistory(String cotisationId) async {
try {
// Essayer le cache d'abord
final cachedPayments = await _cacheService.getPayments();
if (cachedPayments != null) {
final filteredPayments = cachedPayments
.where((p) => p.cotisationId == cotisationId)
.toList();
if (filteredPayments.isNotEmpty) {
return filteredPayments;
}
}
// Si pas de cache, retourner une liste vide
// En production, on pourrait appeler l'API ici
return [];
} catch (e) {
throw PaymentException('Erreur lors de la récupération de l\'historique: ${e.toString()}');
}
}
/// Valide les données de paiement avant envoi
bool validatePaymentData({
required String cotisationId,
required double montant,
required String methodePaiement,
required String numeroTelephone,
}) {
// Validation du montant
if (montant <= 0) return false;
// Validation du numéro de téléphone selon l'opérateur
if (!_validatePhoneNumber(numeroTelephone, methodePaiement)) {
return false;
}
// Validation de la méthode de paiement
if (!_isValidPaymentMethod(methodePaiement)) {
return false;
}
return true;
}
/// Calcule les frais de transaction selon la méthode
double calculateTransactionFees(double montant, String methodePaiement) {
switch (methodePaiement) {
case 'ORANGE_MONEY':
return _calculateOrangeMoneyFees(montant);
case 'WAVE':
return _calculateWaveFees(montant);
case 'MOOV_MONEY':
return _calculateMoovMoneyFees(montant);
case 'CARTE_BANCAIRE':
return _calculateCardFees(montant);
default:
return 0.0;
}
}
/// Retourne les méthodes de paiement disponibles
List<PaymentMethod> getAvailablePaymentMethods() {
return [
PaymentMethod(
id: 'ORANGE_MONEY',
nom: 'Orange Money',
icone: '📱',
couleur: '#FF6600',
description: 'Paiement via Orange Money',
fraisMinimum: 0,
fraisMaximum: 1000,
montantMinimum: 100,
montantMaximum: 1000000,
),
PaymentMethod(
id: 'WAVE',
nom: 'Wave',
icone: '🌊',
couleur: '#00D4FF',
description: 'Paiement via Wave',
fraisMinimum: 0,
fraisMaximum: 500,
montantMinimum: 100,
montantMaximum: 2000000,
),
PaymentMethod(
id: 'MOOV_MONEY',
nom: 'Moov Money',
icone: '💙',
couleur: '#0066CC',
description: 'Paiement via Moov Money',
fraisMinimum: 0,
fraisMaximum: 800,
montantMinimum: 100,
montantMaximum: 1500000,
),
PaymentMethod(
id: 'CARTE_BANCAIRE',
nom: 'Carte bancaire',
icone: '💳',
couleur: '#4CAF50',
description: 'Paiement par carte bancaire',
fraisMinimum: 100,
fraisMaximum: 2000,
montantMinimum: 500,
montantMaximum: 5000000,
),
];
}
/// Méthodes privées
Future<void> _cachePayment(PaymentModel payment) async {
try {
// Utiliser le service de cache pour sauvegarder
final payments = await _cacheService.getPayments() ?? [];
// Remplacer ou ajouter le paiement
final index = payments.indexWhere((p) => p.id == payment.id);
if (index >= 0) {
payments[index] = payment;
} else {
payments.add(payment);
}
await _cacheService.savePayments(payments);
} catch (e) {
// Ignorer les erreurs de cache
}
}
Future<PaymentModel?> _getCachedPayment(String paymentId) async {
try {
final payments = await _cacheService.getPayments();
if (payments != null) {
return payments.firstWhere(
(p) => p.id == paymentId,
orElse: () => throw StateError('Payment not found'),
);
}
return null;
} catch (e) {
return null;
}
}
bool _validatePhoneNumber(String numero, String operateur) {
// Supprimer les espaces et caractères spéciaux
final cleanNumber = numero.replaceAll(RegExp(r'[^\d]'), '');
switch (operateur) {
case 'ORANGE_MONEY':
// Orange: 07, 08, 09 (Côte d'Ivoire)
return RegExp(r'^(225)?(0[789])\d{8}$').hasMatch(cleanNumber);
case 'WAVE':
// Wave accepte tous les numéros ivoiriens
return RegExp(r'^(225)?(0[1-9])\d{8}$').hasMatch(cleanNumber);
case 'MOOV_MONEY':
// Moov: 01, 02, 03
return RegExp(r'^(225)?(0[123])\d{8}$').hasMatch(cleanNumber);
default:
return cleanNumber.length >= 8;
}
}
bool _isValidPaymentMethod(String methode) {
const validMethods = [
'ORANGE_MONEY',
'WAVE',
'MOOV_MONEY',
'CARTE_BANCAIRE',
'VIREMENT',
'ESPECES'
];
return validMethods.contains(methode);
}
double _calculateOrangeMoneyFees(double montant) {
if (montant <= 1000) return 0;
if (montant <= 5000) return 25;
if (montant <= 10000) return 50;
if (montant <= 25000) return 100;
if (montant <= 50000) return 200;
return montant * 0.005; // 0.5%
}
double _calculateWaveFees(double montant) {
// Wave a généralement des frais plus bas
if (montant <= 2000) return 0;
if (montant <= 10000) return 25;
if (montant <= 50000) return 100;
return montant * 0.003; // 0.3%
}
double _calculateMoovMoneyFees(double montant) {
if (montant <= 1000) return 0;
if (montant <= 5000) return 30;
if (montant <= 15000) return 75;
if (montant <= 50000) return 150;
return montant * 0.004; // 0.4%
}
double _calculateCardFees(double montant) {
// Frais fixes + pourcentage pour les cartes
return 100 + (montant * 0.025); // 100 XOF + 2.5%
}
}
/// Modèle pour les méthodes de paiement disponibles
class PaymentMethod {
final String id;
final String nom;
final String icone;
final String couleur;
final String description;
final double fraisMinimum;
final double fraisMaximum;
final double montantMinimum;
final double montantMaximum;
PaymentMethod({
required this.id,
required this.nom,
required this.icone,
required this.couleur,
required this.description,
required this.fraisMinimum,
required this.fraisMaximum,
required this.montantMinimum,
required this.montantMaximum,
});
}
/// Exception personnalisée pour les erreurs de paiement
class PaymentException implements Exception {
final String message;
PaymentException(this.message);
@override
String toString() => 'PaymentException: $message';
}