feat: Implement major features and fix theming bug

This commit introduces several new features and a critical bug fix.

- Implemented a full "Quick Pay" flow for both within and outside the bank, including IFSC validation, beneficiary verification, and a TPIN-based payment process.
- Added a date range filter to the Account Statement screen and streamlined the UI by removing the amount filters.
- Fixed a major bug that prevented dynamic theme changes from being applied. The app now correctly switches between color themes.
- Refactored and improved beneficiary management, transaction models, and the fund transfer flow to support NEFT/RTGS.
This commit is contained in:
asif
2025-08-11 04:06:05 +05:30
parent 3024ddef15
commit f91d0f739b
34 changed files with 1638 additions and 911 deletions

View File

@@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:kmobile/data/models/ifsc.dart';
import 'package:kmobile/data/models/beneficiary.dart';
@@ -9,14 +11,16 @@ class BeneficiaryService {
Future<String> validateBeneficiaryWithinBank(String accountNumber) async {
try {
final response = await _dio.get('/api/beneficiary/validate/within-bank', queryParameters: {
final response = await _dio
.get('/api/beneficiary/validate/within-bank', queryParameters: {
'accountNumber': accountNumber,
});
if (response.statusCode == 200) {
return response.data['name'];
} else {
throw Exception(response.data['error'] ?? 'Failed to validate beneficiary');
throw Exception(
response.data['error'] ?? 'Failed to validate beneficiary');
}
} on DioException catch (e) {
throw Exception('Network error: ${e.message}');
@@ -24,147 +28,76 @@ class BeneficiaryService {
throw Exception('Unexpected error: ${e.toString()}');
}
}
Future<ifsc?> validateIFSC(String ifscCode) async {
try {
final response = await _dio.get('/api/beneficiary/ifsc-details', queryParameters: {
"ifscCode": ifscCode
}
);
if (response.statusCode == 200) {
return ifsc.fromJson(response.data);
Future<Ifsc> validateIFSC(String ifscCode) async {
try {
final response = await _dio.get('/api/beneficiary/ifsc-details',
queryParameters: {"ifscCode": ifscCode});
if (response.statusCode == 200) {
return Ifsc.fromJson(response.data);
}
} on DioException catch (e) {
if (e.response?.statusCode == 404) {
throw Exception('INVALID IFSC CODE');
}
} catch (e) {
rethrow;
}
} on DioException catch (e) {
if (e.response?.statusCode == 404) {
print('Invalid IFSC code.');
} else {
print('API error: ${e.message}');
}
} catch (e) {
print('Unexpected error: $e');
return Ifsc.fromJson({});
}
return null;
}
///Step 1: Validate beneficiary (returns refNo)
Future<String?> validateBeneficiary({
Future<String> validateBeneficiary({
required String accountNo,
required String ifscCode,
required String remitterName,
}) async {
try {
final response = await _dio.post(
'/api/beneficiary/validate/outside_bank',
queryParameters: {
'accountNo': accountNo,
'ifscCode': ifscCode,
'remitterName': remitterName,
},
);
if (response.statusCode == 200) {
return response.data['refNo'];
} else {
return null;
}
} catch (e) {
print("Error validating beneficiary: $e");
return null;
}
}
/// Step 2: Check validation status (returns Beneficiary name if success)
Future<String?> checkValidationStatus(String refNo) async {
try {
final response = await _dio.get(
'/api/beneficiary/check',
queryParameters: {
'refNo': refNo,
},
);
if (response.statusCode == 200 && response.data != null) {
return Beneficiary.fromJson(response.data).name;
} else {
return null;
}
} catch (e) {
print("Error checking validation status: $e");
return null;
log('inside validate beneficiary service');
final response = await _dio.get(
'/api/beneficiary/validate/outside-bank',
queryParameters: {
'accountNo': accountNo,
'ifscCode': ifscCode,
'remitterName': remitterName,
},
);
if (response.statusCode != 200) {
throw Exception("Invalid Beneficiary Details");
}
return response.data['name'];
}
// Send Data for Validation
Future<void> sendForValidation(Beneficiary beneficiary) async {
Future<bool> sendForValidation(Beneficiary beneficiary) async {
try {
print(beneficiary.toJson());
final response = await _dio.post(
'/api/beneficiary/add',
'/api/beneficiary',
data: beneficiary.toJson(),
);
if (response.statusCode == 200) {
print("SENT FOR VALIDATION");
return true;
} else {
print("VALIDATION REQUEST FAILED: ${response.statusCode}");
return false;
}
} catch (e) {
print("ERROR IN SENDING REQUEST: $e");
rethrow;
}
}
// Poll to check if beneficiary is found
Future<bool> checkIfFound(String accountNo) async {
const int timeoutInSeconds = 30;
const int intervalInSeconds = 2;
const int maxTries = timeoutInSeconds ~/ intervalInSeconds;
int attempts = 0;
while (attempts < maxTries) {
try {
final response = await _dio.get(
'/api/beneficiary/check?',
queryParameters: {
'accountNo': accountNo
}
);
if (response.statusCode == 200) {
print("FOUND");
return true;
} else if (response.statusCode == 404) {
print("NOT FOUND");
}
} catch (e) {
print("ERROR DURING STATUS: $e");
}
attempts++;
}
print("Beneficiary not found within timeout.");
return false;
}
Future<List<Beneficiary>> fetchBeneficiaryList() async{
Future<List<Beneficiary>> fetchBeneficiaryList() async {
try {
final response = await _dio.get(
"/api/beneficiary/get",
options: Options(
"/api/beneficiary",
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
if (response.statusCode == 200) {
return Beneficiary.listFromJson(response.data);
if (response.statusCode == 200) {
return Beneficiary.listFromJson(response.data);
} else {
throw Exception("Failed to fetch beneficiaries");
}
@@ -173,7 +106,4 @@ class BeneficiaryService {
return [];
}
}
}
}

View File

@@ -0,0 +1,30 @@
import 'package:dio/dio.dart';
import 'package:kmobile/data/models/neft_response.dart';
import 'package:kmobile/data/models/neft_transaction.dart';
class NeftService {
final Dio _dio;
NeftService(this._dio);
Future<NeftResponse> processNeftTransaction(NeftTransaction transaction) async {
try {
await Future.delayed(const Duration(seconds: 3));
final response = await _dio.post(
'/api/payment/neft',
data: transaction.toJson(),
);
if (response.statusCode == 200) {
return NeftResponse.fromJson(response.data);
} else {
throw Exception(
'NEFT transaction failed with status code: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Failed to process NEFT transaction: ${e.message}');
} catch (e) {
throw Exception('An unexpected error occurred: ${e.toString()}');
}
}
}

View File

@@ -0,0 +1,31 @@
import 'package:dio/dio.dart';
import 'package:kmobile/data/models/rtgs_response.dart';
import 'package:kmobile/data/models/rtgs_transaction.dart';
class RtgsService {
final Dio _dio;
RtgsService(this._dio);
Future<RtgsResponse> processRtgsTransaction(
RtgsTransaction transaction) async {
try {
await Future.delayed(const Duration(seconds: 3));
final response = await _dio.post(
'/api/payment/rtgs',
data: transaction.toJson(),
);
if (response.statusCode == 200) {
return RtgsResponse.fromJson(response.data);
} else {
throw Exception(
'RTGS transaction failed with status code: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Failed to process RTGS transaction: ${e.message}');
} catch (e) {
throw Exception('An unexpected error occurred: ${e.toString()}');
}
}
}