Files
kmobile/lib/features/fund_transfer/screens/fund_transfer_amount_screen.dart

437 lines
17 KiB
Dart

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';
import 'package:kmobile/data/models/payment_response.dart';
import 'package:kmobile/data/models/rtgs_transaction.dart';
import 'package:kmobile/di/injection.dart';
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';
import 'package:kmobile/api/services/payment_service.dart';
import 'package:kmobile/data/models/transfer.dart';
enum TransactionMode { neft, rtgs, imps }
class FundTransferAmountScreen extends StatefulWidget {
final String debitAccountNo;
final Beneficiary creditBeneficiary;
final String remitterName;
final bool isOwnBank;
const FundTransferAmountScreen({
super.key,
required this.debitAccountNo,
required this.creditBeneficiary,
required this.remitterName,
this.isOwnBank = false,
});
@override
State<FundTransferAmountScreen> createState() =>
_FundTransferAmountScreenState();
}
class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
final _amountController = TextEditingController();
final _formKey = GlobalKey<FormState>();
TransactionMode _selectedMode = TransactionMode.neft;
@override
void dispose() {
_amountController.dispose();
super.dispose();
}
void _onProceed() {
if (_formKey.currentState!.validate()) {
final amount = double.tryParse(_amountController.text) ?? 0;
if (widget.isOwnBank) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (pinScreenContext, tpin) async {
final transfer = Transfer(
fromAccount: widget.debitAccountNo,
toAccount: widget.creditBeneficiary.accountNo,
toAccountType: 'Savings', // Assuming 'SB' for savings
amount: _amountController.text,
tpin: tpin,
);
final paymentService = getIt<PaymentService>();
final paymentResponseFuture =
paymentService.processQuickPayWithinBank(transfer);
Navigator.of(pinScreenContext).pushReplacement(
MaterialPageRoute(
builder: (_) => PaymentAnimationScreen(
paymentResponse: paymentResponseFuture),
),
);
},
),
),
);
} else {
if (_selectedMode == TransactionMode.rtgs && amount < 200000) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(AppLocalizations.of(context).invalidRtgs),
content: Text(AppLocalizations.of(context).invalidRtgsPopUp),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text(AppLocalizations.of(context).ok),
),
],
),
);
return; // Stop further execution
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (pinScreenContext, tpin) async {
if (_selectedMode == TransactionMode.neft) {
final neftTx = NeftTransaction(
fromAccount: widget.debitAccountNo,
toAccount: widget.creditBeneficiary.accountNo,
amount: _amountController.text,
ifscCode: widget.creditBeneficiary.ifscCode,
remitterName: widget.remitterName,
beneficiaryName: widget.creditBeneficiary.name,
tpin: tpin,
);
final neftService = getIt<NeftService>();
final completer = Completer<PaymentResponse>();
Navigator.of(pinScreenContext).pushReplacement(
MaterialPageRoute(
builder: (_) => PaymentAnimationScreen(
paymentResponse: completer.future),
),
);
try {
final neftResponse =
await neftService.processNeftTransaction(neftTx);
final paymentResponse = PaymentResponse(
isSuccess:
neftResponse.message.toUpperCase() == 'SUCCESS',
date: DateTime.now(),
creditedAccount: neftTx.toAccount,
amount: neftTx.amount,
currency: 'INR',
utr: neftResponse.utr,
);
completer.complete(paymentResponse);
} on DioException catch (e) {
String errorMessage;
if (e.response != null && e.response!.data != null) {
try {
// final error = jsonDecode(e.response!.toString())['error'];
final error = e.response?.data['error'];
errorMessage = {
"INCORRECT_TPIN": "Please Enter the correct TPIN",
"INSUFFICIENT_FUNDS":
"Your account does not have sufficient balance"
}[error] ??
"Something Went Wrong";
} catch (_) {
errorMessage = "Something Went Wrong";
}
} else {
errorMessage = "Something Went Wrong";
}
final paymentResponse = PaymentResponse(
isSuccess: false,
errorMessage: errorMessage,
);
completer.complete(paymentResponse);
} catch (e) {
if (!pinScreenContext.mounted) return;
final paymentResponse = PaymentResponse(
isSuccess: false,
errorMessage: AppLocalizations.of(pinScreenContext)
.somethingWentWrong,
);
completer.complete(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),
),
);
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) {
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) {
final paymentResponse = PaymentResponse(
isSuccess: false,
errorMessage: "Something went Wrong",
);
completer.complete(paymentResponse);
}
}
//RTGS
else {
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>();
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) {
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"
// ignore: use_build_context_synchronously
}[error] ??
"Something Went Wrong";
final paymentResponse = PaymentResponse(
isSuccess: false,
errorMessage: errorMessage,
);
completer.complete(paymentResponse);
} catch (e) {
final paymentResponse = PaymentResponse(
isSuccess: false,
errorMessage: "Something Went Wrong",
);
completer.complete(paymentResponse);
}
}
},
),
),
);
}
}
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(loc.fundTransfer),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Debit Account (User)
Text(
loc.debitFrom,
style: Theme.of(context).textTheme.titleSmall,
),
Card(
elevation: 0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading: Image.asset(
'assets/images/logo.png',
width: 40,
height: 40,
),
title: Text(widget.remitterName),
subtitle: Text(widget.debitAccountNo),
),
),
const SizedBox(height: 24),
// Credit Account (Beneficiary)
Text(
AppLocalizations.of(context).creditedTo,
style: Theme.of(context).textTheme.titleSmall,
),
Card(
elevation: 0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading: getBankLogo(widget.creditBeneficiary.bankName),
title: Text(widget.creditBeneficiary.name),
subtitle: Text(widget.creditBeneficiary.accountNo),
),
),
const SizedBox(height: 24),
if (!widget.isOwnBank) ...[
// Transaction Mode Selection
Text(
AppLocalizations.of(context).selectTransactionType,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
),
child: ToggleButtons(
isSelected: [
_selectedMode == TransactionMode.neft,
_selectedMode == TransactionMode.rtgs,
_selectedMode == TransactionMode.imps,
],
onPressed: (index) {
setState(() {
_selectedMode = TransactionMode.values[index];
});
},
borderRadius: BorderRadius.circular(10),
selectedColor: Theme.of(context).colorScheme.onPrimary,
fillColor: Theme.of(context).primaryColor,
color: Theme.of(context).colorScheme.onSurface,
borderColor: Colors.transparent,
selectedBorderColor: Colors.transparent,
splashColor:
Theme.of(context).primaryColor,
highlightColor:
Theme.of(context).primaryColor,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0, vertical: 12.0),
child: Text(AppLocalizations.of(context).neft),
),
Padding(
padding: const EdgeInsets.symmetric(
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),
),
],
),
),
const SizedBox(height: 24),
],
// Amount
TextFormField(
controller: _amountController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: loc.amount,
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.currency_rupee),
),
validator: (value) {
if (value == null || value.isEmpty) {
return loc.amountRequired;
}
if (double.tryParse(value) == null ||
double.parse(value) <= 0) {
return loc.validAmount;
}
return null;
},
),
const Spacer(),
// Proceed Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _onProceed,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Text(AppLocalizations.of(context).proceed),
),
),
],
),
),
),
);
}
}