diff --git a/backend/ride/pricing/get.php b/backend/ride/pricing/get.php index 6c9e6cd2..17a9bf8d 100644 --- a/backend/ride/pricing/get.php +++ b/backend/ride/pricing/get.php @@ -373,6 +373,7 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR // 2. Validate Promo Code $discount = 0; +$promo_applied = false; if (!empty($promo_code)) { $sqlPromo = "SELECT amount FROM `promos` WHERE promo_code = :promo_code @@ -384,10 +385,31 @@ if (!empty($promo_code)) { ':promo_code' => $promo_code, ':passenger_id' => $passenger_id ]); - if ($stmtPromo->rowCount() > 0) { - $promoData = $stmtPromo->fetch(PDO::FETCH_ASSOC); - $discount = (float) $promoData['amount']; + + // ✅ FIX P4: إذا لم يُوجد الكود أو كان منتهي الصلاحية → failure فوراً + if ($stmtPromo->rowCount() === 0) { + echo json_encode([ + 'status' => 'failure', + 'message' => 'Promo code not found or has expired', + 'applied_discount' => 0, + ]); + exit; } + + $promoData = $stmtPromo->fetch(PDO::FETCH_ASSOC); + $discount = (float) $promoData['amount']; + + // ✅ FIX P4: إذا كان الخصم صفر → failure مع رسالة واضحة + if ($discount <= 0) { + echo json_encode([ + 'status' => 'failure', + 'message' => 'This promo code has no discount value', + 'applied_discount' => 0, + ]); + exit; + } + + $promo_applied = true; } // 3. Fetch Passenger Wallet (Negative Balance / Debt) diff --git a/backend/ride/rides/add_ride.php b/backend/ride/rides/add_ride.php index bb520643..2a1c1295 100644 --- a/backend/ride/rides/add_ride.php +++ b/backend/ride/rides/add_ride.php @@ -139,18 +139,10 @@ if (!isset($tokenData['prices'][$carType])) { exit; } -// ✅ FIX H-05: التحقق من distance و duration في الـ token أيضاً -if (isset($tokenData['distance']) && $tokenData['distance'] != $distance) { - error_log("[add_ride] Security failed — distance mismatch."); - printFailure("Tampered ride data (distance mismatch)"); - exit; -} - -if (isset($tokenData['duration']) && $tokenData['duration'] != $duration_text) { - error_log("[add_ride] Security failed — duration mismatch."); - printFailure("Tampered ride data (duration mismatch)"); - exit; -} +// ✅ FIX P2: تم حذف التحقق من distance و duration +// السبب: token['distance'] هو الإحداثيات بينما $distance هو المسافة بالكيلومتر (0.x) +// وtoken['duration'] هو الثواني بينما $duration_text هو الدقائق — mismatch دائم يكسر جميع الرحلات +// الإحداثيات كافية للتحقق من سلامة الطلب عبر coordsMatch() أعلاه // Securely override pricing from the cryptographically signed token $price = $tokenData['prices'][$carType]['price']; diff --git a/backend/serviceapp/getPassengersByPhone.php b/backend/serviceapp/getPassengersByPhone.php index 475902cc..9cc7827c 100644 --- a/backend/serviceapp/getPassengersByPhone.php +++ b/backend/serviceapp/getPassengersByPhone.php @@ -1,7 +1,6 @@ encryptData($phone); @@ -26,12 +25,7 @@ $sql = "SELECT COALESCE(r.rideTimeFinish, '1970-01-01 00:00:00') AS ride_time_finish, COALESCE(r.price_for_driver, 0) AS price_for_driver, COALESCE(r.price_for_passenger, 0) AS price_for_passenger, - COALESCE(r.distance, 0) AS distance, - 0 AS passenger_wallet_balance, - 0 AS passenger_payment_amount, - '' AS passenger_payment_method, - 0 AS driver_payment_amount, - '' AS driver_payment_method + COALESCE(r.distance, 0) AS distance FROM passengers p LEFT JOIN @@ -42,7 +36,6 @@ LEFT JOIN ORDER BY date DESC, time DESC LIMIT 1 ) - WHERE p.phone = :phone"; @@ -53,7 +46,20 @@ $stmt->execute(); if ($stmt->rowCount() > 0) { $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - // فك التشفير للحقول الحساسة + $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"; + } elseif (strtolower($country) == 'egypt') { + $walletServer = getenv('WALLET_SERVER_EGYPT') ?: "https://walletintaleq.intaleq.xyz"; + } else { + $walletServer = getenv('WALLET_SERVER_SYRIA') ?: "https://walletintaleq.intaleq.xyz"; + } + foreach ($rows as &$row) { if (isset($row['phone'])) $row['phone'] = $encryptionHelper->decryptData($row['phone']); if (isset($row['email'])) $row['email'] = $encryptionHelper->decryptData($row['email']); @@ -64,7 +70,37 @@ if ($stmt->rowCount() > 0) { if (isset($row['last_name'])) $row['last_name'] = $encryptionHelper->decryptData($row['last_name']); if (isset($row['employmentType']))$row['employmentType'] = $encryptionHelper->decryptData($row['employmentType']); if (isset($row['maritalStatus'])) $row['maritalStatus'] = $encryptionHelper->decryptData($row['maritalStatus']); - unset($r['password']); + unset($row['password']); + + $passenger_id = $row['id'] ?? ''; + if (!empty($passenger_id)) { + $walletUrl = "$walletServer/v2/main/ride/passengerWallet/get_s2s_wallet.php"; + $ch = curl_init($walletUrl); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query(["passenger_id" => $passenger_id]), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 5, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/x-www-form-urlencoded', + 'X-S2S-Api-Key: ' . getenv('S2S_SHARED_KEY') + ] + ]); + $s2sRes = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $totalWallet = 0.0; + if ($httpCode === 200 && $s2sRes) { + $resDecoded = json_decode($s2sRes, true); + if ($resDecoded && isset($resDecoded['status']) && $resDecoded['status'] === 'success') { + $totalWallet = (float)($resDecoded['message']['totalWallet'] ?? 0.0); + } + } + $row['passenger_wallet_balance'] = $totalWallet; + } else { + $row['passenger_wallet_balance'] = 0; + } } jsonSuccess($rows); diff --git a/siro_rider/lib/controller/home/map/location_search_controller.dart b/siro_rider/lib/controller/home/map/location_search_controller.dart index 220e65d2..a2fa0f10 100644 --- a/siro_rider/lib/controller/home/map/location_search_controller.dart +++ b/siro_rider/lib/controller/home/map/location_search_controller.dart @@ -482,7 +482,19 @@ class LocationSearchController extends GetxController { bool _pendingGeocode = true; void updateCurrentLocationFromCamera(LatLng target) { - Log.print('📍 updateCurrentLocationFromCamera: $target'); + // ✅ FIX P3: Guard — تجاهل التحديثات إذا كانت المسافة < 20 متر + final double distanceDelta = Geolocator.distanceBetween( + newMyLocation.latitude, + newMyLocation.longitude, + target.latitude, + target.longitude, + ); + if (distanceDelta < 20.0) { + Log.print('📍 updateCurrentLocationFromCamera: Skipped (المسافة ${distanceDelta.toStringAsFixed(1)}م < 20م)'); + return; + } + + Log.print('📍 updateCurrentLocationFromCamera: $target (تغيير ${distanceDelta.toStringAsFixed(1)}م)'); newMyLocation = target; if (_pendingGeocode) { @@ -522,7 +534,16 @@ class LocationSearchController extends GetxController { void onCameraMoveThrottled(CameraPosition pos) { _camThrottle?.cancel(); _camThrottle = Timer(const Duration(milliseconds: 160), () { - Log.print('📸 onCameraMoveThrottled: ${pos.target}'); + // ✅ FIX P3: Guard — تجاهل التحديث إذا كان التغيير ضئيلاً (< 20م) + final double distanceDelta = Geolocator.distanceBetween( + newMyLocation.latitude, + newMyLocation.longitude, + pos.target.latitude, + pos.target.longitude, + ); + if (distanceDelta < 20.0) return; + + Log.print('📸 onCameraMoveThrottled: ${pos.target} (تغيير ${distanceDelta.toStringAsFixed(1)}م)'); int waypointsLength = Get.find().wayPoints.length; int index = wayPointIndex; if (waypointsLength > 0 && index < placesCoordinate.length) { diff --git a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart index b22106b5..72812811 100644 --- a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart +++ b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart @@ -1451,11 +1451,27 @@ class RideLifecycleController extends GetxController { "has_steps": Get.find().wayPoints.length > 1 ? 'true' : 'false', - "step0": placesCoordinate.length > 0 ? placesCoordinate[0] : "", - "step1": placesCoordinate.length > 1 ? placesCoordinate[1] : "", - "step2": placesCoordinate.length > 2 ? placesCoordinate[2] : "", - "step3": placesCoordinate.length > 3 ? placesCoordinate[3] : "", - "step4": placesCoordinate.length > 4 ? placesCoordinate[4] : "", + // ✅ FIX P5: إرسال step0-4 فقط عندما has_steps == true + "step0": Get.find().wayPoints.length > 1 && + placesCoordinate.length > 0 + ? placesCoordinate[0] + : "", + "step1": Get.find().wayPoints.length > 1 && + placesCoordinate.length > 1 + ? placesCoordinate[1] + : "", + "step2": Get.find().wayPoints.length > 1 && + placesCoordinate.length > 2 + ? placesCoordinate[2] + : "", + "step3": Get.find().wayPoints.length > 1 && + placesCoordinate.length > 3 + ? placesCoordinate[3] + : "", + "step4": Get.find().wayPoints.length > 1 && + placesCoordinate.length > 4 + ? placesCoordinate[4] + : "", "price_token": priceToken, }; @@ -1796,15 +1812,30 @@ class RideLifecycleController extends GetxController { if (!promoFormKey.currentState!.validate()) return; try { + final String pricingPassengerLat = + mapEngine.polylineCoordinates.isNotEmpty + ? mapEngine.polylineCoordinates.first.latitude.toString() + : newMyLocation.latitude.toString(); + final String pricingPassengerLng = + mapEngine.polylineCoordinates.isNotEmpty + ? mapEngine.polylineCoordinates.first.longitude.toString() + : newMyLocation.longitude.toString(); + final String pricingDestLat = mapEngine.polylineCoordinates.isNotEmpty + ? mapEngine.polylineCoordinates.last.latitude.toString() + : myDestination.latitude.toString(); + final String pricingDestLng = mapEngine.polylineCoordinates.isNotEmpty + ? mapEngine.polylineCoordinates.last.longitude.toString() + : myDestination.longitude.toString(); + final res = await CRUD().post(link: AppLink.getPrices, payload: { 'distance': distance.toString(), 'durationToRide': durationToRide.toString(), 'startNameAddress': startNameAddress, 'endNameAddress': endNameAddress, - 'destLat': myDestination.latitude.toString(), - 'destLng': myDestination.longitude.toString(), - 'passengerLat': newMyLocation.latitude.toString(), - 'passengerLng': newMyLocation.longitude.toString(), + 'destLat': pricingDestLat, + 'destLng': pricingDestLng, + 'passengerLat': pricingPassengerLat, + 'passengerLng': pricingPassengerLng, 'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0', 'activeMenuWaypointCount': activeMenuWaypointCount.toString(), 'promo_code': promo.text, @@ -1833,6 +1864,56 @@ class RideLifecycleController extends GetxController { totalPassengerRayehGaiBalash = data['totalPassengerRayehGaiBalash']?.toString() ?? '0'; + priceToken = res['price_token']?.toString() ?? ''; + + num appliedDiscount = (res['applied_discount'] ?? 0) is num + ? res['applied_discount'] ?? 0 + : num.tryParse(res['applied_discount']?.toString() ?? '0') ?? 0; + + // ✅ FIX P4: إظهار خطأ إذا كان الخصم صفر (برومو غير صالح) + if (appliedDiscount <= 0) { + Log.print('⚠️ Promo code returned zero discount. Showing error.'); + MyDialog().getDialog( + 'Invalid Promo Code'.tr, + 'This promo code is invalid, expired, or does not apply to your account.' + .tr, + () => Get.back(), + ); + return; + } + + Log.print('💰 Promo discount applied: $appliedDiscount'); + + String carType = box.read(BoxName.carType) ?? ''; + switch (carType) { + case 'Comfort': + totalPassenger = totalPassengerComfort; + break; + case 'Fixed Price': + totalPassenger = totalPassengerSpeed; + break; + case 'Electric': + totalPassenger = totalPassengerElectric; + break; + case 'Awfar Car': + totalPassenger = totalPassengerBalash; + break; + case 'Scooter': + case 'Pink Bike': + totalPassenger = totalPassengerScooter; + break; + case 'Van': + totalPassenger = totalPassengerVan; + break; + case 'Lady': + totalPassenger = totalPassengerLady; + break; + case 'Rayeh Gai': + totalPassenger = totalPassengerRayehGai; + break; + } + totalCostPassenger = totalPassenger; + promoTaken = true; update(); @@ -1872,15 +1953,30 @@ class RideLifecycleController extends GetxController { newTime = currentTime.add(durationToAdd); try { + final String pricingPassengerLat = + mapEngine.polylineCoordinates.isNotEmpty + ? mapEngine.polylineCoordinates.first.latitude.toString() + : newMyLocation.latitude.toString(); + final String pricingPassengerLng = + mapEngine.polylineCoordinates.isNotEmpty + ? mapEngine.polylineCoordinates.first.longitude.toString() + : newMyLocation.longitude.toString(); + final String pricingDestLat = mapEngine.polylineCoordinates.isNotEmpty + ? mapEngine.polylineCoordinates.last.latitude.toString() + : myDestination.latitude.toString(); + final String pricingDestLng = mapEngine.polylineCoordinates.isNotEmpty + ? mapEngine.polylineCoordinates.last.longitude.toString() + : myDestination.longitude.toString(); + final res = await CRUD().post(link: AppLink.getPrices, payload: { 'distance': distance.toString(), 'durationToRide': durationToRide.toString(), 'startNameAddress': startNameAddress, 'endNameAddress': endNameAddress, - 'destLat': myDestination.latitude.toString(), - 'destLng': myDestination.longitude.toString(), - 'passengerLat': newMyLocation.latitude.toString(), - 'passengerLng': newMyLocation.longitude.toString(), + 'destLat': pricingDestLat, + 'destLng': pricingDestLng, + 'passengerLat': pricingPassengerLat, + 'passengerLng': pricingPassengerLng, 'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0', 'activeMenuWaypointCount': activeMenuWaypointCount.toString(), 'passenger_id': box.read(BoxName.passengerID) ?? '', @@ -2140,7 +2236,8 @@ class RideLifecycleController extends GetxController { if (statusRide == 'Begin' || currentRideState.value == RideState.inProgress) { polyLines = { - ...polyLines.where((p) => !p.polylineId.value.startsWith('driver_route')), + ...polyLines + .where((p) => !p.polylineId.value.startsWith('driver_route')), Polyline( polylineId: const PolylineId('main_route'), points: remainingPoints, @@ -2190,8 +2287,7 @@ class RideLifecycleController extends GetxController { payload: {'driver_id': driverId}); if (res != 'failure' && res is Map) { - if (res['message'] != null && - (res['message'] as List).isNotEmpty) { + if (res['message'] != null && (res['message'] as List).isNotEmpty) { var _data = (res['message'] as List)[0]; LatLng newDriverPos = LatLng( @@ -2225,8 +2321,7 @@ class RideLifecycleController extends GetxController { } } mapEngine.clearMarkersExceptStartEndAndDriver(); - reloadMarkerDriverCarsLocationToPassengerAfterApplied( - res); + reloadMarkerDriverCarsLocationToPassengerAfterApplied(res); } } update(); @@ -3393,18 +3488,39 @@ class RideLifecycleController extends GetxController { hours = durationToAdd.inHours; minutes = (durationToAdd.inMinutes % 60).round(); + if (polyLines.isNotEmpty) mapEngine.clearPolyline(); + + rideConfirm = false; + isMarkersShown = true; + update(); + + await bottomSheet(); + + await mapEngine.playRouteAnimation( + mapEngine.polylineCoordinates, mapEngine.lastComputedBounds); + + // ✅ FIX P1: انتظار تحميل الأيقونات قبل إنشاء الـ markers + // intaleq_maps تتطلب صوراً مسجلة عبر addImage() وليس أصولاً مباشرة + if (!mapEngine.isIconsLoaded) { + Log.print('⏳ Waiting for map icons to load before placing markers...'); + for (int i = 0; i < 20 && !mapEngine.isIconsLoaded; i++) { + await Future.delayed(const Duration(milliseconds: 200)); + } + } + + // ✅ FIX P1: استخدام fromStyleImage مع المعرّفات المسجلة مسبقاً markers = { Marker( markerId: const MarkerId('start'), position: startLoc, - icon: InlqBitmap.fromStyleImage('orange_marker'), + icon: InlqBitmap.fromStyleImage(mapEngine.startIcon), infoWindow: const InfoWindow(title: 'A'), anchor: const Offset(0.5, 1.0), ), Marker( markerId: const MarkerId('end'), position: endLoc, - icon: InlqBitmap.fromStyleImage('violet_marker'), + icon: InlqBitmap.fromStyleImage(mapEngine.endIcon), infoWindow: const InfoWindow(title: 'B'), anchor: const Offset(0.5, 1.0), ), @@ -3418,7 +3534,7 @@ class RideLifecycleController extends GetxController { markerId: MarkerId('waypoint_$i'), position: wp, icon: InlqBitmap.fromStyleImage( - isFirstWaypoint ? 'orange_marker' : 'violet_marker'), + isFirstWaypoint ? mapEngine.startIcon : mapEngine.endIcon), infoWindow: InfoWindow(title: isFirstWaypoint ? 'Stop 1' : 'Stop 2'), anchor: const Offset(0.5, 1.0), @@ -3426,16 +3542,8 @@ class RideLifecycleController extends GetxController { } } - if (polyLines.isNotEmpty) mapEngine.clearPolyline(); - - rideConfirm = false; - isMarkersShown = true; + Log.print('✅ FIX P1: Markers placed — start: $startLoc, end: $endLoc'); update(); - - await bottomSheet(); - - await mapEngine.playRouteAnimation( - mapEngine.polylineCoordinates, mapEngine.lastComputedBounds); } catch (e, stackTrace) { if (isDrawingRoute) { isDrawingRoute = false; @@ -4034,23 +4142,26 @@ class RideLifecycleController extends GetxController { await _checkAndRefreshMapStyle(); Get.put(DeepLinkController(), permanent: true); await initilizeGetStorage(); - - final bool isLoggedIn = box.read(BoxName.isVerified) == '1' && - box.read(BoxName.passengerID) != null; - + + final bool isLoggedIn = box.read(BoxName.isVerified) == '1' && + box.read(BoxName.passengerID) != null; + // We intentionally DO NOT initialize data here during onInit // because this controller is instantiated globally before the map is opened. // Initialization will be triggered by MapPagePassenger calling initializeDataAfterLogin() - Log.print("RideLifecycleController.onInit: Waiting for MapPagePassenger to trigger initialization."); + Log.print( + "RideLifecycleController.onInit: Waiting for MapPagePassenger to trigger initialization."); } Future initializeDataAfterLogin() async { if (isDataInitializedAfterLogin) { - Log.print("RideLifecycleController: Already initialized, skipping duplicate initializeDataAfterLogin."); + Log.print( + "RideLifecycleController: Already initialized, skipping duplicate initializeDataAfterLogin."); return; } isDataInitializedAfterLogin = true; - Log.print("RideLifecycleController: Initializing data after successful login..."); + Log.print( + "RideLifecycleController: Initializing data after successful login..."); getLocationArea(passengerLocation.latitude, passengerLocation.longitude); await _stagePricingAndState(); await _stageNiceToHave(); diff --git a/siro_rider/lib/views/home/map_widget.dart/car_details_widget_to_go.dart b/siro_rider/lib/views/home/map_widget.dart/car_details_widget_to_go.dart index 0e42cb13..71e391fd 100644 --- a/siro_rider/lib/views/home/map_widget.dart/car_details_widget_to_go.dart +++ b/siro_rider/lib/views/home/map_widget.dart/car_details_widget_to_go.dart @@ -137,7 +137,7 @@ class CarDetailsTypeToChoose extends StatelessWidget { // ── Car Selection List ─────────────────────────────── SizedBox( - height: 120, + height: 134, child: ListView.builder( physics: const BouncingScrollPhysics(), scrollDirection: Axis.horizontal, @@ -303,32 +303,32 @@ class CarDetailsTypeToChoose extends StatelessWidget { child: AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeOutCubic, - width: 104, + width: 122, decoration: BoxDecoration( gradient: isSelected ? LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - AppColor.primaryColor.withAlpha(18), - AppColor.primaryColor.withAlpha(8), + AppColor.primaryColor.withAlpha(22), + AppColor.primaryColor.withAlpha(10), ], ) : null, color: isSelected ? null : AppColor.secondaryColor, - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(18), border: Border.all( color: isSelected ? AppColor.primaryColor - : AppColor.grayColor.withOpacity(0.2), - width: isSelected ? 2.0 : 1.0, + : AppColor.grayColor.withOpacity(0.25), + width: isSelected ? 2.5 : 1.5, ), boxShadow: [ BoxShadow( color: isSelected - ? AppColor.primaryColor.withAlpha(40) - : Colors.black.withAlpha(12), - blurRadius: isSelected ? 12 : 4, + ? AppColor.primaryColor.withAlpha(50) + : Colors.black.withAlpha(16), + blurRadius: isSelected ? 14 : 6, offset: const Offset(0, 3), ), ], @@ -338,11 +338,11 @@ class CarDetailsTypeToChoose extends StatelessWidget { // Selected indicator if (isSelected) Positioned( - top: 4, - right: 4, + top: 5, + right: 5, child: Container( - width: 18, - height: 18, + width: 22, + height: 22, decoration: BoxDecoration( gradient: LinearGradient( colors: [ @@ -358,13 +358,13 @@ class CarDetailsTypeToChoose extends StatelessWidget { ), ], ), - child: const Icon(Icons.check, size: 11, color: Colors.white), + child: const Icon(Icons.check, size: 13, color: Colors.white), ), ), // Card content Padding( - padding: const EdgeInsets.fromLTRB(6, 6, 6, 6), + padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -374,11 +374,11 @@ class CarDetailsTypeToChoose extends StatelessWidget { duration: const Duration(milliseconds: 300), child: Image.asset( carType.image, - height: 44, + height: 54, fit: BoxFit.contain, ), ), - const SizedBox(height: 4), + const SizedBox(height: 2), // Car name FittedBox( @@ -388,7 +388,7 @@ class CarDetailsTypeToChoose extends StatelessWidget { style: TextStyle( fontWeight: isSelected ? FontWeight.w800 : FontWeight.w600, - fontSize: 12, + fontSize: 13, color: isSelected ? AppColor.primaryColor : AppColor.writeColor, @@ -397,12 +397,12 @@ class CarDetailsTypeToChoose extends StatelessWidget { ), ), - const SizedBox(height: 3), + const SizedBox(height: 2), // Price tag Container( padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: isSelected ? AppColor.primaryColor @@ -417,7 +417,7 @@ class CarDetailsTypeToChoose extends StatelessWidget { child: Text( '${_getPassengerPriceText(carType, controller)} ${CurrencyHelper.currency}', style: TextStyle( - fontSize: 11, + fontSize: 12, fontWeight: FontWeight.w700, color: isSelected ? Colors.white @@ -653,7 +653,6 @@ class CarDetailsTypeToChoose extends StatelessWidget { if (controller.promoFormKey.currentState! .validate()) { controller.applyPromoCodeToPassenger(context); - Navigator.of(context).pop(); } }, ), diff --git a/siro_service/lib/controller/firbase_messge.dart b/siro_service/lib/controller/firbase_messge.dart index 8546a0ea..00e775b7 100644 --- a/siro_service/lib/controller/firbase_messge.dart +++ b/siro_service/lib/controller/firbase_messge.dart @@ -65,13 +65,21 @@ class FirebaseMessagesController extends GetxController { // : Get.put(NotificationController()); Future getToken() async { - fcmToken.getToken().then((token) { - // Log.print('fcmToken: ${token}'); - box.write(BoxName.tokenFCM, (token.toString())); - }); - // 🔹 الاشتراك في topic - await fcmToken.subscribeToTopic("service"); // أو "users" حسب نوع المستخدم - Log.print("Subscribed to 'service' topic ✅"); + try { + final token = await fcmToken.getToken(); + if (token != null) { + box.write(BoxName.tokenFCM, token); + } + } catch (e) { + Log.print('⚠️ getToken error (APNS not ready yet): $e'); + } + + try { + await fcmToken.subscribeToTopic("service"); + Log.print("Subscribed to 'service' topic ✅"); + } catch (e) { + Log.print('⚠️ subscribeToTopic error (APNS not ready yet): $e'); + } FirebaseMessaging.onMessage.listen((RemoteMessage message) { // If the app is in the background or terminated, show a system tray message diff --git a/siro_service/lib/controller/functions/crud.dart b/siro_service/lib/controller/functions/crud.dart index 078ff6ac..9c454183 100644 --- a/siro_service/lib/controller/functions/crud.dart +++ b/siro_service/lib/controller/functions/crud.dart @@ -221,7 +221,6 @@ class CRUD { } if (sc == 401) { - // استخدام SessionManager لتجديد الجلسة عند 401 if (Get.isRegistered()) { final sessionManager = Get.find(); if (!sessionManager.isRefreshing.value && @@ -229,7 +228,6 @@ class CRUD { await sessionManager.refreshSession(silent: false); } } else { - // fallback للتجديد القديم await getJWT(); } return 'token_expired'; @@ -262,12 +260,10 @@ class CRUD { if (Get.isRegistered()) { final sessionManager = Get.find(); await sessionManager.refreshSession(silent: true); - token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0]; } else { - // fallback: تجديد يدوي await getJWT(); - token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0]; } + token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0]; } // Initialize app signature if null @@ -302,7 +298,7 @@ class CRUD { // ═══════════════════════════════════════════════════════════════ // getJWT — V1 Login Flow // ═══════════════════════════════════════════════════════════════ - Future getJWT() async { + Future getJWT() async { var payload = { 'fingerprint': _getFpHeader(), 'password': box.read(BoxName.password) ?? '', @@ -310,7 +306,6 @@ class CRUD { 'aud': 'service', }; - // Initialize app signature if null if (_appSignature == null) { try { _appSignature = await SecurityHelper.getAppSignature(); @@ -340,10 +335,11 @@ class CRUD { await storage.write(key: BoxName.jwt, value: c(jwt)); if (hmac != null) { await box.write(BoxName.hmac, hmac); - final verify = box.read(BoxName.hmac); - Log.print('✅ Verified stored HMAC: $verify'); } + return true; } + Log.print('❌ getJWT failed: $response'); + return false; } // ───────────────────────────────────────────────────────────── diff --git a/siro_service/lib/controller/functions/session_manager.dart b/siro_service/lib/controller/functions/session_manager.dart index 9ef2e9da..c4e5711f 100644 --- a/siro_service/lib/controller/functions/session_manager.dart +++ b/siro_service/lib/controller/functions/session_manager.dart @@ -79,18 +79,12 @@ class SessionManager extends GetxController { for (int attempt = 1; attempt <= _maxRetries; attempt++) { Log.print('🔄 Session refresh attempt $attempt/$_maxRetries'); - await CRUD().getJWT(); + final success = await CRUD().getJWT(); - // التحقق من نجاح التجديد - final newRawToken = box.read(BoxName.jwt)?.toString() ?? ''; - final newToken = newRawToken.isNotEmpty ? r(newRawToken).toString().split(Env.addd)[0] : ''; - final isValid = CRUD.isJwtValid(newToken); - - if (isValid) { + if (success) { status.value = SessionStatus.valid; isRefreshing.value = false; - // إشعار النجاح if (!silent) { _showSessionRefreshedNotification(); } @@ -99,7 +93,6 @@ class SessionManager extends GetxController { return true; } - // إذا فشلت المحاولة، انتظر قبل إعادة المحاولة if (attempt < _maxRetries) { await Future.delayed(Duration(seconds: attempt)); } diff --git a/walletintaleq.intaleq.xyz/v2/main/ride/passengerWallet/get_s2s_wallet.php b/walletintaleq.intaleq.xyz/v2/main/ride/passengerWallet/get_s2s_wallet.php new file mode 100644 index 00000000..44bf14a6 --- /dev/null +++ b/walletintaleq.intaleq.xyz/v2/main/ride/passengerWallet/get_s2s_wallet.php @@ -0,0 +1,35 @@ +prepare(" + SELECT COALESCE(SUM(balance), 0) + FROM `passengerWallet` + WHERE passenger_id = :passenger_id + "); + $stmtTotalWallet->execute([':passenger_id' => $passenger_id]); + $totalWallet = (float)($stmtTotalWallet->fetchColumn() ?: 0.0); + + printSuccess([ + "totalWallet" => $totalWallet + ]); +} catch (Exception $e) { + error_log("[get_s2s_passenger_wallet] " . $e->getMessage()); + printFailure("An error occurred"); +} +?> \ No newline at end of file