Implemented Razorpay Payment Gateway integration - April 17, 2026, 5:56 PM
This commit is contained in:
parent
ece167ea75
commit
41d7cfa8c6
@ -45,6 +45,10 @@ android {
|
||||
getByName("release") {
|
||||
// TODO: Replace with your release signing config
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
android/app/proguard-rules.pro
vendored
Normal file
5
android/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
-keep class com.razorpay.** {*;}
|
||||
-dontwarn com.razorpay.**
|
||||
@ -117,6 +117,12 @@ PODS:
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
- razorpay-core-pod (1.0.6)
|
||||
- razorpay-pod (1.5.3):
|
||||
- razorpay-core-pod (= 1.0.6)
|
||||
- razorpay_flutter (1.1.10):
|
||||
- Flutter
|
||||
- razorpay-pod
|
||||
- SDWebImage (5.21.7):
|
||||
- SDWebImage/Core (= 5.21.7)
|
||||
- SDWebImage/Core (5.21.7)
|
||||
@ -140,6 +146,7 @@ DEPENDENCIES:
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- razorpay_flutter (from `.symlinks/plugins/razorpay_flutter/ios`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
@ -156,6 +163,8 @@ SPEC REPOS:
|
||||
- GoogleUtilities
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
- razorpay-core-pod
|
||||
- razorpay-pod
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
|
||||
@ -184,6 +193,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
razorpay_flutter:
|
||||
:path: ".symlinks/plugins/razorpay_flutter/ios"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
url_launcher_ios:
|
||||
@ -213,6 +224,9 @@ SPEC CHECKSUMS:
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
razorpay-core-pod: 6da5fb4ed280279d665507fdb50e213d793a5fe2
|
||||
razorpay-pod: 4385cf844aa29389313b741e20c72fe668970d49
|
||||
razorpay_flutter: 0e98e4fcaae27ad50e011d85f66d85e0a008754a
|
||||
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import Firebase
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
@ -7,6 +8,7 @@ import UIKit
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
FirebaseApp.configure()
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
if #available(iOS 10.0, *) {
|
||||
|
||||
@ -27,4 +27,5 @@ class ConstsApi {
|
||||
"$baseUrl/api/notification/un_read_count";
|
||||
static const String dashboard = "$baseUrl/api/get_dashboard_data";
|
||||
static const String proformaaccept = "$baseUrl/api/proforma/accept";
|
||||
static const String payNow = "$baseUrl/api/pay_now";
|
||||
}
|
||||
|
||||
@ -861,6 +861,36 @@ debugPrint('📦 KYC Response Body: ${response.body}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> payNow({required int id}) async {
|
||||
try {
|
||||
final token = await _localStore.getToken();
|
||||
final uri = Uri.parse(ConstsApi.payNow).replace(
|
||||
queryParameters: {'id': id.toString()},
|
||||
);
|
||||
|
||||
final response = await http.post(
|
||||
uri,
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
);
|
||||
|
||||
final data = jsonDecode(response.body);
|
||||
debugPrint("🚀 Pay Now API Response: $data");
|
||||
print("🚀 Pay Now API Response: $data"); // Force print as well
|
||||
|
||||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||
throw data['message'] ?? 'Failed to process payment';
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
debugPrint('❌ Pay Now API Error: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> proformaaccept({required int proformaId}) async {
|
||||
try {
|
||||
final token = await _localStore.getToken();
|
||||
|
||||
70
lib/services/razorpay_service.dart
Normal file
70
lib/services/razorpay_service.dart
Normal file
@ -0,0 +1,70 @@
|
||||
import 'package:razorpay_flutter/razorpay_flutter.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class RazorpayService {
|
||||
late Razorpay _razorpay;
|
||||
final Function(PaymentSuccessResponse)? onSuccess;
|
||||
final Function(PaymentFailureResponse)? onFailure;
|
||||
final Function(ExternalWalletResponse)? onExternalWallet;
|
||||
|
||||
RazorpayService({
|
||||
this.onSuccess,
|
||||
this.onFailure,
|
||||
this.onExternalWallet,
|
||||
}) {
|
||||
_razorpay = Razorpay();
|
||||
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
|
||||
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
|
||||
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
|
||||
}
|
||||
|
||||
void _handlePaymentSuccess(PaymentSuccessResponse response) {
|
||||
debugPrint("✅ Razorpay Payment Success: ${response.paymentId}");
|
||||
onSuccess?.call(response);
|
||||
}
|
||||
|
||||
void _handlePaymentError(PaymentFailureResponse response) {
|
||||
debugPrint("❌ Razorpay Payment Error: ${response.code} - ${response.message}");
|
||||
onFailure?.call(response);
|
||||
}
|
||||
|
||||
void _handleExternalWallet(ExternalWalletResponse response) {
|
||||
debugPrint("💰 Razorpay External Wallet: ${response.walletName}");
|
||||
onExternalWallet?.call(response);
|
||||
}
|
||||
|
||||
void openCheckout({
|
||||
required String key,
|
||||
required dynamic amount,
|
||||
required String orderId,
|
||||
required String description,
|
||||
String? contact,
|
||||
String? email,
|
||||
}) {
|
||||
var options = {
|
||||
'key': key,
|
||||
'amount': amount,
|
||||
'name': 'Taxglide',
|
||||
'order_id': orderId,
|
||||
'description': description,
|
||||
'theme': {'color': '#61277A'},
|
||||
'prefill': {
|
||||
'contact': contact ?? '',
|
||||
'email': email ?? '',
|
||||
},
|
||||
'external': {
|
||||
'wallets': ['paytm']
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
_razorpay.open(options);
|
||||
} catch (e) {
|
||||
debugPrint("❌ Error opening Razorpay: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_razorpay.clear();
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,8 @@ import 'package:taxglide/model/detail_model.dart';
|
||||
import 'package:taxglide/view/Mahi_chat/live_chat_screen.dart';
|
||||
import 'package:taxglide/view/Main_controller/main_controller.dart';
|
||||
import 'package:taxglide/view/screens/history/completed_live_chat_screen.dart';
|
||||
import 'package:taxglide/services/razorpay_service.dart';
|
||||
import 'package:razorpay_flutter/razorpay_flutter.dart';
|
||||
|
||||
class DetailScreen extends ConsumerStatefulWidget {
|
||||
final int id;
|
||||
@ -30,13 +32,33 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
String? _downloadedInvoicePath;
|
||||
|
||||
bool _isWebSocketInitialized = false; // Add this flag
|
||||
late RazorpayService _razorpayService;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_razorpayService = RazorpayService(
|
||||
onSuccess: _handlePaymentSuccess,
|
||||
onFailure: _handlePaymentFailure,
|
||||
);
|
||||
_initializeWebSocket(); // Initialize WebSocket
|
||||
}
|
||||
|
||||
void _handlePaymentSuccess(PaymentSuccessResponse response) {
|
||||
ValidationPopup().showSuccessMessage(
|
||||
context,
|
||||
"Payment successful! ID: ${response.paymentId}",
|
||||
);
|
||||
ref.invalidate(serviceDetailProvider(widget.id));
|
||||
}
|
||||
|
||||
void _handlePaymentFailure(PaymentFailureResponse response) {
|
||||
ValidationPopup().showErrorMessage(
|
||||
context,
|
||||
"Payment failed: ${response.message}",
|
||||
);
|
||||
}
|
||||
|
||||
// Add this method
|
||||
Future<void> _initializeWebSocket() async {
|
||||
if (_isWebSocketInitialized) return;
|
||||
@ -82,6 +104,7 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_razorpayService.dispose();
|
||||
// Optionally disconnect WebSocket when leaving screen
|
||||
// DetailsWebscokect().disconnect();
|
||||
super.dispose();
|
||||
@ -639,20 +662,26 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Date: ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: data.createdDate ?? "-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
style: AppTextStyles.regular
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -668,20 +697,26 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Payment : ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: data.paymentStatus ?? "-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
style: AppTextStyles.regular
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -891,9 +926,9 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
Column(
|
||||
children: [
|
||||
if (data.paymentStatus == "Un Paid" &&
|
||||
!(data.serviceStatus ?? "")
|
||||
.toLowerCase()
|
||||
.contains("cancelled")) ...[
|
||||
!(data.serviceStatus ?? "").toLowerCase().contains(
|
||||
"cancelled",
|
||||
)) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 31,
|
||||
@ -926,7 +961,8 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
Text(
|
||||
"Payment Advice",
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.042,
|
||||
height: 1.4,
|
||||
letterSpacing: 0.03 * 16,
|
||||
@ -945,22 +981,34 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Date: ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
style: AppTextStyles
|
||||
.semiBold
|
||||
.copyWith(
|
||||
fontSize:
|
||||
width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
data.createdDate ??
|
||||
"-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
style: AppTextStyles
|
||||
.regular
|
||||
.copyWith(
|
||||
fontSize:
|
||||
width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -976,22 +1024,34 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Payment : ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
style: AppTextStyles
|
||||
.semiBold
|
||||
.copyWith(
|
||||
fontSize:
|
||||
width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
data.paymentStatus ??
|
||||
"-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
style: AppTextStyles
|
||||
.regular
|
||||
.copyWith(
|
||||
fontSize:
|
||||
width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1009,17 +1069,23 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
Text(
|
||||
"Total Amount",
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.04,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"₹ ${data.paymentAmount ?? '0'}",
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontFamily: 'Roboto',
|
||||
fontSize: width * 0.045,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1029,7 +1095,37 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CommanButton(text: 'Pay Now', onPressed: () {}),
|
||||
CommanButton(
|
||||
text: 'Pay Now',
|
||||
onPressed: () async {
|
||||
try {
|
||||
final response = await ApiRepository()
|
||||
.payNow(
|
||||
id: int.parse(data.id.toString()),
|
||||
);
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
_razorpayService.openCheckout(
|
||||
key: response['razorpay_key'],
|
||||
amount: response['amount'],
|
||||
orderId: response['order_id'],
|
||||
description:
|
||||
"Payment for Service ID: ${data.id}",
|
||||
);
|
||||
} else {
|
||||
validationPopup.showErrorMessage(
|
||||
context,
|
||||
"Failed to initiate payment",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
validationPopup.showErrorMessage(
|
||||
context,
|
||||
e.toString(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -1066,7 +1162,8 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
Text(
|
||||
"Payment Advice",
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.042,
|
||||
height: 1.4,
|
||||
letterSpacing: 0.03 * 16,
|
||||
@ -1085,22 +1182,34 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Date: ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
style: AppTextStyles
|
||||
.semiBold
|
||||
.copyWith(
|
||||
fontSize:
|
||||
width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
data.createdDate ??
|
||||
"-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
style: AppTextStyles
|
||||
.regular
|
||||
.copyWith(
|
||||
fontSize:
|
||||
width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1116,22 +1225,34 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Payment: ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
style: AppTextStyles
|
||||
.semiBold
|
||||
.copyWith(
|
||||
fontSize:
|
||||
width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
data.paymentStatus ??
|
||||
"-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
style: AppTextStyles
|
||||
.regular
|
||||
.copyWith(
|
||||
fontSize:
|
||||
width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1149,17 +1270,23 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
Text(
|
||||
"Total Amount",
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.04,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"₹ ${data.paymentAmount ?? '0'}",
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontFamily: 'Roboto',
|
||||
fontSize: width * 0.045,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1232,22 +1359,27 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'proforma Number : ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: data.proformaNumber ??
|
||||
text:
|
||||
data.proformaNumber ??
|
||||
"-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
style: AppTextStyles.regular
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
letterSpacing:
|
||||
0.04 * 13.97,
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
@ -1267,20 +1399,26 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'proforma Status : ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: data.proformaStatus ?? "-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
style: AppTextStyles.regular
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1294,20 +1432,26 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Subject : ',
|
||||
style: AppTextStyles.semiBold.copyWith(
|
||||
style: AppTextStyles.semiBold
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: data.subject ?? "-",
|
||||
style: AppTextStyles.regular.copyWith(
|
||||
style: AppTextStyles.regular
|
||||
.copyWith(
|
||||
fontSize: width * 0.035,
|
||||
height: 1.3,
|
||||
letterSpacing: 0.04 * 13.97,
|
||||
color: const Color(0xFF111827),
|
||||
color: const Color(
|
||||
0xFF111827,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1662,7 +1806,9 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
),
|
||||
if (data.chatId != null &&
|
||||
data.chatId!.isNotEmpty &&
|
||||
!(data.serviceStatus ?? "").toLowerCase().contains("cancelled"))
|
||||
!(data.serviceStatus ?? "").toLowerCase().contains(
|
||||
"cancelled",
|
||||
))
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
@ -1758,7 +1904,9 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 80.0 + MediaQuery.of(context).padding.bottom),
|
||||
SizedBox(
|
||||
height: 80.0 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -7,15 +7,60 @@ import 'package:taxglide/view/Main_controller/main_controller.dart';
|
||||
import 'package:taxglide/view/screens/history/detail_screen.dart';
|
||||
import 'package:taxglide/view/Mahi_chat/live_chat_screen.dart';
|
||||
import 'package:taxglide/view/screens/history/completed_live_chat_screen.dart';
|
||||
import 'package:taxglide/services/razorpay_service.dart';
|
||||
import 'package:razorpay_flutter/razorpay_flutter.dart';
|
||||
import 'package:taxglide/consts/validation_popup.dart';
|
||||
import 'package:taxglide/controller/api_repository.dart';
|
||||
|
||||
class PendingScreen extends ConsumerWidget {
|
||||
class PendingScreen extends ConsumerStatefulWidget {
|
||||
final String status;
|
||||
|
||||
const PendingScreen({super.key, required this.status});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pendingAsync = ref.watch(serviceHistoryNotifierProvider(status));
|
||||
ConsumerState<PendingScreen> createState() => _PendingScreenState();
|
||||
}
|
||||
|
||||
class _PendingScreenState extends ConsumerState<PendingScreen> {
|
||||
late RazorpayService _razorpayService;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_razorpayService = RazorpayService(
|
||||
onSuccess: _handlePaymentSuccess,
|
||||
onFailure: _handlePaymentFailure,
|
||||
);
|
||||
}
|
||||
|
||||
void _handlePaymentSuccess(PaymentSuccessResponse response) {
|
||||
ValidationPopup().showSuccessMessage(
|
||||
context,
|
||||
"Payment successful! ID: ${response.paymentId}",
|
||||
);
|
||||
ref
|
||||
.read(serviceHistoryNotifierProvider(widget.status).notifier)
|
||||
.fetchServiceHistory();
|
||||
}
|
||||
|
||||
void _handlePaymentFailure(PaymentFailureResponse response) {
|
||||
ValidationPopup().showErrorMessage(
|
||||
context,
|
||||
"Payment failed: ${response.message}",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_razorpayService.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pendingAsync = ref.watch(
|
||||
serviceHistoryNotifierProvider(widget.status),
|
||||
);
|
||||
final size = MediaQuery.of(context).size;
|
||||
final width = size.width;
|
||||
final height = size.height;
|
||||
@ -23,9 +68,11 @@ class PendingScreen extends ConsumerWidget {
|
||||
// 🔄 Silent refresh when notification trigger changes
|
||||
ref.listen(notificationTriggerProvider, (previous, next) {
|
||||
if (previous != null && next != previous) {
|
||||
debugPrint("🔔 Silent refresh triggered for PendingScreen ($status)");
|
||||
debugPrint(
|
||||
"🔔 Silent refresh triggered for PendingScreen (${widget.status})",
|
||||
);
|
||||
ref
|
||||
.read(serviceHistoryNotifierProvider(status).notifier)
|
||||
.read(serviceHistoryNotifierProvider(widget.status).notifier)
|
||||
.fetchServiceHistory(isSilent: true);
|
||||
ref.invalidate(countProvider);
|
||||
}
|
||||
@ -36,7 +83,9 @@ class PendingScreen extends ConsumerWidget {
|
||||
final list = data.data ?? [];
|
||||
if (list.isEmpty) {
|
||||
// Using Get's capitalize extension explicitly
|
||||
return Center(child: Text("No ${status.capitalizeFirst} Requests"));
|
||||
return Center(
|
||||
child: Text("No ${widget.status.capitalizeFirst} Requests"),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
@ -202,11 +251,13 @@ class PendingScreen extends ConsumerWidget {
|
||||
item.chatId != null &&
|
||||
item.chatId.toString().isNotEmpty &&
|
||||
item.chatId.toString() != "0") ...[
|
||||
|
||||
/// 💬 Chat Icon with Badge
|
||||
Builder(builder: (context) {
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final chatIdStr = item.chatId.toString();
|
||||
final countAsync = ref.watch(countProvider(chatIdStr));
|
||||
final countAsync = ref.watch(
|
||||
countProvider(chatIdStr),
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
@ -214,22 +265,44 @@ class PendingScreen extends ConsumerWidget {
|
||||
if (chatid == 0) return;
|
||||
|
||||
if (item.status == 'In Progress') {
|
||||
Get.to(() => LiveChatScreen(
|
||||
Get.to(
|
||||
() => LiveChatScreen(
|
||||
fileid: item.id.toString(),
|
||||
chatid: chatid,
|
||||
))?.then((_) {
|
||||
ref.read(notificationTriggerProvider.notifier).state++;
|
||||
ref.invalidate(chatMessagesProvider(chatid));
|
||||
ref.invalidate(countProvider(chatIdStr));
|
||||
),
|
||||
)?.then((_) {
|
||||
ref
|
||||
.read(
|
||||
notificationTriggerProvider
|
||||
.notifier,
|
||||
)
|
||||
.state++;
|
||||
ref.invalidate(
|
||||
chatMessagesProvider(chatid),
|
||||
);
|
||||
ref.invalidate(
|
||||
countProvider(chatIdStr),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
Get.to(() => CompletedLiveChatScreen(
|
||||
Get.to(
|
||||
() => CompletedLiveChatScreen(
|
||||
fileid: item.id.toString(),
|
||||
chatid: chatid,
|
||||
))?.then((_) {
|
||||
ref.read(notificationTriggerProvider.notifier).state++;
|
||||
ref.invalidate(chatMessagesProvider(chatid));
|
||||
ref.invalidate(countProvider(chatIdStr));
|
||||
),
|
||||
)?.then((_) {
|
||||
ref
|
||||
.read(
|
||||
notificationTriggerProvider
|
||||
.notifier,
|
||||
)
|
||||
.state++;
|
||||
ref.invalidate(
|
||||
chatMessagesProvider(chatid),
|
||||
);
|
||||
ref.invalidate(
|
||||
countProvider(chatIdStr),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -246,7 +319,9 @@ class PendingScreen extends ConsumerWidget {
|
||||
color: const Color(0xFF61277A),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.25),
|
||||
color: Colors.black.withOpacity(
|
||||
0.25,
|
||||
),
|
||||
blurRadius: 8.08,
|
||||
offset: const Offset(0, 4.14),
|
||||
),
|
||||
@ -268,12 +343,16 @@ class PendingScreen extends ConsumerWidget {
|
||||
top: -4,
|
||||
right: -4,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: const BoxDecoration(
|
||||
padding: const EdgeInsets.all(
|
||||
4,
|
||||
),
|
||||
decoration:
|
||||
const BoxDecoration(
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
constraints:
|
||||
const BoxConstraints(
|
||||
minWidth: 16,
|
||||
minHeight: 16,
|
||||
),
|
||||
@ -283,25 +362,27 @@ class PendingScreen extends ConsumerWidget {
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
error: (_, __) =>
|
||||
const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
],
|
||||
]
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: height * 0.015),
|
||||
|
||||
@ -432,8 +513,37 @@ class PendingScreen extends ConsumerWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// Add payment functionality here
|
||||
onTap: () async {
|
||||
try {
|
||||
final response = await ref
|
||||
.read(apiRepositoryProvider)
|
||||
.payNow(
|
||||
id: int.parse(item.id.toString()),
|
||||
);
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
_razorpayService.openCheckout(
|
||||
key: response['razorpay_key'],
|
||||
amount: response['amount'],
|
||||
orderId: response['order_id'],
|
||||
description:
|
||||
"Payment for Service ID: ${item.id}",
|
||||
);
|
||||
} else {
|
||||
ValidationPopup().showErrorMessage(
|
||||
context,
|
||||
"Failed to initiate payment",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Error handled in repository/notifier or via snackbar
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
e.toString(),
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: height * 0.055,
|
||||
|
||||
16
pubspec.lock
16
pubspec.lock
@ -257,6 +257,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
eventify:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: eventify
|
||||
sha256: b829429f08586cc2001c628e7499e3e3c2493a1d895fd73b00ecb23351aa5a66
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -944,6 +952,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
razorpay_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: razorpay_flutter
|
||||
sha256: "2057bfaa769813f58bdc23b9cdc4d5ea493e92154f216f1d2edac507d0936b31"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.4"
|
||||
redux:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@ -41,6 +41,7 @@ dependencies:
|
||||
flutter_local_notifications: ^18.0.1
|
||||
curved_labeled_navigation_bar: ^2.0.6
|
||||
http_parser: ^4.1.2
|
||||
razorpay_flutter: ^1.4.4
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user