// 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> getServices() async { return _retryRequest>(() 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 jsonData = json.decode(response.body); final List 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> groupServicesBySector( List services, ) { Map> 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 _retryRequest(Future 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 testConnection() async { try { return await _retryRequest(() 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> login(String agentId, String pin) async { try { return await _retryRequest>(() 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> createUser( String nom, String pin, { String role = 'agent', }) async { try { return await _retryRequest>(() 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> getAgentBalance(String agentId) async { try { return await _retryRequest>(() 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> rechargeAgent( String agentId, double montant, ) async { try { return await _retryRequest>(() 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> updateAgentBalance( String agentId, double nouveauSolde, ) async { try { return await _retryRequest>(() 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> recordTransaction({ required String agentId, required String serviceId, required String serviceName, required double montant, required double commission, required String typeTransaction, required Map detailsTransaction, }) async { try { return await _retryRequest>(() 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 _retryRequest(Future 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> fetchServiceFields(String serviceName) async { return _retryRequest>(() 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> verifyDataGet( String url, Map params, ) async { return _retryRequest>(() 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> verifyDataPost( String url, Map data, String operationId, ) async { return _retryRequest>(() 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 submitFormData( BuildContext context, String url, Map data, Map? serviceData, dynamic additionalData, bool isCardPayment, ) async { try { await _retryRequest(() 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 _retryRequest(Future 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', ); } }