import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:local_auth/local_auth.dart'; import 'package:sefer_driver/constant/colors.dart'; import 'package:sefer_driver/constant/style.dart'; import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart'; import 'package:sefer_driver/controller/payment/payment_controller.dart'; import 'package:sefer_driver/controller/payment/smsPaymnet/payment_services.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../../../constant/box_name.dart'; import '../../../constant/links.dart'; import '../../../controller/functions/crud.dart'; import '../../../controller/payment/mtn_new/mtn_payment_new_screen.dart'; import '../../../main.dart'; import '../../../print.dart'; import '../../widgets/elevated_btn.dart'; import '../../widgets/my_textField.dart'; import 'ecash.dart'; class PointsCaptain extends StatelessWidget { PaymentController paymentController = Get.put(PaymentController()); CaptainWalletController captainWalletController = Get.put(CaptainWalletController()); PointsCaptain({ super.key, required this.kolor, required this.countPoint, required this.pricePoint, }); final Color kolor; final String countPoint; double pricePoint; @override Widget build(BuildContext context) { return InkWell( onTap: () async { Get.defaultDialog( title: 'Which method you will pay'.tr, titleStyle: AppStyle.title, content: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '${'you can buy '.tr}$countPoint ${'L.S'.tr}${'by '.tr}${'$pricePoint'.tr}', style: AppStyle.title, ), // Add some spacing between buttons GestureDetector( onTap: () async { Get.back(); payWithEcashDriver(context, pricePoint.toString()); }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Pay with Debit Card'.tr), const SizedBox(width: 10), Icon(Icons.credit_card_sharp, color: AppColor.blueColor, size: 70), ], )), // GestureDetector( // onTap: () async { // Get.back(); // Get.defaultDialog( // barrierDismissible: false, // title: 'Insert Wallet phone number'.tr, // content: Form( // key: paymentController.formKey, // child: MyTextForm( // controller: // paymentController.walletphoneController, // label: 'Insert Wallet phone number'.tr, // hint: '963941234567', // type: TextInputType.phone)), // confirm: MyElevatedButton( // title: 'OK'.tr, // onPressed: () async { // Get.back(); // if (paymentController.formKey.currentState! // .validate()) { // box.write( // BoxName.phoneWallet, // paymentController // .walletphoneController.text); // await payWithMTNWallet( // context, pricePoint.toString(), 'SYP'); // } // })); // }, // child: Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // Text('Pay by MTN Wallet'.tr), // const SizedBox(width: 10), // Image.asset( // 'assets/images/cashMTN.png', // width: 70, // height: 70, // fit: BoxFit.fill, // ), // ], // )), GestureDetector( onTap: () async { Get.back(); Get.defaultDialog( barrierDismissible: false, title: 'Insert Wallet phone number'.tr, content: Form( key: paymentController.formKey, child: MyTextForm( controller: paymentController.walletphoneController, label: 'Insert Wallet phone number'.tr, hint: '963991234567', type: TextInputType.phone)), confirm: MyElevatedButton( title: 'OK'.tr, onPressed: () async { Get.back(); if (paymentController.formKey.currentState! .validate()) { box.write( BoxName.phoneWallet, paymentController .walletphoneController.text); await payWithSyriaTelWallet( context, pricePoint.toString(), 'SYP'); } })); }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Pay by Syriatel Wallet'.tr), const SizedBox(width: 10), Image.asset( 'assets/images/syriatel.jpeg', width: 70, height: 70, fit: BoxFit.fill, ), ], )), GestureDetector( onTap: () async { Get.back(); Get.defaultDialog( barrierDismissible: false, title: 'Insert Wallet phone number'.tr, content: Form( key: paymentController.formKey, child: MyTextForm( controller: paymentController.walletphoneController, label: 'Insert Wallet phone number'.tr, hint: '963941234567', type: TextInputType.phone)), confirm: MyElevatedButton( title: 'OK'.tr, onPressed: () async { Get.back(); if (paymentController.formKey.currentState! .validate()) { box.write( BoxName.phoneWallet, paymentController .walletphoneController.text); // await payWithSyriaTelWallet( // context, pricePoint.toString(), 'SYP'); bool isAuthSupported = await LocalAuthentication() .isDeviceSupported(); if (isAuthSupported) { bool didAuthenticate = await LocalAuthentication() .authenticate( localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع', ); if (!didAuthenticate) { if (Get.isDialogOpen ?? false) Get.back(); print( "❌ User did not authenticate with biometrics"); return; } } Get.to(() => PaymentScreenSmsProvider( amount: pricePoint)); } })); }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Pay by Sham Cash'.tr), const SizedBox(width: 10), Image.asset( 'assets/images/shamCash.png', width: 70, height: 70, fit: BoxFit.fill, ), ], )), // GestureDetector( // onTap: () async { // Get.back(); // Get.defaultDialog( // barrierDismissible: false, // title: 'Insert Wallet phone number'.tr, // content: Form( // key: paymentController.formKey, // child: MyTextForm( // controller: // paymentController.walletphoneController, // label: 'Insert Wallet phone number'.tr, // hint: '963941234567', // type: TextInputType.phone)), // confirm: MyElevatedButton( // title: 'OK'.tr, // onPressed: () async { // Get.back(); // if (paymentController.formKey.currentState! // .validate()) { // box.write( // BoxName.phoneWallet, // paymentController // .walletphoneController.text); // // await payWithSyriaTelWallet( // // context, pricePoint.toString(), 'SYP'); // bool isAuthSupported = // await LocalAuthentication() // .isDeviceSupported(); // if (isAuthSupported) { // bool didAuthenticate = // await LocalAuthentication() // .authenticate( // localizedReason: // 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع', // ); // if (!didAuthenticate) { // if (Get.isDialogOpen ?? false) Get.back(); // print( // "❌ User did not authenticate with biometrics"); // return; // } // } // Get.to(() => PaymentScreenMtn( // amount: pricePoint, // userType: 'Driver', // )); // } // })); // }, // child: Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // Text('Pay by MTN Wallet'.tr), // const SizedBox(width: 10), // Image.asset( // 'assets/images/cashMTN.png', // width: 70, // height: 70, // fit: BoxFit.fill, // ), // ], // )), ], )); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 8), child: Container( width: Get.width * .22, height: Get.width * .22, margin: const EdgeInsets.all(4), decoration: BoxDecoration( gradient: LinearGradient( colors: [ kolor.withOpacity(0.3), kolor, kolor.withOpacity(0.7), kolor, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), border: Border.all(color: AppColor.accentColor), borderRadius: BorderRadius.circular(12), shape: BoxShape.rectangle, ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text( '$countPoint ${'L.S'.tr}', style: AppStyle.subtitle .copyWith(color: AppColor.secondaryColor), ), Text( '$pricePoint ${'L.S'.tr}', style: AppStyle.title.copyWith(color: AppColor.secondaryColor), textAlign: TextAlign.center, ), ], ), ), ), ), ); } } class PaymentScreen extends StatefulWidget { final String iframeUrl; final String countPrice; const PaymentScreen( {required this.iframeUrl, Key? key, required this.countPrice}) : super(key: key); @override State createState() => _PaymentScreenState(); } class _PaymentScreenState extends State { late final WebViewController _controller; final controller = Get.find(); @override void initState() { super.initState(); _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (url) { if (url.contains("success")) { _fetchPaymentStatus(); // ✅ استدعاء الويب هوك بعد نجاح الدفع } else if (url.contains("failed")) { showCustomDialog( title: "Error".tr, message: 'Payment Failed'.tr, // يتم جلب رسالة الخطأ من الخادم isSuccess: false, ); } }, )) ..loadRequest(Uri.parse(widget.iframeUrl)); } Future _fetchPaymentStatus() async { final String userId = box.read(BoxName.phoneDriver); await Future.delayed(const Duration(seconds: 2)); try { final response = await CRUD().postWallet( link: AppLink.paymetVerifyDriver, payload: { 'user_id': userId, 'driverID': box.read(BoxName.driverID), 'paymentMethod': 'visa-in', }, ); if (response != 'failure' && response != 'token_expired') { if (response['status'] == 'success') { final payment = response['message']; final amount = payment['amount'].toString(); final bonus = payment['bonus'].toString(); final paymentID = payment['paymentID'].toString(); await controller.getCaptainWalletFromBuyPoints(); showCustomDialog( title: "payment_success".tr, message: "${"transaction_id".tr}: $paymentID\n${"amount_paid".tr}: $amount EGP\n${"bonus_added".tr}: $bonus ${"points".tr}", isSuccess: true, ); } else { showCustomDialog( title: "transaction_failed".tr, message: response['message'].toString(), isSuccess: false, ); } } else { showCustomDialog( title: "connection_failed".tr, message: response.toString(), isSuccess: false, ); } } catch (e) { showCustomDialog( title: "server_error".tr, message: "server_error_message".tr, isSuccess: false, ); } } void showCustomDialog({ required String title, required String message, required bool isSuccess, }) { showDialog( barrierDismissible: false, context: Get.context!, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), title: Row( children: [ Icon( isSuccess ? Icons.check_circle : Icons.error, color: isSuccess ? Colors.green : Colors.red, ), const SizedBox(width: 8), Text( title, style: TextStyle( color: isSuccess ? Colors.green : Colors.red, fontWeight: FontWeight.bold, ), ), ], ), content: Text( message, style: const TextStyle(fontSize: 16), ), actions: [ TextButton( onPressed: () { Navigator.pop(context); Navigator.pop(context); }, style: TextButton.styleFrom( backgroundColor: isSuccess ? Colors.green : Colors.red, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), ), child: Text( "OK", style: const TextStyle(color: Colors.white), ), ), ], ); }, ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('إتمام الدفع')), body: WebViewWidget(controller: _controller), ); } } class PaymentScreenWallet extends StatefulWidget { final String iframeUrl; final String countPrice; const PaymentScreenWallet( {required this.iframeUrl, Key? key, required this.countPrice}) : super(key: key); @override State createState() => _PaymentScreenWalletState(); } class _PaymentScreenWalletState extends State { late final WebViewController _controller; final controller = Get.find(); @override void initState() { super.initState(); _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (url) { if (url.contains("success")) { _fetchPaymentStatus(); // ✅ استدعاء الويب هوك بعد نجاح الدفع } else if (url.contains("failed")) { showCustomDialog( title: "Error".tr, message: 'Payment Failed'.tr, // يتم جلب رسالة الخطأ من الخادم isSuccess: false, ); } }, )) ..loadRequest(Uri.parse(widget.iframeUrl)); } Future _fetchPaymentStatus() async { final String userId = '+963' + box.read(BoxName.phoneWallet); await Future.delayed(const Duration(seconds: 2)); try { final response = await CRUD().postWallet( link: AppLink.paymetVerifyDriver, payload: { 'user_id': userId, 'driverID': box.read(BoxName.driverID), 'paymentMethod': 'visa-in', }, ); if (response != 'failure' && response != 'token_expired') { if (response['status'] == 'success') { final payment = response['message']; final amount = payment['amount'].toString(); final bonus = payment['bonus'].toString(); final paymentID = payment['paymentID'].toString(); await controller.getCaptainWalletFromBuyPoints(); showCustomDialog( title: "payment_success".tr, message: "${"transaction_id".tr}: $paymentID\n${"amount_paid".tr}: $amount EGP\n${"bonus_added".tr}: $bonus ${"points".tr}", isSuccess: true, ); } else { showCustomDialog( title: "transaction_failed".tr, message: response['message'].toString(), isSuccess: false, ); } } else { showCustomDialog( title: "connection_failed".tr, message: response.toString(), isSuccess: false, ); } } catch (e) { showCustomDialog( title: "server_error".tr, message: "server_error_message".tr, isSuccess: false, ); } } void showCustomDialog({ required String title, required String message, required bool isSuccess, }) { showDialog( barrierDismissible: false, context: Get.context!, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), title: Row( children: [ Icon( isSuccess ? Icons.check_circle : Icons.error, color: isSuccess ? Colors.green : Colors.red, ), const SizedBox(width: 8), Text( title, style: TextStyle( color: isSuccess ? Colors.green : Colors.red, fontWeight: FontWeight.bold, ), ), ], ), content: Text( message, style: const TextStyle(fontSize: 16), ), actions: [ TextButton( onPressed: () { Navigator.pop(context); Navigator.pop(context); }, style: TextButton.styleFrom( backgroundColor: isSuccess ? Colors.green : Colors.red, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), ), child: Text( "OK", style: const TextStyle(color: Colors.white), ), ), ], ); }, ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('إتمام الدفع')), body: WebViewWidget(controller: _controller), ); } } Future payWithMTNWallet( BuildContext context, String amount, String currency) async { // استخدام مؤشر تحميل لتجربة مستخدم أفضل Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false); try { String phone = box.read(BoxName.phoneWallet); String driverID = box.read(BoxName.driverID).toString(); String formattedAmount = double.parse(amount).toStringAsFixed(0); print("🚀 بدء عملية دفع MTN"); print( "📦 Payload: driverID: $driverID, amount: $formattedAmount, phone: $phone"); // التحقق من البصمة (اختياري) bool isAuthSupported = await LocalAuthentication().isDeviceSupported(); if (isAuthSupported) { bool didAuthenticate = await LocalAuthentication().authenticate( localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع', ); if (!didAuthenticate) { if (Get.isDialogOpen ?? false) Get.back(); print("❌ المستخدم لم يؤكد بالبصمة/الوجه"); return; } } // 1️⃣ استدعاء mtn_start_payment.php (الملف الجديد) var responseData = await CRUD().postWalletMtn( link: AppLink.payWithMTNStart, payload: { "amount": formattedAmount, "passengerId": driverID, "phone": phone, "lang": box.read(BoxName.lang) ?? 'ar', }, ); print("✅ استجابة الخادم (mtn_start_payment.php):"); print(responseData); // --- بداية التعديل المهم --- // التحقق القوي من الاستجابة لتجنب الأخطاء Map startRes; if (responseData is Map) { // إذا كانت الاستجابة بالفعل Map، استخدمها مباشرة startRes = responseData; } else if (responseData is String) { // إذا كانت نص، حاول تحليلها كـ JSON try { startRes = json.decode(responseData); } catch (e) { throw Exception( "فشل في تحليل استجابة الخادم. الاستجابة: $responseData"); } } else { // نوع غير متوقع throw Exception("تم استلام نوع بيانات غير متوقع من الخادم."); } if (startRes['status'] != 'success') { final errorMsg = startRes['message']['Error']?.toString().tr ?? "فشل بدء عملية الدفع. حاول مرة أخرى."; throw Exception(errorMsg); } // --- نهاية التعديل المهم --- // استخراج البيانات بأمان final messageData = startRes["message"]; final invoiceNumber = messageData["invoiceNumber"].toString(); final operationNumber = messageData["operationNumber"].toString(); final guid = messageData["guid"].toString(); print( "📄 invoiceNumber: $invoiceNumber, 🔢 operationNumber: $operationNumber, 🧭 guid: $guid"); if (Get.isDialogOpen == true) Get.back(); // إغلاق مؤشر التحميل قبل عرض حوار OTP // 2️⃣ عرض واجهة إدخال OTP String? otp = await showDialog( context: context, barrierDismissible: false, builder: (context) { String input = ""; return AlertDialog( title: const Text("أدخل كود التحقق"), content: TextField( keyboardType: TextInputType.number, decoration: const InputDecoration(hintText: "كود OTP"), onChanged: (val) => input = val, ), actions: [ TextButton( child: const Text("تأكيد"), onPressed: () => Navigator.of(context).pop(input), ), TextButton( child: const Text("إلغاء"), onPressed: () => Navigator.of(context).pop(), ), ], ); }, ); if (otp == null || otp.isEmpty) { print("❌ لم يتم إدخال OTP"); return; } print("🔐 تم إدخال OTP: $otp"); Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false); // 3️⃣ استدعاء mtn_confirm.php var confirmRes = await CRUD().postWalletMtn( link: AppLink.payWithMTNConfirm, payload: { "invoiceNumber": invoiceNumber, "operationNumber": operationNumber, "guid": guid, "otp": otp, "phone": phone, }, ); if (Get.isDialogOpen ?? false) Get.back(); print("✅ استجابة mtn_confirm.php:"); // print(confirmRes); Log.print('confirmRes: ${confirmRes}'); if (confirmRes != null && confirmRes['status'] == 'success') { Get.defaultDialog( title: "✅ نجاح", content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."), ); } else { String errorMsg = confirmRes?['message']['message']?.toString() ?? "فشل في تأكيد الدفع"; Get.defaultDialog( title: "❌ فشل", content: Text(errorMsg.tr), ); } } catch (e, s) { print("🔥 خطأ أثناء الدفع عبر MTN:"); print(e); print(s); if (Get.isDialogOpen ?? false) Get.back(); Get.defaultDialog( title: 'حدث خطأ', content: Text(e.toString().replaceFirst("Exception: ", "")), ); } } Future payWithSyriaTelWallet( BuildContext context, String amount, String currency) async { // Show a loading indicator for better user experience Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false); try { String phone = box.read(BoxName.phoneWallet); String driverID = box.read(BoxName.driverID).toString(); String formattedAmount = double.parse(amount).toStringAsFixed(0); // --- CHANGE 1: Updated log messages for clarity --- print("🚀 Starting Syriatel payment process"); print( "📦 Payload: driverID: $driverID, amount: $formattedAmount, phone: $phone"); // Optional: Biometric authentication bool isAuthSupported = await LocalAuthentication().isDeviceSupported(); if (isAuthSupported) { bool didAuthenticate = await LocalAuthentication().authenticate( localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع', ); if (!didAuthenticate) { if (Get.isDialogOpen ?? false) Get.back(); print("❌ User did not authenticate with biometrics"); return; } } // --- CHANGE 2: Updated API link and payload for starting payment --- // Make sure you have defined `payWithSyriatelStart` in your AppLink class var responseData = await CRUD().postWalletMtn( link: AppLink.payWithSyriatelStart, // Use the new Syriatel start link payload: { "amount": formattedAmount, "driverId": driverID, // Key changed from 'passengerId' to 'driverId' "phone": phone, "lang": box.read(BoxName.lang) ?? 'ar', }, ); print("✅ Server response (start_payment.php):"); Log.print('responseData: ${responseData}'); // Robustly parse the server's JSON response Map startRes; if (responseData is Map) { startRes = responseData; } else if (responseData is String) { try { startRes = json.decode(responseData); } catch (e) { throw Exception( "Failed to parse server response. Response: $responseData"); } } else { throw Exception("Received an unexpected data type from the server."); } if (startRes['status'] != 'success') { String errorMsg = startRes['message']?.toString() ?? "Failed to start the payment process. Please try again."; throw Exception(errorMsg); } // --- CHANGE 3: Extract `transactionID` from the response --- // The response structure is now simpler. We only need the transaction ID. final messageData = startRes["message"]; final transactionID = messageData["transactionID"].toString(); print("📄 TransactionID: $transactionID"); if (Get.isDialogOpen == true) Get.back(); // Close loading indicator // Show the OTP input dialog String? otp = await showDialog( context: context, barrierDismissible: false, builder: (context) { String input = ""; return AlertDialog( title: const Text("أدخل كود التحقق"), content: TextField( keyboardType: TextInputType.number, decoration: const InputDecoration(hintText: "كود OTP"), onChanged: (val) => input = val, ), actions: [ TextButton( child: const Text("تأكيد"), onPressed: () => Navigator.of(context).pop(input), ), TextButton( child: const Text("إلغاء"), onPressed: () => Navigator.of(context).pop(), ), ], ); }, ); if (otp == null || otp.isEmpty) { print("❌ OTP was not entered."); return; } print("🔐 OTP entered: $otp"); Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false); // --- CHANGE 4: Updated API link and payload for confirming payment --- // Make sure you have defined `payWithSyriatelConfirm` in your AppLink class var confirmRes = await CRUD().postWalletMtn( // Changed from postWalletMtn if they are different link: AppLink.payWithSyriatelConfirm, // Use the new Syriatel confirm link payload: { "transactionID": transactionID, // Use the transaction ID "otp": otp, // The other parameters (phone, guid, etc.) are no longer needed }, ); if (Get.isDialogOpen ?? false) Get.back(); print("✅ Response from confirm_payment.php:"); Log.print('confirmRes: ${confirmRes}'); if (confirmRes != null && confirmRes['status'] == 'success') { Get.defaultDialog( title: "✅ نجاح", content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."), ); } else { // --- CHANGE 5: Simplified error message extraction --- // The new PHP script sends the error directly in the 'message' field. String errorMsg = confirmRes?['message']?.toString() ?? "فشل في تأكيد الدفع"; Get.defaultDialog( title: "❌ فشل", content: Text(errorMsg.tr), ); } } catch (e, s) { // --- CHANGE 6: Updated general error log message --- print("🔥 Error during Syriatel Wallet payment:"); print(e); print(s); if (Get.isDialogOpen ?? false) Get.back(); Get.defaultDialog( title: 'حدث خطأ', content: Text(e.toString().replaceFirst("Exception: ", "")), ); } }