Module: BrazilianUtils::PixKeyUtils

Defined in:
lib/brazilian-utils/pix-key-utils.rb

Overview

Utilities for identifying and validating a Pix key (DICT key formats): a CPF, a CNPJ, an email, a Brazilian mobile phone or a random EVP key.

Constant Summary collapse

UUID_REGEX =
/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i.freeze
EMAIL_REGEX =
/\A[^\s@]+@[^\s@]+\.[^\s@]+\z/.freeze
MAX_EMAIL_LENGTH =
77

Class Method Summary collapse

Class Method Details

.get_info(value) ⇒ Hash?

Identifies a Pix key and normalizes it to the canonical form the DICT expects inside a BR Code.

Parameters:

  • value (String)

Returns:

  • (Hash, nil) —

    { type:, value: }, or nil when value is not a valid Pix key.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/brazilian-utils/pix-key-utils.rb', line 19

def self.get_info(value)
  return nil unless value.is_a?(String)

  str = value.strip
  return nil if str.empty?

  return { type: 'evp', value: str.downcase } if UUID_REGEX.match?(str)

  if str.include?('@')
    return nil if str.length > MAX_EMAIL_LENGTH
    return nil unless EMAIL_REGEX.match?(str)

    return { type: 'email', value: str.downcase }
  end

  if str.start_with?('+') || str.include?('(')
    digits = PhoneUtils.parse(str)
    return nil unless PhoneUtils.is_valid_mobile(digits)

    return { type: 'phone', value: "+55#{digits}" }
  end

  digits = str.gsub(/\D/, '')
  case digits.length
  when 11
    CPFUtils.valid?(digits) ? { type: 'cpf', value: digits } : nil
  when 14
    CNPJUtils.valid?(digits) ? { type: 'cnpj', value: digits } : nil
  end
end

.is_valid(value, options = {}) ⇒ Boolean Also known as: valid?

Checks whether a value is a valid Pix key.

Parameters:

  • value (String)
  • options (Hash) (defaults to: {}) —

    :types restricts the accepted key types (%w[cpf cnpj email phone evp]); an empty list rejects everything.

Returns:

  • (Boolean)


56
57
58
59
60
61
62
63
64
# File 'lib/brazilian-utils/pix-key-utils.rb', line 56

def self.is_valid(value, options = {})
  info = get_info(value)
  return false unless info

  types = options[:types] || options['types']
  return true if types.nil?

  types.map(&:to_s).include?(info[:type])
end