Appli Flutter se connecte bien à l'API.
This commit is contained in:
@@ -3,7 +3,7 @@ import 'package:injectable/injectable.dart';
|
||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
||||
import '../models/auth_state.dart';
|
||||
import '../models/login_request.dart';
|
||||
import '../models/login_response.dart';
|
||||
|
||||
import '../models/user_info.dart';
|
||||
import '../storage/secure_token_storage.dart';
|
||||
import 'auth_api_service.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../models/auth_state.dart';
|
||||
import '../models/login_request.dart';
|
||||
import '../models/user_info.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../models/auth_state.dart';
|
||||
import '../models/login_request.dart';
|
||||
import '../models/user_info.dart';
|
||||
|
||||
@@ -83,7 +83,8 @@ class SecureTokenStorage {
|
||||
/// Récupère la date d'expiration du refresh token
|
||||
Future<DateTime?> getRefreshTokenExpirationDate() async {
|
||||
try {
|
||||
final expiresAtString = await _storage.read(key: _refreshExpiresAtKey);
|
||||
final prefs = await _prefs;
|
||||
final expiresAtString = prefs.getString(_refreshExpiresAtKey);
|
||||
if (expiresAtString == null) return null;
|
||||
|
||||
return DateTime.parse(expiresAtString);
|
||||
@@ -133,9 +134,10 @@ class SecureTokenStorage {
|
||||
/// Met à jour le token d'accès
|
||||
Future<void> updateAccessToken(String accessToken, DateTime expiresAt) async {
|
||||
try {
|
||||
final prefs = await _prefs;
|
||||
await Future.wait([
|
||||
_storage.write(key: _accessTokenKey, value: accessToken),
|
||||
_storage.write(key: _expiresAtKey, value: expiresAt.toIso8601String()),
|
||||
prefs.setString(_accessTokenKey, accessToken),
|
||||
prefs.setString(_expiresAtKey, expiresAt.toIso8601String()),
|
||||
]);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la mise à jour du token d\'accès: $e');
|
||||
@@ -145,8 +147,9 @@ class SecureTokenStorage {
|
||||
/// Vérifie si les données d'authentification existent
|
||||
Future<bool> hasAuthData() async {
|
||||
try {
|
||||
final accessToken = await _storage.read(key: _accessTokenKey);
|
||||
final refreshToken = await _storage.read(key: _refreshTokenKey);
|
||||
final prefs = await _prefs;
|
||||
final accessToken = prefs.getString(_accessTokenKey);
|
||||
final refreshToken = prefs.getString(_refreshTokenKey);
|
||||
return accessToken != null && refreshToken != null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
@@ -184,12 +187,13 @@ class SecureTokenStorage {
|
||||
/// Efface toutes les données d'authentification
|
||||
Future<void> clearAuthData() async {
|
||||
try {
|
||||
final prefs = await _prefs;
|
||||
await Future.wait([
|
||||
_storage.delete(key: _accessTokenKey),
|
||||
_storage.delete(key: _refreshTokenKey),
|
||||
_storage.delete(key: _userInfoKey),
|
||||
_storage.delete(key: _expiresAtKey),
|
||||
_storage.delete(key: _refreshExpiresAtKey),
|
||||
prefs.remove(_accessTokenKey),
|
||||
prefs.remove(_refreshTokenKey),
|
||||
prefs.remove(_userInfoKey),
|
||||
prefs.remove(_expiresAtKey),
|
||||
prefs.remove(_refreshExpiresAtKey),
|
||||
]);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de l\'effacement des données d\'authentification: $e');
|
||||
@@ -199,7 +203,8 @@ class SecureTokenStorage {
|
||||
/// Active/désactive l'authentification biométrique
|
||||
Future<void> setBiometricEnabled(bool enabled) async {
|
||||
try {
|
||||
await _storage.write(key: _biometricEnabledKey, value: enabled.toString());
|
||||
final prefs = await _prefs;
|
||||
await prefs.setBool(_biometricEnabledKey, enabled);
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de la configuration biométrique: $e');
|
||||
}
|
||||
@@ -208,8 +213,8 @@ class SecureTokenStorage {
|
||||
/// Vérifie si l'authentification biométrique est activée
|
||||
Future<bool> isBiometricEnabled() async {
|
||||
try {
|
||||
final enabled = await _storage.read(key: _biometricEnabledKey);
|
||||
return enabled == 'true';
|
||||
final prefs = await _prefs;
|
||||
return prefs.getBool(_biometricEnabledKey) ?? false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
@@ -218,7 +223,8 @@ class SecureTokenStorage {
|
||||
/// Efface toutes les données stockées
|
||||
Future<void> clearAll() async {
|
||||
try {
|
||||
await _storage.deleteAll();
|
||||
final prefs = await _prefs;
|
||||
await prefs.clear();
|
||||
} catch (e) {
|
||||
throw StorageException('Erreur lors de l\'effacement de toutes les données: $e');
|
||||
}
|
||||
@@ -227,8 +233,8 @@ class SecureTokenStorage {
|
||||
/// Vérifie si le stockage sécurisé est disponible
|
||||
Future<bool> isAvailable() async {
|
||||
try {
|
||||
await _storage.containsKey(key: 'test');
|
||||
return true;
|
||||
final prefs = await _prefs;
|
||||
return prefs.containsKey('test');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class AppConstants {
|
||||
// API Configuration
|
||||
static const String baseUrl = 'http://localhost:8099'; // Backend UnionFlow
|
||||
static const String apiVersion = '/api/v1';
|
||||
static const String baseUrl = 'http://192.168.1.13:8080'; // Backend UnionFlow
|
||||
static const String apiVersion = '/api';
|
||||
|
||||
// Timeout
|
||||
static const Duration connectTimeout = Duration(seconds: 30);
|
||||
|
||||
@@ -4,42 +4,60 @@
|
||||
// InjectableConfigGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: unnecessary_lambdas
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
// ignore_for_file: type=lint
|
||||
// coverage:ignore-file
|
||||
|
||||
import 'package:get_it/get_it.dart' as _i1;
|
||||
import 'package:injectable/injectable.dart' as _i2;
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:get_it/get_it.dart' as _i174;
|
||||
import 'package:injectable/injectable.dart' as _i526;
|
||||
import 'package:unionflow_mobile_apps/core/auth/bloc/auth_bloc.dart' as _i635;
|
||||
import 'package:unionflow_mobile_apps/core/auth/services/auth_api_service.dart'
|
||||
as _i705;
|
||||
import 'package:unionflow_mobile_apps/core/auth/services/auth_service.dart'
|
||||
as _i423;
|
||||
import 'package:unionflow_mobile_apps/core/auth/storage/secure_token_storage.dart'
|
||||
as _i394;
|
||||
import 'package:unionflow_mobile_apps/core/network/auth_interceptor.dart'
|
||||
as _i772;
|
||||
import 'package:unionflow_mobile_apps/core/network/dio_client.dart' as _i978;
|
||||
import 'package:unionflow_mobile_apps/core/services/api_service.dart' as _i238;
|
||||
import 'package:unionflow_mobile_apps/features/members/data/repositories/membre_repository_impl.dart'
|
||||
as _i108;
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart'
|
||||
as _i930;
|
||||
import 'package:unionflow_mobile_apps/features/members/presentation/bloc/membres_bloc.dart'
|
||||
as _i41;
|
||||
|
||||
import '../auth/bloc/auth_bloc.dart' as _i7;
|
||||
import '../auth/services/auth_api_service.dart' as _i4;
|
||||
import '../auth/services/auth_service.dart' as _i6;
|
||||
import '../auth/storage/secure_token_storage.dart' as _i3;
|
||||
import '../network/auth_interceptor.dart' as _i5;
|
||||
import '../network/dio_client.dart' as _i8;
|
||||
|
||||
extension GetItInjectableX on _i1.GetIt {
|
||||
// initializes the registration of main-scope dependencies inside of GetIt
|
||||
Future<_i1.GetIt> init({
|
||||
extension GetItInjectableX on _i174.GetIt {
|
||||
// initializes the registration of main-scope dependencies inside of GetIt
|
||||
_i174.GetIt init({
|
||||
String? environment,
|
||||
_i2.EnvironmentFilter? environmentFilter,
|
||||
}) async {
|
||||
final gh = _i2.GetItHelper(
|
||||
_i526.EnvironmentFilter? environmentFilter,
|
||||
}) {
|
||||
final gh = _i526.GetItHelper(
|
||||
this,
|
||||
environment,
|
||||
environmentFilter,
|
||||
);
|
||||
gh.singleton<_i3.SecureTokenStorage>(() => _i3.SecureTokenStorage());
|
||||
gh.singleton<_i8.DioClient>(() => _i8.DioClient());
|
||||
gh.singleton<_i5.AuthInterceptor>(() => _i5.AuthInterceptor(gh<_i3.SecureTokenStorage>()));
|
||||
gh.singleton<_i4.AuthApiService>(() => _i4.AuthApiService(gh<_i8.DioClient>()));
|
||||
gh.singleton<_i6.AuthService>(() => _i6.AuthService(
|
||||
gh<_i3.SecureTokenStorage>(),
|
||||
gh<_i4.AuthApiService>(),
|
||||
gh<_i5.AuthInterceptor>(),
|
||||
gh<_i8.DioClient>(),
|
||||
));
|
||||
gh.singleton<_i7.AuthBloc>(() => _i7.AuthBloc(gh<_i6.AuthService>()));
|
||||
gh.singleton<_i394.SecureTokenStorage>(() => _i394.SecureTokenStorage());
|
||||
gh.singleton<_i978.DioClient>(() => _i978.DioClient());
|
||||
gh.singleton<_i705.AuthApiService>(
|
||||
() => _i705.AuthApiService(gh<_i978.DioClient>()));
|
||||
gh.singleton<_i238.ApiService>(
|
||||
() => _i238.ApiService(gh<_i978.DioClient>()));
|
||||
gh.singleton<_i772.AuthInterceptor>(
|
||||
() => _i772.AuthInterceptor(gh<_i394.SecureTokenStorage>()));
|
||||
gh.lazySingleton<_i930.MembreRepository>(
|
||||
() => _i108.MembreRepositoryImpl(gh<_i238.ApiService>()));
|
||||
gh.factory<_i41.MembresBloc>(
|
||||
() => _i41.MembresBloc(gh<_i930.MembreRepository>()));
|
||||
gh.singleton<_i423.AuthService>(() => _i423.AuthService(
|
||||
gh<_i394.SecureTokenStorage>(),
|
||||
gh<_i705.AuthApiService>(),
|
||||
gh<_i772.AuthInterceptor>(),
|
||||
gh<_i978.DioClient>(),
|
||||
));
|
||||
gh.singleton<_i635.AuthBloc>(() => _i635.AuthBloc(gh<_i423.AuthService>()));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ final GetIt getIt = GetIt.instance;
|
||||
/// Configure l'injection de dépendances
|
||||
@InjectableInit()
|
||||
Future<void> configureDependencies() async {
|
||||
await getIt.init();
|
||||
getIt.init();
|
||||
}
|
||||
|
||||
/// Réinitialise les dépendances (utile pour les tests)
|
||||
|
||||
186
unionflow-mobile-apps/lib/core/models/membre_model.dart
Normal file
186
unionflow-mobile-apps/lib/core/models/membre_model.dart
Normal file
@@ -0,0 +1,186 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'membre_model.g.dart';
|
||||
|
||||
/// Modèle de données pour un membre UnionFlow
|
||||
/// Aligné avec MembreDTO du serveur API
|
||||
@JsonSerializable()
|
||||
class MembreModel extends Equatable {
|
||||
/// ID unique du membre
|
||||
final String? id;
|
||||
|
||||
/// Numéro unique du membre (format: UF-YYYY-XXXXXXXX)
|
||||
@JsonKey(name: 'numeroMembre')
|
||||
final String numeroMembre;
|
||||
|
||||
/// Nom de famille du membre
|
||||
final String nom;
|
||||
|
||||
/// Prénom du membre
|
||||
final String prenom;
|
||||
|
||||
/// Adresse email
|
||||
final String email;
|
||||
|
||||
/// Numéro de téléphone
|
||||
final String telephone;
|
||||
|
||||
/// Date de naissance
|
||||
@JsonKey(name: 'dateNaissance')
|
||||
final DateTime? dateNaissance;
|
||||
|
||||
/// Adresse complète
|
||||
final String? adresse;
|
||||
|
||||
/// Ville
|
||||
final String? ville;
|
||||
|
||||
/// Code postal
|
||||
@JsonKey(name: 'codePostal')
|
||||
final String? codePostal;
|
||||
|
||||
/// Pays
|
||||
final String? pays;
|
||||
|
||||
/// Profession
|
||||
final String? profession;
|
||||
|
||||
/// Statut du membre (ACTIF, INACTIF, SUSPENDU)
|
||||
final String statut;
|
||||
|
||||
/// Date d'adhésion
|
||||
@JsonKey(name: 'dateAdhesion')
|
||||
final DateTime dateAdhesion;
|
||||
|
||||
/// Date de création
|
||||
@JsonKey(name: 'dateCreation')
|
||||
final DateTime dateCreation;
|
||||
|
||||
/// Date de dernière modification
|
||||
@JsonKey(name: 'dateModification')
|
||||
final DateTime? dateModification;
|
||||
|
||||
/// Indique si le membre est actif
|
||||
final bool actif;
|
||||
|
||||
/// Version pour optimistic locking
|
||||
final int version;
|
||||
|
||||
const MembreModel({
|
||||
this.id,
|
||||
required this.numeroMembre,
|
||||
required this.nom,
|
||||
required this.prenom,
|
||||
required this.email,
|
||||
required this.telephone,
|
||||
this.dateNaissance,
|
||||
this.adresse,
|
||||
this.ville,
|
||||
this.codePostal,
|
||||
this.pays,
|
||||
this.profession,
|
||||
required this.statut,
|
||||
required this.dateAdhesion,
|
||||
required this.dateCreation,
|
||||
this.dateModification,
|
||||
required this.actif,
|
||||
required this.version,
|
||||
});
|
||||
|
||||
/// Constructeur depuis JSON
|
||||
factory MembreModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$MembreModelFromJson(json);
|
||||
|
||||
/// Conversion vers JSON
|
||||
Map<String, dynamic> toJson() => _$MembreModelToJson(this);
|
||||
|
||||
/// Nom complet du membre
|
||||
String get nomComplet => '$prenom $nom';
|
||||
|
||||
/// Initiales du membre
|
||||
String get initiales {
|
||||
final prenomInitial = prenom.isNotEmpty ? prenom[0].toUpperCase() : '';
|
||||
final nomInitial = nom.isNotEmpty ? nom[0].toUpperCase() : '';
|
||||
return '$prenomInitial$nomInitial';
|
||||
}
|
||||
|
||||
/// Adresse complète formatée
|
||||
String get adresseComplete {
|
||||
final parts = <String>[];
|
||||
if (adresse?.isNotEmpty == true) parts.add(adresse!);
|
||||
if (ville?.isNotEmpty == true) parts.add(ville!);
|
||||
if (codePostal?.isNotEmpty == true) parts.add(codePostal!);
|
||||
if (pays?.isNotEmpty == true) parts.add(pays!);
|
||||
return parts.join(', ');
|
||||
}
|
||||
|
||||
/// Copie avec modifications
|
||||
MembreModel copyWith({
|
||||
String? id,
|
||||
String? numeroMembre,
|
||||
String? nom,
|
||||
String? prenom,
|
||||
String? email,
|
||||
String? telephone,
|
||||
DateTime? dateNaissance,
|
||||
String? adresse,
|
||||
String? ville,
|
||||
String? codePostal,
|
||||
String? pays,
|
||||
String? profession,
|
||||
String? statut,
|
||||
DateTime? dateAdhesion,
|
||||
DateTime? dateCreation,
|
||||
DateTime? dateModification,
|
||||
bool? actif,
|
||||
int? version,
|
||||
}) {
|
||||
return MembreModel(
|
||||
id: id ?? this.id,
|
||||
numeroMembre: numeroMembre ?? this.numeroMembre,
|
||||
nom: nom ?? this.nom,
|
||||
prenom: prenom ?? this.prenom,
|
||||
email: email ?? this.email,
|
||||
telephone: telephone ?? this.telephone,
|
||||
dateNaissance: dateNaissance ?? this.dateNaissance,
|
||||
adresse: adresse ?? this.adresse,
|
||||
ville: ville ?? this.ville,
|
||||
codePostal: codePostal ?? this.codePostal,
|
||||
pays: pays ?? this.pays,
|
||||
profession: profession ?? this.profession,
|
||||
statut: statut ?? this.statut,
|
||||
dateAdhesion: dateAdhesion ?? this.dateAdhesion,
|
||||
dateCreation: dateCreation ?? this.dateCreation,
|
||||
dateModification: dateModification ?? this.dateModification,
|
||||
actif: actif ?? this.actif,
|
||||
version: version ?? this.version,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
numeroMembre,
|
||||
nom,
|
||||
prenom,
|
||||
email,
|
||||
telephone,
|
||||
dateNaissance,
|
||||
adresse,
|
||||
ville,
|
||||
codePostal,
|
||||
pays,
|
||||
profession,
|
||||
statut,
|
||||
dateAdhesion,
|
||||
dateCreation,
|
||||
dateModification,
|
||||
actif,
|
||||
version,
|
||||
];
|
||||
|
||||
@override
|
||||
String toString() => 'MembreModel(id: $id, numeroMembre: $numeroMembre, '
|
||||
'nomComplet: $nomComplet, email: $email, statut: $statut)';
|
||||
}
|
||||
54
unionflow-mobile-apps/lib/core/models/membre_model.g.dart
Normal file
54
unionflow-mobile-apps/lib/core/models/membre_model.g.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'membre_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
MembreModel _$MembreModelFromJson(Map<String, dynamic> json) => MembreModel(
|
||||
id: json['id'] as String?,
|
||||
numeroMembre: json['numeroMembre'] as String,
|
||||
nom: json['nom'] as String,
|
||||
prenom: json['prenom'] as String,
|
||||
email: json['email'] as String,
|
||||
telephone: json['telephone'] as String,
|
||||
dateNaissance: json['dateNaissance'] == null
|
||||
? null
|
||||
: DateTime.parse(json['dateNaissance'] as String),
|
||||
adresse: json['adresse'] as String?,
|
||||
ville: json['ville'] as String?,
|
||||
codePostal: json['codePostal'] as String?,
|
||||
pays: json['pays'] as String?,
|
||||
profession: json['profession'] as String?,
|
||||
statut: json['statut'] as String,
|
||||
dateAdhesion: DateTime.parse(json['dateAdhesion'] as String),
|
||||
dateCreation: DateTime.parse(json['dateCreation'] as String),
|
||||
dateModification: json['dateModification'] == null
|
||||
? null
|
||||
: DateTime.parse(json['dateModification'] as String),
|
||||
actif: json['actif'] as bool,
|
||||
version: (json['version'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$MembreModelToJson(MembreModel instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'numeroMembre': instance.numeroMembre,
|
||||
'nom': instance.nom,
|
||||
'prenom': instance.prenom,
|
||||
'email': instance.email,
|
||||
'telephone': instance.telephone,
|
||||
'dateNaissance': instance.dateNaissance?.toIso8601String(),
|
||||
'adresse': instance.adresse,
|
||||
'ville': instance.ville,
|
||||
'codePostal': instance.codePostal,
|
||||
'pays': instance.pays,
|
||||
'profession': instance.profession,
|
||||
'statut': instance.statut,
|
||||
'dateAdhesion': instance.dateAdhesion.toIso8601String(),
|
||||
'dateCreation': instance.dateCreation.toIso8601String(),
|
||||
'dateModification': instance.dateModification?.toIso8601String(),
|
||||
'actif': instance.actif,
|
||||
'version': instance.version,
|
||||
};
|
||||
@@ -0,0 +1,206 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'wave_checkout_session_model.g.dart';
|
||||
|
||||
/// Modèle pour les sessions de paiement Wave Money
|
||||
/// Aligné avec WaveCheckoutSessionDTO du serveur API
|
||||
@JsonSerializable()
|
||||
class WaveCheckoutSessionModel extends Equatable {
|
||||
/// ID unique de la session
|
||||
final String? id;
|
||||
|
||||
/// ID de la session Wave (retourné par l'API Wave)
|
||||
@JsonKey(name: 'waveSessionId')
|
||||
final String waveSessionId;
|
||||
|
||||
/// URL de la session de paiement Wave
|
||||
@JsonKey(name: 'waveUrl')
|
||||
final String? waveUrl;
|
||||
|
||||
/// Montant du paiement
|
||||
final double montant;
|
||||
|
||||
/// Devise (XOF pour la Côte d'Ivoire)
|
||||
final String devise;
|
||||
|
||||
/// URL de succès (redirection après paiement réussi)
|
||||
@JsonKey(name: 'successUrl')
|
||||
final String successUrl;
|
||||
|
||||
/// URL d'erreur (redirection après échec)
|
||||
@JsonKey(name: 'errorUrl')
|
||||
final String errorUrl;
|
||||
|
||||
/// Statut de la session
|
||||
final String statut;
|
||||
|
||||
/// ID de l'organisation qui effectue le paiement
|
||||
@JsonKey(name: 'organisationId')
|
||||
final String? organisationId;
|
||||
|
||||
/// Nom de l'organisation
|
||||
@JsonKey(name: 'nomOrganisation')
|
||||
final String? nomOrganisation;
|
||||
|
||||
/// ID du membre qui effectue le paiement
|
||||
@JsonKey(name: 'membreId')
|
||||
final String? membreId;
|
||||
|
||||
/// Nom du membre
|
||||
@JsonKey(name: 'nomMembre')
|
||||
final String? nomMembre;
|
||||
|
||||
/// Type de paiement (COTISATION, ADHESION, AIDE, EVENEMENT)
|
||||
@JsonKey(name: 'typePaiement')
|
||||
final String? typePaiement;
|
||||
|
||||
/// Description du paiement
|
||||
final String? description;
|
||||
|
||||
/// Référence externe
|
||||
@JsonKey(name: 'referenceExterne')
|
||||
final String? referenceExterne;
|
||||
|
||||
/// Date de création
|
||||
@JsonKey(name: 'dateCreation')
|
||||
final DateTime dateCreation;
|
||||
|
||||
/// Date d'expiration
|
||||
@JsonKey(name: 'dateExpiration')
|
||||
final DateTime? dateExpiration;
|
||||
|
||||
/// Date de dernière modification
|
||||
@JsonKey(name: 'dateModification')
|
||||
final DateTime? dateModification;
|
||||
|
||||
/// Indique si la session est active
|
||||
final bool actif;
|
||||
|
||||
/// Version pour optimistic locking
|
||||
final int version;
|
||||
|
||||
const WaveCheckoutSessionModel({
|
||||
this.id,
|
||||
required this.waveSessionId,
|
||||
this.waveUrl,
|
||||
required this.montant,
|
||||
required this.devise,
|
||||
required this.successUrl,
|
||||
required this.errorUrl,
|
||||
required this.statut,
|
||||
this.organisationId,
|
||||
this.nomOrganisation,
|
||||
this.membreId,
|
||||
this.nomMembre,
|
||||
this.typePaiement,
|
||||
this.description,
|
||||
this.referenceExterne,
|
||||
required this.dateCreation,
|
||||
this.dateExpiration,
|
||||
this.dateModification,
|
||||
required this.actif,
|
||||
required this.version,
|
||||
});
|
||||
|
||||
/// Constructeur depuis JSON
|
||||
factory WaveCheckoutSessionModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$WaveCheckoutSessionModelFromJson(json);
|
||||
|
||||
/// Conversion vers JSON
|
||||
Map<String, dynamic> toJson() => _$WaveCheckoutSessionModelToJson(this);
|
||||
|
||||
/// Montant formaté avec devise
|
||||
String get montantFormate => '${montant.toStringAsFixed(0)} $devise';
|
||||
|
||||
/// Indique si la session est expirée
|
||||
bool get estExpiree {
|
||||
if (dateExpiration == null) return false;
|
||||
return DateTime.now().isAfter(dateExpiration!);
|
||||
}
|
||||
|
||||
/// Indique si la session est en attente
|
||||
bool get estEnAttente => statut == 'PENDING' || statut == 'EN_ATTENTE';
|
||||
|
||||
/// Indique si la session est réussie
|
||||
bool get estReussie => statut == 'SUCCESS' || statut == 'REUSSIE';
|
||||
|
||||
/// Indique si la session a échoué
|
||||
bool get aEchoue => statut == 'FAILED' || statut == 'ECHEC';
|
||||
|
||||
/// Copie avec modifications
|
||||
WaveCheckoutSessionModel copyWith({
|
||||
String? id,
|
||||
String? waveSessionId,
|
||||
String? waveUrl,
|
||||
double? montant,
|
||||
String? devise,
|
||||
String? successUrl,
|
||||
String? errorUrl,
|
||||
String? statut,
|
||||
String? organisationId,
|
||||
String? nomOrganisation,
|
||||
String? membreId,
|
||||
String? nomMembre,
|
||||
String? typePaiement,
|
||||
String? description,
|
||||
String? referenceExterne,
|
||||
DateTime? dateCreation,
|
||||
DateTime? dateExpiration,
|
||||
DateTime? dateModification,
|
||||
bool? actif,
|
||||
int? version,
|
||||
}) {
|
||||
return WaveCheckoutSessionModel(
|
||||
id: id ?? this.id,
|
||||
waveSessionId: waveSessionId ?? this.waveSessionId,
|
||||
waveUrl: waveUrl ?? this.waveUrl,
|
||||
montant: montant ?? this.montant,
|
||||
devise: devise ?? this.devise,
|
||||
successUrl: successUrl ?? this.successUrl,
|
||||
errorUrl: errorUrl ?? this.errorUrl,
|
||||
statut: statut ?? this.statut,
|
||||
organisationId: organisationId ?? this.organisationId,
|
||||
nomOrganisation: nomOrganisation ?? this.nomOrganisation,
|
||||
membreId: membreId ?? this.membreId,
|
||||
nomMembre: nomMembre ?? this.nomMembre,
|
||||
typePaiement: typePaiement ?? this.typePaiement,
|
||||
description: description ?? this.description,
|
||||
referenceExterne: referenceExterne ?? this.referenceExterne,
|
||||
dateCreation: dateCreation ?? this.dateCreation,
|
||||
dateExpiration: dateExpiration ?? this.dateExpiration,
|
||||
dateModification: dateModification ?? this.dateModification,
|
||||
actif: actif ?? this.actif,
|
||||
version: version ?? this.version,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
waveSessionId,
|
||||
waveUrl,
|
||||
montant,
|
||||
devise,
|
||||
successUrl,
|
||||
errorUrl,
|
||||
statut,
|
||||
organisationId,
|
||||
nomOrganisation,
|
||||
membreId,
|
||||
nomMembre,
|
||||
typePaiement,
|
||||
description,
|
||||
referenceExterne,
|
||||
dateCreation,
|
||||
dateExpiration,
|
||||
dateModification,
|
||||
actif,
|
||||
version,
|
||||
];
|
||||
|
||||
@override
|
||||
String toString() => 'WaveCheckoutSessionModel(id: $id, '
|
||||
'waveSessionId: $waveSessionId, montant: $montantFormate, '
|
||||
'statut: $statut, typePaiement: $typePaiement)';
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'wave_checkout_session_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
WaveCheckoutSessionModel _$WaveCheckoutSessionModelFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
WaveCheckoutSessionModel(
|
||||
id: json['id'] as String?,
|
||||
waveSessionId: json['waveSessionId'] as String,
|
||||
waveUrl: json['waveUrl'] as String?,
|
||||
montant: (json['montant'] as num).toDouble(),
|
||||
devise: json['devise'] as String,
|
||||
successUrl: json['successUrl'] as String,
|
||||
errorUrl: json['errorUrl'] as String,
|
||||
statut: json['statut'] as String,
|
||||
organisationId: json['organisationId'] as String?,
|
||||
nomOrganisation: json['nomOrganisation'] as String?,
|
||||
membreId: json['membreId'] as String?,
|
||||
nomMembre: json['nomMembre'] as String?,
|
||||
typePaiement: json['typePaiement'] as String?,
|
||||
description: json['description'] as String?,
|
||||
referenceExterne: json['referenceExterne'] as String?,
|
||||
dateCreation: DateTime.parse(json['dateCreation'] as String),
|
||||
dateExpiration: json['dateExpiration'] == null
|
||||
? null
|
||||
: DateTime.parse(json['dateExpiration'] as String),
|
||||
dateModification: json['dateModification'] == null
|
||||
? null
|
||||
: DateTime.parse(json['dateModification'] as String),
|
||||
actif: json['actif'] as bool,
|
||||
version: (json['version'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$WaveCheckoutSessionModelToJson(
|
||||
WaveCheckoutSessionModel instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'waveSessionId': instance.waveSessionId,
|
||||
'waveUrl': instance.waveUrl,
|
||||
'montant': instance.montant,
|
||||
'devise': instance.devise,
|
||||
'successUrl': instance.successUrl,
|
||||
'errorUrl': instance.errorUrl,
|
||||
'statut': instance.statut,
|
||||
'organisationId': instance.organisationId,
|
||||
'nomOrganisation': instance.nomOrganisation,
|
||||
'membreId': instance.membreId,
|
||||
'nomMembre': instance.nomMembre,
|
||||
'typePaiement': instance.typePaiement,
|
||||
'description': instance.description,
|
||||
'referenceExterne': instance.referenceExterne,
|
||||
'dateCreation': instance.dateCreation.toIso8601String(),
|
||||
'dateExpiration': instance.dateExpiration?.toIso8601String(),
|
||||
'dateModification': instance.dateModification?.toIso8601String(),
|
||||
'actif': instance.actif,
|
||||
'version': instance.version,
|
||||
};
|
||||
@@ -19,7 +19,7 @@ class DioClient {
|
||||
void _configureOptions() {
|
||||
_dio.options = BaseOptions(
|
||||
// URL de base de l'API
|
||||
baseUrl: 'http://localhost:8081', // Adresse de votre API Quarkus
|
||||
baseUrl: 'http://192.168.1.13:8080', // Adresse de votre API Quarkus
|
||||
|
||||
// Timeouts
|
||||
connectTimeout: const Duration(seconds: 30),
|
||||
|
||||
214
unionflow-mobile-apps/lib/core/services/api_service.dart
Normal file
214
unionflow-mobile-apps/lib/core/services/api_service.dart
Normal file
@@ -0,0 +1,214 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../models/membre_model.dart';
|
||||
import '../models/wave_checkout_session_model.dart';
|
||||
import '../network/dio_client.dart';
|
||||
|
||||
/// Service API principal pour communiquer avec le serveur UnionFlow
|
||||
@singleton
|
||||
class ApiService {
|
||||
final DioClient _dioClient;
|
||||
|
||||
ApiService(this._dioClient);
|
||||
|
||||
Dio get _dio => _dioClient.dio;
|
||||
|
||||
// ========================================
|
||||
// MEMBRES
|
||||
// ========================================
|
||||
|
||||
/// Récupère la liste de tous les membres actifs
|
||||
Future<List<MembreModel>> getMembres() async {
|
||||
try {
|
||||
final response = await _dio.get('/api/membres');
|
||||
|
||||
if (response.data is List) {
|
||||
return (response.data as List)
|
||||
.map((json) => MembreModel.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
throw Exception('Format de réponse invalide pour la liste des membres');
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la récupération des membres');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère un membre par son ID
|
||||
Future<MembreModel> getMembreById(String id) async {
|
||||
try {
|
||||
final response = await _dio.get('/api/membres/$id');
|
||||
return MembreModel.fromJson(response.data as Map<String, dynamic>);
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la récupération du membre');
|
||||
}
|
||||
}
|
||||
|
||||
/// Crée un nouveau membre
|
||||
Future<MembreModel> createMembre(MembreModel membre) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
'/api/membres',
|
||||
data: membre.toJson(),
|
||||
);
|
||||
return MembreModel.fromJson(response.data as Map<String, dynamic>);
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la création du membre');
|
||||
}
|
||||
}
|
||||
|
||||
/// Met à jour un membre existant
|
||||
Future<MembreModel> updateMembre(String id, MembreModel membre) async {
|
||||
try {
|
||||
final response = await _dio.put(
|
||||
'/api/membres/$id',
|
||||
data: membre.toJson(),
|
||||
);
|
||||
return MembreModel.fromJson(response.data as Map<String, dynamic>);
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la mise à jour du membre');
|
||||
}
|
||||
}
|
||||
|
||||
/// Désactive un membre
|
||||
Future<void> deleteMembre(String id) async {
|
||||
try {
|
||||
await _dio.delete('/api/membres/$id');
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la suppression du membre');
|
||||
}
|
||||
}
|
||||
|
||||
/// Recherche des membres par nom ou prénom
|
||||
Future<List<MembreModel>> searchMembres(String query) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
'/api/membres/recherche',
|
||||
queryParameters: {'q': query},
|
||||
);
|
||||
|
||||
if (response.data is List) {
|
||||
return (response.data as List)
|
||||
.map((json) => MembreModel.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
throw Exception('Format de réponse invalide pour la recherche');
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la recherche de membres');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère les statistiques des membres
|
||||
Future<Map<String, dynamic>> getMembresStats() async {
|
||||
try {
|
||||
final response = await _dio.get('/api/membres/stats');
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la récupération des statistiques');
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// PAIEMENTS WAVE
|
||||
// ========================================
|
||||
|
||||
/// Crée une session de paiement Wave
|
||||
Future<WaveCheckoutSessionModel> createWaveSession({
|
||||
required double montant,
|
||||
required String devise,
|
||||
required String successUrl,
|
||||
required String errorUrl,
|
||||
String? organisationId,
|
||||
String? membreId,
|
||||
String? typePaiement,
|
||||
String? description,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
'/api/paiements/wave/sessions',
|
||||
data: {
|
||||
'montant': montant,
|
||||
'devise': devise,
|
||||
'successUrl': successUrl,
|
||||
'errorUrl': errorUrl,
|
||||
if (organisationId != null) 'organisationId': organisationId,
|
||||
if (membreId != null) 'membreId': membreId,
|
||||
if (typePaiement != null) 'typePaiement': typePaiement,
|
||||
if (description != null) 'description': description,
|
||||
},
|
||||
);
|
||||
return WaveCheckoutSessionModel.fromJson(response.data as Map<String, dynamic>);
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la création de la session Wave');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère une session de paiement Wave par son ID
|
||||
Future<WaveCheckoutSessionModel> getWaveSession(String sessionId) async {
|
||||
try {
|
||||
final response = await _dio.get('/api/paiements/wave/sessions/$sessionId');
|
||||
return WaveCheckoutSessionModel.fromJson(response.data as Map<String, dynamic>);
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la récupération de la session Wave');
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie le statut d'une session de paiement Wave
|
||||
Future<String> checkWaveSessionStatus(String sessionId) async {
|
||||
try {
|
||||
final response = await _dio.get('/api/paiements/wave/sessions/$sessionId/status');
|
||||
return response.data['statut'] as String;
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e, 'Erreur lors de la vérification du statut Wave');
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// GESTION DES ERREURS
|
||||
// ========================================
|
||||
|
||||
/// Gère les exceptions Dio et les convertit en messages d'erreur appropriés
|
||||
Exception _handleDioException(DioException e, String defaultMessage) {
|
||||
switch (e.type) {
|
||||
case DioExceptionType.connectionTimeout:
|
||||
case DioExceptionType.sendTimeout:
|
||||
case DioExceptionType.receiveTimeout:
|
||||
return Exception('Délai d\'attente dépassé. Vérifiez votre connexion internet.');
|
||||
|
||||
case DioExceptionType.badResponse:
|
||||
final statusCode = e.response?.statusCode;
|
||||
final responseData = e.response?.data;
|
||||
|
||||
if (statusCode == 400) {
|
||||
if (responseData is Map && responseData.containsKey('message')) {
|
||||
return Exception(responseData['message']);
|
||||
}
|
||||
return Exception('Données invalides');
|
||||
} else if (statusCode == 401) {
|
||||
return Exception('Non autorisé. Veuillez vous reconnecter.');
|
||||
} else if (statusCode == 403) {
|
||||
return Exception('Accès interdit');
|
||||
} else if (statusCode == 404) {
|
||||
return Exception('Ressource non trouvée');
|
||||
} else if (statusCode == 500) {
|
||||
return Exception('Erreur serveur. Veuillez réessayer plus tard.');
|
||||
}
|
||||
|
||||
return Exception('$defaultMessage (Code: $statusCode)');
|
||||
|
||||
case DioExceptionType.cancel:
|
||||
return Exception('Requête annulée');
|
||||
|
||||
case DioExceptionType.connectionError:
|
||||
return Exception('Erreur de connexion. Vérifiez votre connexion internet.');
|
||||
|
||||
case DioExceptionType.badCertificate:
|
||||
return Exception('Certificat SSL invalide');
|
||||
|
||||
case DioExceptionType.unknown:
|
||||
default:
|
||||
return Exception(defaultMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user