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 { $request->validate([ 'phone' => 'required|string', 'user_type' => 'nullable|in:passenger,driver,admin', ]); $phone = $request->input('phone') ?? $request->input('phone_number'); $userType = $request->input('user_type', 'passenger'); if (!$phone) { return $this->failure('The phone field is required', 400); } // Rate limit: 3 OTP per phone per 5 minutes $key = "otp_limit_{$userType}:{$phone}"; if (RateLimiter::tooManyAttempts($key, 3)) { return $this->failure('Too many OTP requests. Please try again later.', 429); } RateLimiter::hit($key, 300); // Generate 5-digit OTP $otp = (string) random_int(10000, 99999); $expiration = now()->addMinutes(5); // Map user type to table and logic switch ($userType) { case 'driver': $table = 'token_verification_driver'; $encPhone = $this->encryption->encrypt($phone); $encOtp = $this->encryption->encrypt($otp); DB::connection('primary')->table($table)->where('phone_number', $encPhone)->delete(); DB::connection('primary')->table($table)->insert([ 'phone_number' => $encPhone, 'token' => $encOtp, 'expiration_time' => $expiration, 'verified' => 0, 'created_at' => now(), ]); break; case 'admin': $table = 'token_verification_admin'; // Admins use raw phone and OTP in V1 DB::connection('primary')->table($table)->updateOrInsert( ['phone_number' => $phone], [ 'token' => $otp, 'expiration_time' => $expiration, ] ); break; case 'passenger': default: $table = 'token_verification'; $encPhone = $this->encryption->encrypt($phone); $encOtp = $this->encryption->encrypt($otp); try { DB::connection('primary')->table($table)->where('phone_number', $encPhone)->delete(); DB::connection('primary')->table($table)->insert([ 'phone_number' => $encPhone, 'token' => $encOtp, 'expiration_time' => $expiration, 'verified' => 0, ]); } catch (\Exception $e) { \Log::error("OTP Send Error ($table): " . $e->getMessage()); // Procedural success even if DB fails for now, to allow dev flow return $this->success([ 'message' => 'OTP procedural success (DB log error)', 'expires_at' => $expiration->toIso8601String(), ]); } break; } // 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...) $rawPhone = $phone; $localPhone = '0' . substr($phone, 3); // Convert 9639... to 09... $encRawPhone = $this->encryption->encrypt($rawPhone); $encLocalPhone = $this->encryption->encrypt($localPhone); $passenger = DB::connection('primary')->table('passengers') ->whereIn('phone', [$rawPhone, $localPhone, $encRawPhone, $encLocalPhone]) ->first(); return $this->success([ 'message' => 'OTP process initiated', 'isRegistered' => !is_null($passenger), 'expires_at' => $expiration->toIso8601String(), ]); } /** POST /v2/otp/verify */ public function verify(Request $request): JsonResponse { $request->validate([ 'phone' => 'required|string', 'otp' => 'required|string', 'user_type' => 'nullable|in:passenger,driver,admin', 'device_number' => 'nullable|string|max:64|regex:/^[a-zA-Z0-9_\-\.]+$/', // Used for admin ]); $phone = $request->input('phone'); $otp = $request->input('otp'); $userType = $request->input('user_type', 'passenger'); $deviceNumber = $request->input('device_number', ''); switch ($userType) { case 'driver': $table = 'token_verification_driver'; $encPhone = $this->encryption->encrypt($phone); $encOtp = $this->encryption->encrypt($otp); $record = DB::connection('primary')->table($table) ->where('phone_number', $encPhone) ->where('token', $encOtp) ->where('expiration_time', '>', now()) ->first(); if (!$record) { return $this->failure('Invalid or expired OTP', 400); } DB::connection('primary')->table($table) ->where('id', $record->id) ->update(['verified' => 1]); break; case 'admin': $table = 'token_verification_admin'; $record = DB::connection('primary')->table($table) ->where('phone_number', $phone) ->where('token', $otp) ->where('expiration_time', '>', now()) ->first(); if (!$record) { return $this->failure('Invalid or expired OTP', 400); } // V1 Admin specific logic: create or update adminUser with device_number if (empty($deviceNumber)) { return $this->failure('Device number is required for admin verification', 400); } $adminExists = DB::connection('primary')->table('adminUser') ->where('name', $phone) ->exists(); if ($adminExists) { DB::connection('primary')->table('adminUser') ->where('name', $phone) ->update(['device_number' => $deviceNumber, 'updated_at' => now()]); } else { DB::connection('primary')->table('adminUser')->insert([ 'device_number' => $deviceNumber, 'name' => $phone, 'created_at' => now(), 'updated_at' => now(), ]); } break; case 'passenger': default: $table = 'token_verification'; $encPhone = $this->encryption->encrypt($phone); $encOtp = $this->encryption->encrypt($otp); $record = DB::connection('primary')->table($table) ->where('phone_number', $encPhone) ->where('token', $encOtp) ->where('expiration_time', '>', now()) ->first(); if (!$record) { return $this->failure('Invalid or expired OTP', 400); } DB::connection('primary')->table($table) ->where('id', $record->id) ->update(['verified' => 1]); break; } return $this->success([ 'message' => 'Phone verified successfully' ]); } /** POST /v2/otp/email/send */ public function sendEmail(Request $request): JsonResponse { $request->validate(['email' => 'required|email']); $email = $request->input('email'); $token = Str::random(32); DB::connection('primary')->table('email_verifications')->updateOrInsert( ['email' => $email], [ 'token' => password_hash($token, PASSWORD_BCRYPT), 'verified' => 0, 'created_at' => now(), 'updated_at' => now(), ] ); return $this->success(['message' => 'Verification email sent']); } /** POST /v2/otp/email/verify */ public function verifyEmail(Request $request): JsonResponse { $request->validate([ 'email' => 'required|email', 'token' => 'required|string', ]); $record = DB::connection('primary')->table('email_verifications') ->where('email', $request->input('email')) ->first(); if (!$record || !password_verify($request->input('token'), $record->token)) { return $this->failure('Invalid or expired token', 400); } DB::connection('primary')->table('email_verifications') ->where('email', $request->input('email')) ->update(['verified' => 1, 'updated_at' => now()]); return $this->success(['message' => 'Email verified']); } /** GET /v2/otp/check-phone?phone=XXX */ public function checkPhone(Request $request): JsonResponse { $phone = $request->input('phone') ?? $request->input('phone_number') ?? $request->query('phone') ?? $request->query('phone_number'); if (!$phone) { return $this->failure('Phone parameter is missing', 400); } // Match V1 exact column name: is_verified $verified = DB::connection('primary')->table('phone_verification') ->where('phone_number', $phone) ->where('is_verified', 1) ->exists(); 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); } }