Skip to content

Commit 6eb900c

Browse files
Merge pull request #183 from nfe/develop
Melhorias na lida de erros de cancelamento, mensagens de notificação e webhooks
2 parents 57ce5c0 + 4bcb7d1 commit 6eb900c

File tree

7 files changed

+369
-28
lines changed

7 files changed

+369
-28
lines changed

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,39 @@
1+
## v3.1.1 (Em desenvolvimento)
2+
Esta versão adiciona melhorias na interface administrativa, aprimora o fluxo de cancelamento de notas fiscais e inclui ferramentas de diagnóstico para a configuração de webhooks.
3+
4+
### Novos Recursos
5+
#### Visualização de Informações do Webhook na Página Sobre
6+
Administradores agora podem visualizar informações detalhadas sobre o webhook configurado diretamente na página "Sobre" do módulo, facilitando o diagnóstico de problemas de integração.
7+
8+
**Informações Exibidas:**
9+
- **URL do Webhook**: Endpoint local para recebimento de callbacks da NFE.io
10+
- **ID do Webhook**: Identificador único do webhook registrado na API
11+
- **Secret (mascarado)**: Primeiros 8 caracteres do secret para verificação de segurança
12+
- **Última Verificação**: Timestamp da última validação manual realizada
13+
14+
**Verificação Manual do Webhook:**
15+
16+
Um novo botão "Verificar Status na API" permite que administradores validem sob demanda se o webhook está corretamente configurado na API NFE.io, incluindo:
17+
- Verificação de existência do webhook na API
18+
- Validação de consistência da URL entre configuração local e API
19+
- Verificação de status ativo do webhook
20+
- Logs detalhados de todas as operações de verificação
21+
22+
Esta funcionalidade auxilia no diagnóstico de problemas relacionados ao recebimento de callbacks, reduzindo o tempo de troubleshooting e melhorando a transparência da configuração do sistema.
23+
24+
### Melhorias
25+
#### Tratamento de cancelamento de notas fiscais
26+
O fluxo de cancelamento de notas fiscais foi aprimorado para lidar melhor com cenários de falha parcial ou inconsistências entre o módulo e a API:
27+
28+
- Identificação de cancelamentos parciais: quando apenas parte da série de NFs é cancelada com sucesso, o módulo exibe uma mensagem de aviso listando as notas que falharam e orientando a verificação da empresa emissora configurada.
29+
- Novo status "Falha no Cancelamento" na listagem de notas, permitindo que administradores identifiquem visualmente notas cuja tentativa de cancelamento não foi concluída com sucesso (referência: #180).
30+
- Ajuste na regra de reemissão para considerar notas com status "Cancelled" e "CancelFailed" como elegíveis, evitando que o fluxo fique travado quando a nota já não existe mais na API, mas permanece registrada localmente.
31+
- Mensagens mais claras quando a API informa que a nota não foi encontrada, sugerindo a verificação de possíveis alterações na empresa emissora configurada para a fatura.
32+
33+
### Correções
34+
- Conversão explícita para string ao salvar o ID e o secret do webhook no armazenamento legado, evitando inconsistências de tipo em ambientes mais recentes. Referência: #182.
35+
- Conversão explícita para string dos campos de configuração de empresa (ID da empresa, CNPJ, código de serviço, código NBS, indicador de operação e classificação tributária) antes da persistência, garantindo maior consistência dos dados e compatibilidade com os novos campos adicionados para a Reforma Tributária.
36+
137
## v3.1.0
238
A versão 3.1.0 implementa o suporte à Reforma Tributária Brasileira, com a inclusão de novos campos obrigatórios para a emissão de notas fiscais de serviço.
339

modules/addons/NFEioServiceInvoices/lib/Admin/Controller.php

Lines changed: 231 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use WHMCS\Exception;
1616
use WHMCSExpert\Template\Template;
1717
use NFEioServiceInvoices\Addon;
18+
use WHMCSExpert\Addon\Storage;
19+
use NFEioServiceInvoices\Configuration;
1820

1921

2022
class Controller
@@ -356,14 +358,14 @@ public function associateCompany($vars)
356358

357359
// salva os dados da empresa
358360
$response = $companyRepository->save(
359-
$company_id,
360-
$company_taxnumber,
361+
(string) $company_id,
362+
(string) $company_taxnumber,
361363
$company_name,
362-
$service_code,
364+
(string) $service_code,
363365
$iss_held,
364-
$nbs_code,
365-
$operation_indicator,
366-
$class_code,
366+
(string) $nbs_code,
367+
(string) $operation_indicator,
368+
(string) $class_code,
367369
$company_default,
368370
);
369371

@@ -984,6 +986,29 @@ public function cancelNf($vars)
984986

985987
if ($response['status'] == 'success') {
986988
$msg->success("Nota(s) fiscal(is) para fatura #{$invoiceId} canceladas. Sincronização do status pode demorar alguns minutos, por favor aguarde.", $redirectUrl);
989+
} elseif ($response['status'] == 'partial') {
990+
// Partial success - some NFs cancelled, some failed
991+
$failedIds = array_map(function ($f) {
992+
return $f['nfe_id'];
993+
}, $response['failures']);
994+
$failedList = implode(', ', $failedIds);
995+
$msg->warning("{$response['message']} Notas com falha: {$failedList}. Verifique se a empresa emissora foi alterada.", $redirectUrl);
996+
} elseif ($response['status'] == 'error') {
997+
// All NFs failed to cancel
998+
$failures = $response['failures'] ?? [];
999+
if (!empty($failures)) {
1000+
$hasNotFoundError = array_reduce($failures, function ($carry, $f) {
1001+
return $carry || ($f['is_not_found'] ?? false);
1002+
}, false);
1003+
1004+
if ($hasNotFoundError) {
1005+
$msg->warning("Nota fiscal não encontrada na API. Verifique se a empresa emissora foi alterada.", $redirectUrl);
1006+
} else {
1007+
$msg->error("Erro ao cancelar nota(s) fiscal(is) para fatura #{$invoiceId}. Verifique os logs para mais detalhes.", $redirectUrl);
1008+
}
1009+
} else {
1010+
$msg->error($response['message'] ?? "Erro ao cancelar nota(s) fiscal(is).", $redirectUrl);
1011+
}
9871012
} else {
9881013
$msg->info($response['message'], $redirectUrl);
9891014
}
@@ -1114,8 +1139,208 @@ public function about($vars)
11141139
$msg->display();
11151140
}
11161141

