Skip to main content

nfe_parser/base/
nfce.rs

1//! NFC-e - Nota Fiscal de Consumidor Eletrônica (Modelo 65)
2//!
3//! Este módulo contém estruturas e funções específicas para NFC-e,
4//! incluindo geração de QR Code e validações específicas.
5
6use serde::{Deserialize, Serialize};
7use sha1::{Sha1, Digest};
8
9/// Dados para geração do QR Code da NFC-e
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct QrCodeNfce {
12    /// Chave de acesso (44 dígitos)
13    pub chave_acesso: String,
14    /// Ambiente (1=Produção, 2=Homologação)
15    pub ambiente: u8,
16    /// Código do CSC (Código de Segurança do Contribuinte)
17    pub csc: String,
18    /// ID do CSC (Token)
19    pub id_csc: String,
20}
21
22/// Configuração do CSC (Código de Segurança do Contribuinte)
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ConfiguracaoCsc {
25    /// ID do token CSC (sequencial fornecido pela SEFAZ)
26    pub id_token: String,
27    /// Código CSC (alfanumérico de 36 caracteres)
28    pub codigo_csc: String,
29}
30
31impl QrCodeNfce {
32    /// Gera a URL do QR Code para NFC-e
33    ///
34    /// ## Formato do QR Code (versão 2.0)
35    ///
36    /// ```text
37    /// URL_Base?p=CHAVE|VERSAO|AMBIENTE|ID_CSC|HASH
38    /// ```
39    ///
40    /// Onde HASH = SHA1(CHAVE|VERSAO|AMBIENTE|CSC)
41    pub fn gerar_url(&self) -> String {
42        let versao_qrcode = "2";
43
44        // Monta a string para hash: chave|versao|ambiente|csc
45        let dados_hash = format!(
46            "{}|{}|{}|{}",
47            self.chave_acesso,
48            versao_qrcode,
49            self.ambiente,
50            self.csc
51        );
52
53        // Calcula SHA1
54        let mut hasher = Sha1::new();
55        hasher.update(dados_hash.as_bytes());
56        let hash = hasher.finalize();
57        let hash_hex = hex::encode(hash);
58
59        // URL base por ambiente
60        let url_base = if self.ambiente == 1 {
61            "https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
62        } else {
63            "https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
64        };
65
66        // Monta URL final
67        format!(
68            "{}?p={}|{}|{}|{}|{}",
69            url_base,
70            self.chave_acesso,
71            versao_qrcode,
72            self.ambiente,
73            self.id_csc,
74            hash_hex.to_uppercase()
75        )
76    }
77
78    /// Gera o conteúdo do QR Code para NFC-e em contingência offline
79    ///
80    /// Em contingência, inclui informações adicionais do digest do XML
81    pub fn gerar_url_contingencia(&self, digest_value: &str, data_emissao: &str, valor_total: f32) -> String {
82        let versao_qrcode = "2";
83
84        // Em contingência: chave|versao|ambiente|dia_emissao|valor|digest|csc
85        let dia = &data_emissao[8..10];
86        let valor_str = format!("{:.2}", valor_total);
87
88        let dados_hash = format!(
89            "{}|{}|{}|{}|{}|{}|{}",
90            self.chave_acesso,
91            versao_qrcode,
92            self.ambiente,
93            dia,
94            valor_str,
95            digest_value,
96            self.csc
97        );
98
99        let mut hasher = Sha1::new();
100        hasher.update(dados_hash.as_bytes());
101        let hash = hasher.finalize();
102        let hash_hex = hex::encode(hash);
103
104        let url_base = if self.ambiente == 1 {
105            "https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
106        } else {
107            "https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
108        };
109
110        format!(
111            "{}?p={}|{}|{}|{}|{}|{}|{}|{}",
112            url_base,
113            self.chave_acesso,
114            versao_qrcode,
115            self.ambiente,
116            dia,
117            valor_str,
118            digest_value,
119            self.id_csc,
120            hash_hex.to_uppercase()
121        )
122    }
123}
124
125/// Validações específicas para NFC-e
126#[derive(Debug)]
127pub struct ValidadorNfce;
128
129impl ValidadorNfce {
130    /// Valida se a NFC-e atende aos requisitos do modelo 65
131    pub fn validar(
132        modelo: u8,
133        valor_total: f32,
134        qtd_itens: usize,
135        tem_destinatario: bool,
136        cfop: &str,
137    ) -> Result<(), Vec<String>> {
138        let mut erros = Vec::new();
139
140        // Deve ser modelo 65
141        if modelo != 65 {
142            erros.push("NFC-e deve usar modelo 65".to_string());
143        }
144
145        // Limite de 990 itens
146        if qtd_itens > 990 {
147            erros.push(format!("NFC-e permite no máximo 990 itens (encontrado: {})", qtd_itens));
148        }
149
150        // Destinatário obrigatório para valores acima de R$ 10.000
151        if valor_total > 10000.0 && !tem_destinatario {
152            erros.push("Destinatário é obrigatório para NFC-e com valor acima de R$ 10.000,00".to_string());
153        }
154
155        // CFOP deve ser de saída (5xxx ou 6xxx para venda)
156        if !cfop.starts_with('5') && !cfop.starts_with('6') {
157            erros.push(format!("CFOP {} não permitido para NFC-e (use CFOP de saída)", cfop));
158        }
159
160        // NFC-e não permite CFOP de entrada (1xxx, 2xxx, 3xxx)
161        if cfop.starts_with('1') || cfop.starts_with('2') || cfop.starts_with('3') {
162            erros.push("NFC-e não permite CFOP de entrada".to_string());
163        }
164
165        if erros.is_empty() {
166            Ok(())
167        } else {
168            Err(erros)
169        }
170    }
171
172    /// Valida se a chave de acesso é de uma NFC-e (modelo 65)
173    pub fn validar_chave(chave: &str) -> bool {
174        if chave.len() != 44 {
175            return false;
176        }
177
178        // Posições 20-21 contêm o modelo
179        if let Ok(modelo) = chave[20..22].parse::<u8>() {
180            modelo == 65
181        } else {
182            false
183        }
184    }
185}
186
187/// URLs dos WebServices de NFC-e por UF
188pub fn url_nfce_por_uf(uf: &str, ambiente: u8) -> Option<UrlsNfce> {
189    let producao = ambiente == 1;
190
191    match uf {
192        "SP" => Some(UrlsNfce {
193            autorizacao: if producao {
194                "https://nfce.fazenda.sp.gov.br/ws/NFeAutorizacao4.asmx"
195            } else {
196                "https://homologacao.nfce.fazenda.sp.gov.br/ws/NFeAutorizacao4.asmx"
197            }.to_string(),
198            consulta: if producao {
199                "https://nfce.fazenda.sp.gov.br/ws/NFeConsultaProtocolo4.asmx"
200            } else {
201                "https://homologacao.nfce.fazenda.sp.gov.br/ws/NFeConsultaProtocolo4.asmx"
202            }.to_string(),
203            qrcode: if producao {
204                "https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
205            } else {
206                "https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx"
207            }.to_string(),
208            consulta_publica: if producao {
209                "https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaPublica.aspx"
210            } else {
211                "https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaPublica.aspx"
212            }.to_string(),
213        }),
214        // Adicione mais UFs conforme necessário
215        _ => None,
216    }
217}
218
219/// URLs dos WebServices de NFC-e
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct UrlsNfce {
222    pub autorizacao: String,
223    pub consulta: String,
224    pub qrcode: String,
225    pub consulta_publica: String,
226}
227
228/// Modos de emissão da NFC-e
229#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
230pub enum ModoEmissaoNfce {
231    /// Normal - transmissão online
232    Normal = 1,
233    /// Contingência offline
234    ContingenciaOffline = 9,
235}
236
237/// Formas de pagamento aceitas em NFC-e
238#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
239#[repr(u8)]
240pub enum FormaPagamentoNfce {
241    Dinheiro = 1,
242    Cheque = 2,
243    CartaoCredito = 3,
244    CartaoDebito = 4,
245    CreditoLoja = 5,
246    ValeAlimentacao = 10,
247    ValeRefeicao = 11,
248    ValePresente = 12,
249    ValeCombustivel = 13,
250    BoletoBancario = 15,
251    DepositoBancario = 16,
252    Pix = 17,
253    TransferenciaBancaria = 18,
254    CashbackDebito = 19,
255    SemPagamento = 90,
256    Outros = 99,
257}
258
259impl FormaPagamentoNfce {
260    pub fn descricao(&self) -> &'static str {
261        match self {
262            Self::Dinheiro => "Dinheiro",
263            Self::Cheque => "Cheque",
264            Self::CartaoCredito => "Cartão de Crédito",
265            Self::CartaoDebito => "Cartão de Débito",
266            Self::CreditoLoja => "Crédito Loja",
267            Self::ValeAlimentacao => "Vale Alimentação",
268            Self::ValeRefeicao => "Vale Refeição",
269            Self::ValePresente => "Vale Presente",
270            Self::ValeCombustivel => "Vale Combustível",
271            Self::BoletoBancario => "Boleto Bancário",
272            Self::DepositoBancario => "Depósito Bancário",
273            Self::Pix => "PIX",
274            Self::TransferenciaBancaria => "Transferência Bancária",
275            Self::CashbackDebito => "Cashback Débito",
276            Self::SemPagamento => "Sem Pagamento",
277            Self::Outros => "Outros",
278        }
279    }
280}