IMPS implementation

This commit is contained in:
2025-08-22 16:36:58 +05:30
parent fc76528206
commit 04c992c934
14 changed files with 309 additions and 128 deletions

View File

@@ -12,7 +12,7 @@ class ImpsService {
try {
await Future.delayed(const Duration(seconds: 3));
final response = await _dio.post(
'/api/payment/rtgs',
'/api/payment/imps',
data: transaction.toJson(),
);
@@ -20,7 +20,7 @@ class ImpsService {
return ImpsResponse.fromJson(response.data);
} else {
throw Exception(
'RTGS transaction failed with status code: ${response.statusCode}');
'IMPS transaction failed with status code: ${response.statusCode}');
}
} on DioException {
rethrow;

View File

@@ -98,7 +98,7 @@ class _KMobileState extends State<KMobile> {
],
title: 'kMobile',
theme: themeState.getThemeData(),
darkTheme: themeState.getThemeData(),
//darkTheme: themeState.getThemeData(),
themeMode: ThemeMode.system,
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: AppRoutes.splash,
@@ -472,3 +472,4 @@ class BiometricPromptScreen extends StatelessWidget {
}
}
}

View File

@@ -16,17 +16,17 @@ class AppThemes {
}
}
static ThemeData getDarkTheme(ThemeType type) {
switch (type) {
case ThemeType.green:
return ThemeData.dark().copyWith(primaryColor: Colors.green);
case ThemeType.orange:
return ThemeData.dark().copyWith(primaryColor: Colors.orange);
case ThemeType.blue:
return ThemeData.dark().copyWith(primaryColor: Colors.blue);
case ThemeType.violet:
default:
return ThemeData.dark().copyWith(primaryColor: Colors.deepPurple);
}
}
// static ThemeData getDarkTheme(ThemeType type) {
// switch (type) {
// case ThemeType.green:
// return ThemeData.dark().copyWith(primaryColor: Colors.green);
// case ThemeType.orange:
// return ThemeData.dark().copyWith(primaryColor: Colors.orange);
// case ThemeType.blue:
// return ThemeData.dark().copyWith(primaryColor: Colors.blue);
// case ThemeType.violet:
// default:
// return ThemeData.dark().copyWith(primaryColor: Colors.deepPurple);
// }
// }
}

View File

