Brasilex

Elixir library for Brazilian utilities and helpers.

[![Hex.pm](https://img.shields.io/hexpm/v/brasilex.svg)](https://hex.pm/packages/brasilex) [![Hex Docs](https://img.shields.io/badge/hex-docs-blue.svg)](https://hexdocs.pm/brasilex) [![License](https://img.shields.io/hexpm/l/brasilex.svg)](https://github.com/stlucasgarcia/brasilex/blob/main/LICENSE)

Features

Boleto (Bank Slip)

State Registration (Inscrição Estadual - IE)

General

Roadmap

Upcoming features for future releases:

Installation

Add brasilex to your list of dependencies in mix.exs:

def deps do
  [
    {:brasilex, "~> 0.2.0"}
  ]
end

Then run:

$ mix deps.get

Usage

Validate a Boleto

# Returns :ok or {:error, reason}
Brasilex.validate_boleto("00190.00009 01234.567890 12345.678908 1 00000000000000")
#=> :ok

Brasilex.validate_boleto("invalid")
#=> {:error, :invalid_length}

# Bang version raises on error
Brasilex.validate_boleto!("00190.00009 01234.567890 12345.678908 1 00000000000000")
#=> :ok

Brasilex.validate_boleto!("invalid")
#=> ** (Brasilex.ValidationError) Invalid length: wrong number of digits

Parse a Boleto

{:ok, boleto} = Brasilex.parse_boleto("00190000090123456789012345678908100000000000000")

boleto.type        #=> :banking
boleto.bank_code   #=> "001"
boleto.amount      #=> Decimal.new("150.00") or nil
boleto.due_date    #=> ~D[2020-07-04] or nil
boleto.barcode     #=> "00191000000000000001234567890123456789080"

# Bang version raises on error
boleto = Brasilex.parse_boleto!("00190000090123456789012345678908100000000000000")

Input Formats

The library accepts both linha digitável and barcode, with or without formatting:

# Linha digitável with dots, spaces, hyphens
Brasilex.validate_boleto("00190.00009 01234.567890 12345.678908 1 00000000000000")

# Linha digitável digits only (47 or 48 digits)
Brasilex.validate_boleto("00190000090123456789012345678908100000000000000")

# Barcode (44 digits)
Brasilex.validate_boleto("23791843400000199003812860000000003000000004")

Boleto Types

Banking Boleto (47 digits)

Bank collection boletos used for payments, invoices, etc.

{:ok, boleto} = Brasilex.parse_boleto("23793.38128 60000.000003 00000.000400 1 84340000019900")

boleto.type          #=> :banking
boleto.bank_code     #=> "237" (e.g., "001" = Banco do Brasil)
boleto.currency_code #=> "9" (BRL)
boleto.amount        #=> Decimal.new("199.00") or nil if any amount
boleto.due_date      #=> ~D[2020-07-04] or nil if no due date
boleto.free_field    #=> "3812860000000003000000004" (25 digits of bank-defined content)

Convenio Boleto (48 digits)

Utility bills, taxes, and government collections. First digit is always "8".

{:ok, boleto} = Brasilex.parse_boleto("846700000005 573200481018 150820204176 494672890166")

boleto.type       #=> :convenio
boleto.segment    #=> "6" (determines validation algorithm)
boleto.amount     #=> Decimal.new("573.20") or nil
boleto.company_id #=> "0481018150820204176494672890166"
boleto.free_field #=> Segment-specific content

State Registration (IE)

Validate an IE

# Returns :ok or {:error, reason}
Brasilex.validate_ie("110.042.490.114")
#=> :ok

Brasilex.validate_ie("12345")
#=> {:error, :invalid_length}

# Bang version raises on error
Brasilex.validate_ie!("110.042.490.114")
#=> :ok

Parse an IE

# Returns all states that match the IE
{:ok, [ie]} = Brasilex.parse_ie("110.042.490.114")

ie.state      #=> :sp
ie.raw        #=> "110042490114"
ie.formatted  #=> "110.042.490.114"

# Some IEs are valid for multiple states (shared algorithms)
{:ok, ies} = Brasilex.parse_ie("820000000")
Enum.map(ies, & &1.state)
#=> [:am, :sc, :se]

# Each IE has state-specific formatting
am_ie = Enum.find(ies, & &1.state == :am)
am_ie.formatted  #=> "82.000.000-0"

sc_ie = Enum.find(ies, & &1.state == :sc)
sc_ie.formatted  #=> "820.000.000"

Supported States

All 27 Brazilian states are supported with their specific validation rules:

State Digits Algorithm Notes
AC 13 Mod11 (2 DVs) Prefix "01"
AL 9 Mod11 Prefix "24", type codes 0,3,5,7,8
AM 9 Mod11 Special case when sum < 11
AP 9 Mod11 Prefix "03", special p/d values
BA 8-9 Mod10/Mod11 Based on first digit
CE 9 Mod11 Weights 9-2
DF 13 Mod11 (2 DVs) Prefix "07"
ES 9 Mod11 Weights 9-2
GO 9 Mod11 Prefixes 10, 11, 20-29
MA 9 Mod11 Prefix "12"
MG 13 Mod10 + Mod11 2 check digits
MS 9 Mod11 Prefix "28"
MT 11 Mod11 Single DV
PA 9 Mod11 Prefixes 15, 75-79
PB 9 Mod11 Weights 9-2
PE 9/14 Mod11 eFisco (9) or CACEPE (14)
PI 9 Mod11 Weights 9-2
PR 10 Mod11 (2 DVs) Weights 3,2,7,6,5,4,3,2
RJ 8 Mod11 Weights 2,7,6,5,4,3,2
RN 9-10 Mod11 Prefix "20"
RO 9/14 Mod11 Legacy (9) or new (14)
RR 9 Mod9 Prefix "24"
RS 10 Mod11 Single DV
SC 9 Mod11 Weights 9-2
SE 9 Mod11 Weights 9-2
SP 12/13 Custom Regular (12) or rural "P" (13)
TO 11 Mod11 Type codes 01, 02, 03, 99

Error Handling

All functions return {:ok, result} or {:error, reason} tuples:

case Brasilex.validate_boleto(input) do
  :ok ->
    IO.puts("Valid boleto!")

  {:error, :invalid_length} ->
    IO.puts("Wrong number of digits (expected 47 or 48)")

  {:error, :invalid_format} ->
    IO.puts("Invalid characters found")

  {:error, :invalid_checksum} ->
    IO.puts("General check digit validation failed")

  {:error, {:invalid_field_checksum, n}} ->
    IO.puts("Field #{n} check digit validation failed")

  {:error, :unknown_type} ->
    IO.puts("Could not determine boleto type")
end

Bang Variants

Bang variants raise Brasilex.ValidationError for cleaner pipelines:

try do
  boleto = Brasilex.parse_boleto!("invalid")
  # Process boleto...
rescue
  e in Brasilex.ValidationError ->
    IO.puts("Validation failed: #{e.message}")
end

Response Types with Structs

For better type safety and developer experience, Brasilex provides struct definitions:

# Boleto struct with all parsed fields
{:ok, %Brasilex.Boleto{} = boleto} = Brasilex.parse_boleto("...")

# Banking boleto fields
boleto.type          #=> :banking
boleto.bank_code     #=> String.t()
boleto.currency_code #=> String.t()
boleto.amount        #=> Decimal.t() | nil
boleto.due_date      #=> Date.t() | nil
boleto.free_field    #=> String.t()
boleto.barcode       #=> String.t()

# Convenio boleto fields
boleto.type       #=> :convenio
boleto.segment    #=> String.t()
boleto.amount     #=> Decimal.t() | nil
boleto.company_id #=> String.t()
boleto.free_field #=> String.t()
boleto.barcode    #=> String.t()

# IE struct with parsed fields
{:ok, [%Brasilex.IE{} = ie]} = Brasilex.parse_ie("110.042.490.114")

ie.state      #=> :sp
ie.raw        #=> "110042490114"
ie.formatted  #=> "110.042.490.114"

Available Structs

Struct Description
Brasilex.Boleto Parsed boleto with all fields (type, amount, dates, etc.)
Brasilex.IE Parsed state registration with state, raw, and formatted
Brasilex.ValidationError Exception raised by bang functions

Validation Details

Check Digit Algorithms

Brasilex validates boletos using the standard Brazilian algorithms:

What Gets Validated

  1. Length: Must be exactly 44, 47, or 48 digits (after removing formatting)
  2. Format: Must contain only digits (after removing dots, spaces, hyphens)
  3. Check digits: All field-level and general check digits are verified
  4. Type detection: Boleto type is determined from digit count and first character
    • 44 digits starting with "8" → Convenio barcode
    • 44 digits (other) → Banking barcode
    • 47 digits → Banking linha digitável
    • 48 digits starting with "8" → Convenio linha digitável

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/stlucasgarcia/brasilex.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/my-feature)
  3. Commit your changes (git commit -am 'Add my feature')
  4. Push to the branch (git push origin feature/my-feature)
  5. Create a Pull Request

License

The library is available as open source under the terms of the MIT License.