schurig/ynab-bank-importer

View on GitHub
lib/dumper/fints.rb

Summary

Maintainability
A
0 mins
Test Coverage
class Dumper
  # Implements logic to fetch transactions via the Fints protocol
  # and implements methods that convert the response to meaningful data.
  class Fints < Dumper
    require 'ruby_fints'
    require 'digest/md5'

    def initialize(params = {})
      @ynab_id  = params.fetch('ynab_id')
      @username = params.fetch('username').to_s
      @password = params.fetch('password').to_s
      @iban     = params.fetch('iban')
      @endpoint = params.fetch('fints_endpoint')
      @blz      = params.fetch('fints_blz')
    end

    def fetch_transactions
      FinTS::Client.logger.level = Logger::WARN
      client = FinTS::PinTanClient.new(@blz, @username, @password, @endpoint)

      account = client.get_sepa_accounts.find { |a| a[:iban] == @iban }
      statement = client.get_statement(account, Date.today - 35, Date.today)

      statement.map { |t| to_ynab_transaction(t) }
    end

    private

    def account_id
      @ynab_id
    end

    def date(transaction)
      transaction.entry_date || transaction.date
    rescue NoMethodError
      # https://github.com/schurig/ynab-bank-importer/issues/52
      # Some banks think Feb 29 and 30 exist in non-leap years.
      entry_date(transaction) || to_date(transaction['date'])
    end

    def payee_name(transaction)
      transaction.name.try(:strip)
    end

    def payee_iban(transaction)
      transaction.iban
    end

    def memo(transaction)
      [
        transaction.description,
        transaction.information
      ].compact.join(' / ').try(:strip)
    end

    def amount(transaction)
      (transaction.amount * transaction.sign * 1000).to_i
    end

    def withdrawal?(transaction)
      memo = memo(transaction)
      return nil unless memo

      memo.include?('Atm') || memo.include?('Bargeld')
    end

    def import_id(transaction)
      Digest::MD5.hexdigest(transaction.source)
    end

    # Patches

    # taken from https://github.com/railslove/cmxl/blob/master/lib/cmxl/field.rb
    # and modified so that it takes the last day of the month if the provided day
    # doesn't exist in that month.
    # See issue: https://github.com/schurig/ynab-bank-importer/issues/52
    DATE = /(?<year>\d{0,2})(?<month>\d{2})(?<day>\d{2})/
    def to_date(date, year = nil)
      if match = date.to_s.match(DATE)
        year ||= "20#{match['year'] || Date.today.strftime('%y')}"
        month = match['month']
        day = match['day']

        begin
          Date.new(year.to_i, month.to_i, day.to_i)
        rescue ArgumentError
          # Take the last day of that month
          Date.civil(year.to_i, month.to_i, -1)
        end
      else
        date
      end
    end

    def entry_date(transaction)
      data = transaction.data
      date = to_date(data['date'])

      return unless transaction.data['entry_date'] && date

      entry_date_with_date_year = to_date(data['entry_date'], date.year)
      if date.month == 1 && date.month < entry_date_with_date_year.month
        to_date(data['entry_date'], date.year - 1)
      else
        to_date(data['entry_date'], date.year)
      end
    end
  end
end