marco.pms.mobileapp/lib/controller/payment/payment_controller.dart

216 lines
6.2 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<void> startPayment({
required double amount,
required String description,
required BuildContext context,
}) async {
try {
isProcessing = true;
notifyListeners();
// Step 1: Create payment order
final result = await _paymentService.createOrder(amount);
logSafe("🧩 Raw response in PaymentController: $result");
// Step 2: Validate result before accessing keys
if (result == null) {
_showError("Failed to create order. Server returned null response.");
isProcessing = false;
notifyListeners();
return;
}
// Step 2: Handle both wrapped and unwrapped formats
final data = result['data'] ?? result;
final orderId = data?['orderId'];
final key = data?['key'];
if (orderId == null || key == null) {
_showError("Invalid response from server. Missing orderId or key.");
logSafe("💥 Invalid response structure: $result");
isProcessing = false;
notifyListeners();
return;
}
// Step 3: Initialize Razorpay if needed
_razorpay ??= Razorpay();
_razorpay!.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay!.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay!.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
// Step 4: Open Razorpay checkout
final options = {
'key': key,
'amount': (amount * 100).toInt(), // Razorpay expects amount in paise
'name': 'Marco',
'description': 'Subscription Payment',
'order_id': orderId,
'prefill': {'contact': '9999999999', 'email': 'test@marco.com'},
};
logSafe("🟠 Opening Razorpay checkout with options: $options");
_razorpay!.open(options);
} catch (e, s) {
_showError("Payment initialization failed. Please try again.");
logSafe("💥 Exception in startPayment: $e\n$s");
} finally {
isProcessing = false;
notifyListeners();
}
}
/// ==============================
/// 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,
);
}
}