|
@@ -0,0 +1,336 @@
|
|
|
|
|
+<?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;
|
|
|
|
|
+
|
|
|
|
|
+ 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->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'] ?? ($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<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);
|
|
|
|
|
+
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (empty($payload['clientValidationId'])) {
|
|
|
|
|
+ $payload['clientValidationId'] = $validationId;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (empty($payload['validationId'])) {
|
|
|
|
|
+ $payload['validationId'] = $validationId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (empty($payload['client'])) {
|
|
|
|
|
+ $payload['client'] = $this->clientId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (empty($payload['cliente'])) {
|
|
|
|
|
+ $payload['cliente'] = $this->clientId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $payload;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|