Overview
The Bankscrap::Bank class is the foundation of all bank adapters. It provides credential management, HTTP communication, and a structured lifecycle for fetching financial data.
All bank adapters inherit from this class and implement its abstract interface methods.
Class Definition
module Bankscrap
class Bank
attr_accessor :headers , :accounts , :cards , :loans , :investments
REQUIRED_CREDENTIALS = %i[user password] . freeze
def initialize ( credentials = {})
# Implementation details...
end
end
end
Attributes
The Bank class exposes these public attributes:
Attribute Type Description headersHashHTTP headers used for API requests accountsArray<Account>All accounts for the authenticated user cardsArray<Card>All cards (if supported by bank) loansArray<Loan>All loans (if supported by bank) investmentsArray<Investment>All investments (if supported by bank)
Constants
WEB_USER_AGENT
Default user agent string that emulates a mobile browser:
WEB_USER_AGENT = 'Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 4 Build/JOP40D) ' \
'AppleWebKit/535.19 (KHTML, like Gecko) ' \
'Chrome/18.0.1025.166 Mobile Safari/535.19' . freeze
Many banks have better support for mobile APIs than web scraping, making mobile user agents more reliable.
REQUIRED_CREDENTIALS
Defines which credentials are required for authentication:
REQUIRED_CREDENTIALS = %i[user password] . freeze
Adapters override this constant to specify their required credentials:
module Bankscrap
module MyBank
class Bank < :: Bankscrap :: Bank
REQUIRED_CREDENTIALS = %i[customer_id pin] . freeze
end
end
end
Initialization Lifecycle
The initialize method orchestrates the entire setup process:
Step 1: Credential Validation
self . class :: REQUIRED_CREDENTIALS . each do | field |
value = credentials. with_indifferent_access [field] ||
ENV [ " #{ env_vars_prefix } _ #{ field. upcase } " ]
raise MissingCredential , "Missing credential: ' #{ field } '" if value. blank?
instance_variable_set ( "@ #{ field } " , value)
end
Behavior :
Checks for credentials in the credentials hash
Falls back to environment variables if not provided
Raises MissingCredential error if any required credential is missing
Sets instance variables for each credential (e.g., @user, @password)
Environment variable format : BANKSCRAP_BANKNAME_FIELDNAME
Example:
export BANKSCRAP_MYBANK_USER = john
export BANKSCRAP_MYBANK_PASSWORD = secret123
Step 2: HTTP Client Initialization
def initialize_http_client
@http = Mechanize . new do | mechanize |
mechanize. user_agent = WEB_USER_AGENT
mechanize. agent . http . verify_mode = OpenSSL :: SSL :: VERIFY_NONE
mechanize. log = Logger . new ( STDOUT ) if Bankscrap . debug
if Bankscrap . proxy
mechanize. set_proxy Bankscrap . proxy [ :host ], Bankscrap . proxy [ :port ]
end
end
@headers = {}
end
Features :
Uses Mechanize for HTTP communication
Disables SSL verification (for banks with certificate issues)
Enables logging in debug mode
Configures proxy if specified
Initializes empty headers hash
Step 3: Adapter Customization
Adapters can provide a block to customize initialization:
module Bankscrap
module MyBank
class Bank < :: Bankscrap :: Bank
def initialize ( credentials = {})
super do
# Custom initialization logic
@device_id = SecureRandom . hex ( 16 )
add_headers ( 'X-Device-ID' => @device_id )
end
end
end
end
end
Step 4: Login
Calls the adapter’s login method to authenticate with the bank’s API.
Step 5: Data Fetching
@accounts = fetch_accounts
# Optional features
@investments = fetch_investments if respond_to? ( :fetch_investments )
@loans = fetch_loans if respond_to? ( :fetch_loans )
@cards = fetch_cards if respond_to? ( :fetch_cards )
Behavior :
Always fetches accounts (required)
Only fetches investments, loans, and cards if the adapter implements those methods
Abstract Interface Methods
These methods must be implemented by all adapters:
login
Establish a session with the bank’s API.
def login
raise " #{ self . class } should implement a login method"
end
Adapter implementation :
def login
response = post ( LOGIN_ENDPOINT , fields: {
user: @user ,
password: @password
}. to_json )
token = JSON . parse (response)[ 'access_token' ]
add_headers ( 'Authorization' => "Bearer #{ token } " )
end
fetch_accounts
Retrieve all accounts for the authenticated user.
def fetch_accounts
raise " #{ self . class } should implement a fetch_account method"
end
Must return : Array<Bankscrap::Account>
Adapter implementation :
def fetch_accounts
response = get ( ACCOUNTS_ENDPOINT )
data = JSON . parse (response)
data[ 'accounts' ]. map do | account_data |
Account . new (
bank: self ,
id: account_data[ 'id' ],
name: account_data[ 'name' ],
balance: Money . new (account_data[ 'balance' ], 'EUR' ),
available_balance: Money . new (account_data[ 'available' ], 'EUR' ),
iban: account_data[ 'iban' ]
)
end
end
fetch_transactions_for
Retrieve transactions for a specific account.
def fetch_transactions_for ( * )
raise " #{ self . class } should implement a fetch_transactions method"
end
Parameters :
account - Bankscrap::Account object
start_date: - Start of date range (keyword argument)
end_date: - End of date range (keyword argument)
Must return : Array<Bankscrap::Transaction>
Adapter implementation :
def fetch_transactions_for ( account , start_date: Date . today - 1 . month , end_date: Date . today )
response = post ( TRANSACTIONS_ENDPOINT , fields: {
account_id: account. id ,
from: start_date. strftime ( '%Y-%m-%d' ),
to: end_date. strftime ( '%Y-%m-%d' )
}. to_json )
data = JSON . parse (response)
data[ 'transactions' ]. map do | tx |
Transaction . new (
account: account,
id: tx[ 'id' ],
amount: Money . new (tx[ 'amount' ], tx[ 'currency' ]),
description: tx[ 'description' ],
effective_date: Date . parse (tx[ 'date' ])
)
end
end
HTTP Helper Methods
The Bank class provides convenient HTTP methods for adapter implementations:
get(url, params:, referer:)
Perform an HTTP GET request.
def get ( url , params: [], referer: nil )
@http . get (url, params, referer, @headers ). body
end
Parameters :
url - Full URL to request
params - Array of [key, value] pairs for query string (optional)
referer - Referer header value (optional)
Returns : Response body as string
Example :
# Simple GET
response = get ( 'https://api.bank.com/accounts' )
# With query parameters
response = get ( 'https://api.bank.com/search' , params: [
[ 'query' , 'transactions' ],
[ 'limit' , '50' ]
])
# With referer
response = get ( 'https://api.bank.com/details' , referer: 'https://bank.com/home' )
post(url, fields:)
Perform an HTTP POST request.
def post ( url , fields: {})
@http . post (url, fields, @headers ). body
end
Parameters :
url - Full URL to request
fields - Hash or string for request body
Returns : Response body as string
Example :
# JSON POST
response = post ( 'https://api.bank.com/auth' , fields: {
username: @user ,
password: @password
}. to_json )
# Form POST
response = post ( 'https://api.bank.com/submit' , fields: {
field1: 'value1' ,
field2: 'value2'
})
put(url, fields:)
Perform an HTTP PUT request.
def put ( url , fields: {})
@http . put (url, fields, @headers ). body
end
Parameters : Same as post
Example :
response = put ( 'https://api.bank.com/settings' , fields: {
notifications: true
}. to_json )
Add or update HTTP headers for all subsequent requests.
def add_headers ( headers )
@headers . merge! headers
@http . request_headers = @headers
end
Example :
add_headers (
'Authorization' => 'Bearer token123' ,
'X-API-Version' => '2.0'
)
Temporarily use different headers for a code block.
def with_headers ( tmp_headers )
current_headers = @headers
add_headers (tmp_headers)
yield
ensure
add_headers (current_headers)
end
Example :
with_headers ( 'X-Request-ID' => SecureRandom . uuid ) do
response = get ( 'https://api.bank.com/special-endpoint' )
end
# Original headers are restored here
Utility Methods
account_with_iban(iban)
Find an account by its IBAN.
def account_with_iban ( iban )
accounts. find do | account |
account. iban . delete ( ' ' ) == iban. delete ( ' ' )
end
end
Example :
account = bank. account_with_iban ( 'ES12 3456 7890 1234 5678 9012' )
puts account. balance
initialize_cookie(url)
Initialize cookies by visiting a URL (useful for session setup).
def initialize_cookie ( url )
@http . get (url). body
end
Example :
def login
# Visit home page to get session cookie
initialize_cookie ( 'https://www.bank.com' )
# Now perform login
post ( LOGIN_ENDPOINT , fields: credentials)
end
log(msg)
Log a message if logging is enabled.
def log ( msg )
puts msg if Bankscrap . log
end
Example :
log "Fetching accounts for user #{ @user } "
Enable logging :
Exception Classes
MissingCredential
Raised when a required credential is not provided.
class MissingCredential < ArgumentError ; end
Example :
begin
bank = Bankscrap :: MyBank :: Bank . new ( user: 'john' )
rescue Bankscrap :: Bank :: MissingCredential => e
puts "Error: #{ e. message } "
# => "Error: Missing credential: 'password'"
end
Usage Examples
Basic Usage
bank = Bankscrap :: MyBank :: Bank . new (
user: 'john.doe' ,
password: 'secret123'
)
# Access accounts
bank. accounts . each do | account |
puts " #{ account. name } : #{ account. balance . format } "
end
# Access other products (if available)
bank. cards . each { | card | puts card. name }
bank. loans . each { | loan | puts loan. description }
bank. investments . each { | inv | puts inv. name }
Using Environment Variables
export BANKSCRAP_MYBANK_USER = john . doe
export BANKSCRAP_MYBANK_PASSWORD = secret123
# Credentials loaded automatically from environment
bank = Bankscrap :: MyBank :: Bank . new
Finding Specific Accounts
bank = Bankscrap :: MyBank :: Bank . new (credentials)
# By IBAN
checking = bank. account_with_iban ( 'ES12 3456 7890 1234 5678 9012' )
# By name
savings = bank. accounts . find { | a | a. name . include? ( 'Savings' ) }
# By balance
richest = bank. accounts . max_by ( & :balance )
Best Practices
Always Use Money Never use floats for amounts. Always create Money objects to ensure precision.
Validate Responses Check that API responses are valid before parsing to avoid cryptic errors.
Store Raw Data Keep original API responses in the raw_data field for debugging.
Handle Errors Gracefully Provide clear error messages when authentication or API calls fail.
Next Steps
Adapter Pattern Learn how to implement bank adapters
Data Models Explore Account, Transaction, and other models
Creating Adapters Step-by-step guide to building adapters
Architecture Understanding the overall system design