userModel = new UserModel(); $this->tshieldService = new TshieldService(); } public function __invoke(ServerRequestInterface $request) { $body = json_decode((string) $request->getBody(), true) ?? []; try { val::key('email', val::email()) ->key('password', val::stringType()->notEmpty()->length(8, null)) ->assert($body); } catch (ValidationException $e) { return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(401); } $email = $body['email']; $password = $body['password']; $user = $this->userModel->validateLogin($email, $password); if (!$user) { return ResponseLib::sendFail("Invalid credentials", [], "E_VALIDATE")->withStatus(401); } $kycStatus = (int)($user['user_kyc'] ?? 0); $roleId = (int)($user['role_id'] ?? 0); if ($kycStatus === 0) { if ($roleId === 1) { return ResponseLib::sendFail( 'Necessário finalizar análise PJ ou contatar o suporte.', ['reason' => 'KYC_PJ_PENDING'], 'E_KYC' )->withStatus(403); } $analysisPayload = [ 'name' => $user['user_name'] ?? $user['user_email'], 'document' => $this->buildDocumentPayload($user['user_cpf'] ?? ''), 'email' => $user['user_email'], 'phone' => $user['user_phone'] ?? '', 'birthdate' => $this->formatBirthdate($user['user_birthdate'] ?? null), ]; if (empty($analysisPayload['document']['documentNumber'])) { return ResponseLib::sendFail( 'CPF não cadastrado. Contate o suporte para concluir a verificação.', ['reason' => 'KYC_PF_MISSING_DOCUMENT'], 'E_KYC' )->withStatus(403); } try { $tshield = $this->tshieldService->generateIndividualLink( (int)$user['user_id'], $analysisPayload ); } catch (\Throwable $e) { return ResponseLib::sendFail( 'Não foi possível gerar o link de verificação: ' . $e->getMessage(), [], 'E_EXTERNAL' )->withStatus(502); } return ResponseLib::sendFail( 'KYC pendente. Conclua a verificação pelo link disponibilizado.', [ 'link' => $tshield['link'], 'numberToken' => $tshield['number'], ], 'E_KYC' )->withStatus(403); } $payload = [ 'sub' => $user['user_id'], 'email' => $user['user_email'], 'iat' => time(), 'exp' => time() + 3600 ]; $jwt = JWT::encode($payload, $_ENV['JWT_SECRET'], 'HS256'); return ResponseLib::sendOk(['token' => $jwt, 'user_id' => $user['user_id'], 'company_id' => $user['company_id']]); } private function buildDocumentPayload(?string $cpf): array { $number = preg_replace('/\D+/', '', (string)$cpf); return [ 'documentNumber' => $number, 'documentType' => 'CPF', ]; } private function formatBirthdate($value): ?string { if ($value === null || $value === '') { return null; } if (is_numeric($value)) { $numeric = (string)$value; $timestamp = (int)$value; if ($timestamp > 0) { if (strlen($numeric) === 8) { $formatted = \DateTimeImmutable::createFromFormat('Ymd', $numeric); if ($formatted) { return $formatted->format('Y-m-d'); } } if (strlen($numeric) >= 13) { $timestamp = (int) floor($timestamp / 1000); } if ($timestamp >= 1000000000) { $dt = (new \DateTimeImmutable())->setTimestamp($timestamp); return $dt->format('Y-m-d'); } } } if (is_string($value)) { $value = trim($value); if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { return $value; } if (preg_match('/^\d{8}$/', $value)) { $formatted = \DateTimeImmutable::createFromFormat('Ymd', $value); if ($formatted) { return $formatted->format('Y-m-d'); } } foreach (['d/m/Y', 'd-m-Y', 'Y/m/d', 'Y.m.d'] as $fmt) { $formatted = \DateTimeImmutable::createFromFormat($fmt, $value); if ($formatted) { return $formatted->format('Y-m-d'); } } } return null; } private function formatAddress(array $user): ?string { $parts = array_filter([ $user['user_address'] ?? null, $user['user_city'] ?? null, $user['user_state'] ?? null, $user['user_zip'] ?? null, $user['user_country'] ?? null, ]); return empty($parts) ? null : implode(', ', $parts); } }