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 $currency (سقف $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; } $kazanRow = getKazanForRide($con, $rideId, 'Syria', $driverId); $country = strtolower((string) ($kazanRow['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)"); } }