1use super::consulta::ResultadoConsulta;
6use crate::certificado::{CertificadoA1, AssinadorXml};
7use reqwest::Client;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum AmbienteNfe {
13 Producao = 1,
14 Homologacao = 2,
15}
16
17#[derive(Debug, Clone, Copy)]
19pub enum ServicoNfe {
20 Autorizacao,
21 RetAutorizacao,
22 ConsultaProtocolo,
23 Inutilizacao,
24 RecepcaoEvento,
25 StatusServico,
26}
27
28pub struct WebServiceUrls {
30 pub uf: String,
31 pub ambiente: AmbienteNfe,
32}
33
34impl WebServiceUrls {
35 pub fn new(uf: &str, ambiente: AmbienteNfe) -> Self {
36 Self {
37 uf: uf.to_uppercase(),
38 ambiente,
39 }
40 }
41
42 pub fn get_url(&self, servico: ServicoNfe) -> String {
43 let base = self.get_base_url();
44 let service = match servico {
45 ServicoNfe::Autorizacao => "NFeAutorizacao4",
46 ServicoNfe::RetAutorizacao => "NFeRetAutorizacao4",
47 ServicoNfe::ConsultaProtocolo => "NFeConsultaProtocolo4",
48 ServicoNfe::Inutilizacao => "NFeInutilizacao4",
49 ServicoNfe::RecepcaoEvento => "NFeRecepcaoEvento4",
50 ServicoNfe::StatusServico => "NFeStatusServico4",
51 };
52 format!("{}/{}", base, service)
53 }
54
55 fn get_base_url(&self) -> String {
56 let is_prod = self.ambiente == AmbienteNfe::Producao;
57 match self.uf.as_str() {
58 "SP" => if is_prod { "https://nfe.fazenda.sp.gov.br/ws" } else { "https://homologacao.nfe.fazenda.sp.gov.br/ws" },
59 "RS" => if is_prod { "https://nfe.sefazrs.rs.gov.br/ws" } else { "https://nfe-homologacao.sefazrs.rs.gov.br/ws" },
60 "MG" => if is_prod { "https://nfe.fazenda.mg.gov.br/nfe2/services" } else { "https://hnfe.fazenda.mg.gov.br/nfe2/services" },
61 "PR" => if is_prod { "https://nfe.sefa.pr.gov.br/nfe/NFeServices" } else { "https://homologacao.nfe.sefa.pr.gov.br/nfe/NFeServices" },
62 _ => if is_prod { "https://nfe.svrs.rs.gov.br/ws" } else { "https://nfe-homologacao.svrs.rs.gov.br/ws" },
63 }.to_string()
64 }
65}
66
67pub struct SefazClient {
69 certificado: CertificadoA1,
70 http_client: Client,
71 ambiente: AmbienteNfe,
72 uf: String,
73}
74
75impl SefazClient {
76 pub fn new(certificado: CertificadoA1, uf: &str, ambiente: AmbienteNfe) -> Result<Self, String> {
78 let identity = reqwest::Identity::from_pkcs12_der(
80 certificado.pfx_bytes(),
81 certificado.senha()
82 ).map_err(|e| format!("Erro ao criar identidade: {}", e))?;
83
84 let http_client = Client::builder()
85 .identity(identity)
86 .danger_accept_invalid_certs(false)
87 .build()
88 .map_err(|e| format!("Erro ao criar cliente HTTP: {}", e))?;
89
90 Ok(Self {
91 certificado,
92 http_client,
93 ambiente,
94 uf: uf.to_uppercase(),
95 })
96 }
97
98 pub async fn status_servico(&self) -> Result<StatusServicoResult, String> {
100 let urls = WebServiceUrls::new(&self.uf, self.ambiente);
101 let url = urls.get_url(ServicoNfe::StatusServico);
102 let envelope = self.criar_envelope_status();
103
104 let response = self.enviar_soap(&url, &envelope, "nfeStatusServicoNF").await?;
105 self.parsear_status_servico(&response)
106 }
107
108 pub async fn consultar_nfe(&self, chave_acesso: &str) -> Result<ResultadoConsulta, String> {
110 let urls = WebServiceUrls::new(&self.uf, self.ambiente);
111 let url = urls.get_url(ServicoNfe::ConsultaProtocolo);
112 let envelope = self.criar_envelope_consulta(chave_acesso);
113
114 let response = self.enviar_soap(&url, &envelope, "nfeConsultaNF").await?;
115 self.parsear_consulta(&response)
116 }
117
118 pub async fn autorizar_nfe(&self, xml_nfe: &str) -> Result<AutorizacaoResult, String> {
120 let urls = WebServiceUrls::new(&self.uf, self.ambiente);
121 let url = urls.get_url(ServicoNfe::Autorizacao);
122
123 let assinador = AssinadorXml::new(self.certificado.clone());
125 let xml_assinado = assinador.assinar_nfe(xml_nfe)?;
126
127 let lote_id = uuid::Uuid::new_v4().to_string().replace("-", "")[..15].to_string();
129 let xml_lote = self.criar_lote_nfe(&lote_id, &xml_assinado);
130 let envelope = self.criar_envelope_autorizacao(&xml_lote);
131
132 let response = self.enviar_soap(&url, &envelope, "nfeAutorizacaoLote").await?;
133 self.parsear_autorizacao(&response)
134 }
135
136 pub async fn cancelar_nfe(
138 &self,
139 chave_acesso: &str,
140 protocolo: &str,
141 justificativa: &str,
142 ) -> Result<EventoResult, String> {
143 let urls = WebServiceUrls::new(&self.uf, self.ambiente);
144 let url = urls.get_url(ServicoNfe::RecepcaoEvento);
145
146 let xml_evento = self.criar_evento_cancelamento(chave_acesso, protocolo, justificativa)?;
147 let assinador = AssinadorXml::new(self.certificado.clone());
148 let xml_assinado = assinador.assinar_evento(&xml_evento)?;
149 let envelope = self.criar_envelope_evento(&xml_assinado);
150
151 let response = self.enviar_soap(&url, &envelope, "nfeRecepcaoEvento").await?;
152 self.parsear_evento(&response)
153 }
154
155 pub async fn carta_correcao(
157 &self,
158 chave_acesso: &str,
159 sequencia: u32,
160 correcao: &str,
161 ) -> Result<EventoResult, String> {
162 let urls = WebServiceUrls::new(&self.uf, self.ambiente);
163 let url = urls.get_url(ServicoNfe::RecepcaoEvento);
164
165 let xml_evento = self.criar_evento_cce(chave_acesso, sequencia, correcao)?;
166 let assinador = AssinadorXml::new(self.certificado.clone());
167 let xml_assinado = assinador.assinar_evento(&xml_evento)?;
168 let envelope = self.criar_envelope_evento(&xml_assinado);
169
170 let response = self.enviar_soap(&url, &envelope, "nfeRecepcaoEvento").await?;
171 self.parsear_evento(&response)
172 }
173
174 async fn enviar_soap(&self, url: &str, envelope: &str, action: &str) -> Result<String, String> {
175 let response = self.http_client
176 .post(url)
177 .header("Content-Type", "application/soap+xml; charset=utf-8")
178 .header("SOAPAction", action)
179 .body(envelope.to_string())
180 .send()
181 .await
182 .map_err(|e| format!("Erro na requisição SOAP: {}", e))?;
183
184 if !response.status().is_success() {
185 return Err(format!("SEFAZ retornou erro HTTP: {}", response.status()));
186 }
187
188 response.text().await.map_err(|e| format!("Erro ao ler resposta: {}", e))
189 }
190
191 fn criar_envelope_status(&self) -> String {
192 let tp_amb = if self.ambiente == AmbienteNfe::Producao { "1" } else { "2" };
193 let c_uf = self.get_codigo_uf();
194 format!(r#"<?xml version="1.0" encoding="UTF-8"?>
195<soap12:Envelope xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
196 <soap12:Body>
197 <nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4">
198 <consStatServ versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
199 <tpAmb>{tp_amb}</tpAmb>
200 <cUF>{c_uf}</cUF>
201 <xServ>STATUS</xServ>
202 </consStatServ>
203 </nfeDadosMsg>
204 </soap12:Body>
205</soap12:Envelope>"#)
206 }
207
208 fn criar_envelope_consulta(&self, chave_acesso: &str) -> String {
209 let tp_amb = if self.ambiente == AmbienteNfe::Producao { "1" } else { "2" };
210 format!(r#"<?xml version="1.0" encoding="UTF-8"?>
211<soap12:Envelope xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
212 <soap12:Body>
213 <nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4">
214 <consSitNFe versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
215 <tpAmb>{tp_amb}</tpAmb>
216 <xServ>CONSULTAR</xServ>
217 <chNFe>{chave_acesso}</chNFe>
218 </consSitNFe>
219 </nfeDadosMsg>
220 </soap12:Body>
221</soap12:Envelope>"#)
222 }
223
224 fn criar_lote_nfe(&self, lote_id: &str, xml_nfe: &str) -> String {
225 format!(r#"<enviNFe versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
226 <idLote>{lote_id}</idLote>
227 <indSinc>1</indSinc>
228 {xml_nfe}
229</enviNFe>"#)
230 }
231
232 fn criar_envelope_autorizacao(&self, xml_lote: &str) -> String {
233 format!(r#"<?xml version="1.0" encoding="UTF-8"?>
234<soap12:Envelope xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
235 <soap12:Body>
236 <nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4">
237 {xml_lote}
238 </nfeDadosMsg>
239 </soap12:Body>
240</soap12:Envelope>"#)
241 }
242
243 fn criar_evento_cancelamento(&self, chave_acesso: &str, protocolo: &str, justificativa: &str) -> Result<String, String> {
244 let tp_amb = if self.ambiente == AmbienteNfe::Producao { "1" } else { "2" };
245 let cnpj = self.certificado.info.cnpj.clone().unwrap_or_default();
246 let dh_evento = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S-03:00").to_string();
247 let c_orgao = &chave_acesso[0..2];
248
249 Ok(format!(r#"<evento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
250 <infEvento Id="ID110111{chave_acesso}01">
251 <cOrgao>{c_orgao}</cOrgao>
252 <tpAmb>{tp_amb}</tpAmb>
253 <CNPJ>{cnpj}</CNPJ>
254 <chNFe>{chave_acesso}</chNFe>
255 <dhEvento>{dh_evento}</dhEvento>
256 <tpEvento>110111</tpEvento>
257 <nSeqEvento>1</nSeqEvento>
258 <verEvento>1.00</verEvento>
259 <detEvento versao="1.00">
260 <descEvento>Cancelamento</descEvento>
261 <nProt>{protocolo}</nProt>
262 <xJust>{justificativa}</xJust>
263 </detEvento>
264 </infEvento>
265</evento>"#))
266 }
267
268 fn criar_evento_cce(&self, chave_acesso: &str, sequencia: u32, correcao: &str) -> Result<String, String> {
269 let tp_amb = if self.ambiente == AmbienteNfe::Producao { "1" } else { "2" };
270 let cnpj = self.certificado.info.cnpj.clone().unwrap_or_default();
271 let dh_evento = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S-03:00").to_string();
272 let c_orgao = &chave_acesso[0..2];
273
274 Ok(format!(r#"<evento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
275 <infEvento Id="ID110110{chave_acesso}{sequencia:02}">
276 <cOrgao>{c_orgao}</cOrgao>
277 <tpAmb>{tp_amb}</tpAmb>
278 <CNPJ>{cnpj}</CNPJ>
279 <chNFe>{chave_acesso}</chNFe>
280 <dhEvento>{dh_evento}</dhEvento>
281 <tpEvento>110110</tpEvento>
282 <nSeqEvento>{sequencia}</nSeqEvento>
283 <verEvento>1.00</verEvento>
284 <detEvento versao="1.00">
285 <descEvento>Carta de Correcao</descEvento>
286 <xCorrecao>{correcao}</xCorrecao>
287 <xCondUso>A Carta de Correcao e disciplinada pelo paragrafo 1o-A do art. 7o do Convenio S/N, de 15 de dezembro de 1970...</xCondUso>
288 </detEvento>
289 </infEvento>
290</evento>"#))
291 }
292
293 fn criar_envelope_evento(&self, xml_evento: &str) -> String {
294 format!(r#"<?xml version="1.0" encoding="UTF-8"?>
295<soap12:Envelope xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
296 <soap12:Body>
297 <nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4">
298 <envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
299 <idLote>1</idLote>
300 {xml_evento}
301 </envEvento>
302 </nfeDadosMsg>
303 </soap12:Body>
304</soap12:Envelope>"#)
305 }
306
307 fn get_codigo_uf(&self) -> &str {
308 match self.uf.as_str() {
309 "AC" => "12", "AL" => "27", "AP" => "16", "AM" => "13", "BA" => "29",
310 "CE" => "23", "DF" => "53", "ES" => "32", "GO" => "52", "MA" => "21",
311 "MT" => "51", "MS" => "50", "MG" => "31", "PA" => "15", "PB" => "25",
312 "PR" => "41", "PE" => "26", "PI" => "22", "RJ" => "33", "RN" => "24",
313 "RS" => "43", "RO" => "11", "RR" => "14", "SC" => "42", "SP" => "35",
314 "SE" => "28", "TO" => "17",
315 _ => "35",
316 }
317 }
318
319 fn parsear_status_servico(&self, xml: &str) -> Result<StatusServicoResult, String> {
320 let c_stat = extract_xml_value(xml, "cStat").unwrap_or_default();
321 let x_motivo = extract_xml_value(xml, "xMotivo").unwrap_or_default();
322 let dh_recbto = extract_xml_value(xml, "dhRecbto");
323 let t_med = extract_xml_value(xml, "tMed").and_then(|s| s.parse().ok());
324
325 Ok(StatusServicoResult {
326 codigo_status: c_stat.clone(),
327 motivo: x_motivo,
328 data_hora: dh_recbto,
329 tempo_medio: t_med,
330 online: c_stat == "107",
331 })
332 }
333
334 fn parsear_consulta(&self, xml: &str) -> Result<ResultadoConsulta, String> {
335 let c_stat = extract_xml_value(xml, "cStat").unwrap_or_default();
336 let x_motivo = extract_xml_value(xml, "xMotivo").unwrap_or_default();
337 let ch_nfe = extract_xml_value(xml, "chNFe");
338 let n_prot = extract_xml_value(xml, "nProt");
339 let dh_recbto = extract_xml_value(xml, "dhRecbto");
340
341 let situacao = match c_stat.as_str() {
342 "100" => Some("Autorizada".to_string()),
343 "101" => Some("Cancelada".to_string()),
344 "110" => Some("Denegada".to_string()),
345 _ => Some(x_motivo.clone()),
346 };
347
348 Ok(ResultadoConsulta {
349 sucesso: c_stat == "100" || c_stat == "101",
350 codigo_status: Some(c_stat),
351 motivo: Some(x_motivo),
352 chave_acesso: ch_nfe,
353 situacao,
354 data_autorizacao: dh_recbto,
355 protocolo: n_prot,
356 numero: None,
357 serie: None,
358 emit_cnpj: None,
359 emit_razao_social: None,
360 valor_total: None,
361 url_consulta: None,
362 })
363 }
364
365 fn parsear_autorizacao(&self, xml: &str) -> Result<AutorizacaoResult, String> {
366 let c_stat = extract_xml_value(xml, "cStat").unwrap_or_default();
367 let x_motivo = extract_xml_value(xml, "xMotivo").unwrap_or_default();
368 let ch_nfe = extract_xml_value(xml, "chNFe");
369 let n_prot = extract_xml_value(xml, "nProt");
370
371 Ok(AutorizacaoResult {
372 sucesso: c_stat == "100" || c_stat == "104",
373 codigo_status: c_stat.clone(),
374 motivo: x_motivo,
375 chave_acesso: ch_nfe,
376 protocolo: n_prot,
377 xml_autorizado: if c_stat == "100" { Some(xml.to_string()) } else { None },
378 })
379 }
380
381 fn parsear_evento(&self, xml: &str) -> Result<EventoResult, String> {
382 let c_stat = extract_xml_value(xml, "cStat").unwrap_or_default();
383 let x_motivo = extract_xml_value(xml, "xMotivo").unwrap_or_default();
384 let n_prot = extract_xml_value(xml, "nProt");
385 let dh_reg = extract_xml_value(xml, "dhRegEvento");
386
387 Ok(EventoResult {
388 sucesso: c_stat == "135" || c_stat == "136",
389 codigo_status: c_stat,
390 motivo: x_motivo,
391 protocolo: n_prot,
392 data_evento: dh_reg,
393 })
394 }
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct StatusServicoResult {
401 pub codigo_status: String,
402 pub motivo: String,
403 pub data_hora: Option<String>,
404 pub tempo_medio: Option<u32>,
405 pub online: bool,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
409pub struct AutorizacaoResult {
410 pub sucesso: bool,
411 pub codigo_status: String,
412 pub motivo: String,
413 pub chave_acesso: Option<String>,
414 pub protocolo: Option<String>,
415 pub xml_autorizado: Option<String>,
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct EventoResult {
420 pub sucesso: bool,
421 pub codigo_status: String,
422 pub motivo: String,
423 pub protocolo: Option<String>,
424 pub data_evento: Option<String>,
425}
426
427fn extract_xml_value(xml: &str, tag: &str) -> Option<String> {
428 let start_tag = format!("<{}>", tag);
429 let end_tag = format!("</{}>", tag);
430 let start = xml.find(&start_tag)?;
431 let value_start = start + start_tag.len();
432 let end = xml[value_start..].find(&end_tag)?;
433 Some(xml[value_start..value_start + end].to_string())
434}