class Bankscrap::BBVANetCash::Bank

Constants

ACCOUNTS_ENDPOINT
BASE_ENDPOINT
LOGIN_ENDPOINT
TRANSACTIONS_ENDPOINT

Public Class Methods

new(user, password, log: false, debug: false, extra_args: nil) click to toggle source
Calls superclass method
# File lib/bankscrap/bbva-net-cash/bank.rb, line 13
def initialize(user, password, log: false, debug: false, extra_args: nil)
  @company_code = extra_args.with_indifferent_access['company_code']
  @user = format_user(user.dup, @company_code.to_s.dup)
  @password = password.upcase
  @log = log
  @debug = debug

  initialize_connection

  # Create a user_agent with a random string for privacy
  user_agent = SecureRandom.hex(32).upcase + ';Android;LGE;Nexus 5;1080x1776;Android;5.1.1;BMES;4.4;xxhd'

  add_headers(
    'User-Agent'       => user_agent,
    'Accept'           => 'application/json',
    'Accept-Charset'   => 'UTF-8',
    'Connection'       => 'Keep-Alive',
    'Host'             => 'www.bbvanetcash.mobi'
  )

  login
  super
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-net-cash/bank.rb, line 39
def fetch_accounts
  log 'fetch_accounts'

  custom_headers = {
    'Content-Type' => 'application/json; charset=UTF-8',
    'Contexto' => get_context
  }

  params = {
    "peticionCuentasKYOSPaginadas" =>  {
      "favoritos" => false,
      "paginacion" => "0"
    }
  }

  response = with_headers(custom_headers) do
    post(BASE_ENDPOINT + ACCOUNTS_ENDPOINT, fields: params.to_json)
  end

  json = JSON.parse(response)

  if json['respuestacuentas']['cuentas'].is_a? Array
    # TODO: test this with a user with multiple accounts
    json['respuestacuentas']['cuentas'].map { |data| build_account(data) }
  else
    [build_account(json['respuestacuentas']['cuentas'])]
  end
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-net-cash/bank.rb, line 74
def fetch_transactions_for(account, start_date: Date.today - 1.month, end_date: Date.today)
  from_date = start_date.strftime('%Y-%m-%d')

  custom_headers = {
    'Content-Type' => 'application/json; charset=UTF-8',
    'Contexto' => get_context
  }

  params = {
    "peticionMovimientosKYOS" => {
      "numAsunto" => account.iban,
      "bancoAsunto" => "BANCO BILBAO VIZCAYA ARGENTARIA S.A",
      "fechaDesde" => start_date.strftime("%Y%m%d"),
      "fechaHasta" => end_date.strftime("%Y%m%d"),
      "concepto" => [],
      "importe_Desde" => "",
      "importe_Hasta" => "",
      "divisa" => "EUR",
      "paginacionTLSMT017" => "N000000000000+0000000000000000000",
      "paginacionTLSMT016" => "N00000000000+0000000000000000",
      "descargaInformes" => false,
      "numElem" => 0,
      "banco" => "1",
      "idioma" => "51",
      "formatoFecha" => "dd\/MM\/yyyy",
      "paginacionMOVDIA" => "1",
      "ultimaFechaPaginacionAnterior" => "",
      "ordenacion" => "DESC"
    }
  }

  url = BASE_ENDPOINT + TRANSACTIONS_ENDPOINT

  transactions = []
  with_headers(custom_headers) do
    # Loop over pagination
    loop do
      json = JSON.parse(post(url, fields: params.to_json))['respuestamovimientos']

      if json['movimientos'].is_a?(Array)
        unless json['movimientos'].blank?
          transactions += json['movimientos'].map do |data|
            build_transaction(data, account)
          end
          
          params['peticionMovimientosKYOS']['paginacionMOVDIA'] = json['paginacionMOVDIA']
          params['peticionMovimientosKYOS']['paginacionTLSMT016'] = json['paginacionTLSMT016']
          params['peticionMovimientosKYOS']['paginacionTLSMT017'] = json['paginacionTLSMT017']
        end
        break unless (json['descripcion'] == 'More records available')
      elsif json['movimientos'].is_a?(Hash)
        # There was only 1 transaction for this query
        transactions << build_transaction(json['movimientos'], account)
        break
      else
        # No transactions
        break
      end
    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-net-cash/bank.rb, line 161
