fiscal_core/complement/
protocol.rs1use crate::FiscalError;
4use crate::status_codes::VALID_PROTOCOL_STATUSES;
5use crate::xml_utils::extract_xml_tag_value;
6
7use super::helpers::{
8 DEFAULT_VERSION, extract_all_tags, extract_attribute, extract_inf_nfe_id, extract_tag, join_xml,
9};
10
11pub fn attach_protocol(request_xml: &str, response_xml: &str) -> Result<String, FiscalError> {
32 if request_xml.is_empty() {
33 return Err(FiscalError::XmlParsing("Request XML (NFe) is empty".into()));
34 }
35 if response_xml.is_empty() {
36 return Err(FiscalError::XmlParsing(
37 "Response XML (protocol) is empty".into(),
38 ));
39 }
40
41 let nfe_content = extract_tag(request_xml, "NFe")
42 .ok_or_else(|| FiscalError::XmlParsing("Could not find <NFe> tag in request XML".into()))?;
43
44 let digest_nfe = extract_xml_tag_value(request_xml, "DigestValue");
46 let access_key = extract_inf_nfe_id(request_xml);
47
48 let mut matched_prot: Option<String> = None;
50
51 let prot_nodes = extract_all_tags(response_xml, "protNFe");
52
53 for prot in &prot_nodes {
54 let dig_val = extract_xml_tag_value(prot, "digVal");
55 let ch_nfe = extract_xml_tag_value(prot, "chNFe");
56
57 if let (Some(dn), Some(dv)) = (&digest_nfe, &dig_val) {
58 if let (Some(ak), Some(cn)) = (&access_key, &ch_nfe) {
59 if dn == dv && ak == cn {
60 let c_stat = extract_xml_tag_value(prot, "cStat").unwrap_or_default();
62 if !VALID_PROTOCOL_STATUSES.contains(&c_stat.as_str()) {
63 let x_motivo = extract_xml_tag_value(prot, "xMotivo").unwrap_or_default();
64 return Err(FiscalError::SefazRejection {
65 code: c_stat,
66 message: x_motivo,
67 });
68 }
69 matched_prot = Some(prot.clone());
70 break;
71 }
72 }
73 }
74 }
75
76 if matched_prot.is_none() {
77 let mut found_dig_val = false;
79 for prot in &prot_nodes {
80 if extract_xml_tag_value(prot, "digVal").is_some() {
81 found_dig_val = true;
82 break;
83 }
84 }
85
86 if !prot_nodes.is_empty() && !found_dig_val {
87 let first_prot = &prot_nodes[0];
89 let c_stat = extract_xml_tag_value(first_prot, "cStat").unwrap_or_default();
90 let x_motivo = extract_xml_tag_value(first_prot, "xMotivo").unwrap_or_default();
91 let msg = format!("digVal ausente na resposta SEFAZ: [{c_stat}] {x_motivo}");
92 return Err(FiscalError::SefazRejection {
93 code: c_stat,
94 message: msg,
95 });
96 }
97
98 if found_dig_val {
99 let key_info = access_key.as_deref().unwrap_or("unknown");
101 return Err(FiscalError::XmlParsing(format!(
102 "Os digest são diferentes [{key_info}]"
103 )));
104 }
105
106 let single_prot = extract_tag(response_xml, "protNFe").ok_or_else(|| {
108 FiscalError::XmlParsing("Could not find <protNFe> in response XML".into())
109 })?;
110
111 let c_stat = extract_xml_tag_value(&single_prot, "cStat").unwrap_or_default();
113 if !VALID_PROTOCOL_STATUSES.contains(&c_stat.as_str()) {
114 let x_motivo = extract_xml_tag_value(&single_prot, "xMotivo").unwrap_or_default();
115 return Err(FiscalError::SefazRejection {
116 code: c_stat,
117 message: x_motivo,
118 });
119 }
120 matched_prot = Some(single_prot);
121 }
122
123 let version = extract_attribute(&nfe_content, "infNFe", "versao")
124 .unwrap_or_else(|| DEFAULT_VERSION.to_string());
125
126 Ok(join_xml(
127 &nfe_content,
128 &matched_prot.unwrap(),
129 "nfeProc",
130 &version,
131 ))
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn attach_protocol_empty_request_xml() {
140 let err = attach_protocol("", "<protNFe/>").unwrap_err();
141 assert!(matches!(err, FiscalError::XmlParsing(_)));
142 }
143
144 #[test]
145 fn attach_protocol_empty_response_xml() {
146 let err = attach_protocol("<NFe/>", "").unwrap_err();
147 assert!(matches!(err, FiscalError::XmlParsing(_)));
148 }
149
150 #[test]
151 fn attach_protocol_matching_digest_and_key() {
152 let request = concat!(
153 r#"<NFe><infNFe versao="4.00" Id="NFe35260112345678000199650010000000011123456780">"#,
154 r#"<ide/></infNFe>"#,
155 r#"<Signature><SignedInfo/><SignatureValue/>"#,
156 r#"<KeyInfo><DigestValue>abc123</DigestValue></KeyInfo></Signature>"#,
157 r#"</NFe>"#
158 );
159 let response = concat!(
160 r#"<protNFe versao="4.00"><infProt>"#,
161 r#"<digVal>abc123</digVal>"#,
162 r#"<chNFe>35260112345678000199650010000000011123456780</chNFe>"#,
163 r#"<cStat>100</cStat>"#,
164 r#"<xMotivo>Autorizado</xMotivo>"#,
165 r#"</infProt></protNFe>"#
166 );
167 let result = attach_protocol(request, response).unwrap();
168 assert!(result.contains("<nfeProc"));
169 assert!(result.contains("</nfeProc>"));
170 assert!(result.contains("<NFe>"));
171 assert!(result.contains("<protNFe"));
172 }
173
174 #[test]
175 fn attach_protocol_rejected_status_in_exact_match() {
176 let request = concat!(
177 r#"<NFe><infNFe versao="4.00" Id="NFe35260112345678000199650010000000011123456780">"#,
178 r#"<ide/></infNFe>"#,
179 r#"<Signature><SignedInfo/><SignatureValue/>"#,
180 r#"<KeyInfo><DigestValue>abc123</DigestValue></KeyInfo></Signature>"#,
181 r#"</NFe>"#
182 );
183 let response = concat!(
184 r#"<protNFe versao="4.00"><infProt>"#,
185 r#"<digVal>abc123</digVal>"#,
186 r#"<chNFe>35260112345678000199650010000000011123456780</chNFe>"#,
187 r#"<cStat>999</cStat>"#,
188 r#"<xMotivo>Rejeitada</xMotivo>"#,
189 r#"</infProt></protNFe>"#
190 );
191 let err = attach_protocol(request, response).unwrap_err();
192 assert!(matches!(err, FiscalError::SefazRejection { .. }));
193 }
194
195 #[test]
196 fn attach_protocol_fallback_rejected_status() {
197 let request = concat!(
199 r#"<NFe><infNFe versao="4.00" Id="NFe35260112345678000199650010000000011123456780">"#,
200 r#"<ide/></infNFe></NFe>"#
201 );
202 let response = concat!(
203 r#"<protNFe versao="4.00"><infProt>"#,
204 r#"<cStat>999</cStat>"#,
205 r#"<xMotivo>Rejeitada</xMotivo>"#,
206 r#"</infProt></protNFe>"#
207 );
208 let err = attach_protocol(request, response).unwrap_err();
209 assert!(matches!(err, FiscalError::SefazRejection { .. }));
210 }
211
212 #[test]
215 fn attach_protocol_fallback_prot_invalid_status() {
216 let request = concat!(
218 r#"<NFe><infNFe versao="4.00" Id="NFe35260112345678000199550010000000011123456780">"#,
219 r#"<DigestValue>abc123</DigestValue>"#,
220 r#"</infNFe></NFe>"#
221 );
222 let response = concat!(
225 r#"<protNFe versao="4.00"><infProt>"#,
226 r#"<cStat>999</cStat>"#,
227 r#"<xMotivo>Rejeitado</xMotivo>"#,
228 r#"</infProt></protNFe>"#
229 );
230 let err = attach_protocol(request, response).unwrap_err();
231 match err {
232 FiscalError::SefazRejection { code, .. } => assert_eq!(code, "999"),
233 other => panic!("Expected SefazRejection, got {:?}", other),
234 }
235 }
236}