Fix: Corriger le type PDO dans StripeService et retirer getConnection()
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,31 +0,0 @@
|
||||
Extension Discovery Cache
|
||||
=========================
|
||||
|
||||
This folder is used by `package:extension_discovery` to cache lists of
|
||||
packages that contains extensions for other packages.
|
||||
|
||||
DO NOT USE THIS FOLDER
|
||||
----------------------
|
||||
|
||||
* Do not read (or rely) the contents of this folder.
|
||||
* Do write to this folder.
|
||||
|
||||
If you're interested in the lists of extensions stored in this folder use the
|
||||
API offered by package `extension_discovery` to get this information.
|
||||
|
||||
If this package doesn't work for your use-case, then don't try to read the
|
||||
contents of this folder. It may change, and will not remain stable.
|
||||
|
||||
Use package `extension_discovery`
|
||||
---------------------------------
|
||||
|
||||
If you want to access information from this folder.
|
||||
|
||||
Feel free to delete this folder
|
||||
-------------------------------
|
||||
|
||||
Files in this folder act as a cache, and the cache is discarded if the files
|
||||
are older than the modification time of `.dart_tool/package_config.json`.
|
||||
|
||||
Hence, it should never be necessary to clear this cache manually, if you find a
|
||||
need to do please file a bug.
|
||||
@@ -1 +0,0 @@
|
||||
{"version":2,"entries":[{"package":"geosector_app","rootUri":"../","packageUri":"lib/"}]}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1944,6 +1944,7 @@ file:///home/pierre/dev/geosector/app/lib/core/services/hive_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/hive_web_fix.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/location_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/logger_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/stripe_connect_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/sync_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/theme_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/theme/app_theme.dart
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1943,6 +1943,7 @@ file:///home/pierre/dev/geosector/app/lib/core/services/hive_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/hive_web_fix.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/location_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/logger_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/stripe_connect_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/sync_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/theme_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/theme/app_theme.dart
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "geosector_app",
|
||||
"version": "3.2.0+320",
|
||||
"version": "3.2.1+321",
|
||||
"dependencies": [
|
||||
"connectivity_plus",
|
||||
"cupertino_icons",
|
||||
|
||||
File diff suppressed because one or more lines are too long
213
app/lib/core/services/stripe_connect_service.dart
Normal file
213
app/lib/core/services/stripe_connect_service.dart
Normal file
@@ -0,0 +1,213 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
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';
|
||||
|
||||
/// Service pour gérer Stripe Connect dans l'application
|
||||
class StripeConnectService {
|
||||
final ApiService apiService;
|
||||
|
||||
StripeConnectService({required this.apiService});
|
||||
|
||||
/// Créer un compte Stripe Connect pour une amicale
|
||||
/// Retourne l'URL d'onboarding ou null si erreur
|
||||
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(
|
||||
'/stripe/accounts',
|
||||
data: {'fk_entite': amicale.id},
|
||||
);
|
||||
|
||||
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';
|
||||
|
||||
// Si le compte existe déjà, récupérer l'account_id
|
||||
if (error.toString().contains('existe déjà')) {
|
||||
final accountId = createResponse.data?['account_id'];
|
||||
debugPrint(' Compte existant détecté, account_id: $accountId');
|
||||
if (accountId != null) {
|
||||
return await getOnboardingLink(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('❌ Erreur création compte: $error');
|
||||
throw Exception(error);
|
||||
}
|
||||
|
||||
final accountId = createResponse.data?['account_id'];
|
||||
debugPrint('✅ Compte créé/récupéré: $accountId');
|
||||
|
||||
if (accountId == null) {
|
||||
throw Exception('account_id non retourné par l\'API');
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
// 3. Obtenir le lien d'onboarding
|
||||
return await getOnboardingLink(accountId);
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur StripeConnect: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtenir le lien d'onboarding pour finaliser la configuration
|
||||
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://dapp.geosector.fr'; // À adapter selon l'environnement
|
||||
final returnUrl = Uri.encodeFull('$baseUrl/stripe/success');
|
||||
final refreshUrl = Uri.encodeFull('$baseUrl/stripe/refresh');
|
||||
|
||||
debugPrint(' Return URL: $returnUrl');
|
||||
debugPrint(' Refresh URL: $refreshUrl');
|
||||
|
||||
final response = await apiService.post(
|
||||
'/stripe/accounts/$accountId/onboarding-link',
|
||||
data: {
|
||||
'return_url': returnUrl,
|
||||
'refresh_url': refreshUrl,
|
||||
},
|
||||
);
|
||||
|
||||
debugPrint(' Response status: ${response.statusCode}');
|
||||
debugPrint(' Response data: ${response.data}');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final url = response.data['url'];
|
||||
debugPrint('✅ Lien onboarding généré: $url');
|
||||
return url;
|
||||
}
|
||||
|
||||
final errorMessage = response.data?['message'] ?? 'Erreur inconnue';
|
||||
throw Exception('Impossible de générer le lien: $errorMessage');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur onboarding link: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifier le statut du compte Stripe d'une amicale
|
||||
Future<StripeAccountStatus> checkAccountStatus(int amicaleId) async {
|
||||
try {
|
||||
final response = await apiService.get('/stripe/accounts/$amicaleId/status');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
return StripeAccountStatus(
|
||||
hasAccount: data['has_account'] ?? false,
|
||||
accountId: data['account_id'],
|
||||
chargesEnabled: data['charges_enabled'] ?? false,
|
||||
payoutsEnabled: data['payouts_enabled'] ?? false,
|
||||
onboardingCompleted: data['onboarding_completed'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
return StripeAccountStatus(hasAccount: false);
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur vérification statut: $e');
|
||||
return StripeAccountStatus(hasAccount: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ouvrir le Dashboard Stripe pour l'amicale
|
||||
Future<void> openStripeDashboard(String? accountId) async {
|
||||
if (accountId == null || accountId.isEmpty) return;
|
||||
|
||||
// URL du dashboard Stripe Express
|
||||
final url = Uri.parse('https://dashboard.stripe.com/express/$accountId');
|
||||
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
/// Lancer le processus d'onboarding dans un navigateur externe
|
||||
Future<bool> launchOnboarding(String url) async {
|
||||
try {
|
||||
debugPrint('🚀 Lancement onboarding URL: $url');
|
||||
debugPrint(' Plateforme: ${kIsWeb ? "Web" : "Mobile"}');
|
||||
|
||||
final uri = Uri.parse(url);
|
||||
|
||||
// Sur web, on peut directement lancer sans vérification
|
||||
if (kIsWeb) {
|
||||
final launched = await launchUrl(
|
||||
uri,
|
||||
webOnlyWindowName: '_blank', // Ouvre dans un nouvel onglet
|
||||
);
|
||||
debugPrint(launched ? '✅ URL lancée avec succès' : '❌ Échec du lancement');
|
||||
return launched;
|
||||
} else {
|
||||
// Sur mobile, vérifier d'abord si on peut lancer l'URL
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
debugPrint('✅ URL lancée avec succès');
|
||||
return true;
|
||||
} else {
|
||||
debugPrint('❌ Impossible de lancer l\'URL sur mobile');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur lancement onboarding: $e');
|
||||
debugPrint(' Détails: $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Statut d'un compte Stripe Connect
|
||||
class StripeAccountStatus {
|
||||
final bool hasAccount;
|
||||
final String? accountId;
|
||||
final bool chargesEnabled;
|
||||
final bool payoutsEnabled;
|
||||
final bool onboardingCompleted;
|
||||
|
||||
StripeAccountStatus({
|
||||
required this.hasAccount,
|
||||
this.accountId,
|
||||
this.chargesEnabled = false,
|
||||
this.payoutsEnabled = false,
|
||||
this.onboardingCompleted = false,
|
||||
});
|
||||
|
||||
bool get canAcceptPayments => chargesEnabled && payoutsEnabled;
|
||||
|
||||
String get statusMessage {
|
||||
if (!hasAccount) return 'Pas de compte Stripe';
|
||||
if (!onboardingCompleted) return 'Configuration en cours';
|
||||
if (!chargesEnabled) return 'Paiements non activés';
|
||||
if (!payoutsEnabled) return 'Virements non activés';
|
||||
return 'Compte actif';
|
||||
}
|
||||
|
||||
Color get statusColor {
|
||||
if (canAcceptPayments) return Colors.green;
|
||||
if (hasAccount) return Colors.orange;
|
||||
return Colors.red;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/services/stripe_connect_service.dart';
|
||||
import 'package:geosector_app/presentation/widgets/mapbox_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
@@ -66,6 +67,11 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
XFile? _selectedImage;
|
||||
String? _logoUrl;
|
||||
|
||||
// Pour Stripe Connect
|
||||
StripeConnectService? _stripeService;
|
||||
bool _isCheckingStripeStatus = false;
|
||||
StripeAccountStatus? _stripeStatus;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -97,6 +103,159 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
_chkUserDeletePass = amicale?.chkUserDeletePass ?? false;
|
||||
|
||||
// Note : Le logo sera chargé dynamiquement depuis l'API
|
||||
|
||||
// Initialiser le service Stripe si API disponible
|
||||
if (widget.apiService != null) {
|
||||
_stripeService = StripeConnectService(apiService: widget.apiService!);
|
||||
// Vérifier le statut Stripe si l'amicale a déjà un compte
|
||||
if (_chkStripe && widget.amicale != null) {
|
||||
_checkStripeStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier le statut du compte Stripe
|
||||
Future<void> _checkStripeStatus() async {
|
||||
if (_stripeService == null || widget.amicale == null) return;
|
||||
|
||||
setState(() => _isCheckingStripeStatus = true);
|
||||
|
||||
try {
|
||||
final status = await _stripeService!.checkAccountStatus(widget.amicale!.id);
|
||||
setState(() {
|
||||
_stripeStatus = status;
|
||||
if (status.accountId != null) {
|
||||
_stripeIdController.text = status.accountId!;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('Erreur vérification statut Stripe: $e');
|
||||
} finally {
|
||||
setState(() => _isCheckingStripeStatus = false);
|
||||
}
|
||||
}
|
||||
|
||||
// Configurer Stripe Connect
|
||||
Future<void> _configureStripe() async {
|
||||
if (_stripeService == null || widget.amicale == null) return;
|
||||
|
||||
// Vérifier que nous sommes sur Web
|
||||
if (!kIsWeb) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Configuration Web requise'),
|
||||
content: const Text(
|
||||
'La configuration du compte Stripe doit être effectuée depuis un navigateur web.\n\n'
|
||||
'Veuillez vous connecter depuis un ordinateur pour configurer les paiements par carte bancaire.',
|
||||
),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Compris'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher un dialog de confirmation
|
||||
final bool? confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Configuration Stripe'),
|
||||
content: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Vous allez être redirigé vers Stripe pour :'),
|
||||
SizedBox(height: 8),
|
||||
Text('• Créer votre compte marchand'),
|
||||
Text('• Configurer vos informations bancaires'),
|
||||
Text('• Activer les paiements par carte'),
|
||||
SizedBox(height: 16),
|
||||
Text('Ce processus prend environ 5-10 minutes.'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('Continuer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirm != true) return;
|
||||
|
||||
// Afficher le loading
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const AlertDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SizedBox(height: 16),
|
||||
Text('Préparation de votre compte Stripe...'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
// Créer ou récupérer le lien d'onboarding
|
||||
final url = await _stripeService!.createStripeAccount(widget.amicale!);
|
||||
|
||||
// Fermer le loading
|
||||
if (mounted) Navigator.of(context).pop();
|
||||
|
||||
if (url != null) {
|
||||
// Lancer l'onboarding
|
||||
final success = await _stripeService!.launchOnboarding(url);
|
||||
|
||||
if (success) {
|
||||
// Activer la checkbox
|
||||
setState(() => _chkStripe = true);
|
||||
|
||||
// Afficher un message de succès
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Configuration Stripe lancée. Revenez ici après avoir terminé.'),
|
||||
duration: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier le statut après un délai
|
||||
Future.delayed(const Duration(seconds: 5), _checkStripeStatus);
|
||||
}
|
||||
} else {
|
||||
throw Exception('Impossible de créer le lien de configuration');
|
||||
}
|
||||
} catch (e) {
|
||||
// Fermer le loading si encore ouvert
|
||||
if (mounted && Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
// Afficher l'erreur
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur: ${e.toString()}'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -744,7 +903,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
}
|
||||
|
||||
// Construire le formulaire principal
|
||||
Widget _buildMainForm(ThemeData theme, bool restrictedFieldsReadOnly) {
|
||||
Widget _buildMainForm(ThemeData theme, bool restrictedFieldsReadOnly, bool stripeReadOnly) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -976,78 +1135,133 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Stripe Checkbox et Stripe ID sur la même ligne
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
// Stripe Checkbox et configuration
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Checkbox Stripe
|
||||
Checkbox(
|
||||
value: _chkStripe,
|
||||
onChanged: restrictedFieldsReadOnly
|
||||
? null
|
||||
: (value) {
|
||||
if (value == true) {
|
||||
// Afficher une boîte de dialogue de confirmation
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Confirmation'),
|
||||
content: const Text(
|
||||
'En acceptant les règlements par carte bancaire, des commissions de 1.4% seront prélevées sur les montants encaissés. Souhaitez-vous continuer ?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// L'utilisateur a répondu "non"
|
||||
setState(() {
|
||||
_chkStripe = false;
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Non'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// L'utilisateur a répondu "oui"
|
||||
setState(() {
|
||||
_chkStripe = true;
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF20335E),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Oui'),
|
||||
),
|
||||
],
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Checkbox Stripe
|
||||
Checkbox(
|
||||
value: _chkStripe,
|
||||
onChanged: stripeReadOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkStripe = value ?? false;
|
||||
});
|
||||
// Si on active la checkbox et qu'on a une amicale, vérifier le statut
|
||||
if (value == true && widget.amicale != null) {
|
||||
_checkStripeStatus();
|
||||
}
|
||||
},
|
||||
activeColor: const Color(0xFF20335E),
|
||||
),
|
||||
Text(
|
||||
"Accepte les règlements en CB",
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Bouton Configuration Stripe si checkbox cochée
|
||||
if (_chkStripe && !stripeReadOnly && widget.amicale != null) ...[
|
||||
ElevatedButton.icon(
|
||||
onPressed: _isCheckingStripeStatus ? null : _configureStripe,
|
||||
icon: _isCheckingStripeStatus
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: Icon(kIsWeb ? Icons.settings : Icons.computer, size: 18),
|
||||
label: Text(
|
||||
!kIsWeb
|
||||
? 'Config. Web requise'
|
||||
: (_stripeStatus?.canAcceptPayments == true
|
||||
? 'Compte actif'
|
||||
: 'Configurer Stripe')
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: !kIsWeb
|
||||
? Colors.grey
|
||||
: (_stripeStatus?.statusColor ?? Colors.orange),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Indicateur de statut
|
||||
if (_stripeStatus != null)
|
||||
Tooltip(
|
||||
message: _stripeStatus!.statusMessage,
|
||||
child: Icon(
|
||||
_stripeStatus!.canAcceptPayments
|
||||
? Icons.check_circle
|
||||
: Icons.warning_amber_rounded,
|
||||
color: _stripeStatus!.statusColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
// Stripe ID (sur une ligne séparée)
|
||||
const SizedBox(height: 8),
|
||||
CustomTextField(
|
||||
controller: _stripeIdController,
|
||||
label: "ID Compte Stripe",
|
||||
readOnly: true,
|
||||
helperText: _chkStripe
|
||||
? (_stripeStatus?.accountId != null
|
||||
? "Compte Stripe Connect: ${_stripeStatus!.accountId}"
|
||||
: "L'ID sera généré automatiquement lors de la configuration")
|
||||
: "Activez les paiements CB pour configurer Stripe",
|
||||
),
|
||||
// Message d'information sur les commissions
|
||||
if (_chkStripe)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: _stripeStatus?.statusColor.withOpacity(0.1) ?? Colors.orange.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: _stripeStatus?.statusColor.withOpacity(0.3) ?? Colors.orange.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
_stripeStatus?.canAcceptPayments == true
|
||||
? Icons.check_circle_outline
|
||||
: Icons.info_outline,
|
||||
color: _stripeStatus?.statusColor ?? Colors.orange,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_stripeStatus?.canAcceptPayments == true
|
||||
? "✅ Compte Stripe configuré - Commission plateforme: 2.5% (min 0.50€)"
|
||||
: _stripeStatus?.onboardingCompleted == false
|
||||
? "⏳ Configuration Stripe en cours. Veuillez compléter le processus d'onboarding."
|
||||
: "💳 Les paiements CB seront soumis à une commission de 2.5% (min 0.50€)",
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Si l'utilisateur décoche la case, pas besoin de confirmation
|
||||
setState(() {
|
||||
_chkStripe = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
activeColor: const Color(0xFF20335E),
|
||||
),
|
||||
Text(
|
||||
"Accepte les règlements en CB",
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Stripe ID
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _stripeIdController,
|
||||
label: "ID Stripe Paiements CB",
|
||||
readOnly: restrictedFieldsReadOnly,
|
||||
helperText: "Les règlements par CB sont taxés d'une commission de 1.4%",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -1251,9 +1465,15 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
|
||||
// Déterminer si l'utilisateur peut modifier les champs restreints
|
||||
final bool canEditRestrictedFields = userRole > 2;
|
||||
|
||||
// Pour Stripe, les admins d'amicale (rôle 2) peuvent aussi configurer
|
||||
final bool canEditStripe = userRole >= 2;
|
||||
|
||||
// Lecture seule pour les champs restreints si l'utilisateur n'a pas les droits
|
||||
final bool restrictedFieldsReadOnly = widget.readOnly || !canEditRestrictedFields;
|
||||
|
||||
// Lecture seule spécifique pour Stripe
|
||||
final bool stripeReadOnly = widget.readOnly || !canEditStripe;
|
||||
|
||||
// Calculer la largeur maximale du formulaire pour les écrans larges
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
@@ -1281,7 +1501,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Formulaire principal
|
||||
_buildMainForm(theme, restrictedFieldsReadOnly),
|
||||
_buildMainForm(theme, restrictedFieldsReadOnly, stripeReadOnly),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,8 +3,8 @@ FLUTTER_ROOT=/home/pierre/dev/flutter
|
||||
FLUTTER_APPLICATION_PATH=/home/pierre/dev/geosector/app
|
||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||
FLUTTER_BUILD_DIR=build
|
||||
FLUTTER_BUILD_NAME=3.2.0
|
||||
FLUTTER_BUILD_NUMBER=320
|
||||
FLUTTER_BUILD_NAME=3.2.1
|
||||
FLUTTER_BUILD_NUMBER=321
|
||||
FLUTTER_CLI_BUILD_MODE=debug
|
||||
DART_OBFUSCATION=false
|
||||
TRACK_WIDGET_CREATION=true
|
||||
|
||||
@@ -4,8 +4,8 @@ export "FLUTTER_ROOT=/home/pierre/dev/flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=/home/pierre/dev/geosector/app"
|
||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
export "FLUTTER_BUILD_NAME=3.2.0"
|
||||
export "FLUTTER_BUILD_NUMBER=320"
|
||||
export "FLUTTER_BUILD_NAME=3.2.1"
|
||||
export "FLUTTER_BUILD_NUMBER=321"
|
||||
export "FLUTTER_CLI_BUILD_MODE=debug"
|
||||
export "DART_OBFUSCATION=false"
|
||||
export "TRACK_WIDGET_CREATION=true"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: geosector_app
|
||||
description: 'GEOSECTOR - Gestion de distribution des calendriers par secteurs géographiques pour les amicales de pompiers'
|
||||
publish_to: 'none'
|
||||
version: 3.2.0+320
|
||||
version: 3.2.1+321
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
Reference in New Issue
Block a user