25-10-9/1
This commit is contained in:
@@ -47,8 +47,8 @@ android {
|
|||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 22
|
versionCode = 25
|
||||||
versionName = '1.0.22'
|
versionName = '1.0.25'
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "arm64-v8a"
|
abiFilters "armeabi-v7a", "arm64-v8a"
|
||||||
|
|||||||
@@ -33,11 +33,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>10</string>
|
<string>12</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0.10</string>
|
<string>1.0.12</string>
|
||||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||||
<string>NO</string>
|
<string>NO</string>
|
||||||
<key>GMSApiKey</key>
|
<key>GMSApiKey</key>
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
List<String> passengerMessages = [
|
List<String> passengerMessages = [
|
||||||
// --- رسائل العروض والتوفير ---
|
// --- رسائل العروض والتوفير ---
|
||||||
"مشاويرك علينا بأحلى الأسعار! 🚗 فوت على تطبيق انطلق وشوف العروض الجديدة اللي ناطرتك. 🌟",
|
"وفر على حالك: 🚗 أسعار انطلق نازلة كتير! شوف العروض الجديدة وفوت هلأ قبل ما تخلص. 🌟",
|
||||||
"بدك توفّر بمشاويرك؟ 🤔 افتح تطبيق انطلق هلأ! أسعارنا ما بتلاقي متلها.",
|
"خصم اليوم: 🤔 لا تفوّت الفرصة! افتح تطبيق انطلق وشوف الأسعار يلي ما بتنعاد.",
|
||||||
"خصم خاص الك اليوم! 🎁 افتح تطبيق انطلق واحجز مشوارك الجاي بسعر أقل.",
|
"عروض نارية: 🎁 اليوم خصم خاص إلك، احجز مشوارك الجاي بسعر ولا أروع!",
|
||||||
"لا تحمل هم المصاريف! 💸 مع انطلق، كل مشاويرك اقتصادية ومريحة.",
|
"مشاوير اقتصادية: 💸 مع انطلق بتتحرك براحتك وبتدفع أقل، جرب وشوف الفرق!",
|
||||||
|
|
||||||
// --- رسائل السهولة والراحة ---
|
// --- رسائل السهولة والراحة ---
|
||||||
"كبسة زر ومشوارك صار عندك! 📲 افتح تطبيق انطلق واحجز بسهولة.",
|
"مشوار بكبسة زر: 📲 افتح تطبيق انطلق، وخلِّي السيارة توصلك لعندك بثواني.",
|
||||||
"ليش معجوق بالمواصلات؟ 🤔 انطلق بيوصلك وين ما بدك براحة وسرعة.",
|
"ارتاح من المواصلات: 🤔 خلّي انطلق يريحك من الانتظار والزحمة، وانطلق وين ما بدك.",
|
||||||
"سيارتك ناطرتك! 🚕 وين ما كنت، اطلب من انطلق وبنوصلك بأسرع وقت.",
|
"سيارتك جاهزة: 🚕 الكابتن ناطر طلبك، حدد وجهتك وخلّي الطريق علينا.",
|
||||||
"مشوارك الجاي مع انطلق! 🛣️ استرخي واستمتع بالطريق، نحنا بنهتم بالتفاصيل.",
|
"رحلة مريحة: 🛣️ ارتاح بالكرسي، نحنا منهتم بكل التفاصيل من الباب للباب.",
|
||||||
|
|
||||||
// --- رسائل الأمان والثقة ---
|
// --- رسائل الأمان والثقة ---
|
||||||
"مشوارك آمن ومريح مع انطلق. 🙏 كباتنّا محترفين و بيهمنا توصل بالسلامة.",
|
"رحلتك بأمان: 🙏 كل كباتنّا مدرّبين وملتزمين، وسلامتك أولويتنا.",
|
||||||
"انطلق بقلب مطمن! 🔒 سلامتك أولويتنا بكل رحلة بتطلعها معنا.",
|
"سافر وانت مطمّن: 🔒 مع انطلق كل شي موثوق ومسجّل لتكون مرتاح البال.",
|
||||||
"مع انطلق، فيك تشارك تفاصيل رحلتك مع اللي بتحبن ليضل بالك مرتاح. ❤️",
|
"شارك رحلتك: ❤️ فيك تبعت تفاصيل المشوار لأهلك أو رفقاتك بخطوة وحدة.",
|
||||||
"كل رحلاتنا مسجّلة لضمان أمانك. سافر بثقة مع انطلق. ✅",
|
"انطلق بثقة: ✅ كل الرحلات مراقبة لتضمن تجربة آمنة ومريحة 100%.",
|
||||||
|
|
||||||
// --- رسائل تفاعلية ومناسبات خاصة ---
|
// --- رسائل تفاعلية ومناسبات ---
|
||||||
"الويكند بلّش! 🥳 مشاويرك مع الصحبة صارت أسهل وأوفر. اطلب انطلق.",
|
"الويكند بلّش: 🥳 خلي مشاويرك مع الأصحاب علينا، وفر وقتك وفلوسك مع انطلق.",
|
||||||
"رايح عالشغل؟ 💼 خلي انطلق يوصلك بلا عجقة السير وخليك مرتاح.",
|
"رايح عالشغل: 💼 لا تتأخر، افتح التطبيق وخلي الكابتن يوصلك بلا تعب.",
|
||||||
"الدنيا شوب؟ ☀️ اطلب من انطلق والكابتن بيوصل لعندك.",
|
"الشمس مولّعة: ☀️ لا تمشي تحت الحر، خلي السيارة تجي لعندك.",
|
||||||
"تأخرت؟ لا تاكل هم! 🏃♂️ تطبيق انطلق هو أسرع طريق لتوصل عبكير.",
|
"مستعجل: 🏃♂️ لا تقلق، انطلق أسرع طريق لتوصل عموعدك.",
|
||||||
|
|
||||||
// --- رسائل تشجيعية عامة ---
|
// --- رسائل تشجيعية عامة ---
|
||||||
"وين وجهتك اليوم؟ 🗺️ مدينة كاملة بانتظارك، وخلي مشوارك على انطلق.",
|
"وين رايح اليوم؟ 🗺️ وين ما كانت وجهتك، انطلق بيخدمك بكل مكان وبأي وقت.",
|
||||||
"جرّبت فئات سيارات انطلق الجديدة؟ 🚘 دلّل حالك بتجربة مميزة اليوم.",
|
"جرب شي جديد: 🚘 شوف فئات السيارات الجديدة وخلي رحلتك أريح وأجمل.",
|
||||||
"شكراً لاختيارك انطلق! ⭐ منتمنى الك دايماً رحلات سعيدة ومريحة.",
|
"شكراً لاختيارك: ⭐ وجودك معنا بيفرحنا، ونتمنى دايماً تكون رحلتك مريحة وسعيدة.",
|
||||||
"عند انطلق كل شي جديد! ✨ فوت عالبرنامج وضلّك على اطلاع بآخر خدماتنا وعروضنا."
|
"كل يوم جديد: ✨ فوت عالتطبيق وتابع آخر التحديثات والعروض يلي نازلة خصيصاً إلك."
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class LoginController extends GetxController {
|
|||||||
var dev = '';
|
var dev = '';
|
||||||
@override
|
@override
|
||||||
void onInit() async {
|
void onInit() async {
|
||||||
// await getAppTester();
|
await getJWT();
|
||||||
// Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}');
|
// Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}');
|
||||||
box.write(BoxName.countryCode, 'Syria');
|
box.write(BoxName.countryCode, 'Syria');
|
||||||
FirebaseMessagesController().getToken();
|
FirebaseMessagesController().getToken();
|
||||||
|
|||||||
@@ -22,26 +22,29 @@ class PhoneAuthHelper {
|
|||||||
/// Sends an OTP to the provided phone number.
|
/// Sends an OTP to the provided phone number.
|
||||||
static Future<bool> sendOtp(String phoneNumber) async {
|
static Future<bool> sendOtp(String phoneNumber) async {
|
||||||
try {
|
try {
|
||||||
|
// Log.print('_sendOtpUrl: ${_sendOtpUrl}');
|
||||||
|
// Log.print('phoneNumber: ${phoneNumber}');
|
||||||
|
|
||||||
final response = await CRUD().post(
|
final response = await CRUD().post(
|
||||||
link: _sendOtpUrl,
|
link: _sendOtpUrl,
|
||||||
payload: {'receiver': phoneNumber},
|
payload: {'receiver': phoneNumber},
|
||||||
);
|
);
|
||||||
Log.print('response: ${response}');
|
// Log.print('response: ${response}');
|
||||||
if (response != 'failure') {
|
if (response != 'failure') {
|
||||||
final data = (response);
|
final data = (response);
|
||||||
// if (data['status'] == 'success') {
|
if (data['status'] == 'success') {
|
||||||
mySnackbarSuccess('An OTP has been sent to your WhatsApp number.'.tr);
|
mySnackbarSuccess('An OTP has been sent to your WhatsApp number.'.tr);
|
||||||
return true;
|
return true;
|
||||||
// } else {
|
} else {
|
||||||
// mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
|
mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
|
||||||
// return false;
|
return false;
|
||||||
// }
|
}
|
||||||
} else {
|
} else {
|
||||||
mySnackeBarError('Server error. Please try again.'.tr);
|
mySnackeBarError('Server error. Please try again.'.tr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.print('e: ${e}');
|
// Log.print('e: ${e}');
|
||||||
// mySnackeBarError('An error occurred: $e');
|
// mySnackeBarError('An error occurred: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -673,312 +673,305 @@ class PaymentController extends GetxController {
|
|||||||
|
|
||||||
/// شاشة جديدة ومبسطة خاصة بدفع السائقين عبر ecash
|
/// شاشة جديدة ومبسطة خاصة بدفع السائقين عبر ecash
|
||||||
|
|
||||||
Future<void> payWithMTNWallet(
|
// Future<void> payWithMTNWallet(
|
||||||
BuildContext context, String amount, String currency) async {
|
// BuildContext context, String amount, String currency) async {
|
||||||
// خزن سياق علوي آمن من البداية
|
// // خزن سياق علوي آمن من البداية
|
||||||
final BuildContext safeContext =
|
// final BuildContext safeContext =
|
||||||
Get.overlayContext ?? Get.context ?? context;
|
// Get.overlayContext ?? Get.context ?? context;
|
||||||
|
|
||||||
// سبينر تحميل
|
// // سبينر تحميل
|
||||||
if (!(Get.isDialogOpen ?? false)) {
|
// if (!(Get.isDialogOpen ?? false)) {
|
||||||
Get.dialog(const Center(child: CircularProgressIndicator()),
|
// Get.dialog(const Center(child: CircularProgressIndicator()),
|
||||||
barrierDismissible: false);
|
// 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<String, dynamic> startRes;
|
||||||
|
// if (responseData is Map<String, dynamic>) {
|
||||||
|
// startRes = responseData;
|
||||||
|
// } else if (responseData is String) {
|
||||||
|
// startRes = json.decode(responseData) as Map<String, dynamic>;
|
||||||
|
// } else {
|
||||||
|
// throw Exception("تم استلام نوع بيانات غير متوقع من الخادم.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (startRes['status'] != 'success') {
|
||||||
|
// final errorMsg = startRes['message']['Error']?.toString().tr ??
|
||||||
|
// "فشل بدء عملية الدفع. حاول مرة أخرى.";
|
||||||
|
// throw Exception(errorMsg);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// final messageData = startRes["message"] as Map<String, dynamic>;
|
||||||
|
// 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<void> payWithSyriaTelWallet(String amount, String currency) async {
|
||||||
|
// helper لفتح لودينغ بأمان
|
||||||
|
Future<void> _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 {
|
try {
|
||||||
final phone = box.read(BoxName.phoneWallet) as String;
|
final phone = box.read(BoxName.phoneWallet) as String;
|
||||||
final passengerID = box.read(BoxName.passengerID).toString();
|
final passengerId = box.read(BoxName.passengerID).toString();
|
||||||
final formattedAmount = double.parse(amount).toStringAsFixed(0);
|
final formattedAmount = double.parse(amount).toStringAsFixed(0);
|
||||||
|
|
||||||
print("🚀 بدء عملية دفع MTN");
|
print("🚀 Syriatel payment start");
|
||||||
print(
|
print(
|
||||||
"📦 Payload: passengerID: $passengerID, amount: $formattedAmount, phone: $phone");
|
"📦 Payload => passengerId:$passengerId amount:$formattedAmount phone:$phone");
|
||||||
|
|
||||||
// التحقق بالبصمة (اختياري) + حماية من الـ await
|
// مصادقة حيوية (اختياري)
|
||||||
final localAuth = LocalAuthentication();
|
final auth = LocalAuthentication();
|
||||||
final isAuthSupported = await localAuth.isDeviceSupported();
|
if (await auth.isDeviceSupported()) {
|
||||||
if (isAuthSupported) {
|
final ok = await auth.authenticate(
|
||||||
final didAuth = await localAuth.authenticate(
|
|
||||||
localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||||
);
|
);
|
||||||
if (!didAuth) {
|
if (!ok) {
|
||||||
if (Get.isDialogOpen == true) Get.back();
|
_closeAnyDialog();
|
||||||
print("❌ المستخدم لم يؤكد بالبصمة/الوجه");
|
print("❌ User did not authenticate");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1) بدء الدفع
|
// 1) بدء عملية الدفع
|
||||||
final responseData = await CRUD().postWalletMtn(
|
final startRaw = await CRUD().postWalletMtn(
|
||||||
link: AppLink.payWithMTNStart,
|
link: AppLink.payWithSyriatelStart,
|
||||||
payload: {
|
payload: {
|
||||||
"amount": formattedAmount,
|
"amount": formattedAmount,
|
||||||
"passengerId": passengerID,
|
"passengerId": passengerId,
|
||||||
"phone": phone,
|
"phone": phone,
|
||||||
"lang": box.read(BoxName.lang) ?? 'ar',
|
"lang": box.read(BoxName.lang) ?? 'ar',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// print("✅ استجابة الخادم (mtn_start_payment.php):");
|
print("✅ Server response (start): $startRaw");
|
||||||
// print(responseData);
|
|
||||||
Log.print('responseData: ${responseData}');
|
|
||||||
|
|
||||||
// فحص الاستجابة بقوة
|
// تحويل الاستجابة إلى Map
|
||||||
late final Map<String, dynamic> startRes;
|
late final Map<String, dynamic> startRes;
|
||||||
if (responseData is Map<String, dynamic>) {
|
if (startRaw is Map<String, dynamic>) {
|
||||||
startRes = responseData;
|
startRes = startRaw;
|
||||||
} else if (responseData is String) {
|
} else if (startRaw is String) {
|
||||||
startRes = json.decode(responseData) as Map<String, dynamic>;
|
startRes = json.decode(startRaw) as Map<String, dynamic>;
|
||||||
} else {
|
} else {
|
||||||
throw Exception("تم استلام نوع بيانات غير متوقع من الخادم.");
|
throw Exception("Unexpected start response type");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startRes['status'] != 'success') {
|
if (startRes['status'] != 'success') {
|
||||||
final errorMsg = startRes['message']['Error']?.toString().tr ??
|
final msg =
|
||||||
"فشل بدء عملية الدفع. حاول مرة أخرى.";
|
(startRes['message'] ?? 'Failed to start payment').toString();
|
||||||
throw Exception(errorMsg);
|
throw Exception(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
final messageData = startRes["message"] as Map<String, dynamic>;
|
final messageData = startRes['message'] as Map<String, dynamic>;
|
||||||
final invoiceNumber = messageData["invoiceNumber"].toString();
|
final transactionID = messageData['transactionID'].toString();
|
||||||
final operationNumber = messageData["operationNumber"].toString();
|
print("📄 transactionID: $transactionID");
|
||||||
final guid = messageData["guid"].toString();
|
|
||||||
|
|
||||||
// print(
|
// 2) اطلب من المستخدم إدخال OTP عبر Get.dialog (بدون context)
|
||||||
// "📄 invoiceNumber: $invoiceNumber, 🔢 operationNumber: $operationNumber, 🧭 guid: $guid");
|
_closeAnyDialog(); // أغلق اللودينغ أولاً
|
||||||
|
final otpController = TextEditingController();
|
||||||
// أغلق السبينر قبل إظهار حوار OTP
|
final otp = await Get.dialog<String>(
|
||||||
if (Get.isDialogOpen == true) Get.back();
|
AlertDialog(
|
||||||
|
title: const Text("أدخل كود التحقق"),
|
||||||
// 2) إدخال OTP بـ Get.defaultDialog (لا يستخدم context قابل للتلف)
|
content: TextField(
|
||||||
String otpInput = "";
|
controller: otpController,
|
||||||
await Get.defaultDialog(
|
keyboardType: TextInputType.number,
|
||||||
title: "أدخل كود التحقق",
|
decoration: const InputDecoration(hintText: "كود OTP"),
|
||||||
barrierDismissible: false,
|
),
|
||||||
content: TextField(
|
actions: [
|
||||||
keyboardType: TextInputType.number,
|
TextButton(
|
||||||
decoration: const InputDecoration(hintText: "كود OTP"),
|
child: const Text("تأكيد"),
|
||||||
onChanged: (v) => otpInput = v,
|
onPressed: () => Get.back(result: otpController.text.trim()),
|
||||||
),
|
|
||||||
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<void> 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):");
|
|
||||||
print(responseData);
|
|
||||||
|
|
||||||
// Robustly parse the server's JSON response
|
|
||||||
Map<String, dynamic> startRes;
|
|
||||||
if (responseData is Map<String, dynamic>) {
|
|
||||||
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<String>(
|
|
||||||
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(
|
||||||
TextButton(
|
child: const Text("إلغاء"),
|
||||||
child: const Text("تأكيد"),
|
onPressed: () => Get.back(result: null),
|
||||||
onPressed: () => Navigator.of(context).pop(input),
|
),
|
||||||
),
|
],
|
||||||
TextButton(
|
),
|
||||||
child: const Text("إلغاء"),
|
barrierDismissible: false,
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (otp == null || otp.isEmpty) {
|
if (otp == null || otp.isEmpty) {
|
||||||
print("❌ OTP was not entered.");
|
print("❌ OTP not provided");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
print("🔐 OTP entered: $otp");
|
print("🔐 OTP: $otp");
|
||||||
|
|
||||||
Get.dialog(const Center(child: CircularProgressIndicator()),
|
await _showLoading();
|
||||||
barrierDismissible: false);
|
|
||||||
|
|
||||||
// --- CHANGE 4: Updated API link and payload for confirming payment ---
|
// 3) تأكيد الدفع
|
||||||
// Make sure you have defined `payWithSyriatelConfirm` in your AppLink class
|
final confirmRaw = await CRUD().postWallet(
|
||||||
var confirmRes = await CRUD().postWallet(
|
link: AppLink.payWithSyriatelConfirm,
|
||||||
// Changed from postWalletMtn if they are different
|
|
||||||
link:
|
|
||||||
AppLink.payWithSyriatelConfirm, // Use the new Syriatel confirm link
|
|
||||||
payload: {
|
payload: {
|
||||||
"transactionID": transactionID, // Use the transaction ID
|
"transactionID": transactionID,
|
||||||
"otp": otp,
|
"otp": otp,
|
||||||
// The other parameters (phone, guid, etc.) are no longer needed
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Get.isDialogOpen ?? false) Get.back();
|
_closeAnyDialog(); // أغلق اللودينغ
|
||||||
|
|
||||||
print("✅ Response from confirm_payment.php:");
|
print("✅ Response (confirm): $confirmRaw");
|
||||||
Log.print('confirmRes: ${confirmRes}');
|
|
||||||
|
|
||||||
if (confirmRes != null && confirmRes['status'] == 'success') {
|
late final Map<String, dynamic> confirmRes;
|
||||||
|
if (confirmRaw is Map<String, dynamic>) {
|
||||||
|
confirmRes = confirmRaw;
|
||||||
|
} else if (confirmRaw is String) {
|
||||||
|
confirmRes = json.decode(confirmRaw) as Map<String, dynamic>;
|
||||||
|
} else {
|
||||||
|
throw Exception("Unexpected confirm response type");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirmRes['status'] == 'success') {
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
title: "✅ نجاح",
|
title: "✅ نجاح",
|
||||||
content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."),
|
content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// --- CHANGE 5: Simplified error message extraction ---
|
final msg = (confirmRes['message'] ?? 'فشل في تأكيد الدفع').toString();
|
||||||
// The new PHP script sends the error directly in the 'message' field.
|
|
||||||
String errorMsg =
|
|
||||||
confirmRes?['message']?.toString() ?? "فشل في تأكيد الدفع";
|
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
title: "❌ فشل",
|
title: "❌ فشل",
|
||||||
content: Text(errorMsg.tr),
|
content: Text(msg),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
// --- CHANGE 6: Updated general error log message ---
|
print("🔥 Error during Syriatel Wallet payment:\n$e\n$s");
|
||||||
print("🔥 Error during Syriatel Wallet payment:");
|
_closeAnyDialog();
|
||||||
print(e);
|
|
||||||
print(s);
|
|
||||||
if (Get.isDialogOpen ?? false) Get.back();
|
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
title: 'حدث خطأ',
|
title: 'حدث خطأ',
|
||||||
content: Text(e.toString().replaceFirst("Exception: ", "")),
|
content: Text(e.toString().replaceFirst("Exception: ", "")),
|
||||||
|
|||||||
@@ -338,68 +338,69 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
|
|||||||
// );
|
// );
|
||||||
// },
|
// },
|
||||||
// ),
|
// ),
|
||||||
GestureDetector(
|
// GestureDetector(
|
||||||
onTap: () async {
|
// onTap: () async {
|
||||||
Get.back();
|
// Get.back();
|
||||||
Get.defaultDialog(
|
// Get.defaultDialog(
|
||||||
barrierDismissible: false,
|
// barrierDismissible: false,
|
||||||
title: 'Insert Wallet phone number'.tr,
|
// title: 'Insert Wallet phone number'.tr,
|
||||||
content: Form(
|
// content: Form(
|
||||||
key: controller.formKey,
|
// key: controller.formKey,
|
||||||
child: MyTextForm(
|
// child: MyTextForm(
|
||||||
controller: controller.walletphoneController,
|
// controller: controller.walletphoneController,
|
||||||
label: 'Insert Wallet phone number'.tr,
|
// label: 'Insert Wallet phone number'.tr,
|
||||||
hint: '963941234567',
|
// hint: '963941234567',
|
||||||
type: TextInputType.phone)),
|
// type: TextInputType.phone)),
|
||||||
confirm: MyElevatedButton(
|
// confirm: MyElevatedButton(
|
||||||
title: 'OK'.tr,
|
// title: 'OK'.tr,
|
||||||
onPressed: () async {
|
// onPressed: () async {
|
||||||
Get.back();
|
// Get.back();
|
||||||
if (controller.formKey.currentState!.validate()) {
|
// if (controller.formKey.currentState!.validate()) {
|
||||||
if (controller.selectedAmount != 0) {
|
// if (controller.selectedAmount != 0) {
|
||||||
controller.isLoading = true;
|
// controller.isLoading = true;
|
||||||
controller.update();
|
// controller.update();
|
||||||
box.write(BoxName.phoneWallet,
|
// box.write(BoxName.phoneWallet,
|
||||||
(controller.walletphoneController.text));
|
// (controller.walletphoneController.text));
|
||||||
Get.back();
|
// Get.back();
|
||||||
await controller.payWithMTNWallet(
|
// await controller.payWithMTNWallet(
|
||||||
context,
|
// context,
|
||||||
controller.selectedAmount.toString(),
|
// controller.selectedAmount.toString(),
|
||||||
'SYP',
|
// 'SYP',
|
||||||
);
|
// );
|
||||||
await controller.getPassengerWallet();
|
// await controller.getPassengerWallet();
|
||||||
|
|
||||||
|
// controller.isLoading = false;
|
||||||
|
// controller.update();
|
||||||
|
// } else {
|
||||||
|
// Toast.show(
|
||||||
|
// context,
|
||||||
|
// '⚠️ You need to choose an amount!'.tr,
|
||||||
|
// AppColor.redColor,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }));
|
||||||
|
// },
|
||||||
|
// child: Padding(
|
||||||
|
// padding: const EdgeInsets.all(8.0),
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// 'Pay by MTN Wallet'.tr,
|
||||||
|
// style: AppStyle.title,
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 10),
|
||||||
|
// Image.asset(
|
||||||
|
// 'assets/images/cashMTN.png',
|
||||||
|
// width: 70,
|
||||||
|
// height: 70,
|
||||||
|
// fit: BoxFit.contain,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// )),
|
||||||
|
|
||||||
controller.isLoading = false;
|
|
||||||
controller.update();
|
|
||||||
} else {
|
|
||||||
Toast.show(
|
|
||||||
context,
|
|
||||||
'⚠️ You need to choose an amount!'.tr,
|
|
||||||
AppColor.redColor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Pay by MTN Wallet'.tr,
|
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/cashMTN.png',
|
|
||||||
width: 70,
|
|
||||||
height: 70,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Get.back();
|
Get.back();
|
||||||
@@ -420,7 +421,7 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
|
|||||||
if (controller.formKey.currentState!.validate()) {
|
if (controller.formKey.currentState!.validate()) {
|
||||||
box.write(BoxName.phoneWallet,
|
box.write(BoxName.phoneWallet,
|
||||||
controller.walletphoneController.text);
|
controller.walletphoneController.text);
|
||||||
await controller.payWithSyriaTelWallet(context,
|
await controller.payWithSyriaTelWallet(
|
||||||
controller.selectedAmount.toString(), 'SYP');
|
controller.selectedAmount.toString(), 'SYP');
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user