getMessage()); } $rideId = filterRequest("ride_id"); $driverId = filterRequest("driver_id"); $passengerToken = filterRequest("passengerToken"); if (!$rideId || !$driverId) { jsonError("Missing required parameters."); exit; } // ‏نصف قطر قبول ختم الوصول. أوسع من فحص الـ100م في التطبيق بهامش يستوعب // ‏ضعف الـGPS بين المباني، وأضيق من أن يسمح بالختم من حي آخر. const ARRIVAL_STAMP_RADIUS_KM = 0.30; try { // 1. تحديث الحالة في السيرفر البعيد (Remote DB - con_ride) $stmtRemote = $con_ride->prepare("UPDATE ride SET status = 'arrived', updated_at = NOW() WHERE id = ? AND driver_id = ? AND status = 'Apply'"); $stmtRemote->execute([$rideId, $driverId]); // 2. تحديث الحالة في السيرفر المحلي (Local DB - con) if (isset($con)) { $stmtLocal = $con->prepare("UPDATE ride SET status = 'arrived', updated_at = NOW() WHERE id = ? AND driver_id = ? AND status = 'Apply'"); $stmtLocal->execute([$rideId, $driverId]); } // ───────────────────────────────────────────────────────────── // 2.1 ختم لحظة الوصول — أساس تعويض عدم حضور الراكب // // ‏فحص المسافة في التطبيق (100م) على العميل، فيمكن تجاوزه بموقع مزيَّف // ‏ثم انتظار خمس دقائق وقبض تعويض بلا وجود حقيقي. لذلك يتحقق الخادم // ‏هنا بنفسه من موقع السائق في Redis قبل أن يختم الوقت. // // ‏إن تعذّر التحقق (Redis غير متاح أو الموقع مفقود) لا نمنع الوصول — // ‏الحالة تُحدَّث والراكب يُشعَر كالمعتاد — لكن لا نختم وقتاً يترتب عليه // ‏مال لا نستطيع إثباته. النتيجة: لا تعويض لهذه الرحلة، وسجلّ واضح. // ───────────────────────────────────────────────────────────── // ‏الختم في Redis لا MySQL — قيمة عابرة تُقرأ مرة واحدة عند الإلغاء، // ‏وتُثبَّت في MySQL كتابةً واحدة هناك. hSetNx يجعله ذرّياً: نداء ثانٍ // ‏لا يعيد ضبط عدّاد الانتظار. try { $stateKey = "ride:$rideId:state"; if (isset($redisLocation) && $redisLocation && !$redisLocation->hExists($stateKey, 'driver_arrived_at')) { $stmtPickup = $con->prepare("SELECT start_location FROM ride WHERE id = ? AND driver_id = ?"); $stmtPickup->execute([$rideId, $driverId]); [$pLat, $pLng] = array_pad( array_map('trim', explode(',', (string) $stmtPickup->fetchColumn(), 2)), 2, null ); $canStamp = false; if (is_numeric($pLat) && is_numeric($pLng)) { $loc = $redisLocation->hGetAll("driver:public:$driverId"); if (!empty($loc['lat']) && !empty($loc['lng'])) { $gap = getDistanceBetweenPoints( (float) $loc['lat'], (float) $loc['lng'], (float) $pLat, (float) $pLng ); $canStamp = ($gap <= ARRIVAL_STAMP_RADIUS_KM); if (!$canStamp) { error_log("[arrive_ride] ختم الوصول مرفوض — السائق $driverId" . " يبعد " . round($gap, 3) . "كم عن نقطة الالتقاط (ride=$rideId)"); } } } if ($canStamp) { $redisLocation->hSetNx($stateKey, 'driver_arrived_at', time()); // ‏تأمين TTL: لو وصل هذا الطلب قبل أول update_ride_state من // ‏السوكِت، الهاش يكون بلا انتهاء صلاحية. if ($redisLocation->ttl($stateKey) < 0) { $redisLocation->expire($stateKey, 86400); } } else { error_log("[arrive_ride] تعذّر إثبات الوصول — لا تعويض لهذه" . " الرحلة (ride=$rideId driver=$driverId)"); } } } catch (Throwable $eStamp) { // ‏فشل الختم لا يمنع إشعار الراكب بالوصول. error_log("[arrive_ride] تعذّر ختم وقت الوصول: " . $eStamp->getMessage()); } // 3. جلب بيانات الراكب للإرسال // نستخدم con_ride لضمان الدقة $stmtPas = $con_ride->prepare("SELECT passenger_id FROM ride WHERE id = ?"); $stmtPas->execute([$rideId]); $passenger_id = $stmtPas->fetchColumn(); if ($passenger_id) { // أ) إرسال Socket (الأسرع) $payload = [ 'status' => 'arrived', 'ride_id' => $rideId, 'msg' => 'السائق وصل إلى موقعك 🚖' ]; if (function_exists('notifyPassengerOnRideServer')) { notifyPassengerOnRideServer($passenger_id, $payload); } // ب) إرسال FCM (باستخدام الدالة الجديدة) if (!empty($passengerToken)) { $fcmData = [ 'category' => 'Arrive Ride', // نفس الاسم القديم لضمان عمل التطبيق 'ride_id' => (string)$rideId, 'status' => 'arrived' ]; // 🔥 استخدام sendFCM_Internal كرسالة صامتة sendFCM_Internal( $passengerToken, // الهدف "", // تفريغ العنوان "", // تفريغ النص $fcmData, // البيانات "Arrive Ride", // التصنيف false // ليس Topic ); } } // 🆕 Cache ride state in Redis sendToLocationServer('update_ride_state', [ 'ride_id' => $rideId, 'status' => 'arrived', 'driver_id' => $driverId, 'passenger_id' => $passenger_id ?? '', ]); jsonSuccess(null, "Arrival notified successfully"); } catch (Exception $e) { error_log("[arrive_ride] " . $e->getMessage()); jsonError("Error notifying arrival"); } ?>