Update: 2026-08-07 15:50:04
This commit is contained in:
@@ -391,6 +391,42 @@ try {
|
||||
'passenger_id' => $passengerIdValue ?? '',
|
||||
]);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// الراكب الذي طلب برسالة نصية لا يملك إنترنت: السوكيت لن يصله،
|
||||
// والإشعار الصامت أعلاه يذهب إلى العدم. الرسالة النصية هي قناته
|
||||
// الوحيدة لمعرفة أن سائقاً قَبِل، ومن هو، وكيف يعرف سيارته.
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
try {
|
||||
$stmtSms = $con->prepare("
|
||||
SELECT sender_norm FROM sms_ride_requests
|
||||
WHERE ride_id = ? AND status = 'dispatched' LIMIT 1
|
||||
");
|
||||
$stmtSms->execute([$rideId]);
|
||||
$smsSender = $stmtSms->fetchColumn();
|
||||
|
||||
if ($smsSender) {
|
||||
require_once __DIR__ . '/../../sms/helpers.php';
|
||||
|
||||
$plate = trim((string) ($driverInfo['car_plate'] ?? ''));
|
||||
$car = trim(($driverInfo['make'] ?? '') . ' ' . ($driverInfo['model'] ?? ''));
|
||||
$color = trim((string) ($driverInfo['color'] ?? ''));
|
||||
|
||||
$lines = ['سيرو: وجدنا لك سائقاً.'];
|
||||
$lines[] = 'السائق: ' . ($driverInfo['driverName'] ?? '—');
|
||||
if (!empty($driverInfo['phone'])) $lines[] = 'هاتفه: ' . $driverInfo['phone'];
|
||||
if ($car !== '' || $color !== '') $lines[] = 'السيارة: ' . trim("$car $color");
|
||||
if ($plate !== '') $lines[] = 'اللوحة: ' . $plate;
|
||||
$lines[] = 'سيتصل بك عند وصوله.';
|
||||
|
||||
smsEnqueue($con, (string) $smsSender, implode("\n", $lines),
|
||||
'driver_found', (string) $rideId);
|
||||
}
|
||||
} catch (Throwable $eSms) {
|
||||
// لا نُسقط قبول الرحلة من أجل رسالة: السائق قَبِل فعلاً،
|
||||
// والرسالة الفائتة أهون من رحلة تُلغى بخطأ.
|
||||
error_log('[accept_ride] تعذّر تجهيز رسالة SMS للراكب: ' . $eSms->getMessage());
|
||||
}
|
||||
|
||||
error_log("[accept_ride] SUCCESS. RideID=$rideId accepted by DriverID=$driverId");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
@@ -122,6 +122,11 @@ error_log("[add_ride] Request started. passenger_id=" . ($_POST['passenger_id']
|
||||
$isScheduledRide = filterRequest("source") === 'scheduled';
|
||||
$scheduledAtLabel = filterRequest("scheduled_at") ?: '';
|
||||
|
||||
// وسم الطلب بالرسائل النصية: يمرّره sms/ride_request.php لراكب لا
|
||||
// يملك إنترنت. تطبيق السائق يقرأ الوسم ليعرف أن التواصل بالاتصال لا
|
||||
// بالمحادثة — الراكب لن يرى أي إشعار في التطبيق.
|
||||
$isSmsRide = filterRequest("source") === 'sms';
|
||||
|
||||
$start_location = filterRequest("start_location");
|
||||
$end_location = filterRequest("end_location");
|
||||
$price = filterRequest("price");
|
||||
@@ -145,6 +150,11 @@ $price_token = filterRequest("price_token");
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
$isInternalScheduled = false;
|
||||
|
||||
// يُملأ أحدهما فقط حسب المصدر. التصريح بهما هنا يمنع تحذير «متغيّر
|
||||
// غير معرّف» في كتلة التسعير الداخلي التي تقرأ الاثنين.
|
||||
$booking = null;
|
||||
$smsReq = null;
|
||||
|
||||
if ($isScheduledRide) {
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
$expectedKey = getenv('S2S_SHARED_KEY') ?: '';
|
||||
@@ -196,6 +206,62 @@ if ($isScheduledRide) {
|
||||
$end_location = $booking['end_location'];
|
||||
$carType = $booking['car_type'];
|
||||
$distance = $booking['distance'];
|
||||
} elseif ($isSmsRide) {
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// نفس البوّابة المزدوجة بالضبط، على جدول sms_ride_requests.
|
||||
//
|
||||
// الفرق عن الحجز المسبق أن مصدر الطلب هنا رسالة نصية من رقم لا
|
||||
// يحمل JWT إطلاقاً — فالشرط الثاني هو كل الحماية: لا رحلة إلا
|
||||
// مقابل صف أنشأه الويبهوك من رسالة فعلية وقفله للتوّ.
|
||||
// ══════════════════════════════════════════════════════════
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
$expectedKey = getenv('S2S_SHARED_KEY') ?: '';
|
||||
$smsRequestId = (int) (filterRequest('sms_request_id', 'int') ?: 0);
|
||||
$claimedPassenger = filterRequest('passenger_id');
|
||||
|
||||
if (empty($expectedKey) || !hash_equals($expectedKey, (string) $providedKey)) {
|
||||
error_log('[add_ride] SECURITY: مسار SMS بمفتاح S2S غير صالح');
|
||||
printFailure('Unauthorized');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($smsRequestId <= 0 || empty($claimedPassenger)) {
|
||||
printFailure('Missing sms_request_id or passenger_id');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmtSms = $con->prepare("
|
||||
SELECT passenger_id, start_location, end_location, distance, duration
|
||||
FROM sms_ride_requests
|
||||
WHERE id = ? AND status = 'dispatching' LIMIT 1
|
||||
");
|
||||
$stmtSms->execute([$smsRequestId]);
|
||||
$smsReq = $stmtSms->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $eS) {
|
||||
error_log('[add_ride] تعذّرت قراءة طلب SMS: ' . $eS->getMessage());
|
||||
printFailure('Server error');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$smsReq) {
|
||||
error_log("[add_ride] SECURITY: طلب SMS #$smsRequestId غير موجود أو ليس قيد الإطلاق");
|
||||
printFailure('Invalid sms request');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ((string) $smsReq['passenger_id'] !== (string) $claimedPassenger
|
||||
|| !coordsMatch($smsReq['start_location'], $start_location)) {
|
||||
error_log("[add_ride] SECURITY: طلب SMS #$smsRequestId لا يطابق الراكب أو النقطة");
|
||||
printFailure('SMS request mismatch');
|
||||
exit;
|
||||
}
|
||||
|
||||
// نثق بالصف المخزَّن لا بما وصل في الطلب.
|
||||
$isInternalScheduled = true; // نفس الإعفاء من رمز السعر و JWT
|
||||
$passenger_id = $smsReq['passenger_id'];
|
||||
$end_location = $smsReq['end_location'];
|
||||
$distance = $smsReq['distance'];
|
||||
} else {
|
||||
// Force passenger_id from JWT — never trust user-supplied passenger_id
|
||||
$passenger_id = $user_id;
|
||||
@@ -260,14 +326,19 @@ if ($isInternalScheduled) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// المدّة من الصف المخزَّن أيّاً كان مصدره — الحجز المسبق أو طلب
|
||||
// الرسائل. قراءتها من `$booking` وحده كانت تمرّر صفراً لكل رحلة
|
||||
// قادمة من SMS، فيسقط شقّ الزمن من السعر.
|
||||
$internalDuration = (int) ($booking['duration'] ?? $smsReq['duration'] ?? 0);
|
||||
|
||||
$price = computeInternalRidePrice(
|
||||
$kazanRow, (string) $carType,
|
||||
(float) $distance, (int) ($booking['duration'] ?? 0)
|
||||
$kazanRow, (string) $carType, (float) $distance, $internalDuration
|
||||
);
|
||||
$price_for_driver = $price;
|
||||
$price_for_passenger = $price;
|
||||
|
||||
error_log("[add_ride] حجز #$scheduledId سُعِّر داخلياً: $price");
|
||||
$internalRef = $isSmsRide ? "طلب SMS #$smsRequestId" : "حجز #$scheduledId";
|
||||
error_log("[add_ride] $internalRef سُعِّر داخلياً: $price");
|
||||
}
|
||||
|
||||
// SECURE PRICE TOKEN VERIFICATION
|
||||
@@ -515,6 +586,10 @@ try {
|
||||
// ليست طلباً لحظياً بل موعد مضبوط، وأن الراكب ينتظره في وقت محدد.
|
||||
$isScheduledRide ? '1' : '',
|
||||
$isScheduledRide ? (string) $scheduledAtLabel : '',
|
||||
// 🆕 Index 41: طلب وصل عبر رسالة نصية — الراكب بلا إنترنت.
|
||||
// السائق يجب أن يعرف أن المحادثة داخل التطبيق لن تصل إليه،
|
||||
// وأن الاتصال الهاتفي هو وسيلة التواصل الوحيدة معه.
|
||||
$isSmsRide ? '1' : '',
|
||||
];
|
||||
|
||||
// Direct dispatch للسائقين القريبين
|
||||
|
||||
Reference in New Issue
Block a user