* @category Class * @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License * @link www.splendidbear.org * @since 2026. 04. 12. */ readonly class RecaptchaService { private const string SITEVERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'; /** * Minimum score to accept a request (0.0 = bot, 1.0 = human). * 0.5 is Google's recommended default threshold. */ private const float SCORE_THRESHOLD = 0.5; public function __construct( private HttpClientInterface $httpClient, private LoggerInterface $logger, #[Autowire(env: 'RECAPTCHA_SECRET_KEY')] private string $secretKey, ) {} public function verify(string $token, string $remoteIp = ''): bool { if ($token === '') { return false; } try { $body = ['secret' => $this->secretKey, 'response' => $token]; if ($remoteIp !== '') { $body['remoteip'] = $remoteIp; } $data = $this->httpClient ->request('POST', self::SITEVERIFY_URL, ['body' => $body]) ->toArray(); return ($data['success'] ?? false) === true && ($data['score'] ?? 0.0) >= self::SCORE_THRESHOLD; } catch (\Throwable $e) { $this->logger->error('reCAPTCHA verification failed: ' . $e->getMessage()); return false; } } }