diff --git a/backend/auth/otp/verify.php b/backend/auth/otp/verify.php index a6d5b826..66cfb03e 100644 --- a/backend/auth/otp/verify.php +++ b/backend/auth/otp/verify.php @@ -177,8 +177,10 @@ try { $isRegistered = false; $driverData = null; - $chkStmt = $con->prepare("SELECT id, first_name, last_name, email, phone FROM driver WHERE phone = ?"); - $chkStmt->execute([$encryptionHelper->encryptData($phone_number)]); + $chkStmt = $con->prepare("SELECT id, first_name, last_name, email, phone FROM driver WHERE phone = ? OR (? IS NOT NULL AND phone_bidx = ?)"); + global $blindIndex; + $phoneBidx = $blindIndex ? $blindIndex->index('driver.phone', $phone_number) : null; + $chkStmt->execute([$encryptionHelper->encryptData($phone_number), $phoneBidx, $phoneBidx]); $driver = $chkStmt->fetch(PDO::FETCH_ASSOC); // Generate driverID for unregistered users (hash of phone) @@ -273,8 +275,10 @@ try { $passengerData = null; $passengerID = ''; - $chkStmt = $con->prepare("SELECT id, first_name, last_name, email, phone FROM passengers WHERE phone = ?"); - $chkStmt->execute([$encryptionHelper->encryptData($phone_number)]); + $chkStmt = $con->prepare("SELECT id, first_name, last_name, email, phone FROM passengers WHERE phone = ? OR (? IS NOT NULL AND phone_bidx = ?)"); + global $blindIndex; + $phoneBidx = $blindIndex ? $blindIndex->index('passengers.phone', $phone_number) : null; + $chkStmt->execute([$encryptionHelper->encryptData($phone_number), $phoneBidx, $phoneBidx]); $passenger = $chkStmt->fetch(PDO::FETCH_ASSOC); if ($passenger) { diff --git a/backend/auth/passenger/register.php b/backend/auth/passenger/register.php index fb0f7ded..100674ad 100644 --- a/backend/auth/passenger/register.php +++ b/backend/auth/passenger/register.php @@ -63,7 +63,7 @@ try { $firstName_encrypted = $encryptionHelper->encryptData($firstName); $lastName_encrypted = $encryptionHelper->encryptData($lastName); $email_encrypted = $encryptionHelper->encryptData($email); - $uniqueId = substr(md5($phoneNumber), 0, 20); + $uniqueId = substr(md5($phoneNumber), 0, 16); $password_hashed = password_hash($email . $uniqueId, PASSWORD_DEFAULT); $unknown_encrypted = $encryptionHelper->encryptData("unknown yet"); @@ -77,12 +77,13 @@ try { // (مهلة أوسع من صلاحية الرمز نفسه [5 دقائق] لإعطاء وقت كافٍ لإكمال // نموذج التسجيل بعد التحقق مباشرة). $step = 4.5; + $otpSearchKey = otpPhoneKey($phoneNumber); $verifyCheckStmt = $con->prepare( "SELECT id FROM phone_verification_passenger WHERE phone_number = ? AND verified = 1 AND created_at > DATE_SUB(NOW(), INTERVAL 30 MINUTE) LIMIT 1" ); - $verifyCheckStmt->execute([$phoneNumber_encrypted]); + $verifyCheckStmt->execute([$otpSearchKey]); if ($verifyCheckStmt->rowCount() === 0) { error_log("$logTag Step 4.5 Error: Phone number not verified via OTP."); jsonError("Phone number must be verified before registration."); diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 46e1fc1e..7bee486a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -30,8 +30,8 @@ services: # التقسيم لأبعد من هذا لا يضيف أداءً — نفس الصورة، نفس النواة. php: build: - context: ./php - dockerfile: Dockerfile.fpm + context: .. + dockerfile: docker/php/Dockerfile.fpm args: PHP_VERSION: "${PHP_VERSION:-8.2}" volumes: @@ -67,7 +67,9 @@ services: # ‏TLS على هذا المنفذ (‎https://…:2020‎) فتتجمّد المصافحة وينتهي بـ timeout. # ‏nginx على المضيف يستمع على 2020 بالشهادة ويمرّر إلى 12020 هنا. # ‏انظر nginx/siro-sockets-tls.conf - - "127.0.0.1:12020:2020" + # ‏المنفذ قابل للضبط: على سيرفر تعمل عليه أكثر من نسخة (siro/tripz/intaleq) + # ‏يتصادم 12020 الثابت فلا تبدأ الحاوية أصلاً. اضبط DRIVER_SOCKET_PORT في .env. + - "127.0.0.1:${DRIVER_SOCKET_PORT:-12020}:2020" # ‏و2021 داخلي فقط: الباك إند يناديه عبر http://socket_driver:2021 depends_on: - redis @@ -90,7 +92,7 @@ services: ports: # ‏نفس منطق سوكيت السائقين: nginx على المضيف يستمع على 3030 بالشهادة # ‏ويمرّر إلى 13030 هنا. و3031 داخلي فقط (http://socket_passenger:3031). - - "127.0.0.1:13030:3030" + - "127.0.0.1:${PASSENGER_SOCKET_PORT:-13030}:3030" depends_on: - redis mem_limit: 512m diff --git a/docker/php/Dockerfile.fpm b/docker/php/Dockerfile.fpm index 46c4e5ce..afc26981 100644 --- a/docker/php/Dockerfile.fpm +++ b/docker/php/Dockerfile.fpm @@ -6,4 +6,9 @@ ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/do RUN chmod +x /usr/local/bin/install-php-extensions && \ install-php-extensions mysqli pdo_mysql redis opcache gd zip @composer +# تثبيت مكتبات سيرفر المدفوعات (firebase/php-jwt وغيره) +# هذا يخبز vendor/ داخل الصورة مباشرة حتى لا يعتمد على مجلد خارجي +COPY payment_server/v2/composer.json payment_server/v2/composer.lock /var/www/v2/ +RUN cd /var/www/v2 && composer install --no-dev --no-interaction --optimize-autoloader + WORKDIR /var/www diff --git a/payment_server/v2/main/connect.php b/payment_server/v2/main/connect.php index f30bd57a..77c7d85c 100755 --- a/payment_server/v2/main/connect.php +++ b/payment_server/v2/main/connect.php @@ -1,55 +1,63 @@ false, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_EMULATE_PREPARES => false, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8" + PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8", ]; - $user = getenv('DB_PAYMENT_USER') ?: (getenv('USER') ?: 'root'); - $pass = getenv('DB_PAYMENT_PASS') ?: (getenv('PASS') ?: getenv('MYSQL_ROOT_PASSWORD')); $con = new PDO($dsn, $user, $pass, $options); - -// echo $con; - // --- JWT Authentication --- - include "functions.php"; // Include the functions file - - $decodedToken = authenticateJWT(); // Call the authentication function + // 5. JWT Authentication + include __DIR__ . "/functions.php"; + $decodedToken = authenticateJWT(); } catch (PDOException $e) { - error_log($e->getMessage()); - http_response_code(500); // Internal Server Error + error_log('[WALLET] DB Error: ' . $e->getMessage()); + http_response_code(500); echo json_encode(['error' => 'A database error occurred.']); exit; } + ?> \ No newline at end of file diff --git a/payment_server/v2/main/ride/cliq/create_cliq_invoice.php b/payment_server/v2/main/ride/cliq/create_cliq_invoice.php index 6365bee8..7e075961 100755 --- a/payment_server/v2/main/ride/cliq/create_cliq_invoice.php +++ b/payment_server/v2/main/ride/cliq/create_cliq_invoice.php @@ -7,7 +7,7 @@ try { $userId = filterRequest("user_id"); $userType = filterRequest("user_type"); $amount = filterRequest("amount"); - $cliqPhone = filterRequest("cliq_phone"); + $cliqPhone = filterRequest("cliq_phone") ?: filterRequest("click_phone"); $phone = filterRequest("phone"); if (empty($userId) || empty($userType) || !is_numeric($amount) || $amount <= 0 || empty($cliqPhone)) { @@ -37,14 +37,12 @@ try { $upd = $con->prepare(" UPDATE cliq_invoices SET amount = :amount, - phone = :phone, cliq_phone = :cliq_phone, updated_at = NOW() WHERE id = :id "); $upd->execute([ ':amount' => $amount, - ':phone' => $phone ?: null, ':cliq_phone' => $cliqPhone, ':id' => $existing['id'], ]); @@ -62,15 +60,14 @@ try { $ins = $con->prepare(" INSERT INTO cliq_invoices - (invoice_number, user_id, user_type, phone, amount, cliq_phone, status, created_at, updated_at) + (invoice_number, user_id, user_type, amount, cliq_phone, status, created_at, updated_at) VALUES - (:invoice_number, :user_id, :user_type, :phone, :amount, :cliq_phone, 'pending', NOW(), NOW()) + (:invoice_number, :user_id, :user_type, :amount, :cliq_phone, 'pending', NOW(), NOW()) "); $ins->execute([ ':invoice_number' => $invoiceNumber, ':user_id' => $userId, ':user_type' => $userType, - ':phone' => $phone ?: null, ':amount' => $amount, ':cliq_phone' => $cliqPhone ]); diff --git a/payment_server/v2/main/ride/passengerWallet/getWalletByPassenger.php b/payment_server/v2/main/ride/passengerWallet/getWalletByPassenger.php index 0a256ea5..e81caf17 100755 --- a/payment_server/v2/main/ride/passengerWallet/getWalletByPassenger.php +++ b/payment_server/v2/main/ride/passengerWallet/getWalletByPassenger.php @@ -3,13 +3,13 @@ include "../../connect.php"; $passenger_id = filterRequest("passenger_id"); $sql = "SELECT - COALESCE(dummy.passenger_id, '$passenger_id') AS passenger_id, - COALESCE(SUM(pw.balance), 0) AS total, -- Adjust column to represent payments + COALESCE(dummy.passenger_id, :passenger_id) AS passenger_id, + COALESCE(SUM(pw.balance), 0) AS total, COALESCE(p.first_name, '') AS first_name, COALESCE(p.last_name, '') AS last_name, COALESCE(p.phone, '') AS phone FROM - (SELECT '$passenger_id' AS passenger_id) AS dummy + (SELECT :passenger_id2 AS passenger_id) AS dummy LEFT JOIN `passengerWallet` pw ON pw.passenger_id = dummy.passenger_id LEFT JOIN passengers p ON p.id = dummy.passenger_id GROUP BY @@ -17,17 +17,13 @@ GROUP BY LIMIT 0, 25; "; $stmt = $con->prepare($sql); -$stmt->execute(); +$stmt->execute(['passenger_id' => $passenger_id, 'passenger_id2' => $passenger_id]); -if ($stmt->rowCount() > 0) { - // Fetch the record - $row = $stmt->fetchAll(PDO::FETCH_ASSOC); - - printSuccess( $row); - -} - else{ - // Print a failure message - printFailure($message = "No wallet record found"); +// LEFT JOIN يُرجع دائماً صفاً واحداً على الأقل (balance=0 إذا لم توجد محفظة) +$row = $stmt->fetchAll(PDO::FETCH_ASSOC); +if (!empty($row)) { + printSuccess($row); +} else { + printFailure("No wallet record found"); } ?> \ No newline at end of file diff --git a/siro_rider/lib/controller/auth/otp_controller.dart b/siro_rider/lib/controller/auth/otp_controller.dart index 5b5a8753..0e33906c 100644 --- a/siro_rider/lib/controller/auth/otp_controller.dart +++ b/siro_rider/lib/controller/auth/otp_controller.dart @@ -97,6 +97,15 @@ class PhoneAuthHelper { // ✅ المستخدم موجود مسبقاً -> تسجيل دخول مباشر await _handleSuccessfulLogin(data['message']['passenger']); } else { + // ✅ Save the new JWT token for registration before navigating + final jwtToken = data['message']?['jwt'] ?? + data['message']?['token'] ?? + data['message']?['access_token']; + if (jwtToken != null && jwtToken.toString().isNotEmpty) { + box.write(BoxName.jwt, c(jwtToken.toString())); + await storage.write( + key: BoxName.jwt, value: c(jwtToken.toString())); + } // ✅ مستخدم جديد -> الذهاب لصفحة التسجيل mySnackbarSuccess( 'Phone verified. Please complete registration.'.tr); @@ -144,13 +153,16 @@ class PhoneAuthHelper { }, ); final data = (response); - if (data != 'failure') { - // Registration successful, log user in - - await _handleSuccessfulLogin(data['message']['data']); + if (data != 'failure' && data is Map) { + if (data['status'] == 'success') { + // Registration successful, log user in + await _handleSuccessfulLogin(data['message']['data']); + } else { + mySnackbarError( + data['message']?.toString() ?? "Registration failed.".tr); + } } else { - mySnackbarError( - "User with this phone number or email already exists.".tr); + mySnackbarError("Server error or user already exists.".tr); } } catch (e) { Log.print('e: ${e}'); diff --git a/siro_rider/lib/controller/payment/payment_controller.dart b/siro_rider/lib/controller/payment/payment_controller.dart index 0ad5a264..3f214c76 100644 --- a/siro_rider/lib/controller/payment/payment_controller.dart +++ b/siro_rider/lib/controller/payment/payment_controller.dart @@ -53,8 +53,13 @@ class PaymentController extends GetxController { await CRUD().getWallet( link: AppLink.getWalletByPassenger, payload: {'passenger_id': box.read(BoxName.passengerID)}).then((value) { - box.write(BoxName.passengerWalletTotal, - value['message'][0]['total'].toString()); + final msg = value['message']; + if (msg is List && msg.isNotEmpty) { + box.write(BoxName.passengerWalletTotal, + (msg[0]['total'] ?? '0').toString()); + } else { + box.write(BoxName.passengerWalletTotal, '0'); + } }); isLoading = false; update();