From d8e3b88b3eff433187e32f5968ca7b91c1d8eb30 Mon Sep 17 00:00:00 2001 From: Vusumuzi Khoza Date: Wed, 22 Oct 2025 23:17:56 +0200 Subject: [PATCH] aligning logic to backend workflows --- lib/main.dart | 2 +- lib/screens/confirm/confirm_controller.dart | 19 ++- lib/screens/gateway/gateway_controller.dart | 7 +- lib/screens/home/home_screen.dart | 14 ++- lib/screens/pay/pay_controller.dart | 6 +- lib/screens/pay/pay_screen.dart | 4 +- lib/screens/receipt/receipt_screen.dart | 124 ++++++++++++++++++-- 7 files changed, 151 insertions(+), 25 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d4c92be..86ca056 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -79,7 +79,7 @@ class _MyAppState extends State { themeMode: ThemeMode.light, // Force light mode regardless of OS setting debugShowCheckedModeBanner: false, routerConfig: GoRouter( - initialLocation: '/', + initialLocation: '/receipt', routes: [ ShellRoute( builder: (context, state, child) { diff --git a/lib/screens/confirm/confirm_controller.dart b/lib/screens/confirm/confirm_controller.dart index 591c946..a9fa3cb 100644 --- a/lib/screens/confirm/confirm_controller.dart +++ b/lib/screens/confirm/confirm_controller.dart @@ -57,6 +57,7 @@ class ConfirmController extends ChangeNotifier { model.isLoading = true; notifyListeners(); + // todo: find out if we still need to update these fields transactionController .model .formData = transactionController.model.formData.copyWith( @@ -66,11 +67,14 @@ class ConfirmController extends ChangeNotifier { authType: transactionController.model.selectedPaymentProcessor.authType, ); - dynamic response = await http.post( - '/public/transaction/request', - transactionController.model.formData, + dynamic workflowResponse = await http.get( + '/public/transaction/request/' + '${transactionController.model.confirmationData['id']}/' + '${transactionController.model.selectedPaymentProcessor.authType}' ); - logger.i(response.toString()); + logger.i(workflowResponse.toString()); + + dynamic response = workflowResponse['body']; if (response['status'] != 'FAILED') { model.status = response['status']; @@ -133,4 +137,11 @@ class ConfirmController extends ChangeNotifier { await Future.delayed(const Duration(seconds: 5)); } } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + model.isCancelled = true; + } } diff --git a/lib/screens/gateway/gateway_controller.dart b/lib/screens/gateway/gateway_controller.dart index c4f43b0..dd6c86a 100644 --- a/lib/screens/gateway/gateway_controller.dart +++ b/lib/screens/gateway/gateway_controller.dart @@ -58,8 +58,11 @@ class GatewayController extends ChangeNotifier { if (model.isCancelled) break; try { - dynamic response = await http.get('/public/transaction/poll/$uid'); - logger.i(response.toString()); + dynamic workflowResponse = await http.get('/public/transaction/poll/$uid'); + + logger.i(workflowResponse.toString()); + + dynamic response = workflowResponse['body']; if (response['status'] == 'SUCCESS') { model.status = response['status']; diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 710d850..5432d8c 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -101,6 +101,10 @@ class _HomeScreenState extends State }); } + if(prefs.getString("userId") == null) { + prefs.setString("userId", Uuid().v4()); + }; + await homeController.getTransactions(prefs.getString("userId")!); } @@ -197,7 +201,7 @@ class _HomeScreenState extends State actions: [ if(_isLoggedIn) IconButton( - icon: const Icon(Icons.exit_to_app_sharp), + icon: const Icon(Icons.turn_slight_right_sharp), onPressed: () { showDialog( context: context, @@ -525,16 +529,16 @@ class _HomeScreenState extends State ), SizedBox(width: 10), Icon( - e['status'] == 'SUCCESS' + e['integrationStatus'] == 'SUCCESS' ? Icons.check_circle - : e['status'] == 'PENDING' + : e['integrationStatus'] == 'PENDING' ? Icons.pending : Icons.cancel, size: 16, color: - e['status'] == 'SUCCESS' + e['integrationStatus'] == 'SUCCESS' ? Colors.green - : e['status'] == 'PENDING' + : e['integrationStatus'] == 'PENDING' ? Colors.orange : Colors.red, ), diff --git a/lib/screens/pay/pay_controller.dart b/lib/screens/pay/pay_controller.dart index 01fd76c..8df6e12 100644 --- a/lib/screens/pay/pay_controller.dart +++ b/lib/screens/pay/pay_controller.dart @@ -111,11 +111,13 @@ class PayController extends ChangeNotifier { productUid: model.selectedProduct?.uid, ); - dynamic response = await http.post( + dynamic workflowResponse = await http.post( '/public/transaction', transactionController.model.formData, ); - logger.i(response.toString()); + logger.i(workflowResponse.toString()); + + dynamic response = workflowResponse['body']; if (response['status'] == 'SUCCESS') { model.status = response['status']; diff --git a/lib/screens/pay/pay_screen.dart b/lib/screens/pay/pay_screen.dart index 838a322..7acf0a0 100644 --- a/lib/screens/pay/pay_screen.dart +++ b/lib/screens/pay/pay_screen.dart @@ -73,7 +73,7 @@ class _PayScreenState extends State with TickerProviderStateMixin { transactionController.model.formData.creditEmail ?? ''; // Initialize phone number and country code - updatePhoneNumber(transactionController.model.formData.creditPhone ?? ''); + // updatePhoneNumber(transactionController.model.formData.creditPhone ?? ''); _controller = AnimationController( vsync: this, @@ -296,7 +296,7 @@ class _PayScreenState extends State with TickerProviderStateMixin { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - 'Notification Phone Number', + 'Debit Phone Number', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), TextButton( diff --git a/lib/screens/receipt/receipt_screen.dart b/lib/screens/receipt/receipt_screen.dart index f7aa8b6..29fd43b 100644 --- a/lib/screens/receipt/receipt_screen.dart +++ b/lib/screens/receipt/receipt_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:qpay/screens/gateway/gateway_controller.dart'; import 'package:qpay/screens/transaction_controller.dart'; import 'package:go_router/go_router.dart'; import 'package:qpay/screens/receipt/receipt_controller.dart'; @@ -17,6 +18,7 @@ class ReceiptScreen extends StatefulWidget { class _ReceiptScreenState extends State with TickerProviderStateMixin { late TransactionController transactionController; + late GatewayController gatewayController; late Animation _fadeAnimation; late Animation _slideAnimation; late AnimationController _controller; @@ -34,7 +36,7 @@ class _ReceiptScreenState extends State context, listen: false, ); - + gatewayController = GatewayController(context); receiptController = ReceiptController(context); receiptController.getReceiptData( @@ -81,6 +83,25 @@ class _ReceiptScreenState extends State } } + _retry() async { + receiptController.setLoading(true); + await gatewayController.pollTransaction(receiptController.model.receiptData?["id"]); + if(gatewayController.model.status == "SUCCESS"){ + receiptController.getReceiptData(transactionController.model.receiptData?["id"]); + } + receiptController.setLoading(false); + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + _controller.dispose(); + _tabController.dispose(); + gatewayController.dispose(); + receiptController.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -174,12 +195,12 @@ class _ReceiptScreenState extends State Icon( transactionController .model - .receiptData?["status"] == + .receiptData?["integrationStatus"] == 'SUCCESS' ? Icons.check_circle : transactionController .model - .receiptData?["status"] == + .receiptData?["integrationStatus"] == 'PENDING' ? Icons.pending : Icons.cancel, @@ -187,12 +208,12 @@ class _ReceiptScreenState extends State color: transactionController .model - .receiptData?["status"] == + .receiptData?["integrationStatus"] == 'SUCCESS' ? Colors.green : transactionController .model - .receiptData?["status"] == + .receiptData?["integrationStatus"] == 'PENDING' ? Colors.orange : Colors.red, @@ -338,9 +359,9 @@ class _ReceiptScreenState extends State icon: Icon( Icons.share, color: - Theme.of( - context, - ).colorScheme.primary, + Theme.of( + context, + ).colorScheme.primary, size: 24, ), ), @@ -355,6 +376,48 @@ class _ReceiptScreenState extends State ), ], ), + if(receiptController.model.receiptData?["integrationStatus"] == "PENDING" && + receiptController.model.receiptData?["paymentStatus"] == "SUCCESS") + Column( + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + border: Border.all( + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.3), + width: 1, + ), + ), + child: IconButton( + onPressed: () { + _retry(); + }, + icon: Icon( + Icons.redo, + color: + Theme.of( + context, + ).colorScheme.primary, + size: 24, + ), + ), + ), + SizedBox(height: 8), + Text( + "Retry", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), ], ), ], @@ -437,7 +500,7 @@ class _ReceiptScreenState extends State Text( transactionController .model - .receiptData?["status"] ?? + .receiptData?["integrationStatus"] ?? "", style: TextStyle( fontSize: 16, @@ -446,6 +509,49 @@ class _ReceiptScreenState extends State ], ), ), + if(transactionController + .model + .receiptData?["errorMessage"] != null && + transactionController.model.receiptData?["errorMessage"] != "") + Column( + children: [ + const SizedBox(height: 10), + Skeletonizer( + enabled: + receiptController + .model + .isLoading, + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Message", + style: TextStyle( + fontSize: 16, + fontWeight: + FontWeight.bold, + ), + ), + Expanded( + child: Text( + transactionController + .model + .receiptData?["errorMessage"] ?? + "", + style: TextStyle( + fontSize: 16, + ), + textAlign: TextAlign.end, + softWrap: true, + ), + ), + ], + ), + ), + ], + ), const SizedBox(height: 10), Skeletonizer( enabled: