Commit e4263a00 authored by Sai Srinivas's avatar Sai Srinivas
Browse files

Razorpay Setup

parents 42164fa1 2af2b2b7
......@@ -32,7 +32,7 @@ android {
applicationId = "in.webgrid.genrentals"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
minSdk = 23
targetSdk = 36
versionCode = flutter.versionCode
versionName = flutter.versionName
......
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_468_717" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
<path d="M24 0H0V24H24V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_468_717)">
<path d="M12 18.75C11.59 18.75 11.25 18.41 11.25 18V15C11.25 14.59 11.59 14.25 12 14.25C12.41 14.25 12.75 14.59 12.75 15V18C12.75 18.41 12.41 18.75 12 18.75Z" fill="white"/>
<path d="M17.6 22.5608H6.39996C4.57996 22.5608 2.91996 21.1608 2.61996 19.3708L1.28996 11.4008C1.06996 10.1608 1.67996 8.5708 2.66996 7.7808L9.59996 2.2308C10.94 1.1508 13.05 1.1608 14.4 2.2408L21.33 7.7808C22.31 8.5708 22.91 10.1608 22.71 11.4008L21.38 19.3608C21.08 21.1308 19.38 22.5608 17.6 22.5608ZM11.99 2.9308C11.46 2.9308 10.93 3.0908 10.54 3.4008L3.60996 8.9608C3.04996 9.4108 2.64996 10.4508 2.76996 11.1608L4.09996 19.1208C4.27996 20.1708 5.32996 21.0608 6.39996 21.0608H17.6C18.67 21.0608 19.72 20.1708 19.9 19.1108L21.23 11.1508C21.34 10.4508 20.94 9.3908 20.39 8.9508L13.46 3.4108C13.06 3.0908 12.52 2.9308 11.99 2.9308Z" fill="white"/>
</g>
</svg>
......@@ -3,6 +3,7 @@ class BillListResponse {
List<Bills>? bills;
LatestBill? latestBill;
String? message;
String? payAmount;
int? sessionExists;
BillListResponse(
......@@ -24,6 +25,7 @@ class BillListResponse {
? new LatestBill.fromJson(json['latest_bill'])
: null;
message = json['message'];
payAmount = json['pay_amount'];
sessionExists = json['session_exists'];
}
......@@ -37,6 +39,7 @@ class BillListResponse {
data['latest_bill'] = this.latestBill!.toJson();
}
data['message'] = this.message;
data['pay_amount'] = this.payAmount;
data['session_exists'] = this.sessionExists;
return data;
}
......
class PayAmountResponse {
String? error;
String? amount;
String? message;
String? orderId;
String? razorKey;
PayAmountResponse(
{this.error, this.amount, this.message, this.orderId, this.razorKey});
PayAmountResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
amount = json['amount'];
message = json['message'];
orderId = json['order_id'];
razorKey = json['razor_key'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
data['amount'] = this.amount;
data['message'] = this.message;
data['order_id'] = this.orderId;
data['razor_key'] = this.razorKey;
return data;
}
}
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:gen_rentals/Services/api_calling.dart';
import '../Models/CommonResponse.dart';
import '../Models/TransactionModels/PayAmountResponse.dart';
class PayAmountProvider with ChangeNotifier {
bool _isLoading = false;
PayAmountResponse? _payResponse;
PaymentStatusResponse? _statusResponse;
String? _errorMessage;
bool get isLoading => _isLoading;
PayAmountResponse? get payResponse => _payResponse;
PaymentStatusResponse? get statusResponse => _statusResponse;
String? get errorMessage => _errorMessage;
/// Pay Amount API
Future<void> payAmount({
required String sessionId,
required String empId,
required String amount,
required String refType,
required String refId,
}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final res = await ApiCalling.payAmountApi(
sessionId,
empId,
amount,
refType,
refId,
);
if (res != null) {
_payResponse = res;
} else {
_errorMessage = "No response from server";
}
} catch (e) {
_errorMessage = "Error: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Get Payment Status API
Future<void> getPaymentStatus({
required String sessionId,
required String empId,
required String razorpayOrderId,
}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final res = await ApiCalling.getPaymentStatusApi(
sessionId,
empId,
razorpayOrderId,
);
if (res != null) {
_statusResponse = res;
} else {
_errorMessage = "No response from server";
}
} catch (e) {
_errorMessage = "Error: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Reset all states
void reset() {
_payResponse = null;
_statusResponse = null;
_errorMessage = null;
notifyListeners();
}
}
// Payment Status Response
class PaymentStatusResponse {
int? error;
double? amount;
String? date;
String? message;
int? sessionExists;
PaymentStatusResponse(
{this.error, this.amount, this.date, this.message, this.sessionExists});
PaymentStatusResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
amount = json['amount'];
date = json['date'];
message = json['message'];
sessionExists = json['session_exists'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
data['amount'] = this.amount;
data['date'] = this.date;
data['message'] = this.message;
data['session_exists'] = this.sessionExists;
return data;
}
}
......@@ -50,16 +50,12 @@ class RentalProvider extends ChangeNotifier {
isOtpLoading = true;
notifyListeners();
try {
if(Platform.isAndroid){
initAndroidId();
}else{
getDevId();
}
final result = await ApiCalling.fetchMobileOtpApi(mob, otp, deviceDetails);
final result = await ApiCalling.fetchMobileOtpApi(mob, otp, deviceDetails??"");
otpResponse = result;
} catch (e) {
debugPrint("❌ OTPs API Error: $e");
debugPrint("❌ OT API Error: $e");
otpResponse = null;
} finally {
isOtpLoading = false;
......
......@@ -4,9 +4,13 @@ import 'package:gen_rentals/Models/BillsModels/BillDetailsResponse.dart';
import 'package:gen_rentals/Utility/AppColors.dart';
import 'package:gen_rentals/Utility/Reusablewidgets.dart';
import 'package:provider/provider.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import '../../Notifier/BillProvider.dart';
import '../../Notifier/PayAmountProvider.dart';
import '../../Utility/CustomSnackbar.dart';
import '../DashboardScreen.dart';
import '../TransactionScreens/PaymentSuccessfailScreen.dart';
import 'BillDetailScreen.dart';
......@@ -27,15 +31,123 @@ class BillDetailListScreen extends StatefulWidget {
}
class _BillDetailListScreenState extends State<BillDetailListScreen> {
late Razorpay _razorpay;
bool? isSuccess;
var paymentMethod = "";
var User_contact = "0";
@override
void initState() {
super.initState();
_razorpay =Razorpay();
Future.microtask(() {
final provider = Provider.of<BillProvider>(context, listen: false);
provider.fetchBillList(widget.sessionId, widget.orderId, widget.accId);
});
}
void _handlePaymentSuccess(PaymentSuccessResponse response) {
setState(() {
CustomSnackBar.showSuccess(
context: context,
message: "Payment Success!",
);
// buttonLoading = false;
});
}
void _handlePaymentError(PaymentFailureResponse response) {
setState(() {
CustomSnackBar.showError(
context: context,
message: "Payment failed!",
);
// buttonLoading = false;
});
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
}
void _handleExternalWallet(ExternalWalletResponse response) {}
Future<void> payAmountFunction(String amount, String id) async {
try {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.payAmount(
sessionId: widget.sessionId,
empId: widget.accId,
amount: amount,
refType: "Payment",
refId: id,
);
final data = provider.payResponse;
if (data != null) {
if (data.error == "0") {
openCheckout(data.orderId, data.razorKey!);
} else {
CustomSnackBar.showError(
context: context,
message: "Could not Complete Payment: ${data.message}",
);
debugPrint("❌ Could not Complete Payment: ${data.message}");
}
} else {
debugPrint("❌ No response received from PayAmount API");
}
} catch (e) {
CustomSnackBar.showError(
context: context,
message: 'Error occurred: $e',
);
}
}
//razorpay payments__________________________________________________________
void openCheckout(razorPayOrderId, String razorpayKey) async {
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
// _buildCheckWidget();
Map<String, dynamic> options = {
'key': razorpayKey,
'amount': int.parse("${((0) * 100).round()}"),
'name': 'Gen Rentals',
'order_id': razorPayOrderId,
'description': "Bill",
'currency': 'INR',
'method': 'upi',
'prefill': {'contact': User_contact, 'email': ''}
};
// print(options);
try {
_razorpay.open(options);
} catch (e, s) {
// FirebaseCrashlytics.instance.log('Error occurred: $e');
// FirebaseCrashlytics.instance.recordError(e, s);
debugPrint(e.toString());
}
}
void verifyPayment(String orderId) {
isSuccess = true;
setState(() {
// toast(context, "Order Placed Successfully");
// print("Verify Payment");
});
}
@override
Widget build(BuildContext context) {
return Consumer<BillProvider>(
......@@ -136,13 +248,27 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"₹${latest.totalPrice ?? '0'}",
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.cardAmountText,
fontSize: 34,
fontWeight: FontWeight.w500,
InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen(
total: "10218",
date: "8th Oct, 2025",
payMode: "UPI",
status: "Fail",
)),
);
},
child: Text(
"₹${latest.totalPrice ?? '0'}",
style: TextStyle(
fontFamily: "Poppins",
color: AppColors.cardAmountText,
fontSize: 34,
fontWeight: FontWeight.w500,
),
),
),
Container(
......@@ -175,26 +301,26 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
],
),
// Pending
// if(latest.billPaid == "No")
// InkResponse(
// onTap: () => showPaymentBottomSheet(context, payBill: "1299", payTotal: "4218"),
// child: Container(
// padding: const EdgeInsets.symmetric(
// horizontal: 12, vertical: 8),
// decoration: BoxDecoration(
// color: const Color(0xE0008CDE),
// borderRadius: BorderRadius.circular(14),
// ),
// child: const Text(
// "Pay Now",
// style: TextStyle(
// color: Colors.white,
// fontSize: 14,
// fontWeight: FontWeight.w500,
// ),
// ),
// ),
// ),
if(latest.billPaid == "No")
InkResponse(
onTap: () => _openPaymentSheet(context, provider.billListResponse!.payAmount.toString(), latest.totalPrice ?? '0', latest.orderId.toString()),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xE0008CDE),
borderRadius: BorderRadius.circular(14),
),
child: const Text(
"Pay Now",
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
),
......@@ -290,7 +416,7 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
padding: const EdgeInsets.only(bottom: 8),
child: InkResponse(
onTap: () {
Navigator.push(
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BillDetailScreen(
sessionId: widget.sessionId,
......@@ -405,6 +531,234 @@ class _BillDetailListScreenState extends State<BillDetailListScreen> {
);
}
/// Pay balance sheet
void _openPaymentSheet(BuildContext context, String totalAmountStr, String billAmount, String id) {
TextEditingController amountController = TextEditingController();
bool isPartPayment = false;
final double totalAmount = double.tryParse(totalAmountStr) ?? 0;
showModalBottomSheet(
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 16,
left: 16,
right: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Handle Bar
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(height: 16),
// Title
const Text(
"Balance Amount Bill",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 10),
// Pay Total Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = false);
},
child: Row(
children: [
Radio<bool>(
value: false,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
// Radio<bool>(
// value: false,
// groupValue: isPartPayment,
// onChanged: (v) => setState(() => isPartPayment = v!),
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Pay Total",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
Text(
"Avoid late payment fees.",
style: TextStyle(
color: Color(0xff5FBB54),
fontSize: 12,
fontFamily: "Poppins",
),
),
],
),
const Spacer(),
Text(
"₹${totalAmount.toStringAsFixed(0)}",
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 10),
// Part Payment Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = true);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Radio<bool>(
value: true,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
Text(
"Pay Bill",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
],
),
const SizedBox(width: 24),
Text(
"₹${billAmount}",
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 6),
// Continue Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
double enteredAmount = isPartPayment
? double.tryParse(amountController.text) ?? 0
: totalAmount;
if (enteredAmount <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please enter a valid amount"),
),
);
return;
}
if (isPartPayment && enteredAmount > totalAmount) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Entered amount cannot exceed total amount",
),
),
);
return;
}
Navigator.pop(context);
// Pass selected amount to your payAmountFunction
payAmountFunction(enteredAmount.toStringAsFixed(2), id);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF008CDE),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 22),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Continue Payment",
style: TextStyle(
fontFamily: "Poppins",
color: Colors.white,
fontSize: 16,
),
),
SvgPicture.asset(
"assets/svg/continue_ic.svg",
color: Colors.white,
height: 25,
width: 25,
),
],
),
),
),
),
const SizedBox(height: 16),
],
),
);
},
);
},
);
}
void showPaymentBottomSheet(
BuildContext context, {
String? payTotal = "4218",
......
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import '../../Notifier/BillProvider.dart';
import '../../Notifier/PayAmountProvider.dart';
import '../../Utility/AppColors.dart';
import '../../Utility/CustomSnackbar.dart';
import '../../Utility/Reusablewidgets.dart';
class BillDetailScreen extends StatefulWidget {
......@@ -22,6 +25,12 @@ class BillDetailScreen extends StatefulWidget {
}
class _BillDetailScreenState extends State<BillDetailScreen> {
late Razorpay _razorpay;
bool? isSuccess;
var paymentMethod = "";
var User_contact = "0";
// Responsive text size function
double getResponsiveTextSize(BuildContext context, double baseSize) {
final double width = MediaQuery.of(context).size.width;
......@@ -55,12 +64,110 @@ class _BillDetailScreenState extends State<BillDetailScreen> {
@override
void initState() {
super.initState();
_razorpay = Razorpay();
Future.microtask(() {
final provider = Provider.of<BillProvider>(context, listen: false);
provider.fetchBillDetails(widget.sessionId, widget.accId, widget.billId);
});
}
void _handlePaymentSuccess(PaymentSuccessResponse response) {
setState(() {
CustomSnackBar.showSuccess(
context: context,
message: "Payment Success!",
);
// buttonLoading = false;
});
}
void _handlePaymentError(PaymentFailureResponse response) {
setState(() {
CustomSnackBar.showError(
context: context,
message: "Payment failed!",
);
// buttonLoading = false;
});
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
}
void _handleExternalWallet(ExternalWalletResponse response) {}
Future<void> payAmountFunction(String amount, String id) async {
try {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.payAmount(
sessionId: widget.sessionId,
empId: widget.accId,
amount: amount,
refType: "Payment",
refId: id,
);
final data = provider.payResponse;
if (data != null) {
if (data.error == "0") {
openCheckout(data.orderId, data.razorKey!);
} else {
CustomSnackBar.showError(
context: context,
message: "Could not Complete Payment: ${data.message}",
);
debugPrint("❌ Could not Complete Payment: ${data.message}");
}
} else {
debugPrint("❌ No response received from PayAmount API");
}
} catch (e) {
CustomSnackBar.showError(
context: context,
message: 'Error occurred: $e',
);
}
}
//razorpay payments__________________________________________________________
void openCheckout(razorPayOrderId, String razorpayKey) async {
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
// _buildCheckWidget();
Map<String, dynamic> options = {
'key': razorpayKey,
'amount': int.parse("${((0) * 100).round()}"),
'name': 'Gen Rentals',
'order_id': razorPayOrderId,
'description': "Bill",
'currency': 'INR',
'method': 'upi',
'prefill': {'contact': User_contact, 'email': ''}
};
// print(options);
try {
_razorpay.open(options);
} catch (e, s) {
// FirebaseCrashlytics.instance.log('Error occurred: $e');
// FirebaseCrashlytics.instance.recordError(e, s);
debugPrint(e.toString());
}
}
void verifyPayment(String orderId) {
isSuccess = true;
setState(() {
// toast(context, "Order Placed Successfully");
// print("Verify Payment");
});
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
......@@ -230,7 +337,7 @@ class _BillDetailScreenState extends State<BillDetailScreen> {
],
),
),
bottomNavigationBar: _buildBottomButtons(provider, isPaid, screenWidth, screenHeight),
bottomNavigationBar: _buildBottomButtons(provider, isPaid, screenWidth, screenHeight, details.totalPrice ?? "0", details.id ?? ""),
),
);
},
......@@ -238,7 +345,7 @@ class _BillDetailScreenState extends State<BillDetailScreen> {
}
/// Conditional Bottom Buttons based on payment status
Widget _buildBottomButtons(BillProvider provider, bool isPaid, double screenWidth, double screenHeight) {
Widget _buildBottomButtons(BillProvider provider, bool isPaid, double screenWidth, double screenHeight, String totalPrice, String id ) {
return Container(
padding: EdgeInsets.symmetric(
horizontal: getResponsivePadding(context),
......@@ -257,33 +364,35 @@ class _BillDetailScreenState extends State<BillDetailScreen> {
child: Row(
children: [
// Pay Now Button (only if not paid)
// if (!isPaid) ...[
// Expanded(
// child: ElevatedButton(
// onPressed: () {
// _showPayNowDialog(screenWidth, screenHeight);
// },
// style: ElevatedButton.styleFrom(
// backgroundColor: AppColors.buttonColor,
// foregroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(screenWidth * 0.03),
// ),
// padding: EdgeInsets.symmetric(vertical: screenHeight * 0.018),
// elevation: 0,
// ),
// child: Text(
// "Pay Now",
// style: TextStyle(
// fontSize: getResponsiveTextSize(context, 14),
// fontFamily: "Poppins",
// fontWeight: FontWeight.w600,
// ),
// ),
// ),
// ),
// SizedBox(width: screenWidth * 0.03),
// ],
if (!isPaid) ...[
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
// handle payment navigation
payAmountFunction(totalPrice, id);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(screenWidth * 0.03),
),
padding: EdgeInsets.symmetric(vertical: screenHeight * 0.018),
elevation: 0,
),
child: Text(
"Pay Now",
style: TextStyle(
fontSize: getResponsiveTextSize(context, 14),
fontFamily: "Poppins",
fontWeight: FontWeight.w600,
),
),
),
),
SizedBox(width: screenWidth * 0.03),
],
// Download Receipt Button
Expanded(
......
......@@ -14,7 +14,10 @@ import 'package:gen_rentals/Utility/Reusablewidgets.dart';
import 'package:provider/provider.dart';
import '../Models/DashboardResponse.dart';
import '../Notifier/DashboardProvider.dart';
import '../Notifier/PayAmountProvider.dart';
import 'TransactionScreens/PaymentSuccessfailScreen.dart';
import 'authScreen/LoginScreen.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
class DashboardScreen extends StatefulWidget {
final String accId;
......@@ -28,9 +31,16 @@ class DashboardScreen extends StatefulWidget {
class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingObserver {
DateTime? currentBackPressTime;
late Razorpay _razorpay;
bool? isSuccess;
var paymentMethod = "";
var User_contact = "0";
@override
void initState() {
super.initState();
_razorpay = Razorpay();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) {
......@@ -38,6 +48,138 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
});
}
void _handlePaymentSuccess(PaymentSuccessResponse response) {
setState(() async {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.getPaymentStatus(
sessionId: widget.sessionId,
empId: widget.accId,
razorpayOrderId: response.orderId.toString()
);
final data = provider.statusResponse;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen(
total: "${data?.amount}",
date: "${data?.date}",
payMode: "UPI",
status: "Success",
)),
);
CustomSnackBar.showSuccess(
context: context,
message: data?.message ?? "Payment Success!",
);
// buttonLoading = false;
});
}
void _handlePaymentError(PaymentFailureResponse response) {
setState(() async {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.getPaymentStatus(
sessionId: widget.sessionId,
empId: widget.accId,
razorpayOrderId: ""
);
final data = provider.statusResponse;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen(
total: "${data?.amount}",
date: "${data?.date}",
payMode: "UPI",
status: "Fail",
)),
);
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
});
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
}
void _handleExternalWallet(ExternalWalletResponse response) {}
Future<void> payAmountFunction(String amount) async {
try {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.payAmount(
sessionId: widget.sessionId,
empId: widget.accId,
amount: amount,
refType: "Payment",
refId: "1",
);
final data = provider.payResponse;
if (data != null) {
if (data.error == "0") {
openCheckout(data.orderId, data.razorKey!);
} else {
CustomSnackBar.showError(
context: context,
message: "${data.message}",
);
debugPrint("❌ Could not Complete Payment: ${data.message}");
}
} else {
debugPrint("❌ No response received from PayAmount API");
}
} catch (e) {
debugPrint("❌ 'Error occurred: $e'");
}
}
//razorpay payments__________________________________________________________
void openCheckout(razorPayOrderId, String razorpayKey) async {
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
// _buildCheckWidget();
Map<String, dynamic> options = {
'key': razorpayKey,
'amount': int.parse("${((0) * 100).round()}"),
'name': 'Gen Rentals',
'order_id': razorPayOrderId,
'description': "Payment",
'currency': 'INR',
'method': 'upi',
'prefill': {'contact': User_contact, 'email': ''}
};
// print(options);
try {
_razorpay.open(options);
} catch (e, s) {
// FirebaseCrashlytics.instance.log('Error occurred: $e');
// FirebaseCrashlytics.instance.recordError(e, s);
debugPrint(e.toString());
}
}
void verifyPayment(String orderId) {
isSuccess = true;
setState(() {
// toast(context, "Order Placed Successfully");
// print("Verify Payment");
});
}
// void onError(CFErrorResponse errorResponse, String orderId) {
// isSuccess = false;
// setState(() {
// // print(errorResponse.getMessage());
// // print("Error while making payment");
// });
// }
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
......@@ -362,7 +504,6 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
Text(
dashboardData?.balanceAmount?.toString() ?? "0",
style: TextStyle(
color: Colors.black,
fontSize: getResponsiveTextSize(context, 34),
fontWeight: FontWeight.w500,
......@@ -370,6 +511,18 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
),
],
),
InkResponse(
onTap: () => _openPaymentSheet(context, dashboardData!.balanceAmount.toString()),
child: Text(
"Pay Now",
style: TextStyle(
color: Colors.blue,
fontFamily: "Poppins",
fontSize: getResponsiveTextSize(context, 14),
fontWeight: FontWeight.w500,
),
),
),
],
),
SizedBox(height: screenHeight * 0.015),
......@@ -794,7 +947,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
],
),
// Gradient expiry badge
// Gradient expiry badge
if (product.expiringText != null && product.expiringText!.isNotEmpty)
Container(
padding: EdgeInsets.symmetric(
......@@ -1112,6 +1265,251 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
);
}
}
/// Pay balance sheet
void _openPaymentSheet(BuildContext context, String totalAmountStr) {
TextEditingController amountController = TextEditingController();
bool isPartPayment = false;
final double totalAmount = double.tryParse(totalAmountStr) ?? 0;
showModalBottomSheet(
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 16,
left: 16,
right: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Handle Bar
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(height: 16),
// Title
const Text(
"Balance Amount Bill",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 10),
// Pay Total Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = false);
},
child: Row(
children: [
Radio<bool>(
value: false,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
// Radio<bool>(
// value: false,
// groupValue: isPartPayment,
// onChanged: (v) => setState(() => isPartPayment = v!),
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Pay Total",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
Text(
"Avoid late payment fees.",
style: TextStyle(
color: Color(0xff5FBB54),
fontSize: 12,
fontFamily: "Poppins",
),
),
],
),
const Spacer(),
Text(
"₹${totalAmount.toStringAsFixed(0)}",
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 10),
// Part Payment Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = true);
},
child: Row(
children: [
Radio<bool>(
value: true,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
const Text(
"Part Payment",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
const SizedBox(width: 24),
Expanded(
child: Container(
height: 50,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: TextFormField(
controller: amountController,
enabled: isPartPayment,
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
color: Colors.black87,
),
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: "Enter amount",
hintStyle: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
color: Colors.grey,
),
border: InputBorder.none,
),
),
),
),
],
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 6),
// Continue Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
double enteredAmount = isPartPayment
? double.tryParse(amountController.text) ?? 0
: totalAmount;
if (enteredAmount <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please enter a valid amount"),
),
);
return;
}
if (isPartPayment && enteredAmount > totalAmount) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Entered amount cannot exceed total amount",
),
),
);
return;
}
Navigator.pop(context);
// Pass selected amount to your payAmountFunction
payAmountFunction(enteredAmount.toStringAsFixed(2));
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF008CDE),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 22),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Continue Payment",
style: TextStyle(
fontFamily: "Poppins",
color: Colors.white,
fontSize: 16,
),
),
SvgPicture.asset(
"assets/svg/continue_ic.svg",
color: Colors.white,
height: 25,
width: 25,
),
],
),
),
),
),
const SizedBox(height: 16),
],
),
);
},
);
},
);
}
void showPaymentBottomSheet(
BuildContext context, {
......@@ -1204,7 +1602,7 @@ class _PaymentBottomSheetContentState extends State<PaymentBottomSheetContent> {
}
void _handleContinuePayment() {
// Validation
// Validation
if (selectedOption == -1) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
......
......@@ -27,7 +27,7 @@ class _HelpScreenState extends State<HelpScreen> {
void initState() {
super.initState();
/// Fetch ticket list on screen load
/// Fetch ticket list on screen load
Future.microtask(() async {
final provider = Provider.of<HelpAndEnquiryProvider>(context, listen: false);
await provider.fetchTicketList(
......@@ -37,7 +37,7 @@ class _HelpScreenState extends State<HelpScreen> {
});
}
// (unchanged)
// (unchanged)
final List<Map<String, dynamic>> createNewTickets = [
{
'title': 'Payment Issues',
......@@ -102,7 +102,7 @@ class _HelpScreenState extends State<HelpScreen> {
),
),
// Provider Consumer used here
// Provider Consumer used here
body: Consumer<HelpAndEnquiryProvider>(
builder: (context, provider, _) {
if (provider.isLoading) {
......@@ -257,7 +257,7 @@ class _HelpScreenState extends State<HelpScreen> {
);
}
/// Processing tickets from provider
/// Processing tickets from provider
Widget _buildProcessingTicketsSection(List<dynamic> tickets) {
if (tickets.isEmpty) {
return const Center(child: Text("No processing tickets"));
......@@ -294,7 +294,7 @@ class _HelpScreenState extends State<HelpScreen> {
);
}
/// Closed tickets from provider
/// Closed tickets from provider
Widget _buildClosedTicketsSection(List<dynamic> tickets) {
if (tickets.isEmpty) {
return const Center(child: Text("No closed tickets"));
......
......@@ -90,7 +90,7 @@ class _ProductsDetailScreenState extends State<ProductsDetailScreen> {
),
SizedBox(width: screenWidth * 0.025),
Text(
"Bill Details",
"Order Details",
style: TextStyle(
fontFamily: "Poppins",
fontSize: isSmallScreen ? 14 : 16,
......
......@@ -26,7 +26,7 @@ class _BillPendingToastState extends State<BillPendingToast> {
void initState() {
super.initState();
/// 🔹 Fetch data after one frame (safe after build)
/// Fetch data after one frame (safe after build)
WidgetsBinding.instance.addPostFrameCallback((_) {
final provider = Provider.of<TransactionsProvider>(context, listen: false);
provider.fetchPaymentReceiptDetails(
......@@ -246,27 +246,27 @@ class _BillPendingToastState extends State<BillPendingToast> {
),
),
const SizedBox(width: 12),
// Expanded(
// child: ElevatedButton(
// onPressed: widget.onPayNow,
// style: ElevatedButton.styleFrom(
// backgroundColor: AppColors.amountText,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(50),
// ),
// padding: const EdgeInsets.symmetric(vertical: 14),
// ),
// child: const Text(
// "Pay Now",
// style: TextStyle(
// fontSize: 15,
// fontFamily: "Poppins",
// fontWeight: FontWeight.w600,
// color: Colors.white,
// ),
// ),
// ),
// ),
Expanded(
child: ElevatedButton(
onPressed: widget.onPayNow,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.amountText,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: const Text(
"Pay Now",
style: TextStyle(
fontSize: 15,
fontFamily: "Poppins",
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
],
),
],
......
......@@ -25,7 +25,7 @@ class _BillStatusToastState extends State<BillStatusToast> {
void initState() {
super.initState();
// Automatically fetch data when dialog opens
// Automatically fetch data when dialog opens
Future.microtask(() {
final provider =
Provider.of<TransactionsProvider>(context, listen: false);
......@@ -105,7 +105,7 @@ class _BillStatusToastState extends State<BillStatusToast> {
),
const SizedBox(height: 16),
// Title & Amount
// Title & Amount
const Text(
"Payment Receipt",
style: TextStyle(
......@@ -130,7 +130,7 @@ class _BillStatusToastState extends State<BillStatusToast> {
const Divider(thickness: 1, color: Color(0xFFE6E6E6)),
const SizedBox(height: 12),
// Payment Details
// Payment Details
const Align(
alignment: Alignment.centerLeft,
child: Text(
......@@ -153,7 +153,7 @@ class _BillStatusToastState extends State<BillStatusToast> {
const Divider(thickness: 1, color: Color(0xFFE6E6E6)),
const SizedBox(height: 12),
// Products
// Products
const Align(
alignment: Alignment.centerLeft,
child: Text(
......@@ -177,7 +177,7 @@ class _BillStatusToastState extends State<BillStatusToast> {
const Divider(thickness: 1, color: Color(0xFFE6E6E6)),
const SizedBox(height: 8),
// Total
// Total
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
......@@ -204,7 +204,7 @@ class _BillStatusToastState extends State<BillStatusToast> {
const SizedBox(height: 22),
// Download Button
// Download Button
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
......
......@@ -3,14 +3,32 @@ import 'package:gen_rentals/Utility/Reusablewidgets.dart';
import '../../Utility/AppColors.dart';
import 'package:flutter_svg/flutter_svg.dart';
class PaymentSuccessfulScreen extends StatefulWidget {
const PaymentSuccessfulScreen({super.key});
import '../../Utility/SharedpreferencesService.dart';
import '../DashboardScreen.dart';
class PaymentSuccessFaillScreen extends StatefulWidget {
final String total;
final String date;
final String payMode;
final String status;
const PaymentSuccessFaillScreen({
super.key,
required this.total,
required this.date,
required this.payMode,
required this.status,
});
@override
State<PaymentSuccessfulScreen> createState() => _PaymentSuccessfulScreenState();
State<PaymentSuccessFaillScreen> createState() => _PaymentSuccessFaillScreenState();
}
class _PaymentSuccessfulScreenState extends State<PaymentSuccessfulScreen> {
class _PaymentSuccessFaillScreenState extends State<PaymentSuccessFaillScreen> {
final prefs = SharedPreferencesService.instance;
@override
Widget build(BuildContext context) {
return Scaffold(
......@@ -38,6 +56,7 @@ class _PaymentSuccessfulScreenState extends State<PaymentSuccessfulScreen> {
const SizedBox(height: 40),
// Success Icon
if (widget.status == "Success")
Container(
width: double.infinity,
height: 140,
......@@ -48,23 +67,45 @@ class _PaymentSuccessfulScreenState extends State<PaymentSuccessfulScreen> {
width: 80,
),
),
if (widget.status == "Fail")
Container(
width: double.infinity,
height: 140,
color: Colors.white,
child: Image.asset(
'assets/images/failed_pay_gif.gif',
height: 80,
width: 80,
),
),
const SizedBox(height: 24),
// Success Title
if (widget.status == "Success")
const Text(
"Payment Successful",
style: TextStyle(
fontFamily: "Poppins",
fontSize: 24,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
if (widget.status == "Fail")
const Text(
"Payment Failed",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
const SizedBox(height: 8),
// Success Message
if (widget.status == "Success")
Text(
"Now enjoy a seamless,\nuninterrupted rental service.",
textAlign: TextAlign.center,
......@@ -76,49 +117,85 @@ class _PaymentSuccessfulScreenState extends State<PaymentSuccessfulScreen> {
height: 1.5,
),
),
if (widget.status == "Fail")
Text(
"There may be an issue with your,\n Payment, please try again.",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: "Poppins",
fontSize: 16,
fontWeight: FontWeight.w400,
color: AppColors.subtitleText,
height: 1.5,
),
),
const SizedBox(height: 22),
Text(
"₹${widget.total}",
style: TextStyle(
fontSize: 34,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
const SizedBox(height: 9),
Divider(),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 25),
const SectionHeading(title: "Payment Details"),
SectionHeading(
title: "Payment Details",
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
// Payment Details Section
_buildSection(
children: [
_buildDetailRow(title: "Date", value: "8th Oct, 2025"),
_buildDetailRow(title: "Payment Mode", value: "Credit Card **56"),
_buildDetailRow(
title: "Payment Status",
value: "Paid",
valueStyle: const TextStyle(
color: Color(0xFF4CAF50),
fontWeight: FontWeight.w600,
),
),
_buildDetailRow(title: "Payment Mode", value: widget.payMode),
],
),
const SizedBox(height: 10),
const SectionHeading(title: "Bill Details"),
// Bill Details Section
_buildSection(
children: [
_buildDetailRow(title: "Total Amount", value: "₹421"),
_buildDetailRow(title: "Bill Cycle", value: "7th Sep, 2025 – 7th Oct, 2025"),
_buildDetailRow(title: "Bill Generated Date", value: "8th Oct, 2025"),
_buildDetailRow(title: "Payable Amount", value: "₹421"),
_buildDetailRow(title: "Paid Date/Due Date", value: "7th Oct, 2025"),
],
),
const SizedBox(height: 2),
Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SectionHeading(
title: "Total",
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
Text(
"₹${widget.total}",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
)
],
),
)
],
),
const SizedBox(height: 25),
// Back to Home Button
const SizedBox(height: 110),
if (widget.status == "Fail")
SizedBox(
width: double.infinity,
child: ElevatedButton(
......@@ -134,20 +211,16 @@ class _PaymentSuccessfulScreenState extends State<PaymentSuccessfulScreen> {
),
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 22),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Back to Home",
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 22),
child: Text(
"Pay Now",
style: TextStyle(
fontFamily: "Poppins",
color: Color(0xFFFFFFFF),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
......@@ -159,6 +232,59 @@ class _PaymentSuccessfulScreenState extends State<PaymentSuccessfulScreen> {
),
),
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () async {
final String? savedAccId = await prefs.getString("accId");
final String? savedSessionId = await prefs.getString("session_id");
// Add navigation logic here
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DashboardScreen(sessionId: savedSessionId!, accId: savedAccId.toString(),)
)
//route
);
},
style: ElevatedButton.styleFrom(
backgroundColor: widget.status == "Fail" ? AppColors.backgroundRegular : AppColors.buttonColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 22),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
"assets/svg/homes_ic.svg",
color: widget.status == "Fail" ? Colors.blue : Color(0xFFFFFFFF),
height: 22,
width: 22,
),
SizedBox(width: 12),
Text(
"Back to Home",
style: TextStyle(
fontFamily: "Poppins",
color: widget.status == "Fail" ? Colors.blue : Color(0xFFFFFFFF),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
),
),
);
}
......@@ -167,9 +293,9 @@ class _PaymentSuccessfulScreenState extends State<PaymentSuccessfulScreen> {
}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFFF8F9FA),
color: const Color(0x11F8F9FA),
borderRadius: BorderRadius.circular(18),
),
child: Column(
......@@ -218,7 +344,7 @@ class _PaymentSuccessfulScreenState extends State<PaymentSuccessfulScreen> {
style: valueStyle ?? const TextStyle(
fontFamily: "Poppins",
fontSize: 14,
fontWeight: FontWeight.w400,
fontWeight: FontWeight.w500,
color: AppColors.normalText,
),
),
......
......@@ -2,14 +2,17 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gen_rentals/Screens/TransactionScreens/BillStatusToast.dart';
import 'package:provider/provider.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import '../../Notifier/BillProvider.dart';
import '../../Notifier/PayAmountProvider.dart';
import '../../Notifier/TransactionsProvider.dart';
import '../../Utility/AppColors.dart';
import '../../Utility/CustomSnackbar.dart';
import '../BillScreens/BillDetailScreen.dart';
import '../DashboardScreen.dart';
import 'BillPendingToast.dart';
import 'PaymentSuccessfailScreen.dart';
class TransactionsScreen extends StatefulWidget {
......@@ -26,8 +29,15 @@ class TransactionsScreen extends StatefulWidget {
}
class _TransactionsScreenState extends State<TransactionsScreen> {
bool isSuccess = false;
late Razorpay _razorpay;
var paymentMethod = "";
var User_contact = "0";
@override
void initState() {
_razorpay = Razorpay();
super.initState();
Future.microtask(() {
Provider.of<TransactionsProvider>(context, listen: false)
......@@ -35,6 +45,125 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
});
}
void _handlePaymentSuccess(PaymentSuccessResponse response) {
setState(() async {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.getPaymentStatus(
sessionId: widget.sessionId,
empId: widget.accId,
razorpayOrderId: response.orderId.toString()
);
final data = provider.statusResponse;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen(
total: "${data?.amount}",
date: "${data?.date}",
payMode: "UPI",
status: "Success",
)),
);
CustomSnackBar.showSuccess(
context: context,
message: data?.message ?? "Payment Success!",
);
// buttonLoading = false;
});
}
void _handlePaymentError(PaymentFailureResponse response) {
setState(() async {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.getPaymentStatus(
sessionId: widget.sessionId,
empId: widget.accId,
razorpayOrderId: ""
);
final data = provider.statusResponse;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen(
total: "${data?.amount}",
date: "${data?.date}",
payMode: "UPI",
status: "Fail",
)),
);
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
});
CustomSnackBar.showError(
context: context,
message: "Payment failed, please try again.",
);
}
void _handleExternalWallet(ExternalWalletResponse response) {}
Future<void> payAmountFunction(String amount) async {
try {
final provider = Provider.of<PayAmountProvider>(context, listen: false);
await provider.payAmount(
sessionId: widget.sessionId,
empId: widget.accId,
amount: amount,
refType: "Payment",
refId: "1",
);
final data = provider.payResponse;
if (data != null) {
if (data.error == "0") {
openCheckout(data.orderId, data.razorKey!);
} else {
CustomSnackBar.showError(
context: context,
message: "${data.message}",
);
debugPrint("❌ Could not Complete Payment: ${data.message}");
}
} else {
debugPrint("❌ No response received from PayAmount API");
}
} catch (e) {
debugPrint("❌ 'Error occurred: $e'");
}
}
//razorpay payments__________________________________________________________
void openCheckout(razorPayOrderId, String razorpayKey) async {
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
//
Map<String, dynamic> options = {
'key': razorpayKey,
'amount': int.parse("${((0) * 100).round()}"),
'name': 'Gen Rentals',
'order_id': razorPayOrderId,
'description': "Payment",
'currency': 'INR',
'method': 'upi',
'prefill': {'contact': User_contact, 'email': ''}
};
// print(options);
try {
_razorpay.open(options);
} catch (e, s) {
debugPrint(e.toString());
}
}
@override
Widget build(BuildContext context) {
final provider = Provider.of<TransactionsProvider>(context);
......@@ -110,6 +239,7 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
SizedBox(height: 8),
...items.map((txn) => InkResponse(
onTap: () async {
final amount = txn.amount ?? 0;
final provider = Provider.of<TransactionsProvider>(context, listen: false);
await provider.fetchPaymentReceiptDetails(
widget.sessionId,
......@@ -119,6 +249,7 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
if (txn.type == "Credit") {
showDialog(
context: context,
builder: (context) => BillStatusToast(
sessionId: widget.sessionId,
......@@ -136,6 +267,7 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
onPayNow: () {
Navigator.pop(context);
// handle payment navigation
payAmountFunction(amount.toString());
},
),
);
......@@ -346,18 +478,18 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
fontWeight: FontWeight.w500,
),
),
// InkResponse(
// onTap: () => showPaymentBottomSheet(context),
// child: Text(
// "Pay Now",
// style: TextStyle(
// fontFamily: "Poppins",
// color: Color(0xFF008CDE),
// fontSize: 14,
// fontWeight: FontWeight.w500,
// ),
// ),
// ),
InkResponse(
onTap: () => _openPaymentSheet(context , balance.toString()),
child: Text(
"Pay Now",
style: TextStyle(
fontFamily: "Poppins",
color: Color(0xFF008CDE),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
],
),
const SizedBox(height: 12),
......@@ -451,25 +583,248 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
);
}
void showPaymentBottomSheet(
BuildContext context, {
String? payTotal = "4218",
String? payBill = "2018",
}) {
void _openPaymentSheet(BuildContext context, String totalAmountStr) {
TextEditingController amountController = TextEditingController();
bool isPartPayment = false;
final double totalAmount = double.tryParse(totalAmountStr) ?? 0;
showModalBottomSheet(
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
context: context,
isScrollControlled: true, // This is important
backgroundColor: Colors.transparent,
isDismissible: true,
enableDrag: true,
builder: (BuildContext context) {
return PaymentBottomSheetContent(
payTotal: payTotal,
payBill: payBill,
billFlag: false,
partFlag: true,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 16,
left: 16,
right: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Handle Bar
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(height: 16),
// Title
const Text(
"Balance Amount Bill",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: "Poppins",
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 10),
// Pay Total Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = false);
},
child: Row(
children: [
Radio<bool>(
value: false,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
// Radio<bool>(
// value: false,
// groupValue: isPartPayment,
// onChanged: (v) => setState(() => isPartPayment = v!),
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Pay Total",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
Text(
"Avoid late payment fees.",
style: TextStyle(
color: Color(0xff5FBB54),
fontSize: 12,
fontFamily: "Poppins",
),
),
],
),
const Spacer(),
Text(
"₹${totalAmount.toStringAsFixed(0)}",
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 10),
// Part Payment Option
GestureDetector(
onTap: () {
setState(() => isPartPayment = true);
},
child: Row(
children: [
Radio<bool>(
value: true,
groupValue: isPartPayment,
onChanged: (v) => setState(() => isPartPayment = v!),
activeColor: const Color(0xFF008CDE),
),
const Text(
"Part Payment",
style: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
fontWeight: FontWeight.w400,
),
),
const SizedBox(width: 24),
Expanded(
child: Container(
height: 50,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: TextFormField(
controller: amountController,
enabled: isPartPayment,
style: const TextStyle(
fontSize: 14,
fontFamily: "Poppins",
color: Colors.black87,
),
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: "Enter amount",
hintStyle: TextStyle(
fontSize: 14,
fontFamily: "Poppins",
color: Colors.grey,
),
border: InputBorder.none,
),
),
),
),
],
),
),
const SizedBox(height: 6),
Divider(),
const SizedBox(height: 6),
// Continue Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
double enteredAmount = isPartPayment
? double.tryParse(amountController.text) ?? 0
: totalAmount;
if (enteredAmount <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please enter a valid amount"),
),
);
return;
}
if (isPartPayment && enteredAmount > totalAmount) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Entered amount cannot exceed total amount",
),
),
);
return;
}
Navigator.pop(context);
// Pass selected amount to your payAmountFunction
payAmountFunction(enteredAmount.toStringAsFixed(2));
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF008CDE),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 22),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Continue Payment",
style: TextStyle(
fontFamily: "Poppins",
color: Colors.white,
fontSize: 16,
),
),
SvgPicture.asset(
"assets/svg/continue_ic.svg",
color: Colors.white,
height: 25,
width: 25,
),
],
),
),
),
),
const SizedBox(height: 16),
],
),
);
},
);
},
);
}
}
// forgot_password_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class ForgotPasswordScreen extends StatelessWidget {
const ForgotPasswordScreen({super.key});
@override
Widget build(BuildContext context) {
final emailController = TextEditingController();
return Scaffold(
backgroundColor: const Color(0xFFF5F8FC),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.network(
"https://genrentals.in/assets/img/logo-black.svg",
height: 70,
),
const SizedBox(height: 20),
const Text(
"Forgot Password",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 2),
)
],
),
child: Column(
children: [
TextFormField(
controller: emailController,
decoration: InputDecoration(
labelText: "Email Address",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2563EB),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Password reset link sent to email")),
);
},
child: const Text(
"Confirm",
style: TextStyle(color: Colors.white),
),
),
)
],
),
),
],
),
),
),
);
}
}
......@@ -14,6 +14,8 @@ const billListUrl = "${baseUrl}bill_list";
const downloadBillUrl = "${baseUrl}download_bill";
const paymentReceiptDetailsUrl = "${baseUrl}payment_receipt_details";
const downloadReceiptUrl = "${baseUrl}download_receipt";
const payAmountUrl = "${baseUrl}pay_amount";
const getPaymentStatusUrl = "${baseUrl}get_payment_status";
/// info
const checkInOutSubmitUrl = "${baseUrl}check_in_out_submit";
......
......@@ -14,10 +14,12 @@ import 'package:gen_rentals/Models/HelpAndEnquiryModels/ticketListResponse.dart'
import '../Models/BillsModels/BillDetailsResponse.dart';
import '../Models/DashboardResponse.dart';
import '../Models/RentalPaymentDetailsResponse.dart';
import '../Models/TransactionModels/PayAmountResponse.dart';
import '../Models/TransactionModels/TransactionsResponse.dart';
import '../Models/orderDetailsBillResponse.dart';
import '../Models/rentalAccountResponse.dart';
import '../Models/rentalContactResponse.dart';
import '../Notifier/PayAmountProvider.dart';
import '../Notifier/RentalContactProvider .dart';
import 'api_URLs.dart';
import 'api_post_request.dart';
......@@ -54,7 +56,7 @@ class ApiCalling {
String otp,
deviceDetails
) async {
debugPrint("############################### Api calling ");
debugPrint("############################### Api fetch otpcalling ");
try {
Map<String, String> data = {
"mob": mob,
......@@ -64,7 +66,7 @@ class ApiCalling {
print(data);
final res = await post(data, fetchOtpUrl, {});
if (res != null) {
print(res);
return FetchMobileResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
......@@ -101,27 +103,61 @@ class ApiCalling {
}
/// Fetch Rental Account Info
static Future<RentalAccountResponse?> fetchRentalAccountInfoApi(
/// pay_amount
static Future<PayAmountResponse?> payAmountApi(
String sessionId,
String empId,
String ammount,
String refType,
String refId,
) async {
try {
Map<String, String> data = {
"session_id": sessionId,
"acc_id": empId,
"amount": ammount,
"ref_type": refType,
"ref_id": refId,
};
final res = await post(data, payAmountUrl, {});
debugPrint("PayAmount Response ${res?.body}");
if (res != null) {
return PayAmountResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint("❌ API Error (payAmountApi): $e");
return null;
}
}
/// Fetch or get_payment_status
static Future<PaymentStatusResponse?> getPaymentStatusApi(
String sessionId,
String empId,
String razorpayOrderId,
) async {
try {
Map<String, String> data = {
"session_id": sessionId,
"emp_id": empId,
"razorpay_order_id": razorpayOrderId,
};
final res = await post(data, getRentalAccInfoUrl, {});
final res = await post(data, getPaymentStatusUrl, {});
if (res != null) {
return RentalAccountResponse.fromJson(jsonDecode(res.body));
return PaymentStatusResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint("❌ API Error (fetchRentalAccountInfo): $e");
debugPrint("❌ API Error (razorpay_order_id): $e");
return null;
}
}
......@@ -296,32 +332,7 @@ class ApiCalling {
}
/// Fetch Order Details Product
static Future<OrderDetailsProductResponse?> fetchOrderDetailProductApi(
String sessionId,
String empId,
String orderId,
) async {
try {
Map<String, String> data = {
"session_id": sessionId,
"emp_id": empId,
"order_id": orderId,
};
final res = await post(data, orderDetailsProductUrl, {});
if (res != null) {
return OrderDetailsProductResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint("❌ API Error (fetchOrderDetailProduct): $e");
return null;
}
}
/// Fetch Order Details Main
static Future<OrderDetailsMainResponse?> fetchOrderDetailMainApi(
......
......@@ -5,6 +5,7 @@ import 'package:gen_rentals/Notifier/HelpAndEnquiryProvider.dart';
import 'package:gen_rentals/Notifier/TransactionsProvider.dart';
import 'package:gen_rentals/Screens/SplashScreen.dart';
import 'package:provider/provider.dart';
import 'Notifier/PayAmountProvider.dart';
import 'Notifier/RentalContactProvider .dart';
import 'Notifier/SubscribeOrderDetailsProvider.dart';
import 'Notifier/theme_provider.dart';
......@@ -36,6 +37,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => TransactionsProvider()),
ChangeNotifierProvider(create: (_) => HelpAndEnquiryProvider()),
ChangeNotifierProvider(create: (_) => BillProvider()),
ChangeNotifierProvider(create: (_) => PayAmountProvider()),
],
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
......
......@@ -153,14 +153,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.3"
eventify:
dependency: transitive
description:
name: eventify
sha256: b829429f08586cc2001c628e7499e3e3c2493a1d895fd73b00ecb23351aa5a66
url: "https://pub.dev"
source: hosted
version: "1.0.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
version: "1.3.2"
ffi:
dependency: transitive
description:
......@@ -296,6 +304,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
fluttertoast:
dependency: transitive
description:
name: fluttertoast
sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8"
url: "https://pub.dev"
source: hosted
version: "8.2.14"
google_fonts:
dependency: "direct main"
description:
......@@ -396,26 +412,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.dev"
source: hosted
version: "11.0.2"
version: "10.0.8"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.10"
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.1"
lints:
dependency: transitive
description:
......@@ -672,6 +688,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
razorpay_flutter:
dependency: "direct main"
description:
name: razorpay_flutter
sha256: "7d86b2a2ba2c3a71366bbfb65664236ba4b12fd6aeaed4c13dfc5c998786b2d6"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
rxdart:
dependency: transitive
description:
......@@ -841,10 +865,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.6"
version: "0.7.4"
typed_data:
dependency: transitive
description:
......@@ -889,10 +913,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.1.4"
vm_service:
dependency: transitive
description:
......@@ -942,5 +966,5 @@ packages:
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.8.0-0 <4.0.0"
dart: ">=3.7.2 <4.0.0"
flutter: ">=3.29.0"
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment