fix(security): wallet race conditions - FOR UPDATE + atomic claims on payments, webhooks, bonuses
This commit is contained in:
@@ -86,17 +86,13 @@ if (!$payment) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. تمت عملية الدفع بنجاح، لنقم بإضافة الرصيد
|
||||
// 4. Payment successful, proceed to add balance.
|
||||
// 4. Atomic status claim + wallet update (prevents double-processing)
|
||||
// 4. معالجة ذرية لمنح الرصيد — تمنع التكرار في حال التزامن
|
||||
try {
|
||||
$driverId = $payment['user_id'];
|
||||
// eCash لا تحتاج للقسمة على 100
|
||||
// eCash amount does not need division by 100.
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
// حساب المكافأة
|
||||
// Calculate the bonus.
|
||||
$bonusAmount = match ((int)$originalAmount) {
|
||||
80 => 80.0,
|
||||
200 => 215.0,
|
||||
@@ -105,8 +101,18 @@ try {
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// --- تنفيذ منطق تحديث المحافظ ---
|
||||
// --- Execute wallet update logic ---
|
||||
// بدء معاملة: تحديث الحالة claim + إضافة المحافظ
|
||||
$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);
|
||||
if (!$tokenDriver) throw new Exception('Failed to generate token for driver wallet.');
|
||||
@@ -117,8 +123,6 @@ try {
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
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->execute([':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => $bonusAmount, ':paymentMethod' => $paymentMethod]);
|
||||
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->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->execute([':driverId' => $driverId, ':passengerId' => 'driver', ':amount' => $originalAmount, ':paymentMethod' => $paymentMethod, ':token' => $tokenSiro]);
|
||||
|
||||
$markTokenSiro = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenSiro->execute([':token' => $tokenSiro]);
|
||||
|
||||
// 5. عرض صفحة النجاح النهائية
|
||||
// 5. Display final success page.
|
||||
$con->commit();
|
||||
|
||||
echo "<h1>تمت العملية بنجاح</h1><p>تمت إضافة الرصيد إلى محفظتك. يمكنك الآن العودة إلى التطبيق.</p>";
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// في حال حدوث خطأ، يتم تسجيله وعرض رسالة للمستخدم
|
||||
// In case of an error, log it and display a message to the user.
|
||||
if ($con->inTransaction()) { $con->rollBack(); }
|
||||
error_log("VERIFY_ERROR: " . $e->getMessage() . " | OrderRef: " . $orderRef);
|
||||
echo "<h1>حدث خطأ</h1><p>لقد تم استلام دفعتك بنجاح، ولكن حدث خطأ أثناء تحديث رصيدك. يرجى التواصل مع الدعم الفني وتزويدهم بالرقم المرجعي: " . htmlspecialchars($orderRef) . "</p>";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user