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/tenant_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(); final TenantService _tenantService = TenantService(); bool isProcessing = false; // Pending values to use after payment verification String? _pendingTenantEnquireId; String? _pendingPlanId; /// ============================== /// START PAYMENT (Safe init) /// ============================== Future startPayment({ required double amount, required String description, required BuildContext context, String? tenantEnquireId, String? planId, }) async { try { isProcessing = true; notifyListeners(); // Save pending ids for post-payment subscription call _pendingTenantEnquireId = tenantEnquireId; _pendingPlanId = planId; // 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 3: 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 4: 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 5: Open Razorpay checkout final options = { 'key': key, 'amount': (amount * 100).toInt(), // Razorpay expects amount in paise 'name': 'Marco', 'description': description, '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? 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)); logSafe("๐Ÿงฉ Verification result: $verificationResult"); } catch (e) { logSafe("โฑ๏ธ Verification timeout/error: $e", level: LogLevel.error); } isProcessing = false; notifyListeners(); // Handle backend verification response properly if (verificationResult != null) { final isVerified = verificationResult['verified'] == true; final msg = verificationResult['message'] ?? ''; if (isVerified) { // If we have pending tenant and plan IDs, call subscription API await _maybeSubscribeTenantAfterPayment( verificationResult: verificationResult, razorpayResponse: response, ); _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(); } Future _maybeSubscribeTenantAfterPayment({ required Map verificationResult, required PaymentSuccessResponse razorpayResponse, }) async { if (_pendingTenantEnquireId == null || _pendingPlanId == null) { logSafe("โ„น๏ธ No pending tenant/plan id to subscribe."); return; } // Determine paymentDetailId โ€” prefer backend value if provided final paymentDetailId = verificationResult['paymentDetailId'] ?? verificationResult['paymentId'] ?? razorpayResponse.paymentId; final subscribePayload = { "tenantEnquireId": _pendingTenantEnquireId, "paymentDetailId": paymentDetailId, "planId": _pendingPlanId, }; logSafe("๐ŸŸข Subscribing tenant automatically: $subscribePayload"); final subResp = await _tenantService.subscribeTenant(subscribePayload); // Clear pending values immediately to avoid double-invoke _pendingTenantEnquireId = null; _pendingPlanId = null; if (subResp == null) { logSafe("โŒ subscribeTenant returned null"); _showDialog( title: "Subscription Failed", message: "Failed to call subscription API. Please contact support.", success: false, ); return; } final data = subResp['data'] ?? subResp; // backend success flag might be in different places; check robustly final bool success = (data is Map && (data['success'] == true || subResp['success'] == true)) || (subResp['statusCode'] == 200); if (success) { _showDialog( title: "Subscription Active โœ…", message: data['message'] ?? "Tenant subscribed successfully.", success: true, ); } else { _showDialog( title: "Subscription Failed", message: data['message'] ?? "Subscription API did not confirm success.", success: false, ); } } 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, ); } }