950 lines
32 KiB
Dart
Executable File
950 lines
32 KiB
Dart
Executable File
import 'package:siro_driver/constant/currency.dart';
|
||
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 '../../widgets/mydialoug.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 ${CurrencyHelper.currency}',
|
||
style: TextStyle(
|
||
fontWeight: FontWeight.w900,
|
||
fontSize: 15,
|
||
color: FinanceDesignSystem.primaryDark,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
'${'Price:'.tr} ${pricePoint.toStringAsFixed(0)} ${CurrencyHelper.currency}',
|
||
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 ${CurrencyHelper.currency}",
|
||
style: FinanceDesignSystem.subHeadingStyle),
|
||
const SizedBox(height: 24),
|
||
if (box.read(BoxName.countryCode) == 'Syria') ...[
|
||
_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));
|
||
},
|
||
),
|
||
const SizedBox(height: 16),
|
||
_buildPaymentMethodTile(
|
||
image: 'assets/images/cashMTN.png',
|
||
title: 'MTN Cash'.tr,
|
||
subtitle: 'Pay using MTN Cash wallet'.tr,
|
||
color: Colors.yellow.shade700,
|
||
onTap: () => _showPhoneInputDialog(context, 'MTN'),
|
||
),
|
||
],
|
||
if (box.read(BoxName.countryCode) == 'Jordan') ...[
|
||
_buildPaymentMethodTile(
|
||
icon: Icons.qr_code_scanner_rounded,
|
||
title: 'CliQ'.tr,
|
||
subtitle: 'Pay via CliQ (Alias: siroapp)'.tr,
|
||
color: Colors.purple,
|
||
onTap: () {
|
||
Get.back();
|
||
MyDialog().getDialog(
|
||
'CliQ Payment'.tr,
|
||
'Please transfer the amount to alias: siroapp'.tr,
|
||
() => Get.back());
|
||
},
|
||
),
|
||
],
|
||
if (box.read(BoxName.countryCode) == 'Egypt') ...[
|
||
_buildPaymentMethodTile(
|
||
icon: Icons.account_balance_wallet_rounded,
|
||
title: 'Wallet Payment'.tr,
|
||
subtitle: 'Mobile Wallets'.tr,
|
||
color: Colors.red,
|
||
onTap: () => _showPhoneInputDialog(context, 'PaymobWallet'),
|
||
),
|
||
const SizedBox(height: 16),
|
||
_buildPaymentMethodTile(
|
||
icon: Icons.credit_card_rounded,
|
||
title: 'Bank Card Payment'.tr,
|
||
subtitle: 'Card Payment'.tr,
|
||
color: Colors.blue,
|
||
onTap: () {
|
||
Get.back();
|
||
// Navigate to Paymob card payment
|
||
},
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
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(), CurrencyHelper.currency);
|
||
} else {
|
||
await payWithMTNWallet(
|
||
context, pricePoint.toString(), CurrencyHelper.currency);
|
||
}
|
||
}
|
||
},
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class PaymentScreen extends StatefulWidget {
|
||
final String iframeUrl;
|
||
final String countPrice;
|
||
|
||
const PaymentScreen(
|
||
{required this.iframeUrl, super.key, required this.countPrice});
|
||
|
||
@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, super.key, required this.countPrice});
|
||
|
||
@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: ", "")),
|
||
);
|
||
}
|
||
}
|