prepare("SELECT * FROM kazan LIMIT 1"); $stmtKazan->execute(); $kazan = $stmtKazan->fetch(PDO::FETCH_ASSOC) ?: []; $cap = (float) ($kazan['passengerCancelCap'] ?? 0); if ($cap <= 0) { // ‏الرسم معطّل لهذه الدولة — الافتراضي حتى تضبطه الإدارة. $exemptReason = 'policy_disabled'; return 0.0; } $freeMinutes = (int) ($kazan['passengerCancelFreeMinutes'] ?? 2); $lateExemptMinutes = (int) ($kazan['driverLateExemptMinutes'] ?? 10); // ── الزمن منذ القبول ──────────────────────────────────── // ‏acceptRide.php يكتب rideTimeStart = NOW() لحظة فوز السائق بالقفل. $acceptedAt = !empty($ride['rideTimeStart']) ? strtotime($ride['rideTimeStart']) : 0; if ($acceptedAt <= 0) { $exemptReason = 'accept_time_unknown'; return 0.0; } $minutesSinceAccept = (int) floor((time() - $acceptedAt) / 60); // ── إعفاء ٢: نافذة الإلغاء المجاني ────────────────────── // ‏الراكب يغيّر رأيه أو يخطئ الوجهة — دقيقتان لتصحيح ذلك بلا عقاب. if ($minutesSinceAccept < $freeMinutes) { $exemptReason = 'within_free_window'; return 0.0; } // ── حالة الوصول من Redis ──────────────────────────────── $approachKm = 0.0; $arrivedTs = 0; if ($redisLocation) { try { $state = $redisLocation->hMGet("ride:$rideId:state", ['pickup_approach_km', 'driver_arrived_at']) ?: []; $approachKm = (float) ($state['pickup_approach_km'] ?? 0); $arrivedTs = (int) ($state['driver_arrived_at'] ?? 0); } catch (Throwable $e) { error_log("[cancel_fee] تعذّرت قراءة حالة الرحلة: " . $e->getMessage()); } } // ── إعفاء ٣: السائق تأخّر ولم يصل ─────────────────────── // ‏أهم الإعفاءات الثلاثة: بدونه نعاقب الراكب على بطء السائق، وهذا // ‏يطرد الركاب أسرع مما يوفّره الرسم. if ($arrivedTs <= 0 && $minutesSinceAccept >= $lateExemptMinutes) { $exemptReason = 'driver_late'; return 0.0; } // ── الحساب ────────────────────────────────────────────── $billableMinutes = min($minutesSinceAccept, PASSENGER_CANCEL_MAX_BILLABLE_MINUTES); $carType = (string) ($ride['carType'] ?? $ride['car_type'] ?? 'Speed'); $fee = ($approachKm * getPerKmRate($carType, $kazan)) + ($billableMinutes * getPerMinRate($kazan)); $fee = round(min($fee, $cap, PASSENGER_CANCEL_ABSOLUTE_CAP), 2); error_log("[cancel_fee] ride=$rideId رسم إلغاء: {$approachKm}كم" . " + {$billableMinutes}د = $fee (سقف $cap)"); return max(0.0, $fee); } /** * ‏ينفّذ التحويل: خصم من الراكب وإضافة للسائق. * * ‏يُستدعى بعد الـcommit — نداءات S2S شبكية لا تُترك داخل معاملة مفتوحة. * ‏لا يرمي أبداً: فشل التحويل يُسجَّل للتسوية ولا يُبطل إلغاءً تمّ فعلاً. */ function applyCancelFeeTransfer(PDO $con, $passengerId, $driverId, $rideId, float $fee): void { if ($fee <= 0 || empty($passengerId) || empty($driverId)) { return; } try { $stmtKazan = $con->prepare("SELECT country FROM kazan LIMIT 1"); $stmtKazan->execute(); $country = strtolower((string) ($stmtKazan->fetchColumn() ?: 'jordan')); } catch (PDOException $e) { $country = 'jordan'; } $walletServer = "https://walletintaleq.intaleq.xyz"; if ($country === 'jordan') { $walletServer = getenv('WALLET_SERVER_JORDAN') ?: $walletServer; } elseif ($country === 'egypt') { $walletServer = getenv('WALLET_SERVER_EGYPT') ?: $walletServer; } else { $walletServer = getenv('WALLET_SERVER_SYRIA') ?: $walletServer; } $s2s = function (string $url, array $fields): int { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($fields), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_HTTPHEADER => [ 'Content-Type: application/x-www-form-urlencoded', 'X-S2S-Api-Key: ' . getenv('S2S_SHARED_KEY'), ], ]); curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return (int) $code; }; // ‏خصم من الراكب (دين سالب) $debtCode = $s2s( "$walletServer/v2/main/ride/passengerWallet/add_s2s_debt.php", ['passengerID' => $passengerId, 'amount' => -$fee] ); // ‏إضافة للسائق — paymentID مشتق من الرحلة فيمنع ازدواج الصرف $rewardCode = $s2s( "$walletServer/v2/main/ride/driverWallet/add_s2s_reward.php", [ 'driverID' => $driverId, 'paymentID' => "pax_cancel_$rideId", 'amount' => $fee, 'paymentMethod' => 'passenger_cancel_compensation', ] ); if ($debtCode !== 200 || $rewardCode !== 200) { // ‏أي طرف فشل يعني اختلالاً يجب أن يظهر في التسوية، لا أن يمرّ صامتاً. error_log("[cancel_fee] MONEY: رسم $fee للرحلة $rideId —" . " خصم الراكب=$debtCode، رصيد السائق=$rewardCode" . " (راكب=$passengerId سائق=$driverId)"); } }