diff --git a/assets/.env b/assets/.env index d978b67..6f89a20 100644 --- a/assets/.env +++ b/assets/.env @@ -1,6 +1,6 @@ -; BASE_URL=https://peakapi.qantra.co.zw/api +BASE_URL=https://peakapi.qantra.co.zw/api ; BASE_URL=http://192.168.100.26:6950/api -BASE_URL=http://10.69.5.204:6950/api +; BASE_URL=http://10.69.5.204:6950/api ; BASE_URL=http://192.168.1.164:6950/api ; BASE_URL=http://192.168.120.160:6950/api ; BASE_URL=http://10.10.2.92:6950/api diff --git a/lib/main.dart b/lib/main.dart index 4218bda..d37a350 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -110,9 +110,7 @@ class _MyAppState extends State { ), GoRoute( path: '/gateway', - builder: - (context, state) => - GatewayScreen(url: state.extra as String), + builder: (context, state) => const GatewayScreen(), ), GoRoute( path: '/receipt', diff --git a/lib/screens/confirm/confirm_controller.dart b/lib/screens/confirm/confirm_controller.dart index a9fa3cb..d5db3af 100644 --- a/lib/screens/confirm/confirm_controller.dart +++ b/lib/screens/confirm/confirm_controller.dart @@ -84,14 +84,11 @@ class ConfirmController extends ChangeNotifier { if (transactionController.model.selectedPaymentProcessor.authType == "WEB") { if (context.mounted) { - context.push( - '/gateway', - extra: transactionController.model.receiptData?['targetUrl'], - ); + context.push('/gateway'); } } else { await pollTransaction(transactionController.model.receiptData?['id']); - context.push('/receipt'); + context.push('/integration'); } } else { model.status = response['status']; diff --git a/lib/screens/gateway/gateway_controller.dart b/lib/screens/gateway/gateway_controller.dart index 57f4e95..666aff6 100644 --- a/lib/screens/gateway/gateway_controller.dart +++ b/lib/screens/gateway/gateway_controller.dart @@ -60,15 +60,10 @@ class GatewayController extends ChangeNotifier { dynamic response = workflowResponse['body']; model.status = response['status']; - transactionController.model.paymentStatus = response['status']; - transactionController.updateReceiptData(response); + transactionController.model.paymentStatus = response['paymentStatus']; - if(model.status == 'PENDING') { - _showErrorSnackBar('Transaction status still pending'); - } - - if(model.status != 'FAILED') { - _showErrorSnackBar(response['errorMessage'].isEmpty ? 'Transaction failed' : response['errorMessage']); + if(model.status == 'SUCCESS') { + transactionController.updateReceiptData(response); } notifyListeners(); } catch (e) { diff --git a/lib/screens/gateway/gateway_screen.dart b/lib/screens/gateway/gateway_screen.dart index bcb67db..bfc8e78 100644 --- a/lib/screens/gateway/gateway_screen.dart +++ b/lib/screens/gateway/gateway_screen.dart @@ -7,8 +7,7 @@ import 'package:qpay/screens/gateway/gateway_controller.dart'; import 'package:webview_flutter/webview_flutter.dart'; class GatewayScreen extends StatefulWidget { - final String url; - const GatewayScreen({super.key, required this.url}); + const GatewayScreen({super.key}); @override State createState() => _GatewayScreenState(); @@ -17,20 +16,22 @@ class GatewayScreen extends StatefulWidget { class _GatewayScreenState extends State { late WebViewController _webViewController; late GatewayController controller; + late String url; bool integrationStarted = false; @override void initState() { super.initState(); - _initWebView(widget.url); controller = GatewayController(context); + url = controller.transactionController.model.receiptData?['targetUrl']; + _initWebView(url); bool simulate = bool.parse(dotenv.env['SIMULATE_PAYMENT_SUCCESS']!); if(simulate) { sleep(Duration(seconds: 10)); - String url = widget.url.replaceAll("/pay/", "/receipt/"); - _webViewController.loadRequest(Uri.parse(url)); + String targetUrl = url.replaceAll("/pay/", "/receipt/"); + _webViewController.loadRequest(Uri.parse(targetUrl)); } } @@ -71,7 +72,6 @@ class _GatewayScreenState extends State { setState(() { integrationStarted = true; }); - await controller.poll(controller.transactionController.model.receiptData?['id']); WidgetsBinding.instance.addPostFrameCallback((_) { context.go('/integration'); diff --git a/lib/screens/integration/integration_controller.dart b/lib/screens/integration/integration_controller.dart index 798761b..beea518 100644 --- a/lib/screens/integration/integration_controller.dart +++ b/lib/screens/integration/integration_controller.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:qpay/http/http.dart'; +import 'package:qpay/screens/gateway/gateway_controller.dart'; import 'package:qpay/screens/transaction_controller.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -18,6 +19,7 @@ class IntegrationController extends ChangeNotifier { final IntegrationModel model = IntegrationModel(); final Http http = Http(); late TransactionController transactionController; + late GatewayController gatewayController; late SharedPreferences prefs; BuildContext context; @@ -27,6 +29,8 @@ class IntegrationController extends ChangeNotifier { context, listen: false, ); + + gatewayController = GatewayController(context); } void _showErrorSnackBar(String? message) { @@ -58,6 +62,8 @@ class IntegrationController extends ChangeNotifier { if (response['status'] == 'SUCCESS') { model.status = response['status']; transactionController.updateReceiptData(response); + + gatewayController.poll(transactionController.model.confirmationData['id']); } else { model.status = response['status']; model.errorMessage = response['errorMessage']; diff --git a/lib/screens/integration/integration_screen.dart b/lib/screens/integration/integration_screen.dart index c866780..9e0a8b2 100644 --- a/lib/screens/integration/integration_screen.dart +++ b/lib/screens/integration/integration_screen.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'package:qpay/screens/gateway/gateway_controller.dart'; import 'package:qpay/screens/transaction_controller.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -17,7 +16,6 @@ class IntegrationScreen extends StatefulWidget { class _IntegrationScreenState extends State { late IntegrationController controller; late TransactionController transactionController; - late GatewayController gatewayController; bool integrating = false; @@ -25,27 +23,12 @@ class _IntegrationScreenState extends State { void initState() { super.initState(); controller = IntegrationController(context); - gatewayController = GatewayController(context); transactionController = Provider.of( context, listen: false, ); - } - - @override - void didChangeDependencies() async { - super.didChangeDependencies(); - - if(integrating) return; - - String status = Provider.of(context).model.paymentStatus; - if(status == 'SUCCESS') { - setState(() { - integrating = true; - }); - await controller.doIntegration(); - } + controller.doIntegration(); } void _showErrorSnackBar(String? message) { @@ -74,91 +57,64 @@ class _IntegrationScreenState extends State { title: const Text('Completing your transaction'), ), body: ListenableBuilder( - listenable: gatewayController, + listenable: controller, builder: (context, child) { - return ListenableBuilder( - listenable: controller, - builder: (context, child) { - if (controller.model.status == 'SUCCESS') { - WidgetsBinding.instance.addPostFrameCallback((_) { - context.go('/receipt'); - }); - } + if (controller.model.status == 'SUCCESS') { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.go('/receipt'); + }); + } - return Container( - padding: EdgeInsets.all(50), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + return Container( + padding: EdgeInsets.all(50), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 75), + + Column( children: [ - SizedBox(height: 75), - - if(transactionController.model.paymentStatus != 'SUCCESS') + Text( + 'We\'ve successfully collected your funds. We are now ' + 'updating your billing account. This won\'t take long', + style: TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ), + Image.asset('assets/peak-animation.gif', width: 150), + SizedBox( + width: 50, + child: LinearProgressIndicator(), + ), + SizedBox(height: 50), + if(controller.model.status == 'FAILED') Column( children: [ Text( - 'We failed to check your payment status', + 'We failed to update your billing account', style: TextStyle(fontSize: 20), textAlign: TextAlign.center, ), - SizedBox(height: 50,), + SizedBox(height: 20,), Skeletonizer( - enabled: gatewayController.model.isLoading, + enabled: controller.model.isLoading, child: OutlinedButton( - child: Text('Check latest status'), + child: Text('Retry'), onPressed: () { - gatewayController.poll(transactionController.model.receiptData?['id']); + controller.doIntegration(); }, ), ), ], ), - - if(transactionController.model.paymentStatus == 'SUCCESS') - Column( - children: [ - Text( - 'We\'ve successfully collected your funds. We are now ' - 'updating your billing account. This won\'t take long', - style: TextStyle(fontSize: 20), - textAlign: TextAlign.center, - ), - Image.asset('assets/peak-animation.gif', width: 150), - SizedBox( - width: 50, - child: LinearProgressIndicator(), - ), - SizedBox(height: 50), - if(controller.model.status == 'FAILED') - Column( - children: [ - Text( - 'We failed to update your billing account', - style: TextStyle(fontSize: 20), - textAlign: TextAlign.center, - ), - SizedBox(height: 20,), - Skeletonizer( - enabled: controller.model.isLoading, - child: OutlinedButton( - child: Text('Retry'), - onPressed: () { - controller.doIntegration(); - }, - ), - ), - ], - ), - ], - ), ], ), - ), - ); - }, + ], + ), + ), ); - } + }, ), ); } diff --git a/lib/screens/receipt/receipt_screen.dart b/lib/screens/receipt/receipt_screen.dart index 17d43ef..6e55997 100644 --- a/lib/screens/receipt/receipt_screen.dart +++ b/lib/screens/receipt/receipt_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:qpay/screens/gateway/gateway_controller.dart'; import 'package:qpay/screens/transaction_controller.dart'; @@ -6,6 +7,7 @@ import 'package:go_router/go_router.dart'; import 'package:qpay/screens/receipt/receipt_controller.dart'; import 'package:share_plus/share_plus.dart'; import 'package:skeletonizer/skeletonizer.dart'; +import 'package:timeago/timeago.dart' as timeago; import 'package:widgets_to_image/widgets_to_image.dart'; class ReceiptScreen extends StatefulWidget { @@ -96,6 +98,11 @@ class _ReceiptScreenState extends State receiptController.setLoading(true); await gatewayController.poll(receiptController.model.receiptData?["id"]); if(gatewayController.model.status == "SUCCESS"){ + receiptController.getReceiptData(transactionController.model.receiptData?["id"]); + if(transactionController.model.receiptData?["pollingStatus"]) { + receiptController.setLoading(false); + return; // no need to proceed if we've already polled successfully + } await _retry(); } receiptController.setLoading(false); @@ -253,21 +260,22 @@ class _ReceiptScreenState extends State CrossAxisAlignment.center, children: [ SizedBox(height: 5), - // receiptController - // .model - // .receiptData?["providerImage"] != - // null - // ? Image.asset( - // 'assets/${receiptController.model.receiptData?["providerImage"] ?? ''}', - // width: 50, - // ) - // : SizedBox.shrink(), Text( receiptController .model .receiptData?["billName"] ?? "", - style: TextStyle(fontSize: 14), + style: TextStyle(fontSize: 16), + ), + Text( + receiptController + .model + .receiptData?["createdAt"] != null ? + DateFormat.yMMMd().add_jm().format(DateTime.parse( + receiptController + .model + .receiptData?["createdAt"])) : "", + style: TextStyle(fontSize: 15), ), ], ), @@ -602,43 +610,47 @@ class _ReceiptScreenState extends State ), ], ), - const SizedBox(height: 10), if(receiptController.model.receiptData?["integrationStatus"] != "SUCCESS" && receiptController.model.receiptData?["paymentStatus"] == "SUCCESS") - Skeletonizer( - enabled: - receiptController - .model - .isLoading, - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - "Additional action", - style: TextStyle( - fontSize: 16, - fontWeight: - FontWeight.bold, - ), - ), - Expanded( - child: Text("Billing account update failed, please tap on retry to try again", + Column( + children: [ + const SizedBox(height: 10), + Skeletonizer( + enabled: + receiptController + .model + .isLoading, + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Additional action", style: TextStyle( fontSize: 16, - color: Theme.of( - context, - ).colorScheme.primary, + fontWeight: + FontWeight.bold, ), - textAlign: TextAlign.end, - softWrap: true, - ), - ), - ], + Expanded( + child: Text("Billing account update failed, please tap on Retry to try again", + style: TextStyle( + fontSize: 16, + color: Theme.of( + context, + ).colorScheme.primary, + ), + textAlign: TextAlign.end, + softWrap: true, + + ), + ), + ], + ), ), - ), + ], + ), SizedBox(height: 10,), Skeletonizer( @@ -767,39 +779,44 @@ class _ReceiptScreenState extends State .model .receiptData?["additionalData"] .map((data) { - return Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - data["name"] ?? - "", - style: TextStyle( - fontSize: - 16, - fontWeight: - FontWeight - .bold, + return Skeletonizer( + enabled: receiptController + .model + .isLoading, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + data["name"] ?? + "", + style: TextStyle( + fontSize: + 16, + fontWeight: + FontWeight + .bold, + ), ), - ), - Text( - data["value"] ?? - "", - style: - TextStyle( - fontSize: - 16, - ), - ), - ], - ), - const SizedBox( - height: 10, - ), - ], + Text( + data["value"] ?? + "", + style: + TextStyle( + fontSize: + 16, + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + ], + ), ); }) .toList(), diff --git a/lib/screens/transaction_controller.dart b/lib/screens/transaction_controller.dart index 0c1525d..8788c84 100644 --- a/lib/screens/transaction_controller.dart +++ b/lib/screens/transaction_controller.dart @@ -41,6 +41,7 @@ class TransactionModel { providerLabel: '', ); String paymentStatus = 'PENDING'; + String pollStatus = 'PENDING'; String? errorMessage; }