import 'package:flutter/material.dart'; import 'package:geosector_app/core/data/models/passage_model.dart'; import 'package:geosector_app/core/services/stripe_connect_service.dart'; import 'package:geosector_app/core/services/device_info_service.dart'; import 'package:geosector_app/core/utils/api_exception.dart'; import 'package:geosector_app/core/repositories/passage_repository.dart'; import 'package:geosector_app/presentation/widgets/qr_code_payment_dialog.dart'; /// Dialog de sélection de la méthode de paiement CB /// Affiche les options QR Code et/ou Tap to Pay selon la compatibilité class PaymentMethodSelectionDialog extends StatefulWidget { final PassageModel passage; final double amount; final String habitantName; final StripeConnectService stripeConnectService; final PassageRepository? passageRepository; final VoidCallback? onTapToPaySelected; final VoidCallback? onQRCodeCompleted; const PaymentMethodSelectionDialog({ super.key, required this.passage, required this.amount, required this.habitantName, required this.stripeConnectService, this.passageRepository, this.onTapToPaySelected, this.onQRCodeCompleted, }); @override State createState() => _PaymentMethodSelectionDialogState(); /// Afficher le dialog de sélection de méthode de paiement static Future show({ required BuildContext context, required PassageModel passage, required double amount, required String habitantName, required StripeConnectService stripeConnectService, PassageRepository? passageRepository, VoidCallback? onTapToPaySelected, VoidCallback? onQRCodeCompleted, }) { return showDialog( context: context, barrierDismissible: false, // Ne peut pas fermer en cliquant à côté builder: (context) => PaymentMethodSelectionDialog( passage: passage, amount: amount, habitantName: habitantName, stripeConnectService: stripeConnectService, passageRepository: passageRepository, onTapToPaySelected: onTapToPaySelected, onQRCodeCompleted: onQRCodeCompleted, ), ); } } class _PaymentMethodSelectionDialogState extends State { String? _tapToPayUnavailableReason; bool _isCheckingNFC = true; @override void initState() { super.initState(); _checkTapToPayAvailability(); } Future _checkTapToPayAvailability() async { final reason = await DeviceInfoService.instance.getTapToPayUnavailableReasonAsync(); setState(() { _tapToPayUnavailableReason = reason; _isCheckingNFC = false; }); } @override Widget build(BuildContext context) { final canUseTapToPay = !_isCheckingNFC && _tapToPayUnavailableReason == null; final amountEuros = widget.amount.toStringAsFixed(2); return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Container( constraints: const BoxConstraints(maxWidth: 450), padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ // En-tête const Text( 'Règlement CB', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), // Informations du paiement Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200), ), child: Column( children: [ Row( children: [ const Icon(Icons.person, color: Colors.blue), const SizedBox(width: 12), Expanded( child: Text( widget.habitantName, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ), ], ), const Divider(height: 24), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.euro, color: Colors.blue, size: 28, ), const SizedBox(width: 8), Text( amountEuros, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: Colors.blue, ), ), ], ), ], ), ), const SizedBox(height: 32), // Titre section méthodes const Text( 'Sélectionnez une méthode de paiement :', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 16), // Bouton QR Code _buildPaymentButton( context: context, icon: Icons.qr_code_2, label: 'Paiement par QR Code', description: 'Le client scanne le code avec son téléphone', onPressed: () => _handleQRCodePayment(context), color: Colors.blue, isEnabled: true, ), const SizedBox(height: 12), // Bouton Tap to Pay (toujours affiché, désactivé si non disponible) _buildPaymentButton( context: context, icon: Icons.contactless, label: _isCheckingNFC ? 'Tap to Pay (vérification...)' : 'Tap to Pay', description: canUseTapToPay ? 'Paiement sans contact sur cet appareil' : _tapToPayUnavailableReason ?? 'Vérification en cours...', onPressed: canUseTapToPay ? () { Navigator.of(context).pop(); widget.onTapToPaySelected?.call(); } : null, color: Colors.green, isEnabled: canUseTapToPay, ), const SizedBox(height: 24), // Logo Stripe Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.security, color: Colors.grey.shade600, size: 16, ), const SizedBox(width: 8), Text( 'Paiements sécurisés par Stripe', style: TextStyle( fontSize: 12, color: Colors.grey.shade600, fontStyle: FontStyle.italic, ), ), ], ), ], ), ), ); } Widget _buildPaymentButton({ required BuildContext context, required IconData icon, required String label, required String description, required VoidCallback? onPressed, required Color color, required bool isEnabled, }) { // Couleurs selon l'état activé/désactivé final effectiveColor = isEnabled ? color : Colors.grey; final backgroundColor = isEnabled ? color.withOpacity(0.1) : Colors.grey.shade100; final borderColor = isEnabled ? color.withOpacity(0.3) : Colors.grey.shade300; return InkWell( onTap: isEnabled ? onPressed : null, borderRadius: BorderRadius.circular(12), child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: backgroundColor, border: Border.all(color: borderColor, width: 2), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: isEnabled ? color.withOpacity(0.2) : Colors.grey.shade200, borderRadius: BorderRadius.circular(8), ), child: Icon( icon, color: effectiveColor, size: 32, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( label, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: effectiveColor, ), ), ), if (!isEnabled) Icon( Icons.lock_outline, color: Colors.grey.shade600, size: 20, ), ], ), const SizedBox(height: 4), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!isEnabled) ...[ Icon( Icons.warning_amber_rounded, color: Colors.orange.shade700, size: 16, ), const SizedBox(width: 4), ], Expanded( child: Text( description, style: TextStyle( fontSize: 13, color: isEnabled ? Colors.grey.shade700 : Colors.orange.shade700, fontWeight: isEnabled ? FontWeight.normal : FontWeight.w500, ), ), ), ], ), ], ), ), if (isEnabled) Icon(Icons.arrow_forward_ios, color: effectiveColor, size: 20), ], ), ), ); } /// Gérer le paiement par QR Code Future _handleQRCodePayment(BuildContext context) async { // Sauvegarder le navigator avant de fermer les dialogs final navigator = Navigator.of(context); bool loaderDisplayed = false; try { // Afficher un loader showDialog( context: context, barrierDismissible: false, builder: (context) => const Center( child: CircularProgressIndicator(), ), ); loaderDisplayed = true; // Créer le Payment Link final amountInCents = (widget.amount * 100).round(); debugPrint('🔵 Création Payment Link : ${amountInCents} cents, passage ${widget.passage.id}'); final paymentLink = await widget.stripeConnectService.createPaymentLink( amountInCents: amountInCents, passageId: widget.passage.id, description: 'Calendrier pompiers - ${widget.habitantName}', metadata: { 'passage_id': widget.passage.id.toString(), 'habitant_name': widget.habitantName, 'adresse': '${widget.passage.numero} ${widget.passage.rue}, ${widget.passage.ville}', }, ); debugPrint('🔵 Payment Link reçu : ${paymentLink != null ? "OK" : "NULL"}'); if (paymentLink != null) { debugPrint(' URL: ${paymentLink.url}'); debugPrint(' ID: ${paymentLink.paymentLinkId}'); } if (paymentLink == null) { throw Exception('Impossible de créer le lien de paiement'); } // Sauvegarder l'URL du Payment Link dans le passage if (widget.passageRepository != null) { try { debugPrint('🔵 Sauvegarde de l\'URL du Payment Link dans le passage...'); final updatedPassage = widget.passage.copyWith( stripePaymentLinkUrl: paymentLink.url, ); await widget.passageRepository!.updatePassage(updatedPassage); debugPrint('✅ URL du Payment Link sauvegardée'); } catch (e) { debugPrint('⚠️ Erreur lors de la sauvegarde de l\'URL: $e'); // On continue quand même, ce n'est pas bloquant } } // Fermer le loader navigator.pop(); loaderDisplayed = false; debugPrint('🔵 Loader fermé'); // Fermer le dialog de sélection (seulement en cas de succès) navigator.pop(); debugPrint('🔵 Dialog de sélection fermé'); // Attendre un frame pour que les dialogs soient bien fermés await Future.delayed(const Duration(milliseconds: 100)); // Afficher le QR Code avec le navigator root debugPrint('🔵 Ouverture dialog QR Code...'); await showDialog( context: navigator.context, barrierDismissible: true, builder: (context) => QRCodePaymentDialog( paymentLink: paymentLink, ), ); debugPrint('🔵 Dialog QR Code affiché'); // Notifier que le QR Code est complété widget.onQRCodeCompleted?.call(); debugPrint('✅ Callback onQRCodeCompleted appelé'); } catch (e, stack) { debugPrint('❌ Erreur dans _handleQRCodePayment: $e'); debugPrint(' Stack: $stack'); // Fermer le loader si encore ouvert if (loaderDisplayed) { try { navigator.pop(); } catch (_) {} } // Afficher l'erreur (le dialogue de sélection reste ouvert) if (context.mounted) { ApiException.showError(context, e); } } } }