Files
Siro/backend/ride/rides/cancel_ride_by_passenger.php
T

213 lines
9.0 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../../connect.php';
require_once __DIR__ . '/../pricing/pricing_helper.php';
require_once __DIR__ . '/cancel_fee_helper.php';
try {
$con_ride = Database::get('ride');
} catch (Exception $e) {
error_log("[cancel_ride_by_passenger] Failed to connect to Ride Database: " . $e->getMessage());
}
// cancel_ride_by_passenger.php
$rideId = filterRequest("ride_id");
$reason = filterRequest("reason");
if (!$rideId) {
jsonError("Missing Ride ID");
exit;
}
try {
// ‏SELECT * لا قائمة أعمدة: نحتاج rideTimeStart و carType لحساب الرسم،
// ‏وأسماء بعض الأعمدة متضاربة بين الschema والقاعدة الحية (carType /
// ‏car_type). النجمة تجعل الاستعلام يعمل في كل الحالات.
$stmt = $con->prepare("SELECT * FROM ride WHERE id = ?");
$stmt->execute([$rideId]);
$ride = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$ride) {
jsonError("Ride not found");
exit;
}
$driverId = $ride['driver_id'];
$currentStatus = $ride['status'];
if ($currentStatus == 'Begin') {
jsonError("Cannot cancel started ride");
exit;
}
// =================================================================
// 1. تحديث قواعد البيانات (Transaction)
// =================================================================
$con->beginTransaction();
// تحديث waitingRides
$updateWaiting = $con->prepare("UPDATE waitingRides SET status = ? WHERE id = ?");
$updateWaiting->execute(['cancelled_by_passenger', $rideId]);
// تحديث ride (محلي)
$updateRide = $con->prepare("UPDATE ride SET status = ?, updated_at = NOW() WHERE id = ?");
$updateRide->execute(['cancelled_by_passenger', $rideId]);
// تحديث driver_orders
if ($driverId > 0) {
$updateOrder = $con->prepare("UPDATE driver_orders SET status = 'cancelled_by_passenger', notes = ? WHERE order_id = ? AND driver_id = ?");
$updateOrder->execute([$reason, $rideId, $driverId]);
}
// إدراج سبب الإلغاء في جدول canecl المخصص
if ($driverId > 0 && !empty($reason)) {
$passengerId = $ride['passenger_id'] ?? '0';
$insertCanecl = $con->prepare(
"INSERT INTO canecl (driverID, passengerID, rideID, note) VALUES (?, ?, ?, ?)"
);
$insertCanecl->execute([$driverId, $passengerId, $rideId, $reason]);
}
// =================================================================
// 1.5 رسم الإلغاء — تعويض للسائق لا إيراد للشركة
//
// ‏متماثل مع تعويض عدم الحضور في cancel_ride_by_driver.php: من ضيّع
// ‏وقت الآخر يعوّضه. الفرق أن الزمن يُحتسب من لحظة القبول لا من لحظة
// ‏الوصول — السائق كان في طريقه لا واقفاً ينتظر.
//
// ‏المبلغ لا يأتي من العميل بأي حال، ويُحسب من أسعار kazan نفسها.
// =================================================================
$cancelFee = 0.0;
$feeExemptReason = '';
try {
$cancelFee = computePassengerCancelFee(
$con, $redisLocation ?? null, $rideId, $ride, $driverId, $feeExemptReason
);
} catch (Throwable $eFee) {
// ‏فشل حساب الرسم لا يمنع الإلغاء أبداً — الإلغاء حق للراكب.
error_log("[cancel_ride_by_passenger] تعذّر حساب الرسم: " . $eFee->getMessage());
}
if ($cancelFee > 0) {
// ‏عدّاد عدم الحضور — للتقارير وقرارات الحظر اليدوية.
try {
$con->prepare("UPDATE passengers SET noShowCount = noShowCount + 1 WHERE id = ?")
->execute([$ride['passenger_id'] ?? '']);
} catch (PDOException $eCnt) {
error_log("[cancel_ride_by_passenger] عدّاد عدم الحضور غير مُرحَّل بعد: "
. $eCnt->getMessage());
}
}
$con->commit();
// ‏التحويل المالي بعد الـcommit لا داخله: نداءات S2S شبكية قد تستغرق
// ‏ثوانٍ، وإبقاء المعاملة مفتوحة خلالها يقفل صفوف ride تحت ضغط.
if ($cancelFee > 0) {
applyCancelFeeTransfer(
$con, $ride['passenger_id'] ?? '', $driverId, $rideId, $cancelFee
);
}
// تحديث السيرفر البعيد (Remote DB)
if (isset($con_ride)) {
try {
$updateRide2 = $con_ride->prepare("UPDATE ride SET status = ?, updated_at = NOW() WHERE id = ?");
$updateRide2->execute(['cancelled_by_passenger', $rideId]);
} catch (PDOException $e) {
error_log("Secondary DB update failed: " . $e->getMessage());
}
}
// =================================================================
// 2. إشعار السائق/السائقين (Socket + FCM)
// =================================================================
// 🔥 يُرسل دائماً بغض النظر عن driver_id — إذا كانت الرحلة لم تُقبل بعد
// (driver_id = 0)، لوكيشن سيرفر يستخدم ride:offered_drivers:{rideId}
// من Redis لإشعار كل السائقين الذين وصلهم عرض هذه الرحلة أصلاً.
$socketUrl = getenv('LOCATION_SERVER_URL') ?: 'http://socket_driver:2021';
$internalKeyPath = getenv('INTERNAL_SOCKET_KEY_PATH') ?: '';
$internalKey = ($internalKeyPath && file_exists($internalKeyPath)) ? trim(file_get_contents($internalKeyPath)) : (getenv('INTERNAL_SOCKET_KEY') ?: '');
$ch = curl_init($socketUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'action' => 'cancel_ride',
'driver_id' => $driverId,
'ride_id' => $rideId,
'reason' => $reason
]));
if (!empty($internalKey)) curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $internalKey"]);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 500);
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
@curl_exec($ch);
curl_close($ch);
if ($driverId > 0) {
// FCM للسائق الذي قَبِل الرحلة فعلاً (باستخدام الدالة الجديدة مع فك التشفير)
$driverToken = filterRequest("driver_token");
if (empty($driverToken)) {
$stmtToken = $con->prepare("SELECT token FROM driverToken WHERE captain_id = ?");
$stmtToken->execute([$driverId]);
$rawToken = $stmtToken->fetchColumn();
if ($rawToken) {
$driverToken = $rawToken;
// 🔥 محاولة فك التشفير (لأن التوكنات غالباً مشفرة)
if (!empty($encryptionHelper)) {
try {
$decrypted = $encryptionHelper->decryptData($rawToken);
if ($decrypted !== false && !empty($decrypted)) {
$driverToken = trim($decrypted);
}
} catch (Exception $e) {
// في حال الفشل نستخدم الخام
}
}
}
}
if (!empty($driverToken)) {
// تجهيز البيانات
$fcmData = [
'category' => 'Cancel Trip',
'ride_id' => (string)$rideId,
'reason' => $reason
];
// إرسال الإشعار
sendFCM_Internal(
$driverToken, // الهدف
"إلغاء الرحلة 🚫", // العنوان
"قام الراكب بإلغاء الرحلة: $reason", // النص
$fcmData, // البيانات
'Cancel Trip', // التصنيف
false // ليس Topic
);
}
}
// 🆕 Cache ride state in Redis
sendToLocationServer('update_ride_state', [
'ride_id' => $rideId,
'status' => 'cancelled_by_passenger',
'driver_id' => $driverId ?? '',
'passenger_id' => $ride['passenger_id'] ?? '',
]);
// ‏الرسم يُرجَع للتطبيق ليعرضه صراحةً. خصم صامت من المحفظة يكتشفه
// ‏الراكب لاحقاً هو أسرع طريق لشكوى ولمراجعة سيئة.
jsonSuccess([
'cancel_fee' => $cancelFee ?? 0,
'fee_exempt' => ($cancelFee ?? 0) > 0 ? '' : ($feeExemptReason ?? ''),
], "Ride cancelled successfully");
} catch (PDOException $e) {
if ($con->inTransaction()) $con->rollBack();
error_log("Cancel ride error: " . $e->getMessage());
jsonError("Database error occurred");
}
?>