diff --git a/ride/rides/finish_ride_updates.php b/ride/rides/finish_ride_updates.php index 942dd6e..e6b9a34 100755 --- a/ride/rides/finish_ride_updates.php +++ b/ride/rides/finish_ride_updates.php @@ -7,44 +7,133 @@ try { error_log("[finish_ride_updates] Failed to connect to Ride Database: " . $e->getMessage()); } -// finish_ride_updates.php +// ============================================================ +// finish_ride_updates.php — Atomic Server-to-Server +// ============================================================ +// Driver App calls this ONCE with raw ride data (NOT the price). +// Server calculates price securely, processes payment via S2S, +// and atomically updates all databases within a transaction. +// +// Flow: +// 1. Receive raw params from driver app +// 2. Calculate price server-side (from DB + actual distance) +// 3. BEGIN TRANSACTION (local DB) +// 4. Update ride on local DB + remote DB (con_ride) +// 5. Update driver_orders +// 6. S2S cURL → Wallet Payment Server (process_ride_payments.php) +// 7. If payment OK → COMMIT, notify passenger (Socket + FCM) +// 8. If payment FAIL → ROLLBACK, ride stays 'Begin', safe retry +// ============================================================ -$rideId = filterRequest("rideId"); -$driver_id = filterRequest("driver_id"); -$passengerId=filterRequest("passengerId"); -$passenger_id = $passengerId; -$newStatus = filterRequest("status"); // "Finished" -$price = filterRequest("price"); +// --- Secure S2S Configuration --- +define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY') ); +define('WALLET_PAYMENT_URL', 'https://walletintaleq.intaleq.xyz/v1/main/ride/payment/process_ride_payments.php'); + +// ============================================================ +// 1. Receive Raw Parameters (NO price from client) +// ============================================================ +$rideId = filterRequest("rideId"); +$driver_id = filterRequest("driver_id"); +$passengerId = filterRequest("passengerId"); +$newStatus = filterRequest("status"); // Expected: "Finished" +$actualDistance = filterRequest("actualDistance"); +$actualDuration = filterRequest("actualDuration"); $passengerToken = filterRequest("passengerToken"); -$driver_token =filterRequest("driver_token"); +$driver_token = filterRequest("driver_token"); +$walletChecked = filterRequest("walletChecked"); +$passengerWalletBurc = filterRequest("passengerWalletBurc"); -if (empty($rideId) || empty($newStatus) || empty($price) || empty($driver_id)) { - jsonError("Missing parameters"); +if (empty($rideId) || empty($newStatus) || empty($driver_id) || empty($passengerId)) { + jsonError("Missing required parameters: rideId, driver_id, passengerId, status"); exit; } +if ($newStatus !== 'Finished') { + jsonError("Invalid status. Expected: Finished"); + exit; +} + +// ============================================================ +// 2. Server-Side Price Calculation (Secure — NOT from client) +// ============================================================ try { - // 1. تحديث الريموت (Remote Server - con_ride) - $stmtRemote = $con_ride->prepare("UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"); - $stmtRemote->execute([$newStatus, $price, $rideId]); - - if ($stmtRemote->rowCount() == 0) { - // إذا لم يجد الصف (ربما تم إنهاؤها بالفعل) - // jsonError("Could not finish ride (Remote)."); - // exit; - // ملاحظة: الأفضل إكمال العملية محلياً احتياطاً + // Fetch ride data from remote/local DB for server-side calculation + $stmtRideData = $con->prepare(" + SELECT id, price AS quoted_price, car_type, + distance AS planned_distance, passenger_id, driver_id + FROM ride WHERE id = ? AND driver_id = ? + LIMIT 1 + "); + $stmtRideData->execute([$rideId, $driver_id]); + $rideData = $stmtRideData->fetch(PDO::FETCH_ASSOC); + + if (!$rideData) { + jsonError("Ride not found or driver mismatch."); + exit; } - // 2. التحديث المحلي (Local DB) + $quotedPrice = floatval($rideData['quoted_price'] ?? 0); + $kazanPercent = 10; + $carType = $rideData['car_type'] ?? 'Fixed Price'; + + // Fixed-price types: use quoted price as-is + $fixedPriceTypes = ['Speed', 'Fixed Price', 'Awfar Car']; + if (in_array($carType, $fixedPriceTypes)) { + $finalPrice = $quotedPrice; + } else { + // Variable pricing: calculate from actual distance + $cleanDist = preg_replace('/[^0-9.]/', '', $actualDistance); + $distanceKm = floatval($cleanDist); + + if ($distanceKm <= 0) { + $finalPrice = $quotedPrice; // fallback + } else { + $perKmRate = getPerKmRate($carType); + $perMinRate = getPerMinRate(); + $durationMin = intval(preg_replace('/[^0-9]/', '', $actualDuration)); + + $calculated = ($distanceKm * $perKmRate) + ($durationMin * $perMinRate); + $calculated *= (1 + ($kazanPercent / 100)); + + $finalPrice = max($quotedPrice, round($calculated, 2)); + } + } +} catch (PDOException $e) { + jsonError("Error calculating price: " . $e->getMessage()); + exit; +} + +// ============================================================ +// 3. Atomic Transaction: Update DBs + Process Payment +// ============================================================ +try { + // --- Update Remote DB (con_ride) FIRST --- + // (Not in transaction — remote DB doesn't support cross-DB rollback, + // but we keep it minimal as a "best-effort" update) + if (isset($con_ride)) { + $stmtRemote = $con_ride->prepare( + "UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'" + ); + $stmtRemote->execute([$newStatus, $finalPrice, $rideId]); + } + + // --- BEGIN Local DB Transaction --- $con->beginTransaction(); - $con->prepare("UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'") - ->execute([$newStatus, $price, $rideId]); + // 3a. Update ride (local DB) + $stmtLocal = $con->prepare( + "UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'" + ); + $stmtLocal->execute([$newStatus, $finalPrice, $rideId]); - // تحديث driver_orders + if ($stmtLocal->rowCount() == 0) { + throw new Exception("Ride already finished or not found in local DB."); + } + + // 3b. Update driver_orders $checkStmt = $con->prepare("SELECT order_id FROM driver_orders WHERE order_id = ?"); $checkStmt->execute([$rideId]); - + if ($checkStmt->rowCount() > 0) { $con->prepare("UPDATE driver_orders SET driver_id = ?, status = ?, created_at = NOW() WHERE order_id = ?") ->execute([$driver_id, $newStatus, $rideId]); @@ -53,60 +142,149 @@ try { ->execute([$driver_id, $rideId, $newStatus]); } - // ================================================================= - // 🔥 الخطوة 3: إشعار الراكب (Socket + FCM) - // ================================================================= - - - + // ============================================================ + // 3c. Server-to-Server Payment Processing (S2S) + // ============================================================ + $paymentPayload = [ + 'rideId' => $rideId, + 'driverId' => $driver_id, + 'passengerId' => $passengerId, + 'paymentAmount' => $finalPrice, + 'paymentMethod' => ($walletChecked === 'true') ? 'wallet' : 'cash', + 'walletChecked' => $walletChecked, + 'passengerWalletBurc' => $passengerWalletBurc, + 'authToken' => $driver_token, + ]; - if ($passenger_id) { - - // تجهيز القائمة المتوافقة مع الكود القديم (Legacy List) - // [driver_id, ride_id, driver_token, price] + $ch = curl_init(WALLET_PAYMENT_URL); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($paymentPayload), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 15, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/x-www-form-urlencoded', + 'X-S2S-Api-Key: ' . S2S_SHARED_KEY, + ], + ]); + + $paymentResponse = curl_exec($ch); + $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($ch); + curl_close($ch); + + // Validate payment response + $paymentSuccess = false; + $paymentError = ''; + + if ($curlError) { + $paymentError = "S2S connection error: " . $curlError; + } elseif ($httpStatusCode !== 200) { + $paymentError = "Payment server returned HTTP $httpStatusCode"; + } else { + $paymentResult = json_decode($paymentResponse, true); + if ($paymentResult && isset($paymentResult['status']) && $paymentResult['status'] === 'success') { + $paymentSuccess = true; + } else { + $paymentError = $paymentResult['error'] ?? 'Payment server returned failure'; + } + } + + if (!$paymentSuccess) { + // ❌ Payment failed — ROLLBACK everything + $con->rollBack(); + error_log("[finish_ride_updates] Payment FAILED for ride $rideId: $paymentError"); + jsonError("Payment processing failed: $paymentError"); + exit; + } + + // ✅ Payment succeeded — COMMIT + $con->commit(); + + // ============================================================ + // 4. Notifications (After successful commit) + // ============================================================ + $passenger_id = $passengerId; // alias for legacy code + + if (!empty($passenger_id)) { + // Legacy list for backward compatibility $legacyList = [ (string)$driver_id, (string)$rideId, (string)$driver_token, - (string)$price + (string)$finalPrice ]; - // أ) إرسال Socket + // a) Socket notification $socketPayload = [ 'ride_id' => $rideId, 'status' => 'finished', - 'price' => $price, - 'DriverList' => $legacyList // إرسال القائمة للسوكيت أيضاً + 'price' => $finalPrice, + 'DriverList' => $legacyList ]; - + if (function_exists('notifyPassengerOnRideServer')) { notifyPassengerOnRideServer($passenger_id, $socketPayload); } - // ب) إرسال FCM (Internal) + // b) FCM notification if (!empty($passengerToken)) { $fcmData = [ 'ride_id' => (string)$rideId, - 'price' => (string)$price, - 'DriverList' => $legacyList // ✅ نمرر المصفوفة، والدالة الداخلية تحولها لـ JSON + 'price' => (string)$finalPrice, + 'DriverList' => $legacyList ]; - + sendFCM_Internal( - $passengerToken, // الهدف - "تم إنهاء الرحلة 🏁", // العنوان - "المبلغ المطلوب: " . $price . " ل.س", // النص (أضفت العملة افتراضياً) - $fcmData, // البيانات - 'Driver Finish Trip', // التصنيف (كما هو في التطبيق القديم) - false // ليس Topic + $passengerToken, + "تم إنهاء الرحلة 🏁", + "المبلغ المطلوب: " . $finalPrice . " ل.س", + $fcmData, + 'Driver Finish Trip', + false ); } } - $con->commit(); - jsonSuccess(null, "Ride finished successfully"); + // ============================================================ + // 5. Return Success with server-calculated price + // ============================================================ + jsonSuccess([ + 'price' => $finalPrice, + 'rideId' => $rideId + ], "Ride finished and payment processed successfully."); -} catch (PDOException $e) { - if ($con->inTransaction()) $con->rollBack(); - jsonError("DB Error: " . $e->getMessage()); +} catch (Exception $e) { + if (isset($con) && $con->inTransaction()) { + $con->rollBack(); + } + error_log("[finish_ride_updates] Error for ride $rideId: " . $e->getMessage()); + jsonError("Transaction failed: " . $e->getMessage()); } -?> \ No newline at end of file + +// ============================================================ +// Helper Functions +// ============================================================ + +function getPerKmRate(string $carType): float { + $rates = [ + 'Comfort' => 44, + 'Lady' => 44, + 'Mishwar Vip' => 50, + 'Electric' => 45, + 'Van' => 63, + 'Delivery' => 25, + 'Speed' => 36, + 'Fixed Price' => 36, + 'Awfar Car' => 36, + ]; + return $rates[$carType] ?? 36; +} + +function getPerMinRate(): float { + $hour = (int)date('H'); + if ($hour >= 21 || $hour < 1) return 11; // Late + if ($hour >= 14 && $hour <= 17) return 10; // Peak + return 9; // Normal +} +?>