import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'dart:async'; import '../../core/models/chit_group.dart'; import '../../core/models/group_member.dart'; import '../../core/services/api_service.dart'; /// UPI QR Payment Dialog /// Shows QR code for direct UPI payment + auto-checks for payment confirmation class UPIQRPaymentDialog extends StatefulWidget { final ChitGroup group; final GroupMember member; final int month; final int year; const UPIQRPaymentDialog({ super.key, required this.group, required this.member, required this.month, required this.year, }); @override State createState() => _UPIQRPaymentDialogState(); } class _UPIQRPaymentDialogState extends State { final _apiService = ApiService(); bool _isLoading = true; bool _isCheckingStatus = false; String? _error; String? _qrCodeData; String? _upiId; String? _upiReference; double? _amount; Timer? _statusCheckTimer; int _checkCount = 0; static const int _maxChecks = 60; // Check for 5 minutes (60 x 5 seconds) @override void initState() { super.initState(); _loadQRCode(); } @override void dispose() { _statusCheckTimer?.cancel(); super.dispose(); } Future _loadQRCode() async { try { setState(() { _isLoading = true; _error = null; }); final response = await _apiService.get( '/payments/phonepe/qr/${widget.group.id}/${widget.month}/${widget.year}', ); if (response['success'] == true) { final data = response['data']; setState(() { _qrCodeData = data['qr_code_data']; _upiId = data['upi_id']; _upiReference = data['upi_reference']; _amount = double.tryParse(data['amount'].toString()) ?? 0.0; _isLoading = false; }); // Start checking for payment status _startStatusChecking(); } else { setState(() { _error = response['message'] ?? 'Failed to load QR code'; _isLoading = false; }); } } catch (e) { setState(() { _error = 'Error loading QR code: $e'; _isLoading = false; }); } } void _startStatusChecking() { _statusCheckTimer = Timer.periodic( const Duration(seconds: 5), (timer) async { if (_checkCount >= _maxChecks || !mounted) { timer.cancel(); return; } _checkCount++; await _checkPaymentStatus(); }, ); } Future _checkPaymentStatus() async { if (_isCheckingStatus || _upiReference == null) return; try { setState(() { _isCheckingStatus = true; }); final response = await _apiService.get( '/payments/phonepe/status/$_upiReference', ); if (response['success'] == true && response['data']['status'] == 'success') { // Payment confirmed! _statusCheckTimer?.cancel(); if (mounted) { Get.snackbar( 'Payment Confirmed!', 'Your payment has been received and recorded', backgroundColor: Colors.green.shade100, colorText: Colors.green.shade800, icon: Icon(Icons.check_circle, color: Colors.green.shade600), duration: const Duration(seconds: 4), ); Navigator.of(context).pop(true); // Return success } } } catch (e) { // Silently fail - will check again print('Status check error: $e'); } finally { if (mounted) { setState(() { _isCheckingStatus = false; }); } } } void _copyToClipboard(String text, String label) { Clipboard.setData(ClipboardData(text: text)); Get.snackbar( 'Copied!', '$label copied to clipboard', backgroundColor: Colors.blue.shade50, colorText: Colors.blue.shade900, duration: const Duration(seconds: 2), snackPosition: SnackPosition.BOTTOM, ); } @override Widget build(BuildContext context) { final monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), ), child: Container( width: double.infinity, constraints: BoxConstraints( maxWidth: 450.w, maxHeight: 0.9.sh, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header Container( padding: EdgeInsets.all(20.w), decoration: BoxDecoration( gradient: LinearGradient( colors: [ const Color(0xFF5F259F), const Color(0xFF7E3BB4), ], ), borderRadius: BorderRadius.only( topLeft: Radius.circular(16.r), topRight: Radius.circular(16.r), ), ), child: Row( children: [ Container( padding: EdgeInsets.all(8.w), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8.r), ), child: Icon( Icons.qr_code_2, color: Colors.white, size: 24.w, ), ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Pay via UPI', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, color: Colors.white, ), ), Text( 'Scan QR or use UPI ID', style: TextStyle( fontSize: 14.sp, color: Colors.white.withOpacity(0.9), ), ), ], ), ), IconButton( onPressed: () => Navigator.of(context).pop(), icon: Icon( Icons.close, size: 24.w, color: Colors.white, ), ), ], ), ), // Content Flexible( child: SingleChildScrollView( padding: EdgeInsets.all(20.w), child: _isLoading ? _buildLoadingState() : _error != null ? _buildErrorState() : _buildQRContent(monthNames), ), ), // Footer with status if (!_isLoading && _error == null) Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: _isCheckingStatus ? Colors.orange.shade50 : Colors.blue.shade50, border: Border( top: BorderSide(color: Colors.grey.shade200), ), ), child: Row( children: [ if (_isCheckingStatus) SizedBox( width: 16.w, height: 16.h, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Colors.orange.shade600, ), ), ) else Icon( Icons.info_outline, size: 16.w, color: Colors.blue.shade700, ), SizedBox(width: 8.w), Expanded( child: Text( _isCheckingStatus ? 'Checking for payment...' : 'Payment will be auto-detected once completed', style: TextStyle( fontSize: 12.sp, color: _isCheckingStatus ? Colors.orange.shade800 : Colors.blue.shade700, ), ), ), ], ), ), ], ), ), ); } Widget _buildLoadingState() { return Center( child: Padding( padding: EdgeInsets.all(40.w), child: Column( children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( const Color(0xFF5F259F), ), ), SizedBox(height: 16.h), Text( 'Generating QR Code...', style: TextStyle( fontSize: 14.sp, color: Colors.grey.shade600, ), ), ], ), ), ); } Widget _buildErrorState() { return Center( child: Padding( padding: EdgeInsets.all(20.w), child: Column( children: [ Icon( Icons.error_outline, size: 48.w, color: Colors.red.shade400, ), SizedBox(height: 16.h), Text( _error!, textAlign: TextAlign.center, style: TextStyle( fontSize: 14.sp, color: Colors.red.shade700, ), ), SizedBox(height: 16.h), ElevatedButton.icon( onPressed: _loadQRCode, icon: Icon(Icons.refresh, size: 20.w), label: Text('Retry'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red.shade600, foregroundColor: Colors.white, ), ), ], ), ), ); } Widget _buildQRContent(List monthNames) { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Payment Details Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(12.r), border: Border.all(color: Colors.grey.shade200), ), child: Column( children: [ _buildInfoRow('Group', widget.group.name), _buildInfoRow('Month', '${monthNames[widget.month - 1]} ${widget.year}'), Divider(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Amount', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w700, color: Colors.grey.shade800, ), ), Text( '₹${_amount?.toStringAsFixed(2)}', style: TextStyle( fontSize: 24.sp, fontWeight: FontWeight.w800, color: Colors.green.shade700, ), ), ], ), ], ), ), SizedBox(height: 24.h), // QR Code Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8.r, offset: Offset(0, 2.h), ), ], ), child: Column( children: [ QrImageView( data: _qrCodeData!, version: QrVersions.auto, size: 200.w, backgroundColor: Colors.white, ), SizedBox(height: 12.h), Text( 'Scan with any UPI app', style: TextStyle( fontSize: 14.sp, color: Colors.grey.shade600, fontWeight: FontWeight.w500, ), ), ], ), ), SizedBox(height: 24.h), // UPI ID Container( padding: EdgeInsets.all(12.w), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8.r), border: Border.all(color: Colors.blue.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Or pay using UPI ID', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.blue.shade900, ), ), SizedBox(height: 8.h), Row( children: [ Expanded( child: SelectableText( _upiId!, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w700, color: Colors.blue.shade700, fontFamily: 'monospace', ), ), ), IconButton( onPressed: () => _copyToClipboard(_upiId!, 'UPI ID'), icon: Icon(Icons.copy, size: 20.w), style: IconButton.styleFrom( backgroundColor: Colors.blue.shade100, ), tooltip: 'Copy UPI ID', ), ], ), ], ), ), SizedBox(height: 16.h), // Reference Number Container( padding: EdgeInsets.all(12.w), decoration: BoxDecoration( color: Colors.orange.shade50, borderRadius: BorderRadius.circular(8.r), border: Border.all(color: Colors.orange.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.warning_amber_rounded, size: 18.w, color: Colors.orange.shade700, ), SizedBox(width: 8.w), Text( 'Important: Add this reference', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.orange.shade900, ), ), ], ), SizedBox(height: 8.h), Row( children: [ Expanded( child: SelectableText( _upiReference!, style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w700, color: Colors.orange.shade700, fontFamily: 'monospace', ), ), ), IconButton( onPressed: () => _copyToClipboard(_upiReference!, 'Reference'), icon: Icon(Icons.copy, size: 20.w), style: IconButton.styleFrom( backgroundColor: Colors.orange.shade100, ), tooltip: 'Copy Reference', ), ], ), SizedBox(height: 4.h), Text( 'Add this in remarks/note for auto-matching', style: TextStyle( fontSize: 12.sp, color: Colors.orange.shade700, ), ), ], ), ), SizedBox(height: 24.h), // Instructions Container( padding: EdgeInsets.all(12.w), decoration: BoxDecoration( color: Colors.green.shade50, borderRadius: BorderRadius.circular(8.r), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'How to pay:', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.green.shade900, ), ), SizedBox(height: 8.h), _buildInstructionItem('1. Open any UPI app (PhonePe, GPay, Paytm, etc.)'), _buildInstructionItem('2. Scan the QR code above'), _buildInstructionItem('3. Or enter the UPI ID manually'), _buildInstructionItem('4. Add the reference number in remarks'), _buildInstructionItem('5. Complete the payment'), SizedBox(height: 8.h), Text( '✓ Payment will be auto-detected within seconds!', style: TextStyle( fontSize: 13.sp, fontWeight: FontWeight.w600, color: Colors.green.shade700, ), ), ], ), ), ], ); } Widget _buildInfoRow(String label, String value) { return Padding( padding: EdgeInsets.symmetric(vertical: 4.h), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: 14.sp, color: Colors.grey.shade700, ), ), Flexible( child: Text( value, style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.grey.shade900, ), overflow: TextOverflow.ellipsis, ), ), ], ), ); } Widget _buildInstructionItem(String text) { return Padding( padding: EdgeInsets.only(bottom: 4.h), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('• ', style: TextStyle(fontSize: 14.sp, color: Colors.green.shade700)), Expanded( child: Text( text, style: TextStyle( fontSize: 12.sp, color: Colors.green.shade700, height: 1.4, ), ), ), ], ), ); } }