|
15 | 15 | use WHMCS\Exception; |
16 | 16 | use WHMCSExpert\Template\Template; |
17 | 17 | use NFEioServiceInvoices\Addon; |
| 18 | +use WHMCSExpert\Addon\Storage; |
| 19 | +use NFEioServiceInvoices\Configuration; |
18 | 20 |
|
19 | 21 |
|
20 | 22 | class Controller |
@@ -356,14 +358,14 @@ public function associateCompany($vars) |
356 | 358 |
|
357 | 359 | // salva os dados da empresa |
358 | 360 | $response = $companyRepository->save( |
359 | | - $company_id, |
360 | | - $company_taxnumber, |
| 361 | + (string) $company_id, |
| 362 | + (string) $company_taxnumber, |
361 | 363 | $company_name, |
362 | | - $service_code, |
| 364 | + (string) $service_code, |
363 | 365 | $iss_held, |
364 | | - $nbs_code, |
365 | | - $operation_indicator, |
366 | | - $class_code, |
| 366 | + (string) $nbs_code, |
| 367 | + (string) $operation_indicator, |
| 368 | + (string) $class_code, |
367 | 369 | $company_default, |
368 | 370 | ); |
369 | 371 |
|
@@ -984,6 +986,29 @@ public function cancelNf($vars) |
984 | 986 |
|
985 | 987 | if ($response['status'] == 'success') { |
986 | 988 | $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 | + } |
987 | 1012 | } else { |
988 | 1013 | $msg->info($response['message'], $redirectUrl); |
989 | 1014 | } |
@@ -1114,8 +1139,208 @@ public function about($vars) |
1114 | 1139 | $msg->display(); |
1115 | 1140 | } |
1116 | 1141 |
|
| 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 | + |
1117 | 1178 | $vars['assetsURL'] = $assetsURL; |
1118 | 1179 |
|
1119 | 1180 | return $template->fetch('about', $vars); |
1120 | 1181 | } |
| 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 | + } |
1121 | 1346 | } |
0 commit comments