use serde::{Deserialize, Serialize};
use sha1::{Sha1, Digest};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QrCodeNfce {
pub chave_acesso: String,
pub ambiente: u8,
pub csc: String,
pub id_csc: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfiguracaoCsc {
pub id_token: String,
pub codigo_csc: String,
}
impl QrCodeNfce {
pub fn gerar_url(&self) -> String {
let versao_qrcode = "2";
let dados_hash = format!(
"{}|{}|{}|{}",
self.chave_acesso,
versao_qrcode,
self.ambiente,
self.csc
);
let mut hasher = Sha1::new();
hasher.update(dados_hash.as_bytes());
let hash = hasher.finalize();
let hash_hex = hex::encode(hash);
let url_base = if self.ambiente == 1 {
"https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
} else {
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
};
format!(
"{}?p={}|{}|{}|{}|{}",
url_base,
self.chave_acesso,
versao_qrcode,
self.ambiente,
self.id_csc,
hash_hex.to_uppercase()
)
}
pub fn gerar_url_contingencia(&self, digest_value: &str, data_emissao: &str, valor_total: f32) -> String {
let versao_qrcode = "2";
let dia = &data_emissao[8..10];
let valor_str = format!("{:.2}", valor_total);
let dados_hash = format!(
"{}|{}|{}|{}|{}|{}|{}",
self.chave_acesso,
versao_qrcode,
self.ambiente,
dia,
valor_str,
digest_value,
self.csc
);
let mut hasher = Sha1::new();
hasher.update(dados_hash.as_bytes());
let hash = hasher.finalize();
let hash_hex = hex::encode(hash);
let url_base = if self.ambiente == 1 {
"https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
} else {
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
};
format!(
"{}?p={}|{}|{}|{}|{}|{}|{}|{}",
url_base,
self.chave_acesso,
versao_qrcode,
self.ambiente,
dia,
valor_str,
digest_value,
self.id_csc,
hash_hex.to_uppercase()
)
}
}
#[derive(Debug)]
pub struct ValidadorNfce;
impl ValidadorNfce {
pub fn validar(
modelo: u8,
valor_total: f32,
qtd_itens: usize,
tem_destinatario: bool,
cfop: &str,
) -> Result<(), Vec<String>> {
let mut erros = Vec::new();
if modelo != 65 {
erros.push("NFC-e deve usar modelo 65".to_string());
}
if qtd_itens > 990 {
erros.push(format!("NFC-e permite no máximo 990 itens (encontrado: {})", qtd_itens));
}
if valor_total > 10000.0 && !tem_destinatario {
erros.push("Destinatário é obrigatório para NFC-e com valor acima de R$ 10.000,00".to_string());
}
if !cfop.starts_with('5') && !cfop.starts_with('6') {
erros.push(format!("CFOP {} não permitido para NFC-e (use CFOP de saída)", cfop));
}
if cfop.starts_with('1') || cfop.starts_with('2') || cfop.starts_with('3') {
erros.push("NFC-e não permite CFOP de entrada".to_string());
}
if erros.is_empty() {
Ok(())
} else {
Err(erros)
}
}
pub fn validar_chave(chave: &str) -> bool {
if chave.len() != 44 {
return false;
}
if let Ok(modelo) = chave[20..22].parse::<u8>() {
modelo == 65
} else {
false
}
}
}
pub fn url_nfce_por_uf(uf: &str, ambiente: u8) -> Option<UrlsNfce> {
let producao = ambiente == 1;
match uf {
"SP" => Some(UrlsNfce {
autorizacao: if producao {
"https://nfce.fazenda.sp.gov.br/ws/NFeAutorizacao4.asmx"
} else {
"https://homologacao.nfce.fazenda.sp.gov.br/ws/NFeAutorizacao4.asmx"
}.to_string(),
consulta: if producao {
"https://nfce.fazenda.sp.gov.br/ws/NFeConsultaProtocolo4.asmx"
} else {
"https://homologacao.nfce.fazenda.sp.gov.br/ws/NFeConsultaProtocolo4.asmx"
}.to_string(),
qrcode: if producao {
"https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
} else {
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
}.to_string(),
consulta_publica: if producao {
"https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaPublica.aspx"
} else {
"https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaPublica.aspx"
}.to_string(),
}),
_ => None,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UrlsNfce {
pub autorizacao: String,
pub consulta: String,
pub qrcode: String,
pub consulta_publica: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ModoEmissaoNfce {
Normal = 1,
ContingenciaOffline = 9,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
pub enum FormaPagamentoNfce {
Dinheiro = 1,
Cheque = 2,
CartaoCredito = 3,
CartaoDebito = 4,
CreditoLoja = 5,
ValeAlimentacao = 10,
ValeRefeicao = 11,
ValePresente = 12,
ValeCombustivel = 13,
BoletoBancario = 15,
DepositoBancario = 16,
Pix = 17,
TransferenciaBancaria = 18,
CashbackDebito = 19,
SemPagamento = 90,
Outros = 99,
}
impl FormaPagamentoNfce {
pub fn descricao(&self) -> &'static str {
match self {
Self::Dinheiro => "Dinheiro",
Self::Cheque => "Cheque",
Self::CartaoCredito => "Cartão de Crédito",
Self::CartaoDebito => "Cartão de Débito",
Self::CreditoLoja => "Crédito Loja",
Self::ValeAlimentacao => "Vale Alimentação",
Self::ValeRefeicao => "Vale Refeição",
Self::ValePresente => "Vale Presente",
Self::ValeCombustivel => "Vale Combustível",
Self::BoletoBancario => "Boleto Bancário",
Self::DepositoBancario => "Depósito Bancário",
Self::Pix => "PIX",
Self::TransferenciaBancaria => "Transferência Bancária",
Self::CashbackDebito => "Cashback Débito",
Self::SemPagamento => "Sem Pagamento",
Self::Outros => "Outros",
}
}
}