OFX Kit

GitHub Repo Gem Version Gem Total Downloads

A Ruby gem for parsing OFX (Open Financial Exchange) files. Supports OFX 1.x (SGML) and OFX 2.x (XML), bank statements and credit card statements, with a fluent API and configurable field mappings.

Installation

Add to your Gemfile:

gem 'ofx_kit', '~> 1.0', '>= 1.0.2'

Usage

Basic parsing

# Parse from a file path
ofx = OFX.new("statement.ofx")

# Parse from an IO object
ofx = OFX.new(File.open("statement.ofx"))
ofx = OFX.new(StringIO.new(raw_content))

# Block form — yields the parser
OFX.new("statement.ofx") do |p|
  puts p..
end

Accessing data

ofx.filename      # => "statement.ofx" (nil for IO inputs without a path)
ofx.headers       # => { "VERSION" => "102", "ENCODING" => "USASCII", ... }

# Single-statement files
ofx.       # => OFX::BankAccount or OFX::CreditCardAccount
ofx.transactions  # => Array of OFX::Transaction
ofx.balance       # => OFX::Balance

# Multiple-statement files — use the plural forms
ofx.accounts      # => [OFX::BankAccount, ...]
ofx.statements    # => [OFX::BankStatement, ...]
ofx.balances      # => [OFX::Balance, ...]

Transactions

t = ofx.transactions.first

t.fit_id        # => "20240115001"       String
t.type          # => "DEBIT"             String
t.memo          # => "Pagamento boleto"  String
t.posted_at     # => Time object
t.amount        # => Money object (positive = credit, negative = debit)
t.amount_cents  # => Integer (same as t.amount.fractional)

t.amount.currency.iso_code  # => "BRL"
t.amount.to_d               # => BigDecimal("-150.50")

Credits, debits, and scopes

stmt.transactions and account.transactions both return an OFX::TransactionCollection with .credits, .debits, length, and the full Enumerable API:

stmt = ofx.statements.first
txns = stmt.transactions           # => OFX::TransactionCollection

txns.length                        # => 2
txns.credits                       # => TransactionCollection of positive amounts
txns.debits                        # => TransactionCollection of negative amounts
txns.total_credits                 # => Money (sum of positive transactions)
txns.total_debits                  # => Money (sum of negative transactions)
txns.net                           # => Money (total_credits + total_debits)
txns.map(&:memo)                   # => ["Pagamento boleto", "Deposito salario"]
txns.sort_by(&:posted_at)          # => Array, sorted by date

Balance

bal = ofx.balance
bal.amount        # => Money object
bal.amount_cents  # => Integer
bal.posted_at     # => Time object

Summary

ofx.summary
# => {
#   headers: { "VERSION" => "102", ... },
#   statements: {
#     "12345-6" => {
#       currency:      "BRL",
#       transactions:  { count: 2, net_cents: 284_950 },
#       credits:       { count: 1, total_cents: 300_000 },
#       debits:        { count: 1, total_cents: -15_050 },
#       balance_cents: 500_000
#     }
#   }
# }

Error handling

OFX.new("missing.ofx")      # => Errno::ENOENT
OFX.new("bad_header.ofx")   # => OFX::InvalidHeaderError
OFX.new("bad_xml.ofx")      # => OFX::InvalidBodyError
OFX.new(42)                 # => ArgumentError

# Calling #account or #balance on a multi-statement file:
ofx.   # => OFX::MultipleStatementsError (use `accounts`)
ofx.balance   # => OFX::MultipleStatementsError (use `balances`)

Configuration

Rails setup

Generate a pre-filled initializer with all options commented out:

rails generate ofx_kit:install

This creates config/initializers/ofx_kit.rb. Uncomment and adjust whatever you need — everything else stays at its default.

Field mappings

Use map inside an OFX.configure block to add custom attributes or rename built-in ones:

OFX.configure do |config|
  # Add a bank-specific tag the gem doesn't know about by default
  config..map "AGENCIA",      to: "branch_code"
  config.transaction.map  "HISPAYEEMEMO", to: "extended_memo"

  # Rename a built-in field to match your domain
  config.transaction.map "FITID", to: "uid"        # default is fit_id
  config.transaction.map "NAME",  to: "payee_name"
end

ofx = OFX.new("statement.ofx")
ofx..branch_code               # => "0272"
ofx.transactions.first.extended_memo  # => "Tarifa bancaria"
ofx.transactions.first.uid            # => "20240115001"
# ofx.transactions.first.fit_id       # => nil (FITID is now mapped to uid)

Protected core fieldsCURDEF, TRNAMT, DTPOSTED, DTUSER, BALAMT, DTASOF are used internally to build Money objects and parse dates. They cannot be remapped; attempting to do so raises OFX::ConfigurationError.

Silencing warnings

transactions and balances emit a warning when aggregating across multiple statements in the same file. To silence them:

OFX.configure do |config|
  config.multi_statement_warnings = false
end

This option is already included (commented out) in the initializer generated by ofx_kit:install.

Contributing

  1. Fork the repository and create a feature branch.
  2. Install dependencies:
   bundle install
  1. Make your changes. Add or update specs to cover them.
  2. Run the test suite and linter before opening a pull request:
   bundle exec rspec
   bundle exec rubocop

All tests must pass and RuboCop must report no offenses.

Generating documentation locally

bundle exec rake rdoc

Testing locally via console

You can exercise the gem interactively using irb from the project root. The spec/fixtures/ directory contains sample OFX files ready to use.

bundle exec irb -r ./lib/ofx_kit
# Parse a bank statement (OFX 1.x)
ofx = OFX.new("spec/fixtures/bank_simple.ofx")
ofx..   # => "12345-6"
ofx.transactions.length  # => 2
ofx.balance.amount       # => Money object

# Parse an OFX 2.x file
ofx = OFX.new("spec/fixtures/bank_ofx2.ofx")
ofx.headers              # => { "VERSION" => "220", ... }

# Parse a credit card statement
ofx = OFX.new("spec/fixtures/credit_card.ofx")
ofx.              # => OFX::CreditCardAccount
ofx.transactions.first.amount.to_d  # => BigDecimal

# Multiple statements
ofx = OFX.new("spec/fixtures/bank_multiple.ofx")
ofx.accounts.length      # => 2
ofx.statements.map { |s| s.. }

# Try custom field mappings
OFX.configure do |config|
  config.transaction.map "HISPAYEEMEMO", to: "extended_memo"
end
ofx = OFX.new("spec/fixtures/bank_simple.ofx")
ofx.transactions.first.extended_memo
OFX.reset_config!  # restore defaults between tests

License

MIT