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
# 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 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 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 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 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
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
# 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
# File lib/bankscrap/bbva/bank.rb, line 178 def transaction_amount(data) Money.new(data['amount'] * 100, data['currency']) end
# File lib/bankscrap/bbva/bank.rb, line 182 def transaction_balance(data) return unless data['accountBalanceAfterMovement'] Money.new(data['accountBalanceAfterMovement'] * 100, data['currency']) end