use crate::error::{DfeError, Result};
use crate::interno::cert::{Cert, DigestValue, RawPubKey, Sign};
use crate::interno::chave_acesso::ChaveAcesso;
use crate::interno::cleaner::Strings;
use crate::interno::connection::WebService;
use crate::interno::dates::get_current_date_time;
use crate::interno::ws::nfe_recepcao_evento;
use crate::tipos::cancelar::{InfEvento, Response};
use quick_xml::de;
use quick_xml::events::BytesText;
use quick_xml::writer::Writer;
use std::fs::File;
use std::io::Cursor;
use std::io::Write;
const TP_EVENTO: &str = "110111";
pub struct CancelarBuilder {
cert_path: Option<String>,
cert_pass: Option<String>,
tp_amb: Option<u8>,
mod_: Option<u32>,
chave: Option<String>,
protocolo: Option<String>,
justificativa: Option<String>,
}
impl CancelarBuilder {
pub fn new() -> Self {
Self {
cert_path: None, cert_pass: None, tp_amb: None, mod_: None,
chave: None, protocolo: None, justificativa: None,
}
}
pub fn cert(mut self, path: &str, pass: &str) -> Self {
self.cert_path = Some(path.to_string());
self.cert_pass = Some(pass.to_string());
self
}
pub fn tp_amb(mut self, v: u8) -> Self { self.tp_amb = Some(v); self }
pub fn mod_(mut self, v: u32) -> Self { self.mod_ = Some(v); self }
pub fn chave(mut self, v: &str) -> Self { self.chave = Some(v.to_string()); self }
pub fn protocolo(mut self, v: &str) -> Self { self.protocolo = Some(v.to_string()); self }
pub fn justificativa(mut self, v: &str) -> Self { self.justificativa = Some(v.to_string()); self }
pub async fn send(self) -> Result<Response> {
let cert_path = self.cert_path .ok_or_else(|| DfeError::Configuracao("cert_path não informado".to_string()))?;
let cert_pass = self.cert_pass .ok_or_else(|| DfeError::Configuracao("cert_pass não informado".to_string()))?;
let tp_amb = self.tp_amb .ok_or_else(|| DfeError::Configuracao("tp_amb não informado".to_string()))?;
let chave = self.chave .ok_or_else(|| DfeError::Validacao("chave não informada".to_string()))?;
let protocolo = self.protocolo .ok_or_else(|| DfeError::Validacao("protocolo não informado".to_string()))?;
let justificativa = self.justificativa.ok_or_else(|| DfeError::Validacao("justificativa não informada".to_string()))?;
let mod_ = self.mod_.unwrap_or(55);
if justificativa.len() < 15 {
return Err(DfeError::Validacao("justificativa deve ter no mínimo 15 caracteres".to_string()));
}
cancelar_nfe(cert_path, cert_pass, tp_amb, mod_, chave, protocolo, justificativa).await
}
}
async fn cancelar_nfe(
cert_path: String, cert_pass: String,
tp_amb: u8, mod_: u32,
chave: String, protocolo: String, justificativa: String,
) -> Result<Response> {
let inf_evento_xml = inf_evento_xml(&chave, tp_amb, &protocolo, &justificativa)?;
let inf_evento_xml = Strings::clear_xml_string(&inf_evento_xml);
let digest_value = DigestValue::sha1(&inf_evento_xml)?;
let signed_info = signed_info_xml(&digest_value, &chave)?;
let signature_base64 = Sign::xml_string(&signed_info, &cert_path, &cert_pass).await?;
let x509_cert = RawPubKey::get_from_file(&cert_path, &cert_pass).await?;
let signature = signature_xml(&signed_info, &signature_base64, &x509_cert)?;
let envelope = envelope_xml(&inf_evento_xml, &signature)?;
let envelope = Strings::clear_xml_string(&envelope);
let mut file = File::create("cancelar.xml")?;
file.write_all(envelope.as_bytes())?;
let url = nfe_recepcao_evento(tp_amb, "SP", mod_, false)?;
let cert = Cert::from_pfx(&cert_path, &cert_pass)?;
let client = WebService::client(cert.identity)?;
let send_envelope = envelope.clone();
let response = client
.post(url)
.header("Content-Type", "application/soap+xml; charset=utf-8")
.header("Content-Length", envelope.len().to_string())
.body(envelope)
.send()
.await?;
let response = response.text().await?;
let mut file = File::create("cancelar_response.xml")?;
file.write_all(response.as_bytes())?;
let re = regex::bytes::Regex::new(r"(?s)<infEvento.*?</infEvento>").unwrap();
match re.captures(response.as_bytes()) {
Some(captures) => {
let inf_evento = captures.get(0)
.map_or("", |m| std::str::from_utf8(m.as_bytes()).unwrap());
match de::from_str::<InfEvento>(inf_evento) {
Ok(parsed) => Ok(Response { response: parsed, send_xml: send_envelope, receive_xml: response }),
Err(_) => Err(DfeError::Xml("Erro ao converter xml para struct".to_string())),
}
}
None => Err(DfeError::Xml("Erro ao capturar infEvento".to_string())),
}
}
fn inf_evento_xml(chave: &str, tp_amb: u8, protocolo: &str, justificativa: &str) -> Result<String> {
let lote_seq = 1u32;
let inf_evento_id = format!("ID{}{}{:>02}", TP_EVENTO, chave, lote_seq);
let chave_comp = ChaveAcesso::extract_composition(chave)
.map_err(|e| DfeError::Xml(e.to_string()))?;
let c_orgao = &chave_comp.uf_code;
let tp_amb_str = tp_amb.to_string();
let dh_evento = get_current_date_time();
let n_seq_evento = lote_seq.to_string();
let ver_evento = "1.00";
let mut writer = Writer::new(Cursor::new(Vec::new()));
writer.create_element("infEvento")
.with_attribute(("xmlns", "http://www.portalfiscal.inf.br/nfe"))
.with_attribute(("Id", inf_evento_id.as_str()))
.write_inner_content(|w| {
w.create_element("cOrgao") .write_text_content(BytesText::new(c_orgao))?;
w.create_element("tpAmb") .write_text_content(BytesText::new(&tp_amb_str))?;
w.create_element("CNPJ") .write_text_content(BytesText::new(&chave_comp.doc))?;
w.create_element("chNFe") .write_text_content(BytesText::new(chave))?;
w.create_element("dhEvento") .write_text_content(BytesText::new(&dh_evento))?;
w.create_element("tpEvento") .write_text_content(BytesText::new(TP_EVENTO))?;
w.create_element("nSeqEvento").write_text_content(BytesText::new(&n_seq_evento))?;
w.create_element("verEvento").write_text_content(BytesText::new(ver_evento))?;
w.create_element("detEvento")
.with_attribute(("versao", ver_evento))
.write_inner_content(|w| {
w.create_element("descEvento").write_text_content(BytesText::new("Cancelamento"))?;
w.create_element("nProt") .write_text_content(BytesText::new(protocolo))?;
w.create_element("xJust") .write_text_content(BytesText::new(justificativa))?;
Ok(())
})?;
Ok(())
})?;
let xml = String::from_utf8(writer.into_inner().into_inner())?;
let mut f = File::create("inf_evento.xml")?;
f.write_all(xml.as_bytes())?;
Ok(xml)
}
fn signed_info_xml(digest: &str, chave: &str) -> Result<String> {
let lote_seq = 1u32;
let reference_uri = format!("#ID{}{}{:>02}", TP_EVENTO, chave, lote_seq);
let mut writer = Writer::new(Cursor::new(Vec::new()));
writer.create_element("SignedInfo")
.with_attribute(("xmlns", "http://www.w3.org/2000/09/xmldsig#"))
.write_inner_content(|w| {
w.create_element("CanonicalizationMethod")
.with_attribute(("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"))
.write_inner_content(|_| Ok(()))?;
w.create_element("SignatureMethod")
.with_attribute(("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"))
.write_inner_content(|_| Ok(()))?;
w.create_element("Reference")
.with_attribute(("URI", reference_uri.as_str()))
.write_inner_content(|w| {
w.create_element("Transforms").write_inner_content(|w| {
w.create_element("Transform")
.with_attribute(("Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"))
.write_inner_content(|_| Ok(()))?;
w.create_element("Transform")
.with_attribute(("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"))
.write_inner_content(|_| Ok(()))?;
Ok(())
})?;
w.create_element("DigestMethod")
.with_attribute(("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"))
.write_inner_content(|_| Ok(()))?;
w.create_element("DigestValue").write_text_content(BytesText::new(digest))?;
Ok(())
})?;
Ok(())
})?;
let xml = String::from_utf8(writer.into_inner().into_inner())?;
Ok(Strings::clear_xml_string(&xml))
}
fn signature_xml(signed_info: &str, signed_value: &str, certificate: &str) -> Result<String> {
let mut writer = Writer::new(Cursor::new(Vec::new()));
writer.create_element("Signature")
.with_attribute(("xmlns", "http://www.w3.org/2000/09/xmldsig#"))
.write_inner_content(|w| {
w.create_element("SIGNED_INFO_REPLACER").write_empty()?;
w.create_element("SignatureValue").write_text_content(BytesText::new(signed_value))?;
w.create_element("KeyInfo").write_inner_content(|w| {
w.create_element("X509Data").write_inner_content(|w| {
w.create_element("X509Certificate").write_text_content(BytesText::new(certificate))?;
Ok(())
})?;
Ok(())
})?;
Ok(())
})?;
let xml = String::from_utf8(writer.into_inner().into_inner())?;
Ok(xml.replace("<SIGNED_INFO_REPLACER/>", signed_info))
}
fn envelope_xml(inf_evento: &str, signature: &str) -> Result<String> {
let lote_id = {
let date = chrono::Local::now();
format!("{}{}", date.format("%Y%m%d%H%M%S"), rand::random::<u8>() % 10)
};
let mut writer = Writer::new(Cursor::new(Vec::new()));
writer.create_element("soap12:Envelope")
.with_attribute(("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"))
.with_attribute(("xmlns:xsd", "http://www.w3.org/2001/XMLSchema"))
.with_attribute(("xmlns:soap12", "http://www.w3.org/2003/05/soap-envelope"))
.write_inner_content(|w| {
w.create_element("soap12:Body").write_inner_content(|w| {
w.create_element("nfeDadosMsg")
.with_attribute(("xmlns", "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4"))
.write_inner_content(|w| {
w.create_element("envEvento")
.with_attribute(("xmlns", "http://www.portalfiscal.inf.br/nfe"))
.with_attribute(("versao", "1.00"))
.write_inner_content(|w| {
w.create_element("idLote").write_text_content(BytesText::new(&lote_id))?;
w.create_element("evento")
.with_attribute(("xmlns", "http://www.portalfiscal.inf.br/nfe"))
.with_attribute(("versao", "1.00"))
.write_inner_content(|w| {
w.create_element("REPLACER_INF_EVENTO").write_empty()?;
w.create_element("REPLACER_SIGNATURE").write_empty()?;
Ok(())
})?;
Ok(())
})?;
Ok(())
})?;
Ok(())
})?;
Ok(())
})?;
let xml = String::from_utf8(writer.into_inner().into_inner())?;
let xml = xml
.replace("<REPLACER_INF_EVENTO/>", inf_evento)
.replace("<REPLACER_SIGNATURE/>", signature);
Ok("<?xml version=\"1.0\" encoding=\"utf-8\"?>".to_string() + &xml)
}