@@ -19,13 +19,12 @@ class ImpsTransaction {
Map<String, dynamic> toJson() {
return {
'stFromAccDetails': fromAccount,
'stBenAccNo': toAccount,
'stTransferAmount': amount,
'stBenIFSC': ifscCode,
//'remitterName': remitterName,
'stBeneName': beneficiaryName,
'stRemarks': "Check",
'fromAccount': fromAccount,
'toAccount': toAccount,
'amount': amount,
'ifscCode': ifscCode,
'remitterName': remitterName,
'beneficiaryName': beneficiaryName,
'tpin': tpin,
};
}

View File

@@ -14,3 +14,6 @@ class RtgsResponse {
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/api/services/neft_service.dart';
import 'package:kmobile/api/services/imps_service.dart';
import 'package:get_it/get_it.dart';
import 'package:dio/dio.dart';
import 'package:kmobile/api/services/beneficiary_service.dart';
@@ -44,6 +45,7 @@ Future<void> setupDependencies() async {
getIt.registerSingleton<BeneficiaryService>(BeneficiaryService(getIt<Dio>()));
getIt.registerSingleton<NeftService>(NeftService(getIt<Dio>()));
getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>()));
getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>()));
// Add auth interceptor after repository is available
getIt<Dio>().interceptors.add(
@@ -59,8 +61,8 @@ Dio _createDioClient() {
final dio = Dio(
BaseOptions(
baseUrl:
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080',
//'http://localhost:8081',
//'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080',
'http://localhost:8081',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 10),
headers: {

View File

@@ -1,10 +1,12 @@
// ignore_for_file: avoid_print
// ignore_for_file: avoid_print, use_build_context_synchronously, duplicate_ignore
import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/neft_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/api/services/imps_service.dart';
import 'package:kmobile/data/models/imps_transaction.dart';
import 'package:kmobile/widgets/bank_logos.dart';
import 'package:kmobile/data/models/beneficiary.dart';
import 'package:kmobile/data/models/neft_transaction.dart';
@@ -15,7 +17,7 @@ import 'package:kmobile/features/fund_transfer/screens/payment_animation.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import '../../../l10n/app_localizations.dart';
enum TransactionMode { neft, rtgs }
enum TransactionMode { neft, rtgs, imps }
class FundTransferAmountScreen extends StatefulWidget {
final String debitAccountNo;
@@ -133,64 +135,66 @@ class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
completer.complete(paymentResponse);
}
}
// else if (_selectedMode == TransactionMode.imps){
// final rtgsTx = RtgsTransaction(
// fromAccount: widget.debitAccountNo,
// toAccount: widget.creditBeneficiary.accountNo,
// amount: _amountController.text,
// ifscCode: widget.creditBeneficiary.ifscCode,
// remitterName: widget.remitterName,
// beneficiaryName: widget.creditBeneficiary.name,
// tpin: tpin,
// );
// final rtgsService = getIt<RtgsService>();
// final completer = Completer<PaymentResponse>();
//IMPS transaction
else if (_selectedMode == TransactionMode.imps){
final impsTx = ImpsTransaction(
fromAccount: widget.debitAccountNo,
toAccount: widget.creditBeneficiary.accountNo,
amount: _amountController.text,
ifscCode: widget.creditBeneficiary.ifscCode,
remitterName: widget.remitterName,
beneficiaryName: widget.creditBeneficiary.name,
tpin: tpin,
);
final impsService = getIt<ImpsService>();
final completer = Completer<PaymentResponse>();
// Navigator.of(pinScreenContext).pushReplacement(
// MaterialPageRoute(
// builder: (_) => PaymentAnimationScreen(
// paymentResponse: completer.future),
// ),
// );
Navigator.of(pinScreenContext).pushReplacement(
MaterialPageRoute(
builder: (_) => PaymentAnimationScreen(
paymentResponse: completer.future),
),
);
// try {
// final rtgsResponse =
// await rtgsService.processRtgsTransaction(rtgsTx);
// final paymentResponse = PaymentResponse(
// isSuccess: rtgsResponse.message.toUpperCase() == 'SUCCESS',
// date: DateTime.now(),
// creditedAccount: rtgsTx.toAccount,
// amount: rtgsTx.amount,
// currency: 'INR',
// utr: rtgsResponse.utr,
// );
// completer.complete(paymentResponse);
// } on DioException catch(e) {
// print('dio exception');
// print(e.toString());
try {
final impsResponse =
await impsService.processImpsTransaction(impsTx);
final paymentResponse = PaymentResponse(
isSuccess: impsResponse.message.toUpperCase() == 'SUCCESS',
date: DateTime.now(),
creditedAccount: impsTx.toAccount,
amount: impsTx.amount,
currency: 'INR',
utr: impsResponse.utr,
);
completer.complete(paymentResponse);
} on DioException catch(e) {
print('dio exception');
print(e.toString());
// final error = jsonDecode(e.response.toString())['error'];
// var errorMessage =
// {
// "INCORRECT_TPIN" : "Please Enter the correct TPIN",
// "INSUFFICIENT_FUNDS": "Your account does not have sufficient balance"
// }[error] ?? "Something Went Wrong";
final error = jsonDecode(e.response.toString())['error'];
var errorMessage =
{
"INCORRECT_TPIN" : "Please Enter the correct TPIN",
"INSUFFICIENT_FUNDS": "Your account does not have sufficient balance"
}[error] ?? "Something Went Wrong";
// final paymentResponse = PaymentResponse(
// isSuccess: false,
// errorMessage: errorMessage,
// );
// completer.complete(paymentResponse);
// } catch (e) {
// print('generic exception');
// print(e.toString());
// final paymentResponse = PaymentResponse(
// isSuccess: false,
// errorMessage: "Something went Wrong",
// );
// completer.complete(paymentResponse);
// }
// }
final paymentResponse = PaymentResponse(
isSuccess: false,
errorMessage: errorMessage,
);
completer.complete(paymentResponse);
} catch (e) {
print('generic exception');
print(e.toString());
final paymentResponse = PaymentResponse(
isSuccess: false,
errorMessage: "Something went Wrong",
);
completer.complete(paymentResponse);
}
}
else {
final rtgsTx = RtgsTransaction(
fromAccount: widget.debitAccountNo,
@@ -325,7 +329,9 @@ class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
child: ToggleButtons(
isSelected: [
_selectedMode == TransactionMode.neft,
_selectedMode == TransactionMode.rtgs
_selectedMode == TransactionMode.rtgs,
_selectedMode == TransactionMode.imps,
],
onPressed: (index) {
setState(() {
@@ -352,10 +358,10 @@ class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
horizontal: 24.0, vertical: 12.0),
child: Text(AppLocalizations.of(context).rtgs),
),
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
// child: Text(AppLocalizations.of(context).imps),
// ),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
child: Text(AppLocalizations.of(context).imps),
),
],
),
),

View File

@@ -1,7 +1,11 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:kmobile/api/services/imps_service.dart';
import 'package:kmobile/api/services/neft_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/data/models/imps_transaction.dart';
import 'package:kmobile/data/models/neft_transaction.dart';
import 'package:kmobile/data/models/payment_response.dart';
import 'package:kmobile/data/models/rtgs_transaction.dart';
@@ -118,6 +122,7 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
return [
AppLocalizations.of(context).neft,
AppLocalizations.of(context).rtgs,
AppLocalizations.of(context).imps,
];
}
@@ -151,6 +156,8 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
final amount = double.tryParse(amountController.text) ?? 0;
final selectedMode = transactionModes(context)[selectedTransactionIndex];
final isRtgs = selectedMode == AppLocalizations.of(context).rtgs;
final isNeft = selectedMode == AppLocalizations.of(context).neft;
final isImps = selectedMode == AppLocalizations.of(context).imps;
if (isRtgs && amount < 200000) {
showDialog(
@@ -174,14 +181,14 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (pinScreenContext, tpin) async {
if (!isRtgs) {
if (isNeft) {
// NEFT
final neftTx = NeftTransaction(
fromAccount: widget.debitAccount,
toAccount: accountNumberController.text,
amount: amountController.text,
ifscCode: ifscController.text,
remitterName: "Unknown", // TODO: Get actual remitter name
remitterName: "Unknown",
beneficiaryName: nameController.text,
tpin: tpin,
);
@@ -214,14 +221,58 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
);
completer.complete(paymentResponse);
}
} else {
}
if (isImps) {
// IMPS
final impsTx = ImpsTransaction(
fromAccount: widget.debitAccount,
toAccount: accountNumberController.text,
amount: amountController.text,
ifscCode: ifscController.text,
remitterName: "Unknown",
beneficiaryName: nameController.text,
tpin: tpin,
);
final impsService = getIt<ImpsService>();
final completer = Completer<PaymentResponse>();
Navigator.of(pinScreenContext).pushReplacement(
MaterialPageRoute(
builder: (_) => PaymentAnimationScreen(
paymentResponse: completer.future),
),
);
try {
final neftResponse =
await impsService.processImpsTransaction(impsTx);
final paymentResponse = PaymentResponse(
isSuccess: neftResponse.message.toUpperCase() == 'SUCCESS',
date: DateTime.now(),
creditedAccount: impsTx.toAccount,
amount: impsTx.amount,
currency: 'INR',
utr: neftResponse.utr,
);
completer.complete(paymentResponse);
} catch (e) {
final paymentResponse = PaymentResponse(
isSuccess: false,
errorMessage: e.toString(),
);
completer.complete(paymentResponse);
}
}
if(isRtgs) {
// RTGS
final rtgsTx = RtgsTransaction(
fromAccount: widget.debitAccount,
toAccount: accountNumberController.text,
amount: amountController.text,
ifscCode: ifscController.text,
remitterName: "Unknown", // TODO: Get actual remitter name
remitterName: "Unknown",
beneficiaryName: nameController.text,
tpin: tpin,
);

View File

@@ -1,43 +1,148 @@
import '../../../l10n/app_localizations.dart';
import 'package:flutter/material.dart';
class BranchLocatorScreen extends StatelessWidget {
class Branch {
final String name;
final String code;
final String ifsc;
final String address;
Branch({required this.name, required this.code, required this.ifsc, required this.address,});
}
class BranchLocatorScreen extends StatefulWidget {
const BranchLocatorScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).branchLocator),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.location_on,
size: 80,
color: Theme.of(context).primaryColor,
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context).findnearbybranched,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 24),
ElevatedButton.icon(
icon: const Icon(Icons.search),
label: Text( AppLocalizations.of(context).searchbranch),
onPressed: () {
// Place API here
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text( AppLocalizations.of(context).branchsearchsoon)),
);
},
),
],
),
),
);
}
}
@override
State<BranchLocatorScreen> createState() => _BranchLocatorScreenState();
}
class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
final TextEditingController _searchController = TextEditingController();
// Static list of 5 branches
final List<Branch> _branches = [
Branch(name: "Dharamsala - Head Office", code: "002", ifsc: "KACE0000002", address: "Civil Lines Dharmashala, Kangra, HP - 176215"),
Branch(name: "Kangra", code: "033", ifsc: "KACE0000033", address: "Rajput Bhawankangrapo Kangra, Kangra, HP "),
];
List<Branch> _filteredBranches = [];
@override
void initState() {
super.initState();
_filteredBranches = _branches; // Initially show all branches
}
void _filterBranches(String query) {
setState(() {
if (query.isEmpty) {
_filteredBranches = _branches;
} else {
_filteredBranches = _branches.where((branch) {
final lowerQuery = query.toLowerCase();
return branch.name.toLowerCase().contains(lowerQuery) ||
branch.code.toLowerCase().contains(lowerQuery) ||
branch.ifsc.toLowerCase().contains(lowerQuery) ||
branch.address.toLowerCase().contains(lowerQuery);
}).toList();
}
});
}
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// title: Text(AppLocalizations.of(context).branchLocator),
// ),
// body: Center(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(
// Icons.location_on,
// size: 80,
// color: Theme.of(context).primaryColor,
// ),
// const SizedBox(height: 16),
// Text(
// AppLocalizations.of(context).findnearbybranched,
// style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
// ),
// const SizedBox(height: 24),
// ElevatedButton.icon(
// icon: const Icon(Icons.search),
// label: Text( AppLocalizations.of(context).searchbranch),
// onPressed: () {
// // Place API here
// // ScaffoldMessenger.of(context).showSnackBar(
// // SnackBar(content: Text( AppLocalizations.of(context).branchsearchsoon)),
// // );
// },
// ),
// ],
// ),
// ),
// );
// }
// }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).branchLocator),
),
body: Column(
children: [
// Search bar
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: _searchController,
onChanged: _filterBranches,
decoration: InputDecoration(
hintText: AppLocalizations.of(context).searchbranchby,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
// List of branches
Expanded(
child: _filteredBranches.isEmpty
? const Center(child: Text("No matching branches found"))
: ListView.builder(
itemCount: _filteredBranches.length,
itemBuilder: (context, index) {
final branch = _filteredBranches[index];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile(
leading: Icon(Icons.location_city, color: Theme.of(context).primaryColor),
title: Text(branch.name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text("Code: ${branch.code} | IFSC: ${branch.ifsc} \nBranch Address: ${branch.address}"),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Selected ${branch.name}")),
);
},
),
);
},
),
),
],
),
);
}
}

