Update: 2026-08-07 00:36:27
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
-- 2026_08_07_kazan_cancel_penalty_policy.sql
|
||||
--
|
||||
-- سبب الترحيل: كان مبلغ غرامة الإلغاء في ride/rides/cancel_ride_by_driver.php
|
||||
-- يُقرأ من طلب العميل مباشرة (filterRequest("penalty_fee")) ويُخصم من محفظة
|
||||
-- الراكب بلا سقف ولا تحقق — أي أن طلباً مصاغاً يدوياً كان يقدر يخصم أي مبلغ
|
||||
-- من أي راكب. صار المبلغ يُقرأ من هنا فقط، ولا يقبل الخادم أي قيمة من العميل.
|
||||
--
|
||||
-- القيمة الافتراضية '0' تحفظ السلوك الحالي حرفياً: لا عميل يرسل penalty_fee
|
||||
-- اليوم، فالغرامة لا تُطبَّق فعلياً. تفعيلها قرار إداري صريح بتحديث الصف.
|
||||
--
|
||||
-- القاعدة المستهدفة: primary فقط — الكود يقرأ kazan عبر $con، وجدول kazan في
|
||||
-- قاعدتي ride/tracking بنية أقدم ومختلفة الأعمدة (لا يُستخدم في هذا المسار).
|
||||
|
||||
ALTER TABLE `kazan`
|
||||
ADD COLUMN `cancelPenaltyFee` VARCHAR(10)
|
||||
CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
|
||||
NOT NULL DEFAULT '0'
|
||||
COMMENT 'غرامة إلغاء السائق بعد القبول، تُخصم من محفظة الراكب. 0 = معطّلة'
|
||||
AFTER `startPrice`;
|
||||
|
||||
-- للتراجع:
|
||||
-- ALTER TABLE `kazan` DROP COLUMN `cancelPenaltyFee`;
|
||||
@@ -13,11 +13,22 @@ require_once __DIR__ . '/streak_helper.php';
|
||||
$rideId = filterRequest("ride_id");
|
||||
$driverId = filterRequest("driver_id");
|
||||
$reason = filterRequest("reason");
|
||||
$passengerToken = filterRequest("passenger_token");
|
||||
$penaltyFee = (float) filterRequest("penalty_fee");
|
||||
$passengerToken = filterRequest("passenger_token");
|
||||
|
||||
// مبلغ الغرامة لا يُقرأ من الطلب إطلاقاً. كان سابقاً filterRequest("penalty_fee")
|
||||
// فيُخصم من محفظة الراكب بلا سقف ولا تحقق. صار يُحسم من سياسة الخادم أدناه.
|
||||
// إن وصل المفتاح من عميل فهو إما نسخة قديمة أو محاولة عبث — نسجّله ونتجاهله.
|
||||
if (filterRequest("penalty_fee") !== null && filterRequest("penalty_fee") !== '') {
|
||||
error_log("[cancel_ride_by_driver] SECURITY: تجاهل penalty_fee من العميل"
|
||||
. " (ride=$rideId driver=$driverId) — السياسة تُقرأ من kazan فقط");
|
||||
}
|
||||
|
||||
// سقف صلب فوق أي قيمة في قاعدة البيانات — يمنع خطأ إدارياً أو صفاً تالفاً
|
||||
// من تحويل الغرامة إلى خصم كارثي.
|
||||
const CANCEL_PENALTY_ABSOLUTE_CAP = 50000.0;
|
||||
|
||||
// تثبيت الحالة
|
||||
$statusText = "CancelFromDriverAfterApply";
|
||||
$statusText = "CancelFromDriverAfterApply";
|
||||
|
||||
if (!$rideId || !$driverId) {
|
||||
jsonError("Missing parameters");
|
||||
@@ -27,6 +38,28 @@ if (!$rideId || !$driverId) {
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 0. التحقق من الملكية — الرحلة يجب أن تكون مُسندة لهذا السائق
|
||||
// بدون هذا الفحص كان أي driver_id يقدر يلغي أي رحلة ويغرّم راكبها.
|
||||
// ---------------------------------------------------------
|
||||
$stmtOwn = $con->prepare("SELECT driver_id FROM ride WHERE id = ? FOR UPDATE");
|
||||
$stmtOwn->execute([$rideId]);
|
||||
$assignedDriver = $stmtOwn->fetchColumn();
|
||||
|
||||
if ($assignedDriver === false) {
|
||||
$con->rollBack();
|
||||
jsonError("Ride not found");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ((string) $assignedDriver !== (string) $driverId) {
|
||||
$con->rollBack();
|
||||
error_log("[cancel_ride_by_driver] SECURITY: رفض إلغاء رحلة غير مُسندة"
|
||||
. " (ride=$rideId caller=$driverId assigned=$assignedDriver)");
|
||||
jsonError("Ride is not assigned to this driver");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 1. معالجة driver_orders (Insert or Update)
|
||||
// ---------------------------------------------------------
|
||||
@@ -99,17 +132,35 @@ try {
|
||||
notifyPassengerOnRideServer($passenger_id, $socketPayload);
|
||||
}
|
||||
|
||||
// 4.1. إضافة غرامة الإلغاء على الراكب (الدين) إذا كانت موجودة
|
||||
// 4.1. غرامة الإلغاء على الراكب — المبلغ من سياسة الخادم لا من العميل
|
||||
// Resolve country + penalty policy (kazan هو مصدر الحقيقة الوحيد)
|
||||
// قراءة متسامحة: لو نزل الكود قبل ترحيل cancelPenaltyFee، لا نُسقط
|
||||
// الإلغاء كله — نرجع لقراءة الدولة وحدها ونعتبر الغرامة صفراً.
|
||||
try {
|
||||
$stmtKazan = $con->prepare("SELECT country, cancelPenaltyFee FROM kazan LIMIT 1");
|
||||
$stmtKazan->execute();
|
||||
$kazan = $stmtKazan->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
} catch (PDOException $eKazan) {
|
||||
error_log("[cancel_ride_by_driver] cancelPenaltyFee غير موجود بعد"
|
||||
. " (شغّل migrations/2026_08_07_kazan_cancel_penalty_policy.sql): "
|
||||
. $eKazan->getMessage());
|
||||
$stmtKazan = $con->prepare("SELECT country FROM kazan LIMIT 1");
|
||||
$stmtKazan->execute();
|
||||
$kazan = $stmtKazan->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
}
|
||||
$country = $kazan['country'] ?? 'Jordan';
|
||||
|
||||
$penaltyFee = (float) ($kazan['cancelPenaltyFee'] ?? 0);
|
||||
if ($penaltyFee > CANCEL_PENALTY_ABSOLUTE_CAP) {
|
||||
error_log("[cancel_ride_by_driver] kazan.cancelPenaltyFee=$penaltyFee"
|
||||
. " تجاوز السقف الصلب — قُصّ إلى " . CANCEL_PENALTY_ABSOLUTE_CAP);
|
||||
$penaltyFee = CANCEL_PENALTY_ABSOLUTE_CAP;
|
||||
}
|
||||
|
||||
if ($penaltyFee > 0) {
|
||||
// إضافة القيمة كدين سالب في المحفظة
|
||||
$negativeDebt = -$penaltyFee;
|
||||
|
||||
// Resolve country and wallet server
|
||||
$stmtKazan = $con->prepare("SELECT country FROM kazan LIMIT 1");
|
||||
$stmtKazan->execute();
|
||||
$kazan = $stmtKazan->fetch(PDO::FETCH_ASSOC) ?: ["country" => "Jordan"];
|
||||
$country = $kazan['country'] ?? 'Jordan';
|
||||
|
||||
$walletServer = "https://walletintaleq.intaleq.xyz";
|
||||
if (strtolower($country) == 'jordan') {
|
||||
$walletServer = getenv('WALLET_SERVER_JORDAN') ?: "https://walletintaleq.intaleq.xyz";
|
||||
|
||||
@@ -839,6 +839,7 @@ CREATE TABLE `kazan` (
|
||||
`fuelPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الوقود المرجعي',
|
||||
`currency` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'SYP' COMMENT 'رمز العملة: SYP, EGP, JOD, IQD, ...',
|
||||
`startPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT 'فتحة العداد',
|
||||
`cancelPenaltyFee` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT 'غرامة إلغاء السائق بعد القبول، تُخصم من محفظة الراكب. 0 = معطّلة',
|
||||
|
||||
-- ========== 🚗 أسعار الكيلومتر لكل نوع سيارة ==========
|
||||
`speedPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Speed',
|
||||
|
||||
@@ -209,7 +209,10 @@ services:
|
||||
PMA_PORT: 3306
|
||||
UPLOAD_LIMIT: 300M
|
||||
ports:
|
||||
- "8082:80" # ادخل عليه عبر http://your-domain:8082 أو افتح بورت 8082 في CloudPanel
|
||||
# مقيَّد على loopback عمداً: الربط السابق "8082:80" كان يكشف phpMyAdmin
|
||||
# للإنترنت بلا TLS (docker-proxy يلتفّ على UFW). الوصول عبر نفق SSH فقط:
|
||||
# ssh -N -L 8082:127.0.0.1:8082 USER@SERVER
|
||||
- "127.0.0.1:8082:80"
|
||||
depends_on:
|
||||
- mysql
|
||||
mem_limit: 256m
|
||||
|
||||
+728
-1024
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user