import 'package:flutter/material.dart'; import 'dart:async'; import 'package:qpay/http/http.dart'; import 'package:qpay/models/api_response.dart'; import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/widgets/app_snack_bar.dart'; import 'package:provider/provider.dart'; class GatewayModel { bool isLoading = false; String status = ''; String? errorMessage; bool isCancelled = false; int count = 0; } class GatewayController extends ChangeNotifier { final GatewayModel model = GatewayModel(); final Http http = Http(); Timer? _pollTimer; // Add timer field BuildContext context; late TransactionController transactionController; GatewayController(this.context) { transactionController = Provider.of( context, listen: false, ); } void _showErrorSnackBar(String? message) { WidgetsBinding.instance.addPostFrameCallback((_) { AppSnackBar.showError(context, message.toString()); }); } void startLoading() { model.isLoading = true; model.status = ''; notifyListeners(); } void finishLoading() { model.isLoading = false; notifyListeners(); } Future>> poll(String uid) async { startLoading(); try { dynamic workflowResponse = await http.get('/public/transaction/poll/$uid'); logger.i(workflowResponse.toString()); dynamic response = workflowResponse['body']; model.status = response['status']; transactionController.model.paymentStatus = response['paymentStatus']; finishLoading(); if (model.status == 'SUCCESS') { transactionController.updateReceiptData(response); } notifyListeners(); return ApiResponse.success(Map.from(response)); } catch (e) { finishLoading(); model.status = 'FAILED'; model.errorMessage = "Network error. Please try again or contact support"; logger.e(e); notifyListeners(); return ApiResponse.failure( "Network error. Please try again or contact support", ); } } Future>> pollTransaction(String uid) async { // poll up to 30 times (~5 minutes at 10-second intervals) int maxAttempts = 30; int attempt = 0; while (!model.isCancelled && attempt < maxAttempts) { attempt++; // Wait 10 seconds before each poll request await Future.delayed(const Duration(seconds: 10)); // Check again after delay in case it was cancelled during the delay if (model.isCancelled) break; ApiResponse> result = await poll(uid); // Only stop on success or failure if (result.isSuccess) { String status = model.status; // If status is SUCCESS, we're done if (status == 'SUCCESS') { return result; } // Otherwise (e.g. PENDING), continue polling } else { // Network error — show failure UI and stop return result; } } // Timed out after max attempts model.isCancelled = true; model.status = 'FAILED'; model.errorMessage = 'We failed to check your transaction status'; finishLoading(); notifyListeners(); return ApiResponse.failure("Polling timed out"); } // Alternative method using Timer instead of while loop Future>> pollTransactionWithTimer( String uid) async { _pollTimer = Timer.periodic(const Duration(seconds: 5), (timer) async { if (model.isCancelled) { timer.cancel(); return; } try { dynamic response = await http.get('/public/transaction/poll/$uid'); logger.i(response.toString()); if (response['status'] == 'SUCCESS') { model.status = response['status']; transactionController.updateReceiptData(response); notifyListeners(); timer.cancel(); // Stop polling on success } } catch (e) { logger.e(e); _showErrorSnackBar( "Network error. Please try again or contact support", ); } }); // This returns immediately; the caller should use the timer callback approach. // Consider refactoring this to use poll(String uid) directly for a consistent API. return ApiResponse.failure( "Polling via timer started; use a different flow for the result"); } @override void dispose() { model.isCancelled = true; _pollTimer?.cancel(); // Cancel timer if it exists super.dispose(); } }