<?php namespace App\Integrations\Celcoin; use App\Models\CelcoinToken; use Exception; /** * Serviço de autenticação OAuth da Celcoin * Gerencia tokens de acesso, refresh e renovação automática */ class CelcoinAuthService { private CelcoinHttpClient $httpClient; private int $companyId; private int $gatewaySettingId; private ?CelcoinToken $currentToken = null; public function __construct(int $companyId, int $gatewaySettingId, array $config = []) { $this->companyId = $companyId; $this->gatewaySettingId = $gatewaySettingId; $this->httpClient = new CelcoinHttpClient($companyId, $config); } /** * Obtém um token de acesso válido * Renova automaticamente se necessário */ public function getValidAccessToken(): string { // Carregar token atual do banco $this->loadCurrentToken(); // Verificar se o token é válido if ($this->isTokenValid()) { return $this->currentToken->access_token; } // Tentar renovar o token if ($this->currentToken && $this->currentToken->refresh_token) { try { return $this->refreshAccessToken(); } catch (Exception $e) { // Log do erro de refresh $this->logAuthError('Token refresh failed: ' . $e->getMessage()); } } // Obter novo token return $this->requestNewAccessToken(); } /** * Solicita um novo token de acesso */ public function requestNewAccessToken(): string { try { // Obter configurações da empresa $config = $this->getCompanyGatewayConfig(); if (!$config['client_id'] || !$config['client_secret']) { throw new Exception('Client ID and Client Secret are required for authentication'); } // Preparar dados para OAuth $authData = [ 'grant_type' => 'client_credentials', 'client_id' => $config['client_id'], 'client_secret' => $config['client_secret'] ]; // Fazer requisição de token $response = $this->httpClient->post('/v5/token', $authData); if (!$response['success']) { throw new Exception('Failed to obtain access token'); } $tokenData = $response['data']; // Validar resposta if (!isset($tokenData['access_token'])) { throw new Exception('Invalid token response: missing access_token'); } // Calcular data de expiração $expiresIn = $tokenData['expires_in'] ?? 3600; // padrão 1 hora $expiresAt = date('Y-m-d H:i:s', time() + $expiresIn); // Salvar token no banco $this->saveToken($tokenData, $expiresAt); // Configurar token no HTTP client $this->httpClient->setAccessToken($tokenData['access_token']); $this->logAuthSuccess('New access token obtained'); return $tokenData['access_token']; } catch (Exception $e) { $this->logAuthError('Failed to obtain new access token: ' . $e->getMessage()); throw new Exception('Authentication failed: ' . $e->getMessage()); } } /** * Renova o token de acesso usando refresh token */ public function refreshAccessToken(): string { try { if (!$this->currentToken || !$this->currentToken->refresh_token) { throw new Exception('No refresh token available'); } // Obter configurações da empresa $config = $this->getCompanyGatewayConfig(); // Preparar dados para refresh $refreshData = [ 'grant_type' => 'refresh_token', 'refresh_token' => $this->currentToken->refresh_token, 'client_id' => $config['client_id'], 'client_secret' => $config['client_secret'] ]; // Fazer requisição de refresh $response = $this->httpClient->post('/v5/token', $refreshData); if (!$response['success']) { throw new Exception('Failed to refresh access token'); } $tokenData = $response['data']; // Validar resposta if (!isset($tokenData['access_token'])) { throw new Exception('Invalid refresh response: missing access_token'); } // Calcular data de expiração $expiresIn = $tokenData['expires_in'] ?? 3600; $expiresAt = date('Y-m-d H:i:s', time() + $expiresIn); // Atualizar token no banco $this->updateToken($tokenData, $expiresAt); // Configurar novo token no HTTP client $this->httpClient->setAccessToken($tokenData['access_token']); $this->logAuthSuccess('Access token refreshed successfully'); return $tokenData['access_token']; } catch (Exception $e) { $this->logAuthError('Failed to refresh access token: ' . $e->getMessage()); throw $e; } } /** * Verifica se o token atual é válido */ public function isTokenValid(): bool { if (!$this->currentToken) { return false; } // Verificar se não expirou (com margem de 5 minutos) $expiresAt = strtotime($this->currentToken->expires_at); $now = time(); $margin = 300; // 5 minutos return ($expiresAt - $margin) > $now; } /** * Carrega o token atual do banco de dados */ private function loadCurrentToken(): void { $tokenModel = new CelcoinToken(); $this->currentToken = $tokenModel->findLatestValid($this->companyId, $this->gatewaySettingId); if ($this->currentToken) { $this->httpClient->setAccessToken($this->currentToken->access_token); } } /** * Salva um novo token no banco */ private function saveToken(array $tokenData, string $expiresAt): void { $tokenModel = new CelcoinToken(); $tokenModel->create([ 'company_id' => $this->companyId, 'gateway_setting_id' => $this->gatewaySettingId, 'access_token' => $tokenData['access_token'], 'refresh_token' => $tokenData['refresh_token'] ?? null, 'token_type' => $tokenData['token_type'] ?? 'Bearer', 'expires_at' => $expiresAt, 'scope' => $tokenData['scope'] ?? null ]); // Recarregar token $this->loadCurrentToken(); } /** * Atualiza o token existente */ private function updateToken(array $tokenData, string $expiresAt): void { if (!$this->currentToken) { throw new Exception('No current token to update'); } $tokenModel = new CelcoinToken(); $tokenModel->update($this->currentToken->id, [ 'access_token' => $tokenData['access_token'], 'refresh_token' => $tokenData['refresh_token'] ?? $this->currentToken->refresh_token, 'token_type' => $tokenData['token_type'] ?? $this->currentToken->token_type, 'expires_at' => $expiresAt, 'scope' => $tokenData['scope'] ?? $this->currentToken->scope, 'updated_at' => date('Y-m-d H:i:s') ]); // Recarregar token $this->loadCurrentToken(); } /** * Obtém configurações do gateway para a empresa */ private function getCompanyGatewayConfig(): array { // Aqui você implementaria a lógica para buscar as configurações // Por enquanto, retornando dados mockados return [ 'client_id' => 'your_client_id', 'client_secret' => 'your_client_secret', 'is_sandbox' => true ]; } /** * Log de sucesso na autenticação */ private function logAuthSuccess(string $message): void { // Implementar logging error_log("[Celcoin Auth] {$message} - Company: {$this->companyId}"); } /** * Log de erro na autenticação */ private function logAuthError(string $message): void { // Implementar logging error_log("[Celcoin Auth Error] {$message} - Company: {$this->companyId}"); } /** * Limpa tokens expirados */ public function cleanupExpiredTokens(): int { $tokenModel = new CelcoinToken(); return $tokenModel->deleteExpired($this->companyId); } }