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:webview_flutter/webview_flutter.dart'; import '../../../constant/box_name.dart'; import '../../../constant/links.dart'; import '../../../controller/functions/crud.dart'; import '../../../main.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( children: [ Text( '${'you can buy '.tr}$countPoint ${'L.S'.tr}${'by '.tr}${'$pricePoint'.tr}', style: AppStyle.title, ), MyElevatedButton( title: 'Pay with Credit Card'.tr, onPressed: () async { Get.back(); payWithEcashDriver(context, pricePoint.toString()); // var d = jsonDecode(res); }, //51524 ), // Add some spacing between buttons MyElevatedButton( kolor: AppColor.redColor, title: 'Pay with Wallet'.tr, onPressed: () 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: 'Insert Wallet phone number'.tr, 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: 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) ?? '963992952235'; 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().postWallet( link: AppLink.payWithMTNStart, payload: { "amount": formattedAmount, "passengerId": driverID, "phone": phone, }, ); 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') { String errorMsg = startRes['message']?.toString() ?? "فشل بدء عملية الدفع. حاول مرة أخرى."; 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 ?? false) Get.back(); // إغلاق مؤشر التحميل قبل عرض حوار OTP // 2️⃣ عرض واجهة إدخال OTP String? otp = await showDialog( context: context, 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().postWallet( 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); if (confirmRes != null && confirmRes['status'] == 'success') { Get.defaultDialog( title: "✅ نجاح", content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."), ); } else { String errorMsg = confirmRes?['message']?.toString() ?? "فشل في تأكيد الدفع"; Get.defaultDialog( title: "❌ فشل", content: Text(errorMsg), ); } } catch (e, s) { print("🔥 خطأ أثناء الدفع عبر MTN:"); print(e); print(s); if (Get.isDialogOpen ?? false) Get.back(); Get.defaultDialog( title: 'حدث خطأ', content: Text(e.toString().replaceFirst("Exception: ", "")), ); } }