| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- <?php
- namespace Services;
- use Models\UserModel;
- class TshieldService
- {
- private string $baseUrl;
- private string $login;
- private string $password;
- private string $clientId;
- private string $validationIdPf;
- private string $validationIdPj;
- private int $timeout = 30;
- private ?string $token = null;
- private UserModel $userModel;
- private array $individualFieldMap;
- private string $logFilePath;
- public function __construct()
- {
- $this->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->logFilePath = $this->resolveLogFilePath($_ENV['TSHIELD_LOG_FILE'] ?? null);
- $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<mixed>,
- * link_response: array<mixed>
- * }
- */
- 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'] ?? 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<mixed>,
- * link_response: array<mixed>
- * }
- */
- 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);
- $this->logToFile($method, $url, $httpCode, $payload, $responseBody);
- 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 resolveLogFilePath(?string $path): string
- {
- $path = trim((string)$path);
- if ($path === '') {
- $path = 'storage/logs/tshield_response.txt';
- }
- if (!preg_match('/^(?:[A-Za-z]:\\\\|\\/)/', $path)) {
- $path = rtrim(dirname(__DIR__), '/\\') . DIRECTORY_SEPARATOR . $path;
- }
- $dir = dirname($path);
- if (!is_dir($dir)) {
- @mkdir($dir, 0775, true);
- }
- return $path;
- }
- private function logToFile(string $method, string $url, int $httpCode, array $payload, $responseBody): void
- {
- $ts = (new \DateTimeImmutable())->format('c');
- $payloadEncoded = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
- $responseText = is_string($responseBody) ? $responseBody : '';
- $entry = "[$ts] $method $url HTTP=$httpCode\n" .
- "payload=$payloadEncoded\n" .
- "response=$responseText\n" .
- "----\n";
- try {
- @file_put_contents($this->logFilePath, $entry, FILE_APPEND | LOCK_EX);
- } catch (\Throwable $e) {
- }
- }
- 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 = [];
- if (isset($analysisPayload['birthdate'])) {
- $analysisPayload['birthdate'] = $this->normalizeBirthdate($analysisPayload['birthdate']);
- }
- $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 normalizeBirthdate($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 injectDefaults(array $payload, string $validationId): array
- {
- if (empty($payload['client_validation_id'])) {
- $payload['client_validation_id'] = $validationId;
- }
- return $payload;
- }
- }
|