br_validators 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +18 -0
- data/LICENSE +21 -0
- data/README.md +108 -0
- data/lib/br_validators/active_model.rb +33 -0
- data/lib/br_validators/boleto.rb +132 -0
- data/lib/br_validators/cnpj.rb +65 -0
- data/lib/br_validators/cns.rb +68 -0
- data/lib/br_validators/cpf.rb +56 -0
- data/lib/br_validators/locale/en.yml +8 -0
- data/lib/br_validators/locale/pt-BR.yml +8 -0
- data/lib/br_validators/pix.rb +57 -0
- data/lib/br_validators/validators/boleto_validator.rb +7 -0
- data/lib/br_validators/validators/cnpj_validator.rb +7 -0
- data/lib/br_validators/validators/cns_validator.rb +7 -0
- data/lib/br_validators/validators/cpf_validator.rb +7 -0
- data/lib/br_validators/validators/pix_validator.rb +7 -0
- data/lib/br_validators/version.rb +6 -0
- data/lib/br_validators.rb +22 -0
- metadata +61 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2a8eb5df36f0aae424cad2f9f7be8d3ca4b319d144b58023af72d61a94a597bc
|
|
4
|
+
data.tar.gz: 6cbba05d5a095be1b04d0d9b0e5aa63116fca27cd0b2eb2902fc7a66a844a7b7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: aa4a7ece5360498679b28eeef9b8dea03c73e24c2742bc7f6a669a2e97f8a5453bf0ee313dec7f5804f8ca3c34d4e7c57a24b56710ab1e23f583c5c0c90b11d0
|
|
7
|
+
data.tar.gz: f97f6579ff6a8333b86bff8da5eb6d279defa9aca7641b1eedc51e794b9884092d245ecacaaa45f68719d925246e7242e827daa8957e28886af5b262745a0642
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
As mudanças de cada versão ficam aqui. O projeto segue o
|
|
4
|
+
[versionamento semântico](https://semver.org/lang/pt-BR/).
|
|
5
|
+
|
|
6
|
+
## [Não lançado]
|
|
7
|
+
|
|
8
|
+
- Página para testar os validadores no navegador (https://br-validators-gem.vercel.app).
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
- `BrValidators::CPF` com `valid?`, `format` e `strip`.
|
|
13
|
+
- `BrValidators::CNPJ` para o formato numérico e o alfanumérico da Receita Federal.
|
|
14
|
+
- `BrValidators::CNS` com as rotinas de validação do Ministério da Saúde.
|
|
15
|
+
- `BrValidators::Pix` com `valid?` e `type`, seguindo a especificação do DICT.
|
|
16
|
+
- `BrValidators::Boleto` para a linha digitável de cobrança (47 dígitos) e de arrecadação (48 dígitos).
|
|
17
|
+
- Validadores para ActiveModel (`validates :cpf, cpf: true` e afins) com mensagens em pt-BR e en.
|
|
18
|
+
- Publicação automática no RubyGems ao criar uma tag.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pedro Pedrassolli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# br_validators
|
|
2
|
+
|
|
3
|
+
Gem Ruby para validar e formatar documentos brasileiros, sem dependências.
|
|
4
|
+
|
|
5
|
+
## Instalação
|
|
6
|
+
|
|
7
|
+
No `Gemfile`:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "br_validators"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> A versão 0.1.0 está pronta e vai ser publicada no RubyGems em breve. Até lá, use direto
|
|
14
|
+
> do GitHub: `gem "br_validators", github: "PedroPedrassolli/br_validators"`.
|
|
15
|
+
|
|
16
|
+
## O que tem
|
|
17
|
+
|
|
18
|
+
| Documento | Observação |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| CPF | |
|
|
21
|
+
| CNPJ | numérico e alfanumérico (o formato novo da Receita Federal, a partir de julho de 2026) |
|
|
22
|
+
| CNS | Cartão Nacional de Saúde |
|
|
23
|
+
| Chave Pix | CPF, CNPJ, e-mail, celular e chave aleatória |
|
|
24
|
+
| Boleto | linha digitável de cobrança e de arrecadação |
|
|
25
|
+
|
|
26
|
+
Cada documento tem os mesmos três métodos:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
BrValidators::CPF.valid?("123.456.789-09") # => true
|
|
30
|
+
BrValidators::CPF.format("12345678909") # => "123.456.789-09"
|
|
31
|
+
BrValidators::CPF.strip("123.456.789-09") # => "12345678909"
|
|
32
|
+
|
|
33
|
+
BrValidators::CNPJ.valid?("12.ABC.345/01DE-35") # => true (CNPJ alfanumérico)
|
|
34
|
+
BrValidators::CNPJ.format("12abc34501de35") # => "12.ABC.345/01DE-35"
|
|
35
|
+
|
|
36
|
+
BrValidators::CNS.format("898000000000436") # => "898 0000 0000 0436"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
A chave Pix é um caso à parte: em vez de `format` e `strip`, ela tem `type`, que diz
|
|
40
|
+
o tipo da chave. Ela precisa estar como o Banco Central guarda (sem máscara, e-mail e
|
|
41
|
+
chave aleatória em minúsculas):
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
BrValidators::Pix.type("+5511987654321") # => :phone
|
|
45
|
+
BrValidators::Pix.type("maria.souza@exemplo.com.br") # => :email
|
|
46
|
+
BrValidators::Pix.valid?("123.456.789-09") # => false (chave de CPF não tem máscara)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
O boleto aceita a linha digitável de cobrança (bancos) e de arrecadação (contas de
|
|
50
|
+
consumo e tributos) e também tem `type`:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
BrValidators::Boleto.type("00190.00009 02345.678904 12345.678176 1 10000000012345") # => :cobranca
|
|
54
|
+
BrValidators::Boleto.valid?("826700000019000000000000000000000000000000000000") # => true
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Rails e ActiveModel
|
|
58
|
+
|
|
59
|
+
Em projetos com ActiveModel (todo app Rails tem), os validadores entram sozinhos:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
class Cliente < ApplicationRecord
|
|
63
|
+
validates :cpf, cpf: true
|
|
64
|
+
validates :cnpj, cnpj: true, allow_blank: true
|
|
65
|
+
validates :cartao_sus, cns: true
|
|
66
|
+
validates :chave_pix, pix: { message: "não parece uma chave Pix" }
|
|
67
|
+
validates :linha_digitavel, boleto: true
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
As mensagens de erro vêm em português (`pt-BR`) e inglês (`en`). Para trocar o texto,
|
|
72
|
+
sobrescreva as chaves `errors.messages.cpf_invalid`, `cnpj_invalid`, `cns_invalid`,
|
|
73
|
+
`pix_invalid` e `boleto_invalid` no seu arquivo de tradução.
|
|
74
|
+
|
|
75
|
+
Os validadores se chamam `CpfValidator`, `CnpjValidator`, `CnsValidator`, `PixValidator` e
|
|
76
|
+
`BoletoValidator`, no nível de cima, porque é assim que o Rails encontra `cpf: true`.
|
|
77
|
+
|
|
78
|
+
Já existem boas gems para CPF e CNPJ. A ideia aqui é juntar num lugar só os documentos
|
|
79
|
+
que costumam aparecer em sistemas brasileiros (inclusive CNS, Pix e boleto), com
|
|
80
|
+
mensagens de erro em português e inglês.
|
|
81
|
+
|
|
82
|
+
## Página de teste
|
|
83
|
+
|
|
84
|
+
Dá para testar cada validador no navegador em https://br-validators-gem.vercel.app. A página
|
|
85
|
+
(`public/`) é HTML e JavaScript puro e chama uma função Ruby da Vercel
|
|
86
|
+
(`api/validar.rb`) que usa a própria gem, então as regras ficam num lugar só:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
GET /api/validar?documento=cnpj&valor=12.ABC.345/01DE-35
|
|
90
|
+
{"documento":"cnpj","valido":true,"formatado":"12.ABC.345/01DE-35","tipo":null}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Desenvolvimento
|
|
94
|
+
|
|
95
|
+
Precisa de Ruby 3.3 ou mais novo.
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
bundle install
|
|
99
|
+
bundle exec rake test # testes (a cobertura mínima é 100% das linhas)
|
|
100
|
+
bundle exec rubocop # estilo
|
|
101
|
+
node --test "test/js/*.test.mjs" # testes da página de teste
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
O passo a passo para contribuir está no [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
105
|
+
|
|
106
|
+
## Licença
|
|
107
|
+
|
|
108
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_model"
|
|
4
|
+
|
|
5
|
+
# Validação nos models do Rails (ou de qualquer classe com ActiveModel):
|
|
6
|
+
#
|
|
7
|
+
# validates :cpf, cpf: true
|
|
8
|
+
# validates :cnpj, cnpj: true, allow_blank: true
|
|
9
|
+
# validates :chave_pix, pix: { message: "não parece uma chave Pix" }
|
|
10
|
+
#
|
|
11
|
+
# O Rails procura a classe pelo nome da opção ("cpf: true" vira CpfValidator),
|
|
12
|
+
# por isso os validadores ficam no nível de cima, fora do módulo BrValidators.
|
|
13
|
+
# As mensagens de erro estão em lib/br_validators/locale (pt-BR e en).
|
|
14
|
+
I18n.load_path += Dir[File.expand_path("locale/*.yml", __dir__)]
|
|
15
|
+
|
|
16
|
+
module BrValidators
|
|
17
|
+
# Base comum: cada validador só diz qual módulo usa e qual mensagem mostra.
|
|
18
|
+
# allow_nil e allow_blank já são tratados pelo próprio ActiveModel.
|
|
19
|
+
class DocumentValidator < ActiveModel::EachValidator
|
|
20
|
+
def validate_each(record, attribute, value)
|
|
21
|
+
return if self.class::DOCUMENT.valid?(value)
|
|
22
|
+
|
|
23
|
+
record.errors.add(attribute, options[:message] || self.class::ERROR)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Um arquivo por validador (o Rails espera uma classe por arquivo).
|
|
29
|
+
require_relative "validators/cpf_validator"
|
|
30
|
+
require_relative "validators/cnpj_validator"
|
|
31
|
+
require_relative "validators/cns_validator"
|
|
32
|
+
require_relative "validators/pix_validator"
|
|
33
|
+
require_relative "validators/boleto_validator"
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrValidators
|
|
4
|
+
# Linha digitável de boleto. Aceita os dois tipos:
|
|
5
|
+
#
|
|
6
|
+
# Cobrança (bancos), 47 dígitos em 5 campos:
|
|
7
|
+
# AAABC.CCCCX DDDDD.DDDDDY EEEEE.EEEEEZ K UUUUVVVVVVVVVV
|
|
8
|
+
# - campos 1, 2 e 3 terminam com um dígito verificador de módulo 10;
|
|
9
|
+
# - K é o dígito verificador geral do código de barras (módulo 11);
|
|
10
|
+
# - UUUU é o fator de vencimento e V o valor.
|
|
11
|
+
# Fonte: especificação de boleto do Banco do Brasil, que segue o padrão FEBRABAN.
|
|
12
|
+
# https://www.bb.com.br/docs/pub/emp/empl/dwn/Doc5175Bloqueto.pdf
|
|
13
|
+
#
|
|
14
|
+
# Arrecadação (contas de consumo, tributos), 48 dígitos, sempre começa com 8:
|
|
15
|
+
# 4 blocos de 11 dígitos, cada um seguido do seu dígito verificador.
|
|
16
|
+
# O 3º dígito diz o módulo: 6 ou 7 usam módulo 10; 8 ou 9 usam módulo 11.
|
|
17
|
+
# O 4º dígito é o verificador geral do código de barras.
|
|
18
|
+
# Fonte: "Layout padrão de arrecadação" da FEBRABAN, versão 7 (2023). O manual
|
|
19
|
+
# diz que o dígito de cada bloco usa o mesmo módulo do verificador geral.
|
|
20
|
+
# https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%207%20-%2001_03_2023_mn.pdf
|
|
21
|
+
module Boleto
|
|
22
|
+
# Pontos, espaços e hífens que aparecem na linha impressa.
|
|
23
|
+
MASK = /[.\-\s]/
|
|
24
|
+
|
|
25
|
+
def self.strip(value)
|
|
26
|
+
value.to_s.gsub(MASK, "")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# :cobranca, :arrecadacao ou nil se a linha for inválida.
|
|
30
|
+
def self.type(value)
|
|
31
|
+
line = strip(value)
|
|
32
|
+
return :cobranca if line.match?(/\A\d{47}\z/) && valid_cobranca?(line)
|
|
33
|
+
return :arrecadacao if line.match?(/\A8\d{47}\z/) && valid_arrecadacao?(line)
|
|
34
|
+
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.valid?(value)
|
|
39
|
+
!type(value).nil?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Devolve a linha com a máscara de cada tipo, ou nil se for inválida:
|
|
43
|
+
# cobrança: "00190.00009 02345.678904 12345.678176 1 10000000012345"
|
|
44
|
+
# arrecadação: "82670000001-9 00000000000-0 00000000000-0 00000000000-0"
|
|
45
|
+
def self.format(value)
|
|
46
|
+
line = strip(value)
|
|
47
|
+
case type(line)
|
|
48
|
+
when :cobranca then format_cobranca(line)
|
|
49
|
+
when :arrecadacao then line.scan(/\d{12}/).map { |block| "#{block[0, 11]}-#{block[11]}" }.join(" ")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.format_cobranca(line)
|
|
54
|
+
"#{line[0, 5]}.#{line[5, 5]} #{line[10, 5]}.#{line[15, 6]} " \
|
|
55
|
+
"#{line[21, 5]}.#{line[26, 6]} #{line[32]} #{line[33, 14]}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.valid_cobranca?(line)
|
|
59
|
+
fields = [line[0, 10], line[10, 11], line[21, 11]]
|
|
60
|
+
return false unless fields.all? { |field| check_digit_ok?(field, method(:mod10)) }
|
|
61
|
+
|
|
62
|
+
barcode = cobranca_barcode(line)
|
|
63
|
+
barcode[4] == mod11_cobranca(without(barcode, 4)).to_s
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Remonta o código de barras (44 dígitos) a partir da linha digitável:
|
|
67
|
+
# banco e moeda, DV geral, fator e valor, e o "campo livre" dos campos 1 a 3.
|
|
68
|
+
def self.cobranca_barcode(line)
|
|
69
|
+
line[0, 4] + line[32] + line[33, 14] + line[4, 5] + line[10, 10] + line[21, 10]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.valid_arrecadacao?(line)
|
|
73
|
+
check = arrecadacao_check(line[2])
|
|
74
|
+
return false unless check
|
|
75
|
+
|
|
76
|
+
blocks = line.scan(/\d{12}/)
|
|
77
|
+
return false unless blocks.all? { |block| check_digit_ok?(block, check) }
|
|
78
|
+
|
|
79
|
+
barcode = blocks.map { |block| block[0, 11] }.join
|
|
80
|
+
barcode[3] == check.call(without(barcode, 3)).to_s
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# O 3º dígito da arrecadação diz qual módulo usar. Outros valores são inválidos.
|
|
84
|
+
def self.arrecadacao_check(value_id)
|
|
85
|
+
case value_id
|
|
86
|
+
when "6", "7" then method(:mod10)
|
|
87
|
+
when "8", "9" then method(:mod11_arrecadacao)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# O último dígito do trecho é o verificador dos anteriores?
|
|
92
|
+
def self.check_digit_ok?(chunk, check)
|
|
93
|
+
chunk[-1] == check.call(chunk[0..-2]).to_s
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# O texto sem o caractere da posição indicada (onde fica o DV geral).
|
|
97
|
+
def self.without(text, index)
|
|
98
|
+
text[0, index] + text[(index + 1)..]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Pesos 2, 1, 2, 1... da direita para a esquerda; produtos com dois algarismos
|
|
102
|
+
# entram somados (12 vira 1 + 2). O dígito é o que falta para a próxima dezena.
|
|
103
|
+
def self.mod10(digits)
|
|
104
|
+
sum = digits.reverse.chars.each_with_index.sum do |digit, index|
|
|
105
|
+
product = digit.to_i * (index.even? ? 2 : 1)
|
|
106
|
+
(product / 10) + (product % 10)
|
|
107
|
+
end
|
|
108
|
+
(10 - (sum % 10)) % 10
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Pesos 2 a 9, da direita para a esquerda, voltando ao 2 depois do 9.
|
|
112
|
+
def self.mod11_sum(digits)
|
|
113
|
+
digits.reverse.chars.each_with_index.sum { |digit, index| digit.to_i * ((index % 8) + 2) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Na cobrança, quando a conta dá 0, 10 ou 11, o dígito vira 1 (nunca é 0).
|
|
117
|
+
def self.mod11_cobranca(digits)
|
|
118
|
+
digit = 11 - (mod11_sum(digits) % 11)
|
|
119
|
+
[0, 10, 11].include?(digit) ? 1 : digit
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Na arrecadação, resto 0 ou 1 vira dígito 0.
|
|
123
|
+
def self.mod11_arrecadacao(digits)
|
|
124
|
+
rest = mod11_sum(digits) % 11
|
|
125
|
+
rest < 2 ? 0 : 11 - rest
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private_class_method :format_cobranca, :valid_cobranca?, :cobranca_barcode, :valid_arrecadacao?,
|
|
129
|
+
:arrecadacao_check, :check_digit_ok?, :without, :mod10, :mod11_sum,
|
|
130
|
+
:mod11_cobranca, :mod11_arrecadacao
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrValidators
|
|
4
|
+
# CNPJ: 14 caracteres. Os 12 primeiros podem ser números ou letras maiúsculas
|
|
5
|
+
# (formato alfanumérico, usado pela Receita Federal a partir de julho de 2026);
|
|
6
|
+
# os 2 últimos são sempre dígitos verificadores. Os CNPJs só com números
|
|
7
|
+
# continuam válidos e seguem a mesma conta.
|
|
8
|
+
#
|
|
9
|
+
# Cada caractere vale o código ASCII dele menos 48: "0" vale 0, "9" vale 9,
|
|
10
|
+
# "A" vale 17, "Z" vale 42. Os dígitos verificadores usam módulo 11 com pesos
|
|
11
|
+
# de 2 a 9, da direita para a esquerda (voltando ao 2 depois do 9).
|
|
12
|
+
# Se o resto for 0 ou 1, o dígito é 0; senão, é 11 menos o resto.
|
|
13
|
+
#
|
|
14
|
+
# Fonte: manual do dígito verificador do CNPJ, da Receita Federal.
|
|
15
|
+
# https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj/manual-dv-cnpj.pdf
|
|
16
|
+
module CNPJ
|
|
17
|
+
# Caracteres de máscara aceitos na entrada: ponto, barra, hífen e espaços.
|
|
18
|
+
MASK = %r{[./\-\s]}
|
|
19
|
+
|
|
20
|
+
# 12 caracteres alfanuméricos e 2 dígitos no final.
|
|
21
|
+
PATTERN = /\A[0-9A-Z]{12}\d{2}\z/
|
|
22
|
+
|
|
23
|
+
# Tira a máscara e passa as letras para maiúsculas:
|
|
24
|
+
# "12.abc.345/01de-35" vira "12ABC34501DE35".
|
|
25
|
+
# A Receita só usa maiúsculas, mas é comum alguém digitar em minúsculas.
|
|
26
|
+
def self.strip(value)
|
|
27
|
+
value.to_s.gsub(MASK, "").upcase
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.valid?(value)
|
|
31
|
+
cnpj = strip(value)
|
|
32
|
+
return false unless cnpj.match?(PATTERN)
|
|
33
|
+
|
|
34
|
+
# 00.000.000/0000-00 e parecidos passam na conta, mas não existem.
|
|
35
|
+
return false if cnpj.squeeze.size == 1
|
|
36
|
+
|
|
37
|
+
cnpj[12, 2] == check_digits(cnpj[0, 12])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Devolve "12.ABC.345/01DE-35", ou nil se o CNPJ for inválido.
|
|
41
|
+
def self.format(value)
|
|
42
|
+
return nil unless valid?(value)
|
|
43
|
+
|
|
44
|
+
cnpj = strip(value)
|
|
45
|
+
"#{cnpj[0, 2]}.#{cnpj[2, 3]}.#{cnpj[5, 3]}/#{cnpj[8, 4]}-#{cnpj[12, 2]}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.check_digits(base)
|
|
49
|
+
first = check_digit(base)
|
|
50
|
+
second = check_digit("#{base}#{first}")
|
|
51
|
+
"#{first}#{second}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.check_digit(chars)
|
|
55
|
+
# Pesos 2, 3, ..., 9, 2, 3... a partir do último caractere.
|
|
56
|
+
sum = chars.reverse.chars.each_with_index.sum do |char, index|
|
|
57
|
+
(char.ord - 48) * ((index % 8) + 2)
|
|
58
|
+
end
|
|
59
|
+
rest = sum % 11
|
|
60
|
+
(rest < 2 ? 0 : 11 - rest).to_s
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private_class_method :check_digits, :check_digit
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrValidators
|
|
4
|
+
# CNS (Cartão Nacional de Saúde): 15 dígitos, só números.
|
|
5
|
+
#
|
|
6
|
+
# A validação segue as duas rotinas do Ministério da Saúde:
|
|
7
|
+
# - Números que começam com 1 ou 2 são gerados a partir do PIS (os 11 primeiros
|
|
8
|
+
# dígitos). A soma do PIS com pesos de 15 a 5 dá o dígito verificador; o número
|
|
9
|
+
# esperado é PIS + "000" + DV, ou PIS + "001" + DV quando a primeira conta dá 10.
|
|
10
|
+
# - Números que começam com 7, 8 ou 9 são válidos quando a soma dos 15 dígitos,
|
|
11
|
+
# com pesos de 15 a 1, é divisível por 11.
|
|
12
|
+
# Qualquer outro primeiro dígito é inválido.
|
|
13
|
+
#
|
|
14
|
+
# O CNS não tem máscara oficial. O 16º número impresso no cartão é o número da
|
|
15
|
+
# via e não faz parte do CNS.
|
|
16
|
+
#
|
|
17
|
+
# Fonte: regras de validação do CNS publicadas pela Anvisa, com as rotinas do
|
|
18
|
+
# Ministério da Saúde.
|
|
19
|
+
# https://rni-docs.anvisa.gov.br/docs/regras_gerais/validacoes/validacaoCNS/
|
|
20
|
+
module CNS
|
|
21
|
+
# O cartão mostra o número em grupos separados por espaço; aceitamos também
|
|
22
|
+
# pontos e hífens, que às vezes aparecem em sistemas antigos.
|
|
23
|
+
MASK = /[.\-\s]/
|
|
24
|
+
|
|
25
|
+
def self.strip(value)
|
|
26
|
+
value.to_s.gsub(MASK, "")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.valid?(value)
|
|
30
|
+
cns = strip(value)
|
|
31
|
+
return false unless cns.match?(/\A\d{15}\z/)
|
|
32
|
+
|
|
33
|
+
case cns[0]
|
|
34
|
+
when "1", "2" then cns == from_pis(cns[0, 11])
|
|
35
|
+
when "7", "8", "9" then (weighted_sum(cns) % 11).zero?
|
|
36
|
+
else false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Devolve o CNS em grupos de 3, 4, 4 e 4 dígitos, como aparece impresso no
|
|
41
|
+
# cartão ("123 4567 8901 0000"), ou nil se for inválido.
|
|
42
|
+
def self.format(value)
|
|
43
|
+
return nil unless valid?(value)
|
|
44
|
+
|
|
45
|
+
cns = strip(value)
|
|
46
|
+
"#{cns[0, 3]} #{cns[3, 4]} #{cns[7, 4]} #{cns[11, 4]}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Monta o CNS esperado para um PIS, como na rotina do Ministério da Saúde.
|
|
50
|
+
def self.from_pis(pis)
|
|
51
|
+
sum = weighted_sum(pis)
|
|
52
|
+
digit = 11 - (sum % 11)
|
|
53
|
+
digit = 0 if digit == 11
|
|
54
|
+
return "#{pis}000#{digit}" unless digit == 10
|
|
55
|
+
|
|
56
|
+
# Quando o dígito dá 10, a rotina soma 2 e coloca "001" no meio.
|
|
57
|
+
digit = 11 - ((sum + 2) % 11)
|
|
58
|
+
"#{pis}001#{digit}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Pesos começando em 15 e diminuindo de 1 em 1 a cada dígito.
|
|
62
|
+
def self.weighted_sum(digits)
|
|
63
|
+
digits.chars.each_with_index.sum { |digit, index| digit.to_i * (15 - index) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private_class_method :from_pis, :weighted_sum
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrValidators
|
|
4
|
+
# CPF: 11 dígitos, sendo os dois últimos verificadores.
|
|
5
|
+
#
|
|
6
|
+
# Cada dígito verificador é calculado com módulo 11 sobre os dígitos anteriores:
|
|
7
|
+
# o primeiro usa pesos de 10 a 2 nos 9 primeiros dígitos; o segundo usa pesos de
|
|
8
|
+
# 11 a 2 nos 10 primeiros (os 9 + o primeiro verificador). Se o resto da divisão
|
|
9
|
+
# por 11 for 0 ou 1, o dígito é 0; senão, é 11 menos o resto.
|
|
10
|
+
module CPF
|
|
11
|
+
# Caracteres de máscara que aceitamos na entrada: ponto, hífen e espaços.
|
|
12
|
+
MASK = /[.\-\s]/
|
|
13
|
+
|
|
14
|
+
# Tira a máscara: "123.456.789-09" vira "12345678909".
|
|
15
|
+
# Não remove letras nem outros símbolos, para que valid? possa recusá-los.
|
|
16
|
+
def self.strip(value)
|
|
17
|
+
value.to_s.gsub(MASK, "")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.valid?(value)
|
|
21
|
+
digits = strip(value)
|
|
22
|
+
return false unless digits.match?(/\A\d{11}\z/)
|
|
23
|
+
|
|
24
|
+
# 111.111.111-11, 222.222.222-22... passam na conta, mas não são CPFs reais.
|
|
25
|
+
return false if digits.squeeze.size == 1
|
|
26
|
+
|
|
27
|
+
digits[9, 2] == check_digits(digits[0, 9])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Devolve "123.456.789-09", ou nil se o CPF for inválido
|
|
31
|
+
# (formatar um número errado esconderia o erro de quem chamou).
|
|
32
|
+
def self.format(value)
|
|
33
|
+
return nil unless valid?(value)
|
|
34
|
+
|
|
35
|
+
digits = strip(value)
|
|
36
|
+
"#{digits[0, 3]}.#{digits[3, 3]}.#{digits[6, 3]}-#{digits[9, 2]}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.check_digits(base)
|
|
40
|
+
first = check_digit(base)
|
|
41
|
+
second = check_digit("#{base}#{first}")
|
|
42
|
+
"#{first}#{second}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Pesos decrescentes até 2, começando por (tamanho + 1): 10..2 para 9 dígitos,
|
|
46
|
+
# 11..2 para 10 dígitos.
|
|
47
|
+
def self.check_digit(digits)
|
|
48
|
+
weights = (2..(digits.size + 1)).to_a.reverse
|
|
49
|
+
sum = digits.chars.zip(weights).sum { |digit, weight| digit.to_i * weight }
|
|
50
|
+
rest = sum % 11
|
|
51
|
+
(rest < 2 ? 0 : 11 - rest).to_s
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private_class_method :check_digits, :check_digit
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrValidators
|
|
4
|
+
# Chave Pix: diz se a chave está no formato que o DICT (o diretório de chaves
|
|
5
|
+
# do Banco Central) aceita e qual é o tipo dela.
|
|
6
|
+
#
|
|
7
|
+
# Tipos e formatos, como o DICT guarda as chaves (sem máscara):
|
|
8
|
+
# - :cpf -> 11 dígitos, com os dígitos verificadores corretos
|
|
9
|
+
# - :cnpj -> 14 caracteres, numérico ou alfanumérico, com os verificadores corretos
|
|
10
|
+
# - :phone -> "+", código do país e número, só dígitos (ex.: "+5511987654321")
|
|
11
|
+
# - :email -> em minúsculas, com no máximo 77 caracteres
|
|
12
|
+
# - :evp -> chave aleatória, um UUID em minúsculas
|
|
13
|
+
#
|
|
14
|
+
# Fontes: especificação da API do DICT e Manual Operacional do DICT (v8.5),
|
|
15
|
+
# que passou a aceitar o CNPJ alfanumérico.
|
|
16
|
+
# https://github.com/bacen/pix-dict-api
|
|
17
|
+
# https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/X_ManualOperacionaldoDICT.pdf
|
|
18
|
+
module Pix
|
|
19
|
+
MAX_LENGTH = 77
|
|
20
|
+
|
|
21
|
+
PHONE = /\A\+[1-9][0-9]\d{1,14}\z/
|
|
22
|
+
EVP = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/
|
|
23
|
+
|
|
24
|
+
# Mesma expressão da especificação do DICT, quebrada em linhas (o "x" no
|
|
25
|
+
# final faz o Ruby ignorar as quebras). O "\#" precisa da barra: sem ela,
|
|
26
|
+
# o Ruby leria "#$&" como uma variável e sumiria com esses caracteres.
|
|
27
|
+
EMAIL = %r{
|
|
28
|
+
\A[a-z0-9.!\#$&'*+/=?^_`{|}~-]+
|
|
29
|
+
@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?
|
|
30
|
+
(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\z
|
|
31
|
+
}x
|
|
32
|
+
|
|
33
|
+
# Cada tipo e a verificação dele, na ordem em que são testados.
|
|
34
|
+
CHECKS = {
|
|
35
|
+
cpf: ->(key) { key.match?(/\A\d{11}\z/) && CPF.valid?(key) },
|
|
36
|
+
cnpj: ->(key) { key.match?(/\A[0-9A-Z]{12}\d{2}\z/) && CNPJ.valid?(key) },
|
|
37
|
+
phone: ->(key) { key.match?(PHONE) },
|
|
38
|
+
email: ->(key) { key.match?(EMAIL) },
|
|
39
|
+
evp: ->(key) { key.match?(EVP) }
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
# Devolve :cpf, :cnpj, :phone, :email ou :evp, ou nil se a chave for inválida.
|
|
43
|
+
# Só os espaços nas pontas são ignorados: o resto precisa estar exatamente
|
|
44
|
+
# como o DICT guarda (sem máscara, e-mail e chave aleatória em minúsculas).
|
|
45
|
+
def self.type(value)
|
|
46
|
+
key = value.to_s.strip
|
|
47
|
+
return nil if key.size > MAX_LENGTH
|
|
48
|
+
|
|
49
|
+
found = CHECKS.find { |_type, check| check.call(key) }
|
|
50
|
+
found&.first
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.valid?(value)
|
|
54
|
+
!type(value).nil?
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "br_validators/version"
|
|
4
|
+
require_relative "br_validators/cpf"
|
|
5
|
+
require_relative "br_validators/cnpj"
|
|
6
|
+
require_relative "br_validators/cns"
|
|
7
|
+
require_relative "br_validators/pix"
|
|
8
|
+
require_relative "br_validators/boleto"
|
|
9
|
+
|
|
10
|
+
# Validação e formatação de documentos brasileiros.
|
|
11
|
+
#
|
|
12
|
+
# Cada documento tem um módulo próprio (BrValidators::CPF, BrValidators::CNPJ...)
|
|
13
|
+
# com os mesmos três métodos:
|
|
14
|
+
# valid?(valor) -> true ou false
|
|
15
|
+
# format(valor) -> o documento com a máscara, ou nil se for inválido
|
|
16
|
+
# strip(valor) -> o documento sem a máscara
|
|
17
|
+
module BrValidators
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Validação nos models (validates :cpf, cpf: true): só entra quando o projeto tem
|
|
21
|
+
# o ActiveModel (todo app Rails tem), para a gem continuar sem dependências.
|
|
22
|
+
require_relative "br_validators/active_model" if Gem.loaded_specs.key?("activemodel")
|
metadata
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: br_validators
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pedro Pedrassolli
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
email:
|
|
13
|
+
- pedropedrassolli@outlook.com
|
|
14
|
+
executables: []
|
|
15
|
+
extensions: []
|
|
16
|
+
extra_rdoc_files: []
|
|
17
|
+
files:
|
|
18
|
+
- CHANGELOG.md
|
|
19
|
+
- LICENSE
|
|
20
|
+
- README.md
|
|
21
|
+
- lib/br_validators.rb
|
|
22
|
+
- lib/br_validators/active_model.rb
|
|
23
|
+
- lib/br_validators/boleto.rb
|
|
24
|
+
- lib/br_validators/cnpj.rb
|
|
25
|
+
- lib/br_validators/cns.rb
|
|
26
|
+
- lib/br_validators/cpf.rb
|
|
27
|
+
- lib/br_validators/locale/en.yml
|
|
28
|
+
- lib/br_validators/locale/pt-BR.yml
|
|
29
|
+
- lib/br_validators/pix.rb
|
|
30
|
+
- lib/br_validators/validators/boleto_validator.rb
|
|
31
|
+
- lib/br_validators/validators/cnpj_validator.rb
|
|
32
|
+
- lib/br_validators/validators/cns_validator.rb
|
|
33
|
+
- lib/br_validators/validators/cpf_validator.rb
|
|
34
|
+
- lib/br_validators/validators/pix_validator.rb
|
|
35
|
+
- lib/br_validators/version.rb
|
|
36
|
+
homepage: https://github.com/PedroPedrassolli/br_validators
|
|
37
|
+
licenses:
|
|
38
|
+
- MIT
|
|
39
|
+
metadata:
|
|
40
|
+
source_code_uri: https://github.com/PedroPedrassolli/br_validators
|
|
41
|
+
changelog_uri: https://github.com/PedroPedrassolli/br_validators/blob/main/CHANGELOG.md
|
|
42
|
+
bug_tracker_uri: https://github.com/PedroPedrassolli/br_validators/issues
|
|
43
|
+
rubygems_mfa_required: 'true'
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '3.3'
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.6.9
|
|
59
|
+
specification_version: 4
|
|
60
|
+
summary: Valida e formata CPF, CNPJ (inclusive alfanumérico), CNS, chave Pix e boleto.
|
|
61
|
+
test_files: []
|