Update: 2026-08-07 05:56:50

This commit is contained in:
Hamza-Ayed
2026-08-07 05:56:50 +03:00
parent 7a932580a3
commit 67465df000
14 changed files with 1257 additions and 9 deletions
+130 -9
View File
@@ -45,6 +45,10 @@ function buildMarketPayload($rideId, $lat, $lng, $payloadData, $extraMarketData
'duration' => $payloadData[15],
'passengerRate' => $payloadData[33],
'passengerId' => $payloadData[7],
// ‏رحلة محجوزة: يقرأها available_rides_page و order_request_page
// ‏ونافذة الأوفرلي. غيابها = رحلة لحظية عادية.
'is_scheduled' => $payloadData[39] ?? '',
'scheduled_at' => $payloadData[40] ?? '',
], $extraMarketData);
}
@@ -113,13 +117,89 @@ function broadcastRideToMarket($rideId, $lat, $lng, $payloadData, $extraMarketDa
error_log("[add_ride] Request started. passenger_id=" . ($_POST['passenger_id'] ?? '?'));
// ── 1. Input ───────────────────────────────────────────────────
// ‏وسم الحجز المسبق: يمرّره bot/cron_scheduled_rides.php حين يحوّل حجزاً
// ‏لرحلة فعلية. الرحلة العادية تتركه فارغاً فلا يظهر شيء في التطبيقات.
$isScheduledRide = filterRequest("source") === 'scheduled';
$scheduledAtLabel = filterRequest("scheduled_at") ?: '';
$start_location = filterRequest("start_location");
$end_location = filterRequest("end_location");
$price = filterRequest("price");
$price_token = filterRequest("price_token");
// Force passenger_id from JWT — never trust user-supplied passenger_id
$passenger_id = $user_id;
// ══════════════════════════════════════════════════════════════
// 🔐 مسار S2S للحجز المسبق — بوّابة مزدوجة لا مفتاح وحده
//
// ‏bot/cron_scheduled_rides.php لا يملك JWT راكب ولا رمز سعر، فلا يمكنه
// ‏المرور من الحراسة العادية. لكن فتح استثناء بمفتاح S2S وحده يعني أن
// ‏تسريب المفتاح = إنشاء رحلات باسم أي راكب.
//
// ‏لذلك شرطان معاً:
// ١. مفتاح S2S صحيح
// ٢. الحجز موجود فعلاً في scheduled_rides بحالة 'dispatching'، ويخصّ
// هذا الراكب بالذات، وبنفس نقطة الانطلاق
//
// ‏الشرط الثاني هو الحماية الحقيقية: لا رحلة تُنشأ إلا مقابل حجز أنشأه
// ‏الراكب بنفسه عبر المسار الموثَّق، والكرون قفله للتوّ. حامل المفتاح
// ‏وحده لا يستطيع اختلاق حجز.
// ══════════════════════════════════════════════════════════════
$isInternalScheduled = false;
if ($isScheduledRide) {
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
$expectedKey = getenv('S2S_SHARED_KEY') ?: '';
$scheduledId = (int) (filterRequest('scheduled_id', 'int') ?: 0);
$claimedPassenger = filterRequest('passenger_id');
if (empty($expectedKey) || !hash_equals($expectedKey, (string) $providedKey)) {
error_log('[add_ride] SECURITY: مسار الحجز بمفتاح S2S غير صالح');
printFailure('Unauthorized');
exit;
}
if ($scheduledId <= 0 || empty($claimedPassenger)) {
printFailure('Missing scheduled_id or passenger_id');
exit;
}
try {
$stmtBooking = $con->prepare("
SELECT passenger_id, start_location, end_location, car_type,
distance, duration
FROM scheduled_rides
WHERE id = ? AND status = 'dispatching' LIMIT 1
");
$stmtBooking->execute([$scheduledId]);
$booking = $stmtBooking->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $eB) {
error_log('[add_ride] تعذّرت قراءة الحجز: ' . $eB->getMessage());
printFailure('Server error');
exit;
}
if (!$booking) {
error_log("[add_ride] SECURITY: حجز #$scheduledId غير موجود أو ليس قيد الإطلاق");
printFailure('Invalid booking');
exit;
}
if ((string) $booking['passenger_id'] !== (string) $claimedPassenger
|| !coordsMatch($booking['start_location'], $start_location)) {
error_log("[add_ride] SECURITY: حجز #$scheduledId لا يطابق الراكب أو النقطة");
printFailure('Booking mismatch');
exit;
}
// ‏من هنا نثق ببيانات **الحجز المخزَّن** لا بما وصل في الطلب.
$isInternalScheduled = true;
$passenger_id = $booking['passenger_id'];
$end_location = $booking['end_location'];
$carType = $booking['car_type'];
$distance = $booking['distance'];
} else {
// Force passenger_id from JWT — never trust user-supplied passenger_id
$passenger_id = $user_id;
}
$driver_id = (string)(filterRequest("driver_id") ?: '0');
$status = filterRequest("status") ?: 'nothing';
$price_for_driver = filterRequest("price_for_driver") ?: ($price ?: '0');
@@ -160,34 +240,68 @@ if (empty($passenger_id) || empty($start_location) || empty($end_location) || em
exit;
}
// ── التسعير الداخلي لمسار الحجز ─────────────────────────────
// ‏السعر لا يأتي من الطلب: الكرون يمرّر تقدير وقت الحجز، وهو قديم بساعات
// ‏ولا يعرف الذروة. نحسبه هنا من أسعار الدولة لحظة الإنشاء.
if ($isInternalScheduled) {
require_once __DIR__ . '/../pricing/pricing_helper.php';
$kazanRow = [];
try {
$stmtK = $con->prepare("SELECT * FROM kazan WHERE country = ? LIMIT 1");
$stmtK->execute([getenv('GLOBAL_COUNTRY') ?: 'Jordan']);
$kazanRow = $stmtK->fetch(PDO::FETCH_ASSOC) ?: [];
} catch (PDOException $eK) {
error_log('[add_ride] تعذّر جلب أسعار الدولة للحجز: ' . $eK->getMessage());
}
if (empty($kazanRow)) {
printFailure('Pricing unavailable');
exit;
}
$price = computeInternalRidePrice(
$kazanRow, (string) $carType,
(float) $distance, (int) ($booking['duration'] ?? 0)
);
$price_for_driver = $price;
$price_for_passenger = $price;
error_log("[add_ride] حجز #$scheduledId سُعِّر داخلياً: $price");
}
// SECURE PRICE TOKEN VERIFICATION
if (empty($price_token)) {
if (!$isInternalScheduled && empty($price_token)) {
error_log("[add_ride] Security failed — price_token is missing.");
printFailure("Secure price token is required");
exit;
}
$decrypted = isset($encryptionHelper) ? $encryptionHelper->decryptData($price_token) : false;
if (!$decrypted) {
$decrypted = (!$isInternalScheduled && isset($encryptionHelper))
? $encryptionHelper->decryptData($price_token) : false;
if (!$isInternalScheduled && !$decrypted) {
error_log("[add_ride] Security failed — failed to decrypt price_token.");
printFailure("Invalid or tampered price token");
exit;
}
$tokenData = json_decode($decrypted, true);
if (!$tokenData || !isset($tokenData['expires']) || $tokenData['expires'] < time()) {
$tokenData = $decrypted ? json_decode($decrypted, true) : null;
if (!$isInternalScheduled
&& (!$tokenData || !isset($tokenData['expires']) || $tokenData['expires'] < time())) {
error_log("[add_ride] Security failed — token is expired or invalid JSON.");
printFailure("Price token has expired, please request estimation again");
exit;
}
if ($tokenData['passenger_id'] != $passenger_id) {
if (!$isInternalScheduled && $tokenData['passenger_id'] != $passenger_id) {
error_log("[add_ride] Security failed — passenger_id mismatch.");
printFailure("Tampered price token (passenger mismatch)");
exit;
}
if (!coordsMatch($tokenData['start_location'], $start_location) || !coordsMatch($tokenData['end_location'], $end_location)) {
if (!$isInternalScheduled
&& (!coordsMatch($tokenData['start_location'], $start_location)
|| !coordsMatch($tokenData['end_location'], $end_location))) {
error_log("[add_ride] Security failed — coordinates mismatch. Token: " . ($tokenData['start_location'] . " / " . $tokenData['end_location']) . " Request: " . ($start_location . " / " . $end_location));
printFailure("Tampered price token (route mismatch)");
exit;
@@ -394,6 +508,13 @@ try {
// اللي بتوصلها البيانات كـ List مش كـ Map مسمّى مثل FCM
isset($extraDispatchData['driver_earnings_extra']) ? $extraDispatchData['driver_earnings_extra'] : '',
isset($extraDispatchData['driver_earnings_currency']) ? $extraDispatchData['driver_earnings_currency'] : '',
// 🆕 Index 37/38: وسم العرض الحصري في الإسناد بالدفعات
'',
'',
// 🆕 Index 39/40: رحلة محجوزة مسبقاً — السائق يجب أن يعرف أنها
// ليست طلباً لحظياً بل موعد مضبوط، وأن الراكب ينتظره في وقت محدد.
$isScheduledRide ? '1' : '',
$isScheduledRide ? (string) $scheduledAtLabel : '',
];
// Direct dispatch للسائقين القريبين