Files
velocity-pay-flutter/lib/screens/gateway/gateway_controller.dart
2026-06-29 16:57:39 +02:00

176 lines
5.0 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:qpay/widgets/app_snack_bar.dart';
import 'package:provider/provider.dart';
class GatewayModel {
bool isLoading = false;
String status = 'PENDING';
String pollStatus = 'PENDING';
String? errorMessage;
bool isCancelled = false;
int count = 0;
int pollAttempt = 0;
int pollMaxAttempts = 30;
}
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((_) {
AppSnackBar.showError(context, message.toString());
});
}
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['pollingStatus'];
transactionController.model.paymentStatus = response['paymentStatus'];
finishLoading();
if (model.status == 'SUCCESS') {
transactionController.updateReceiptData(response);
} else {
model.status = 'FAILED';
model.errorMessage = response['errorMessage'];
return ApiResponse.failure(response['errorMessage']);
}
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 15 times (~45 seconds at 3-second intervals)
int attempt = 0;
model.pollAttempt = 0;
notifyListeners();
while (!model.isCancelled && attempt < model.pollMaxAttempts) {
attempt++;
model.pollAttempt = attempt;
notifyListeners();
ApiResponse<Map<String, dynamic>> result = await poll(uid);
// Only stop on success or failure
if (result.isSuccess) {
// If status is SUCCESS, we're done
if (model.status == 'SUCCESS') {
model.isCancelled = true;
model.pollStatus = model.status;
finishLoading();
notifyListeners();
return result;
}
await Future.delayed(const Duration(seconds: 3));
if (model.isCancelled) break;
// Otherwise (e.g. PENDING), continue polling
} else {
await Future.delayed(const Duration(seconds: 3));
if (model.isCancelled) break;
model.status = 'PENDING';
}
notifyListeners();
}
// Timed out after max attempts
model.isCancelled = true;
model.status = 'FAILED';
model.pollStatus = '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();
}
}