From aa29baab241dd7f65b46614ad69260d2f5d61348 Mon Sep 17 00:00:00 2001 From: bayughazali Date: Mon, 25 May 2026 18:48:33 +0700 Subject: [PATCH] update lupa pw --- .../AdminForgotPasswordController.php | 218 +++++++++++----- .../Auth/ForgotPasswordController.php | 224 ++++++++++------ .../Controllers/Auth/RegisterController.php | 63 ++++- app/Services/FonnteService.php | 66 ++++- config/services.php | 4 + .../views/admin/forgot-password.blade.php | 187 +++++++++---- .../views/auth/forgot-password.blade.php | 245 +++++++++++------- resources/views/auth/verify-otp.blade.php | 83 ++++-- 8 files changed, 758 insertions(+), 332 deletions(-) diff --git a/app/Http/Controllers/AdminForgotPasswordController.php b/app/Http/Controllers/AdminForgotPasswordController.php index 05c9ec9..af41dd5 100644 --- a/app/Http/Controllers/AdminForgotPasswordController.php +++ b/app/Http/Controllers/AdminForgotPasswordController.php @@ -10,62 +10,141 @@ use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; use Carbon\Carbon; +use App\Services\FonnteService; class AdminForgotPasswordController extends Controller { // ───────────────────────────────────────────────────────── // STEP 1 — Tampilkan form input email // ───────────────────────────────────────────────────────── + +protected FonnteService $fonnte; + +public function __construct(FonnteService $fonnte) +{ + $this->fonnte = $fonnte; +} + public function showForgotForm() { + return view('admin.forgot-password'); } // ───────────────────────────────────────────────────────── // STEP 1 — Proses: validasi email & kirim OTP // ───────────────────────────────────────────────────────── - public function sendOtp(Request $request) - { - $request->validate([ - 'email' => ['required', 'email'], - ], [ - 'email.required' => 'Email wajib diisi.', - 'email.email' => 'Format email tidak valid.', - ]); + public function sendOtp(Request $request) +{ + $method = $request->input('method', 'email'); - $admin = User::where('email', $request->email) - ->where('is_admin', true) - ->first(); - - if (!$admin) { - return back()->withErrors([ - 'email' => 'Email tidak terdaftar sebagai akun admin.', - ])->withInput(); - } - - $otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); - - DB::table('password_reset_tokens')->where('email', $request->email)->delete(); - DB::table('password_reset_tokens')->insert([ - 'email' => $request->email, - 'token' => Hash::make($otp), - 'created_at' => Carbon::now(), - ]); - - Mail::send('emails.admin-otp', [ - 'otp' => $otp, - 'name' => $admin->name ?? 'Admin', - ], function ($message) use ($request) { - $message->to($request->email) - ->subject('Kode Verifikasi Reset Password Admin - Bibit Cabai'); - }); - - $request->session()->put('admin_reset_email', $request->email); - - return redirect()->route('admin.password.verify-otp-form') - ->with('success', 'Kode OTP telah dikirim ke email admin Anda. Berlaku 10 menit.'); + if ($method === 'whatsapp') { + return $this->sendOtpViaWhatsapp($request); } + return $this->sendOtpViaEmail($request); +} + +private function sendOtpViaEmail(Request $request) +{ + $request->validate([ + 'email' => ['required', 'email'], + ], [ + 'email.required' => 'Email wajib diisi.', + 'email.email' => 'Format email tidak valid.', + ]); + + $admin = User::where('email', $request->email) + ->where('is_admin', true) + ->first(); + + if (!$admin) { + return back()->withErrors([ + 'email' => 'Email tidak terdaftar sebagai akun admin.', + ])->withInput(); + } + + $otp = $this->generateAndStoreOtp($request->email); + + Mail::send('emails.admin-otp', [ + 'otp' => $otp, + 'name' => $admin->name ?? 'Admin', + ], function ($message) use ($request) { + $message->to($request->email) + ->subject('Kode Verifikasi Reset Password Admin - Bibit Cabai'); + }); + + $request->session()->put('admin_reset_email', $request->email); + $request->session()->put('admin_reset_method', 'email'); + + return redirect()->route('admin.password.verify-otp-form') + ->with('success', 'Kode OTP telah dikirim ke email admin Anda. Berlaku 3 menit.'); +} + +private function sendOtpViaWhatsapp(Request $request) +{ + $request->validate([ + 'phone' => ['required', 'digits_between:10,15'], + ], [ + 'phone.required' => 'Nomor WhatsApp wajib diisi.', + 'phone.digits_between' => 'Nomor WhatsApp tidak valid (10-15 digit).', + ]); + + $phoneFormatted = FonnteService::formatPhone($request->phone); + + // Cari user dengan nomor WA ini — WAJIB is_admin = true + $admin = User::where('is_admin', true) + ->where(function ($q) use ($phoneFormatted, $request) { + $q->where('phone', $phoneFormatted) + ->orWhere('phone', '+' . $phoneFormatted) + ->orWhere('phone', $request->phone) + ->orWhere('phone', '0' . ltrim($request->phone, '0')); + }) + ->first(); + + if (!$admin) { + return back()->withErrors([ + 'phone' => 'Nomor ini tidak terdaftar sebagai admin.', + ])->withInput(); + } + + $otp = $this->generateAndStoreOtp($admin->email); + + $pesan = "🛡️ *Bibit Cabai Bondowoso — Admin*\n\n" + . "Halo {$admin->name},\n\n" + . "Kode verifikasi reset password admin Anda:\n\n" + . "*{$otp}*\n\n" + . "Berlaku selama *3 menit*.\n" + . "Jangan berikan kode ini kepada siapapun."; + + $sent = $this->fonnte->sendMessage($phoneFormatted, $pesan); + + if (!$sent) { + return back()->with('error', 'Gagal mengirim pesan WhatsApp. Coba lagi.'); + } + + $request->session()->put('admin_reset_email', $admin->email); + $request->session()->put('admin_reset_method', 'whatsapp'); + $request->session()->put('admin_reset_phone_display', '****' . substr($request->phone, -4)); + + return redirect()->route('admin.password.verify-otp-form') + ->with('success', 'Kode OTP dikirim ke WhatsApp admin Anda. Berlaku 3 menit.'); +} + +private function generateAndStoreOtp(string $email): string +{ + $otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); + + DB::table('password_reset_tokens')->where('email', $email)->delete(); + DB::table('password_reset_tokens')->insert([ + 'email' => $email, + 'token' => Hash::make($otp), + 'created_at' => Carbon::now(), + ]); + + return $otp; +} + // ───────────────────────────────────────────────────────── // STEP 2 — Tampilkan form input OTP // ───────────────────────────────────────────────────────── @@ -131,33 +210,39 @@ public function verifyOtp(Request $request) // ───────────────────────────────────────────────────────── // STEP 2 — Kirim ulang OTP // ───────────────────────────────────────────────────────── - public function resendOtp(Request $request) - { - $email = $request->session()->get('admin_reset_email'); + public function resendOtp(Request $request) +{ + $email = $request->session()->get('admin_reset_email'); + $method = $request->session()->get('admin_reset_method', 'email'); - if (!$email) { - return redirect()->route('admin.password.request') - ->with('error', 'Sesi tidak valid. Silakan masukkan email admin kembali.'); + if (!$email) { + return redirect()->route('admin.password.request') + ->with('error', 'Sesi tidak valid.'); + } + + $admin = User::where('email', $email)->where('is_admin', true)->first(); + if (!$admin) { + return redirect()->route('admin.password.request') + ->with('error', 'Akun admin tidak ditemukan.'); + } + + $otp = $this->generateAndStoreOtp($email); + + if ($method === 'whatsapp') { + $phone = FonnteService::formatPhone( + preg_replace('/^\+/', '', $admin->phone) + ); + $pesan = "🛡️ *Bibit Cabai Bondowoso — Admin*\n\n" + . "Halo {$admin->name},\n\n" + . "Kode verifikasi baru Anda:\n\n" + . "*{$otp}*\n\n" + . "Berlaku selama *3 menit*."; + + $sent = $this->fonnte->sendMessage($phone, $pesan); + if (!$sent) { + return back()->with('error', 'Gagal mengirim ulang ke WhatsApp.'); } - - $admin = User::where('email', $email) - ->where('is_admin', true) - ->first(); - - if (!$admin) { - return redirect()->route('admin.password.request') - ->with('error', 'Email tidak ditemukan.'); - } - - $otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); - - DB::table('password_reset_tokens')->where('email', $email)->delete(); - DB::table('password_reset_tokens')->insert([ - 'email' => $email, - 'token' => Hash::make($otp), - 'created_at' => Carbon::now(), - ]); - + } else { Mail::send('emails.admin-otp', [ 'otp' => $otp, 'name' => $admin->name ?? 'Admin', @@ -165,10 +250,11 @@ public function resendOtp(Request $request) $message->to($email) ->subject('Kode OTP Baru (Kirim Ulang) - Bibit Cabai Admin'); }); - - return back()->with('success', 'Kode OTP baru telah dikirim ke email admin Anda.'); } + return back()->with('success', 'Kode OTP baru telah dikirim.'); +} + // ───────────────────────────────────────────────────────── // STEP 3 — Tampilkan form password baru // ───────────────────────────────────────────────────────── diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 09174c5..7c37578 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Models\User; +use App\Services\FonnteService; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; @@ -13,18 +14,37 @@ class ForgotPasswordController extends Controller { - // ───────────────────────────────────────────── - // STEP 1 — Show form: enter email - // ───────────────────────────────────────────── + protected FonnteService $fonnte; + + public function __construct(FonnteService $fonnte) + { + $this->fonnte = $fonnte; + } + + // ────────────────────────────────────────────── + // STEP 1 — Show form: pilih metode (email / WA) + // ────────────────────────────────────────────── public function showForgotForm() { return view('auth.forgot-password'); } - // ───────────────────────────────────────────── - // STEP 1 — Handle: send OTP to email - // ───────────────────────────────────────────── + // ────────────────────────────────────────────── + // STEP 1 — Handle: kirim OTP (email ATAU WA) + // ────────────────────────────────────────────── public function sendOtp(Request $request) + { + $method = $request->input('method', 'email'); // 'email' atau 'whatsapp' + + if ($method === 'whatsapp') { + return $this->sendOtpViaWhatsapp($request); + } + + return $this->sendOtpViaEmail($request); + } + + // ── Kirim OTP via Email ── + private function sendOtpViaEmail(Request $request) { $request->validate([ 'email' => ['required', 'email', 'regex:/@gmail\.com$/'], @@ -35,55 +55,103 @@ public function sendOtp(Request $request) ]); $user = User::where('email', $request->email)->first(); + if (!$user) { + return back()->withErrors(['email' => 'Email tidak ditemukan.'])->withInput(); + } + + $otp = $this->generateAndStoreOtp($request->email); + + Mail::send('emails.otp', ['otp' => $otp, 'name' => $user->name], function ($m) use ($request) { + $m->to($request->email) + ->subject('Kode Verifikasi Reset Password - Bibit Cabai Bondowoso'); + }); + + $request->session()->put('reset_email', $request->email); + $request->session()->put('reset_method', 'email'); + + return redirect()->route('password.verify-otp-form') + ->with('success', 'Kode verifikasi dikirim ke email. Berlaku 3 menit.'); + } + + // ── Kirim OTP via WhatsApp ── + private function sendOtpViaWhatsapp(Request $request) + { + $request->validate([ + 'phone' => ['required', 'digits_between:10,15'], + ], [ + 'phone.required' => 'Nomor WhatsApp wajib diisi.', + 'phone.digits_between' => 'Nomor WhatsApp tidak valid (10-15 digit).', + ]); + + $phoneFormatted = FonnteService::formatPhone($request->phone); + + $user = User::where('phone', $phoneFormatted) + ->orWhere('phone', '+' . $phoneFormatted) + ->orWhere('phone', $request->phone) + ->orWhere('phone', '0' . ltrim($request->phone, '0')) + ->first(); if (!$user) { return back()->withErrors([ - 'email' => 'Email tidak ditemukan di sistem kami.', + 'phone' => 'Nomor WhatsApp tidak ditemukan di sistem kami.', ])->withInput(); } - // Generate 6-digit OTP + // Gunakan email user sebagai key di password_reset_tokens + $otp = $this->generateAndStoreOtp($user->email); + + $pesan = "🌶️ *Bibit Cabai Bondowoso*\n\n" + . "Halo {$user->name},\n\n" + . "Kode verifikasi reset password Anda:\n\n" + . "*{$otp}*\n\n" + . "Berlaku selama *3 menit*.\n" + . "Jangan berikan kode ini kepada siapapun."; + + $sent = $this->fonnte->sendMessage($phoneFormatted, $pesan); + + if (!$sent) { + return back()->with('error', 'Gagal mengirim pesan WhatsApp. Coba lagi.'); + } + + $request->session()->put('reset_email', $user->email); + $request->session()->put('reset_method', 'whatsapp'); + $request->session()->put('reset_phone_display', '****' . substr($request->phone, -4)); + + return redirect()->route('password.verify-otp-form') + ->with('success', 'Kode verifikasi dikirim ke WhatsApp Anda. Berlaku 3 menit.'); + } + + // ── Helper: generate & simpan OTP ── + private function generateAndStoreOtp(string $email): string + { $otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); - // Delete any existing token for this email - DB::table('password_reset_tokens')->where('email', $request->email)->delete(); - - // Store hashed OTP + expiry (10 minutes) + DB::table('password_reset_tokens')->where('email', $email)->delete(); DB::table('password_reset_tokens')->insert([ - 'email' => $request->email, + 'email' => $email, 'token' => Hash::make($otp), 'created_at' => Carbon::now(), ]); - // Send OTP via email - Mail::send('emails.otp', ['otp' => $otp, 'name' => $user->name], function ($message) use ($request) { - $message->to($request->email) - ->subject('Kode Verifikasi Reset Password - Bibit Cabai Bondowoso'); - }); - - // Store email in session for next steps - $request->session()->put('reset_email', $request->email); - - return redirect()->route('password.verify-otp-form') - ->with('success', 'Kode verifikasi telah dikirim ke email Anda. Berlaku selama 10 menit.'); + return $otp; } - // ───────────────────────────────────────────── - // STEP 2 — Show form: enter OTP - // ───────────────────────────────────────────── + // ────────────────────────────────────────────── + // STEP 2 — Show form: verifikasi OTP + // ────────────────────────────────────────────── public function showVerifyOtpForm(Request $request) { if (!$request->session()->has('reset_email')) { return redirect()->route('password.request') - ->with('error', 'Silakan masukkan email Anda terlebih dahulu.'); + ->with('error', 'Silakan masukkan email atau nomor WA Anda terlebih dahulu.'); } return view('auth.verify-otp'); } - // ───────────────────────────────────────────── - // STEP 2 — Handle: verify OTP - // ───────────────────────────────────────────── + // ────────────────────────────────────────────── + // STEP 2 — Handle: verifikasi OTP + // ────────────────────────────────────────────── public function verifyOtp(Request $request) { $request->validate([ @@ -94,36 +162,30 @@ public function verifyOtp(Request $request) ]); $email = $request->session()->get('reset_email'); - if (!$email) { return redirect()->route('password.request') ->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.'); } - $record = DB::table('password_reset_tokens') - ->where('email', $email) - ->first(); - + $record = DB::table('password_reset_tokens')->where('email', $email)->first(); if (!$record) { return back()->with('error', 'Kode OTP tidak ditemukan. Silakan kirim ulang.'); } - // Check expiry: 10 minutes $createdAt = Carbon::parse($record->created_at); - if (Carbon::now()->diffInMinutes($createdAt) > 10) { + $diffSeconds = Carbon::now()->diffInSeconds($createdAt); + + if ($diffSeconds > 180) { // 3 menit = 180 detik DB::table('password_reset_tokens')->where('email', $email)->delete(); - return back()->with('error', 'Kode OTP sudah kadaluarsa. Silakan kirim ulang kode.'); + return back()->with('error', 'Kode OTP sudah kadaluarsa. Silakan minta kirim kode lagi.'); } - // Verify OTP if (!Hash::check($request->otp, $record->token)) { - return back()->with('error', 'Kode OTP salah. Periksa kembali kode yang Anda masukkan.'); + return back()->with('error', 'Kode OTP salah. Periksa kembali kode Anda.'); } - // Generate a verified token for the next step $verifiedToken = Str::random(60); - DB::table('password_reset_tokens') - ->where('email', $email) + DB::table('password_reset_tokens')->where('email', $email) ->update(['token' => Hash::make($verifiedToken)]); $request->session()->put('reset_token', $verifiedToken); @@ -132,57 +194,62 @@ public function verifyOtp(Request $request) ->with('success', 'Kode berhasil diverifikasi! Silakan buat password baru.'); } - // ───────────────────────────────────────────── - // STEP 2 — Handle: resend OTP - // ───────────────────────────────────────────── + // ────────────────────────────────────────────── + // STEP 2 — Handle: kirim ulang OTP + // ────────────────────────────────────────────── public function resendOtp(Request $request) { - $email = $request->session()->get('reset_email'); + $email = $request->session()->get('reset_email'); + $method = $request->session()->get('reset_method', 'email'); if (!$email) { return redirect()->route('password.request') - ->with('error', 'Sesi tidak valid. Silakan masukkan email Anda kembali.'); + ->with('error', 'Sesi tidak valid.'); } $user = User::where('email', $email)->first(); if (!$user) { - return redirect()->route('password.request') - ->with('error', 'Email tidak ditemukan.'); + return redirect()->route('password.request')->with('error', 'User tidak ditemukan.'); } - $otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); + $otp = $this->generateAndStoreOtp($email); - DB::table('password_reset_tokens')->where('email', $email)->delete(); - DB::table('password_reset_tokens')->insert([ - 'email' => $email, - 'token' => Hash::make($otp), - 'created_at' => Carbon::now(), - ]); + if ($method === 'whatsapp') { + $phone = FonnteService::formatPhone( + preg_replace('/^\+/', '', $user->phone) + ); + $pesan = "🌶️ *Bibit Cabai Bondowoso*\n\n" + . "Halo {$user->name},\n\n" + . "Kode verifikasi baru Anda:\n\n" + . "*{$otp}*\n\n" + . "Berlaku selama *3 menit*."; - Mail::send('emails.otp', ['otp' => $otp, 'name' => $user->name], function ($message) use ($email) { - $message->to($email) - ->subject('Kode Verifikasi Reset Password (Kirim Ulang) - Bibit Cabai Bondowoso'); - }); + $sent = $this->fonnte->sendMessage($phone, $pesan); + if (!$sent) { + return back()->with('error', 'Gagal mengirim ulang ke WhatsApp.'); + } + } else { + Mail::send('emails.otp', ['otp' => $otp, 'name' => $user->name], function ($m) use ($email) { + $m->to($email)->subject('Kode Verifikasi (Kirim Ulang) - Bibit Cabai Bondowoso'); + }); + } - return back()->with('success', 'Kode OTP baru telah dikirim ke email Anda.'); + return back()->with('success', 'Kode OTP baru telah dikirim.'); } - // ───────────────────────────────────────────── - // STEP 3 — Show form: enter new password - // ───────────────────────────────────────────── + // ────────────────────────────────────────────── + // STEP 3 — Show & Handle reset password + // (sama seperti kode lama kamu — tidak berubah) + // ────────────────────────────────────────────── public function showResetForm(Request $request) { if (!$request->session()->has('reset_token') || !$request->session()->has('reset_email')) { return redirect()->route('password.request') ->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.'); } - return view('auth.reset-password'); } - // ───────────────────────────────────────────── - // STEP 3 — Handle: save new password - // ───────────────────────────────────────────── public function resetPassword(Request $request) { $request->validate([ @@ -199,27 +266,20 @@ public function resetPassword(Request $request) $token = $request->session()->get('reset_token'); if (!$email || !$token) { - return redirect()->route('password.request') - ->with('error', 'Sesi tidak valid. Silakan ulangi dari awal.'); + return redirect()->route('password.request')->with('error', 'Sesi tidak valid.'); } $record = DB::table('password_reset_tokens')->where('email', $email)->first(); - if (!$record || !Hash::check($token, $record->token)) { - return redirect()->route('password.request') - ->with('error', 'Token tidak valid. Silakan ulangi dari awal.'); + return redirect()->route('password.request')->with('error', 'Token tidak valid.'); } - // Update password - User::where('email', $email)->update([ - 'password' => Hash::make($request->password), - ]); + User::where('email', $email)->update(['password' => Hash::make($request->password)]); - // Clean up DB::table('password_reset_tokens')->where('email', $email)->delete(); - $request->session()->forget(['reset_email', 'reset_token']); + $request->session()->forget(['reset_email', 'reset_token', 'reset_method', 'reset_phone_display']); return redirect()->route('login') - ->with('success', 'Password berhasil diubah! Silakan login dengan password baru Anda.'); + ->with('success', 'Password berhasil diubah! Silakan login.'); } } \ No newline at end of file diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 7505ab4..7f84bca 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -24,7 +24,7 @@ public function register(Request $request): RedirectResponse $validator = Validator::make($request->all(), [ 'name' => 'required|string|min:8|max:255', 'email' => 'required|string|email|max:255|unique:users|regex:/@gmail\.com$/|regex:/^\S+$/', - 'phone' => 'required|string|regex:/^\+62[0-9]{9,15}$/|max:20', + 'phone' => 'required|string|regex:/^\+62[0-9]{9,15}$/|max:20', 'address' => 'required|string|min:12|max:500', 'password' => 'required|string|min:8|confirmed|regex:/^\S+$/', 'agree' => 'required|accepted', @@ -36,7 +36,8 @@ public function register(Request $request): RedirectResponse 'email.unique' => 'Email sudah digunakan.', 'email.regex' => 'Email harus menggunakan domain @gmail.com dan tidak boleh mengandung spasi.', 'phone.required' => 'Nomor telepon wajib diisi.', - 'phone.regex' => 'Nomor telepon harus diawali dengan +62 dan hanya boleh berisi angka.', + 'phone.regex' => 'Nomor telepon harus diawali dengan +62 dan hanya boleh berisi angka.', + 'phone.unique' => 'Nomor telepon sudah terdaftar di sistem kami.', // 'phone.min' => 'Nomor telepon minimal harus 12 karakter.', 'address.required' => 'Alamat wajib diisi.', 'address.min' => 'Alamat minimal harus 12 karakter.', @@ -49,11 +50,24 @@ public function register(Request $request): RedirectResponse ]); // Validasi password familiar - $validator->after(function ($validator) use ($request) { - if ($request->has('password') && $this->isCommonPassword($request->password)) { - $validator->errors()->add('password', 'Password yang Anda gunakan terlalu umum dan sering digunakan. Silakan pilih password yang lebih unik untuk keamanan akun Anda.'); - } - }); + // Validasi password familiar & pernah dipakai user lain +$validator->after(function ($validator) use ($request) { + if ($request->has('password')) { + if ($this->isCommonPassword($request->password)) { + $validator->errors()->add('password', 'Password yang Anda gunakan terlalu umum. Silakan pilih password yang lebih unik.'); + } + + if ($this->isPasswordAlreadyUsed($request->password)) { + $validator->errors()->add('password', 'Password ini sudah pernah digunakan oleh akun lain. Silakan gunakan password yang berbeda.'); + } + } + + if ($request->has('phone') && $request->phone) { + if ($this->isPhoneAlreadyUsed($request->phone)) { + $validator->errors()->add('phone', 'Nomor telepon sudah terdaftar di sistem kami.'); + } + } +}); if ($validator->fails()) { return redirect()->back()->withErrors($validator)->withInput(); @@ -124,4 +138,37 @@ private function isCommonPassword(string $password): bool return in_array(strtolower($password), array_map('strtolower', $commonPasswords)); } -} \ No newline at end of file + + /** + * Cek apakah password sudah pernah dipakai oleh user lain di database + */ +private function isPasswordAlreadyUsed(string $password): bool +{ + $users = User::whereNotNull('password')->get(['password']); + + foreach ($users as $user) { + if (Hash::check($password, $user->password)) { + return true; + } + } + + return false; +} + +/** + * Cek nomor telepon sudah terdaftar dalam berbagai format + */ + private function isPhoneAlreadyUsed(string $phone): bool + { + $digitsOnly = preg_replace('/\D/', '', $phone); + + $formats = [ + $phone, // +6281234567890 + $digitsOnly, // 6281234567890 + '+' . $digitsOnly, // +6281234567890 + '0' . substr($digitsOnly, 2), // 081234567890 + ]; + + return User::whereIn('phone', $formats)->exists(); + } +} diff --git a/app/Services/FonnteService.php b/app/Services/FonnteService.php index 8c5dcb9..f767177 100644 --- a/app/Services/FonnteService.php +++ b/app/Services/FonnteService.php @@ -3,21 +3,67 @@ namespace App\Services; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; class FonnteService { - public function sendOTP($nomor, $otp) + protected string $token; + protected string $apiUrl = 'https://api.fonnte.com/send'; + + public function __construct() { - // Format nomor: 08xxx → 628xxx - $nomor = '62' . ltrim($nomor, '0'); + $this->token = config('services.fonnte.token'); + } - $response = Http::withHeaders([ - 'Authorization' => env('FONNTE_TOKEN'), - ])->post('https://api.fonnte.com/send', [ - 'target' => $nomor, - 'message' => "Kode OTP reset password Anda: *$otp*\n\nBerlaku selama 10 menit. Jangan berikan kode ini kepada siapapun.", - ]); + /** + * Kirim pesan WhatsApp via Fonnte + * + * @param string $phone Nomor WA tujuan (format: 628xxx) + * @param string $message Isi pesan + * @return bool + */ + public function sendMessage(string $phone, string $message): bool + { + try { + $response = Http::withHeaders([ + 'Authorization' => $this->token, + ])->post($this->apiUrl, [ + 'target' => $phone, + 'message' => $message, + 'countryCode' => '62', // Indonesia + ]); - return $response->json(); + $body = $response->json(); + + if ($response->successful() && isset($body['status']) && $body['status'] === true) { + return true; + } + + Log::error('Fonnte send failed', ['response' => $body]); + return false; + + } catch (\Exception $e) { + Log::error('Fonnte exception: ' . $e->getMessage()); + return false; + } + } + + /** + * Format nomor WA ke format internasional + * Misal: 08123 → 628123, +628123 → 628123 + */ + public static function formatPhone(string $phone): string +{ + $phone = preg_replace('/[^\d]/', '', $phone); // hapus +, strip, spasi, dll + + if (str_starts_with($phone, '0')) { + $phone = '62' . substr($phone, 1); + } elseif (str_starts_with($phone, '62')) { + // sudah benar + } else { + $phone = '62' . $phone; + } + + return $phone; } } \ No newline at end of file diff --git a/config/services.php b/config/services.php index 6182e4b..727017d 100644 --- a/config/services.php +++ b/config/services.php @@ -34,5 +34,9 @@ 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), ], ], + + 'fonnte' => [ + 'token' => env('FONNTE_TOKEN'), +], ]; diff --git a/resources/views/admin/forgot-password.blade.php b/resources/views/admin/forgot-password.blade.php index 122c481..5a9e6d7 100644 --- a/resources/views/admin/forgot-password.blade.php +++ b/resources/views/admin/forgot-password.blade.php @@ -94,8 +94,8 @@
🔑
-

Lupa Password Admin

-

Masukkan email admin yang terdaftar

+

Lupa Password Admin

+

Gunakan email atau WhatsApp admin terdaftar

{{-- Step Indicator --}} @@ -136,36 +136,83 @@ @endif {{-- Form --}} -
- @csrf + {{-- Toggle Metode --}} +
+ + +
-
- - -
- ℹ️ Hanya akun admin yang terdaftar di sistem -
- @error('email') -
{{ $message }}
- @enderror - -
+ + @csrf + -
- + {{-- EMAIL SECTION --}} +
+
+ + +
+ ℹ️ Hanya akun admin yang terdaftar di sistem +
+ @error('email') +
{{ $message }}
+ @enderror + +
+
+ + {{-- WHATSAPP SECTION --}} + + +
+ +
+
@@ -181,30 +228,74 @@ class="form-control @error('email') is-invalid @enderror" diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index e45db0c..ffff7b1 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -16,87 +16,123 @@
- {{-- Step Indicator --}} -
-
-
1
-
Email
-
-
-
-
2
-
Kode OTP
-
-
-
-
3
-
Password Baru
-
-
+ {{-- Step Indicator --}} +
+
+
1
+
Identitas
+
+
+
+
2
+
Kode OTP
+
+
+
+
3
+
Password Baru
+
+
- @if(session('error')) - - @endif + @if(session('error')) +
+ {{ session('error') }} + +
+ @endif -

- - Kami akan mengirim kode verifikasi ke email Anda untuk mereset password. -

+ {{-- Toggle Metode --}} +
+ + +
-
- @csrf + + @csrf + -
- -
- - - - -
-
- Gunakan email @gmail.com yang terdaftar -
- @error('email') -
{{ $message }}
- @enderror - -
- -
- -
-
- -
- -
+ {{-- Email Section --}} +
+

+ + Kode verifikasi akan dikirim ke email Anda. +

+
+ +
+ + + + +
+
+ Gunakan email @gmail.com yang terdaftar +
+ @error('email') +
{{ $message }}
+ @enderror +
+ + {{-- WhatsApp Section --}} + + +
+ +
+ + +
+
- @endsection \ No newline at end of file