subscription
This commit is contained in:
parent
f70138238b
commit
4a1bd85435
@ -2,13 +2,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:razorpay_flutter/razorpay_flutter.dart';
|
import 'package:razorpay_flutter/razorpay_flutter.dart';
|
||||||
import 'package:marco/helpers/services/payment_service.dart';
|
import 'package:marco/helpers/services/payment_service.dart';
|
||||||
import 'package:marco/helpers/services/app_logger.dart';
|
import 'package:marco/helpers/services/app_logger.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class PaymentController with ChangeNotifier {
|
class PaymentController with ChangeNotifier {
|
||||||
Razorpay? _razorpay;
|
Razorpay? _razorpay;
|
||||||
final PaymentService _paymentService = PaymentService();
|
final PaymentService _paymentService = PaymentService();
|
||||||
|
|
||||||
bool isProcessing = false;
|
bool isProcessing = false;
|
||||||
BuildContext? _context;
|
//BuildContext? _context;
|
||||||
|
|
||||||
/// ==============================
|
/// ==============================
|
||||||
/// START PAYMENT (Safe init)
|
/// START PAYMENT (Safe init)
|
||||||
@ -18,7 +19,7 @@ class PaymentController with ChangeNotifier {
|
|||||||
required String description,
|
required String description,
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
}) async {
|
}) async {
|
||||||
_context = context;
|
//_context = context;
|
||||||
isProcessing = true;
|
isProcessing = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ class PaymentController with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
_showError(context, "Failed to connect to server or timeout.");
|
_showError("Failed to connect to server or timeout.");
|
||||||
isProcessing = false;
|
isProcessing = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return false;
|
return false;
|
||||||
@ -48,7 +49,9 @@ class PaymentController with ChangeNotifier {
|
|||||||
final orderId = result['data']?['orderId'];
|
final orderId = result['data']?['orderId'];
|
||||||
final key = result['data']?['key'];
|
final key = result['data']?['key'];
|
||||||
if (orderId == null || key == null) {
|
if (orderId == null || key == null) {
|
||||||
_showError(context, "Invalid response from server.");
|
_showError("Invalid response from server.");
|
||||||
|
logSafe("Invalid response from server.");
|
||||||
|
|
||||||
isProcessing = false;
|
isProcessing = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return false;
|
return false;
|
||||||
@ -64,7 +67,7 @@ class PaymentController with ChangeNotifier {
|
|||||||
logSafe("✅ Razorpay instance initialized successfully.");
|
logSafe("✅ Razorpay instance initialized successfully.");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logSafe("❌ Razorpay init failed: $e", level: LogLevel.error);
|
logSafe("❌ Razorpay init failed: $e", level: LogLevel.error);
|
||||||
_showError(context, "Payment system initialization failed.");
|
_showError("Payment system initialization failed.");
|
||||||
isProcessing = false;
|
isProcessing = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return false;
|
return false;
|
||||||
@ -87,7 +90,7 @@ class PaymentController with ChangeNotifier {
|
|||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logSafe("❌ Error opening Razorpay: $e", level: LogLevel.error);
|
logSafe("❌ Error opening Razorpay: $e", level: LogLevel.error);
|
||||||
_showError(context, "Error opening payment gateway.");
|
_showError("Error opening payment gateway.");
|
||||||
_cleanup();
|
_cleanup();
|
||||||
isProcessing = false;
|
isProcessing = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -103,15 +106,17 @@ class PaymentController with ChangeNotifier {
|
|||||||
isProcessing = true;
|
isProcessing = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
Map<String, dynamic>? result;
|
Map<String, dynamic>? verificationResult;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = await _paymentService
|
logSafe("🟢 Verifying payment via backend...");
|
||||||
|
verificationResult = await _paymentService
|
||||||
.verifyPayment(
|
.verifyPayment(
|
||||||
paymentId: response.paymentId!,
|
paymentId: response.paymentId!,
|
||||||
orderId: response.orderId!,
|
orderId: response.orderId!,
|
||||||
signature: response.signature!,
|
signature: response.signature!,
|
||||||
)
|
)
|
||||||
.timeout(const Duration(seconds: 10));
|
.timeout(const Duration(seconds: 15));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logSafe("⏱️ Verification timeout/error: $e", level: LogLevel.error);
|
logSafe("⏱️ Verification timeout/error: $e", level: LogLevel.error);
|
||||||
}
|
}
|
||||||
@ -119,16 +124,32 @@ class PaymentController with ChangeNotifier {
|
|||||||
isProcessing = false;
|
isProcessing = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
if (result != null && result['verified'] == true) {
|
// ✅ Handle backend verification response properly
|
||||||
_showDialog(
|
if (verificationResult != null) {
|
||||||
title: "Payment Successful 🎉",
|
// Example backend response: { "verified": true, "message": "Payment verified" }
|
||||||
message: "Your payment was verified successfully.",
|
final isVerified = verificationResult['verified'] == true;
|
||||||
success: true,
|
final msg = verificationResult['message'] ?? '';
|
||||||
);
|
|
||||||
|
if (isVerified) {
|
||||||
|
_showDialog(
|
||||||
|
title: "Payment Successful 🎉",
|
||||||
|
message:
|
||||||
|
msg.isNotEmpty ? msg : "Your payment was verified successfully.",
|
||||||
|
success: true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_showDialog(
|
||||||
|
title: "Verification Failed ❌",
|
||||||
|
message: msg.isNotEmpty
|
||||||
|
? msg
|
||||||
|
: "Payment completed but verification failed.",
|
||||||
|
success: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_showDialog(
|
_showDialog(
|
||||||
title: "Verification Failed ❌",
|
title: "Verification Failed ❌",
|
||||||
message: "Payment completed but could not be verified.",
|
message: "Payment completed but backend verification returned null.",
|
||||||
success: false,
|
success: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -162,6 +183,7 @@ class PaymentController with ChangeNotifier {
|
|||||||
_razorpay?.clear();
|
_razorpay?.clear();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
_razorpay = null;
|
_razorpay = null;
|
||||||
|
logSafe("🧹 Razorpay instance cleaned up.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -180,36 +202,37 @@ class PaymentController with ChangeNotifier {
|
|||||||
required String message,
|
required String message,
|
||||||
required bool success,
|
required bool success,
|
||||||
}) {
|
}) {
|
||||||
if (_context == null) return;
|
if (Get.isDialogOpen == true) Get.back(); // Close any existing dialogs
|
||||||
|
|
||||||
showDialog(
|
Get.defaultDialog(
|
||||||
context: _context!,
|
title: title,
|
||||||
builder: (ctx) => AlertDialog(
|
middleText: message,
|
||||||
title: Text(title),
|
confirm: ElevatedButton(
|
||||||
content: Text(message),
|
onPressed: () {
|
||||||
actions: [
|
Get.back(); // close dialog
|
||||||
TextButton(
|
Get.snackbar(
|
||||||
child: const Text("OK"),
|
success ? "Payment Successful 🎉" : "Payment Failed ❌",
|
||||||
onPressed: () {
|
success
|
||||||
Navigator.of(ctx).pop();
|
? "Payment verified successfully!"
|
||||||
ScaffoldMessenger.of(_context!).showSnackBar(
|
: "Payment failed or could not be verified.",
|
||||||
SnackBar(
|
backgroundColor: success ? Colors.green : Colors.red,
|
||||||
content: Text(success
|
colorText: Colors.white,
|
||||||
? "Payment verified successfully!"
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
: "Payment failed or could not be verified."),
|
);
|
||||||
backgroundColor: success ? Colors.green : Colors.red,
|
},
|
||||||
),
|
child: const Text("OK"),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showError(BuildContext context, String message) {
|
void _showError(String message) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (Get.isDialogOpen == true) Get.back(); // Close any open dialog
|
||||||
SnackBar(content: Text(message), backgroundColor: Colors.red),
|
Get.snackbar(
|
||||||
|
"Payment Error",
|
||||||
|
message,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,16 +93,26 @@ class ApiService {
|
|||||||
|
|
||||||
static dynamic _parseResponse(http.Response response, {String label = ''}) {
|
static dynamic _parseResponse(http.Response response, {String label = ''}) {
|
||||||
_log("$label Response: ${response.body}");
|
_log("$label Response: ${response.body}");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final json = jsonDecode(response.body);
|
final Map<String, dynamic> json = jsonDecode(response.body);
|
||||||
if (response.statusCode == 200 && json['success'] == true) {
|
|
||||||
return json['data'];
|
// ✅ Treat 200–299 range as success
|
||||||
|
final isHttpOk = response.statusCode >= 200 && response.statusCode < 300;
|
||||||
|
final isSuccess = json['success'] == true;
|
||||||
|
|
||||||
|
if (isHttpOk && isSuccess) {
|
||||||
|
return json['data']; // return full data block for use in payment
|
||||||
}
|
}
|
||||||
_log("API Error [$label]: ${json['message'] ?? 'Unknown error'}");
|
|
||||||
|
// ⚠️ Log any API-level error
|
||||||
|
_log(
|
||||||
|
"API Error [$label]: ${json['message'] ?? 'Unknown error'} (code ${response.statusCode})");
|
||||||
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_log("Response parsing error [$label]: $e");
|
_log("Response parsing error [$label]: $e");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static dynamic _parseResponseForAllData(http.Response response,
|
static dynamic _parseResponseForAllData(http.Response response,
|
||||||
|
|||||||
@ -21,12 +21,13 @@ class PaymentService {
|
|||||||
required String signature,
|
required String signature,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
logSafe("🟢 Calling verifyPayment API: orderId=$orderId, paymentId=$paymentId");
|
logSafe("🟢 Calling verifyPayment API...");
|
||||||
final response = await ApiService.verifyPayment(
|
final response = await ApiService.verifyPayment(
|
||||||
orderId: orderId,
|
orderId: orderId,
|
||||||
paymentId: paymentId,
|
paymentId: paymentId,
|
||||||
signature: signature,
|
signature: signature,
|
||||||
);
|
);
|
||||||
|
logSafe("✅ VerifyPayment API response: $response");
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logSafe("❌ Error in verifyPayment: $e", level: LogLevel.error);
|
logSafe("❌ Error in verifyPayment: $e", level: LogLevel.error);
|
||||||
|
|||||||
@ -27,22 +27,27 @@ import 'package:marco/view/subscriptions/subscriptions_screen.dart';
|
|||||||
class AuthMiddleware extends GetMiddleware {
|
class AuthMiddleware extends GetMiddleware {
|
||||||
@override
|
@override
|
||||||
RouteSettings? redirect(String? route) {
|
RouteSettings? redirect(String? route) {
|
||||||
// Public routes (no auth required)
|
// ✅ Public routes that don't require authentication
|
||||||
const publicRoutes = [
|
const publicRoutes = [
|
||||||
'/auth/login-option',
|
'/auth/login-option',
|
||||||
'/auth/login',
|
'/auth/login',
|
||||||
'/subscription', // 👈 Allow this route without auth
|
'/subscription',
|
||||||
'/payment',
|
'/payment',
|
||||||
'/select-tenant',
|
'/select-tenant',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Skip auth checks for public routes
|
// ✅ Allow any route that starts with these public routes
|
||||||
if (publicRoutes.contains(route)) return null;
|
if (route != null &&
|
||||||
|
publicRoutes.any((public) => route.startsWith(public))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Block others if not logged in
|
||||||
if (!AuthService.isLoggedIn) {
|
if (!AuthService.isLoggedIn) {
|
||||||
return const RouteSettings(name: '/auth/login-option');
|
return const RouteSettings(name: '/auth/login-option');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Ensure tenant is selected after login
|
||||||
if (!TenantService.isTenantSelected) {
|
if (!TenantService.isTenantSelected) {
|
||||||
return const RouteSettings(name: '/select-tenant');
|
return const RouteSettings(name: '/select-tenant');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -303,14 +303,14 @@ class _UserProfileBarState extends State<UserProfileBar>
|
|||||||
onTap: _onProfileTap,
|
onTap: _onProfileTap,
|
||||||
),
|
),
|
||||||
SizedBox(height: spacingHeight),
|
SizedBox(height: spacingHeight),
|
||||||
// _menuItemRow(
|
_menuItemRow(
|
||||||
// icon: LucideIcons.bell,
|
icon: LucideIcons.bell,
|
||||||
// label: 'Subscribe',
|
label: 'Subscribe',
|
||||||
// onTap: _onSubscribeTap,
|
onTap: _onSubscribeTap,
|
||||||
// iconColor: Colors.redAccent,
|
iconColor: Colors.redAccent,
|
||||||
// textColor: Colors.redAccent,
|
textColor: Colors.redAccent,
|
||||||
// ),
|
),
|
||||||
// SizedBox(height: spacingHeight),
|
SizedBox(height: spacingHeight),
|
||||||
_menuItemRow(
|
_menuItemRow(
|
||||||
icon: LucideIcons.settings,
|
icon: LucideIcons.settings,
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
@ -380,9 +380,9 @@ class _UserProfileBarState extends State<UserProfileBar>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// void _onSubscribeTap() {
|
void _onSubscribeTap() {
|
||||||
// Get.toNamed("/subscription");
|
Get.toNamed("/subscription");
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
void _onMpinTap() {
|
void _onMpinTap() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user