diff --git a/README.md b/README.md index 2f96199..4013343 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,20 @@ Para guias detalhados de instalação, configuração e uso, acesse nossa docume * 📖 **[Guia de Instalação](https://nfe.io/docs/plugins/whmcs/instalacao/)** * ⚙️ **[Manual de Configuração](https://nfe.io/docs/plugins/whmcs/configuracao/)** +## 📚 Atualização + +Para atualizar o módulo com segurança, siga os passos abaixo: + +1. **Faça um backup:** Antes de começar, faça um backup completo do seu banco de dados e dos arquivos do WHMCS. +2. **Baixe a nova versão:** Faça o download do *release* mais recente e confira os arquivos. +3. **Envie os arquivos:** Faça o upload dos arquivos baixados para a pasta do seu WHMCS, substituindo os antigos. + +⚠️ **Atenção:** Em hipótese alguma desative o módulo no painel do WHMCS, pois isso apagará suas configurações. Apenas substitua os arquivos pela nova versão. + ## 🛠️ Requisitos do Sistema -* **WHMCS:** Versão 8.0 ou superior -* **PHP:** Versão 7.2 ou superior +* **WHMCS:** Versão 8+ ou superior +* **PHP:** Versão 8+ ou superior * **Conta NFE.io:** Uma conta ativa na [NFE.io](https://nfe.io) com chave de API. ## 📦 Changelog diff --git a/modules/addons/NFEioServiceInvoices/callback.php b/modules/addons/NFEioServiceInvoices/callback.php index d699434..f2eb91f 100644 --- a/modules/addons/NFEioServiceInvoices/callback.php +++ b/modules/addons/NFEioServiceInvoices/callback.php @@ -9,18 +9,18 @@ new NFEioServiceInvoices\Loader(); -if ($_SERVER['REQUEST_METHOD'] !== 'POST') { - http_response_code(405); - echo "Method Not Allowed"; - exit(); -} - if (isset($_GET['echo'])) { http_response_code(200); echo "ok"; exit(); } +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + echo "Method Not Allowed"; + exit(); +} + $headers = array_change_key_case(getallheaders(), CASE_LOWER); $signature = $headers['x-hub-signature'] ?? $headers['x-nfeio-signature'] ?? null; @@ -44,6 +44,11 @@ $payload = json_decode($body, true); +// Fix v1 to v2: Se a NFE.io mandar os dados encapsulados dentro da chave 'payload', nós extraímos para a raiz +if (isset($payload['payload']) && is_array($payload['payload'])) { + $payload = $payload['payload']; +} + if (!is_array($payload) || !isset($payload['id'], $payload['status'], $payload['flowStatus'], $payload['environment'])) { logModuleCall('nfeio_serviceinvoices', 'callback_error', 'Payload inválido', ['body' => $payload]); http_response_code(400); @@ -102,4 +107,4 @@ logModuleCall('nfeio_serviceinvoices', 'callback', 'Nenhuma informação foi alterada', ['nfe' => $nfe, 'payload' => $payload]); http_response_code(200); exit(); -} \ No newline at end of file +} diff --git a/modules/addons/NFEioServiceInvoices/lib/Admin/Controller.php b/modules/addons/NFEioServiceInvoices/lib/Admin/Controller.php index b0152e9..1b9a351 100644 --- a/modules/addons/NFEioServiceInvoices/lib/Admin/Controller.php +++ b/modules/addons/NFEioServiceInvoices/lib/Admin/Controller.php @@ -449,6 +449,58 @@ public function configuration($vars) $vars['moduleCallBackUrl'] = $moduleCallBackUrl; $vars['companies'] = $registeredCompanies; + // Lista de países (nome e código) + $isoCountries = [ + 'BR' => 'Brasil', 'US' => 'Estados Unidos', 'AR' => 'Argentina', 'DE' => 'Alemanha', 'FR' => 'França', + 'IT' => 'Itália', 'JP' => 'Japão', 'CN' => 'China', 'GB' => 'Reino Unido', 'CA' => 'Canadá', + 'MX' => 'México', 'ES' => 'Espanha', 'PT' => 'Portugal', 'RU' => 'Rússia', 'IN' => 'Índia', + 'AU' => 'Austrália', 'ZA' => 'África do Sul', 'NL' => 'Holanda', 'CH' => 'Suíça', 'SE' => 'Suécia', + 'NO' => 'Noruega', 'FI' => 'Finlândia', 'DK' => 'Dinamarca', 'KR' => 'Coreia do Sul', 'CL' => 'Chile', + 'CO' => 'Colômbia', 'PE' => 'Peru', 'PL' => 'Polônia', 'TR' => 'Turquia', 'NZ' => 'Nova Zelândia', + 'IE' => 'Irlanda', 'BE' => 'Bélgica', 'AT' => 'Áustria', 'CZ' => 'República Tcheca', 'HU' => 'Hungria', + 'GR' => 'Grécia', 'IL' => 'Israel', 'SG' => 'Singapura', 'TH' => 'Tailândia', 'UA' => 'Ucrânia', + 'RO' => 'Romênia', 'BG' => 'Bulgária', 'HR' => 'Croácia', 'SK' => 'Eslováquia', 'SI' => 'Eslovênia', + 'EE' => 'Estônia', 'LT' => 'Lituânia', 'LV' => 'Letônia', 'LU' => 'Luxemburgo', 'IS' => 'Islândia', + 'MT' => 'Malta', 'CY' => 'Chipre', 'EG' => 'Egito', 'SA' => 'Arábia Saudita', 'AE' => 'Emirados Árabes', + 'QA' => 'Catar', 'KW' => 'Kuwait', 'JO' => 'Jordânia', 'LB' => 'Líbano', 'IR' => 'Irã', 'PK' => 'Paquistão', + 'ID' => 'Indonésia', 'MY' => 'Malásia', 'PH' => 'Filipinas', 'VN' => 'Vietnã', 'BD' => 'Bangladesh', + 'NG' => 'Nigéria', 'KE' => 'Quênia', 'GH' => 'Gana', 'TZ' => 'Tanzânia', 'UG' => 'Uganda', 'MA' => 'Marrocos', + 'DZ' => 'Argélia', 'TN' => 'Tunísia', 'SN' => 'Senegal', 'CI' => 'Costa do Marfim', 'CM' => 'Camarões', + 'ET' => 'Etiópia', 'SD' => 'Sudão', 'AO' => 'Angola', 'MZ' => 'Moçambique', 'ZW' => 'Zimbábue', + 'GH' => 'Gana', 'RW' => 'Ruanda', 'UG' => 'Uganda', 'ZM' => 'Zâmbia', 'MW' => 'Malawi', 'LS' => 'Lesoto', + 'SZ' => 'Suazilândia', 'BW' => 'Botsuana', 'NA' => 'Namíbia', 'CD' => 'Congo', 'CG' => 'Congo', + 'GA' => 'Gabão', 'GQ' => 'Guiné Equatorial', 'ST' => 'São Tomé e Príncipe', 'CV' => 'Cabo Verde', + 'GM' => 'Gâmbia', 'SL' => 'Serra Leoa', 'LR' => 'Libéria', 'BF' => 'Burkina Faso', 'NE' => 'Níger', + 'ML' => 'Mali', 'MR' => 'Mauritânia', 'BJ' => 'Benin', 'TG' => 'Togo', 'CI' => 'Costa do Marfim', + 'GN' => 'Guiné', 'GW' => 'Guiné-Bissau', 'SR' => 'Suriname', 'GY' => 'Guiana', 'TT' => 'Trinidad e Tobago', + 'JM' => 'Jamaica', 'HT' => 'Haiti', 'DO' => 'República Dominicana', 'CU' => 'Cuba', 'BS' => 'Bahamas', + 'BB' => 'Barbados', 'AG' => 'Antígua e Barbuda', 'VC' => 'São Vicente e Granadinas', 'LC' => 'Santa Lúcia', + 'GD' => 'Granada', 'KN' => 'São Cristóvão e Nevis', 'DM' => 'Dominica', 'FM' => 'Micronésia', 'PW' => 'Palau', + 'MH' => 'Ilhas Marshall', 'SB' => 'Ilhas Salomão', 'VU' => 'Vanuatu', 'FJ' => 'Fiji', 'WS' => 'Samoa', + 'TO' => 'Tonga', 'TV' => 'Tuvalu', 'KI' => 'Quiribati', 'NR' => 'Nauru', 'PG' => 'Papua Nova Guiné', + 'NC' => 'Nova Caledônia', 'PF' => 'Polinésia Francesa', 'FR' => 'França', 'ES' => 'Espanha', 'PT' => 'Portugal', + ]; + $vars['countries'] = []; + foreach ($isoCountries as $code => $name) { + $vars['countries'][] = ['code' => $code, 'name' => $name]; + } + + // Carregar países selecionados + $storage = new \WHMCSExpert\Addon\Storage($config->getStorageKey()); + $selectedCountries = $storage->get('issue_note_countries'); + + // Decodifica a string JSON vinda do banco de dados + if (is_string($selectedCountries)) { + $selectedCountries = json_decode($selectedCountries, true); + } + + if ($selectedCountries && is_array($selectedCountries)) { + $vars['issue_note_countries'] = $selectedCountries; + } else { + // Seleciona todos por padrão apenas se não existe no banco + $vars['issue_note_countries'] = array_keys($isoCountries); + } + if ($msg->hasMessages()) { $msg->display(); @@ -476,6 +528,23 @@ public function configuration($vars) */ public function configurationSave($vars) { + $config = new \NFEioServiceInvoices\Configuration(); + $storage = new \WHMCSExpert\Addon\Storage($config->getStorageKey()); + $post = isset($_POST) ? $_POST : null; + + // Salvar países selecionados + $issue_note_countries = isset($post['issue_note_countries']) ? $post['issue_note_countries'] : []; + if (empty($issue_note_countries)) { + // Se não vier nada, salva todos + $isoCountries = [ + 'BR','US','AR','DE','FR','IT','JP','CN','GB','CA','MX','ES','PT','RU','IN','AU','ZA','NL','CH','SE','NO','FI','DK','KR','CL','CO','PE','PL','TR','NZ','IE','BE','AT','CZ','HU','GR','IL','SG','TH','UA','RO','BG','HR','SK','SI','EE','LT','LV','LU','IS','MT','CY','EG','SA','AE','QA','KW','JO','LB','IR','PK','ID','MY','PH','VN','BD','NG','KE','GH','TZ','UG','MA','DZ','TN','SN','CI','CM','ET','SD','AO','MZ','ZW','GH','RW','UG','ZM','MW','LS','SZ','BW','NA','CD','CG','GA','GQ','ST','CV','GM','SL','LR','BF','NE','ML','MR','BJ','TG','CI','GN','GW','SR','GY','TT','JM','HT','DO','CU','BS','BB','AG','VC','LC','GD','KN','DM','FM','PW','MH','SB','VU','FJ','WS','TO','TV','KI','NR','PG','NC','PF','FR','ES','PT' + ]; + $issue_note_countries = $isoCountries; + } + // Garante que o array seja salvo como uma string JSON no banco de dados + $storage->set('issue_note_countries', json_encode($issue_note_countries)); + + $msg = new FlashMessages(); $assetsURL = Addon::I()->getAssetsURL(); diff --git a/modules/addons/NFEioServiceInvoices/lib/Legacy/Functions.php b/modules/addons/NFEioServiceInvoices/lib/Legacy/Functions.php index 5dd6a37..bdc0951 100644 --- a/modules/addons/NFEioServiceInvoices/lib/Legacy/Functions.php +++ b/modules/addons/NFEioServiceInvoices/lib/Legacy/Functions.php @@ -97,16 +97,17 @@ public function gnfe_customer($user_id, $client): array $result['insc_municipal'] = $inscMunicipalCustomFieldValue; } - if ($cpfIsValid) { - $result['success'] = true; - $result['doc_type'] = 1; - $result['document'] = $cpf; - $result['name'] = $client->firstname . ' ' . $client->lastname; - } elseif ($cnpjIsValid) { + // INVERTEMOS A ORDEM AQUI: CNPJ PRIMEIRO! + if ($cnpjIsValid) { $result['success'] = true; $result['doc_type'] = 2; $result['document'] = $cnpj; $result['name'] = $client->companyname ? $client->companyname : $client->firstname . ' ' . $client->lastname; + } elseif ($cpfIsValid) { + $result['success'] = true; + $result['doc_type'] = 1; + $result['document'] = $cpf; + $result['name'] = $client->firstname . ' ' . $client->lastname; } else { $result['error'] = true; $result['message'] = 'Documento cadastrado não é um CPF ou CNPJ válido.'; @@ -159,13 +160,21 @@ function gnfe_issue_nfe($postfields, $companyId) // Verifica se o webhook existe e é válido, senão cria $webhook = $webhook_id ? $nfeio->getWebhook($webhook_id) : null; - if (!$webhook || $webhook->hooks->url !== $webhook_url) { + + $webhookValido = false; + // Confirma se o retorno é um objeto e tem a URL antes de tentar ler + if (is_object($webhook) && isset($webhook->hooks) && $webhook->hooks->url === $webhook_url) { + $webhookValido = true; + } + + if (!$webhookValido) { $newHook = $nfeio->createWebhook($webhook_url); - if (!$newHook) { - return (object)['message' => 'Erro ao criar novo webhook']; + + // Só salva no banco se NÃO for um erro E se o objeto contiver os dados + if ($newHook && !is_array($newHook) && isset($newHook->hooks)) { + $storage->set('webhook_id', (string) $newHook->hooks->id); + $storage->set('webhook_secret', (string) $newHook->hooks->secret); } - $storage->set('webhook_id', (string) $newHook->hooks->id); - $storage->set('webhook_secret', (string) $newHook->hooks->secret); } diff --git a/modules/addons/NFEioServiceInvoices/lib/NFEio/Nfe.php b/modules/addons/NFEioServiceInvoices/lib/NFEio/Nfe.php index ad363d3..67894c6 100644 --- a/modules/addons/NFEioServiceInvoices/lib/NFEio/Nfe.php +++ b/modules/addons/NFEioServiceInvoices/lib/NFEio/Nfe.php @@ -424,6 +424,28 @@ public function queue($invoiceId, $reissue = false) $clientId = $clientData[0]['id']; $clientCompanyId = $clientCompanyRepository->getCompanyByClientId($clientId); + // Validação do país do cliente + $clientCountry = isset($clientData[0]['country']) ? $clientData[0]['country'] : null; + $allowedCountries = $this->storage->get('issue_note_countries'); + + // Decodifica a string JSON + if (is_string($allowedCountries)) { + $allowedCountries = json_decode($allowedCountries, true); + } + + if ($allowedCountries && is_array($allowedCountries)) { + if ($clientCountry && !in_array($clientCountry, $allowedCountries)) { + // País não permitido, não emite nota + logModuleCall('nfeio_serviceinvoices', 'nf_queue_country_block', [ + 'invoiceId' => $invoiceId, + 'clientId' => $clientId, + 'clientCountry' => $clientCountry, + 'allowedCountries' => $allowedCountries + ], 'Nota fiscal não emitida: país do cliente não está entre os permitidos.'); + return ['success' => false, 'reason' => 'client_country_not_allowed']; + } + } + // se cliente possuir empresa associada, utiliza a empresa associada, senao usa a empresa padrão // #163 if ($clientCompanyId) { diff --git a/modules/addons/NFEioServiceInvoices/lib/templates/admin/configuration.tpl b/modules/addons/NFEioServiceInvoices/lib/templates/admin/configuration.tpl index 37d7564..8aba490 100644 --- a/modules/addons/NFEioServiceInvoices/lib/templates/admin/configuration.tpl +++ b/modules/addons/NFEioServiceInvoices/lib/templates/admin/configuration.tpl @@ -189,6 +189,19 @@ {* /issue_note_after *} + {* issue_note_countries *} +