import 'dart:convert'; import 'package:Intaleq/constant/api_key.dart'; import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/controller/firebase/firbase_messge.dart'; import 'package:Intaleq/controller/payment/paymob/paymob_response.dart'; import 'package:Intaleq/views/home/map_page_passenger.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:flutter_paypal/flutter_paypal.dart'; import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:get/get.dart'; import 'package:local_auth/local_auth.dart'; import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../../constant/box_name.dart'; import '../../constant/colors.dart'; import '../../constant/info.dart'; import '../../constant/links.dart'; import '../../main.dart'; import '../../print.dart'; import '../firebase/notification_service.dart'; import '../functions/crud.dart'; import '../functions/encrypt_decrypt.dart'; import '../functions/toast.dart'; import 'paymob/e_cash_screen.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 = Get.find().totalPassenger; 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 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 addSeferWallet(String paymentMethod, point) async { var seferToken = await generateTokenPassenger(point); await CRUD().postWallet(link: AppLink.addSeferWallet, payload: { 'amount': point.toString(), 'paymentMethod': paymentMethod, 'passengerId': box.read(BoxName.passengerID).toString(), 'token': seferToken, 'driverId': 'passenger', }); } Future addPassengersWallet(String point) async { var token = await generateTokenPassenger(point); await CRUD().postWallet(link: AppLink.addPassengersWallet, payload: { 'passenger_id': box.read(BoxName.passengerID).toString(), 'balance': point, 'token': token, }); } 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') { // Get.find().sendNotificationToDriverMAP( // 'Cancel', // 'Trip Cancelled. The cost of the trip will be added to your wallet.' // .tr, // Get.find().driverToken, // [], // 'cancel', // ); 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().post(link: AppLink.addPassengersWallet, payload: { 'passenger_id': box.read(BoxName.passengerID).toString(), 'balance': (costOfWaiting5Minute * -1).toString(), 'token': paymentTokenWaitPassenger1, }); Get.offAll(const MapPagePassenger()); } } addPassengerWallet() async { isLoading = true; update(); await addSeferWallet('visa-in', selectedAmount.toString()); await addPassengersWallet(selectedAmount == 100 ? '100' : selectedAmount == 200 ? '215' : selectedAmount == 400 ? '450' : selectedAmount == 1000 ? '1140' : '0'); // getPassengerWallet(); isLoading = false; update(); } 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(); } } void applyPromoCodeToPassenger() async { //TAWJIHI CRUD().get(link: AppLink.getPassengersPromo, payload: { 'promo_code': promo.text, }).then((value) { var decod = jsonDecode(value); if (decod["status"] == "success") { var firstElement = decod["message"][0]; totalPassenger = totalPassenger - (totalPassenger * int.parse(firstElement['amount'])); MapPassengerController().promoTaken = true; update(); } }); } late String clientSecret; Future makePaymentStripe( double amount, String currency, Function method) async { var newAmount = (amount * 100).toInt(); try { // Check if local authentication is available bool isAvailable = await LocalAuthentication().isDeviceSupported(); if (isAvailable) { // Authenticate the user bool didAuthenticate = await LocalAuthentication().authenticate( localizedReason: 'Use Touch ID or Face ID to confirm payment', ); if (didAuthenticate) { // User authenticated successfully, proceed with payment clientSecret = await getClientSecret(newAmount.toString(), currency); await initializePaymentSheet(clientSecret); await Stripe.instance.presentPaymentSheet(); method(); } else { // Authentication failed, handle accordingly } } else { // Local authentication not available, proceed with payment without authentication clientSecret = await getClientSecret(newAmount.toString(), currency); await initializePaymentSheet(clientSecret); await Stripe.instance.presentPaymentSheet(); method(); } } catch (e) { rethrow; } } Future initializePaymentSheet(String clientSecret) async { await Stripe.instance.initPaymentSheet( paymentSheetParameters: SetupPaymentSheetParameters( // intentConfiguration: IntentConfiguration.fromJson({}), // applePay: const PaymentSheetApplePay(merchantCountryCode: 'US'), // googlePay: const PaymentSheetGooglePay(merchantCountryCode: 'US'), paymentIntentClientSecret: clientSecret, merchantDisplayName: AppInformation.appName, billingDetails: BillingDetails( name: box.read(BoxName.nameDriver) == null ? (box.read(BoxName.name).toString().split(' ')[0]).toString() : box.read(BoxName.nameDriver).toString(), email: box.read(BoxName.emailDriver) == null ? box.read(BoxName.email).toString() : box.read(BoxName.emailDriver).toString(), phone: box.read(BoxName.phoneDriver) == null ? box.read(BoxName.phone).toString() : box.read(BoxName.phoneDriver).toString(), address: Address( city: 'city', country: box.read(BoxName.countryCode), //'United States' line1: '', line2: '', postalCode: '12345', state: box.read(BoxName.countryCode) // 'Boston' )), allowsDelayedPaymentMethods: true, customerEphemeralKeySecret: Stripe.merchantIdentifier, appearance: const PaymentSheetAppearance( shapes: PaymentSheetShape(borderRadius: 12), colors: PaymentSheetAppearanceColors( background: AppColor.secondaryColor, ), ), billingDetailsCollectionConfiguration: const BillingDetailsCollectionConfiguration( name: CollectionMode.automatic, phone: CollectionMode.automatic, email: CollectionMode.automatic, // address: CollectionMode.automatic, ), ), ); } Future getClientSecret(String amount, currency) async { var res = await CRUD().postStripe( link: 'https://api.stripe.com/v1/payment_intents', payload: { 'amount': amount, 'currency': currency, 'payment_method_types[0]': 'card' }, ); // Convert the res object to a JSON object final jsonResponse = jsonDecode(res); // Check if the client_secret property exists and is not null if (jsonResponse.containsKey('client_secret') && jsonResponse['client_secret'] != null) { // Return the client_secret property return jsonResponse['client_secret'] as String; } else { throw Exception('Failed to fetch client secret'); } } Future configure3dSecureFuture() async { await Stripe.instance.openApplePaySetup(); } Future makePaymentPayPal(BuildContext context) async { try { // Check if local authentication is available bool isAvailable = await LocalAuthentication().isDeviceSupported(); if (isAvailable) { // Authenticate the user bool didAuthenticate = await LocalAuthentication().authenticate( localizedReason: 'Use Touch ID or Face ID to confirm payment', ); if (didAuthenticate) { // User authenticated successfully, proceed with payment if (selectedAmount != 0) { changePromoSheetDialogue(); Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) => UsePaypal( sandboxMode: true, clientId: AK.payPalClientId, secretKey: AK.payPalSecret, returnURL: AppInformation.website, cancelURL: "${AppInformation.website}/cancel", transactions: [ { "amount": { //sb-opsju26682403@personal.example.com "total": '$selectedAmount', "currency": box.read(BoxName.countryCode) == 'Egypt' ? 'EGP' : "JOD", "details": { "subtotal": '$selectedAmount', "shipping": '0', "shipping_discount": 0 } }, "description": "The payment transaction description.", "payment_options": const { "allowed_payment_method": "INSTANT_FUNDING_SOURCE" }, "item_list": { "items": [ { "name": "${AppInformation.appName} Wallet ", "quantity": 1, "price": '$selectedAmount', "currency": "USD" } ], // shipping address is not required though "shipping_address": const { "recipient_name": "${AppInformation.appName} Wallet", "line1": "Shafa Badran", "line2": "", "city": "Amman", "country_code": "JO", "postal_code": "13112", "phone": "+962798583052", "state": "Amman" }, } } ], note: "Contact us for any questions on your order.".tr, onSuccess: (Map params) async { addPassengerWallet(); changePromoSheetDialogue(); await getPassengerWallet(); }, onError: (error) { Toast.show(context, ' $error'.tr, AppColor.redColor); }, onCancel: (params) { Toast.show(context, 'Pyament Cancelled .'.tr, AppColor.yellowColor); }), ), ); } else { Toast.show(context, 'You will choose one of above !'.tr, AppColor.redColor); } } else { // Authentication failed, handle accordingly } } } catch (e) { rethrow; } } Map licenseDetailsMap = {}; Future getLicenseInfo() async { var res = await CRUD().get( link: AppLink.getLicense, payload: {'driverID': box.read(BoxName.driverID)}); licenseDetailsMap = jsonDecode(res); } Future createConnectAccount() async { String url = 'https://api.stripe.com/v1/accounts'; await getLicenseInfo(); DateTime dob = DateTime.parse(licenseDetailsMap['message'][0]['dateOfBirth']); int currentTimestamp = (DateTime.now().millisecondsSinceEpoch / 1000).round(); int day = dob.day; int month = dob.month; int year = dob.year; await getIpAddress(); final body = { "type": "custom", "business_profile[name]": box.read(BoxName.nameDriver), "business_profile[product_description]": "Captain", "business_profile[support_address][city]": "San Francisco", "business_profile[support_address][country]": 'US', "business_profile[support_address][line1]": licenseDetailsMap['message'][0]['address'].toString().trim()[0], "business_profile[support_address][postal_code]": licenseDetailsMap['message'][0]['postalCode'], "business_profile[support_address][state]": "MA", "business_profile[support_email]": "support@sefer.live", "business_profile[support_phone]": "555-123-4567", "business_profile[url]": "https://sefer.live", "business_type": "individual", "capabilities[card_payments][requested]": "true", "capabilities[transfers][requested]": "true", "company[address][city]": "ATTLEBORO", "company[address][country]": "US", "company[address][line1]": "1249 NEWPORT AVE", "company[address][postal_code]": "02703 ", "company[address][state]": "MA", "company[name]": AppInformation.companyName, "country": "us", "default_currency": "usd", "email": "support@sefer.live", // "individual[ssn]": "123-45-6789", // "individual[id_number]": licenseDetailsMap['message'][0]['documentNo'], // "individual[id_type]": "drivers_license", // "individual[address][city]": "ATTLEBORO", "individual[address][country]": "US", "individual[address][line1]": licenseDetailsMap['message'][0]['address'], // "individual[address][postal_code]": licenseDetailsMap['message'][0] // ['postalCode'], "individual[address][state]": "MA", // "individual[ssn_last_4]": '1111', //////// "individual[dob][day]": day.toString(), "individual[dob][month]": month.toString(), "individual[dob][year]": year.toString(), "individual[email]": box.read(BoxName.emailDriver), "individual[first_name]": licenseDetailsMap['message'][0]['name'].toString().split(' ')[0], "individual[gender]": licenseDetailsMap['message'][0]['sex'] == 'M' ? 'male' : 'female', "individual[last_name]": licenseDetailsMap['message'][0]['name'].toString().split(' ')[1], // "individual[phone]": box.read(BoxName.phoneDriver),//////////// "tos_acceptance[date]": currentTimestamp.toString(), "tos_acceptance[ip]": ip.toString() }; final response = await CRUD().postStripe( link: url, payload: body, ); final responseData = jsonDecode(response); final accountId = responseData['id']; box.write(BoxName.accountIdStripeConnect, accountId); await updateCaptainAccountBank(); return accountId; } Future updateCaptainAccountBank() async { var res = await CRUD().post(link: AppLink.updateAccountBank, payload: { 'id': box.read(BoxName.driverID), 'accountBank': box.read(BoxName.accountIdStripeConnect), }); if (jsonDecode(res)['status'] == 'success') { Get.snackbar('Account Updated', ''); } } Future createTransactionToCaptain( String amount, String account) async { String url = 'https://api.stripe.com/v1/transfers'; final body = { 'amount': amount, //amount 'currency': 'usd', 'destination': account //'acct_1OKIjQRgcWrsdyDT' //account id }; final response = await CRUD().postStripe( link: url, payload: body, ); final responseData = jsonDecode(response); final transactionId = responseData['id']; return transactionId; } Future getIpAddress() async { var url = Uri.parse('https://api.ipify.org?format=json'); var response = await http.get(url); if (response.statusCode == 200) { ip = jsonDecode(response.body)['ip']; } else {} } // 'https://accept.paymob.com/unifiedcheckout/?publicKey=egy_pk_live_mbjDC9Ni6FSHKmsz8sOHiVk2xd7oWRve&clientSecret=egy_sk_live_c0904e9cf04506ae64f818d4e075b4a957e3713fdf7a22cb7da30a29e72442b5' // أضف هذا الرابط إلى ملف AppLink الخاص بك // هذه هي الدالة الجديدة التي ستستخدمها لبدء الدفع 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 payWithEcashDriver(BuildContext context, String amount) async { // try { // // يمكنك استخدام نفس طريقة التحقق بالبصمة إذا أردت // bool isAvailable = await LocalAuthentication().isDeviceSupported(); // if (isAvailable) { // bool didAuthenticate = await LocalAuthentication().authenticate( // localizedReason: 'Use Touch ID or Face ID to confirm payment'.tr, // ); // if (didAuthenticate) { // // استدعاء الـ Endpoint الجديد على السيرفر الخاص بك // var res = await CRUD().postWallet( // link: AppLink.payWithEcashPassenger, // // link: // // 'https://wl.tripz-egypt.com/v1/main/ride/ecash/driver/payWithEcash.php', // payload: { // // أرسل البيانات التي يحتاجها السيرفر // "amount": amount, // // "driverId": box.read(BoxName.driverID), // تأكد من وجود هذا المتغير // "passengerId": // box.read(BoxName.passengerID), // تأكد من وجود هذا المتغير // }, // ); // // التأكد من أن السيرفر أعاد رابط الدفع بنجاح // if (res != null && // res['status'] == 'success' && // res['message'] != null) { // final String paymentUrl = res['message']; // // الانتقال إلى شاشة الدفع الجديدة الخاصة بـ ecash للسائق // Navigator.push( // context, // MaterialPageRoute( // builder: (context) => // EcashDriverPaymentScreen(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, // ), // ); // } // } /// شاشة جديدة ومبسطة خاصة بدفع السائقين عبر ecash // Future payWithMTNWallet( // BuildContext context, String amount, String currency) async { // // خزن سياق علوي آمن من البداية // final BuildContext safeContext = // Get.overlayContext ?? Get.context ?? context; // // سبينر تحميل // if (!(Get.isDialogOpen ?? false)) { // Get.dialog(const Center(child: CircularProgressIndicator()), // barrierDismissible: false); // } // try { // final phone = box.read(BoxName.phoneWallet) as String; // final passengerID = box.read(BoxName.passengerID).toString(); // final formattedAmount = double.parse(amount).toStringAsFixed(0); // print("🚀 بدء عملية دفع MTN"); // print( // "📦 Payload: passengerID: $passengerID, amount: $formattedAmount, phone: $phone"); // // التحقق بالبصمة (اختياري) + حماية من الـ await // final localAuth = LocalAuthentication(); // final isAuthSupported = await localAuth.isDeviceSupported(); // if (isAuthSupported) { // final didAuth = await localAuth.authenticate( // localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع', // ); // if (!didAuth) { // if (Get.isDialogOpen == true) Get.back(); // print("❌ المستخدم لم يؤكد بالبصمة/الوجه"); // return; // } // } // // 1) بدء الدفع // final responseData = await CRUD().postWalletMtn( // link: AppLink.payWithMTNStart, // payload: { // "amount": formattedAmount, // "passengerId": passengerID, // "phone": phone, // "lang": box.read(BoxName.lang) ?? 'ar', // }, // ); // // print("✅ استجابة الخادم (mtn_start_payment.php):"); // // print(responseData); // Log.print('responseData: ${responseData}'); // // فحص الاستجابة بقوة // late final Map startRes; // if (responseData is Map) { // startRes = responseData; // } else if (responseData is String) { // startRes = json.decode(responseData) as Map; // } else { // throw Exception("تم استلام نوع بيانات غير متوقع من الخادم."); // } // if (startRes['status'] != 'success') { // final errorMsg = startRes['message']['Error']?.toString().tr ?? // "فشل بدء عملية الدفع. حاول مرة أخرى."; // throw Exception(errorMsg); // } // final messageData = startRes["message"] as Map; // final invoiceNumber = messageData["invoiceNumber"].toString(); // final operationNumber = messageData["operationNumber"].toString(); // final guid = messageData["guid"].toString(); // // print( // // "📄 invoiceNumber: $invoiceNumber, 🔢 operationNumber: $operationNumber, 🧭 guid: $guid"); // // أغلق السبينر قبل إظهار حوار OTP // if (Get.isDialogOpen == true) Get.back(); // // 2) إدخال OTP بـ Get.defaultDialog (لا يستخدم context قابل للتلف) // String otpInput = ""; // await Get.defaultDialog( // title: "أدخل كود التحقق", // barrierDismissible: false, // content: TextField( // keyboardType: TextInputType.number, // decoration: const InputDecoration(hintText: "كود OTP"), // onChanged: (v) => otpInput = v, // ), // confirm: TextButton( // onPressed: () { // if (otpInput.isEmpty || // otpInput.length < 4 || // otpInput.length > 8) { // Get.snackbar("تنبيه", "أدخل كود OTP صحيح (4–8 أرقام)"); // return; // } // Get.back(result: otpInput); // }, // child: const Text("تأكيد"), // ), // cancel: TextButton( // onPressed: () => Get.back(result: null), // child: const Text("إلغاء"), // ), // ).then((res) => otpInput = (res ?? "") as String); // if (otpInput.isEmpty) { // print("❌ لم يتم إدخال OTP"); // return; // } // print("🔐 تم إدخال OTP: $otpInput"); // // سبينر أثناء التأكيد // Get.dialog(const Center(child: CircularProgressIndicator()), // barrierDismissible: false); // // 3) تأكيد الدفع // final confirmRes = await CRUD().postWalletMtn( // link: AppLink.payWithMTNConfirm, // payload: { // "invoiceNumber": invoiceNumber, // "operationNumber": operationNumber, // "guid": guid, // "otp": otpInput, // "phone": phone, // "lang": box.read(BoxName.lang) ?? 'ar', // }, // ); // if (Get.isDialogOpen == true) Get.back(); // // print("✅ استجابة mtn_confirm.php:"); // // Log.print('confirmRes: ${confirmRes}'); // final ok = (confirmRes is Map && confirmRes['status'] == 'success'); // if (ok) { // Get.defaultDialog( // title: "✅ نجاح", // content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."), // ); // await getPassengerWallet(); // } else { // final errorMsg = (confirmRes['message']['message']?.toString()) ?? // "فشل في تأكيد الدفع"; // Get.defaultDialog(title: "❌ فشل", content: Text(errorMsg.tr)); // } // } catch (e, s) { // print("🔥 خطأ أثناء الدفع عبر MTN:"); // print(e); // print(s); // if (Get.isDialogOpen == true) Get.back(); // Get.defaultDialog( // title: 'حدث خطأ', // content: Text(e.toString().replaceFirst("Exception: ", "")), // ); // } // } 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); print("🚀 Syriatel payment start"); print( "📦 Payload => passengerId:$passengerId amount:$formattedAmount phone:$phone"); // مصادقة حيوية (اختياري) final auth = LocalAuthentication(); if (await auth.isDeviceSupported()) { final ok = await auth.authenticate( localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع', ); if (!ok) { _closeAnyDialog(); 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', }, ); 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(); 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) { print("❌ OTP not provided"); return; } print("🔐 OTP: $otp"); await _showLoading(); // 3) تأكيد الدفع final confirmRaw = await CRUD().postWallet( link: AppLink.payWithSyriatelConfirm, payload: { "transactionID": transactionID, "otp": otp, }, ); _closeAnyDialog(); // أغلق اللودينغ 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) { print("🔥 Error during Syriatel Wallet payment:\n$e\n$s"); _closeAnyDialog(); Get.defaultDialog( title: 'حدث خطأ', content: Text(e.toString().replaceFirst("Exception: ", "")), ); } } @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 { 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), ); } }