From 474c212bcb65a5d28f757ec0341701826b3b9d5e Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Fri, 24 Jul 2026 15:08:37 +0300 Subject: [PATCH] refactor: migrate amount fields to decimal and implement location service restriction for unsupported regions --- .DS_Store | Bin 10244 -> 10244 bytes backend/functions.php | 2 +- ...6_07_24_fix_seferWallet_amount_varchar.sql | 23 +++++++++++++ backend/ride/rides/acceptRide.php | 32 +++++++++++++++--- backend/schema_primary.sql | 2 +- backend/schema_ride.sql | 2 +- payment_server/WalletDB.sql | 4 +-- .../2026_07_24_fix_wallet_amount_varchar.sql | 29 ++++++++++++++++ .../v2/main/sms_webhook/finalize_payout.php | 25 +++++++++++--- ride_server/schema_ride.sql | 2 +- .../functions/location_controller.dart | 14 ++++++-- .../home/captin/home_captain_controller.dart | 12 ++++--- siro_driver/lib/controller/local/ar_eg.dart | 2 +- siro_driver/lib/controller/local/ar_jo.dart | 2 +- siro_driver/lib/controller/local/ar_sy.dart | 2 +- siro_driver/pubspec.yaml | 2 +- 16 files changed, 129 insertions(+), 26 deletions(-) create mode 100644 backend/migrations/2026_07_24_fix_seferWallet_amount_varchar.sql create mode 100644 payment_server/migrations/2026_07_24_fix_wallet_amount_varchar.sql diff --git a/.DS_Store b/.DS_Store index 964fdbfb2702369b9789ef5fb66851077a794795..b2dd4fd9522fb9cab4c7f7a8d16883a3b6a3a451 100644 GIT binary patch delta 38 ucmZn(XbG6$&uFwUU^hRb(Pkb2HkQrnL?gL27R+JX%&zc@W%FDyW@Z5TS`2Lf delta 415 zcmZn(XbG6$&uFnRU^hRb#bzD>HWpz{hHQpBhJ1!{h8%`eh7^W$hSbT%!j0@k2098x zW(JdOg`_866IQ9mqAn-hFgQ6sw}1fzxR);il2}yb=DWBg<>V&;WjU1Cea?n|I_`)| xWeQG}Ca6v<$Uw51#{p;_Hmf%?i)3(ZW>@&dQjesZ#xBL`P 'update_ride_status', 'passenger_id' => $passenger_id, - 'payload' => $payload + 'payload' => is_array($payload) ? json_encode($payload) : $payload ]; $ch = curl_init(); diff --git a/backend/migrations/2026_07_24_fix_seferWallet_amount_varchar.sql b/backend/migrations/2026_07_24_fix_seferWallet_amount_varchar.sql new file mode 100644 index 00000000..eefcf70b --- /dev/null +++ b/backend/migrations/2026_07_24_fix_seferWallet_amount_varchar.sql @@ -0,0 +1,23 @@ +-- 2026_07_24_fix_seferWallet_amount_varchar.sql +-- Siro audit S5 (docs/21 في Tripz): seferWallet.amount في قاعدتي backend و ride_server +-- كان varchar(10) — نفس عيب driverWallet/siroWallet في payment_server، لكن أخطر هنا: +-- backend/ride/seferWallet/add.php:28 يُدخله بلا أي تحقّق رقمي (bindParam PARAM_STR +-- على $amount = filterRequest("amount") مباشرة). يوسّعه إلى DECIMAL(12,3) NOT NULL. +-- +-- ⚠️ توسيع العمود وحده لا يمنع إدخال قيمة نصية غير رقمية مستقبلاً — سيفشل الإدراج +-- بخطأ SQL بدل التسرّب الصامت (تحسين، لكن add.php يحتاج فحص is_numeric($amount) +-- قبل التنفيذ. هذا خارج نطاق S4/S5 الأصليين ولم يُصلَح هنا — سجّله كبند منفصل. +-- +-- التطبيق: على قاعدتي backend و ride_server كلتيهما (الجدول مكرَّر بينهما). +-- mysql < 2026_07_24_fix_seferWallet_amount_varchar.sql +-- mysql < 2026_07_24_fix_seferWallet_amount_varchar.sql + +START TRANSACTION; + +ALTER TABLE `seferWallet` + MODIFY COLUMN `amount` DECIMAL(12,3) NOT NULL; + +COMMIT; + +-- ── تراجع (لا يُنفَّذ تلقائياً) ───────────────────────────────────── +-- ALTER TABLE `seferWallet` MODIFY COLUMN `amount` VARCHAR(10) NOT NULL; diff --git a/backend/ride/rides/acceptRide.php b/backend/ride/rides/acceptRide.php index 83d0329b..93d34ea0 100644 --- a/backend/ride/rides/acceptRide.php +++ b/backend/ride/rides/acceptRide.php @@ -147,12 +147,23 @@ try { if ($driverRaw) { $encryptedFields = ['first_name', 'last_name', 'gender', 'phone', 'car_plate', 'token']; foreach ($driverRaw as $key => $value) { - $driverInfo[$key] = (in_array($key, $encryptedFields) && !empty($value)) - ? $encryptionHelper->decryptData($value) - : $value; + if (in_array($key, $encryptedFields, true) && !empty($value)) { + $decrypted = false; + try { + $decrypted = $encryptionHelper->decryptData($value); + } catch (\Throwable $e) { + $decrypted = false; + } + $driverInfo[$key] = ($decrypted !== false && $decrypted !== null && $decrypted !== '') ? $decrypted : $value; + } else { + $driverInfo[$key] = $value; + } } $driverInfo['driverName'] = trim(($driverInfo['first_name'] ?? '') . ' ' . ($driverInfo['last_name'] ?? '')); - $driverInfo['ratingDriver'] = $driverInfo['ratingDriver'] ?: "5.0"; + if (empty($driverInfo['driverName']) && !empty($driverInfo['phone'])) { + $driverInfo['driverName'] = $driverInfo['phone']; + } + $driverInfo['ratingDriver'] = !empty($driverInfo['ratingDriver']) ? (string)$driverInfo['ratingDriver'] : "5.0"; $ratingValue = (float) $driverInfo['ratingDriver']; $ratingCount = (int) ($driverInfo['ratingCount'] ?? 0); $completedRides = (int) ($driverInfo['completedRides'] ?? 0); @@ -163,6 +174,19 @@ try { } else { $driverInfo['driverTier'] = 'Verified driver'; } + + if (isset($redisLocation) && $redisLocation) { + try { + $driverLoc = $redisLocation->hGetAll("driver:location:$driverId"); + if (!empty($driverLoc)) { + $driverInfo['lat'] = (float)($driverLoc['lat'] ?? 0); + $driverInfo['lng'] = (float)($driverLoc['lng'] ?? 0); + $driverInfo['heading'] = (float)($driverLoc['heading'] ?? 0); + } + } catch (Exception $eLoc) { + // Ignore location fetch error + } + } } // ═══════════════════════════════════════════════════════════ diff --git a/backend/schema_primary.sql b/backend/schema_primary.sql index 27b8ff05..2ad5cf21 100644 --- a/backend/schema_primary.sql +++ b/backend/schema_primary.sql @@ -1483,7 +1483,7 @@ CREATE TABLE `seferWallet` ( `id` int NOT NULL AUTO_INCREMENT, `driverId` varchar(100) NOT NULL, `passengerId` varchar(100) NOT NULL, - `amount` varchar(10) NOT NULL, + `amount` decimal(12,3) NOT NULL, `paymentMethod` varchar(50) NOT NULL, `token` varchar(100) NOT NULL, `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/backend/schema_ride.sql b/backend/schema_ride.sql index df08bd33..e7a6a4e3 100644 --- a/backend/schema_ride.sql +++ b/backend/schema_ride.sql @@ -1385,7 +1385,7 @@ CREATE TABLE `seferWallet` ( `id` int NOT NULL AUTO_INCREMENT, `driverId` varchar(100) NOT NULL, `passengerId` varchar(100) NOT NULL, - `amount` varchar(10) NOT NULL, + `amount` decimal(12,3) NOT NULL, `paymentMethod` varchar(50) NOT NULL, `token` varchar(100) NOT NULL, `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/payment_server/WalletDB.sql b/payment_server/WalletDB.sql index 8742c4e9..6b3d65af 100644 --- a/payment_server/WalletDB.sql +++ b/payment_server/WalletDB.sql @@ -118,7 +118,7 @@ CREATE TABLE `driverWallet` ( `driverID` varchar(100) NOT NULL, `paymentID` varchar(200) NOT NULL, `dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `amount` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, + `amount` decimal(12,3) NOT NULL, `paymentMethod` varchar(20) NOT NULL, `dateUpdated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=latin1; @@ -528,7 +528,7 @@ CREATE TABLE `siroWallet` ( `id` int NOT NULL, `driverId` varchar(100) NOT NULL, `passengerId` varchar(100) NOT NULL, - `amount` varchar(10) NOT NULL, + `amount` decimal(12,3) NOT NULL, `paymentMethod` varchar(50) NOT NULL, `token` varchar(100) NOT NULL, `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP diff --git a/payment_server/migrations/2026_07_24_fix_wallet_amount_varchar.sql b/payment_server/migrations/2026_07_24_fix_wallet_amount_varchar.sql new file mode 100644 index 00000000..ae21261b --- /dev/null +++ b/payment_server/migrations/2026_07_24_fix_wallet_amount_varchar.sql @@ -0,0 +1,29 @@ +-- 2026_07_24_fix_wallet_amount_varchar.sql +-- Siro audit S5 (docs/21 في Tripz): driverWallet.amount و siroWallet.amount كانا varchar(10) +-- — المال مخزَّن نصاً و SUM() يُجري جمعاً نصياً. يوسّعهما إلى DECIMAL(12,3) NOT NULL. +-- +-- آمن رجعياً: كل الإدخالات الحالية في الكود تمرّر أرقاماً (موجبة أو سالبة) عبر PDO +-- params، فلا قيمة نصية غير رقمية موجودة فعلياً. DECIMAL(12,3) يحتفظ بالإشارة +-- (driverWallet يخزّن دفتر أستاذ بقيود موجبة وسالبة، لا رصيداً تراكمياً) — لذلك +-- لا CHECK (amount >= 0) هنا، خلافاً لعمود الرصيد التراكمي في Tripz. +-- +-- التطبيق: mysql < 2026_07_24_fix_wallet_amount_varchar.sql +-- التراجع: انظر نهاية الملف (معلَّق، فعّله يدوياً عند الحاجة فقط) + +START TRANSACTION; + +ALTER TABLE `driverWallet` + MODIFY COLUMN `amount` DECIMAL(12,3) NOT NULL; + +ALTER TABLE `siroWallet` + MODIFY COLUMN `amount` DECIMAL(12,3) NOT NULL; + +COMMIT; + +-- ── تحقّق يدوي بعد التنفيذ ───────────────────────────────────────── +-- SELECT driverID, SUM(amount) AS balance FROM driverWallet GROUP BY driverID LIMIT 5; +-- تأكد أن balance رقم صحيح منطقي (لا نص مبتور كما كان يحدث مع SUM() على varchar). + +-- ── تراجع (لا يُنفَّذ تلقائياً) ───────────────────────────────────── +-- ALTER TABLE `driverWallet` MODIFY COLUMN `amount` VARCHAR(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL; +-- ALTER TABLE `siroWallet` MODIFY COLUMN `amount` VARCHAR(10) NOT NULL; diff --git a/payment_server/v2/main/sms_webhook/finalize_payout.php b/payment_server/v2/main/sms_webhook/finalize_payout.php index f7a704ee..e7650782 100755 --- a/payment_server/v2/main/sms_webhook/finalize_payout.php +++ b/payment_server/v2/main/sms_webhook/finalize_payout.php @@ -53,13 +53,16 @@ if (!function_exists('finalizePayout')) { * تقوم بتسجيل كل المعاملات في محافظ السائق والشركة وتحديث حالة الدفعات. */ function finalizePayout(PDO $con, int $payoutId) { + global $encryptionHelper; logPayoutError("START", "Starting finalization for payout ID: {$payoutId}"); + $inTransaction = false; + try { // 1. جلب بيانات طلب السحب المكتمل $stmt = $con->prepare("SELECT * FROM `payout_requests` WHERE id = :id AND status = 'completed' LIMIT 1"); $stmt->execute([':id' => $payoutId]); - $payout = $stmt->fetch(PDO::FETCH_ASSOC); + $payout = $stmt->fetch(PDO::FETCH_ASSOC); if (!$payout) { logPayoutError("FETCH_PAYOUT", "Payout request not found or not completed.", ['payoutId' => $payoutId]); @@ -87,6 +90,10 @@ if (!function_exists('finalizePayout')) { $netAmount = (float)$payout['amount']; // The amount requested $totalDeducted = $netAmount + $payoutFee; // Total deducted + // --- S4 Fix: كل الكتابات (2–6) في معاملة واحدة، مع تراجع كامل عند أي فشل --- + $con->beginTransaction(); + $inTransaction = true; + // 2. إنشاء معرف دفع رئيسي لهذه المعاملة // نسجل المبلغ الإجمالي المخصوم بالسالب $paymentID = generatePaymentID($con, $driverId, -$totalDeducted, 'payout'); @@ -102,11 +109,12 @@ if (!function_exists('finalizePayout')) { // 4. Update the reserved deduction in driverWallet with the real paymentID $updateDriver = $con->prepare("UPDATE driverWallet SET paymentID = :paymentID, paymentMethod = 'payout' WHERE driverID = :driverID AND paymentMethod = 'payout_reserved' AND amount = :amount LIMIT 1"); $updateDriver->execute([ - ':driverID' => $driverId, + ':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => -$totalDeducted ]); - + if ($updateDriver->rowCount() === 0) throw new Exception('No matching reserved driverWallet row to finalize'); + $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenDriver]); logPayoutError("DRIVER_WALLET", "Updated reserved transaction of {$totalDeducted} in driverWallet."); @@ -122,17 +130,24 @@ if (!function_exists('finalizePayout')) { if ($insertSiro->rowCount() === 0) throw new Exception('Insert to siroWallet failed'); $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenSiro]); logPayoutError("SIRO_WALLET", "Fee transaction of {$payoutFee} recorded in siroWallet."); - + // 6. تحديث حالة الدفعات التي تم سحبها في جدول 'payments' - // هذا السطر يقوم بتحديث كل الدفعات المعلقة للسائق، معتبراً أن عملية السحب تغطيها + // السائق يسحب رصيده الكامل فقط (لا خيار سحب جزئي في التطبيق)، لذلك + // تحديث كل الدفعات المعلّقة لهذا السائق صحيح دائماً عند وصولنا هذه الخطوة. $updatePayments = $con->prepare("UPDATE payments SET isGiven = TRUE WHERE driverID = :driverId AND isGiven = FALSE"); $updatePayments->execute([':driverId' => $driverId]); logPayoutError("UPDATE_PAYMENTS", "Marked pending payments as 'isGiven' for driver {$driverId}. Rows affected: " . $updatePayments->rowCount()); + $con->commit(); + $inTransaction = false; + logPayoutError("SUCCESS", "Payout finalization completed successfully for payout ID: {$payoutId}"); return true; } catch (Throwable $e) { + if ($inTransaction && $con->inTransaction()) { + $con->rollBack(); + } logPayoutError("EXCEPTION", "An exception occurred: " . $e->getMessage(), ['payoutId' => $payoutId]); return false; } diff --git a/ride_server/schema_ride.sql b/ride_server/schema_ride.sql index df08bd33..e7a6a4e3 100644 --- a/ride_server/schema_ride.sql +++ b/ride_server/schema_ride.sql @@ -1385,7 +1385,7 @@ CREATE TABLE `seferWallet` ( `id` int NOT NULL AUTO_INCREMENT, `driverId` varchar(100) NOT NULL, `passengerId` varchar(100) NOT NULL, - `amount` varchar(10) NOT NULL, + `amount` decimal(12,3) NOT NULL, `paymentMethod` varchar(50) NOT NULL, `token` varchar(100) NOT NULL, `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/siro_driver/lib/controller/functions/location_controller.dart b/siro_driver/lib/controller/functions/location_controller.dart index 5d76d33d..f060630b 100755 --- a/siro_driver/lib/controller/functions/location_controller.dart +++ b/siro_driver/lib/controller/functions/location_controller.dart @@ -11,6 +11,7 @@ import 'package:permission_handler/permission_handler.dart' as ph; import 'package:socket_io_client/socket_io_client.dart' as IO; import 'package:siro_driver/constant/table_names.dart'; import 'package:trip_overlay_plugin/trip_overlay_plugin.dart'; +import '../../constant/country_polygons.dart'; import '../../constant/box_name.dart'; import '../../constant/links.dart'; import '../../main.dart'; @@ -559,7 +560,11 @@ class LocationController extends GetxController with WidgetsBindingObserver { final now = DateTime.now(); final pos = LatLng(loc.latitude!, loc.longitude!); - myLocation = pos; + if (!CountryPolygons.isServiceSupported(pos)) { + myLocation = const LatLng(31.9539, 35.9106); + } else { + myLocation = pos; + } speed = loc.speed ?? 0.0; heading = loc.heading ?? 0.0; @@ -863,7 +868,12 @@ class LocationController extends GetxController with WidgetsBindingObserver { if (locData != null && locData.latitude != null && locData.longitude != null) { - myLocation = LatLng(locData.latitude!, locData.longitude!); + final rawPos = LatLng(locData.latitude!, locData.longitude!); + if (!CountryPolygons.isServiceSupported(rawPos)) { + myLocation = const LatLng(31.9539, 35.9106); + } else { + myLocation = rawPos; + } heading = locData.heading ?? 0.0; speed = locData.speed ?? 0.0; diff --git a/siro_driver/lib/controller/home/captin/home_captain_controller.dart b/siro_driver/lib/controller/home/captin/home_captain_controller.dart index c0c37ea7..3564e267 100755 --- a/siro_driver/lib/controller/home/captin/home_captain_controller.dart +++ b/siro_driver/lib/controller/home/captin/home_captain_controller.dart @@ -736,11 +736,9 @@ class HomeCaptainController extends GetxController { ); if (locData != null && locData.latitude != null) { - myLocation = LatLng(locData.latitude!, locData.longitude!); - print( - "📍 [HomeCaptain] Location updated: ${myLocation.latitude}, ${myLocation.longitude}"); - - if (!CountryPolygons.isServiceSupported(myLocation)) { + final rawPos = LatLng(locData.latitude!, locData.longitude!); + if (!CountryPolygons.isServiceSupported(rawPos)) { + myLocation = const LatLng(31.9539, 35.9106); if (!Get.isSnackbarOpen) { Get.snackbar( 'Alert'.tr, @@ -754,7 +752,11 @@ class HomeCaptainController extends GetxController { isDismissible: true, ); } + } else { + myLocation = rawPos; } + print( + "📍 [HomeCaptain] Location updated: ${myLocation.latitude}, ${myLocation.longitude}"); } else { print( "⚠️ [HomeCaptain] Could not get current location, using default."); diff --git a/siro_driver/lib/controller/local/ar_eg.dart b/siro_driver/lib/controller/local/ar_eg.dart index ad486e61..24cb8435 100644 --- a/siro_driver/lib/controller/local/ar_eg.dart +++ b/siro_driver/lib/controller/local/ar_eg.dart @@ -2692,5 +2692,5 @@ final Map ar_eg = { "Clear Destination": "مسح الوجهة", "Destination cleared!": "تم مسح الوجهة الشخصية!", "Move map to select destination": "حرك الخريطة لتحديد الوجهة الشخصية", - "service_unavailable_area": "هذه الخدمة غير متوفرة حالياً في منطقتك", + "service_unavailable_area": "هذا التطبيق لا يعمل في منطقتك حالياً", }; diff --git a/siro_driver/lib/controller/local/ar_jo.dart b/siro_driver/lib/controller/local/ar_jo.dart index a06d98ed..86b35aef 100644 --- a/siro_driver/lib/controller/local/ar_jo.dart +++ b/siro_driver/lib/controller/local/ar_jo.dart @@ -2692,7 +2692,7 @@ final Map ar_jo = { "Clear Destination": "مسح الوجهة", "Destination cleared!": "تم مسح الوجهة الشخصية!", "Move map to select destination": "حرك الخريطة لتحديد الوجهة الشخصية", - "service_unavailable_area": "هذه الخدمة غير متوفرة حالياً في منطقتك", + "service_unavailable_area": "هذا التطبيق لا يعمل في منطقتك حالياً", "بطاقة الصعود (Boarding Pass)": "بطاقة الصعود (Boarding Pass)", "بطاقة الصعود": "بطاقة الصعود", "يرجى إبراز هذا الرمز للسائق عند الصعود للحافلة": "يرجى إبراز هذا الرمز للسائق عند الصعود للحافلة", diff --git a/siro_driver/lib/controller/local/ar_sy.dart b/siro_driver/lib/controller/local/ar_sy.dart index 119c21a1..684e4382 100644 --- a/siro_driver/lib/controller/local/ar_sy.dart +++ b/siro_driver/lib/controller/local/ar_sy.dart @@ -2694,5 +2694,5 @@ final Map ar_sy = { "🏆 \\\${'Maximum Level Reached!": "🏆 \\\${'وصلت لأعلى مستوى!", "💰 Pay with Wallet": "💰 ادفع من المحفظة", "💳 Pay with Credit Card": "💳 ادفع ببطاقة ائتمان", - "service_unavailable_area": "هذه الخدمة غير متوفرة حالياً في منطقتك", + "service_unavailable_area": "هذا التطبيق لا يعمل في منطقتك حالياً", }; diff --git a/siro_driver/pubspec.yaml b/siro_driver/pubspec.yaml index 41d4826b..e8cdfe1e 100644 --- a/siro_driver/pubspec.yaml +++ b/siro_driver/pubspec.yaml @@ -2,7 +2,7 @@ name: siro_driver description: "A new Flutter project." publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.0.0+5 +version: 1.0.1+6 environment: sdk: ">=3.0.5 <4.0.0"