Files
velocity-pay-flutter/lib/screens/gateway/gateway_controller.dart

162 lines
4.6 KiB
Dart

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: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<TransactionController>(
context,
listen: false,
);
}
void _showErrorSnackBar(String? message) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message.toString()),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.deepOrange,
),
);
});
}
void startLoading() {
model.isLoading = true;
model.status = '';
notifyListeners();
}
void finishLoading() {
model.isLoading = false;
notifyListeners();
}
Future<ApiResponse<Map<String, dynamic>>> 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<String, dynamic>.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<ApiResponse<Map<String, dynamic>>> 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<Map<String, dynamic>> 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<ApiResponse<Map<String, dynamic>>> 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();
}
}