aligning logic to backend workflows

This commit is contained in:
2025-10-22 23:17:56 +02:00
parent 8237ed32a7
commit d8e3b88b3e
7 changed files with 151 additions and 25 deletions

View File

@@ -79,7 +79,7 @@ class _MyAppState extends State<MyApp> {
themeMode: ThemeMode.light, // Force light mode regardless of OS setting
debugShowCheckedModeBanner: false,
routerConfig: GoRouter(
initialLocation: '/',
initialLocation: '/receipt',
routes: [
ShellRoute(
builder: (context, state, child) {

View File

@@ -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;
}
}

View File

@@ -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'];

View File

@@ -101,6 +101,10 @@ class _HomeScreenState extends State<HomeScreen>
});
}
if(prefs.getString("userId") == null) {
prefs.setString("userId", Uuid().v4());
};
await homeController.getTransactions(prefs.getString("userId")!);
}
@@ -197,7 +201,7 @@ class _HomeScreenState extends State<HomeScreen>
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<HomeScreen>
),
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,
),

View File

@@ -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'];

View File

@@ -73,7 +73,7 @@ class _PayScreenState extends State<PayScreen> 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<PayScreen> with TickerProviderStateMixin {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Notification Phone Number',
'Debit Phone Number',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
TextButton(

View File

@@ -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<ReceiptScreen>
with TickerProviderStateMixin {
late TransactionController transactionController;
late GatewayController gatewayController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
late AnimationController _controller;
@@ -34,7 +36,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
context,
listen: false,
);
gatewayController = GatewayController(context);
receiptController = ReceiptController(context);
receiptController.getReceiptData(
@@ -81,6 +83,25 @@ class _ReceiptScreenState extends State<ReceiptScreen>
}
}
_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<ReceiptScreen>
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<ReceiptScreen>
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<ReceiptScreen>
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<ReceiptScreen>
),
],
),
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<ReceiptScreen>
Text(
transactionController
.model
.receiptData?["status"] ??
.receiptData?["integrationStatus"] ??
"",
style: TextStyle(
fontSize: 16,
@@ -446,6 +509,49 @@ class _ReceiptScreenState extends State<ReceiptScreen>
],
),
),
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: