1use std::fmt;
2
3use chrono::NaiveDate;
4use serde::Serialize;
5
6use crate::utils::{dv_utils, fator_vencimento_to_date, u8_array_to_u16};
7use crate::BoletoError;
8
9pub struct CodBarras([u8; Cobranca::COD_BARRAS_LENGTH]);
10
11impl CodBarras {
12 pub fn new(input: &[u8]) -> Result<Self, BoletoError> {
13 if input.len() != Cobranca::COD_BARRAS_LENGTH {
14 return Err(BoletoError::InvalidLength);
15 }
16
17 if input.first() == Some(&b'8') {
18 return Err(BoletoError::InvalidArrecadacaoBarcode);
19 }
20
21 let only_numbers = input.iter().all(|c| c.is_ascii_digit());
22 if !only_numbers {
23 return Err(BoletoError::NumbersOnly);
24 }
25
26 let mut cod_barras = [0u8; Cobranca::COD_BARRAS_LENGTH];
27 cod_barras.copy_from_slice(input);
28
29 Ok(Self(cod_barras))
30 }
31
32 pub fn as_str(&self) -> &str {
33 unsafe { std::str::from_utf8_unchecked(&self.0) }
34 }
35
36 pub fn as_bytes(&self) -> &[u8] {
37 &self.0
38 }
39
40 pub fn calculate_dv(&self) -> u8 {
41 let iterator_without_dv = self[..4]
44 .iter()
45 .chain(self[5..].iter());
46
47 dv_utils::mod_11(iterator_without_dv).unwrap_or(b'1') - b'0'
48 }
49
50 pub fn calculate_dv_campos(&self) -> (u8, u8, u8) {
51 (
52 dv_utils::mod_10(self[0..4].iter().chain(self[19..24].iter())),
53 dv_utils::mod_10(self[24..34].iter()),
54 dv_utils::mod_10(self[34..44].iter()),
55 )
56 }
57
58 pub fn update_dv(&mut self) {
59 self.0[4] = self.calculate_dv() + b'0';
60 }
61}
62
63impl From<&LinhaDigitavel> for CodBarras {
64 fn from(linha_digitavel: &LinhaDigitavel) -> Self {
65
66 let LinhaDigitavel(src) = linha_digitavel;
75 let mut barcode = [0_u8; Cobranca::COD_BARRAS_LENGTH];
76
77 barcode[0..4].copy_from_slice(&src[0..4]);
78 barcode[4..19].copy_from_slice(&src[32..47]);
79 barcode[19..24].copy_from_slice(&src[4..9]);
80 barcode[24..34].copy_from_slice(&src[10..20]);
81 barcode[34..44].copy_from_slice(&src[21..31]);
82
83 Self(barcode)
84 }
85}
86
87impl std::fmt::Debug for CodBarras {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_tuple("CodBarras")
90 .field(unsafe { &std::str::from_utf8_unchecked(&self.0)})
91 .finish()
92 }
93}
94
95impl fmt::Display for CodBarras {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.write_str(self.as_str())
98 }
99}
100
101impl std::ops::Deref for CodBarras {
102 type Target = [u8; Cobranca::COD_BARRAS_LENGTH];
103
104 fn deref(&self) -> &Self::Target {
105 &self.0
106 }
107}
108
109impl Serialize for CodBarras {
110 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111 where
112 S: serde::Serializer {
113 serializer.serialize_str(self.as_str())
114 }
115}
116
117pub struct LinhaDigitavel([u8; Cobranca::LINHA_DIGITAVEL_LENGTH]);
118
119impl LinhaDigitavel {
120 pub fn new(input: &[u8]) -> Result<Self, BoletoError> {
121 if input.len() != Cobranca::LINHA_DIGITAVEL_LENGTH {
122 return Err(BoletoError::InvalidLength);
123 }
124
125 if input.first() == Some(&b'8') {
126 return Err(BoletoError::InvalidArrecadacaoBarcode);
127 }
128
129 let only_numbers = input.iter().all(|c| c.is_ascii_digit());
130 if !only_numbers {
131 return Err(BoletoError::NumbersOnly);
132 }
133
134 let mut linha_digitavel = [0u8; Cobranca::LINHA_DIGITAVEL_LENGTH];
135 linha_digitavel.copy_from_slice(input);
136
137 Ok(Self(linha_digitavel))
138 }
139
140 pub fn as_str(&self) -> &str {
141 unsafe { std::str::from_utf8_unchecked(&self.0) }
142 }
143}
144
145impl From<&CodBarras> for LinhaDigitavel {
146 fn from(cod_barras: &CodBarras) -> Self {
147 let CodBarras(src) = cod_barras;
157
158 let mut digitable_line = [0_u8; Cobranca::LINHA_DIGITAVEL_LENGTH];
159 let (dv1, dv2, dv3) = cod_barras.calculate_dv_campos();
160
161 digitable_line[0..4].copy_from_slice(&src[0..4]);
163 digitable_line[4..9].copy_from_slice(&src[19..24]);
164 digitable_line[9] = dv1;
165
166 digitable_line[10..20].copy_from_slice(&src[24..34]);
168 digitable_line[20] = dv2;
169
170 digitable_line[21..31].copy_from_slice(&src[34..44]);
172 digitable_line[31] = dv3;
173
174 digitable_line[32] = src[4];
176
177 digitable_line[33..47].copy_from_slice(&src[5..19]);
179
180 Self(digitable_line)
181 }
182}
183
184impl std::fmt::Debug for LinhaDigitavel {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 f.debug_tuple("LinhaDigitavel")
187 .field(unsafe { &std::str::from_utf8_unchecked(&self.0)})
188 .finish()
189 }
190}
191
192impl fmt::Display for LinhaDigitavel {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.write_str(self.as_str())
195 }
196}
197
198impl std::ops::Deref for LinhaDigitavel {
199 type Target = [u8; Cobranca::LINHA_DIGITAVEL_LENGTH];
200
201 fn deref(&self) -> &Self::Target {
202 &self.0
203 }
204}
205
206impl Serialize for LinhaDigitavel {
207 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
208 where
209 S: serde::Serializer {
210 serializer.serialize_str(self.as_str())
211 }
212}
213
214
215#[derive(Debug, Serialize)]
216pub enum CodigoMoeda {
217 Real,
218 Outras,
219}
220
221impl Into<u8> for CodigoMoeda{
222 fn into(self) -> u8 {
223 match self {
224 Self::Real => b'9',
225 Self::Outras => b'0',
226 }
227 }
228}
229
230impl fmt::Display for CodigoMoeda {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 write!(f, "{}", match self {
233 Self::Real => "Real",
234 Self::Outras => "Outras",
235 })
236 }
237}
238
239#[derive(Debug, Serialize, Clone, Copy)]
240pub struct CodBanco(pub u16);
241
242impl fmt::Display for CodBanco {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 write!(f, "{:03}", self.0)
245 }
246}
247
248#[derive(Debug, Serialize)]
249#[serde(rename = "cobranca")]
250pub struct Cobranca {
251 pub cod_barras: CodBarras,
252 pub linha_digitavel: LinhaDigitavel,
253 pub cod_banco: CodBanco,
254 pub cod_moeda: CodigoMoeda,
255 #[serde(skip)]
256 pub digito_verificador: u8,
257 #[serde(skip)]
258 pub fator_vencimento: u16,
259 pub data_vencimento: Option<NaiveDate>,
260 pub valor: Option<f64>,
261}
262
263impl fmt::Display for Cobranca {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(
266 f,
267 concat!(
268 " Tipo: Cobrança\n",
269 "Código de barras: {}\n",
270 " Linha digitável: {}\n",
271 " Banco: {}\n",
272 " Moeda: {}\n",
273 " Valor: {}\n",
274 " Data Vencimento: {}"
275 ),
276 self.cod_barras,
277 self.linha_digitavel,
278 self.cod_banco,
279 self.cod_moeda,
280 match self.valor {
281 Some(v) => format!("{v:.2}"),
282 None => "Sem valor".to_owned(),
283 },
284 match self.data_vencimento {
285 Some(date) => format!("{date}"),
286 None => "Sem vencimento".to_owned(),
287 },
288 )
289 }
290}
291
292
293impl Cobranca {
294 pub const COD_BARRAS_LENGTH: usize = 44;
295 pub const LINHA_DIGITAVEL_LENGTH: usize = 47;
296
297 pub fn new(value: &[u8]) -> Result<Self, BoletoError> {
298 let (cod_barras, linha_digitavel): (CodBarras, LinhaDigitavel) = match value.len() {
299 Self::COD_BARRAS_LENGTH => {
300 let cod_barras = CodBarras::new(value)?;
301 let linha_digitavel = LinhaDigitavel::from(&cod_barras);
302 (cod_barras, linha_digitavel)
303 },
304 Self::LINHA_DIGITAVEL_LENGTH => {
305 let linha_digitavel = LinhaDigitavel::new(value)?;
306 ((&linha_digitavel).into(), linha_digitavel)
307 },
308 _ => return Err(BoletoError::InvalidLength),
309 };
310
311 let cod_banco = CodBanco(u8_array_to_u16(&cod_barras[0..3]));
312
313 let cod_moeda = match cod_barras[3] {
314 b'9' => CodigoMoeda::Real,
315 b'0' => CodigoMoeda::Outras,
316 _ => return Err(BoletoError::InvalidCodigoMoeda),
317 };
318
319 let fator_vencimento: u16 = u8_array_to_u16(&cod_barras[5..9]);
320
321 if fator_vencimento > 0 && fator_vencimento < 1000 {
322 return Err(BoletoError::InvalidFatorVencimento);
323 }
324
325 let valor = {
326 let x = unsafe { std::str::from_utf8_unchecked(&cod_barras[9..19]) };
327 match x.parse::<f64>().unwrap()
328 {
329 x if x.is_normal() => Some(x / 100.00),
330 _ => None,
331 }
332 };
333
334 let digito_verificador: u8 = {
335 let dv = cod_barras.calculate_dv();
336
337 if dv != cod_barras[4] - b'0' {
338 return Err(BoletoError::InvalidDigitoVerificadorGeral);
339 }
340
341 dv
342 };
343
344 {
345 let dvs = cod_barras.calculate_dv_campos();
346
347 if linha_digitavel[9] != dvs.0
348 || linha_digitavel[20] != dvs.1
349 || linha_digitavel[31] != dvs.2
350 {
351 return Err(BoletoError::InvalidDigitoVerificadorCampos);
352 }
353 };
354
355 Ok(Self {
356 cod_barras,
357 linha_digitavel,
358 cod_banco,
359 cod_moeda,
360 fator_vencimento,
361 digito_verificador,
362 data_vencimento: fator_vencimento_to_date(fator_vencimento),
363 valor,
364 })
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 #[test]
373 fn get_cod_banco_correctly() {
374 let barcodes: [(&[u8], u16); 5] = [
375 (b"11191444455555555556666666666666666666666666", 111),
376 (b"99996444455555555556666666666666666666666666", 999),
377 (b"12395444455555555556666666666666666666666666", 123),
378 (b"66691444455555555556666666666666666666666666", 666),
379 (b"00091444455555555556666666666666666666666666", 0),
380 ];
381 for (barcode, expected) in barcodes.iter() {
382 match Cobranca::new(barcode) {
383 Err(e) => {
384 panic!("Barcode should be considered valid. ({:?})", e);
385 }
386 Ok(result) => {
387 assert_eq!(result.cod_banco.0, *expected)
388 }
389 };
390 }
391 }
392
393 #[test]
394 fn get_cod_moeda_correctly() {
395 match Cobranca::new(b"11191444455555555556666666666666666666666666") {
396 Ok(result) => {
397 assert!(
398 matches!(result.cod_moeda, CodigoMoeda::Real),
399 "cod_moeda should be 'Real'",
400 );
401 }
402 Err(e) => {
403 panic!("Barcode should be considered valid. ({:?})", e);
404 }
405 };
406
407 match Cobranca::new(b"11105444455555555556666666666666666666666666") {
408 Ok(result) => {
409 assert!(
410 matches!(result.cod_moeda, CodigoMoeda::Outras),
411 "cod_moeda should be 'Outras'",
412 );
413 }
414 Err(e) => {
415 panic!("Barcode should be considered valid. ({:?})", e);
416 }
417 };
418 }
419
420 #[test]
421 fn get_fator_vencimento_correctly() {
422 let barcodes = [
423 (b"11196000055555555556666666666666666666666666", 0_u16, None),
424 (
425 b"11199100055555555556666666666666666666666666",
426 1000,
427 Some(NaiveDate::from_ymd_opt(2025, 2, 22).unwrap()),
428 ),
429 (
430 b"11191100255555555556666666666666666666666666",
431 1002,
432 Some(NaiveDate::from_ymd_opt(2025, 2, 24).unwrap()),
433 ),
434 (
435 b"11196166755555555556666666666666666666666666",
436 1667,
437 Some(NaiveDate::from_ymd_opt(2026, 12, 21).unwrap()),
438 ),
439 (
440 b"11198478955555555556666666666666666666666666",
441 4789,
442 Some(NaiveDate::from_ymd_opt(2010, 11, 17).unwrap()),
443 ),
444 (
445 b"11193999955555555556666666666666666666666666",
446 9999,
447 Some(NaiveDate::from_ymd_opt(2025, 2, 21).unwrap()),
448 ),
449 (
450 b"75696903800002500001434301033723400014933001",
451 9038,
452 Some(NaiveDate::from_ymd_opt(2022, 7, 6).unwrap()),
453 ),
454 (
455 b"00191667900002434790000002656973019362470618",
456 6679,
457 Some(NaiveDate::from_ymd_opt(2016, 1, 20).unwrap()),
458 ),
459 (
460 b"00195586200000773520000002464206011816073018",
461 5862,
462 Some(NaiveDate::from_ymd_opt(2013, 10, 25).unwrap()),
463 ),
464 (
465 b"75592896700003787000003389850761252543475984",
466 8967,
467 Some(NaiveDate::from_ymd_opt(2022, 4, 26).unwrap()),
468 ),
469 (
470 b"23791672000003249052028269705944177105205220",
471 6720,
472 Some(NaiveDate::from_ymd_opt(2016, 3, 1).unwrap()),
473 ),
474 (
475 b"23791672000003097902028060007024617500249000",
476 6720,
477 Some(NaiveDate::from_ymd_opt(2016, 3, 1).unwrap()),
478 ),
479 ];
480 for (barcode, expected_fator, expected_date) in barcodes {
481 match Cobranca::new(barcode.as_slice()) {
482 Err(e) => {
483 panic!("Barcode should be considered valid. ({:?})", e);
484 },
485 Ok(result) => {
486 assert_eq!(result.fator_vencimento, expected_fator);
487 assert_eq!(result.data_vencimento, expected_date);
488 },
489 };
490 }
491
492 assert!(
493 matches!(
494 Cobranca::new(b"11196000155555555556666666666666666666666666".as_slice()),
495 Err(BoletoError::InvalidFatorVencimento),
496 )
497 );
498
499 assert!(
500 matches!(
501 Cobranca::new(b"11196099955555555556666666666666666666666666".as_slice()),
502 Err(BoletoError::InvalidFatorVencimento),
503 )
504 );
505
506 }
507
508 #[test]
509 fn get_valor_correctly() {
510 let barcodes = [
511 (
512 b"11191444455555555556666666666666666666666666",
513 Some(55555555.55_f64),
514 ),
515 (
516 b"11196444499999999996666666666666666666666666",
517 Some(99999999.99),
518 ),
519 (b"11193444400000000006666666666666666666666666", None),
520 ];
521 for (barcode, expected) in barcodes.iter() {
522 match Cobranca::new(barcode.as_slice()) {
523 Err(e) => {
524 panic!("Barcode should be considered valid. ({:?}): {:?}", e, barcode);
525 }
526 Ok(result) => {
527 assert_eq!(result.valor, *expected);
528 }
529 };
530 }
531 }
532
533 #[test]
534 fn validate_digito_verificador_correctly() {
535 let barcodes = [
536 (b"11191444455555555556666666666666666666666666", 1_u8),
537 (b"10499898100000214032006561000100040099726390", 9_u8),
538 (b"75696903800002500001434301033723400014933001", 6_u8),
539 (b"00191667900002434790000002656973019362470618", 1_u8),
540 (b"00195586200000773520000002464206011816073018", 5_u8),
541 (b"75592896700003787000003389850761252543475984", 2_u8),
542 (b"23791672000003249052028269705944177105205220", 1_u8),
543 (b"23791672000003097902028060007024617500249000", 1_u8),
544 (b"11191100255555555556666666666666666666666666", 1_u8),
545 ];
546
547 for (barcode, expected) in barcodes.iter() {
548 assert_eq!(
549 CodBarras::new(*barcode).unwrap().calculate_dv(),
550 *expected,
551 );
552 }
553 }
554
555 #[test]
556 fn validate_convert_barcode_to_linha_digitavel() {
557 let barcodes = [
558 (
559 b"75691434360103372340200149330011690380000250000",
560 b"75696903800002500001434301033723400014933001",
561 ),
562 (
563 b"00190000090265697301993624706185166790000243479",
564 b"00191667900002434790000002656973019362470618",
565 ),
566 (
567 b"00190000090246420601618160730182558620000077352",
568 b"00195586200000773520000002464206011816073018",
569 ),
570 (
571 b"75590003318985076125825434759848289670000378700",
572 b"75592896700003787000003389850761252543475984",
573 ),
574 (
575 b"23792028296970594417671052052207167200000324905",
576 b"23791672000003249052028269705944177105205220",
577 ),
578 (
579 b"23792028036000702461975002490003167200000309790",
580 b"23791672000003097902028060007024617500249000",
581 ),
582 (
583 b"00000000000000000000000000000000000000000000000",
584 b"00000000000000000000000000000000000000000000",
585 ),
586 ];
587
588 for (linha_digitavel, barcode) in barcodes.iter() {
589 assert_eq!(
590 LinhaDigitavel::from(&CodBarras::new(*barcode).unwrap()).0,
591 **linha_digitavel,
592 );
593 }
594 }
595
596 #[test]
597 fn validate_converting_linha_digitavel_to_barcode() {
598 let barcodes = [
599 (
600 b"75691434360103372340200149330011690380000250000",
601 b"75696903800002500001434301033723400014933001",
602 ),
603 (
604 b"00190000090265697301993624706185166790000243479",
605 b"00191667900002434790000002656973019362470618",
606 ),
607 (
608 b"00190000090246420601618160730182558620000077352",
609 b"00195586200000773520000002464206011816073018",
610 ),
611 (
612 b"75590003318985076125825434759848289670000378700",
613 b"75592896700003787000003389850761252543475984",
614 ),
615 (
616 b"23792028296970594417671052052207167200000324905",
617 b"23791672000003249052028269705944177105205220",
618 ),
619 (
620 b"23792028036000702461975002490003167200000309790",
621 b"23791672000003097902028060007024617500249000",
622 ),
623 (
624 b"00000000000000000000000000000000000000000000000",
625 b"00000000000000000000000000000000000000000000",
626 ),
627 ];
628
629 for (linha_digitavel, barcode) in barcodes.iter() {
630 assert_eq!(
631 CodBarras::from(&LinhaDigitavel::new(*linha_digitavel).unwrap()).0,
632 **barcode,
633 );
634 }
635 }
636}