25-7-28-2
This commit is contained in:
601
lib/views/home/my_wallet/points_captain.dart
Executable file
601
lib/views/home/my_wallet/points_captain.dart
Executable file
@@ -0,0 +1,601 @@
|
||||
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<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) ?? '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<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') {
|
||||
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<String>(
|
||||
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: ", "")),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user