import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../../core/api/api_response.dart'; import '../../../core/time/server_time.dart'; import '../../../core/theme/app_colors.dart'; import '../data/wallet_repository.dart'; const _presetAmounts = [10.0, 20.0, 50.0]; const _minAmount = 5.0; const _maxAmount = 150.0; /// Écran de rechargement du portefeuille via Stripe Payment Sheet. class WalletTopUpScreen extends ConsumerStatefulWidget { const WalletTopUpScreen({super.key}); @override ConsumerState createState() => _WalletTopUpScreenState(); } class _WalletTopUpScreenState extends ConsumerState { final _amountController = TextEditingController(); double? _selectedPreset; bool _isProcessing = false; @override void dispose() { _amountController.dispose(); super.dispose(); } double? get _amount { if (_selectedPreset != null) { return _selectedPreset; } final raw = _amountController.text.trim().replaceAll(',', '.'); if (raw.isEmpty) { return null; } return double.tryParse(raw); } String? _validateAmount(double? amount) { if (amount == null) { return 'Saisissez un montant'; } if (amount < _minAmount || amount > _maxAmount) { return 'Montant entre $_minAmount € et $_maxAmount €'; } return null; } bool get _canPay => !_isProcessing && _validateAmount(_amount) == null; Future _pay() async { final amount = _amount; final validationError = _validateAmount(amount); if (validationError != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(validationError)), ); return; } setState(() => _isProcessing = true); try { final idempotencyKey = 'topup-${ServerTime.now().millisecondsSinceEpoch}'; final payment = await ref.read(walletRepositoryProvider).initiateTopUp( amount: amount!, idempotencyKey: idempotencyKey, ); if (payment.provider == 'stripe') { final stripe = payment.stripe; if (stripe == null) { throw StateError('Réponse Stripe incomplète'); } Stripe.publishableKey = stripe.publishableKey; await Stripe.instance.applySettings(); await Stripe.instance.initPaymentSheet( paymentSheetParameters: SetupPaymentSheetParameters( paymentIntentClientSecret: stripe.clientSecret, merchantDisplayName: 'Laverie Connectée', ), ); await Stripe.instance.presentPaymentSheet(); } final result = await ref.read(walletRepositoryProvider).confirmTopUp( paymentUuid: payment.uuid, ); ref.invalidate(walletProvider); ref.invalidate(walletTransactionsProvider); if (!mounted) return; final currency = NumberFormat.currency(locale: 'fr_FR', symbol: '€'); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Rechargement confirmé — nouveau solde : ${currency.format(result.balance)}', ), backgroundColor: AppColors.success, ), ); context.pop(); } on StripeException catch (e) { if (!mounted) return; final message = e.error.localizedMessage ?? 'Paiement annulé'; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); } on DioException catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(ApiResponse.errorMessage(e, fallback: 'Erreur de paiement')), backgroundColor: AppColors.error, ), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('$e'), backgroundColor: AppColors.error, ), ); } finally { if (mounted) { setState(() => _isProcessing = false); } } } @override Widget build(BuildContext context) { final currency = NumberFormat.currency(locale: 'fr_FR', symbol: '€'); return Scaffold( backgroundColor: AppColors.background, appBar: AppBar(title: const Text('Recharger')), body: ListView( padding: const EdgeInsets.all(16), children: [ Text( 'Choisissez le montant à ajouter à votre portefeuille.', style: Theme.of(context).textTheme.bodyLarge, ), const SizedBox(height: 20), Wrap( spacing: 10, runSpacing: 10, children: _presetAmounts.map((amount) { final selected = _selectedPreset == amount; return ChoiceChip( label: Text(currency.format(amount)), selected: selected, onSelected: _isProcessing ? null : (value) { setState(() { _selectedPreset = value ? amount : null; if (value) { _amountController.clear(); } }); }, ); }).toList(), ), const SizedBox(height: 24), Text('Ou saisissez un montant', style: Theme.of(context).textTheme.titleSmall), const SizedBox(height: 8), TextField( controller: _amountController, enabled: !_isProcessing, keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9.,]')), ], decoration: InputDecoration( suffixText: '€', hintText: 'Ex. 15', helperText: 'Entre ${currency.format(_minAmount)} et ${currency.format(_maxAmount)}', border: const OutlineInputBorder(), ), onChanged: (_) => setState(() => _selectedPreset = null), ), const SizedBox(height: 32), FilledButton.icon( onPressed: _canPay ? _pay : null, icon: _isProcessing ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.lock_outline), label: Text(_isProcessing ? 'Paiement en cours…' : 'Payer'), ), const SizedBox(height: 16), Center( child: Text( 'Paiement sécurisé par Stripe', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), ), ], ), ); } }