Skip to main content

boleto_utils/
cobranca.rs

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        // Cria um iterator que itera sobre os caracteres do código de barras
42        // exceto o dígito verificador
43        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        // 00000 00000 11111 111112 22222 222233 3 33333334444444
67        // 01234.56789 01234.567890 12345.678901 2 34567890123456
68        // AAABC.CCCCX DDDDD.DDDDDY EEEEE.EEEEEZ K UUUUVVVVVVVVVV
69
70        // 00000000001111111111222222222233333333334444
71        // 01234567890123456789012345678901234567890123
72        // AAABKUUUUVVVVVVVVVVCCCCCDDDDDDDDDDEEEEEEEEEE
73
74        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        // 00000000001111111111222222222233333333334444
148        // 01234567890123456789012345678901234567890123
149        // AAABKUUUUVVVVVVVVVVCCCCCDDDDDDDDDDEEEEEEEEEE
150
151        // 00000 00000 11111 111112 22222 222233 3 33333334444444
152        // 01234.56789 01234.567890 12345.678901 2 34567890123456
153        // AAABC.CCCCX DDDDD.DDDDDY EEEEE.EEEEEZ K UUUUVVVVVVVVVV
154        // 75691.43436 01033.723402 00149.330011 6 90380000250000
155
156        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        // Campo 1
162        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        // Campo 2
167        digitable_line[10..20].copy_from_slice(&src[24..34]);
168        digitable_line[20] = dv2;
169
170        // Campo 3
171        digitable_line[21..31].copy_from_slice(&src[34..44]);
172        digitable_line[31] = dv3;
173
174        // DV
175        digitable_line[32] = src[4];
176
177        // Campo 4
178        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}