import 'dart:convert'; import 'package:siro_rider/constant/style.dart'; import 'package:siro_rider/views/home/map_page_passenger.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:local_auth/local_auth.dart'; import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../../constant/box_name.dart'; import '../../constant/links.dart'; import '../../main.dart'; import '../../print.dart'; import '../firebase/notification_service.dart'; import '../functions/crud.dart'; import 'paymob/e_cash_screen.dart'; import '../../views/home/my_wallet/payment_screen_mtn.dart'; import '../../views/home/my_wallet/payment_screen_cliq.dart'; class PaymentController extends GetxController { bool isLoading = false; bool isWalletChecked = true; bool isCashChecked = false; bool isWalletFound = false; bool isPromoSheetDialogue = false; final formKey = GlobalKey(); final promo = TextEditingController(); final walletphoneController = TextEditingController(); double totalPassenger = double.parse( Get.find().totalPassenger.toString()); int? selectedAmount = 0; List totalPassengerWalletDetails = []; String passengerTotalWalletAmount = ''; String ip = '1'; DateTime now = DateTime.now(); late int timestamp; void updateSelectedAmount(int value) { selectedAmount = value; update(); } void changePromoSheetDialogue() { isPromoSheetDialogue = !isPromoSheetDialogue; update(); } getPassengerWallet() async { isLoading = true; update(); await CRUD().getWallet( link: AppLink.getWalletByPassenger, payload: {'passenger_id': box.read(BoxName.passengerID)}).then((value) { box.write(BoxName.passengerWalletTotal, jsonDecode(value)['message'][0]['total'].toString()); }); isLoading = false; update(); } String paymentToken = ''; Future addPassengersWallet(String amount) async { try { await CRUD().postWallet(link: AppLink.addPassengersWallet, payload: { 'passenger_id': box.read(BoxName.passengerID).toString(), 'amount': amount, }); await getPassengerWallet(); } catch (e) { Log.print(e.toString()); } } Future generateTokenPassenger(String amount) async { var res = await CRUD().post(link: AppLink.addPaymentTokenPassenger, payload: { 'passengerId': box.read(BoxName.passengerID).toString(), 'amount': amount.toString(), }); var d = jsonDecode(res); return d['message']; } Future generateTokenDriver(String amount) async { var res = await CRUD().post(link: AppLink.addPaymentTokenDriver, payload: { 'driverID': Get.find().driverId, 'amount': amount.toString(), }); var d = jsonDecode(res); return d['message']; } Future payToDriverForCancelAfterAppliedAndHeNearYou( String rideId) async { { double costOfWaiting5Minute = box.read(BoxName.countryCode) == 'Egypt' ? (4 * .08) + (5 * 1) // 4 indicate foe 4 km ditance from driver start move to passenger : (4 * .06) + (5 * .06); //for Eygpt other like jordan .06 per minute var paymentTokenWait = await generateTokenDriver(costOfWaiting5Minute.toString()); var res = await CRUD().postWallet(link: AppLink.addDrivePayment, payload: { 'rideId': rideId, 'amount': costOfWaiting5Minute.toString(), 'payment_method': 'cancel-from-near', 'passengerID': box.read(BoxName.passengerID).toString(), 'token': paymentTokenWait, 'driverID': Get.find().driverId.toString(), }); var paymentTokenWait1 = await generateTokenDriver(costOfWaiting5Minute.toString()); var res1 = await CRUD() .postWallet(link: AppLink.addDriversWalletPoints, payload: { 'paymentID': 'rideId$rideId', 'amount': (costOfWaiting5Minute).toStringAsFixed(0), 'paymentMethod': 'cancel-from-near', 'token': paymentTokenWait1, 'driverID': Get.find().driverId.toString(), }); if (res != 'failure') { await NotificationService.sendNotification( category: 'Cancel', target: Get.find().driverToken.toString(), title: 'Cancel'.tr, body: 'Trip Cancelled. The cost of the trip will be added to your wallet.' .tr, isTopic: false, // Important: this is a token tone: 'cancel', driverList: [], ); } var paymentTokenWaitPassenger1 = await generateTokenPassenger((costOfWaiting5Minute * -1).toString()); await CRUD().postWallet(link: AppLink.addPassengersWallet, payload: { 'passenger_id': box.read(BoxName.passengerID).toString(), 'balance': (costOfWaiting5Minute * -1).toString(), 'token': paymentTokenWaitPassenger1, }); Get.offAll(const MapPagePassenger()); } } void onChangedPaymentMethodWallet(bool? value) { if (box.read(BoxName.passengerWalletTotal) == null || double.parse(box.read(BoxName.passengerWalletTotal).toString()) < totalPassenger) { isWalletChecked = false; isWalletChecked ? isCashChecked = true : isCashChecked = true; update(); } else { isWalletChecked = !isWalletChecked; isWalletChecked ? isCashChecked = false : isCashChecked = true; update(); } } void onChangedPaymentMethodCash(bool? value) { if (box.read(BoxName.passengerWalletTotal) == null || double.parse(box.read(BoxName.passengerWalletTotal)) < totalPassenger) { isWalletChecked = false; isCashChecked = !isCashChecked; isCashChecked ? isWalletChecked = false : isWalletChecked = false; update(); } else { isCashChecked = !isCashChecked; isCashChecked ? isWalletChecked = false : isWalletChecked = true; update(); } } Future payWithEcash(BuildContext context, String amount) async { try { // 1. يمكنك استخدام نفس طريقة التحقق بالبصمة إذا أردت bool isAvailable = await LocalAuthentication().isDeviceSupported(); if (isAvailable) { bool didAuthenticate = await LocalAuthentication().authenticate( localizedReason: 'Use Touch ID or Face ID to confirm payment', ); if (didAuthenticate) { // 2. استدعاء الـ Endpoint الجديد على السيرفر الخاص بك var res = await CRUD().postWallet( link: AppLink.payWithEcashPassenger, payload: { // ✅ أرسل البيانات التي يحتاجها السيرفر الخاص بـ ecash "amount": amount, "passengerId": box.read(BoxName.passengerID), }, ); // 3. التأكد من أن السيرفر أعاد رابط الدفع بنجاح if (res != null && res['status'] == 'success' && res['message'] != null) { final String paymentUrl = res['message']; // 4. الانتقال إلى شاشة الدفع الجديدة الخاصة بـ ecash Navigator.push( context, MaterialPageRoute( builder: (context) => EcashPaymentScreen(paymentUrl: paymentUrl), ), ); } else { // عرض رسالة خطأ في حال فشل السيرفر في إنشاء الرابط Get.defaultDialog( title: 'Error'.tr, content: Text( 'Failed to initiate payment. Please try again.'.tr, style: AppStyle.title, ), ); } } } } catch (e) { Get.defaultDialog( title: 'Error'.tr, content: Text( 'An error occurred during the payment process.'.tr, style: AppStyle.title, ), ); } } Future payWithSyriaTelWallet(String amount, String currency) async { // helper لفتح لودينغ بأمان Future _showLoading() async { if (!(Get.isDialogOpen ?? false)) { Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false); } } // helper لإغلاق أي حوار مفتوح void _closeAnyDialog() { if (Get.isDialogOpen ?? false) { Get.back(); } } await _showLoading(); try { final phone = box.read(BoxName.phoneWallet) as String; final passengerId = box.read(BoxName.passengerID).toString(); final formattedAmount = double.parse(amount).toStringAsFixed(0); Log.print("🚀 Syriatel payment start"); Log.print( "📦 Payload => passengerId:$passengerId amount:$formattedAmount phone:$phone"); // مصادقة حيوية (اختياري) final auth = LocalAuthentication(); if (await auth.isDeviceSupported()) { final ok = await auth.authenticate( localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع', ); if (!ok) { _closeAnyDialog(); Log.print("❌ User did not authenticate"); return; } } // 1) بدء عملية الدفع final startRaw = await CRUD().postWalletMtn( link: AppLink.payWithSyriatelStart, payload: { "amount": formattedAmount, "passengerId": passengerId, "phone": phone, "lang": box.read(BoxName.lang) ?? 'ar', }, ); Log.print("✅ Server response (start): $startRaw"); // تحويل الاستجابة إلى Map late final Map startRes; if (startRaw is Map) { startRes = startRaw; } else if (startRaw is String) { startRes = json.decode(startRaw) as Map; } else { throw Exception("Unexpected start response type"); } if (startRes['status'] != 'success') { final msg = (startRes['message'] ?? 'Failed to start payment').toString(); throw Exception(msg); } final messageData = startRes['message'] as Map; final transactionID = messageData['transactionID'].toString(); Log.print("📄 transactionID: $transactionID"); // // 2) اطلب من المستخدم إدخال OTP عبر Get.dialog (بدون context) _closeAnyDialog(); // أغلق اللودينغ أولاً final otpController = TextEditingController(); final otp = await Get.dialog( AlertDialog( title: const Text("أدخل كود التحقق"), content: TextField( controller: otpController, keyboardType: TextInputType.number, decoration: const InputDecoration(hintText: "كود OTP"), ), actions: [ TextButton( child: const Text("تأكيد"), onPressed: () => Get.back(result: otpController.text.trim()), ), TextButton( child: const Text("إلغاء"), onPressed: () => Get.back(result: null), ), ], ), barrierDismissible: false, ); if (otp == null || otp.isEmpty) { Log.print("❌ OTP not provided"); return; } Log.print("🔐 OTP: $otp"); await _showLoading(); // 3) تأكيد الدفع final confirmRaw = await CRUD().postWallet( link: AppLink.payWithSyriatelConfirm, payload: { "transactionID": transactionID, "otp": otp, }, ); _closeAnyDialog(); // أغلق اللودينغ Log.print("✅ Response (confirm): $confirmRaw"); late final Map confirmRes; if (confirmRaw is Map) { confirmRes = confirmRaw; } else if (confirmRaw is String) { confirmRes = json.decode(confirmRaw) as Map; } else { throw Exception("Unexpected confirm response type"); } if (confirmRes['status'] == 'success') { Get.defaultDialog( title: "✅ نجاح", content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."), ); } else { final msg = (confirmRes['message'] ?? 'فشل في تأكيد الدفع').toString(); Get.defaultDialog( title: "❌ فشل", content: Text(msg), ); } } catch (e, s) { Log.print("🔥 Error during Syriatel Wallet payment:\n$e\n$s"); _closeAnyDialog(); Get.defaultDialog( title: 'حدث خطأ', content: Text(e.toString().replaceFirst("Exception: ", "")), ); } } Future payWithMTNWallet(BuildContext context, String amount, String currency) async { try { final phone = walletphoneController.text.trim(); if (phone.isEmpty) { Get.defaultDialog(title: 'Error'.tr, content: Text('Please enter phone number'.tr)); return; } Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false); var res = await CRUD().postWalletMtn( link: AppLink.createMtnInvoice, payload: { "amount": amount, "user_id": box.read(BoxName.passengerID).toString(), "user_type": "passenger", "mtn_phone": phone, }, ); Get.back(); // close loading late final Map resMap; if (res is Map) { resMap = res; } else if (res is String) { resMap = json.decode(res) as Map; } else { throw Exception("Unexpected response type"); } if (resMap['status'] == 'success') { Get.to(() => PaymentScreenMtn( invoiceNumber: resMap['invoice_number'], mtnNumber: resMap['mtn_payment_number'] ?? '---', amount: double.parse(amount), )); } else { Get.defaultDialog( title: 'Error'.tr, content: Text(resMap['message']?.toString() ?? 'Failed to create invoice'.tr), ); } } catch (e) { if (Get.isDialogOpen ?? false) Get.back(); Get.defaultDialog(title: 'Error'.tr, content: Text(e.toString())); } } Future payWithClickWallet(BuildContext context, String amount, String currency) async { try { final phone = walletphoneController.text.trim(); if (phone.isEmpty) { Get.defaultDialog(title: 'Error'.tr, content: Text('Please enter phone number'.tr)); return; } Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false); var res = await CRUD().postWalletMtn( link: AppLink.createCliqInvoice, payload: { "amount": amount, "user_id": box.read(BoxName.passengerID).toString(), "user_type": "passenger", "click_phone": phone, }, ); Get.back(); // close loading late final Map resMap; if (res is Map) { resMap = res; } else if (res is String) { resMap = json.decode(res) as Map; } else { throw Exception("Unexpected response type"); } if (resMap['status'] == 'success') { Get.to(() => PaymentScreenCliq( invoiceNumber: resMap['invoice_number'], cliqAlias: resMap['cliq_alias'] ?? '---', amount: double.parse(amount), )); } else { Get.defaultDialog( title: 'Error'.tr, content: Text(resMap['message']?.toString() ?? 'Failed to create invoice'.tr), ); } } catch (e) { if (Get.isDialogOpen ?? false) Get.back(); Get.defaultDialog(title: 'Error'.tr, content: Text(e.toString())); } } @override void onInit() { timestamp = now.millisecondsSinceEpoch; if (box.read(BoxName.passengerWalletTotal) == null) { box.write(BoxName.passengerWalletTotal, '0'); } getPassengerWallet(); final localAuth = LocalAuthentication(); super.onInit(); } } class EcashDriverPaymentScreen extends StatefulWidget { final String paymentUrl; const EcashDriverPaymentScreen({required this.paymentUrl, Key? key}) : super(key: key); @override State createState() => _EcashDriverPaymentScreenState(); } class _EcashDriverPaymentScreenState extends State { late final WebViewController _controller; @override void initState() { super.initState(); _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (url) async { Log.print('Ecash Driver WebView URL Finished: $url'); await Get.find().getPassengerWallet(); // هنا نتحقق فقط من أن المستخدم عاد إلى صفحة النجاح // لا حاجة لاستدعاء أي API هنا، فالـ Webhook يقوم بكل العمل if (url.contains("success.php")) { showProcessingDialog(); } }, )) ..loadRequest(Uri.parse(widget.paymentUrl)); } // دالة لعرض رسالة "العملية قيد المعالجة" void showProcessingDialog() { showDialog( barrierDismissible: false, context: Get.context!, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), title: Row( children: [ Icon(Icons.check_circle, color: Colors.green), const SizedBox(width: 8), Text( "Payment Successful".tr, style: TextStyle( color: Colors.green, fontWeight: FontWeight.bold, ), ), ], ), content: Text( "Your payment is being processed and your wallet will be updated shortly." .tr, style: const TextStyle(fontSize: 16), ), actions: [ TextButton( onPressed: () { // أغلق مربع الحوار، ثم أغلق شاشة الدفع Navigator.pop(context); // Close the dialog Navigator.pop(context); // Close the payment screen }, style: TextButton.styleFrom( backgroundColor: Colors.green, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), ), child: Text( "OK".tr, style: const TextStyle(color: Colors.white), ), ), ], ); }, ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Complete Payment'.tr)), body: WebViewWidget(controller: _controller), ); } }