feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles

- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-11-09 18:26:27 +01:00
parent 21657a3820
commit 2f5946a184
812 changed files with 142105 additions and 25992 deletions

View File

@@ -145,8 +145,8 @@ class ApiService {
// Détermine l'environnement actuel (DEV, REC, PROD) en fonction de l'URL
String _determineEnvironment() {
if (!kIsWeb) {
// En mode non-web, utiliser l'environnement de développement par défaut
return 'DEV';
// En mode non-web (iOS/Android), utiliser l'environnement de PRODUCTION par défaut
return 'PROD';
}
final currentUrl = html.window.location.href.toLowerCase();
@@ -196,6 +196,12 @@ class ApiService {
return _baseUrl;
}
// Obtenir l'URL frontend (sans /api) - utile pour les redirections Stripe
String getFrontendUrl() {
// Retirer le /api de l'URL pour obtenir l'URL frontend
return _baseUrl.replaceAll('/api', '');
}
// Obtenir l'identifiant d'application actuel (utile pour le débogage)
String getCurrentAppIdentifier() {
return _appIdentifier;
@@ -208,8 +214,8 @@ class ApiService {
return _connectivityService!.isConnected;
}
// Fallback sur la vérification directe
final connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none;
final connectivityResults = await (Connectivity().checkConnectivity());
return !connectivityResults.contains(ConnectivityResult.none);
}
// Met une requête en file d'attente pour envoi ultérieur
@@ -385,7 +391,8 @@ class ApiService {
// Limiter le nombre de tentatives
if (request.retryCount >= 5) {
debugPrint('⚠️ Nombre maximum de tentatives atteint (5) - Passage à la requête suivante');
debugPrint('⚠️ Nombre maximum de tentatives atteint (5) - Suppression de la requête');
await box.delete(request.key);
continue;
}
@@ -590,7 +597,7 @@ class ApiService {
return await _dio.post(path, data: requestData);
} on DioException catch (e) {
// Si erreur réseau, mettre en file d'attente
if (e.type == DioExceptionType.connectionTimeout ||
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.connectionError ||
e.type == DioExceptionType.unknown) {
await _queueRequest(
@@ -629,12 +636,12 @@ class ApiService {
data: {'queued': true},
);
}
try {
return await _dio.get(path, queryParameters: queryParameters);
} on DioException catch (e) {
// Si erreur réseau, mettre en file d'attente
if (e.type == DioExceptionType.connectionTimeout ||
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.connectionError ||
e.type == DioExceptionType.unknown) {
await _queueRequest(
@@ -655,6 +662,19 @@ class ApiService {
}
}
// Méthode GET sans queue (pour les refresh de session)
// Ne met JAMAIS la requête en file d'attente, échoue directement
Future<Response> getWithoutQueue(String path, {Map<String, dynamic>? queryParameters}) async {
try {
return await _dio.get(path, queryParameters: queryParameters);
} on DioException catch (e) {
throw ApiException.fromDioException(e);
} catch (e) {
if (e is ApiException) rethrow;
throw ApiException('Erreur inattendue lors de la requête GET', originalError: e);
}
}
// Méthode PUT générique
Future<Response> put(String path, {dynamic data, String? tempId}) async {
// Vérifier la connectivité
@@ -684,7 +704,7 @@ class ApiService {
return await _dio.put(path, data: requestData);
} on DioException catch (e) {
// Si erreur réseau, mettre en file d'attente
if (e.type == DioExceptionType.connectionTimeout ||
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.connectionError ||
e.type == DioExceptionType.unknown) {
await _queueRequest(
@@ -728,7 +748,7 @@ class ApiService {
return await _dio.delete(path);
} on DioException catch (e) {
// Si erreur réseau, mettre en file d'attente
if (e.type == DioExceptionType.connectionTimeout ||
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.connectionError ||
e.type == DioExceptionType.unknown) {
await _queueRequest(
@@ -1068,70 +1088,6 @@ class ApiService {
}
}
// === MÉTHODES DE REFRESH DE SESSION ===
/// Rafraîchit toutes les données de session (pour F5, démarrage)
/// Retourne les mêmes données qu'un login normal
Future<Response> refreshSessionAll() async {
try {
debugPrint('🔄 Refresh complet de session');
// Vérifier qu'on a bien un token/session
if (_sessionId == null) {
throw ApiException('Pas de session active pour le refresh');
}
final response = await post('/session/refresh/all');
// Traiter la réponse comme un login
final data = response.data as Map<String, dynamic>?;
if (data != null && data['status'] == 'success') {
// Si nouveau session_id dans la réponse, le mettre à jour
if (data.containsKey('session_id')) {
final newSessionId = data['session_id'];
if (newSessionId != null) {
setSessionId(newSessionId);
}
}
// Collecter et envoyer les informations du device après refresh réussi
debugPrint('📱 Collecte des informations device après refresh de session...');
DeviceInfoService.instance.collectAndSendDeviceInfo().then((_) {
debugPrint('✅ Informations device collectées et envoyées (refresh)');
}).catchError((error) {
debugPrint('⚠️ Erreur lors de l\'envoi des infos device (refresh): $error');
// Ne pas bloquer le refresh si l'envoi des infos device échoue
});
}
return response;
} catch (e) {
debugPrint('❌ Erreur refresh complet: $e');
rethrow;
}
}
/// Rafraîchit partiellement les données modifiées depuis lastSync
/// Ne retourne que les données modifiées (delta)
Future<Response> refreshSessionPartial(DateTime lastSync) async {
try {
debugPrint('🔄 Refresh partiel depuis: ${lastSync.toIso8601String()}');
// Vérifier qu'on a bien un token/session
if (_sessionId == null) {
throw ApiException('Pas de session active pour le refresh');
}
final response = await post('/session/refresh/partial', data: {
'last_sync': lastSync.toIso8601String(),
});
return response;
} catch (e) {
debugPrint('❌ Erreur refresh partiel: $e');
rethrow;
}
}
// Déconnexion
Future<void> logout() async {

View File

@@ -1,13 +1,24 @@
import 'package:package_info_plus/package_info_plus.dart';
// ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
// This file is automatically generated by deploy-app.sh script
// Last update: 2025-11-09 12:39:26
// Source: ../VERSION file
//
// GEOSECTOR App Version Service
// Provides application version and build information without external dependencies
class AppInfoService {
static PackageInfo? _packageInfo;
static Future<void> initialize() async {
_packageInfo = await PackageInfo.fromPlatform();
}
static String get version => _packageInfo?.version ?? '0.0.0';
static String get buildNumber => _packageInfo?.buildNumber ?? '0';
// Version number (format: x.x.x)
static const String version = '3.5.2';
// Build number (version without dots: xxx)
static const String buildNumber = '352';
// Full version string (format: vx.x.x+xxx)
static String get fullVersion => 'v$version+$buildNumber';
}
// Application name
static const String appName = 'GeoSector';
// Package name
static const String packageName = 'fr.geosector.app3';
}

View File

@@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart' show kIsWeb;
/// Service qui gère la surveillance de l'état de connectivité de l'appareil
class ConnectivityService extends ChangeNotifier {
final Connectivity _connectivity = Connectivity();
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription;
List<ConnectivityResult> _connectionStatus = [ConnectivityResult.none];
bool _isInitialized = false;
@@ -87,12 +87,12 @@ class ConnectivityService extends ChangeNotifier {
_connectionStatus = [ConnectivityResult.wifi]; // Valeur par défaut pour le web
} else {
final result = await _connectivity.checkConnectivity();
_connectionStatus = [result];
_connectionStatus = result;
}
// S'abonner aux changements de connectivité
_connectivitySubscription = _connectivity.onConnectivityChanged.listen((ConnectivityResult result) {
_updateConnectionStatus([result]);
_connectivitySubscription = _connectivity.onConnectivityChanged.listen((List<ConnectivityResult> results) {
_updateConnectionStatus(results);
});
_isInitialized = true;
} catch (e) {
@@ -146,9 +146,8 @@ class ConnectivityService extends ChangeNotifier {
} else {
// Version mobile - utiliser l'API standard
final result = await _connectivity.checkConnectivity();
final results = [result];
_updateConnectionStatus(results);
return results;
_updateConnectionStatus(result);
return result;
}
} catch (e) {
debugPrint('Erreur lors de la vérification de la connectivité: $e');

View File

@@ -21,6 +21,7 @@ class CurrentUserService extends ChangeNotifier {
bool get isLoggedIn => _currentUser?.hasValidSession ?? false;
int get userRole => _currentUser?.role ?? 0;
int? get userId => _currentUser?.id;
int? get opeUserId => _currentUser?.opeUserId; // ID dans ope_users pour l'opération active
String? get userEmail => _currentUser?.email;
String? get userName => _currentUser?.name;
String? get userFirstName => _currentUser?.firstName;

View File

@@ -448,9 +448,9 @@ class DataLoadingService extends ChangeNotifier {
for (final userSectorData in userSectorsList) {
try {
final userSector = UserSectorModel.fromJson(userSectorData);
final key = '${userSector.id}_${userSector.fkSector}';
final key = '${userSector.opeUserId}_${userSector.fkSector}';
await _userSectorBox.put(key, userSector);
debugPrint('✅ Association sauvegardée: ${userSector.firstName} ${userSector.name} (ID: ${userSector.id}) -> Secteur ${userSector.fkSector}');
debugPrint('✅ Association sauvegardée: ${userSector.firstName} ${userSector.name} (userId: ${userSector.userId}, opeUserId: ${userSector.opeUserId}) -> Secteur ${userSector.fkSector}');
count++;
} catch (e) {
debugPrint('⚠️ Erreur traitement association: $e');
@@ -467,7 +467,7 @@ class DataLoadingService extends ChangeNotifier {
final key = _userSectorBox.keyAt(i);
final value = _userSectorBox.getAt(i);
if (value != null) {
debugPrint(' - [$key]: ${value.firstName} ${value.name} (ID: ${value.id}) -> Secteur ${value.fkSector}');
debugPrint(' - [$key]: ${value.firstName} ${value.name} (userId: ${value.userId}, opeUserId: ${value.opeUserId}) -> Secteur ${value.fkSector}');
}
}
if (_userSectorBox.length > 5) {

View File

@@ -3,13 +3,12 @@ import 'package:flutter/foundation.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:battery_plus/battery_plus.dart';
import 'package:nfc_manager/nfc_manager.dart';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:hive/hive.dart';
import 'api_service.dart';
import 'current_user_service.dart';
import 'app_info_service.dart'; // Remplace package_info_plus
import '../constants/app_keys.dart';
class DeviceInfoService {
@@ -18,7 +17,6 @@ class DeviceInfoService {
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
final Battery _battery = Battery();
final NetworkInfo _networkInfo = NetworkInfo();
Future<Map<String, dynamic>> collectDeviceInfo() async {
final deviceData = <String, dynamic>{};
@@ -27,8 +25,8 @@ class DeviceInfoService {
// Informations réseau et IP (IPv4 uniquement)
deviceData['device_ip_local'] = await _getLocalIpAddress();
deviceData['device_ip_public'] = await _getPublicIpAddress();
deviceData['device_wifi_name'] = await _networkInfo.getWifiName();
deviceData['device_wifi_bssid'] = await _networkInfo.getWifiBSSID();
deviceData['device_wifi_name'] = null; // ❌ Supprimé network_info_plus (13/10/2025)
deviceData['device_wifi_bssid'] = null; // ❌ Supprimé network_info_plus (13/10/2025)
// Informations batterie
final batteryLevel = await _battery.batteryLevel;
@@ -120,13 +118,7 @@ class DeviceInfoService {
return null;
}
// Méthode 1 : Via network_info_plus (retourne généralement IPv4)
String? wifiIP = await _networkInfo.getWifiIP();
if (wifiIP != null && wifiIP.isNotEmpty && _isIPv4(wifiIP)) {
return wifiIP;
}
// Méthode 2 : Via NetworkInterface avec filtre IPv4 strict
// Via NetworkInterface avec filtre IPv4 strict
for (var interface in await NetworkInterface.list()) {
for (var addr in interface.addresses) {
// Vérifier explicitement IPv4 et non loopback
@@ -252,9 +244,8 @@ class DeviceInfoService {
final deviceData = await collectDeviceInfo();
// 2. Ajouter les infos de l'app
final packageInfo = await PackageInfo.fromPlatform();
deviceData['app_version'] = packageInfo.version;
deviceData['app_build'] = packageInfo.buildNumber;
deviceData['app_version'] = AppInfoService.version;
deviceData['app_build'] = AppInfoService.buildNumber;
// 3. Sauvegarder dans Hive Settings
await _saveToHiveSettings(deviceData);

View File

@@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
@@ -379,7 +380,13 @@ class HiveService {
final config = _boxConfigs[i];
try {
await _createSingleBox(config);
// Ajouter un timeout de 10 secondes pour éviter les blocages infinis
await _createSingleBox(config).timeout(
const Duration(seconds: 10),
onTimeout: () {
throw TimeoutException('Timeout lors de la création de la box ${config.name}');
},
);
debugPrint('✅ Box ${config.name} créée (${i + 1}/${_boxConfigs.length})');
} catch (e) {
debugPrint('❌ Erreur création ${config.name}: $e');

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
import 'package:geosector_app/core/data/models/payment_link_result.dart';
/// Service pour gérer Stripe Connect dans l'application
class StripeConnectService {
@@ -11,65 +12,79 @@ class StripeConnectService {
StripeConnectService({required this.apiService});
/// Créer un compte Stripe Connect pour une amicale
/// Retourne l'URL d'onboarding ou null si erreur
/// ✅ VERSION OPTIMISÉE : 1 seule requête qui crée tout
/// - Compte Stripe Connect
/// - Location Terminal
/// - Lien d'onboarding
Future<String?> createStripeAccount(AmicaleModel amicale) async {
try {
debugPrint('🏢 Création compte Stripe pour ${amicale.name}...');
debugPrint(' Amicale ID: ${amicale.id}');
// 1. Créer le compte Stripe Connect via notre API
final createResponse = await apiService.post(
// URLs de retour après onboarding
final baseUrl = apiService.getFrontendUrl();
final returnUrl = Uri.encodeFull('$baseUrl/stripe/success');
final refreshUrl = Uri.encodeFull('$baseUrl/stripe/refresh');
debugPrint(' Return URL: $returnUrl');
debugPrint(' Refresh URL: $refreshUrl');
// ✅ UNE SEULE REQUÊTE qui crée :
// 1. Le compte Stripe Connect
// 2. La Location Terminal
// 3. Le lien d'onboarding
final response = await apiService.post(
'/stripe/accounts',
data: {'fk_entite': amicale.id},
data: {
'fk_entite': amicale.id,
'return_url': returnUrl,
'refresh_url': refreshUrl,
},
);
debugPrint(' Response status: ${createResponse.statusCode}');
debugPrint(' Response data: ${createResponse.data}');
if (createResponse.statusCode != 200 && createResponse.statusCode != 201) {
final error = createResponse.data?['message'] ?? 'Erreur création compte';
debugPrint(' Response status: ${response.statusCode}');
debugPrint(' Response data: ${response.data}');
if (response.statusCode != 200 && response.statusCode != 201) {
final error = response.data?['message'] ?? 'Erreur création compte';
debugPrint('❌ Erreur création compte: $error');
throw Exception(error);
}
// Récupérer les données de la réponse
final responseData = createResponse.data;
final accountId = responseData?['account_id'];
final isExisting = responseData?['existing'] ?? false;
final chargesEnabled = responseData?['charges_enabled'] ?? false;
final payoutsEnabled = responseData?['payouts_enabled'] ?? false;
// Récupérer toutes les données de la réponse
final data = response.data;
final accountId = data['account_id'];
final locationId = data['location_id'];
final onboardingUrl = data['onboarding_url'];
final isExisting = data['existing'] ?? false;
final chargesEnabled = data['charges_enabled'] ?? false;
final payoutsEnabled = data['payouts_enabled'] ?? false;
if (accountId == null) {
throw Exception('account_id non retourné par l\'API');
}
debugPrint('✅ Compte Stripe créé: $accountId');
debugPrint('✅ Location Terminal créée: $locationId');
if (isExisting) {
debugPrint(' Compte existant détecté, account_id: $accountId');
// Si le compte est déjà complètement configuré, pas besoin d'onboarding
debugPrint(' Compte existant détecté');
// Si le compte est déjà complètement configuré
if (chargesEnabled && payoutsEnabled) {
debugPrint('✅ Compte déjà configuré et actif');
return null; // Pas besoin de lien d'onboarding
}
debugPrint(' Compte existant mais configuration incomplète, génération du lien...');
} else {
debugPrint('✅ Nouveau compte créé: $accountId');
debugPrint(' Compte existant mais configuration incomplète');
}
// 2. Créer la Location pour Terminal/Tap to Pay
try {
await apiService.post(
'/stripe/locations',
data: {'fk_entite': amicale.id},
);
debugPrint('✅ Location Terminal créée');
} catch (e) {
debugPrint('⚠️ Erreur création Location (non bloquant): $e');
if (onboardingUrl == null || onboardingUrl.isEmpty) {
throw Exception('onboarding_url non retourné par l\'API');
}
// 3. Obtenir le lien d'onboarding
return await getOnboardingLink(accountId);
debugPrint('✅ Lien onboarding généré');
return onboardingUrl;
} catch (e) {
debugPrint('❌ Erreur StripeConnect: $e');
return null;
@@ -77,12 +92,14 @@ class StripeConnectService {
}
/// Obtenir le lien d'onboarding pour finaliser la configuration
/// ⚠️ DÉPRÉCIÉ : L'onboarding URL est maintenant retournée par createStripeAccount
/// Conservé pour compatibilité temporaire
Future<String?> getOnboardingLink(String accountId) async {
try {
debugPrint('📋 Génération du lien d\'onboarding pour account: $accountId');
// URLs de retour après onboarding
const baseUrl = 'https://app.geo.dev'; // À adapter selon l'environnement
// URLs de retour après onboarding - utilise l'environnement détecté
final baseUrl = apiService.getFrontendUrl();
final returnUrl = Uri.encodeFull('$baseUrl/stripe/success');
final refreshUrl = Uri.encodeFull('$baseUrl/stripe/refresh');
@@ -124,6 +141,7 @@ class StripeConnectService {
return StripeAccountStatus(
hasAccount: data['has_account'] ?? false,
accountId: data['account_id'],
locationId: data['location_id'],
chargesEnabled: data['charges_enabled'] ?? false,
payoutsEnabled: data['payouts_enabled'] ?? false,
onboardingCompleted: data['onboarding_completed'] ?? false,
@@ -149,6 +167,49 @@ class StripeConnectService {
}
}
/// Créer un Payment Link pour paiement par QRcode
Future<PaymentLinkResult?> createPaymentLink({
required int amountInCents,
required int passageId,
String? description,
Map<String, dynamic>? metadata,
}) async {
try {
debugPrint('💰 Création Payment Link pour ${amountInCents / 100}€...');
debugPrint(' Passage ID: $passageId');
final response = await apiService.post(
'/stripe/payment-links',
data: {
'amount': amountInCents,
'currency': 'eur',
'description': description ?? 'Calendrier pompiers',
'passage_id': passageId,
'metadata': metadata ?? {},
},
);
debugPrint(' Response status: ${response.statusCode}');
debugPrint(' Response data: ${response.data}');
if (response.statusCode != 200 && response.statusCode != 201) {
final error = response.data?['message'] ?? 'Erreur création Payment Link';
debugPrint('❌ Erreur création Payment Link: $error');
throw Exception(error);
}
final result = PaymentLinkResult.fromJson(response.data);
debugPrint('✅ Payment Link créé: ${result.paymentLinkId}');
debugPrint(' URL: ${result.url}');
return result;
} catch (e) {
debugPrint('❌ Erreur PaymentLink: $e');
return null;
}
}
/// Lancer le processus d'onboarding dans un navigateur externe
Future<bool> launchOnboarding(String url) async {
try {
@@ -190,13 +251,15 @@ class StripeConnectService {
class StripeAccountStatus {
final bool hasAccount;
final String? accountId;
final String? locationId;
final bool chargesEnabled;
final bool payoutsEnabled;
final bool onboardingCompleted;
StripeAccountStatus({
required this.hasAccount,
this.accountId,
this.locationId,
this.chargesEnabled = false,
this.payoutsEnabled = false,
this.onboardingCompleted = false,

View File

@@ -54,6 +54,18 @@ class StripeTapToPayService {
}
_stripeAccountId = amicale.stripeId;
_locationId = amicale.stripeLocationId;
// Vérifier que la Location existe
if (_locationId == null || _locationId!.isEmpty) {
debugPrint('❌ Aucune Location Stripe Terminal configurée pour cette amicale');
debugPrint(' La Location doit être créée lors de l\'onboarding Stripe Connect');
_paymentStatusController.add(TapToPayStatus(
type: TapToPayStatusType.error,
message: 'Location Stripe non configurée',
));
return false;
}
// 3. Vérifier la compatibilité de l'appareil
_deviceCompatible = DeviceInfoService.instance.canUseTapToPay();
@@ -66,9 +78,6 @@ class StripeTapToPayService {
return false;
}
// 4. Récupérer la configuration depuis l'API
await _fetchConfiguration();
_isInitialized = true;
debugPrint('✅ Tap to Pay initialisé avec succès');
@@ -92,20 +101,6 @@ class StripeTapToPayService {
}
}
/// Récupère la configuration depuis l'API
Future<void> _fetchConfiguration() async {
try {
final response = await ApiService.instance.get('/api/stripe/configuration');
_locationId = response.data['location_id'];
debugPrint('✅ Configuration récupérée - Location: $_locationId');
} catch (e) {
debugPrint('❌ Erreur récupération config: $e');
throw Exception('Impossible de récupérer la configuration Stripe');
}
}
/// Crée un PaymentIntent pour un paiement Tap to Pay
Future<PaymentIntentResult?> createPaymentIntent({
required int amountInCents,
@@ -130,7 +125,7 @@ class StripeTapToPayService {
final passageId = metadata?['passage_id'] ?? '0';
final response = await ApiService.instance.post(
'/api/stripe/payments/create-intent',
'/stripe/payments/create-intent',
data: {
'amount': amountInCents,
'currency': 'eur',
@@ -220,7 +215,7 @@ class StripeTapToPayService {
// Notifier le serveur du succès
await ApiService.instance.post(
'/api/stripe/payments/confirm',
'/stripe/payments/confirm',
data: {
'payment_intent_id': paymentIntent.paymentIntentId,
'amount': paymentIntent.amount,
@@ -258,7 +253,7 @@ class StripeTapToPayService {
Future<void> cancelPayment(String paymentIntentId) async {
try {
await ApiService.instance.post(
'/api/stripe/payments/cancel',
'/stripe/payments/cancel',
data: {
'payment_intent_id': paymentIntentId,
},

View File

@@ -23,9 +23,9 @@ class SyncService {
void _initConnectivityListener() {
_connectivitySubscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
// Vérifier si la connexion est disponible
if (result != ConnectivityResult.none) {
.listen((List<ConnectivityResult> results) {
// Vérifier si la liste contient au moins un type de connexion autre que 'none'
if (results.any((result) => result != ConnectivityResult.none)) {
// Lorsque la connexion est rétablie, déclencher une synchronisation
syncAll();
}