import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:local_auth/local_auth.dart'; import 'package:siro_driver/controller/home/payment/captain_wallet_controller.dart'; import 'package:siro_driver/controller/payment/payment_controller.dart'; import 'package:siro_driver/controller/payment/smsPaymnet/payment_services.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../../../constant/box_name.dart'; import '../../../constant/finance_design_system.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 { final PaymentController paymentController = Get.put(PaymentController()); final CaptainWalletController captainWalletController = Get.put(CaptainWalletController()); PointsCaptain({ super.key, required this.kolor, required this.countPoint, required this.pricePoint, }); final Color kolor; final String countPoint; final double pricePoint; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(right: 12, bottom: 4), child: Material( color: Colors.white, borderRadius: BorderRadius.circular(20), elevation: 4, shadowColor: kolor.withValues(alpha: 0.3), child: InkWell( onTap: () => _showPaymentOptions(context), borderRadius: BorderRadius.circular(20), child: Container( width: 130, padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ kolor.withValues(alpha: 0.05), Colors.white, ], ), border: Border.all(color: kolor.withValues(alpha: 0.2), width: 1.5), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: kolor.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Icon(Icons.account_balance_wallet_rounded, color: kolor, size: 24), ), const SizedBox(height: 10), Text( '$countPoint ${'SYP'.tr}', style: TextStyle( fontWeight: FontWeight.w900, fontSize: 15, color: FinanceDesignSystem.primaryDark, ), ), const SizedBox(height: 4), Text( '${'Price:'.tr} ${pricePoint.toStringAsFixed(0)} ${'SYP'.tr}', style: TextStyle( fontSize: 11, color: Colors.grey.shade600, fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ); } void _showPaymentOptions(BuildContext context) { Get.bottomSheet( isScrollControlled: true, Container( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.8, ), padding: const EdgeInsets.fromLTRB(24, 24, 24, 32), decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(32)), ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Select Payment Method".tr, style: FinanceDesignSystem.headingStyle), IconButton( icon: const Icon(Icons.close_rounded, color: Colors.grey), onPressed: () => Get.back(), ), ], ), const SizedBox(height: 8), Text("${'Amount to charge:'.tr} $countPoint ${'SYP'.tr}", style: FinanceDesignSystem.subHeadingStyle), const SizedBox(height: 24), _buildPaymentMethodTile( icon: Icons.credit_card_rounded, title: 'Debit Card'.tr, subtitle: 'E-Cash payment gateway'.tr, color: Colors.blue, onTap: () { Get.back(); payWithEcashDriver(context, pricePoint.toString()); }, ), const SizedBox(height: 16), _buildPaymentMethodTile( image: 'assets/images/syriatel.jpeg', title: 'Syriatel Cash'.tr, subtitle: 'Pay using Syriatel mobile wallet'.tr, color: Colors.red, onTap: () => _showPhoneInputDialog(context, 'Syriatel'), ), const SizedBox(height: 16), _buildPaymentMethodTile( image: 'assets/images/shamCash.png', title: 'Sham Cash'.tr, subtitle: 'Pay using Sham Cash wallet'.tr, color: Colors.orange, onTap: () async { Get.back(); bool isAuthSupported = await LocalAuthentication().isDeviceSupported(); if (isAuthSupported) { bool didAuthenticate = await LocalAuthentication().authenticate( localizedReason: 'Confirm payment with biometrics'.tr, ); if (!didAuthenticate) return; } Get.to(() => PaymentScreenSmsProvider(amount: pricePoint)); }, ), ], ), ), ), ); } Widget _buildPaymentMethodTile({ IconData? icon, String? image, required String title, required String subtitle, required Color color, required VoidCallback onTap, }) { return Material( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(20), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(20), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.grey.shade200, width: 1), ), child: Row( children: [ Container( width: 56, height: 56, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), boxShadow: [ BoxShadow( color: color.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: image != null ? Image.asset(image, fit: BoxFit.contain) : Icon(icon, color: color, size: 30), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: FinanceDesignSystem.primaryDark)), const SizedBox(height: 2), Text(subtitle, style: TextStyle( color: Colors.grey.shade600, fontSize: 12)), ], ), ), Icon(Icons.arrow_forward_ios_rounded, size: 14, color: Colors.grey.shade400), ], ), ), ), ); } void _showPhoneInputDialog(BuildContext context, String provider) { Get.back(); Get.defaultDialog( title: 'Wallet Phone Number'.tr, content: Form( key: paymentController.formKey, child: MyTextForm( controller: paymentController.walletphoneController, label: 'Phone Number'.tr, hint: provider == 'Syriatel' ? '963991234567' : '963941234567', type: TextInputType.phone, ), ), confirm: MyElevatedButton( title: 'Confirm'.tr, onPressed: () async { if (paymentController.formKey.currentState!.validate()) { Get.back(); box.write(BoxName.phoneWallet, paymentController.walletphoneController.text); if (provider == 'Syriatel') { await payWithSyriaTelWallet( context, pricePoint.toString(), 'SYP'); } else { await payWithMTNWallet(context, pricePoint.toString(), 'SYP'); } } }, ), ); } } class PaymentScreen extends StatefulWidget { final String iframeUrl; final String countPrice; const PaymentScreen( {required this.iframeUrl, super.key, required this.countPrice}); @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, super.key, required this.countPrice}); @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: ", "")), ); } }