2026-07-01 18:35:39 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Modules\Core\Auth\Services;
|
|
|
|
|
|
|
|
|
|
use Illuminate\Support\Facades\Mail;
|
|
|
|
|
use Modules\Core\Auth\Mail\OtpMail;
|
|
|
|
|
use Modules\Core\Auth\Models\Staff;
|
|
|
|
|
|
|
|
|
|
class OtpService
|
|
|
|
|
{
|
|
|
|
|
private const EXPIRY_MINUTES = 10;
|
|
|
|
|
private const CODE_LENGTH = 6;
|
|
|
|
|
|
2026-09-22 14:36:57 +03:00
|
|
|
/**
|
|
|
|
|
* $purpose is forwarded as-is to OtpMail, which only recognizes a
|
|
|
|
|
* fixed set of keys (see its own COPY_BY_PURPOSE) — an unrecognized
|
|
|
|
|
* value there just falls back to 'login' rather than failing here, so
|
|
|
|
|
* this method has nothing of its own to validate.
|
|
|
|
|
*/
|
|
|
|
|
public function generateAndSend(string $email, string $purpose = 'login'): bool
|
2026-07-01 18:35:39 +03:00
|
|
|
{
|
|
|
|
|
$staff = Staff::where('email', $email)->first();
|
|
|
|
|
|
|
|
|
|
if (! $staff) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$code = str_pad((string) random_int(0, 999999), self::CODE_LENGTH, '0', STR_PAD_LEFT);
|
|
|
|
|
|
|
|
|
|
$staff->otp_code = $code;
|
|
|
|
|
$staff->otp_expires_at = now()->addMinutes(self::EXPIRY_MINUTES);
|
|
|
|
|
$staff->save();
|
|
|
|
|
|
2026-09-22 14:36:57 +03:00
|
|
|
Mail::to($staff->email)->send(new OtpMail($staff->first_name, $code, $purpose));
|
2026-07-01 18:35:39 +03:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function validate(string $email, string $code): ?Staff
|
|
|
|
|
{
|
|
|
|
|
$staff = Staff::where('email', $email)->first();
|
|
|
|
|
|
|
|
|
|
if (! $staff) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! $staff->otp_expires_at || $staff->otp_code != $code || now()->isAfter($staff->otp_expires_at)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$staff->otp_code = null;
|
|
|
|
|
$staff->otp_expires_at = null;
|
|
|
|
|
$staff->save();
|
|
|
|
|
|
|
|
|
|
return $staff;
|
|
|
|
|
}
|
|
|
|
|
}
|