baseUrl = rtrim($_ENV['TSHIELD_BASE_URL'] ?? '', '/'); $this->login = $_ENV['TSHIELD_LOGIN'] ?? ''; $this->password = $_ENV['TSHIELD_PASSWORD'] ?? ''; $this->clientId = $_ENV['TSHIELD_CLIENT'] ?? ''; $this->validationIdPf = $_ENV['TSHIELD_VALIDATION_ID'] ?? ''; $this->validationIdPj = $_ENV['TSHIELD_VALIDATION_ID_CNPJ'] ?? ''; if ($this->baseUrl === '' || $this->login === '' || $this->password === '' || $this->clientId === '' || $this->validationIdPf === '' || $this->validationIdPj === '' ) { throw new \RuntimeException( 'Missing TShield configuration. Required envs: TSHIELD_BASE_URL, TSHIELD_LOGIN, ' . 'TSHIELD_PASSWORD, TSHIELD_CLIENT, TSHIELD_VALIDATION_ID, TSHIELD_VALIDATION_ID_CNPJ.' ); } $this->userModel = new UserModel(); $this->individualFieldMap = [ 'name' => (int)($_ENV['TSHIELD_FIELD_NAME_ID'] ?? 16175), 'document' => (int)($_ENV['TSHIELD_FIELD_DOCUMENT_ID'] ?? 16176), 'birthdate' => (int)($_ENV['TSHIELD_FIELD_BIRTHDATE_ID'] ?? 16177), 'phone' => (int)($_ENV['TSHIELD_FIELD_PHONE_ID'] ?? 16178), 'email' => (int)($_ENV['TSHIELD_FIELD_EMAIL_ID'] ?? 16179), ]; } /** * Cria análise e link de KYC para pessoa física. * * @param int $userId Usuário local que receberá o número externo. * @param array $analysisPayload Payload aceito por POST /api/query. * @param array $linkPayload Payload adicional para POST /api/query/external/token (por exemplo, redirectUrl). * * @return array{ * number: string, * link: ?string, * analysis: array, * link_response: array * } */ public function generateIndividualLink(int $userId, array $analysisPayload, array $linkPayload = []): array { $linkPayload = $this->buildIndividualLinkPayload($analysisPayload, $linkPayload); $linkResponse = $this->createLink($this->validationIdPf, $linkPayload); $analysisNumber = $linkResponse['number'] ?? ($linkResponse['data']['number'] ?? ($linkResponse['data']['token'] ?? null)); if (!$analysisNumber) { throw new \RuntimeException(sprintf( 'Unable to extract analysis number from PF link response: %s', json_encode($linkResponse, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) )); } $this->persistExternalId($userId, (string)$analysisNumber); $link = $linkResponse['link'] ?? $linkResponse['url'] ?? ($linkResponse['data']['link'] ?? ($linkResponse['data']['url'] ?? null)); return [ 'number' => (string)$analysisNumber, 'link' => $link, 'analysis' => $linkPayload, 'link_response' => $linkResponse, ]; } /** * Cria análise e link de KYC para pessoa jurídica. * * @param int $userId * @param array $analysisPayload Payload aceito por POST /api/query/company. * @param array $linkPayload * * @return array{ * number: string, * link: ?string, * analysis: array, * link_response: array * } */ public function generateCompanyLink(int $userId, array $analysisPayload, array $linkPayload = []): array { $analysisPayload = $this->injectDefaults($analysisPayload, $this->validationIdPj); $analysisNumber = $this->createAnalysis('/api/query/company', $analysisPayload); $this->persistExternalId($userId, $analysisNumber); return [ 'number' => $analysisNumber, 'link' => null, 'analysis' => $analysisPayload, 'link_response' => [ 'data' => [ 'client_validation_id' => $this->validationIdPj, 'number' => $analysisNumber, ], ], ]; } private function createAnalysis(string $path, array $payload): string { $response = $this->request('POST', $path, $payload); $number = $response['data']['number'] ?? $response['number'] ?? $response['data']['client_validation_id'] ?? $response['client_validation_id'] ?? null; if (!$number) { throw new \RuntimeException(sprintf( 'Unable to extract analysis number from %s response: %s', $path, json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) )); } return (string)$number; } private function createLink(string $validationId, array $payload): array { $body = array_merge(['client_validation_id' => $validationId], $payload); $response = $this->request('POST', '/api/query/external/token', $body); if ( !isset($response['link']) && !isset($response['url']) && !isset($response['data']['link']) && !isset($response['data']['url']) ) { throw new \RuntimeException(sprintf( 'Invalid link response from TShield: %s', json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) )); } return $response; } private function persistExternalId(int $userId, string $externalId): void { if (!$this->userModel->updateKycExternalId($userId, $externalId)) { throw new \RuntimeException('Unable to persist KYC external id for user ' . $userId); } } private function ensureToken(): void { if ($this->token !== null) { return; } $loginPayload = [ 'login' => $this->login, 'password' => $this->password, 'client' => $this->clientId, ]; $loginResponse = $this->request('POST', '/api/login', $loginPayload, false); $token = $loginResponse['token'] ?? ($loginResponse['data']['token'] ?? ($loginResponse['access_token'] ?? null)) ?? ($loginResponse['user']['token'] ?? null); if ($token === null) { throw new \RuntimeException('TShield login did not return a token.'); } $this->token = (string) $token; } private function request(string $method, string $path, array $payload = [], bool $requiresAuth = true): array { if ($requiresAuth) { $this->ensureToken(); } $url = $this->baseUrl . $path; $ch = curl_init($url); $headers = ['Content-Type: application/json']; if ($requiresAuth && $this->token !== null) { $headers[] = 'Authorization: Bearer ' . $this->token; } curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); if (!empty($payload)) { $encoded = json_encode($payload); if ($encoded === false) { throw new \RuntimeException('Unable to encode payload to JSON.'); } curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded); } $this->logRequest($method, $url, $payload); $responseBody = curl_exec($ch); $curlErrNo = curl_errno($ch); $curlErr = curl_error($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($curlErrNo !== 0) { throw new \RuntimeException(sprintf('cURL error while calling %s: %s (%d)', $url, $curlErr, $curlErrNo)); } if ($responseBody === false || $responseBody === '') { throw new \RuntimeException(sprintf('Empty response from TShield endpoint %s', $url)); } $decoded = json_decode($responseBody, true); if ($decoded === null) { throw new \RuntimeException(sprintf('Invalid JSON response from %s: %s', $url, $responseBody)); } if ($httpCode >= 400) { $message = $decoded['message'] ?? $decoded['error'] ?? 'TShield request failed'; throw new \RuntimeException(sprintf( '%s (HTTP %d) payload=%s', $message, $httpCode, json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) )); } return $decoded; } private function logRequest(string $method, string $url, array $payload): void { $encoded = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); error_log(sprintf('[TShield] %s %s payload=%s', $method, $url, $encoded)); } private function buildIndividualLinkPayload(array $analysisPayload, array $linkPayload): array { $payload = $linkPayload; if (!isset($payload['token_send'])) { $payload['token_send'] = []; } $defaultAdditional = []; $fieldValues = [ 'name' => $analysisPayload['name'] ?? null, 'document' => $analysisPayload['document'] ?? null, 'birthdate' => $analysisPayload['birthdate'] ?? null, 'phone' => $analysisPayload['phone'] ?? null, 'email' => $analysisPayload['email'] ?? null, ]; foreach ($fieldValues as $key => $value) { if (is_array($value)) { $value = $value['number'] ?? reset($value) ?? null; } if (!$value) { continue; } $fieldId = $this->individualFieldMap[$key] ?? null; if (!$fieldId) { continue; } $defaultAdditional[] = [ 'client_validation_additional_field_id' => $fieldId, 'value' => $value, 'file_field_type' => null, 'values' => [$value], ]; } if (!isset($payload['additional_fields']) || !is_array($payload['additional_fields'])) { $payload['additional_fields'] = $defaultAdditional; } elseif (!empty($defaultAdditional)) { $payload['additional_fields'] = array_merge($defaultAdditional, $payload['additional_fields']); } if (empty($payload['company_id']) && isset($_ENV['TSHIELD_COMPANY_ID'])) { $payload['company_id'] = $_ENV['TSHIELD_COMPANY_ID']; } return $payload; } private function injectDefaults(array $payload, string $validationId): array { if (empty($payload['client_validation_id'])) { $payload['client_validation_id'] = $validationId; } return $payload; } }