add new features like realtime 2026-05-31-00

This commit is contained in:
Hamza-Ayed
2026-05-31 00:49:20 +03:00
parent 69b041bd0c
commit 224cc257ca

View File

@@ -7,41 +7,130 @@ 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
// ============================================================
// --- 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");
$passenger_id = $passengerId;
$newStatus = filterRequest("status"); // "Finished"
$price = filterRequest("price");
$newStatus = filterRequest("status"); // Expected: "Finished"
$actualDistance = filterRequest("actualDistance");
$actualDuration = filterRequest("actualDuration");
$passengerToken = filterRequest("passengerToken");
$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;
}
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;
// ملاحظة: الأفضل إكمال العملية محلياً احتياطاً
if ($newStatus !== 'Finished') {
jsonError("Invalid status. Expected: Finished");
exit;
}
// 2. التحديث المحلي (Local DB)
// ============================================================
// 2. Server-Side Price Calculation (Secure — NOT from client)
// ============================================================
try {
// 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;
}
$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]);
@@ -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,
];
$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 ($passenger_id) {
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';
}
}
// تجهيز القائمة المتوافقة مع الكود القديم (Legacy List)
// [driver_id, ride_id, driver_token, price]
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());
}
// ============================================================
// 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
}
?>