239 lines
6.7 KiB
Dart
239 lines
6.7 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:razorpay_flutter/razorpay_flutter.dart';
|
||
import 'package:marco/helpers/services/payment_service.dart';
|
||
import 'package:marco/helpers/services/app_logger.dart';
|
||
import 'package:get/get.dart';
|
||
|
||
class PaymentController with ChangeNotifier {
|
||
Razorpay? _razorpay;
|
||
final PaymentService _paymentService = PaymentService();
|
||
|
||
bool isProcessing = false;
|
||
//BuildContext? _context;
|
||
|
||
/// ==============================
|
||
/// START PAYMENT (Safe init)
|
||
/// ==============================
|
||
Future<bool> startPayment({
|
||
required double amount,
|
||
required String description,
|
||
required BuildContext context,
|
||
}) async {
|
||
//_context = context;
|
||
isProcessing = true;
|
||
notifyListeners();
|
||
|
||
logSafe("🟢 Starting payment for ₹$amount - $description");
|
||
|
||
// ✅ Clear any old instance (prevents freeze on re-init)
|
||
_cleanup();
|
||
|
||
// ✅ Create order first (no login dependency)
|
||
Map<String, dynamic>? result;
|
||
try {
|
||
result = await _paymentService
|
||
.createOrder(amount)
|
||
.timeout(const Duration(seconds: 10));
|
||
} catch (e) {
|
||
logSafe("⏱️ API Timeout or Exception while creating order: $e",
|
||
level: LogLevel.error);
|
||
}
|
||
|
||
if (result == null) {
|
||
_showError("Failed to connect to server or timeout.");
|
||
isProcessing = false;
|
||
notifyListeners();
|
||
return false;
|
||
}
|
||
|
||
final orderId = result['data']?['orderId'];
|
||
final key = result['data']?['key'];
|
||
if (orderId == null || key == null) {
|
||
_showError("Invalid response from server.");
|
||
logSafe("Invalid response from server.");
|
||
|
||
isProcessing = false;
|
||
notifyListeners();
|
||
return false;
|
||
}
|
||
|
||
// ✅ Safe initialization of Razorpay (deferred)
|
||
try {
|
||
logSafe("🟡 Initializing Razorpay instance...");
|
||
_razorpay = Razorpay();
|
||
_razorpay?.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
|
||
_razorpay?.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
|
||
_razorpay?.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
|
||
logSafe("✅ Razorpay instance initialized successfully.");
|
||
} catch (e) {
|
||
logSafe("❌ Razorpay init failed: $e", level: LogLevel.error);
|
||
_showError("Payment system initialization failed.");
|
||
isProcessing = false;
|
||
notifyListeners();
|
||
return false;
|
||
}
|
||
|
||
// ✅ Prepare payment options
|
||
final options = {
|
||
'key': key,
|
||
'amount': (amount * 100).toInt(),
|
||
'name': 'Your Company Name',
|
||
'description': description,
|
||
'order_id': orderId,
|
||
'theme': {'color': '#0D47A1'},
|
||
'timeout': 120, // seconds
|
||
};
|
||
|
||
try {
|
||
logSafe("🟠 Opening Razorpay checkout with options: $options");
|
||
_razorpay!.open(options);
|
||
return true;
|
||
} catch (e) {
|
||
logSafe("❌ Error opening Razorpay: $e", level: LogLevel.error);
|
||
_showError("Error opening payment gateway.");
|
||
_cleanup();
|
||
isProcessing = false;
|
||
notifyListeners();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// ==============================
|
||
/// EVENT HANDLERS
|
||
/// ==============================
|
||
void _handlePaymentSuccess(PaymentSuccessResponse response) async {
|
||
logSafe("✅ Payment Success: ${response.paymentId}");
|
||
isProcessing = true;
|
||
notifyListeners();
|
||
|
||
Map<String, dynamic>? verificationResult;
|
||
|
||
try {
|
||
logSafe("🟢 Verifying payment via backend...");
|
||
verificationResult = await _paymentService
|
||
.verifyPayment(
|
||
paymentId: response.paymentId!,
|
||
orderId: response.orderId!,
|
||
signature: response.signature!,
|
||
)
|
||
.timeout(const Duration(seconds: 15));
|
||
} catch (e) {
|
||
logSafe("⏱️ Verification timeout/error: $e", level: LogLevel.error);
|
||
}
|
||
|
||
isProcessing = false;
|
||
notifyListeners();
|
||
|
||
// ✅ Handle backend verification response properly
|
||
if (verificationResult != null) {
|
||
// Example backend response: { "verified": true, "message": "Payment verified" }
|
||
final isVerified = verificationResult['verified'] == 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 {
|
||
_showDialog(
|
||
title: "Verification Failed ❌",
|
||
message: "Payment completed but backend verification returned null.",
|
||
success: false,
|
||
);
|
||
}
|
||
|
||
_cleanup();
|
||
}
|
||
|
||
void _handlePaymentError(PaymentFailureResponse response) {
|
||
logSafe("❌ Payment Failed: ${response.message}");
|
||
isProcessing = false;
|
||
notifyListeners();
|
||
|
||
_showDialog(
|
||
title: "Payment Failed ❌",
|
||
message: "Reason: ${response.message ?? 'Unknown error'}",
|
||
success: false,
|
||
);
|
||
|
||
_cleanup();
|
||
}
|
||
|
||
void _handleExternalWallet(ExternalWalletResponse response) {
|
||
logSafe("ℹ️ External Wallet Used: ${response.walletName}");
|
||
}
|
||
|
||
/// ==============================
|
||
/// CLEANUP / DISPOSE
|
||
/// ==============================
|
||
void _cleanup() {
|
||
try {
|
||
_razorpay?.clear();
|
||
} catch (_) {}
|
||
_razorpay = null;
|
||
logSafe("🧹 Razorpay instance cleaned up.");
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_cleanup();
|
||
super.dispose();
|
||
}
|
||
|
||
void disposeController() => _cleanup();
|
||
|
||
/// ==============================
|
||
/// HELPER UI FUNCTIONS
|
||
/// ==============================
|
||
void _showDialog({
|
||
required String title,
|
||
required String message,
|
||
required bool success,
|
||
}) {
|
||
if (Get.isDialogOpen == true) Get.back(); // Close any existing dialogs
|
||
|
||
Get.defaultDialog(
|
||
title: title,
|
||
middleText: message,
|
||
confirm: ElevatedButton(
|
||
onPressed: () {
|
||
Get.back(); // close dialog
|
||
Get.snackbar(
|
||
success ? "Payment Successful 🎉" : "Payment Failed ❌",
|
||
success
|
||
? "Payment verified successfully!"
|
||
: "Payment failed or could not be verified.",
|
||
backgroundColor: success ? Colors.green : Colors.red,
|
||
colorText: Colors.white,
|
||
snackPosition: SnackPosition.BOTTOM,
|
||
);
|
||
},
|
||
child: const Text("OK"),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showError(String message) {
|
||
if (Get.isDialogOpen == true) Get.back(); // Close any open dialog
|
||
Get.snackbar(
|
||
"Payment Error",
|
||
message,
|
||
backgroundColor: Colors.red,
|
||
colorText: Colors.white,
|
||
snackPosition: SnackPosition.BOTTOM,
|
||
);
|
||
}
|
||
}
|