883 lines
31 KiB
Dart
Executable File
883 lines
31 KiB
Dart
Executable File
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 '../../../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,
|
||
),
|
||
],
|
||
)),
|
||
],
|
||
));
|
||
},
|
||
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<PaymentScreen> createState() => _PaymentScreenState();
|
||
}
|
||
|
||
class _PaymentScreenState extends State<PaymentScreen> {
|
||
late final WebViewController _controller;
|
||
final controller = Get.find<CaptainWalletController>();
|
||
@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<void> _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<PaymentScreenWallet> createState() => _PaymentScreenWalletState();
|
||
}
|
||
|
||
class _PaymentScreenWalletState extends State<PaymentScreenWallet> {
|
||
late final WebViewController _controller;
|
||
final controller = Get.find<CaptainWalletController>();
|
||
@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<void> _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<void> 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<String, dynamic> startRes;
|
||
|
||
if (responseData is Map<String, dynamic>) {
|
||
// إذا كانت الاستجابة بالفعل 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<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(
|
||
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<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):");
|
||
Log.print('responseData: ${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(
|
||
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: ", "")),
|
||
);
|
||
}
|
||
}
|