mod det;
mod det_process;
mod emit;
mod flag;
mod ide;
mod inf_adic;
pub mod pag;
mod total;
mod transp;
use crate::nfe;
use anyhow::{Error, Result};
use det::det_process;
use emit::{EmitProcess, EnderEmitProcess};
use flag::FlagAutorizacao;
use flag::FlagAutorizacaoEnum;
use ide::*;
use inf_adic::inf_adic_process;
use nfe::common::cert::Cert;
use nfe::common::chave_acesso::ChaveAcesso;
use nfe::common::cleaner;
use nfe::common::cleaner::Strings;
use nfe::common::dates::get_current_date_time;
use nfe::common::validation::is_xml_valid;
use nfe::common::ws::nfe_autorizacao;
use nfe::connection::WebService;
use nfe::types::autorizacao4::*;
use nfe::types::chave_acesso_props::ChaveAcessoProps;
use nfe::xml_rules::dest::*;
use pag::pag_process;
use regex::Regex;
use serde_xml_rs::to_string;
use std::fs::File;
use std::io::Write;
use total::total_process;
use transp::transp_process;
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct Response {
pub protocolo: TagInfProt,
pub xml: String,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct InfProt {
#[serde(rename = "tpAmb")]
pub tp_amb: i32,
#[serde(rename = "verAplic")]
pub ver_aplic: String,
#[serde(rename = "chNFe")]
pub ch_nfe: String,
#[serde(rename = "dhRecbto")]
pub dh_recbto: String,
#[serde(rename = "nProt", skip_serializing_if = "Option::is_none")]
pub n_prot: Option<String>,
#[serde(rename = "digVal", skip_serializing_if = "Option::is_none")]
pub dig_val: Option<String>,
#[serde(rename = "cStat")]
pub c_stat: i32,
#[serde(rename = "xMotivo")]
pub x_motivo: String,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct TagInfProt {
#[serde(rename = "infProt")]
pub inf_prot: InfProt,
}
pub async fn emit(nfe: NFe) -> Result<Response, Error> {
let flag = FlagAutorizacao::start().await.map_err(|e| Error::msg(e))?;
match flag {
FlagAutorizacaoEnum::Ready => {
println!("Flag de autorização: Ready. Prosseguindo com a emissão.");
}
_ => {
return Err(Error::msg(format!(
"Flag de autorização inválida para emissão: [{:?}]. Tente novamente quando a flag estiver como Ready.",
flag
)));
}
}
let codigo_numerico = ChaveAcesso::gerar_codigo_numerico(nfe.ide.c_nf.clone());
let doc = match (nfe.emit.cnpj.as_ref(), nfe.emit.cpf.as_ref()) {
(Some(cnpj), _) => cnpj.clone(),
(None, Some(cpf)) => cpf.clone(),
(None, None) => String::new(),
};
let ch_acc = ChaveAcesso::gerar_chave_acesso(ChaveAcessoProps {
uf: nfe.ide.c_uf,
doc,
modelo: nfe.ide.mod_,
serie: nfe.ide.serie,
numero: nfe.ide.n_nf,
tp_emis: nfe.ide.tp_emis,
codigo_numerico: codigo_numerico.clone(),
});
let chave_acesso = ch_acc.chave;
let dv = ch_acc.dv;
let dh_emi = if let Some(dh_emi) = nfe.ide.dh_emi.clone() {
dh_emi
} else {
get_current_date_time()
};
let dh_sai_ent = if let Some(dh_sai_ent) = nfe.ide.dh_sai_ent.clone() {
dh_sai_ent
} else {
get_current_date_time()
};
let mut ide = IdeProcess {
c_uf: nfe.ide.c_uf,
c_nf: Some(codigo_numerico.clone()),
nat_op: nfe.ide.nat_op.clone(),
ind_pag: nfe.ide.ind_pag,
mod_: nfe.ide.mod_.clone(),
serie: nfe.ide.serie,
n_nf: nfe.ide.n_nf,
dh_emi: Some(dh_emi.clone()),
dh_sai_ent: Some(dh_sai_ent),
tp_nf: nfe.ide.tp_nf,
id_dest: nfe.ide.id_dest,
c_mun_fg: nfe.ide.c_mun_fg.clone(),
tp_imp: nfe.ide.tp_imp,
tp_emis: nfe.ide.tp_emis,
c_dv: Some(dv),
tp_amb: nfe.ide.tp_amb.clone(),
fin_nfe: nfe.ide.fin_nfe,
ind_final: nfe.ide.ind_final,
ind_pres: nfe.ide.ind_pres,
proc_emi: nfe.ide.proc_emi,
ver_proc: nfe.ide.ver_proc.clone(),
};
if nfe.ide.mod_ == 65 {
ide = IdeProcess {
c_uf: nfe.ide.c_uf,
c_nf: Some(codigo_numerico),
nat_op: nfe.ide.nat_op.clone(),
ind_pag: nfe.ide.ind_pag,
mod_: nfe.ide.mod_.clone(),
serie: nfe.ide.serie,
n_nf: nfe.ide.n_nf,
dh_emi: Some(dh_emi),
dh_sai_ent: None,
tp_nf: nfe.ide.tp_nf,
id_dest: nfe.ide.id_dest,
c_mun_fg: nfe.ide.c_mun_fg.clone(),
tp_imp: nfe.ide.tp_imp,
tp_emis: nfe.ide.tp_emis,
c_dv: Some(dv),
tp_amb: nfe.ide.tp_amb.clone(),
fin_nfe: nfe.ide.fin_nfe,
ind_final: nfe.ide.ind_final,
ind_pres: nfe.ide.ind_pres,
proc_emi: nfe.ide.proc_emi,
ver_proc: nfe.ide.ver_proc.clone(),
};
}
let emit = EmitProcess {
cnpj: nfe.emit.cnpj.clone(),
cpf: nfe.emit.cpf.clone(),
x_nome: nfe.emit.x_nome,
x_fant: nfe.emit.x_fant,
ender_emit: EnderEmitProcess {
x_lgr: nfe.emit.x_lgr,
nro: nfe.emit.nro,
x_bairro: nfe.emit.x_bairro,
c_mun: nfe.emit.c_mun,
x_mun: nfe.emit.x_mun,
uf: nfe.emit.uf,
cep: nfe.emit.cep,
c_pais: nfe.emit.c_pais.unwrap_or(0),
x_pais: nfe.emit.x_pais.unwrap_or("".to_string()),
},
ie: nfe.emit.ie,
crt: nfe.emit.crt,
};
let dest_string = DestTAG::build(&nfe.dest, &nfe.ide)?;
let dets = det_process(
nfe.det,
nfe.ide.mod_,
nfe.ide.tp_amb,
nfe.desconto_rateio.clone(),
nfe.active_ibs_cbs.clone(),
)?;
let dets_total = dets.clone();
let mut det_string = String::new();
for (i, det) in dets.iter().enumerate() {
let prod = to_string(&det.prod).unwrap_or_else(|e| {
println!("Erro ao gerar o XML do DetProcess: {:?}", e);
return String::new();
});
let imposto = to_string(&det.imposto).unwrap_or_else(|e| {
println!("Erro ao gerar o XML do DetProcess: {:?}", e);
return String::new();
});
let inf_ad_prod = &det.inf_ad_prod;
let inf_ad_prod = if let Some(inf_ad_prod) = inf_ad_prod {
format!("<infAdProd>{}</infAdProd>", inf_ad_prod)
} else {
"".to_string()
};
det_string.push_str(&format!(
r#"<det nItem="{}">{}{}{}</det>"#,
i + 1,
prod,
imposto,
inf_ad_prod
));
}
let total = total_process(
nfe.total,
dets_total,
nfe.ide.tp_amb,
nfe.active_ibs_cbs.clone(),
)?;
let transp = transp_process(nfe.transp)?;
let pag = pag_process(nfe.pag)?;
let inf_adic = inf_adic_process(nfe.inf_adic)?;
let xml = format!(
"<infNFe xmlns=\"http://www.portalfiscal.inf.br/nfe\" Id=\"NFe{}\" versao=\"4.00\">{}{}{}{}{}{}{}{}{}",
chave_acesso,
to_string(&ide).unwrap_or_else(|e| {
println!("Erro ao gerar o XML do IDE: {:?}", e);
return String::new();
}),
to_string(&emit).unwrap_or_else(|e| {
println!("Erro ao gerar o XML do Emitente: {:?}", e);
return String::new();
}),
dest_string,
det_string,
to_string(&total).unwrap_or_else(|e| {
println!("Erro ao gerar o XML do Total: {:?}", e);
return String::new();
}),
to_string(&transp).unwrap_or_else(|e| {
println!("Erro ao gerar o XML do Transporte: {:?}", e);
return String::new();
}),
to_string(&pag).unwrap_or_else(|e| {
println!("Erro ao gerar o XML do Pagamento: {:?}", e);
return String::new();
}),
to_string(&inf_adic).unwrap_or_else(|e| {
println!("Erro ao gerar o XML das Informações Adicionais: {:?}", e);
return String::new();
}),
"</infNFe>"
);
let xml = Strings::clear_xml_string(&xml);
let digest_value = crate::nfe::common::cert::DigestValue::sha1(&xml)?;
let x509_cert =
crate::nfe::common::cert::RawPubKey::get_from_file(&nfe.cert_path, &nfe.cert_pass).await?;
let mut signed_info = "".to_string()
+ "<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
+ "<CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"></CanonicalizationMethod>"
+ "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"></SignatureMethod>"
+ "<Reference URI=\"#NFe"
+ &chave_acesso
+ "\">"
+ "<Transforms>"
+ "<Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"></Transform>"
+ "<Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"></Transform>"
+ "</Transforms>"
+ "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"></DigestMethod>"
+ "<DigestValue>"
+ &digest_value
+ "</DigestValue>"
+ "</Reference>"
+ "</SignedInfo>";
signed_info = cleaner::Strings::clear_xml_string(&signed_info);
let signature_base64 =
crate::nfe::common::cert::Sign::xml_string(&signed_info, &nfe.cert_path, &nfe.cert_pass)
.await?;
let signature_nodes =
signed_info + "<SignatureValue>" + &signature_base64 + "</SignatureValue>";
let signed_xml = "<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">".to_string()
+ &signature_nodes
+ "<KeyInfo>"
+ "<X509Data>"
+ "<X509Certificate>"
+ &x509_cert
+ "</X509Certificate>"
+ "</X509Data>"
+ "</KeyInfo>"
+ "</Signature>";
let mut qrcode = String::new();
if nfe.ide.mod_ == 65 {
let mut url_base = String::new();
if nfe.ide.tp_amb == 2 {
url_base = "https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx".to_string();
} else if nfe.ide.tp_amb == 1 {
url_base = "https://www.nfce.fazenda.sp.gov.br/qrcode".to_string();
}
let chave = &chave_acesso;
let versao_qr = "2";
let ambiente = nfe.ide.tp_amb.to_string();
let id_csc = match nfe.id_csc {
Some(id) => id,
None => {
return Err(Error::msg("ID do CSC não foi informado."));
}
};
let csc = match nfe.csc {
Some(c) => c,
None => {
return Err(Error::msg("CSC não foi informado."));
}
};
let c_hash = qrcode_hash(&chave_acesso, &versao_qr, &ambiente, &id_csc, &csc)?;
let url_qrcode = format!("{url_base}?p={chave}|{versao_qr}|{ambiente}|{id_csc}|{c_hash}");
let consulta_homologacao = "https://www.homologacao.nfce.fazenda.sp.gov.br/consulta";
let consulta_producao = "https://www.nfce.fazenda.sp.gov.br/consulta";
let url_consulta = if nfe.ide.tp_amb == 2 {
format!("{consulta_homologacao}")
} else {
format!("{consulta_producao}")
};
qrcode = format!(
r#"<infNFeSupl>
<qrCode><![CDATA[{url_qrcode}]]></qrCode>
<urlChave>{url_consulta}</urlChave>
</infNFeSupl>"#
);
qrcode = cleaner::Strings::clear_xml_string(&qrcode);
}
let xml = "<NFe xmlns=\"http://www.portalfiscal.inf.br/nfe\">".to_string()
+ &xml
+ &qrcode
+ &signed_xml
+ "</NFe>";
let mut file = File::create("./nfe_request.xml")
.expect("Não foi possível criar o arquivo nfe_request.xml");
file.write_all(&xml.as_bytes())
.expect("Não foi possível escrever o arquivo nfe_request.xml");
let signed_xml = match is_xml_valid(&xml, "./dfe/shema/PL_010b_NT2025_002_v1.21/nfe_v4.00.xsd")
{
Ok(xml) => {
xml
}
Err(e) => {
return Err(Error::msg(format!(
"Error at is_xml_valid: [{}]",
e.to_string()
)));
}
};
let id_lote = 100;
let lote_ini = format!(
r#"<enviNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00"><idLote>{}</idLote><indSinc>1</indSinc>"#,
id_lote
);
let lote_fim = "</enviNFe>";
let xml = format!(
r#"<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"><soap12:Body><nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4">{}{}{}</nfeDadosMsg></soap12:Body></soap12:Envelope>"#,
lote_ini, &xml, lote_fim
);
let url = nfe_autorizacao(nfe.ide.tp_amb, "SP", nfe.ide.mod_, false)?;
let cert = Cert::from_pfx(&nfe.cert_path, &nfe.cert_pass)?;
let client = WebService::client(cert.identity)?;
let xml_with_declaration = if xml.starts_with("<?xml") {
xml.to_string()
} else {
format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n{}", xml)
};
let mut file = File::create("./nfe_request_envelope.xml")
.expect("Não foi possível criar o arquivo nfe_request_envelope.xml");
file.write_all(&xml_with_declaration.as_bytes())
.expect("Não foi possível escrever o arquivo nfe_request_envelope.xml");
file.sync_all()
.expect("Não foi possível sincronizar o arquivo nfe_request_envelope.xml");
let content_length = xml_with_declaration.len();
let response = client
.post(url)
.header("Content-Type", "application/soap+xml; charset=utf-8")
.header("Content-Length", content_length.to_string())
.body(xml_with_declaration)
.send()
.await?;
if response.status().is_success() {
println!(
"Requisição enviada com sucesso. Resposta : [{:?}]",
response
);
let result = xml_result(&response.text().await?, signed_xml)?;
if result.protocolo.inf_prot.c_stat != 100 {
println!(
"Erro C_STAT diferente de 100, que é [{:?}]",
result.protocolo.inf_prot.c_stat
);
return Ok(result);
} else {
println!("Requisição bem sucedida: {:?}", result);
let protocolo = format!(
r#"</NFe><protNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00"><infProt><tpAmb>{}</tpAmb><verAplic>{}</verAplic><chNFe>{}</chNFe><dhRecbto>{}</dhRecbto><nProt>{}</nProt><digVal>{}</digVal><cStat>{}</cStat><xMotivo>{}</xMotivo></infProt></protNFe></nfeProc>"#,
result.protocolo.inf_prot.tp_amb,
result.protocolo.inf_prot.ver_aplic,
result.protocolo.inf_prot.ch_nfe,
result.protocolo.inf_prot.dh_recbto,
result
.protocolo
.inf_prot
.n_prot
.clone()
.unwrap_or("".to_string()),
result
.protocolo
.inf_prot
.dig_val
.clone()
.unwrap_or("".to_string()),
result.protocolo.inf_prot.c_stat,
result.protocolo.inf_prot.x_motivo
);
let xml = result.xml.replace("</NFe>", &protocolo);
let xml = r#"<?xml version="1.0" encoding="UTF-8"?><nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00">"#
.to_string()
+ &xml;
let mut file =
File::create("./nfe_response.xml").expect("Não foi possível criar o arquivo");
file.write_all(xml.as_bytes())
.expect("Não foi possível escrever o arquivo");
let xml = xml.replace("\\", "");
let response = Response {
protocolo: result.protocolo,
xml,
};
Ok(response)
}
} else {
let status = response.status().clone();
let body = response.text().await?;
println!("Erro na Requisição: {:?} -> Body: {:?}", status, body,);
return Err(Error::msg(format!(
"Erro na Requisição: {:?} -> Body: {:?}",
status, body
)));
}
}
fn xml_result(response: &str, signed_xml: String) -> Result<Response, Error> {
println!("Response XML ------------->: {}", response);
let re = Regex::new(r#"<protNFe versao="4.00">(.*?)</protNFe>"#);
let re = match re {
Ok(regex) => regex,
Err(e) => {
return Err(Error::msg(format!(
"Erro ao compilar a expressão regular: {}",
e
)));
}
};
let prot_nfe = match re.captures(&response) {
Some(captures) => match captures.get(0) {
Some(matched) => matched.as_str(),
None => {
return Err(Error::msg("No match found in regex capture group"));
}
},
None => {
return Err(Error::msg(format!(
"Protocol NFe not found in response. Response content: {}",
response
)));
}
};
let tag_inf_prot = serde_xml_rs::from_str(&prot_nfe);
let tag_inf_prot = match tag_inf_prot {
Ok(tag) => tag,
Err(e) => {
return Err(Error::msg(format!(
"Erro ao desserializar o XML do protocolo: {} Response content: {}",
e, prot_nfe
)));
}
};
Ok(Response {
protocolo: tag_inf_prot,
xml: signed_xml,
})
}
fn qrcode_hash(
chave_acesso: &str,
versao_qr: &str,
ambiente: &str,
id_csc: &str,
csc: &str,
) -> Result<String, Error> {
use sha1::{Digest, Sha1};
let dados = format!("{chave_acesso}|{versao_qr}|{ambiente}|{id_csc}");
let dados_csc = format!("{dados}{csc}");
let mut hasher = Sha1::new();
hasher.update(dados_csc.as_bytes());
let hash = hasher.finalize();
let hash_hex = format!("{:x}", hash);
Ok(hash_hex)
}