View File

@@ -249,6 +249,7 @@
"validateBeneficiaryproceeding": "Please validate beneficiary before proceeding",
"findnearbybranched": "Find nearby branches",
"searchbranch": "Search Branch",
"searchbranchby": "Search by Branch Name / Code / IFSC",
"branchsearchsoon": "Branch search coming soon..."
}

View File

@@ -249,5 +249,6 @@
"validateBeneficiaryproceeding": "कृपया आगे बढ़ने से पहले लाभार्थी को पट्टे पर मान्य करें",
"findnearbybranched": "आस-पास की शाखाएँ खोजें",
"searchbranch": "शाखा खोजें",
"searchbranchby": "शाखा खोजें नाम / बैंक कोड / आईएफएससी द्वारा",
"branchsearchsoon": "शाखा खोज सुविधा जल्द ही आ रही है..."
}

View File

@@ -1517,6 +1517,12 @@ abstract class AppLocalizations {
/// **'Search Branch'**
String get searchbranch;
/// No description provided for @searchbranchby.
///
/// In en, this message translates to:
/// **'Search by Branch Name / Code / IFSC'**
String get searchbranchby;
/// No description provided for @branchsearchsoon.
///
/// In en, this message translates to:

View File

@@ -719,6 +719,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get searchbranch => 'Search Branch';
@override
String get searchbranchby => 'Search by Branch Name / Code / IFSC';
@override
String get branchsearchsoon => 'Branch search coming soon...';
}

View File

@@ -719,6 +719,9 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get searchbranch => 'शाखा खोजें';
@override
String get searchbranchby => 'शाखा खोजें नाम / बैंक कोड / आईएफएससी द्वारा';
@override
String get branchsearchsoon => 'शाखा खोज सुविधा जल्द ही आ रही है...';
}