Update: 2026-06-26 04:04:03
This commit is contained in:
@@ -373,6 +373,7 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
|
|||||||
|
|
||||||
// 2. Validate Promo Code
|
// 2. Validate Promo Code
|
||||||
$discount = 0;
|
$discount = 0;
|
||||||
|
$promo_applied = false;
|
||||||
if (!empty($promo_code)) {
|
if (!empty($promo_code)) {
|
||||||
$sqlPromo = "SELECT amount FROM `promos`
|
$sqlPromo = "SELECT amount FROM `promos`
|
||||||
WHERE promo_code = :promo_code
|
WHERE promo_code = :promo_code
|
||||||
@@ -384,10 +385,31 @@ if (!empty($promo_code)) {
|
|||||||
':promo_code' => $promo_code,
|
':promo_code' => $promo_code,
|
||||||
':passenger_id' => $passenger_id
|
':passenger_id' => $passenger_id
|
||||||
]);
|
]);
|
||||||
if ($stmtPromo->rowCount() > 0) {
|
|
||||||
$promoData = $stmtPromo->fetch(PDO::FETCH_ASSOC);
|
// ✅ FIX P4: إذا لم يُوجد الكود أو كان منتهي الصلاحية → failure فوراً
|
||||||
$discount = (float) $promoData['amount'];
|
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)
|
// 3. Fetch Passenger Wallet (Negative Balance / Debt)
|
||||||
|
|||||||
@@ -139,18 +139,10 @@ if (!isset($tokenData['prices'][$carType])) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ FIX H-05: التحقق من distance و duration في الـ token أيضاً
|
// ✅ FIX P2: تم حذف التحقق من distance و duration
|
||||||
if (isset($tokenData['distance']) && $tokenData['distance'] != $distance) {
|
// السبب: token['distance'] هو الإحداثيات بينما $distance هو المسافة بالكيلومتر (0.x)
|
||||||
error_log("[add_ride] Security failed — distance mismatch.");
|
// وtoken['duration'] هو الثواني بينما $duration_text هو الدقائق — mismatch دائم يكسر جميع الرحلات
|
||||||
printFailure("Tampered ride data (distance mismatch)");
|
// الإحداثيات كافية للتحقق من سلامة الطلب عبر coordsMatch() أعلاه
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($tokenData['duration']) && $tokenData['duration'] != $duration_text) {
|
|
||||||
error_log("[add_ride] Security failed — duration mismatch.");
|
|
||||||
printFailure("Tampered ride data (duration mismatch)");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Securely override pricing from the cryptographically signed token
|
// Securely override pricing from the cryptographically signed token
|
||||||
$price = $tokenData['prices'][$carType]['price'];
|
$price = $tokenData['prices'][$carType]['price'];
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../connect.php';
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
// استلام الرقم وتشفيره
|
|
||||||
$phone = filterRequest("phone");
|
$phone = filterRequest("phone");
|
||||||
$phoneEncrypted = $encryptionHelper->encryptData($phone);
|
$phoneEncrypted = $encryptionHelper->encryptData($phone);
|
||||||
|
|
||||||
@@ -26,12 +25,7 @@ $sql = "SELECT
|
|||||||
COALESCE(r.rideTimeFinish, '1970-01-01 00:00:00') AS ride_time_finish,
|
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_driver, 0) AS price_for_driver,
|
||||||
COALESCE(r.price_for_passenger, 0) AS price_for_passenger,
|
COALESCE(r.price_for_passenger, 0) AS price_for_passenger,
|
||||||
COALESCE(r.distance, 0) AS distance,
|
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
|
|
||||||
FROM
|
FROM
|
||||||
passengers p
|
passengers p
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
@@ -42,7 +36,6 @@ LEFT JOIN
|
|||||||
ORDER BY date DESC, time DESC
|
ORDER BY date DESC, time DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)
|
)
|
||||||
|
|
||||||
WHERE
|
WHERE
|
||||||
p.phone = :phone";
|
p.phone = :phone";
|
||||||
|
|
||||||
@@ -53,7 +46,20 @@ $stmt->execute();
|
|||||||
if ($stmt->rowCount() > 0) {
|
if ($stmt->rowCount() > 0) {
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$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) {
|
foreach ($rows as &$row) {
|
||||||
if (isset($row['phone'])) $row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
if (isset($row['phone'])) $row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||||
if (isset($row['email'])) $row['email'] = $encryptionHelper->decryptData($row['email']);
|
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['last_name'])) $row['last_name'] = $encryptionHelper->decryptData($row['last_name']);
|
||||||
if (isset($row['employmentType']))$row['employmentType'] = $encryptionHelper->decryptData($row['employmentType']);
|
if (isset($row['employmentType']))$row['employmentType'] = $encryptionHelper->decryptData($row['employmentType']);
|
||||||
if (isset($row['maritalStatus'])) $row['maritalStatus'] = $encryptionHelper->decryptData($row['maritalStatus']);
|
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);
|
jsonSuccess($rows);
|
||||||
|
|||||||
@@ -482,7 +482,19 @@ class LocationSearchController extends GetxController {
|
|||||||
bool _pendingGeocode = true;
|
bool _pendingGeocode = true;
|
||||||
|
|
||||||
void updateCurrentLocationFromCamera(LatLng target) {
|
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;
|
newMyLocation = target;
|
||||||
|
|
||||||
if (_pendingGeocode) {
|
if (_pendingGeocode) {
|
||||||
@@ -522,7 +534,16 @@ class LocationSearchController extends GetxController {
|
|||||||
void onCameraMoveThrottled(CameraPosition pos) {
|
void onCameraMoveThrottled(CameraPosition pos) {
|
||||||
_camThrottle?.cancel();
|
_camThrottle?.cancel();
|
||||||
_camThrottle = Timer(const Duration(milliseconds: 160), () {
|
_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<WayPointController>().wayPoints.length;
|
int waypointsLength = Get.find<WayPointController>().wayPoints.length;
|
||||||
int index = wayPointIndex;
|
int index = wayPointIndex;
|
||||||
if (waypointsLength > 0 && index < placesCoordinate.length) {
|
if (waypointsLength > 0 && index < placesCoordinate.length) {
|
||||||
|
|||||||
@@ -1451,11 +1451,27 @@ class RideLifecycleController extends GetxController {
|
|||||||
"has_steps": Get.find<WayPointController>().wayPoints.length > 1
|
"has_steps": Get.find<WayPointController>().wayPoints.length > 1
|
||||||
? 'true'
|
? 'true'
|
||||||
: 'false',
|
: 'false',
|
||||||
"step0": placesCoordinate.length > 0 ? placesCoordinate[0] : "",
|
// ✅ FIX P5: إرسال step0-4 فقط عندما has_steps == true
|
||||||
"step1": placesCoordinate.length > 1 ? placesCoordinate[1] : "",
|
"step0": Get.find<WayPointController>().wayPoints.length > 1 &&
|
||||||
"step2": placesCoordinate.length > 2 ? placesCoordinate[2] : "",
|
placesCoordinate.length > 0
|
||||||
"step3": placesCoordinate.length > 3 ? placesCoordinate[3] : "",
|
? placesCoordinate[0]
|
||||||
"step4": placesCoordinate.length > 4 ? placesCoordinate[4] : "",
|
: "",
|
||||||
|
"step1": Get.find<WayPointController>().wayPoints.length > 1 &&
|
||||||
|
placesCoordinate.length > 1
|
||||||
|
? placesCoordinate[1]
|
||||||
|
: "",
|
||||||
|
"step2": Get.find<WayPointController>().wayPoints.length > 1 &&
|
||||||
|
placesCoordinate.length > 2
|
||||||
|
? placesCoordinate[2]
|
||||||
|
: "",
|
||||||
|
"step3": Get.find<WayPointController>().wayPoints.length > 1 &&
|
||||||
|
placesCoordinate.length > 3
|
||||||
|
? placesCoordinate[3]
|
||||||
|
: "",
|
||||||
|
"step4": Get.find<WayPointController>().wayPoints.length > 1 &&
|
||||||
|
placesCoordinate.length > 4
|
||||||
|
? placesCoordinate[4]
|
||||||
|
: "",
|
||||||
"price_token": priceToken,
|
"price_token": priceToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1796,15 +1812,30 @@ class RideLifecycleController extends GetxController {
|
|||||||
if (!promoFormKey.currentState!.validate()) return;
|
if (!promoFormKey.currentState!.validate()) return;
|
||||||
|
|
||||||
try {
|
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: {
|
final res = await CRUD().post(link: AppLink.getPrices, payload: {
|
||||||
'distance': distance.toString(),
|
'distance': distance.toString(),
|
||||||
'durationToRide': durationToRide.toString(),
|
'durationToRide': durationToRide.toString(),
|
||||||
'startNameAddress': startNameAddress,
|
'startNameAddress': startNameAddress,
|
||||||
'endNameAddress': endNameAddress,
|
'endNameAddress': endNameAddress,
|
||||||
'destLat': myDestination.latitude.toString(),
|
'destLat': pricingDestLat,
|
||||||
'destLng': myDestination.longitude.toString(),
|
'destLng': pricingDestLng,
|
||||||
'passengerLat': newMyLocation.latitude.toString(),
|
'passengerLat': pricingPassengerLat,
|
||||||
'passengerLng': newMyLocation.longitude.toString(),
|
'passengerLng': pricingPassengerLng,
|
||||||
'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0',
|
'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0',
|
||||||
'activeMenuWaypointCount': activeMenuWaypointCount.toString(),
|
'activeMenuWaypointCount': activeMenuWaypointCount.toString(),
|
||||||
'promo_code': promo.text,
|
'promo_code': promo.text,
|
||||||
@@ -1833,6 +1864,56 @@ class RideLifecycleController extends GetxController {
|
|||||||
totalPassengerRayehGaiBalash =
|
totalPassengerRayehGaiBalash =
|
||||||
data['totalPassengerRayehGaiBalash']?.toString() ?? '0';
|
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;
|
promoTaken = true;
|
||||||
update();
|
update();
|
||||||
|
|
||||||
@@ -1872,15 +1953,30 @@ class RideLifecycleController extends GetxController {
|
|||||||
newTime = currentTime.add(durationToAdd);
|
newTime = currentTime.add(durationToAdd);
|
||||||
|
|
||||||
try {
|
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: {
|
final res = await CRUD().post(link: AppLink.getPrices, payload: {
|
||||||
'distance': distance.toString(),
|
'distance': distance.toString(),
|
||||||
'durationToRide': durationToRide.toString(),
|
'durationToRide': durationToRide.toString(),
|
||||||
'startNameAddress': startNameAddress,
|
'startNameAddress': startNameAddress,
|
||||||
'endNameAddress': endNameAddress,
|
'endNameAddress': endNameAddress,
|
||||||
'destLat': myDestination.latitude.toString(),
|
'destLat': pricingDestLat,
|
||||||
'destLng': myDestination.longitude.toString(),
|
'destLng': pricingDestLng,
|
||||||
'passengerLat': newMyLocation.latitude.toString(),
|
'passengerLat': pricingPassengerLat,
|
||||||
'passengerLng': newMyLocation.longitude.toString(),
|
'passengerLng': pricingPassengerLng,
|
||||||
'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0',
|
'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0',
|
||||||
'activeMenuWaypointCount': activeMenuWaypointCount.toString(),
|
'activeMenuWaypointCount': activeMenuWaypointCount.toString(),
|
||||||
'passenger_id': box.read(BoxName.passengerID) ?? '',
|
'passenger_id': box.read(BoxName.passengerID) ?? '',
|
||||||
@@ -2140,7 +2236,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
if (statusRide == 'Begin' ||
|
if (statusRide == 'Begin' ||
|
||||||
currentRideState.value == RideState.inProgress) {
|
currentRideState.value == RideState.inProgress) {
|
||||||
polyLines = {
|
polyLines = {
|
||||||
...polyLines.where((p) => !p.polylineId.value.startsWith('driver_route')),
|
...polyLines
|
||||||
|
.where((p) => !p.polylineId.value.startsWith('driver_route')),
|
||||||
Polyline(
|
Polyline(
|
||||||
polylineId: const PolylineId('main_route'),
|
polylineId: const PolylineId('main_route'),
|
||||||
points: remainingPoints,
|
points: remainingPoints,
|
||||||
@@ -2190,8 +2287,7 @@ class RideLifecycleController extends GetxController {
|
|||||||
payload: {'driver_id': driverId});
|
payload: {'driver_id': driverId});
|
||||||
|
|
||||||
if (res != 'failure' && res is Map) {
|
if (res != 'failure' && res is Map) {
|
||||||
if (res['message'] != null &&
|
if (res['message'] != null && (res['message'] as List).isNotEmpty) {
|
||||||
(res['message'] as List).isNotEmpty) {
|
|
||||||
var _data = (res['message'] as List)[0];
|
var _data = (res['message'] as List)[0];
|
||||||
|
|
||||||
LatLng newDriverPos = LatLng(
|
LatLng newDriverPos = LatLng(
|
||||||
@@ -2225,8 +2321,7 @@ class RideLifecycleController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mapEngine.clearMarkersExceptStartEndAndDriver();
|
mapEngine.clearMarkersExceptStartEndAndDriver();
|
||||||
reloadMarkerDriverCarsLocationToPassengerAfterApplied(
|
reloadMarkerDriverCarsLocationToPassengerAfterApplied(res);
|
||||||
res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
@@ -3393,18 +3488,39 @@ class RideLifecycleController extends GetxController {
|
|||||||
hours = durationToAdd.inHours;
|
hours = durationToAdd.inHours;
|
||||||
minutes = (durationToAdd.inMinutes % 60).round();
|
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 = {
|
markers = {
|
||||||
Marker(
|
Marker(
|
||||||
markerId: const MarkerId('start'),
|
markerId: const MarkerId('start'),
|
||||||
position: startLoc,
|
position: startLoc,
|
||||||
icon: InlqBitmap.fromStyleImage('orange_marker'),
|
icon: InlqBitmap.fromStyleImage(mapEngine.startIcon),
|
||||||
infoWindow: const InfoWindow(title: 'A'),
|
infoWindow: const InfoWindow(title: 'A'),
|
||||||
anchor: const Offset(0.5, 1.0),
|
anchor: const Offset(0.5, 1.0),
|
||||||
),
|
),
|
||||||
Marker(
|
Marker(
|
||||||
markerId: const MarkerId('end'),
|
markerId: const MarkerId('end'),
|
||||||
position: endLoc,
|
position: endLoc,
|
||||||
icon: InlqBitmap.fromStyleImage('violet_marker'),
|
icon: InlqBitmap.fromStyleImage(mapEngine.endIcon),
|
||||||
infoWindow: const InfoWindow(title: 'B'),
|
infoWindow: const InfoWindow(title: 'B'),
|
||||||
anchor: const Offset(0.5, 1.0),
|
anchor: const Offset(0.5, 1.0),
|
||||||
),
|
),
|
||||||
@@ -3418,7 +3534,7 @@ class RideLifecycleController extends GetxController {
|
|||||||
markerId: MarkerId('waypoint_$i'),
|
markerId: MarkerId('waypoint_$i'),
|
||||||
position: wp,
|
position: wp,
|
||||||
icon: InlqBitmap.fromStyleImage(
|
icon: InlqBitmap.fromStyleImage(
|
||||||
isFirstWaypoint ? 'orange_marker' : 'violet_marker'),
|
isFirstWaypoint ? mapEngine.startIcon : mapEngine.endIcon),
|
||||||
infoWindow:
|
infoWindow:
|
||||||
InfoWindow(title: isFirstWaypoint ? 'Stop 1' : 'Stop 2'),
|
InfoWindow(title: isFirstWaypoint ? 'Stop 1' : 'Stop 2'),
|
||||||
anchor: const Offset(0.5, 1.0),
|
anchor: const Offset(0.5, 1.0),
|
||||||
@@ -3426,16 +3542,8 @@ class RideLifecycleController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (polyLines.isNotEmpty) mapEngine.clearPolyline();
|
Log.print('✅ FIX P1: Markers placed — start: $startLoc, end: $endLoc');
|
||||||
|
|
||||||
rideConfirm = false;
|
|
||||||
isMarkersShown = true;
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
await bottomSheet();
|
|
||||||
|
|
||||||
await mapEngine.playRouteAnimation(
|
|
||||||
mapEngine.polylineCoordinates, mapEngine.lastComputedBounds);
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
if (isDrawingRoute) {
|
if (isDrawingRoute) {
|
||||||
isDrawingRoute = false;
|
isDrawingRoute = false;
|
||||||
@@ -4034,23 +4142,26 @@ class RideLifecycleController extends GetxController {
|
|||||||
await _checkAndRefreshMapStyle();
|
await _checkAndRefreshMapStyle();
|
||||||
Get.put(DeepLinkController(), permanent: true);
|
Get.put(DeepLinkController(), permanent: true);
|
||||||
await initilizeGetStorage();
|
await initilizeGetStorage();
|
||||||
|
|
||||||
final bool isLoggedIn = box.read(BoxName.isVerified) == '1' &&
|
final bool isLoggedIn = box.read(BoxName.isVerified) == '1' &&
|
||||||
box.read(BoxName.passengerID) != null;
|
box.read(BoxName.passengerID) != null;
|
||||||
|
|
||||||
// We intentionally DO NOT initialize data here during onInit
|
// We intentionally DO NOT initialize data here during onInit
|
||||||
// because this controller is instantiated globally before the map is opened.
|
// because this controller is instantiated globally before the map is opened.
|
||||||
// Initialization will be triggered by MapPagePassenger calling initializeDataAfterLogin()
|
// 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<void> initializeDataAfterLogin() async {
|
Future<void> initializeDataAfterLogin() async {
|
||||||
if (isDataInitializedAfterLogin) {
|
if (isDataInitializedAfterLogin) {
|
||||||
Log.print("RideLifecycleController: Already initialized, skipping duplicate initializeDataAfterLogin.");
|
Log.print(
|
||||||
|
"RideLifecycleController: Already initialized, skipping duplicate initializeDataAfterLogin.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isDataInitializedAfterLogin = true;
|
isDataInitializedAfterLogin = true;
|
||||||
Log.print("RideLifecycleController: Initializing data after successful login...");
|
Log.print(
|
||||||
|
"RideLifecycleController: Initializing data after successful login...");
|
||||||
getLocationArea(passengerLocation.latitude, passengerLocation.longitude);
|
getLocationArea(passengerLocation.latitude, passengerLocation.longitude);
|
||||||
await _stagePricingAndState();
|
await _stagePricingAndState();
|
||||||
await _stageNiceToHave();
|
await _stageNiceToHave();
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
|
|
||||||
// ── Car Selection List ───────────────────────────────
|
// ── Car Selection List ───────────────────────────────
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 120,
|
height: 134,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
@@ -303,32 +303,32 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeOutCubic,
|
curve: Curves.easeOutCubic,
|
||||||
width: 104,
|
width: 122,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: isSelected
|
gradient: isSelected
|
||||||
? LinearGradient(
|
? LinearGradient(
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: [
|
colors: [
|
||||||
AppColor.primaryColor.withAlpha(18),
|
AppColor.primaryColor.withAlpha(22),
|
||||||
AppColor.primaryColor.withAlpha(8),
|
AppColor.primaryColor.withAlpha(10),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
color: isSelected ? null : AppColor.secondaryColor,
|
color: isSelected ? null : AppColor.secondaryColor,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(18),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? AppColor.primaryColor
|
? AppColor.primaryColor
|
||||||
: AppColor.grayColor.withOpacity(0.2),
|
: AppColor.grayColor.withOpacity(0.25),
|
||||||
width: isSelected ? 2.0 : 1.0,
|
width: isSelected ? 2.5 : 1.5,
|
||||||
),
|
),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? AppColor.primaryColor.withAlpha(40)
|
? AppColor.primaryColor.withAlpha(50)
|
||||||
: Colors.black.withAlpha(12),
|
: Colors.black.withAlpha(16),
|
||||||
blurRadius: isSelected ? 12 : 4,
|
blurRadius: isSelected ? 14 : 6,
|
||||||
offset: const Offset(0, 3),
|
offset: const Offset(0, 3),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -338,11 +338,11 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
// Selected indicator
|
// Selected indicator
|
||||||
if (isSelected)
|
if (isSelected)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 4,
|
top: 5,
|
||||||
right: 4,
|
right: 5,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 18,
|
width: 22,
|
||||||
height: 18,
|
height: 22,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
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
|
// Card content
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(6, 6, 6, 6),
|
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -374,11 +374,11 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
carType.image,
|
carType.image,
|
||||||
height: 44,
|
height: 54,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 2),
|
||||||
|
|
||||||
// Car name
|
// Car name
|
||||||
FittedBox(
|
FittedBox(
|
||||||
@@ -388,7 +388,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight:
|
fontWeight:
|
||||||
isSelected ? FontWeight.w800 : FontWeight.w600,
|
isSelected ? FontWeight.w800 : FontWeight.w600,
|
||||||
fontSize: 12,
|
fontSize: 13,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? AppColor.primaryColor
|
? AppColor.primaryColor
|
||||||
: AppColor.writeColor,
|
: AppColor.writeColor,
|
||||||
@@ -397,12 +397,12 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 3),
|
const SizedBox(height: 2),
|
||||||
|
|
||||||
// Price tag
|
// Price tag
|
||||||
Container(
|
Container(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? AppColor.primaryColor
|
? AppColor.primaryColor
|
||||||
@@ -417,7 +417,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'${_getPassengerPriceText(carType, controller)} ${CurrencyHelper.currency}',
|
'${_getPassengerPriceText(carType, controller)} ${CurrencyHelper.currency}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Colors.white
|
? Colors.white
|
||||||
@@ -653,7 +653,6 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
if (controller.promoFormKey.currentState!
|
if (controller.promoFormKey.currentState!
|
||||||
.validate()) {
|
.validate()) {
|
||||||
controller.applyPromoCodeToPassenger(context);
|
controller.applyPromoCodeToPassenger(context);
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -65,13 +65,21 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
// : Get.put(NotificationController());
|
// : Get.put(NotificationController());
|
||||||
|
|
||||||
Future getToken() async {
|
Future getToken() async {
|
||||||
fcmToken.getToken().then((token) {
|
try {
|
||||||
// Log.print('fcmToken: ${token}');
|
final token = await fcmToken.getToken();
|
||||||
box.write(BoxName.tokenFCM, (token.toString()));
|
if (token != null) {
|
||||||
});
|
box.write(BoxName.tokenFCM, token);
|
||||||
// 🔹 الاشتراك في topic
|
}
|
||||||
await fcmToken.subscribeToTopic("service"); // أو "users" حسب نوع المستخدم
|
} catch (e) {
|
||||||
Log.print("Subscribed to 'service' topic ✅");
|
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) {
|
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
||||||
// If the app is in the background or terminated, show a system tray message
|
// If the app is in the background or terminated, show a system tray message
|
||||||
|
|||||||
@@ -221,7 +221,6 @@ class CRUD {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sc == 401) {
|
if (sc == 401) {
|
||||||
// استخدام SessionManager لتجديد الجلسة عند 401
|
|
||||||
if (Get.isRegistered<SessionManager>()) {
|
if (Get.isRegistered<SessionManager>()) {
|
||||||
final sessionManager = Get.find<SessionManager>();
|
final sessionManager = Get.find<SessionManager>();
|
||||||
if (!sessionManager.isRefreshing.value &&
|
if (!sessionManager.isRefreshing.value &&
|
||||||
@@ -229,7 +228,6 @@ class CRUD {
|
|||||||
await sessionManager.refreshSession(silent: false);
|
await sessionManager.refreshSession(silent: false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// fallback للتجديد القديم
|
|
||||||
await getJWT();
|
await getJWT();
|
||||||
}
|
}
|
||||||
return 'token_expired';
|
return 'token_expired';
|
||||||
@@ -262,12 +260,10 @@ class CRUD {
|
|||||||
if (Get.isRegistered<SessionManager>()) {
|
if (Get.isRegistered<SessionManager>()) {
|
||||||
final sessionManager = Get.find<SessionManager>();
|
final sessionManager = Get.find<SessionManager>();
|
||||||
await sessionManager.refreshSession(silent: true);
|
await sessionManager.refreshSession(silent: true);
|
||||||
token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0];
|
|
||||||
} else {
|
} else {
|
||||||
// fallback: تجديد يدوي
|
|
||||||
await getJWT();
|
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
|
// Initialize app signature if null
|
||||||
@@ -302,7 +298,7 @@ class CRUD {
|
|||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
// getJWT — V1 Login Flow
|
// getJWT — V1 Login Flow
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
Future<void> getJWT() async {
|
Future<bool> getJWT() async {
|
||||||
var payload = {
|
var payload = {
|
||||||
'fingerprint': _getFpHeader(),
|
'fingerprint': _getFpHeader(),
|
||||||
'password': box.read(BoxName.password) ?? '',
|
'password': box.read(BoxName.password) ?? '',
|
||||||
@@ -310,7 +306,6 @@ class CRUD {
|
|||||||
'aud': 'service',
|
'aud': 'service',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize app signature if null
|
|
||||||
if (_appSignature == null) {
|
if (_appSignature == null) {
|
||||||
try {
|
try {
|
||||||
_appSignature = await SecurityHelper.getAppSignature();
|
_appSignature = await SecurityHelper.getAppSignature();
|
||||||
@@ -340,10 +335,11 @@ class CRUD {
|
|||||||
await storage.write(key: BoxName.jwt, value: c(jwt));
|
await storage.write(key: BoxName.jwt, value: c(jwt));
|
||||||
if (hmac != null) {
|
if (hmac != null) {
|
||||||
await box.write(BoxName.hmac, hmac);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -79,18 +79,12 @@ class SessionManager extends GetxController {
|
|||||||
for (int attempt = 1; attempt <= _maxRetries; attempt++) {
|
for (int attempt = 1; attempt <= _maxRetries; attempt++) {
|
||||||
Log.print('🔄 Session refresh attempt $attempt/$_maxRetries');
|
Log.print('🔄 Session refresh attempt $attempt/$_maxRetries');
|
||||||
|
|
||||||
await CRUD().getJWT();
|
final success = await CRUD().getJWT();
|
||||||
|
|
||||||
// التحقق من نجاح التجديد
|
if (success) {
|
||||||
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) {
|
|
||||||
status.value = SessionStatus.valid;
|
status.value = SessionStatus.valid;
|
||||||
isRefreshing.value = false;
|
isRefreshing.value = false;
|
||||||
|
|
||||||
// إشعار النجاح
|
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
_showSessionRefreshedNotification();
|
_showSessionRefreshedNotification();
|
||||||
}
|
}
|
||||||
@@ -99,7 +93,6 @@ class SessionManager extends GetxController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// إذا فشلت المحاولة، انتظر قبل إعادة المحاولة
|
|
||||||
if (attempt < _maxRetries) {
|
if (attempt < _maxRetries) {
|
||||||
await Future.delayed(Duration(seconds: attempt));
|
await Future.delayed(Duration(seconds: attempt));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../jwtconnect.php';
|
||||||
|
|
||||||
|
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||||
|
|
||||||
|
if (empty($providedKey) || $providedKey !== getenv('S2S_SHARED_KEY')) {
|
||||||
|
http_response_code(401);
|
||||||
|
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$passenger_id = filterRequest("passenger_id");
|
||||||
|
|
||||||
|
if (empty($passenger_id)) {
|
||||||
|
printFailure("Missing required parameter: passenger_id");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmtTotalWallet = $con->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");
|
||||||
|
}
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user