def build_account(data)
  Account.new(
    bank: self,
    id: data['referencia'],
    name: data['empresaDes'],
    available_balance: data['saldoValor'].to_f,
    balance: data['saldoContable'],
    currency: data['divisa'],
    iban: data['numeroAsunto'],
    description: "#{data['bancoDes']} #{data['numeroAsuntoMostrar']}"
  )
end
build_transaction(data, account) click to toggle source

Build a transaction object from API data

# File lib/bankscrap/bbva-net-cash/bank.rb, line 175
def build_transaction(data, account)
  Transaction.new(
    account: account,
    id: data['codRmsoperS'],
    amount: transaction_amount(data),
    description: data['concepto'] || data['descConceptoTx'],
    effective_date: Date.strptime(data['fechaContable'], '%d/%m/%Y'),
    currency: data['divisa'],
    balance: Money.new(data['saldoContable'].to_f * 100, data['currency'])
  )
end
format_user(user, company_code) click to toggle source

The user that gets sent to the API is a string composed by 3 items: 00230001 <- no idea why this number ¯_(ツ)_/¯ Company code User <- always in upcase

# File lib/bankscrap/bbva-net-cash/bank.rb, line 144
def format_user(user, company_code)
  '00230001' + company_code + user.upcase
end
get_context() click to toggle source

This API has a custom header called ‘Contexto’ that is required for every request after login. Some of the data has been anonymized.

# File lib/bankscrap/bbva-net-cash/bank.rb, line 193
def get_context
  {
    "perfil" => {
      "usuario" => @user,
      "nombre" => "",
      "apellido1" => "",
      "apellido2" => "",
      "dni" => "",
      "cargoFun" => "",
      "centroCoste" => "",
      "matricula" => "",
      "bancoOperativo" => "",
      "oficinaOperativa" => "",
      "bancoFisico" => "",
      "oficinaFisica" => "",
      "paisOficina" => "",
      "idioma" => "1",
      "idiomaIso" => "1",
      "divisaBase" => "ZZZ",
      "divisaSecundaria" => "",
      "xtiOfiFisica" => "",
      "xtiOfiOperati" => "",
      "listaAutorizaciones" => [
        "AAAA",
        "BBBB"
      ]
    },
    "puesto" => {
      "puestoLogico" => "3"
    },
    "transacciones" => {
      "canalLlamante" => "4",
      "medioAcceso" => "7",
      "secuencia" => nil,
      "servicioProducto" => "27",
      "tipoIdentificacionCliente" => "6",
      "identificacionCliente" => "",
      "modoProceso" => nil,
      "autorizacion" => nil,
      "origenFisico" => nil
    },
    "datosTecnicos" => {
      "idPeticion" => nil,
      "UUAARemitente" => nil,
      "usuarioLogico" => "",
      "cabecerasHttp" => {
        "aap" => "00000034",
        "iv-user" => @user
      }
    },
    "codigoCliente" => "1",
    "tipoAutenticacion" => "1",
    "identificacionCliente" => "",
    "tipoIdentificacionCliente" => "6",
    "propiedades" => nil
  }
end
login() click to toggle source
# File lib/bankscrap/bbva-net-cash/bank.rb, line 148
def login
  log 'login'
  params = {
    'origen'         => 'pibeemovil',
    'eai_tipoCP'     => 'up',
    'eai_URLDestino' => 'success_eail_CAS.jsp',
    'eai_user'       => @user,
    'eai_password'   => @password
  }
  post(BASE_ENDPOINT + LOGIN_ENDPOINT, fields: params)
end
transaction_amount(data) click to toggle source
# File lib/bankscrap/bbva-net-cash/bank.rb, line 187
def transaction_amount(data)
  Money.new(data['importe'].to_f * 100, data['divisa'])
end