fix(security): wallet race conditions - FOR UPDATE + atomic claims on payments, webhooks, bonuses
This commit is contained in:
@@ -13,13 +13,16 @@ $phone = filterRequest("phone");
|
|||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// 1) CHECK IF DRIVER ALREADY RECEIVED THIS PAYMENT BEFORE
|
// 1) ATOMIC CHECK + INSERT TO PREVENT RACE CONDITION
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
|
$con->beginTransaction();
|
||||||
|
|
||||||
$check = $con->prepare("
|
$check = $con->prepare("
|
||||||
SELECT id
|
SELECT id
|
||||||
FROM driverWallet
|
FROM driverWallet
|
||||||
WHERE driverID = :driverID AND paymentMethod = :paymentMethod
|
WHERE driverID = :driverID AND paymentMethod = :paymentMethod
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
|
FOR UPDATE
|
||||||
");
|
");
|
||||||
|
|
||||||
$check->execute([
|
$check->execute([
|
||||||
@@ -28,12 +31,11 @@ $check->execute([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if ($check->rowCount() > 0) {
|
if ($check->rowCount() > 0) {
|
||||||
// Driver already received this "New Driver" payment
|
$con->rollBack();
|
||||||
printFailure("لقد تم منح هذا الدفع للسائق مسبقاً — لا يمكن تكراره.");
|
printFailure("لقد تم منح هذا الدفع للسائق مسبقاً — لا يمكن تكراره.");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// 2) INSERT INTO driverWallet
|
// 2) INSERT INTO driverWallet
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -57,6 +59,8 @@ $stmt->execute(array(
|
|||||||
':paymentMethod' => $paymentMethod
|
':paymentMethod' => $paymentMethod
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$con->commit();
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
if ($stmt->rowCount() > 0) {
|
||||||
|
|
||||||
printSuccess("Record saved successfully");
|
printSuccess("Record saved successfully");
|
||||||
|
|||||||
@@ -86,17 +86,13 @@ if (!$payment) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. تمت عملية الدفع بنجاح، لنقم بإضافة الرصيد
|
// 4. Atomic status claim + wallet update (prevents double-processing)
|
||||||
// 4. Payment successful, proceed to add balance.
|
// 4. معالجة ذرية لمنح الرصيد — تمنع التكرار في حال التزامن
|
||||||
try {
|
try {
|
||||||
$driverId = $payment['user_id'];
|
$driverId = $payment['user_id'];
|
||||||
// eCash لا تحتاج للقسمة على 100
|
|
||||||
// eCash amount does not need division by 100.
|
|
||||||
$originalAmount = floatval($payment['amount']);
|
$originalAmount = floatval($payment['amount']);
|
||||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||||
|
|
||||||
// حساب المكافأة
|
|
||||||
// Calculate the bonus.
|
|
||||||
$bonusAmount = match ((int)$originalAmount) {
|
$bonusAmount = match ((int)$originalAmount) {
|
||||||
80 => 80.0,
|
80 => 80.0,
|
||||||
200 => 215.0,
|
200 => 215.0,
|
||||||
@@ -105,8 +101,18 @@ try {
|
|||||||
default => $originalAmount,
|
default => $originalAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- تنفيذ منطق تحديث المحافظ ---
|
// بدء معاملة: تحديث الحالة claim + إضافة المحافظ
|
||||||
// --- Execute wallet update logic ---
|
$con->beginTransaction();
|
||||||
|
|
||||||
|
// محاولة ذرية لـ claim المعاملة (فقط إذا كانت لا تزال status = 1)
|
||||||
|
$claimStmt = $con->prepare("UPDATE paymentsLogSyriaDriver SET status = 2 WHERE order_ref = :ref AND status = 1");
|
||||||
|
$claimStmt->execute([':ref' => $orderRef]);
|
||||||
|
if ($claimStmt->rowCount() === 0) {
|
||||||
|
$con->rollBack();
|
||||||
|
error_log("VERIFY_RACE: Concurrent claim for OrderRef " . $orderRef);
|
||||||
|
echo "<h1>تمت معالجة هذا الطلب مسبقاً</h1><p>الدفعة قيد المعالجة، يرجى التحقق من رصيدك في التطبيق.</p>";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$tokenDriver = generateToken($con, $driverId, $bonusAmount);
|
$tokenDriver = generateToken($con, $driverId, $bonusAmount);
|
||||||
if (!$tokenDriver) throw new Exception('Failed to generate token for driver wallet.');
|
if (!$tokenDriver) throw new Exception('Failed to generate token for driver wallet.');
|
||||||
@@ -117,8 +123,6 @@ try {
|
|||||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||||
if (!$paymentID) throw new Exception('Failed to generate payment ID.');
|
if (!$paymentID) throw new Exception('Failed to generate payment ID.');
|
||||||
|
|
||||||
// إضافة الرصيد إلى driverWallet
|
|
||||||
// Add balance to driverWallet
|
|
||||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||||
$insertDriver->execute([':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => $bonusAmount, ':paymentMethod' => $paymentMethod]);
|
$insertDriver->execute([':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => $bonusAmount, ':paymentMethod' => $paymentMethod]);
|
||||||
if ($insertDriver->rowCount() === 0) throw new Exception('Failed to insert into driverWallet.');
|
if ($insertDriver->rowCount() === 0) throw new Exception('Failed to insert into driverWallet.');
|
||||||
@@ -126,21 +130,18 @@ try {
|
|||||||
$markTokenDriver = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
$markTokenDriver = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||||
$markTokenDriver->execute([':token' => $tokenDriver]);
|
$markTokenDriver->execute([':token' => $tokenDriver]);
|
||||||
|
|
||||||
// إضافة الرصيد إلى siroWallet
|
|
||||||
// Add balance to siroWallet
|
|
||||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt) VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt) VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||||
$insertSiro->execute([':driverId' => $driverId, ':passengerId' => 'driver', ':amount' => $originalAmount, ':paymentMethod' => $paymentMethod, ':token' => $tokenSiro]);
|
$insertSiro->execute([':driverId' => $driverId, ':passengerId' => 'driver', ':amount' => $originalAmount, ':paymentMethod' => $paymentMethod, ':token' => $tokenSiro]);
|
||||||
|
|
||||||
$markTokenSiro = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
$markTokenSiro = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||||
$markTokenSiro->execute([':token' => $tokenSiro]);
|
$markTokenSiro->execute([':token' => $tokenSiro]);
|
||||||
|
|
||||||
// 5. عرض صفحة النجاح النهائية
|
$con->commit();
|
||||||
// 5. Display final success page.
|
|
||||||
echo "<h1>تمت العملية بنجاح</h1><p>تمت إضافة الرصيد إلى محفظتك. يمكنك الآن العودة إلى التطبيق.</p>";
|
echo "<h1>تمت العملية بنجاح</h1><p>تمت إضافة الرصيد إلى محفظتك. يمكنك الآن العودة إلى التطبيق.</p>";
|
||||||
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
// في حال حدوث خطأ، يتم تسجيله وعرض رسالة للمستخدم
|
if ($con->inTransaction()) { $con->rollBack(); }
|
||||||
// In case of an error, log it and display a message to the user.
|
|
||||||
error_log("VERIFY_ERROR: " . $e->getMessage() . " | OrderRef: " . $orderRef);
|
error_log("VERIFY_ERROR: " . $e->getMessage() . " | OrderRef: " . $orderRef);
|
||||||
echo "<h1>حدث خطأ</h1><p>لقد تم استلام دفعتك بنجاح، ولكن حدث خطأ أثناء تحديث رصيدك. يرجى التواصل مع الدعم الفني وتزويدهم بالرقم المرجعي: " . htmlspecialchars($orderRef) . "</p>";
|
echo "<h1>حدث خطأ</h1><p>لقد تم استلام دفعتك بنجاح، ولكن حدث خطأ أثناء تحديث رصيدك. يرجى التواصل مع الدعم الفني وتزويدهم بالرقم المرجعي: " . htmlspecialchars($orderRef) . "</p>";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,47 +54,57 @@ if ($isSuccess !== true) {
|
|||||||
}
|
}
|
||||||
logError("2", "Payment reported as SUCCESS by ecash.");
|
logError("2", "Payment reported as SUCCESS by ecash.");
|
||||||
|
|
||||||
// 4. Find the original transaction in your database using the Order Reference
|
// 4. Find and process the transaction atomically
|
||||||
try {
|
try {
|
||||||
$stmt = $con->prepare("SELECT * FROM ecash_transactions WHERE order_ref = ? LIMIT 1");
|
$con->beginTransaction();
|
||||||
|
|
||||||
|
$stmt = $con->prepare("SELECT * FROM ecash_transactions WHERE order_ref = ? LIMIT 1 FOR UPDATE");
|
||||||
$stmt->execute([$orderRef]);
|
$stmt->execute([$orderRef]);
|
||||||
$transaction = $stmt->fetch(PDO::FETCH_ASSOC);
|
$transaction = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$transaction) {
|
if (!$transaction) {
|
||||||
|
$con->rollBack();
|
||||||
logError("3", "OrderRef not found in our database.", ["orderRef" => $orderRef]);
|
logError("3", "OrderRef not found in our database.", ["orderRef" => $orderRef]);
|
||||||
http_response_code(404); // Not Found
|
http_response_code(404);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Security Check: Ensure this transaction hasn't already been processed
|
|
||||||
if ($transaction['status'] !== 'pending') {
|
if ($transaction['status'] !== 'pending') {
|
||||||
|
$con->rollBack();
|
||||||
logError("3.1", "Transaction already processed.", ["orderRef" => $orderRef, "status" => $transaction['status']]);
|
logError("3.1", "Transaction already processed.", ["orderRef" => $orderRef, "status" => $transaction['status']]);
|
||||||
http_response_code(200); // Acknowledge receipt, but prevent double-spending
|
http_response_code(200);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomically mark as processing to prevent concurrent webhooks
|
||||||
|
$lockStmt = $con->prepare("UPDATE ecash_transactions SET status = 'processing' WHERE order_ref = ? AND status = 'pending'");
|
||||||
|
$lockStmt->execute([$orderRef]);
|
||||||
|
if ($lockStmt->rowCount() === 0) {
|
||||||
|
$con->rollBack();
|
||||||
|
logError("3.2", "Concurrent webhook detected, transaction already claimed.", ["orderRef" => $orderRef]);
|
||||||
|
http_response_code(200);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$passengerId = $transaction['passenger_id'];
|
$passengerId = $transaction['passenger_id'];
|
||||||
$paidAmount = $transaction['amount']; // Use the amount from your DB as the source of truth
|
$paidAmount = $transaction['amount'];
|
||||||
logError("3", "Transaction found in DB.", ["passengerId" => $passengerId, "amount" => $paidAmount]);
|
logError("3", "Transaction found in DB.", ["passengerId" => $passengerId, "amount" => $paidAmount]);
|
||||||
|
|
||||||
// 5. --- Start Wallet Update Logic (from your paymet_verfy.php) ---
|
|
||||||
|
|
||||||
// Calculate bonus
|
|
||||||
$finalAmount = calculateBonus($paidAmount);
|
$finalAmount = calculateBonus($paidAmount);
|
||||||
logError("4", "Bonus calculated.", ["original" => $paidAmount, "final" => $finalAmount]);
|
logError("4", "Bonus calculated.", ["original" => $paidAmount, "final" => $finalAmount]);
|
||||||
|
|
||||||
// Add to Passenger Wallet
|
|
||||||
$passengerToken = generatePaymentToken($passengerId, $finalAmount);
|
$passengerToken = generatePaymentToken($passengerId, $finalAmount);
|
||||||
if ($passengerToken) {
|
if ($passengerToken) {
|
||||||
addToPassengerWallet($passengerId, $finalAmount, $passengerToken);
|
addToPassengerWallet($passengerId, $finalAmount, $passengerToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to Siro Wallet
|
$paymentMethod = 'ecash';
|
||||||
$paymentMethod = 'ecash'; // Or another identifier
|
|
||||||
addToSiroWallet($passengerId, $paidAmount, $paymentMethod);
|
addToSiroWallet($passengerId, $paidAmount, $paymentMethod);
|
||||||
|
|
||||||
// 6. Mark the transaction as 'success' in your database to prevent reprocessing
|
$stmtUpdate = $con->prepare("UPDATE ecash_transactions SET status = 'success', ecash_transaction_no = ?, updated_at = NOW() WHERE order_ref = ?");
|
||||||
updateTransactionStatus($orderRef, 'success', $transactionNo);
|
$stmtUpdate->execute([$transactionNo, $orderRef]);
|
||||||
|
|
||||||
|
$con->commit();
|
||||||
logError("7", "Process completed successfully.");
|
logError("7", "Process completed successfully.");
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ try {
|
|||||||
// الخطوة 1: التحقق من التوكنات (Security Check)
|
// الخطوة 1: التحقق من التوكنات (Security Check)
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
// أ) فحص توكن السائق
|
// أ) فحص توكن السائق (مع FOR UPDATE)
|
||||||
$stmtCheckD = $con->prepare("SELECT id FROM payment_tokens WHERE token = ? AND isUsed = FALSE");
|
$stmtCheckD = $con->prepare("SELECT id FROM payment_tokens WHERE token = ? AND isUsed = FALSE FOR UPDATE");
|
||||||
$stmtCheckD->execute([$tokenDriver]);
|
$stmtCheckD->execute([$tokenDriver]);
|
||||||
$tokenDriverData = $stmtCheckD->fetch();
|
$tokenDriverData = $stmtCheckD->fetch();
|
||||||
|
|
||||||
@@ -36,8 +36,8 @@ try {
|
|||||||
throw new Exception("Invalid or used Driver Token");
|
throw new Exception("Invalid or used Driver Token");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ب) فحص توكن الراكب
|
// ب) فحص توكن الراكب (مع FOR UPDATE)
|
||||||
$stmtCheckP = $con->prepare("SELECT id FROM payment_tokens_passenger WHERE token = ? AND isUsed = FALSE");
|
$stmtCheckP = $con->prepare("SELECT id FROM payment_tokens_passenger WHERE token = ? AND isUsed = FALSE FOR UPDATE");
|
||||||
$stmtCheckP->execute([$tokenPassenger]);
|
$stmtCheckP->execute([$tokenPassenger]);
|
||||||
$tokenPassengerData = $stmtCheckP->fetch();
|
$tokenPassengerData = $stmtCheckP->fetch();
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,12 @@ if (!$token || !$passenger_id || !$amount || !$payment_method) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// logDebug("Checking token validity: $token with DriverID: $driver_id");
|
|
||||||
|
|
||||||
// Choose correct table based on driverId
|
|
||||||
$table = ($driver_id == 'passenger') ? "payment_tokens_passenger" : "payment_tokens";
|
$table = ($driver_id == 'passenger') ? "payment_tokens_passenger" : "payment_tokens";
|
||||||
// logDebug("table is: " . $table);
|
|
||||||
|
|
||||||
// Check if token is valid and not used
|
$con->beginTransaction();
|
||||||
$stmt = $con->prepare("SELECT * FROM $table WHERE token = :token AND isUsed = FALSE");
|
|
||||||
|
// Check if token is valid and not used (locked row)
|
||||||
|
$stmt = $con->prepare("SELECT * FROM $table WHERE token = :token AND isUsed = FALSE FOR UPDATE");
|
||||||
$stmt->execute(array(':token' => $token));
|
$stmt->execute(array(':token' => $token));
|
||||||
$tokenData = $stmt->fetch();
|
$tokenData = $stmt->fetch();
|
||||||
|
|
||||||
@@ -60,24 +58,21 @@ try {
|
|||||||
$stmt->bindParam(':token', $token, PDO::PARAM_STR);
|
$stmt->bindParam(':token', $token, PDO::PARAM_STR);
|
||||||
|
|
||||||
if ($stmt->execute()) {
|
if ($stmt->execute()) {
|
||||||
// logDebug("Wallet data saved successfully.");
|
|
||||||
|
|
||||||
// Mark token as used in the correct table
|
|
||||||
$stmt = $con->prepare("UPDATE $table SET isUsed = TRUE WHERE id = :tokenID");
|
$stmt = $con->prepare("UPDATE $table SET isUsed = TRUE WHERE id = :tokenID");
|
||||||
$stmt->execute(array(':tokenID' => $tokenData['id']));
|
$stmt->execute(array(':tokenID' => $tokenData['id']));
|
||||||
|
|
||||||
// logDebug("Token marked as used in $table.");
|
$con->commit();
|
||||||
printSuccess("Wallet data saved successfully");
|
printSuccess("Wallet data saved successfully");
|
||||||
} else {
|
} else {
|
||||||
// logDebug("Failed to save wallet data.");
|
$con->rollBack();
|
||||||
printFailure("Failed to save wallet data");
|
printFailure("Failed to save wallet data");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// logDebug("Invalid or already used token: $token");
|
$con->rollBack();
|
||||||
printFailure("Invalid or already used token");
|
printFailure("Invalid or already used token");
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
// logDebug("Exception: " . $e->getMessage());
|
if ($con->inTransaction()) { $con->rollBack(); }
|
||||||
error_log("[siroWallet/add] " . $e->getMessage());
|
error_log("[siroWallet/add] " . $e->getMessage());
|
||||||
printFailure("An error occurred");
|
printFailure("An error occurred");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,24 +96,26 @@ try {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
global $con;
|
global $con;
|
||||||
|
|
||||||
// 4) التحقق من السجل في قاعدة البيانات
|
// بدء المعاملة أولاً (قبل SELECT للحماية من السباق)
|
||||||
$chk = $con->prepare("SELECT status, user_id, amount, payment_method FROM paymentsLogSyria WHERE order_ref = :ref LIMIT 1");
|
$con->beginTransaction();
|
||||||
|
|
||||||
|
// 4) التحقق من السجل في قاعدة البيانات مع FOR UPDATE
|
||||||
|
$chk = $con->prepare("SELECT status, user_id, amount, payment_method FROM paymentsLogSyria WHERE order_ref = :ref LIMIT 1 FOR UPDATE");
|
||||||
$chk->execute([':ref' => $transactionID]);
|
$chk->execute([':ref' => $transactionID]);
|
||||||
$payment = $chk->fetch(PDO::FETCH_ASSOC);
|
$payment = $chk->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$payment) {
|
if (!$payment) {
|
||||||
|
$con->rollBack();
|
||||||
printFailure($lang === 'ar' ? "لم يتم العثور على السجل." : "Payment row not found");
|
printFailure($lang === 'ar' ? "لم يتم العثور على السجل." : "Payment row not found");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((int)$payment['status'] === 1) {
|
if ((int)$payment['status'] === 1) {
|
||||||
|
$con->rollBack();
|
||||||
printSuccess(['message' => 'Already confirmed', 'data' => ['order_ref' => $transactionID]]);
|
printSuccess(['message' => 'Already confirmed', 'data' => ['order_ref' => $transactionID]]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// بدء المعاملة (Transaction)
|
|
||||||
$con->beginTransaction();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// أ) تحديث حالة الدفع في السجل
|
// أ) تحديث حالة الدفع في السجل
|
||||||
$stmtUpdate = $con->prepare("UPDATE `paymentsLogSyria` SET status = 1, updated_at = NOW() WHERE order_ref = :ref");
|
$stmtUpdate = $con->prepare("UPDATE `paymentsLogSyria` SET status = 1, updated_at = NOW() WHERE order_ref = :ref");
|
||||||
|
|||||||
@@ -20,30 +20,27 @@ if (empty($driverId) || $amount <= 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// --- 2. التحقق من بيانات السائق ورصيده ---
|
// --- Atomic balance check + insert with transaction ---
|
||||||
|
$con->beginTransaction();
|
||||||
|
|
||||||
$stmt_driver = $con->prepare("
|
$stmt_driver = $con->prepare("
|
||||||
SELECT SUM(amount) AS balance
|
SELECT COALESCE(SUM(amount), 0) AS balance
|
||||||
FROM driverWallet
|
FROM driverWallet
|
||||||
WHERE driverID = :id
|
WHERE driverID = :id
|
||||||
LIMIT 1
|
FOR UPDATE
|
||||||
");
|
");
|
||||||
$stmt_driver->execute([':id' => $driverId]);
|
$stmt_driver->execute([':id' => $driverId]);
|
||||||
$driver = $stmt_driver->fetch(PDO::FETCH_ASSOC);
|
$driver = $stmt_driver->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$driver) {
|
$payout_fee = 3500.00;
|
||||||
printFailure("Driver not found.");
|
$total_deduction = $amount + $payout_fee;
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$payout_fee = 3500.00; // عمولة السحب
|
|
||||||
$total_deduction = $amount + $payout_fee; // المبلغ المطلوب مع العمولة
|
|
||||||
|
|
||||||
if ($driver['balance'] < $total_deduction) {
|
if ($driver['balance'] < $total_deduction) {
|
||||||
|
$con->rollBack();
|
||||||
printFailure("Insufficient balance. Required: $total_deduction");
|
printFailure("Insufficient balance. Required: $total_deduction");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 3. إنشاء طلب السحب في قاعدة البيانات ---
|
|
||||||
$sql = "
|
$sql = "
|
||||||
INSERT INTO payout_requests (driver_id, driver_phone, amount, wallet_type)
|
INSERT INTO payout_requests (driver_id, driver_phone, amount, wallet_type)
|
||||||
VALUES (:did, :phone, :amount, :wallet)
|
VALUES (:did, :phone, :amount, :wallet)
|
||||||
@@ -56,6 +53,8 @@ try {
|
|||||||
':wallet'=> $wallet_type
|
':wallet'=> $wallet_type
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$con->commit();
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
if ($stmt->rowCount() > 0) {
|
||||||
// --- 4. إرسال إشعار لخدمة العملاء ---
|
// --- 4. إرسال إشعار لخدمة العملاء ---
|
||||||
$customerServicePhone = getenv('CUSTOMER_SERVICE_PHONE');
|
$customerServicePhone = getenv('CUSTOMER_SERVICE_PHONE');
|
||||||
|
|||||||
Reference in New Issue
Block a user