feat: implement secure OTP-based payout workflow with dynamic fee calculation and improved authentication checks

This commit is contained in:
Hamza-Ayed
2026-07-19 01:51:39 +03:00
parent 5e80c886a0
commit 085b180bdb
11 changed files with 252 additions and 193 deletions
@@ -4,13 +4,14 @@ import 'package:siro_driver/controller/functions/crud.dart';
class PayoutService {
final String _baseUrl =
"https://walletintaleq.intaleq.xyz/v1/main/sms_webhook";
static const double payoutFee = 5000.0; // عمولة السحب الثابتة
static const double payoutFee = 50.0; // عمولة السحب الثابتة
/// دالة لإنشاء طلب سحب جديد على السيرفر
///
/// تعيد رسالة النجاح من السيرفر، أو رسالة خطأ في حال الفشل.
Future<String?> requestPayout({
required String driverId,
required String otp,
walletType,
payoutPhoneNumber,
required double amount,
@@ -23,6 +24,7 @@ class PayoutService {
'amount': amount.toString(),
'phone': payoutPhoneNumber.toString(),
'wallet_type': walletType.toString(),
'otp': otp,
}).timeout(const Duration(seconds: 20));
if (response != 'failure') {
@@ -42,4 +44,24 @@ class PayoutService {
return "حدث خطأ غير متوقع. يرجى المحاولة مرة أخرى.";
}
}
Future<bool> requestOtp({required String phone}) async {
final url = "https://walletintaleq.intaleq.xyz/v1/auth/otp/request.php";
try {
final response = await CRUD().postWallet(link: url, payload: {
'phone_number': phone,
'user_type': 'driver',
'context': 'payout',
}).timeout(const Duration(seconds: 20));
if (response != 'failure') {
final data = (response);
return data['status'] == 'success';
}
return false;
} catch (e) {
debugPrint("Exception during OTP request: $e");
return false;
}
}
}
@@ -41,22 +41,15 @@ class _PayoutScreenState extends State<PayoutScreen> {
if (didAuthenticate && mounted) {
setState(() => _isLoading = true);
// 2. إرسال الطلب إلى السيرفر بالبيانات الجاهزة
final result = await _payoutService.requestPayout(
driverId:
box.read(BoxName.driverID).toString(), // استبدله بـ box.read
amount: widget.amountToWithdraw,
payoutPhoneNumber: widget.payoutPhoneNumber,
walletType: widget.walletType,
);
// 2. طلب رمز التحقق OTP
bool otpSent = await _payoutService.requestOtp(phone: widget.payoutPhoneNumber);
setState(() => _isLoading = false);
if (result != null && result.contains("successfully")) {
// 3. عرض رسالة النجاح النهائية
_showSuccessDialog();
if (otpSent) {
// 3. عرض حوار إدخال الرمز
_showOtpDialog();
} else {
_showErrorDialog(result ?? "حدث خطأ غير معروف.");
_showErrorDialog("فشل في إرسال رمز التحقق. يرجى المحاولة لاحقاً.");
}
}
} catch (e) {
@@ -66,6 +59,70 @@ class _PayoutScreenState extends State<PayoutScreen> {
}
}
void _showOtpDialog() {
final TextEditingController otpController = TextEditingController();
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
title: const Text('أدخل رمز التحقق (OTP)'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('تم إرسال رمز تحقق إلى رقم هاتفك.'),
const SizedBox(height: 16),
TextField(
controller: otpController,
keyboardType: TextInputType.number,
maxLength: 4,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'رمز التحقق',
),
),
],
),
actions: [
TextButton(
child: const Text('إلغاء'),
onPressed: () => Navigator.of(ctx).pop(),
),
ElevatedButton(
child: const Text('تأكيد'),
onPressed: () {
final otp = otpController.text.trim();
if (otp.length >= 3) {
Navigator.of(ctx).pop();
_submitPayoutWithOtp(otp);
}
},
),
],
),
);
}
Future<void> _submitPayoutWithOtp(String otp) async {
setState(() => _isLoading = true);
final result = await _payoutService.requestPayout(
driverId: box.read(BoxName.driverID).toString(),
otp: otp,
amount: widget.amountToWithdraw,
payoutPhoneNumber: widget.payoutPhoneNumber,
walletType: widget.walletType,
);
setState(() => _isLoading = false);
if (result != null && result.contains("successfully")) {
_showSuccessDialog();
} else {
_showErrorDialog(result ?? "حدث خطأ غير معروف.");
}
}
@override
Widget build(BuildContext context) {
// حساب المبلغ الإجمالي المخصوم