1142+
// Recuperar dados do webhook do Storage
1143+
$config = new Configuration();
1144+
$storage = new Storage($config->getStorageKey());
1145+
$webhookId = $storage->get('webhook_id');
1146+
$webhookSecret = $storage->get('webhook_secret');
1147+
$lastVerified = $storage->get('webhook_last_verified_at');
1148+
1149+
// Gerar URL do callback
1150+
$callbackUrl = Addon::getCallBackPath();
1151+
1152+
// Mascarar secret (apenas primeiros 8 caracteres)
1153+
$secretMasked = null;
1154+
if ($webhookSecret && strlen($webhookSecret) > 0) {
1155+
$secretMasked = substr($webhookSecret, 0, 8) . '...';
1156+
}
1157+
1158+
// Formatar timestamp de última verificação
1159+
$lastVerifiedFormatted = null;
1160+
if ($lastVerified) {
1161+
try {
1162+
$dt = new \DateTime($lastVerified);
1163+
$lastVerifiedFormatted = $dt->format('d/m/Y H:i:s');
1164+
} catch (\Exception $e) {
1165+
$lastVerifiedFormatted = $lastVerified; // fallback para valor original
1166+
}
1167+
}
1168+
1169+
// Construir array de dados do webhook
1170+
$vars['webhook'] = [
1171+
'id' => $webhookId,
1172+
'secret_masked' => $secretMasked,
1173+
'url' => $callbackUrl,
1174+
'last_verified' => $lastVerifiedFormatted,
1175+
'configured' => !empty($webhookId)
1176+
];
1177+
11171178
$vars['assetsURL'] = $assetsURL;
11181179

