216 lines
5.9 KiB
Dart
216 lines
5.9 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';
|
||
|
||
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(context, "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(context, "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(context, "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(context, "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>? result;
|
||
try {
|
||
result = await _paymentService
|
||
.verifyPayment(
|
||
paymentId: response.paymentId!,
|
||
orderId: response.orderId!,
|
||
signature: response.signature!,
|
||
)
|
||
.timeout(const Duration(seconds: 10));
|
||
} catch (e) {
|
||
logSafe("⏱️ Verification timeout/error: $e", level: LogLevel.error);
|
||
}
|
||
|
||
isProcessing = false;
|
||
notifyListeners();
|
||
|
||
if (result != null && result['verified'] == true) {
|
||
_showDialog(
|
||
title: "Payment Successful 🎉",
|
||
message: "Your payment was verified successfully.",
|
||
success: true,
|
||
);
|
||
} else {
|
||
_showDialog(
|
||
title: "Verification Failed ❌",
|
||
message: "Payment completed but could not be verified.",
|
||
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;
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_cleanup();
|
||
super.dispose();
|
||
}
|
||
|
||
void disposeController() => _cleanup();
|
||
|
||
/// ==============================
|
||
/// HELPER UI FUNCTIONS
|
||
/// ==============================
|
||
void _showDialog({
|
||
required String title,
|
||
required String message,
|
||
required bool success,
|
||
}) {
|
||
if (_context == null) return;
|
||
|
||
showDialog(
|
||
context: _context!,
|
||
builder: (ctx) => AlertDialog(
|
||
title: Text(title),
|
||
content: Text(message),
|
||
actions: [
|
||
TextButton(
|
||
child: const Text("OK"),
|
||
onPressed: () {
|
||
Navigator.of(ctx).pop();
|
||
ScaffoldMessenger.of(_context!).showSnackBar(
|
||
SnackBar(
|
||
content: Text(success
|
||
? "Payment verified successfully!"
|
||
: "Payment failed or could not be verified."),
|
||
backgroundColor: success ? Colors.green : Colors.red,
|
||
),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showError(BuildContext context, String message) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text(message), backgroundColor: Colors.red),
|
||
);
|
||
}
|
||
}
|