class Bankscrap::BBVA::Bank

Constants

ACCOUNT_ENDPOINT
BASE_ENDPOINT
CONSUMER_ID

This is probably some sort of identifier of Android vs iOS consumer app

LOGIN_ENDPOINT
PRODUCTS_ENDPOINT
REQUIRED_CREDENTIALS
SESSIONS_ENDPOINT
USER_AGENT

BBVA expects an identifier before the actual User Agent, but 12345 works fine

Public Class Methods

new(credentials = {}) click to toggle source
Calls superclass method
# File lib/bankscrap/bbva/bank.rb, line 19
def initialize(credentials = {})
  super do
    @user = format_user(@user.dup)

    add_headers(
      'User-Agent'       => USER_AGENT,
      'BBVA-User-Agent'  => USER_AGENT,
      'Accept-Language'  => 'spa',
      'Content-Language' => 'spa',
      'Accept'           => 'application/json',
      'Accept-Charset'   => 'UTF-8',
      'Connection'       => 'Keep-Alive',
      'Host'             => 'servicios.bbva.es',
      'ConsumerID'       => CONSUMER_ID
    )
  end
end

Public Instance Methods

fetch_accounts() click to toggle source

Fetch all the accounts for the given user Returns an array of Bankscrap::Account objects

# File lib/bankscrap/bbva/bank.rb, line 39
def fetch_accounts
  log 'fetch_accounts'

  # Even if the required method is an HTTP POST
  # the API requires a funny header that says is a GET
  # otherwise the request doesn't work.
  response = with_headers('BBVA-Method' => 'GET') do
    post(BASE_ENDPOINT + PRODUCTS_ENDPOINT)
  end

  json = JSON.parse(response)
  json['accounts'].map { |data| build_account(data) }
end
fetch_transactions_for(account, start_date: Date.today - 1.month, end_date: Date.today) click to toggle source

Fetch transactions for the given account. By default it fetches transactions for the last month, The maximum allowed by the BBVA API is the last 3 years.

Account should be a Bankscrap::Account object Returns an array of Bankscrap::Transaction objects

# File lib/bankscrap/bbva/bank.rb, line 59
def fetch_transactions_for(account, start_date: Date.today - 1.month, end_date: Date.today)
  from_date = start_date.strftime('%Y-%m-%d')

  # Misteriously we need a specific content-type here
  funny_headers = {
    'Content-Type' => 'application/json; charset=UTF-8',
    'BBVA-Method' => 'GET'
  }

  # The API accepts a toDate param that we could pass the end_date argument,
  # however when we pass the toDate param, the API stops returning the account balance.
  # Therefore we need to take a workaround: only filter with fromDate and loop
  # over all the available pages, filtering out the movements that doesn't match
  # the end_date argument.
  url = BASE_ENDPOINT +
        ACCOUNT_ENDPOINT +
        account.id +
        "/movements/v1?fromDate=#{from_date}"

  offset = nil
  pagination_balance = nil
  transactions = []

  with_headers(funny_headers) do
    # Loop over pagination
    loop do
      new_url = offset ? (url + "&offset=#{offset}") : url
      new_url = pagination_balance ? (new_url + "&paginationBalance=#{pagination_balance}") : new_url
      json = JSON.parse(post(new_url))

      unless json['movements'].blank?
        # As explained before, we have to discard records newer than end_date.
        filtered_movements = json['movements'].select { |m| Date.parse(m['operationDate']) <= end_date }

        transactions += filtered_movements.map do |data|
          build_transaction(data, account)
        end
        offset = json['offset']
        pagination_balance = json['paginationBalance']
      end

      break unless json['thereAreMoreMovements'] == true
    end
  end

  transactions
end

Private Instance Methods

build_account(data) click to toggle source

Build an Account object from API data

# File lib/bankscrap/bbva/bank.rb, line 154
def build_account(data)
  Account.new(
    bank: self,
    id: data['id'],
    name: data['name'],
    available_balance: Money.new(data['availableBalance'].to_f * 100, data['currency']),
    balance: Money.new(data['actualBalance'].to_f * 100, data['currency']),
    iban: data['iban'],
    description: "#{data['typeDescription']} #{data['familyCode']}"
  )
end
build_transaction(data, account) click to toggle source

Build a transaction object from API data

# File lib/bankscrap/bbva/bank.rb, line 167
def build_transaction(data, account)
  Transaction.new(
    account: account,
    id: data['id'],
    amount: transaction_amount(data),
    description: data['conceptDescription'] || data['description'],
    effective_date: Date.strptime(data['operationDate'], '%Y-%m-%d'),
    balance: transaction_balance(data)
  )
end
format_user(user) click to toggle source

As far as we know there are three types of identifiers BBVA uses 1) A number of 7 characters that gets passed to the API as it is 2) A DNI number, this needs to be transformed before it gets passed to the API

Example: "49021740T" will become "0019-049021740T"

3) A NIE number, this needs to be transformed before it gets passed to the API

Example: "X1234567T" will become "0019-X1234567T"
# File lib/bankscrap/bbva/bank.rb, line 115
def format_user(user)
  user.upcase!

  if user =~ /^[0-9]{8}[A-Z]$/
    # It's a DNI
    "0019-0#{user}"
  elsif user =~ /^[X-Y]([0-9]{6}|[0-9]{7})[A-Z]$/
    # It's a NIE
    "0019-#{user}"
  else
    user
  end
end
login() click to toggle source
# File lib/bankscrap/bbva/bank.rb, line 129
def login
  log 'login'
  params = {
    'origen'         => 'enpp',
    'eai_tipoCP'     => 'up',
    'eai_user'       => @user,
    'eai_password'   => @password
  }
  post(BASE_ENDPOINT + LOGIN_ENDPOINT, fields: params)

  # We also need to initialize a session
  with_headers('Content-Type' => 'application/json') do
    post(SESSIONS_ENDPOINT, fields: {
      consumerID: CONSUMER_ID
    }.to_json)
  end

  # We need to extract the "tsec" header from the last response.
  # As the Bankscrap core library doesn't expose the headers of each response
  # we have to use Mechanize's HTTP client "current_page" method.
  tsec = @http.current_page.response['tsec']
  add_headers('tsec' => tsec)
end
transaction_amount(data) click to toggle source
# File lib/bankscrap/bbva/bank.rb, line 178
def transaction_amount(data)
  Money.new(data['amount'] * 100, data['currency'])
end
transaction_balance(data) click to toggle source
# File lib/bankscrap/bbva/bank.rb, line 182
def transaction_balance(data)
  return unless data['accountBalanceAfterMovement']
  Money.new(data['accountBalanceAfterMovement'] * 100, data['currency'])
end