11191180
return $template->fetch('about', $vars);
11201181
}
1182+
1183+
/**
1184+
* Verifica o status do webhook na API NFE.io
1185+
*
1186+
* Este método realiza a verificação on-demand do webhook configurado,
1187+
* validando sua existência, consistência de URL e status ativo na API NFE.io.
1188+
* Registra logs detalhados e exibe mensagens de feedback via FlashMessages.
1189+
*
1190+
* @param array $vars Variáveis fornecidas pelo WHMCS
1191+
* @return void Redireciona para a página Sobre após verificação
1192+
* @throws \Exception Em caso de erros inesperados
1193+
* @since 3.1.1
1194+
* @version 3.1.1
1195+
*/
1196+
public function verifyWebhook($vars)
1197+
{
1198+
// Verificar autenticação de administrador
1199+
Addon::I()->isAdmin(true);
1200+
1201+
$config = new Configuration();
1202+
$storage = new Storage($config->getStorageKey());
1203+
$msg = new FlashMessages();
1204+
$redirectUrl = $vars['modulelink'] . '&action=about';
1205+
1206+
// Recuperar webhook_id do Storage
1207+
$webhookId = $storage->get('webhook_id');
1208+
1209+
// Verificar se webhook está configurado
1210+
if (empty($webhookId)) {
1211+
$msg->info(
1212+
'Webhook não configurado. Será criado automaticamente na primeira emissão de nota fiscal.',
1213+
$redirectUrl
1214+
);
1215+
return;
1216+
}
1217+
1218+
try {
1219+
// Instanciar classe Nfe e buscar webhook na API
1220+
$nfe = new \NFEioServiceInvoices\NFEio\Nfe();
1221+
$webhook = $nfe->getWebhook($webhookId);
1222+
1223+
// Verificar se webhook existe (não retornou erro)
1224+
if (is_array($webhook) && isset($webhook['error'])) {
1225+
// Webhook não encontrado na API
1226+
$errorMessage = isset($webhook['message']) ? $webhook['message'] : 'Erro desconhecido';
1227+
1228+
logModuleCall(
1229+
'nfeio_serviceinvoices',
1230+
'webhook_verify_notfound',
1231+
['webhook_id' => $webhookId],
1232+
$errorMessage
1233+
);
1234+
1235+
$msg->warning(
1236+
'Webhook não encontrado na API. Será recriado automaticamente na próxima emissão de nota fiscal.',
1237+
$redirectUrl
1238+
);
1239+
return;
1240+
}
1241+
1242+
// Webhook encontrado - validar configuração
1243+
$callbackUrl = Addon::getCallBackPath();
1244+
$apiWebhookUrl = isset($webhook->hooks->url) ? $webhook->hooks->url : null;
1245+
1246+
// Verificar consistência de URL
1247+
if ($apiWebhookUrl !== $callbackUrl) {
1248+
logModuleCall(
1249+
'nfeio_serviceinvoices',
1250+
'webhook_verify_url_mismatch',
1251+
[
1252+
'webhook_id' => $webhookId,
1253+
'local_url' => $callbackUrl,
1254+
'api_url' => $apiWebhookUrl
1255+
],
1256+
$webhook
1257+
);
1258+
1259+
$msg->warning(
1260+
"URL do webhook não corresponde ao esperado.<br>" .
1261+
"API: <code>{$apiWebhookUrl}</code><br>" .
1262+
"Local: <code>{$callbackUrl}</code><br>" .
1263+
"Considere emitir uma nova NF para recriar o webhook.",
1264+
$redirectUrl
1265+
);
1266+
return;
1267+
}
1268+
1269+
// Verificar status do webhook (ativo/inativo/deleted)
1270+
$webhookStatus = isset($webhook->hooks->status) ? $webhook->hooks->status : 'unknown';
1271+
if (in_array(strtolower($webhookStatus), ['deleted', 'disabled', 'inactive'])) {
1272+
logModuleCall(
1273+
'nfeio_serviceinvoices',
1274+
'webhook_verify_error',
1275+
['webhook_id' => $webhookId, 'status' => $webhookStatus],
1276+
$webhook
1277+
);
1278+
1279+
$msg->error(
1280+
"Webhook marcado como inativo na API. Status: {$webhookStatus}<br>" .
1281+
"Emita uma nova nota fiscal para recriar o webhook.",
1282+
$redirectUrl
1283+
);
1284+
return;
1285+
}
1286+
1287+
// Verificação bem-sucedida - atualizar timestamp
1288+
$now = new \DateTime('now', new \DateTimeZone('America/Sao_Paulo'));
1289+
$storage->set('webhook_last_verified_at', $now->format('c')); // ISO8601 com timezone
1290+
1291+
logModuleCall(
1292+
'nfeio_serviceinvoices',
1293+
'webhook_verify_success',
1294+
[
1295+
'webhook_id' => $webhookId,
1296+
'expected_url' => $callbackUrl
1297+
],
1298+
$webhook
1299+
);
1300+
1301+
$msg->success(
1302+
'Webhook verificado com sucesso! Configuração está correta na API NFE.io.',
1303+
$redirectUrl
1304+
);
1305+
1306+
} catch (\Exception $e) {
1307+
// Tratar erros de comunicação com API ou exceções inesperadas
1308+
$errorMessage = $e->getMessage();
1309+
$errorCode = $e->getCode();
1310+
1311+
// Verificar se é erro de autenticação
1312+
if ($errorCode == 401 || $errorCode == 403) {
1313+
logModuleCall(
1314+
'nfeio_serviceinvoices',
1315+
'webhook_verify_error',
1316+
['webhook_id' => $webhookId, 'error_code' => $errorCode],
1317+
['error' => $errorMessage]
1318+
);
1319+
1320+
$msg->error(
1321+
'Erro de autenticação. Verifique se a API Key está correta na configuração do módulo.',
1322+
$redirectUrl
1323+
);
1324+
return;
1325+
}
1326+
1327+
// Erro genérico (timeout, connection, etc)
1328+
logModuleCall(
1329+
'nfeio_serviceinvoices',
1330+
'webhook_verify_error',
1331+
[
1332+
'webhook_id' => $webhookId,
1333+
'error_code' => $errorCode,
1334+
'error_message' => $errorMessage
1335+
],
1336+
$e->getTraceAsString()
1337+
);
1338+
1339+
$msg->error(
1340+
'Erro ao conectar com API NFE.io. Verifique sua conexão ou tente novamente mais tarde.<br>' .
1341+
'Consulte os logs do módulo para mais detalhes.',
1342+
$redirectUrl
1343+
);
1344+
}
1345+
}
11211346
}

modules/addons/NFEioServiceInvoices/lib/Configuration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ final class Configuration extends \WHMCSExpert\mtLibs\process\AbstractConfigurat
2222

2323
private $encryptHash = '';
2424

25-
public $version = '3.1.0';
25+
public $version = '3.1.1';
2626

2727
public $tablePrefix = 'mod_nfeio_si_';
2828

modules/addons/NFEioServiceInvoices/lib/Legacy/Functions.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ function gnfe_issue_nfe($postfields, $companyId)
164164
if (!$newHook) {
165165
return (object)['message' => 'Erro ao criar novo webhook'];
166166
}
167-
$storage->set('webhook_id', $newHook->hooks->id);
168-
$storage->set('webhook_secret', $newHook->hooks->secret);
167+
$storage->set('webhook_id', (string) $newHook->hooks->id);
168+
$storage->set('webhook_secret', (string) $newHook->hooks->secret);
169169
}
170170

171171

0 commit comments

Comments
 (0)