562 lines
17 KiB
Dart
562 lines
17 KiB
Dart
// Enhanced lib/services/wortis_api_service.dart
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import '../models/service_model.dart';
|
|
|
|
class WortisApiService {
|
|
static const String baseUrl = 'https://api.live.wortis.cg/tpe';
|
|
|
|
// Singleton pattern
|
|
static final WortisApiService _instance = WortisApiService._internal();
|
|
factory WortisApiService() => _instance;
|
|
WortisApiService._internal();
|
|
|
|
// Retry configuration
|
|
static const int maxRetries = 3;
|
|
static const Duration retryDelay = Duration(seconds: 2);
|
|
|
|
Future<List<WortisService>> getServices() async {
|
|
return _retryRequest<List<WortisService>>(() async {
|
|
final response = await http
|
|
.get(
|
|
Uri.parse('$baseUrl/get_services_back'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
)
|
|
.timeout(Duration(seconds: 30));
|
|
|
|
if (response.statusCode == 200) {
|
|
final Map<String, dynamic> jsonData = json.decode(response.body);
|
|
final List<dynamic> servicesJson = jsonData['all_services'] ?? [];
|
|
|
|
return servicesJson
|
|
.map((serviceJson) => WortisService.fromJson(serviceJson))
|
|
.toList();
|
|
} else {
|
|
throw Exception('Erreur API: ${response.statusCode}');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Méthode pour grouper les services par secteur
|
|
Map<String, List<WortisService>> groupServicesBySector(
|
|
List<WortisService> services,
|
|
) {
|
|
Map<String, List<WortisService>> grouped = {};
|
|
|
|
for (var service in services) {
|
|
String sector = service.secteurActivite;
|
|
if (grouped[sector] == null) {
|
|
grouped[sector] = [];
|
|
}
|
|
grouped[sector]!.add(service);
|
|
}
|
|
|
|
return grouped;
|
|
}
|
|
|
|
// Generic retry logic
|
|
Future<T> _retryRequest<T>(Future<T> Function() request) async {
|
|
Exception? lastException;
|
|
|
|
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
|
try {
|
|
return await request();
|
|
} on SocketException catch (e) {
|
|
lastException = e;
|
|
print('Tentative $attempt échouée (SocketException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} on HttpException catch (e) {
|
|
lastException = e;
|
|
print('Tentative $attempt échouée (HttpException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} on http.ClientException catch (e) {
|
|
lastException = e;
|
|
print('Tentative $attempt échouée (ClientException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} catch (e) {
|
|
// For other exceptions, don't retry
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
throw Exception(
|
|
'Impossible de se connecter après $maxRetries tentatives: $lastException',
|
|
);
|
|
}
|
|
}
|
|
|
|
class AuthApiService {
|
|
static const String baseUrl = 'https://api.live.wortis.cg/tpe';
|
|
static const int maxRetries = 3;
|
|
static const Duration retryDelay = Duration(seconds: 2);
|
|
|
|
/// Test de connexion à l'API avec retry
|
|
static Future<bool> testConnection() async {
|
|
try {
|
|
return await _retryRequest<bool>(() async {
|
|
final response = await http
|
|
.get(
|
|
Uri.parse('http://google.com/'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
)
|
|
.timeout(Duration(seconds: 10));
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
return data['success'] == true;
|
|
}
|
|
return false;
|
|
});
|
|
} catch (e) {
|
|
print('Erreur test connexion après plusieurs tentatives: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Connexion utilisateur avec retry
|
|
static Future<Map<String, dynamic>> login(String agentId, String pin) async {
|
|
try {
|
|
return await _retryRequest<Map<String, dynamic>>(() async {
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/auth/login'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
body: jsonEncode({'agent_id': agentId, 'pin': pin}),
|
|
)
|
|
.timeout(Duration(seconds: 15));
|
|
|
|
return jsonDecode(response.body);
|
|
});
|
|
} catch (e) {
|
|
print('Erreur login API après retry: $e');
|
|
return {'success': false, 'message': 'Erreur de connexion au serveur'};
|
|
}
|
|
}
|
|
|
|
/// Créer un utilisateur avec retry
|
|
static Future<Map<String, dynamic>> createUser(
|
|
String nom,
|
|
String pin, {
|
|
String role = 'agent',
|
|
}) async {
|
|
try {
|
|
return await _retryRequest<Map<String, dynamic>>(() async {
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/users/register'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
body: jsonEncode({'nom': nom, 'pin': pin, 'role': role}),
|
|
)
|
|
.timeout(Duration(seconds: 15));
|
|
|
|
return jsonDecode(response.body);
|
|
});
|
|
} catch (e) {
|
|
print('Erreur création utilisateur après retry: $e');
|
|
return {'success': false, 'message': 'Erreur de création'};
|
|
}
|
|
}
|
|
|
|
/// Récupérer le solde d'un agent avec retry
|
|
static Future<Map<String, dynamic>> getAgentBalance(String agentId) async {
|
|
try {
|
|
return await _retryRequest<Map<String, dynamic>>(() async {
|
|
final response = await http
|
|
.get(
|
|
Uri.parse('$baseUrl/agent/$agentId/balance'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
)
|
|
.timeout(Duration(seconds: 15));
|
|
|
|
return jsonDecode(response.body);
|
|
});
|
|
} catch (e) {
|
|
print('Erreur récupération solde après retry: $e');
|
|
return {
|
|
'success': false,
|
|
'message': 'Erreur lors de la récupération du solde',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Recharger le solde d'un agent avec retry
|
|
static Future<Map<String, dynamic>> rechargeAgent(
|
|
String agentId,
|
|
double montant,
|
|
) async {
|
|
try {
|
|
return await _retryRequest<Map<String, dynamic>>(() async {
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/agent/$agentId/recharge'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
body: jsonEncode({'montant': montant}),
|
|
)
|
|
.timeout(Duration(seconds: 15));
|
|
|
|
return jsonDecode(response.body);
|
|
});
|
|
} catch (e) {
|
|
print('Erreur recharge après retry: $e');
|
|
return {'success': false, 'message': 'Erreur lors de la recharge'};
|
|
}
|
|
}
|
|
|
|
/// Mettre à jour le solde d'un agent avec retry
|
|
static Future<Map<String, dynamic>> updateAgentBalance(
|
|
String agentId,
|
|
double nouveauSolde,
|
|
) async {
|
|
try {
|
|
return await _retryRequest<Map<String, dynamic>>(() async {
|
|
final response = await http
|
|
.put(
|
|
Uri.parse('$baseUrl/agent/$agentId/balance'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
body: jsonEncode({'solde': nouveauSolde}),
|
|
)
|
|
.timeout(Duration(seconds: 15));
|
|
|
|
return jsonDecode(response.body);
|
|
});
|
|
} catch (e) {
|
|
print('Erreur mise à jour solde après retry: $e');
|
|
return {
|
|
'success': false,
|
|
'message': 'Erreur lors de la mise à jour du solde',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Enregistrer une transaction avec retry
|
|
static Future<Map<String, dynamic>> recordTransaction({
|
|
required String agentId,
|
|
required String serviceId,
|
|
required String serviceName,
|
|
required double montant,
|
|
required double commission,
|
|
required String typeTransaction,
|
|
required Map<String, dynamic> detailsTransaction,
|
|
}) async {
|
|
try {
|
|
return await _retryRequest<Map<String, dynamic>>(() async {
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/transactions'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
body: jsonEncode({
|
|
'agent_id': agentId,
|
|
'service_id': serviceId,
|
|
'service_name': serviceName,
|
|
'montant': montant,
|
|
'commission': commission,
|
|
'type_transaction': typeTransaction,
|
|
'details': detailsTransaction,
|
|
'date_transaction': DateTime.now().toIso8601String(),
|
|
}),
|
|
)
|
|
.timeout(Duration(seconds: 15));
|
|
|
|
return jsonDecode(response.body);
|
|
});
|
|
} catch (e) {
|
|
print('Erreur enregistrement transaction après retry: $e');
|
|
return {
|
|
'success': false,
|
|
'message': 'Erreur lors de l\'enregistrement de la transaction',
|
|
};
|
|
}
|
|
}
|
|
|
|
// Generic retry logic for AuthApiService
|
|
static Future<T> _retryRequest<T>(Future<T> Function() request) async {
|
|
Exception? lastException;
|
|
|
|
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
|
try {
|
|
return await request();
|
|
} on SocketException catch (e) {
|
|
lastException = e;
|
|
print('AuthAPI - Tentative $attempt échouée (SocketException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} on HttpException catch (e) {
|
|
lastException = e;
|
|
print('AuthAPI - Tentative $attempt échouée (HttpException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} on http.ClientException catch (e) {
|
|
lastException = e;
|
|
print('AuthAPI - Tentative $attempt échouée (ClientException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} catch (e) {
|
|
// For other exceptions, don't retry
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
throw Exception(
|
|
'Impossible de se connecter après $maxRetries tentatives: $lastException',
|
|
);
|
|
}
|
|
}
|
|
|
|
class ApiService {
|
|
static const String baseUrl = 'https://api.live.wortis.cg/tpe';
|
|
static const int maxRetries = 3;
|
|
static const Duration retryDelay = Duration(seconds: 2);
|
|
|
|
/// Récupérer les champs d'un service avec retry amélioré
|
|
Future<Map<String, dynamic>> fetchServiceFields(String serviceName) async {
|
|
return _retryRequest<Map<String, dynamic>>(() async {
|
|
// Fix: Remove double slash in URL
|
|
final url = '$baseUrl/service/${Uri.encodeComponent(serviceName)}';
|
|
print('Fetching service fields from: $url');
|
|
|
|
final response = await http
|
|
.get(
|
|
Uri.parse(url),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
'User-Agent': 'WortisApp/1.0',
|
|
},
|
|
)
|
|
.timeout(Duration(seconds: 30));
|
|
|
|
print('Response status: ${response.statusCode}');
|
|
print('Response headers: ${response.headers}');
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
} else {
|
|
throw Exception(
|
|
'Erreur API: ${response.statusCode} - ${response.body}',
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Vérifier les données avec GET et retry
|
|
Future<Map<String, dynamic>> verifyDataGet(
|
|
String url,
|
|
Map<String, dynamic> params,
|
|
) async {
|
|
return _retryRequest<Map<String, dynamic>>(() async {
|
|
final uri = Uri.parse(url).replace(
|
|
queryParameters: params.map(
|
|
(key, value) => MapEntry(key, value.toString()),
|
|
),
|
|
);
|
|
|
|
final response = await http
|
|
.get(
|
|
uri,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
)
|
|
.timeout(Duration(seconds: 30));
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
} else {
|
|
throw Exception(
|
|
'Données non trouvées - Status: ${response.statusCode}',
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Vérifier les données avec POST et retry
|
|
Future<Map<String, dynamic>> verifyDataPost(
|
|
String url,
|
|
Map<String, dynamic> data,
|
|
String operationId,
|
|
) async {
|
|
return _retryRequest<Map<String, dynamic>>(() async {
|
|
final response = await http
|
|
.post(
|
|
Uri.parse(url),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: 30));
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
} else {
|
|
throw Exception(
|
|
'Données non trouvées - Status: ${response.statusCode}',
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Soumettre les données du formulaire avec retry
|
|
Future<void> submitFormData(
|
|
BuildContext context,
|
|
String url,
|
|
Map<String, dynamic> data,
|
|
Map<String, dynamic>? serviceData,
|
|
dynamic additionalData,
|
|
bool isCardPayment,
|
|
) async {
|
|
try {
|
|
await _retryRequest<void>(() async {
|
|
print(url);
|
|
final response = await http
|
|
.post(
|
|
Uri.parse(url),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: 30));
|
|
|
|
if (response.statusCode == 200) {
|
|
final responseData = jsonDecode(response.body);
|
|
|
|
// Afficher un message de succès
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Row(
|
|
children: [
|
|
Icon(Icons.check_circle, color: Colors.white),
|
|
SizedBox(width: 8),
|
|
Text('Transaction effectuée avec succès'),
|
|
],
|
|
),
|
|
backgroundColor: Colors.green,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Retourner à la page précédente
|
|
Navigator.of(context).pop();
|
|
}
|
|
} else {
|
|
throw Exception(
|
|
'Erreur lors de la soumission - Status: ${response.statusCode}',
|
|
);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
print('Erreur submitFormData après retry: $e');
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Row(
|
|
children: [
|
|
Icon(Icons.error, color: Colors.white),
|
|
SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
'Erreur lors de la transaction: ${e.toString()}',
|
|
maxLines: 2,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
backgroundColor: Colors.red,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
duration: Duration(seconds: 5),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Annuler l'opération en cours
|
|
void cancelOperation() {
|
|
print('Opération annulée');
|
|
}
|
|
|
|
// Generic retry logic for ApiService
|
|
Future<T> _retryRequest<T>(Future<T> Function() request) async {
|
|
Exception? lastException;
|
|
|
|
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
|
try {
|
|
return await request();
|
|
} on SocketException catch (e) {
|
|
lastException = e;
|
|
print('API - Tentative $attempt échouée (SocketException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} on HttpException catch (e) {
|
|
lastException = e;
|
|
print('API - Tentative $attempt échouée (HttpException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} on http.ClientException catch (e) {
|
|
lastException = e;
|
|
print('API - Tentative $attempt échouée (ClientException): $e');
|
|
if (attempt < maxRetries) {
|
|
await Future.delayed(retryDelay * attempt);
|
|
}
|
|
} catch (e) {
|
|
// For other exceptions, don't retry
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
throw Exception(
|
|
'Impossible de se connecter après $maxRetries tentatives: $lastException',
|
|
);
|
|
}
|
|
}
|