From fe5fa1feff42a77213c417300f4dbbe8093401a6 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 25 Apr 2026 11:42:40 +0300 Subject: [PATCH] driver api setting 1 --- app/Http/Controllers/AuthController.php | 397 ++++++++++++++++++++- app/Http/Controllers/OtpController.php | 80 ++++- app/Http/Controllers/ProfileController.php | 5 + app/Http/Controllers/WalletController.php | 63 +++- bootstrap/app.php | 50 ++- routes/api.php | 11 +- 6 files changed, 547 insertions(+), 59 deletions(-) diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 3d3175a..9a45297 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -146,6 +146,232 @@ class AuthController extends Controller ], 201); } + public function driverRegister(Request $request): JsonResponse + { + // 1. Validate Required Driver Fields + $request->validate([ + 'phone' => 'required|string', + 'password' => 'required|string', + 'first_name' => 'required|string', + 'last_name' => 'required|string', + // Car Fields + 'vin' => 'required|string', + 'car_plate' => 'required|string', + 'make' => 'required|string', + 'model' => 'required|string', + 'year' => 'required|string', + 'expiration_date' => 'required|string', + 'color' => 'required|string', + 'owner' => 'required|string', + 'color_hex' => 'required|string', + 'fuel' => 'required|string', + // Documents + 'driver_license_front' => 'required|url', + 'driver_license_back' => 'required|url', + 'car_license_front' => 'required|url', + 'car_license_back' => 'required|url', + ]); + + $data = $request->all(); + + // 2. Format Phone (Syrian Logic) + $phone = $data['phone']; + $phone = preg_replace('/[ \-\(\)\+]/', '', $phone); + $phone = trim($phone); + + if (strpos($phone, '00963') === 0) $phone = substr($phone, 2); + elseif (strpos($phone, '0963') === 0) $phone = substr($phone, 1); + + if (strpos($phone, '96309') === 0) $phone = '9639' . substr($phone, 5); + elseif (strpos($phone, '9630') === 0) $phone = '9639' . substr($phone, 4); + elseif (strpos($phone, '09') === 0) $phone = '963' . substr($phone, 1); + elseif (strpos($phone, '9') === 0 && strlen($phone) == 9) $phone = '963' . $phone; + elseif (strpos($phone, '0') === 0 && strlen($phone) == 10) $phone = '963' . substr($phone, 1); + + if (strpos($phone, '963') === 0 && strlen($phone) > 3) { + if (strpos($phone, '9639') !== 0) { + $phone = '9639' . substr($phone, 3); + } + } + $data['phone'] = $phone; + + // 3. Format Birthdate + if (!empty($data['birthdate'])) { + $data['birthdate'] = trim($data['birthdate']) . '-01-01'; + } else { + $data['birthdate'] = '1970-01-01'; + } + + // 4. Generate ID & Email + if (empty($data['id'])) { + $data['id'] = 'DRV' . date('YmdHis') . random_int(1000, 9999); + } + if (empty($data['email'])) { + $data['email'] = $data['phone'] . '@intaleqapp.com'; + } + + // 5. Encrypt Driver Fields + $toEncryptDriver = [ + 'phone', 'email', 'first_name', 'last_name', 'name_arabic', 'gender', + 'national_number', 'address', 'site', 'fullNameMaritial', 'birthdate' + ]; + foreach ($toEncryptDriver as $f) { + if (!empty($data[$f]) && $data[$f] !== 'Not specified') { + $data[$f] = $this->encryption->encrypt($data[$f]); + } else { + $data[$f] = null; + } + } + + // 6. Encrypt Car Fields + $car = [ + 'vin' => $this->encryption->encrypt($request->input('vin')), + 'car_plate' => $this->encryption->encrypt($request->input('car_plate')), + 'make' => $request->input('make'), + 'model' => $request->input('model'), + 'year' => $request->input('year'), + 'expiration_date' => $request->input('expiration_date'), + 'color' => $request->input('color'), + 'owner' => $this->encryption->encrypt($request->input('owner')), + 'color_hex' => $request->input('color_hex'), + 'fuel' => $request->input('fuel'), + 'vehicle_category_id' => $request->input('vehicle_category_id', 1), + 'fuel_type_id' => $request->input('fuel_type_id', 1), + ]; + + // 7. Hash Password (HMAC + password_hash) + $pepper = config('intaleq.hmac_secret', env('SECRET_KEY_HMAC')); + $baseParts = [ + $data['id'], + $phone, // Original formatted phone + ]; + if (!empty($request->input('national_number')) && $request->input('national_number') !== 'Not specified') { + $baseParts[] = $request->input('national_number'); + } elseif (!empty($request->input('birthdate'))) { + $year = substr($request->input('birthdate'), 0, 4); + if (preg_match('/^\d{4}$/', $year)) { + $baseParts[] = $year; + } + } + $baseString = implode('|', $baseParts); + $rawSecret = hash_hmac('sha256', $baseString, $pepper, true); + $pwdHashed = password_hash($rawSecret, PASSWORD_DEFAULT); + + // 8. Check Duplicates + $dup = DB::connection('primary')->table('driver') + ->where('phone', $data['phone']) + ->orWhere('email', $data['email']) + ->exists(); + + if ($dup) { + return response()->json(['status' => 'Failure', 'data' => 'Phone or email already registered.']); + } + + DB::connection('primary')->beginTransaction(); + try { + // 9. Insert Driver + DB::connection('primary')->table('driver')->insert([ + 'id' => $data['id'], + 'phone' => $data['phone'], + 'email' => $data['email'], + 'password' => $pwdHashed, + 'gender' => $data['gender'] ?? 'Male', + 'license_type' => $request->input('license_type', 'yet'), + 'national_number' => $data['national_number'], + 'name_arabic' => $data['name_arabic'], + 'issue_date' => $request->input('issue_date', '2020-01-01'), + 'expiry_date' => $request->input('expiry_date', 'yet'), + 'license_categories' => $request->input('license_categories', 'B'), + 'address' => $data['address'], + 'licenseIssueDate' => $request->input('licenseIssueDate', '2020-01-01'), + 'status' => $request->input('status', 'yet'), + 'birthdate' => $data['birthdate'], + 'site' => $data['site'] ?? 'demascus', + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'accountBank' => 'yet', + 'bankCode' => 'yet', + 'employmentType' => $request->input('employmentType', 'yet'), + 'maritalStatus' => $request->input('maritalStatus', 'yet'), + 'fullNameMaritial' => $data['fullNameMaritial'] ?? 'yet', + 'expirationDate' => $request->input('expirationDate', 'yet'), + 'created_at' => now(), + 'updated_at' => now(), + ]); + + // 10. Insert Car Registration + $isDefault = DB::connection('primary')->table('CarRegistration') + ->where('driverID', $data['id'])->exists() ? 0 : 1; + + $carRegId = DB::connection('primary')->table('CarRegistration')->insertGetId([ + 'driverID' => $data['id'], + 'vin' => $car['vin'], + 'car_plate' => $car['car_plate'], + 'make' => $car['make'], + 'model' => $car['model'], + 'year' => $car['year'], + 'expiration_date' => $car['expiration_date'], + 'color' => $car['color'], + 'owner' => $car['owner'], + 'color_hex' => $car['color_hex'], + 'fuel' => $car['fuel'], + 'vehicle_category_id' => $car['vehicle_category_id'], + 'fuel_type_id' => $car['fuel_type_id'], + 'isDefault' => $isDefault, + 'status' => 'yet', + 'created_at' => now(), + ]); + + // 11. Insert Documents + $docKeys = [ + 'driver_license_front', 'driver_license_back', + 'car_license_front', 'car_license_back' + ]; + $docUrls = []; + foreach ($docKeys as $k) { + $url = $request->input($k); + $docUrls[$k] = $url; + $name = basename(parse_url($url, PHP_URL_PATH) ?? ''); + if ($name === '') { $name = $k . '_' . time() . '.jpg'; } + + DB::connection('primary')->table('driver_documents')->insert([ + 'driverID' => $data['id'], + 'doc_type' => $k, + 'image_name' => $name, + 'link' => $url, + 'upload_date' => now(), + ]); + } + + DB::connection('primary')->commit(); + + // 12. FCM Notification to Admin (Fire and Forget) + try { + $fcm = resolve(\App\Services\FcmService::class); + $driverFullName = $request->input('first_name') . ' ' . $request->input('last_name'); + $fcm->sendToTopic( + 'service', + 'تسجيل سائق جديد', + "سائق جديد ($driverFullName) سجل برقم ID: {$data['id']} وهو بانتظار المراجعة والتفعيل.", + ['category' => 'new_driver_registration'] + ); + } catch (\Exception $e) { + \Illuminate\Support\Facades\Log::error("FCM Admin notification failed: " . $e->getMessage()); + } + + return response()->json([ + 'status' => 'success', + 'driverID' => $data['id'], + 'carRegID' => $carRegId, + 'documents' => $docUrls + ]); + + } catch (\Exception $e) { + DB::connection('primary')->rollBack(); + return response()->json(['status' => 'Failure', 'message' => 'Registration failed: ' . $e->getMessage()]); + } + } + // ══════════════════════════════════════════════ // DRIVER LOGIN // ══════════════════════════════════════════════ @@ -153,28 +379,126 @@ class AuthController extends Controller public function driverLogin(Request $request): JsonResponse { $request->validate([ - 'phone' => 'required|string', + 'email' => 'required|string', 'password' => 'required|string', - 'fingerprint' => 'required|string', - 'fcm_token' => 'required|string', ]); - $encryptedPhone = $this->encryption->encrypt($request->input('phone')); - $driver = Driver::active()->where('phone', $encryptedPhone)->first(); + $email = $request->input('email'); + $searchEmail = (str_contains($email, '+') || str_contains($email, '/')) ? $email : $this->encryption->encrypt($email); - if (!$driver || (!password_verify($request->input('password'), $driver->password) && - !hash_equals($driver->password, hash_hmac('sha256', $request->input('password'), config('intaleq.jwt_secret'))))) { - return $this->failure('Invalid credentials'); + $row = DB::connection('primary') + ->table('driver as d') + ->leftJoin('phone_verification', 'phone_verification.phone_number', '=', 'd.phone') + ->leftJoin('invites', 'invites.inviterDriverPhone', '=', 'd.phone') + ->leftJoin('shamCash', 'shamCash.driver_id', '=', 'd.id') + ->leftJoin('CarRegistration', 'CarRegistration.driverID', '=', 'd.id') + ->leftJoin('captain_bank', 'captain_bank.captain_id', '=', 'd.id') + ->select([ + 'd.id', 'd.phone', 'd.email', 'd.gender', 'd.status', + 'd.first_name', 'd.last_name', 'd.password', + 'd.name_arabic', + 'phone_verification.verified as is_verified', + 'invites.isInstall', + 'shamCash.is_claimed', + 'CarRegistration.make', 'CarRegistration.model', 'CarRegistration.year', + 'captain_bank.bankCode', 'captain_bank.accountBank', + ]) + ->where('d.email', $searchEmail) + ->first(); + + if (!$row) { + return response()->json(['status' => 'failure', 'message' => 'Invalid credentials']); } - $jwt = $this->createJwt($driver->id, 'driver', $request->input('fingerprint'), 14400); // 4 Hours + $driver = (array) $row; + + // Verify password + if (!password_verify($request->input('password'), $driver['password'])) { + return response()->json(['status' => 'failure', 'message' => 'Invalid credentials']); + } - return $this->success([ - 'token' => $jwt, - 'expires_in' => 14400, - 'user_id' => $driver->id, - 'api_key' => $driver->api_key, - 'api_secret' => $driver->api_secret, + // Decrypt necessary fields + $fieldsToDecrypt = ['email', 'phone', 'first_name', 'last_name', 'gender', 'name_arabic']; + foreach ($fieldsToDecrypt as $field) { + if (!empty($driver[$field])) { + $dec = $this->encryption->decrypt($driver[$field]); + if ($dec) $driver[$field] = $dec; + } + } + unset($driver['password']); // don't send password + + // Default values for null fields + $driver['is_verified'] = $driver['is_verified'] ?? 0; + $driver['isInstall'] = $driver['isInstall'] ?? 0; + $driver['is_claimed'] = $driver['is_claimed'] ?? 0; + $driver['bankCode'] = $driver['bankCode'] ?? ''; + $driver['accountBank'] = $driver['accountBank'] ?? ''; + $driver['make'] = $driver['make'] ?? ''; + $driver['model'] = $driver['model'] ?? ''; + $driver['year'] = $driver['year'] ?? ''; + + return response()->json([ + 'status' => 'success', + 'data' => [$driver] + ]); + } + + + + public function driverLoginGoogle(Request $request): JsonResponse + { + $request->validate(['id' => 'required|string']); + $id = $request->input('id'); + + $row = DB::connection('primary') + ->table('driver as d') + ->leftJoin('phone_verification', 'phone_verification.phone_number', '=', 'd.phone') + ->leftJoin('invites', 'invites.inviterDriverPhone', '=', 'd.phone') + ->leftJoin('shamCash', 'shamCash.driver_id', '=', 'd.id') + ->leftJoin('CarRegistration', 'CarRegistration.driverID', '=', 'd.id') + ->leftJoin('captain_bank', 'captain_bank.captain_id', '=', 'd.id') + ->select([ + 'd.id', 'd.phone', 'd.email', 'd.gender', 'd.status', + 'd.first_name', 'd.last_name', + 'd.name_arabic', + 'phone_verification.verified as is_verified', + 'invites.isInstall', + 'shamCash.is_claimed', + 'CarRegistration.make', 'CarRegistration.model', 'CarRegistration.year', + 'captain_bank.bankCode', 'captain_bank.accountBank', + ]) + ->where('d.id', $id) + ->first(); + + if (!$row) { + return response()->json(['status' => 'Failure', 'data' => 'User does not exist.']); + } + + $driver = (array) $row; + + // Decrypt necessary fields + $fieldsToDecrypt = ['email', 'phone', 'first_name', 'last_name', 'gender', 'name_arabic']; + foreach ($fieldsToDecrypt as $field) { + if (!empty($driver[$field])) { + $dec = $this->encryption->decrypt($driver[$field]); + if ($dec) $driver[$field] = $dec; + } + } + + // Default values for null fields + $driver['is_verified'] = $driver['is_verified'] ?? 0; + $driver['isInstall'] = $driver['isInstall'] ?? 0; + $driver['is_claimed'] = $driver['is_claimed'] ?? 0; + $driver['bankCode'] = $driver['bankCode'] ?? ''; + $driver['accountBank'] = $driver['accountBank'] ?? ''; + $driver['make'] = $driver['make'] ?? ''; + $driver['model'] = $driver['model'] ?? ''; + $driver['year'] = $driver['year'] ?? ''; + + return response()->json([ + 'status' => 'success', + 'count' => 1, + 'data' => [$driver] ]); } @@ -214,18 +538,53 @@ class AuthController extends Controller public function driverJwtHandshake(Request $request): JsonResponse { - $request->validate(['id' => 'required|string', 'fingerPrint' => 'required|string']); + // Flutter sends the email in the 'password' field, plus 'id' and 'fingerPrint' + $request->validate([ + 'id' => 'required|string', + 'fingerPrint' => 'required|string', + 'password' => 'required|string', + 'fcm_token' => 'required|string' + ]); - $driver = Driver::find($request->input('id')); + $driver = Driver::where('id', $request->input('id'))->first(); if (!$driver) return $this->failure('User not found'); + // Verify the email (sent as password from Flutter) matches + $decryptedEmail = $this->encryption->decrypt($driver->email); + if (!$decryptedEmail) { + // Fallback if decryption fails (e.g. invalid IV) + if ($driver->email !== $request->input('password')) { + return $this->failure('Security mismatch: Invalid email verification (Decryption Failed)', 403); + } + } elseif ($decryptedEmail !== $request->input('password') && $driver->email !== $request->input('password')) { + return $this->failure('Security mismatch: Invalid email verification', 403); + } + // Security Check: Verify fingerprint matches stored token $storedToken = DB::connection('primary')->table('captainToken') ->where('captain_id', $driver->id) ->first(); - if ($storedToken && !hash_equals((string)$storedToken->fingerPrint, (string)$request->input('fingerPrint'))) { - return $this->failure('Security mismatch: Invalid device fingerprint', 403); + // If a fingerprint exists, verify it. Otherwise, save the new one (acts as first time) + if ($storedToken) { + if (!empty($storedToken->fingerPrint) && !hash_equals((string)$storedToken->fingerPrint, (string)$request->input('fingerPrint'))) { + return $this->failure('Security mismatch: Invalid device fingerprint', 403); + } + // Update fingerprint and token + DB::connection('primary')->table('captainToken')->where('captain_id', $driver->id) + ->update([ + 'fingerPrint' => $request->input('fingerPrint'), + 'token' => $request->input('fcm_token'), + 'updated_at' => now() + ]); + } else { + DB::connection('primary')->table('captainToken')->insert([ + 'captain_id' => $driver->id, + 'fingerPrint' => $request->input('fingerPrint'), + 'token' => $request->input('fcm_token'), + 'created_at' => now(), + 'updated_at' => now() + ]); } if (empty($driver->api_key)) { diff --git a/app/Http/Controllers/OtpController.php b/app/Http/Controllers/OtpController.php index bac3fe5..56d1106 100644 --- a/app/Http/Controllers/OtpController.php +++ b/app/Http/Controllers/OtpController.php @@ -25,6 +25,20 @@ class OtpController extends Controller $this->encryption = $encryption; } + /** POST /v2/otp/driver/send */ + public function sendDriver(Request $request): JsonResponse + { + $request->merge(['user_type' => 'driver']); + return $this->send($request); + } + + /** POST /v2/otp/driver/verify */ + public function verifyDriver(Request $request): JsonResponse + { + $request->merge(['user_type' => 'driver']); + return $this->verify($request); + } + /** POST /v2/otp/send */ public function send(Request $request): JsonResponse { @@ -105,7 +119,9 @@ class OtpController extends Controller break; } - // TODO: Send SMS/WhatsApp via external provider + // Send WhatsApp message (especially for drivers changing devices) + // $message = "Your Intaleq App verification code is: $otp"; + // $this->sendWhatsAppFromServer($phone, $message); // Check if passenger exists to allow immediate login (V1 style) // We check both encrypted and raw phone with multiple formats (963... and 0...) @@ -272,16 +288,72 @@ class OtpController extends Controller /** GET /v2/otp/check-phone?phone=XXX */ public function checkPhone(Request $request): JsonResponse { - $request->validate(['phone' => 'required|string']); + $phone = $request->input('phone') ?? $request->query('phone'); + + if (!$phone) { + return $this->failure('Phone parameter is missing', 400); + } + // We check phone_verification table (Legacy V1 style) $verified = DB::connection('primary')->table('phone_verification') - ->where('phone_number', $request->input('phone')) - ->where('is_verified', 1) + ->where('phone_number', $phone) + ->where('verified', 1) // In V1 it might be 'verified' or 'is_verified' ->exists(); + // Fallback for column name 'is_verified' if 'verified' fails + if (!$verified) { + try { + $verified = DB::connection('primary')->table('phone_verification') + ->where('phone_number', $phone) + ->where('is_verified', 1) + ->exists(); + } catch (\Exception $e) {} + } + return response()->json([ 'status' => 'success', 'data' => ['verified' => $verified], ]); } + + /** + * Send WhatsApp message using the available bot servers (Ported from V1 functions.php) + */ + private function sendWhatsAppFromServer($to, $message) + { + $servers = [ + "https://bot5.intaleq.xyz/send", // ramat bus + "https://bot3.intaleq.xyz/send", // shahd + ]; + + $url = $servers[array_rand($servers)]; + + $payload = [ + "to" => $to, + "message" => $message + ]; + + $curl = curl_init(); + curl_setopt_array($curl, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => "POST", + CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE), + CURLOPT_HTTPHEADER => [ + "Content-Type: application/json" + ], + CURLOPT_TIMEOUT => 5 // Don't block API for too long + ]); + + $response = curl_exec($curl); + $err = curl_error($curl); + curl_close($curl); + + if ($err) { + \Log::error("[sendWhatsAppFromServer] cURL Error on $url: $err"); + return false; + } + + return json_decode($response, true); + } } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 47518fa..5f7efea 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -89,6 +89,11 @@ class ProfileController extends Controller // Rating $data['rating'] = $driver->getAverageRating(); + // Wallet Balance + $wallet = DB::connection('primary')->table('captain_wallet') + ->where('captain_id', $id)->first(); + $data['wallet_balance'] = $wallet->balance ?? '0.00'; + // Ride count $data['ride_count'] = DB::connection('ride')->table('ride') ->where('driver_id', $id)->where('status', 'finish')->count(); diff --git a/app/Http/Controllers/WalletController.php b/app/Http/Controllers/WalletController.php index 5fc176c..ad44b93 100644 --- a/app/Http/Controllers/WalletController.php +++ b/app/Http/Controllers/WalletController.php @@ -39,8 +39,25 @@ class WalletController extends Controller public function balance(Request $request): JsonResponse { $id = $request->attributes->get('_jwt_user_id'); - $bal = DB::connection('primary')->table('passengerWallet') - ->where('passenger_id', $id)->value('balance') ?? '0.00'; + $userType = $request->attributes->get('_jwt_user_type'); + + if ($userType === 'driver') { + $bal = DB::connection('primary')->table('captain_wallet') + ->where('captain_id', $id)->value('balance') ?? '0.00'; + } else { + $bal = DB::connection('primary')->table('passengerWallet') + ->where('passenger_id', $id)->value('balance') ?? '0.00'; + } + + return response()->json(['status' => 'success', 'data' => ['balance' => $bal]]); + } + + /** GET /v2/wallet/driver/balance (Explicit) */ + public function driverBalance(Request $request): JsonResponse + { + $id = $request->attributes->get('_jwt_user_id'); + $bal = DB::connection('primary')->table('captain_wallet') + ->where('captain_id', $id)->value('balance') ?? '0.00'; return response()->json(['status' => 'success', 'data' => ['balance' => $bal]]); } @@ -54,6 +71,11 @@ class WalletController extends Controller ]); $id = $request->attributes->get('_jwt_user_id'); + $userType = $request->attributes->get('_jwt_user_type'); + + if ($userType !== 'passenger') { + return response()->json(['status' => 'failure', 'message' => 'Only passengers can add funds manually'], 403); + } DB::connection('primary')->beginTransaction(); try { @@ -106,33 +128,46 @@ class WalletController extends Controller $request->validate([ 'balance' => 'required|numeric|min:0', - 'passenger_id' => 'required|string', + 'user_id' => 'required|string', + 'type' => 'required|in:passenger,driver', ]); - DB::connection('primary')->table('passengerWallet') - ->where('passenger_id', $request->input('passenger_id')) + $table = $request->input('type') === 'driver' ? 'captain_wallet' : 'passengerWallet'; + $key = $request->input('type') === 'driver' ? 'captain_id' : 'passenger_id'; + + DB::connection('primary')->table($table) + ->where($key, $request->input('user_id')) ->update(['balance' => $request->input('balance')]); return response()->json(['status' => 'success']); } - /** GET /v2/wallet/passenger/transactions */ + /** GET /v2/wallet/transactions */ public function transactions(Request $request): JsonResponse { $id = $request->attributes->get('_jwt_user_id'); + $userType = $request->attributes->get('_jwt_user_type'); $page = (int) $request->input('page', 1); $limit = min((int) $request->input('limit', 20), 50); - // Get from payments table (completed rides) - $payments = DB::connection('primary')->table('payments') - ->where('passengerID', $id) - ->orderBy('created_at', 'desc') - ->skip(($page - 1) * $limit) - ->take($limit) - ->get(); + if ($userType === 'driver') { + $transactions = DB::connection('primary')->table('payments') + ->where('driverID', $id) + ->orderBy('created_at', 'desc') + ->skip(($page - 1) * $limit) + ->take($limit) + ->get(); + } else { + $transactions = DB::connection('primary')->table('payments') + ->where('passengerID', $id) + ->orderBy('created_at', 'desc') + ->skip(($page - 1) * $limit) + ->take($limit) + ->get(); + } - return response()->json(['status' => 'success', 'data' => $payments]); + return response()->json(['status' => 'success', 'data' => $transactions]); } /** POST /v2/wallet/passenger/token */ diff --git a/bootstrap/app.php b/bootstrap/app.php index cf7c58c..4ead7c1 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -24,27 +24,39 @@ return Application::configure(basePath: dirname(__DIR__)) ]); }) ->withExceptions(function (Exceptions $exceptions) { - $exceptions->render(function (\Throwable $e) { - \Log::error('Unhandled Exception', [ - 'message' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'trace' => $e->getTraceAsString(), - ]); - - if (config('app.debug')) { - return response()->json([ - 'status' => 'failure', - 'message' => 'DEBUG ERROR: ' . $e->getMessage(), + $exceptions->render(function (\Throwable $e, \Illuminate\Http\Request $request) { + if ($request->is('api/*')) { + \Log::error('API Exception: ' . get_class($e), [ + 'message' => $e->getMessage(), 'file' => $e->getFile(), - 'line' => $e->getLine() - ], 500); - } + 'line' => $e->getLine(), + ]); - return response()->json([ - 'status' => 'failure', - 'message' => 'Internal server error', - ], 500); + $status = 500; + if ($e instanceof \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface) { + $status = $e->getStatusCode(); + } elseif ($e instanceof \Illuminate\Validation\ValidationException) { + return response()->json([ + 'status' => 'failure', + 'message' => 'Validation error', + 'errors' => $e->errors(), + ], 422); + } + + $response = [ + 'status' => 'failure', + 'message' => $e->getMessage() ?: 'Internal server error', + ]; + + if (config('app.debug')) { + $response['exception'] = get_class($e); + $response['file'] = $e->getFile(); + $response['line'] = $e->getLine(); + $response['trace'] = $e->getTrace(); + } + + return response()->json($response, $status); + } }); }) ->create() diff --git a/routes/api.php b/routes/api.php index fb11f8a..3b97f72 100644 --- a/routes/api.php +++ b/routes/api.php @@ -53,10 +53,10 @@ Route::prefix('v2/auth')->group(function () { Route::get('/passenger/login-google', [AuthController::class, 'passengerLoginGoogle']); // Driver - Route::post('/driver/login', [AuthController::class, 'driverLogin']); + Route::match(['get', 'post'], '/driver/login', [AuthController::class, 'driverLogin']); Route::post('/driver/register', [AuthController::class, 'driverRegister']); Route::post('/driver/wallet-login', [AuthController::class, 'driverWalletLogin']); - Route::get('/driver/login-google', [AuthController::class, 'driverLoginGoogle']); + Route::match(['get', 'post'], '/driver/login-google', [AuthController::class, 'driverLoginGoogle']); // Admin & Service Route::post('/admin/login', [AuthController::class, 'adminLogin']); @@ -73,9 +73,14 @@ Route::post('v2/admin/errors', [MiscController::class, 'logClientError']); Route::prefix('v2/otp')->middleware('throttle:10,1')->group(function () { Route::post('/send', [OtpController::class, 'send']); Route::post('/verify', [OtpController::class, 'verify']); + + // Dedicated Driver OTP Endpoints (matches V1 sendOtpMessageDriver.php) + Route::post('/driver/send', [OtpController::class, 'sendDriver']); + Route::post('/driver/verify', [OtpController::class, 'verifyDriver']); + Route::post('/email/send', [OtpController::class, 'sendEmail']); Route::post('/email/verify', [OtpController::class, 'verifyEmail']); - Route::get('/check-phone', [OtpController::class, 'checkPhone']); + Route::match(['get', 'post'], '/check-phone', [OtpController::class, 'checkPhone']); }); // ══════════════════════════════════════════════