marco.pms.mobileapp/lib/controller/payment/payment_controller.dart
2025-10-30 16:02:14 +05:30

239 lines
6.7 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
);
}
}