1use serde::{Deserialize, Serialize};
7use sha1::{Sha1, Digest};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct QrCodeNfce {
12 pub chave_acesso: String,
14 pub ambiente: u8,
16 pub csc: String,
18 pub id_csc: String,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ConfiguracaoCsc {
25 pub id_token: String,
27 pub codigo_csc: String,
29}
30
31impl QrCodeNfce {
32 pub fn gerar_url(&self) -> String {
42 let versao_qrcode = "2";
43
44 let dados_hash = format!(
46 "{}|{}|{}|{}",
47 self.chave_acesso,
48 versao_qrcode,
49 self.ambiente,
50 self.csc
51 );
52
53 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 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 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 pub fn gerar_url_contingencia(&self, digest_value: &str, data_emissao: &str, valor_total: f32) -> String {
82 let versao_qrcode = "2";
83
84 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#[derive(Debug)]
127pub struct ValidadorNfce;
128
129impl ValidadorNfce {
130 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 if modelo != 65 {
142 erros.push("NFC-e deve usar modelo 65".to_string());
143 }
144
145 if qtd_itens > 990 {
147 erros.push(format!("NFC-e permite no máximo 990 itens (encontrado: {})", qtd_itens));
148 }
149
150 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 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 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 pub fn validar_chave(chave: &str) -> bool {
174 if chave.len() != 44 {
175 return false;
176 }
177
178 if let Ok(modelo) = chave[20..22].parse::<u8>() {
180 modelo == 65
181 } else {
182 false
183 }
184 }
185}
186
187pub 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 _ => None,
216 }
217}
218
219#[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#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
230pub enum ModoEmissaoNfce {
231 Normal = 1,
233 ContingenciaOffline = 9,
235}
236
237#[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}