Update: 2026-07-09 04:13:14
This commit is contained in:
@@ -100,8 +100,8 @@ try {
|
||||
try {
|
||||
// Fetch ride data from remote/local DB for server-side calculation
|
||||
$stmtRideData = $con->prepare("
|
||||
SELECT id, price AS quoted_price, car_type,
|
||||
distance AS planned_distance, passenger_id, driver_id
|
||||
SELECT id, price AS quoted_price, car_type,
|
||||
distance AS planned_distance, passenger_id, driver_id, price_for_driver
|
||||
FROM ride WHERE id = ? AND driver_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
@@ -117,6 +117,20 @@ try {
|
||||
$kazanPercent = floatval($countryPricing['kazanPercent'] ?? $countryPricing['kazan'] ?? 10); // 🆕 من جدول kazan (kazanPercent هو الاسم الجديد)
|
||||
$carType = $rideData['car_type'] ?? 'Fixed Price';
|
||||
|
||||
// 🔥 [Fix Driver Commission] عند عرض السعر (pricing/get.php) قد تُطبَّق نسبة
|
||||
// خصم عمولة خاصة بالمنطقة (kazanDiscountFactor من surge:kazan_discounts)
|
||||
// فتُحفَظ نتيجتها في عمود ride.price_for_driver — وهو "الوعد" الذي رآه
|
||||
// السائق كـ"أرباحك أعلى" وقت قبول الطلب. إعادة جلب kazanPercent الخام هنا
|
||||
// كانت تتجاهل ذلك الخصم بالكامل. نشتق نسبة العمولة الفعلية من الفرق
|
||||
// المحفوظ فعلياً بين السعر المُقتبَس وحصة السائق منه، ونستخدمها بدل
|
||||
// النسبة الخام، حتى تبقى النسبة المطبقة عند التسوية مطابقة لما وُعد به.
|
||||
$priceForDriver = floatval($rideData['price_for_driver'] ?? 0);
|
||||
if ($quotedPrice > 0 && $priceForDriver > 0 && $priceForDriver <= $quotedPrice) {
|
||||
$effectiveKazanPercent = (($quotedPrice - $priceForDriver) / $quotedPrice) * 100;
|
||||
error_log("[finish_ride_updates] Using locked commission rate for ride $rideId: {$effectiveKazanPercent}% (was raw {$kazanPercent}%)");
|
||||
$kazanPercent = $effectiveKazanPercent;
|
||||
}
|
||||
|
||||
// Fixed-price types, Speed & Awfar: use quoted price as-is
|
||||
$fixedPriceTypes = ['Speed', 'Fixed Price', 'Awfar Car'];
|
||||
if (in_array($carType, $fixedPriceTypes)) {
|
||||
@@ -144,6 +158,19 @@ try {
|
||||
$cleanDist = preg_replace('/[^0-9.]/', '', $actualDistance);
|
||||
$distanceKm = floatval($cleanDist);
|
||||
|
||||
// 🔥 [Fix Price Cap] سقف أعلى على الانحراف عن المسافة المخططة —
|
||||
// actualDistance يأتي من العميل، بدون هذا السقف يمكن لانجراف GPS
|
||||
// أو قيمة مُتلاعَب بها أن تُضخّم السعر النهائي بلا حدود. نسمح بهامش
|
||||
// معقول للانحرافات الحقيقية (تحويلة، إغلاق طريق...) ونقصّ الباقي.
|
||||
$plannedDistanceKm = floatval($rideData['planned_distance'] ?? 0);
|
||||
if ($plannedDistanceKm > 0) {
|
||||
$maxAllowedDistanceKm = max($plannedDistanceKm * 1.5, $plannedDistanceKm + 5);
|
||||
if ($distanceKm > $maxAllowedDistanceKm) {
|
||||
error_log("[finish_ride_updates] ⚠️ actualDistance ($distanceKm km) exceeds cap ($maxAllowedDistanceKm km) for ride $rideId — planned was $plannedDistanceKm km. Clamping.");
|
||||
$distanceKm = $maxAllowedDistanceKm;
|
||||
}
|
||||
}
|
||||
|
||||
if ($distanceKm <= 0) {
|
||||
$finalPrice = $quotedPrice; // fallback
|
||||
} else {
|
||||
@@ -151,6 +178,13 @@ try {
|
||||
$perKmRate = getPerKmRate($carType, $countryPricing);
|
||||
$perMinRate = getPerMinRate($countryPricing);
|
||||
$durationMin = intval(preg_replace('/[^0-9]/', '', $actualDuration));
|
||||
// نفس فكرة السقف على المدة: لا نسمح بمدة أكبر من ضعف زمن الرحلة
|
||||
// المعقول (نفترض حد أقصى واسع 3 ساعات إذا لم تتوفر مدة مخططة)
|
||||
$maxAllowedDurationMin = 180;
|
||||
if ($durationMin > $maxAllowedDurationMin) {
|
||||
error_log("[finish_ride_updates] ⚠️ actualDuration ($durationMin min) exceeds cap ($maxAllowedDurationMin min) for ride $rideId. Clamping.");
|
||||
$durationMin = $maxAllowedDurationMin;
|
||||
}
|
||||
|
||||
$calculated = ($distanceKm * $perKmRate) + ($durationMin * $perMinRate);
|
||||
|
||||
@@ -161,11 +195,14 @@ try {
|
||||
error_log("[finish_ride_updates] Driver $driver_id has active 0% commission streak!");
|
||||
}
|
||||
|
||||
// السعر النهائي يجب أن يتضمن العمولة دائماً لكي يدفعها الراكب،
|
||||
// السعر النهائي يجب أن يتضمن العمولة دائماً لكي يدفعها الراكب،
|
||||
// لكن إذا كانت العمولة صفر للسائق، يتم إعطاؤها للسائق بدلاً من الشركة عبر السيرفر المالي.
|
||||
$calculated *= (1 + ($kazanPercent / 100));
|
||||
|
||||
$finalPrice = max($quotedPrice, round($calculated, 2));
|
||||
// 🔥 [Fix Price Cap] سقف أعلى مطلق أيضاً على السعر النهائي نفسه
|
||||
// (دفاع ثانٍ) — لا يتجاوز 1.6 * السعر المُقتبَس أصلاً بأي حال.
|
||||
$maxAllowedPrice = $quotedPrice > 0 ? $quotedPrice * 1.6 : round($calculated, 2);
|
||||
$finalPrice = max($quotedPrice, min(round($calculated, 2), $maxAllowedPrice));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,13 +219,12 @@ try {
|
||||
// 4. Atomic Transaction: Update DBs + Process Payment
|
||||
// ============================================================
|
||||
try {
|
||||
// --- Update Remote DB (con_ride) FIRST ---
|
||||
if (isset($con_ride)) {
|
||||
$stmtRemote = $con_ride->prepare(
|
||||
"UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"
|
||||
);
|
||||
$stmtRemote->execute([$newStatus, $finalPrice, $rideId]);
|
||||
}
|
||||
// 🔥 [Fix Split-Brain] كان تحديث قاعدة البيانات البعيدة (con_ride) يحدث
|
||||
// هنا قبل محاولة الدفع وبدون أي Rollback عليه — فإذا فشل الدفع لاحقاً،
|
||||
// كانت con_ride تبقى 'Finished' بينما المحلية تُرجَع لـ 'Begin'،
|
||||
// فإعادة محاولة لاحقة تفشل بصمت على con_ride (شرط WHERE status='Begin'
|
||||
// لم يعد يتحقق) وتُنتج سعرين مختلفين بين القاعدتين. الآن نؤجل تحديث
|
||||
// con_ride إلى ما بعد نجاح الدفع فعلياً (انظر الأسفل بعد commit()).
|
||||
|
||||
// --- BEGIN Local DB Transaction ---
|
||||
$con->beginTransaction();
|
||||
@@ -276,7 +312,21 @@ try {
|
||||
|
||||
// ✅ Payment succeeded — COMMIT
|
||||
$con->commit();
|
||||
|
||||
|
||||
// 🔥 [Fix Split-Brain] تحديث القاعدة البعيدة الآن فقط، بعد أن أصبح الدفع
|
||||
// والتحديث المحلي مؤكدَين نجاحهما — يبقي الحالتين متطابقتين دائماً.
|
||||
// فشل هذا التحديث best-effort فقط (لا يُرجع الرحلة المحلية المُنجَزة فعلاً).
|
||||
if (isset($con_ride)) {
|
||||
try {
|
||||
$stmtRemote = $con_ride->prepare(
|
||||
"UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"
|
||||
);
|
||||
$stmtRemote->execute([$newStatus, $finalPrice, $rideId]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("[finish_ride_updates] Remote DB (con_ride) update failed for ride $rideId: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 Update Driver Streak (Increment because ride is finished successfully)
|
||||
handleDriverStreak($con, $driver_id, 'increment');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user