Files
unionflow-server-api/unionflow-mobile-apps/lib/features/cotisations/presentation/pages/wave_demo_page.dart
2025-09-17 17:54:06 +00:00

669 lines
19 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/services/wave_integration_service.dart';
import '../../../../core/services/wave_payment_service.dart';
import '../../../../core/models/cotisation_model.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../shared/widgets/buttons/primary_button.dart';
import '../../../../shared/widgets/common/unified_page_layout.dart';
/// Page de démonstration de l'intégration Wave Money
/// Permet de tester toutes les fonctionnalités Wave
class WaveDemoPage extends StatefulWidget {
const WaveDemoPage({super.key});
@override
State<WaveDemoPage> createState() => _WaveDemoPageState();
}
class _WaveDemoPageState extends State<WaveDemoPage>
with TickerProviderStateMixin {
late WaveIntegrationService _waveIntegrationService;
late WavePaymentService _wavePaymentService;
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
final _amountController = TextEditingController(text: '5000');
final _phoneController = TextEditingController(text: '77123456');
final _nameController = TextEditingController(text: 'Test User');
bool _isLoading = false;
String _lastResult = '';
WavePaymentStats? _stats;
@override
void initState() {
super.initState();
_waveIntegrationService = getIt<WaveIntegrationService>();
_wavePaymentService = getIt<WavePaymentService>();
_animationController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
);
_animationController.forward();
_loadStats();
}
@override
void dispose() {
_amountController.dispose();
_phoneController.dispose();
_nameController.dispose();
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return UnifiedPageLayout(
title: 'Wave Money Demo',
subtitle: 'Test d\'intégration Wave Money',
showBackButton: true,
child: FadeTransition(
opacity: _fadeAnimation,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildWaveHeader(),
const SizedBox(height: 24),
_buildTestForm(),
const SizedBox(height: 24),
_buildQuickActions(),
const SizedBox(height: 24),
_buildStatsSection(),
const SizedBox(height: 24),
_buildResultSection(),
],
),
),
),
);
}
Widget _buildWaveHeader() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF00D4FF), Color(0xFF0099CC)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: const Color(0xFF00D4FF).withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(
children: [
Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
),
child: const Icon(
Icons.waves,
size: 32,
color: Color(0xFF00D4FF),
),
),
const SizedBox(width: 16),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Wave Money Integration',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
'Test et démonstration',
style: TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: const Row(
children: [
Icon(Icons.info_outline, color: Colors.white, size: 16),
SizedBox(width: 8),
Expanded(
child: Text(
'Environnement de test - Aucun paiement réel ne sera effectué',
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
),
),
],
),
),
],
),
);
}
Widget _buildTestForm() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppTheme.borderLight),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Paramètres de test',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
// Montant
TextFormField(
controller: _amountController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Montant (XOF)',
prefixIcon: Icon(Icons.attach_money),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
// Numéro de téléphone
TextFormField(
controller: _phoneController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Numéro Wave Money',
prefixIcon: Icon(Icons.phone),
prefixText: '+225 ',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
// Nom
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Nom du payeur',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
// Bouton de test
SizedBox(
width: double.infinity,
child: PrimaryButton(
text: _isLoading ? 'Test en cours...' : 'Tester le paiement Wave',
icon: _isLoading ? null : Icons.play_arrow,
onPressed: _isLoading ? null : _testWavePayment,
isLoading: _isLoading,
backgroundColor: const Color(0xFF00D4FF),
),
),
],
),
);
}
Widget _buildQuickActions() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppTheme.borderLight),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Actions rapides',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildActionChip(
'Calculer frais',
Icons.calculate,
_calculateFees,
),
_buildActionChip(
'Historique',
Icons.history,
_showHistory,
),
_buildActionChip(
'Statistiques',
Icons.analytics,
_loadStats,
),
_buildActionChip(
'Vider cache',
Icons.clear_all,
_clearCache,
),
],
),
],
),
);
}
Widget _buildActionChip(String label, IconData icon, VoidCallback onPressed) {
return ActionChip(
avatar: Icon(icon, size: 16),
label: Text(label),
onPressed: onPressed,
backgroundColor: AppTheme.backgroundLight,
side: const BorderSide(color: AppTheme.borderLight),
);
}
Widget _buildStatsSection() {
if (_stats == null) {
return const SizedBox.shrink();
}
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppTheme.borderLight),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Statistiques Wave Money',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
childAspectRatio: 2.5,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
children: [
_buildStatCard(
'Total paiements',
_stats!.totalPayments.toString(),
Icons.payment,
AppTheme.primaryColor,
),
_buildStatCard(
'Réussis',
_stats!.completedPayments.toString(),
Icons.check_circle,
AppTheme.successColor,
),
_buildStatCard(
'Montant total',
'${_stats!.totalAmount.toStringAsFixed(0)} XOF',
Icons.attach_money,
AppTheme.warningColor,
),
_buildStatCard(
'Taux de réussite',
'${_stats!.successRate.toStringAsFixed(1)}%',
Icons.trending_up,
AppTheme.infoColor,
),
],
),
],
),
);
}
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, color: color, size: 16),
const SizedBox(width: 4),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 12,
color: color,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
Widget _buildResultSection() {
if (_lastResult.isEmpty) {
return const SizedBox.shrink();
}
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppTheme.borderLight),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(
'Dernier résultat',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const Spacer(),
IconButton(
icon: const Icon(Icons.copy, size: 16),
onPressed: () {
Clipboard.setData(ClipboardData(text: _lastResult));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Résultat copié')),
);
},
tooltip: 'Copier',
),
],
),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppTheme.backgroundLight,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppTheme.borderLight),
),
child: Text(
_lastResult,
style: const TextStyle(
fontSize: 12,
fontFamily: 'monospace',
color: AppTheme.textSecondary,
),
),
),
],
),
);
}
// Actions
Future<void> _testWavePayment() async {
setState(() {
_isLoading = true;
_lastResult = '';
});
try {
final amount = double.tryParse(_amountController.text) ?? 0;
if (amount <= 0) {
throw Exception('Montant invalide');
}
// Créer une cotisation de test
final testCotisation = CotisationModel(
id: 'test_${DateTime.now().millisecondsSinceEpoch}',
numeroReference: 'TEST-${DateTime.now().millisecondsSinceEpoch}',
membreId: 'test_member',
nomMembre: _nameController.text,
typeCotisation: 'MENSUELLE',
montantDu: amount,
montantPaye: 0,
codeDevise: 'XOF',
dateEcheance: DateTime.now().add(const Duration(days: 30)),
statut: 'EN_ATTENTE',
recurrente: false,
nombreRappels: 0,
annee: DateTime.now().year,
dateCreation: DateTime.now(),
);
// Initier le paiement Wave
final result = await _waveIntegrationService.initiateWavePayment(
cotisationId: testCotisation.id,
montant: amount,
numeroTelephone: _phoneController.text,
nomPayeur: _nameController.text,
metadata: {
'test_mode': true,
'demo_page': true,
},
);
setState(() {
_lastResult = '''
Test de paiement Wave Money
Résultat: ${result.success ? 'SUCCÈS' : 'ÉCHEC'}
${result.success ? '''
ID Paiement: ${result.payment?.id}
Session Wave: ${result.session?.waveSessionId}
URL Checkout: ${result.checkoutUrl}
Montant: ${amount.toStringAsFixed(0)} XOF
Frais: ${_wavePaymentService.calculateWaveFees(amount).toStringAsFixed(0)} XOF
''' : '''
Erreur: ${result.errorMessage}
'''}
Timestamp: ${DateTime.now().toIso8601String()}
'''.trim();
});
// Feedback haptique
HapticFeedback.lightImpact();
// Recharger les statistiques
await _loadStats();
} catch (e) {
setState(() {
_lastResult = 'Erreur lors du test: $e';
});
} finally {
setState(() {
_isLoading = false;
});
}
}
void _calculateFees() {
final amount = double.tryParse(_amountController.text) ?? 0;
if (amount <= 0) {
setState(() {
_lastResult = 'Montant invalide pour le calcul des frais';
});
return;
}
final fees = _wavePaymentService.calculateWaveFees(amount);
final total = amount + fees;
setState(() {
_lastResult = '''
Calcul des frais Wave Money
Montant: ${amount.toStringAsFixed(0)} XOF
Frais Wave: ${fees.toStringAsFixed(0)} XOF
Total: ${total.toStringAsFixed(0)} XOF
Barème Wave CI 2024:
0-2000 XOF: Gratuit
2001-10000 XOF: 25 XOF
10001-50000 XOF: 100 XOF
50001-100000 XOF: 200 XOF
100001-500000 XOF: 500 XOF
>500000 XOF: 0.1% du montant
'''.trim();
});
}
Future<void> _showHistory() async {
try {
final history = await _waveIntegrationService.getWavePaymentHistory(limit: 10);
setState(() {
_lastResult = '''
Historique des paiements Wave (10 derniers)
${history.isEmpty ? 'Aucun paiement trouvé' : history.map((payment) => '''
${payment.numeroReference} - ${payment.montant.toStringAsFixed(0)} XOF
Statut: ${payment.statut}
Date: ${payment.dateTransaction.toString().substring(0, 16)}
''').join('\n')}
Total: ${history.length} paiement(s)
'''.trim();
});
} catch (e) {
setState(() {
_lastResult = 'Erreur lors de la récupération de l\'historique: $e';
});
}
}
Future<void> _loadStats() async {
try {
final stats = await _waveIntegrationService.getWavePaymentStats();
setState(() {
_stats = stats;
});
} catch (e) {
print('Erreur lors du chargement des statistiques: $e');
}
}
Future<void> _clearCache() async {
try {
// TODO: Implémenter le nettoyage du cache
setState(() {
_lastResult = 'Cache Wave Money vidé avec succès';
_stats = null;
});
await _loadStats();
} catch (e) {
setState(() {
_lastResult = 'Erreur lors du nettoyage du cache: $e';
});
}
}
}