// Certa Fiscal — exemplo de integração em C# (.NET 6+, sem pacotes externos). // // Uso: dotnet run -- cf_test_SUACHAVE ULID_DA_EMPRESA // (ou compile: csc CertaFiscalExemplo.cs && CertaFiscalExemplo.exe ...) // // Demonstra: Bearer, Idempotency-Key (retry nunca duplica), X-Correlation-Id, // retry com backoff p/ 429/5xx e leitura dos achados. using System; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading.Tasks; public static class CertaFiscalExemplo { private const string BaseUrl = "http://127.0.0.1:8000/api/v1"; // produção: https://.../api/v1 public static async Task Main(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Uso: CertaFiscalExemplo "); return 2; } var apiKey = args[0]; var empresaId = args[1]; using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // 1) Health-check var (statusSaude, _) = await Chamar(http, HttpMethod.Get, "/health", null, ""); if (statusSaude != 200) { Console.Error.WriteLine($"API indisponível (HTTP {statusSaude})"); return 3; } Console.WriteLine("Health-check: OK"); // 2) Analisar operação — no ERP, use o ID do teu registro como chave. var idempotencyKey = $"pedido-{Guid.NewGuid():N}"; var corpo = JsonSerializer.Serialize(new { company_id = empresaId, origin_uf = "RS", destination_uf = "SC", ncm = "22021000", cest = "0301100", cfop = "6102", csosn = "102", value = 1500.00, product_description = "Refrigerante Cola 2 L (exemplo C#)", }); var (status, json) = await Chamar(http, HttpMethod.Post, "/analyses/operation", corpo, idempotencyKey); if (status != 200 && status != 201) { Console.Error.WriteLine($"Erro HTTP {status}: {json}"); return 1; } using var doc = JsonDocument.Parse(json); var dados = doc.RootElement.GetProperty("data"); Console.WriteLine($"Análise {dados.GetProperty("reference")} | risco={dados.GetProperty("risk_level")} " + $"| pontuação={dados.GetProperty("score")} | motor={dados.GetProperty("engine_version")}"); var bloquear = false; foreach (var achado in dados.GetProperty("findings").EnumerateArray()) { var severidade = achado.GetProperty("severity").GetString(); Console.WriteLine($" [{severidade} {achado.GetProperty("rule_code")}] {achado.GetProperty("title")} " + $"(confiança {achado.GetProperty("confidence")}%)"); Console.WriteLine($" Sugestão: {achado.GetProperty("suggestion")}"); if (severidade is "high" or "critical") bloquear = true; } Console.WriteLine(bloquear ? "VEREDITO: NÃO emitir — corrigir os achados primeiro." : "VEREDITO: liberado para emitir."); return 0; } private static async Task<(int Status, string Body)> Chamar( HttpClient http, HttpMethod metodo, string caminho, string? corpoJson, string idempotencyKey) { for (var tentativa = 1; ; tentativa++) { using var req = new HttpRequestMessage(metodo, BaseUrl + caminho); req.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString("N")); if (!string.IsNullOrEmpty(idempotencyKey)) req.Headers.Add("Idempotency-Key", idempotencyKey); if (corpoJson is not null) req.Content = new StringContent(corpoJson, Encoding.UTF8, "application/json"); int status; string body = ""; try { using var resp = await http.SendAsync(req); status = (int)resp.StatusCode; body = await resp.Content.ReadAsStringAsync(); } catch (Exception) // falha de rede/timeout { status = 0; } // 401/403/422 = configuração/dados: não repetir. var repetir = (status == 0 || status == 429 || status >= 500) && tentativa < 4; if (!repetir) return (status, body); var espera = TimeSpan.FromSeconds(Math.Pow(2, tentativa - 1)); // 1s, 2s, 4s Console.Error.WriteLine($"HTTP {status} — repetindo em {espera.TotalSeconds}s (mesma Idempotency-Key)..."); await Task.Delay(espera); } } }