updating sequence of flows

This commit is contained in:
2025-11-02 19:49:25 +02:00
parent 087a9aa4bc
commit 895925d32f
9 changed files with 149 additions and 179 deletions

View File

@@ -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://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.1.164:6950/api
; BASE_URL=http://192.168.120.160:6950/api ; BASE_URL=http://192.168.120.160:6950/api
; BASE_URL=http://10.10.2.92:6950/api ; BASE_URL=http://10.10.2.92:6950/api

View File

@@ -110,9 +110,7 @@ class _MyAppState extends State<MyApp> {
), ),
GoRoute( GoRoute(
path: '/gateway', path: '/gateway',
builder: builder: (context, state) => const GatewayScreen(),
(context, state) =>
GatewayScreen(url: state.extra as String),
), ),
GoRoute( GoRoute(
path: '/receipt', path: '/receipt',

View File

@@ -84,14 +84,11 @@ class ConfirmController extends ChangeNotifier {
if (transactionController.model.selectedPaymentProcessor.authType == if (transactionController.model.selectedPaymentProcessor.authType ==
"WEB") { "WEB") {
if (context.mounted) { if (context.mounted) {
context.push( context.push('/gateway');
'/gateway',
extra: transactionController.model.receiptData?['targetUrl'],
);
} }
} else { } else {
await pollTransaction(transactionController.model.receiptData?['id']); await pollTransaction(transactionController.model.receiptData?['id']);
context.push('/receipt'); context.push('/integration');
} }
} else { } else {
model.status = response['status']; model.status = response['status'];

View File

@@ -60,15 +60,10 @@ class GatewayController extends ChangeNotifier {
dynamic response = workflowResponse['body']; dynamic response = workflowResponse['body'];
model.status = response['status']; model.status = response['status'];
transactionController.model.paymentStatus = response['status']; transactionController.model.paymentStatus = response['paymentStatus'];
if(model.status == 'SUCCESS') {
transactionController.updateReceiptData(response); transactionController.updateReceiptData(response);
if(model.status == 'PENDING') {
_showErrorSnackBar('Transaction status still pending');
}
if(model.status != 'FAILED') {
_showErrorSnackBar(response['errorMessage'].isEmpty ? 'Transaction failed' : response['errorMessage']);
} }
notifyListeners(); notifyListeners();
} catch (e) { } catch (e) {

View File

@@ -7,8 +7,7 @@ import 'package:qpay/screens/gateway/gateway_controller.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
class GatewayScreen extends StatefulWidget { class GatewayScreen extends StatefulWidget {
final String url; const GatewayScreen({super.key});
const GatewayScreen({super.key, required this.url});
@override @override
State<GatewayScreen> createState() => _GatewayScreenState(); State<GatewayScreen> createState() => _GatewayScreenState();
@@ -17,20 +16,22 @@ class GatewayScreen extends StatefulWidget {
class _GatewayScreenState extends State<GatewayScreen> { class _GatewayScreenState extends State<GatewayScreen> {
late WebViewController _webViewController; late WebViewController _webViewController;
late GatewayController controller; late GatewayController controller;
late String url;
bool integrationStarted = false; bool integrationStarted = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initWebView(widget.url);
controller = GatewayController(context); controller = GatewayController(context);
url = controller.transactionController.model.receiptData?['targetUrl'];
_initWebView(url);
bool simulate = bool.parse(dotenv.env['SIMULATE_PAYMENT_SUCCESS']!); bool simulate = bool.parse(dotenv.env['SIMULATE_PAYMENT_SUCCESS']!);
if(simulate) { if(simulate) {
sleep(Duration(seconds: 10)); sleep(Duration(seconds: 10));
String url = widget.url.replaceAll("/pay/", "/receipt/"); String targetUrl = url.replaceAll("/pay/", "/receipt/");
_webViewController.loadRequest(Uri.parse(url)); _webViewController.loadRequest(Uri.parse(targetUrl));
} }
} }
@@ -71,7 +72,6 @@ class _GatewayScreenState extends State<GatewayScreen> {
setState(() { setState(() {
integrationStarted = true; integrationStarted = true;
}); });
await controller.poll(controller.transactionController.model.receiptData?['id']);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
context.go('/integration'); context.go('/integration');

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:qpay/http/http.dart'; import 'package:qpay/http/http.dart';
import 'package:qpay/screens/gateway/gateway_controller.dart';
import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/screens/transaction_controller.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@@ -18,6 +19,7 @@ class IntegrationController extends ChangeNotifier {
final IntegrationModel model = IntegrationModel(); final IntegrationModel model = IntegrationModel();
final Http http = Http(); final Http http = Http();
late TransactionController transactionController; late TransactionController transactionController;
late GatewayController gatewayController;
late SharedPreferences prefs; late SharedPreferences prefs;
BuildContext context; BuildContext context;
@@ -27,6 +29,8 @@ class IntegrationController extends ChangeNotifier {
context, context,
listen: false, listen: false,
); );
gatewayController = GatewayController(context);
} }
void _showErrorSnackBar(String? message) { void _showErrorSnackBar(String? message) {
@@ -58,6 +62,8 @@ class IntegrationController extends ChangeNotifier {
if (response['status'] == 'SUCCESS') { if (response['status'] == 'SUCCESS') {
model.status = response['status']; model.status = response['status'];
transactionController.updateReceiptData(response); transactionController.updateReceiptData(response);
gatewayController.poll(transactionController.model.confirmationData['id']);
} else { } else {
model.status = response['status']; model.status = response['status'];
model.errorMessage = response['errorMessage']; model.errorMessage = response['errorMessage'];

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:qpay/screens/gateway/gateway_controller.dart';
import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/screens/transaction_controller.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
@@ -17,7 +16,6 @@ class IntegrationScreen extends StatefulWidget {
class _IntegrationScreenState extends State<IntegrationScreen> { class _IntegrationScreenState extends State<IntegrationScreen> {
late IntegrationController controller; late IntegrationController controller;
late TransactionController transactionController; late TransactionController transactionController;
late GatewayController gatewayController;
bool integrating = false; bool integrating = false;
@@ -25,27 +23,12 @@ class _IntegrationScreenState extends State<IntegrationScreen> {
void initState() { void initState() {
super.initState(); super.initState();
controller = IntegrationController(context); controller = IntegrationController(context);
gatewayController = GatewayController(context);
transactionController = Provider.of<TransactionController>( transactionController = Provider.of<TransactionController>(
context, context,
listen: false, listen: false,
); );
} controller.doIntegration();
@override
void didChangeDependencies() async {
super.didChangeDependencies();
if(integrating) return;
String status = Provider.of<TransactionController>(context).model.paymentStatus;
if(status == 'SUCCESS') {
setState(() {
integrating = true;
});
await controller.doIntegration();
}
} }
void _showErrorSnackBar(String? message) { void _showErrorSnackBar(String? message) {
@@ -74,9 +57,6 @@ class _IntegrationScreenState extends State<IntegrationScreen> {
title: const Text('Completing your transaction'), title: const Text('Completing your transaction'),
), ),
body: ListenableBuilder( body: ListenableBuilder(
listenable: gatewayController,
builder: (context, child) {
return ListenableBuilder(
listenable: controller, listenable: controller,
builder: (context, child) { builder: (context, child) {
if (controller.model.status == 'SUCCESS') { if (controller.model.status == 'SUCCESS') {
@@ -94,28 +74,6 @@ class _IntegrationScreenState extends State<IntegrationScreen> {
children: [ children: [
SizedBox(height: 75), SizedBox(height: 75),
if(transactionController.model.paymentStatus != 'SUCCESS')
Column(
children: [
Text(
'We failed to check your payment status',
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
SizedBox(height: 50,),
Skeletonizer(
enabled: gatewayController.model.isLoading,
child: OutlinedButton(
child: Text('Check latest status'),
onPressed: () {
gatewayController.poll(transactionController.model.receiptData?['id']);
},
),
),
],
),
if(transactionController.model.paymentStatus == 'SUCCESS')
Column( Column(
children: [ children: [
Text( Text(
@@ -157,8 +115,6 @@ class _IntegrationScreenState extends State<IntegrationScreen> {
), ),
); );
}, },
);
}
), ),
); );
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:qpay/screens/gateway/gateway_controller.dart'; import 'package:qpay/screens/gateway/gateway_controller.dart';
import 'package:qpay/screens/transaction_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:qpay/screens/receipt/receipt_controller.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:widgets_to_image/widgets_to_image.dart'; import 'package:widgets_to_image/widgets_to_image.dart';
class ReceiptScreen extends StatefulWidget { class ReceiptScreen extends StatefulWidget {
@@ -96,6 +98,11 @@ class _ReceiptScreenState extends State<ReceiptScreen>
receiptController.setLoading(true); receiptController.setLoading(true);
await gatewayController.poll(receiptController.model.receiptData?["id"]); await gatewayController.poll(receiptController.model.receiptData?["id"]);
if(gatewayController.model.status == "SUCCESS"){ 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(); await _retry();
} }
receiptController.setLoading(false); receiptController.setLoading(false);
@@ -253,21 +260,22 @@ class _ReceiptScreenState extends State<ReceiptScreen>
CrossAxisAlignment.center, CrossAxisAlignment.center,
children: [ children: [
SizedBox(height: 5), SizedBox(height: 5),
// receiptController
// .model
// .receiptData?["providerImage"] !=
// null
// ? Image.asset(
// 'assets/${receiptController.model.receiptData?["providerImage"] ?? ''}',
// width: 50,
// )
// : SizedBox.shrink(),
Text( Text(
receiptController receiptController
.model .model
.receiptData?["billName"] ?? .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,9 +610,11 @@ class _ReceiptScreenState extends State<ReceiptScreen>
), ),
], ],
), ),
const SizedBox(height: 10),
if(receiptController.model.receiptData?["integrationStatus"] != "SUCCESS" && if(receiptController.model.receiptData?["integrationStatus"] != "SUCCESS" &&
receiptController.model.receiptData?["paymentStatus"] == "SUCCESS") receiptController.model.receiptData?["paymentStatus"] == "SUCCESS")
Column(
children: [
const SizedBox(height: 10),
Skeletonizer( Skeletonizer(
enabled: enabled:
receiptController receiptController
@@ -624,7 +634,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
), ),
), ),
Expanded( Expanded(
child: Text("Billing account update failed, please tap on retry to try again", child: Text("Billing account update failed, please tap on Retry to try again",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: Theme.of( color: Theme.of(
@@ -639,6 +649,8 @@ class _ReceiptScreenState extends State<ReceiptScreen>
], ],
), ),
), ),
],
),
SizedBox(height: 10,), SizedBox(height: 10,),
Skeletonizer( Skeletonizer(
@@ -767,7 +779,11 @@ class _ReceiptScreenState extends State<ReceiptScreen>
.model .model
.receiptData?["additionalData"] .receiptData?["additionalData"]
.map<Widget>((data) { .map<Widget>((data) {
return Column( return Skeletonizer(
enabled: receiptController
.model
.isLoading,
child: Column(
children: [ children: [
Row( Row(
mainAxisAlignment: mainAxisAlignment:
@@ -800,6 +816,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
height: 10, height: 10,
), ),
], ],
),
); );
}) })
.toList(), .toList(),

View File

@@ -41,6 +41,7 @@ class TransactionModel {
providerLabel: '', providerLabel: '',
); );
String paymentStatus = 'PENDING'; String paymentStatus = 'PENDING';
String pollStatus = 'PENDING';
String? errorMessage; String? errorMessage;
} }