banker 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.rvmrc +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/Guardfile +10 -0
- data/LICENSE +23 -0
- data/README.md +79 -0
- data/Rakefile +2 -0
- data/banker.gemspec +25 -0
- data/lib/banker.rb +14 -0
- data/lib/banker/account.rb +16 -0
- data/lib/banker/barclaycard_uk.rb +96 -0
- data/lib/banker/barclays.rb +89 -0
- data/lib/banker/base.rb +28 -0
- data/lib/banker/capital_one_uk.rb +88 -0
- data/lib/banker/credit_expert_uk.rb +112 -0
- data/lib/banker/error.rb +5 -0
- data/lib/banker/lloyds_tsb_uk.rb +130 -0
- data/lib/banker/version.rb +3 -0
- data/spec/banker/account_spec.rb +32 -0
- data/spec/banker/barclaycard_uk_spec.rb +167 -0
- data/spec/banker/barclays_spec.rb +225 -0
- data/spec/banker/base_spec.rb +35 -0
- data/spec/banker/capital_one_uk_spec.rb +134 -0
- data/spec/banker/credit_expert_uk_spec.rb +78 -0
- data/spec/banker/lloyds_tsb_uk_spec.rb +53 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/barclaycard_uk/data.ofx +180 -0
- data/spec/support/barclays/data.ofx +86 -0
- data/spec/support/credit_expert_uk/MCC.aspx.html +1914 -0
- data/spec/support/credit_expert_uk/MCCLogin.aspx.html +458 -0
- data/spec/support/credit_expert_uk/MCCLoginMemorableWord.aspx.html +360 -0
- data/spec/support/lloyds_tsb_uk/account_overview_personal.html +328 -0
- data/spec/support/lloyds_tsb_uk/entermemorableinformation.jsp.html +148 -0
- data/spec/support/lloyds_tsb_uk/interstitialpage.jsp.html +155 -0
- data/spec/support/lloyds_tsb_uk/login.jsp.html +281 -0
- metadata +167 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
module Banker
|
2
|
+
# This class allows the retrieval of credit score data
|
3
|
+
# for Credit Expert UK
|
4
|
+
#
|
5
|
+
# == Examples
|
6
|
+
#
|
7
|
+
# Get Score from Credit Expert UK
|
8
|
+
#
|
9
|
+
# site = Banker::CreditExpertUK.new(:username => "joe",
|
10
|
+
# :password => '123456',
|
11
|
+
# :memorable_word => 'superduper')
|
12
|
+
#
|
13
|
+
# site.score #=> 800
|
14
|
+
#
|
15
|
+
class CreditExpertUK
|
16
|
+
attr_accessor :username, :password, :memorable_word, :agent, :score
|
17
|
+
|
18
|
+
LOGIN_ENDPOINT = "https://www.creditexpert.co.uk/MCCLogin.aspx"
|
19
|
+
|
20
|
+
FIELDS = {
|
21
|
+
username: 'loginUser:txtUsername:ECDTextBox',
|
22
|
+
password: 'loginUser:txtPassword:ECDTextBox',
|
23
|
+
questions: [
|
24
|
+
'label span#loginUserMemorableWord_SecurityQuestionLetter1',
|
25
|
+
'label span#loginUserMemorableWord_SecurityQuestionLetter2'
|
26
|
+
],
|
27
|
+
answers: [
|
28
|
+
'loginUserMemorableWord:SecurityQuestionUK1_SecurityAnswer1_ECDTextBox',
|
29
|
+
'loginUserMemorableWord:SecurityQuestionUK1_SecurityAnswer2_ECDTextBox'
|
30
|
+
],
|
31
|
+
score: 'span#MCC_ScoreIntelligence_ScoreIntelligence_Dial1_MyScoreV31_pnlMyScore1_lblMyScore'
|
32
|
+
}
|
33
|
+
|
34
|
+
def initialize(args)
|
35
|
+
@username = args[:username]
|
36
|
+
@password = args[:password]
|
37
|
+
@memorable_word = args[:memorable_word]
|
38
|
+
|
39
|
+
@agent = Mechanize.new
|
40
|
+
@agent.log = Logger.new 'mech.log'
|
41
|
+
@agent.user_agent = 'Mozilla/5.0 (Banker)'
|
42
|
+
|
43
|
+
authenticate
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def authenticate
|
49
|
+
@score = data
|
50
|
+
=begin
|
51
|
+
page = @agent.get(LOGIN_ENDPOINT)
|
52
|
+
|
53
|
+
form = page.form_with(:action => 'MCCLogin.aspx')
|
54
|
+
|
55
|
+
form[FIELDS[:username]] = @username
|
56
|
+
form[FIELDS[:password]] = @password
|
57
|
+
|
58
|
+
|
59
|
+
page = @agent.submit(form, form.buttons.first)
|
60
|
+
|
61
|
+
form = page.form_with(:name => 'MasterPage')
|
62
|
+
|
63
|
+
first_letter = page.at(FIELDS[:questions][0]).content.scan(/\d+/).first.to_i
|
64
|
+
second_letter = page.at(FIELDS[:questions][1]).content.scan(/\d+/).first.to_i
|
65
|
+
|
66
|
+
form[FIELDS[:answers][0]] = get_letter(first_letter)
|
67
|
+
|
68
|
+
form[FIELDS[:answers][1]] = get_letter(second_letter)
|
69
|
+
|
70
|
+
page = @agent.submit(form, form.buttons.first)
|
71
|
+
|
72
|
+
@score = page.at(FIELDS[:score]).content.to_i
|
73
|
+
=end
|
74
|
+
end
|
75
|
+
|
76
|
+
def start
|
77
|
+
page = @agent.get(LOGIN_ENDPOINT)
|
78
|
+
|
79
|
+
form = page.form_with(:action => 'MCCLogin.aspx')
|
80
|
+
|
81
|
+
form[FIELDS[:username]] = @username
|
82
|
+
form[FIELDS[:password]] = @password
|
83
|
+
|
84
|
+
@agent.submit(form, form.buttons.first)
|
85
|
+
end
|
86
|
+
|
87
|
+
def step1
|
88
|
+
page = start
|
89
|
+
form = page.form_with(name: 'MasterPage')
|
90
|
+
|
91
|
+
first_letter = extract_digit(page.at(FIELDS[:questions][0]).content)
|
92
|
+
second_letter = extract_digit(page.at(FIELDS[:questions][1]).content)
|
93
|
+
|
94
|
+
form[FIELDS[:answers][0]] = get_letter(first_letter)
|
95
|
+
form[FIELDS[:answers][1]] = get_letter(second_letter)
|
96
|
+
|
97
|
+
@agent.submit(form, form.buttons.first)
|
98
|
+
end
|
99
|
+
|
100
|
+
def data
|
101
|
+
step1.at(FIELDS[:score]).content.to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
def extract_digit(content)
|
105
|
+
content.scan(/\d+/).first.to_i
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_letter(index)
|
109
|
+
@memorable_word[index-1]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/banker/error.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
module Banker
|
2
|
+
# This class allows the data retrieval of accounts for Lloyds TSB UK
|
3
|
+
#
|
4
|
+
# == Examples
|
5
|
+
#
|
6
|
+
# Retrieve Account Balance
|
7
|
+
#
|
8
|
+
# user_params = { username: 'Joe', password: 'password', memorable_word: 'superduper' }
|
9
|
+
# lloyds = Banker::LloydsTSBUK.new(user_params)
|
10
|
+
# lloyds.balance
|
11
|
+
# # => [ {:current_account => { :balance => 160940,
|
12
|
+
# :details => { :sort_code => "928277",
|
13
|
+
# :account_number => "92837592" }}},
|
14
|
+
#
|
15
|
+
# {:savings_account => { :balance => 0.0,
|
16
|
+
# :details => { :sort_code => "918260",
|
17
|
+
# :account_number=>"91850261" }}},
|
18
|
+
#
|
19
|
+
# {:lloyds_tsb_platinum_mastercard => { :balance => 0.0,
|
20
|
+
# :details => { :card_number => "9284710274618391" }}}
|
21
|
+
# ]
|
22
|
+
#
|
23
|
+
class LloydsTSBUK
|
24
|
+
attr_accessor :username, :password, :memorable_word, :balance, :agent
|
25
|
+
|
26
|
+
LOGIN_ENDPOINT = "https://online.lloydstsb.co.uk/personal/logon/login.jsp"
|
27
|
+
|
28
|
+
def initialize(args)
|
29
|
+
@username = args[:username]
|
30
|
+
@password = args[:password]
|
31
|
+
@memorable_word = args[:memorable_word]
|
32
|
+
|
33
|
+
@agent = Mechanize.new
|
34
|
+
@agent.log = Logger.new 'mech.log'
|
35
|
+
@agent.user_agent = 'Mozilla/5.0 (Banker)'
|
36
|
+
@agent.force_default_encoding = 'utf8'
|
37
|
+
|
38
|
+
@balance = authenticate
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_memorable_word_letter(letter)
|
42
|
+
@memorable_word.to_s[letter.to_i - 1]
|
43
|
+
end
|
44
|
+
|
45
|
+
def cleaner(str)
|
46
|
+
str.gsub(/[^\d+]/, '')
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def authenticate
|
52
|
+
page = @agent.get(LOGIN_ENDPOINT)
|
53
|
+
|
54
|
+
# Login Page
|
55
|
+
page = page.form('frmLogin') do |f|
|
56
|
+
f.fields[0].value = @username
|
57
|
+
f.fields[1].value = @password
|
58
|
+
end.click_button
|
59
|
+
|
60
|
+
# Memorable Word
|
61
|
+
form = page.form('frmentermemorableinformation1')
|
62
|
+
memorable_set(page, form)
|
63
|
+
page = @agent.submit(form, form.buttons.first)
|
64
|
+
|
65
|
+
# Accounts Page
|
66
|
+
return account_return(page)
|
67
|
+
end
|
68
|
+
|
69
|
+
def memorable_letters(page)
|
70
|
+
{
|
71
|
+
first: memorable_required(page)[0],
|
72
|
+
second: memorable_required(page)[1],
|
73
|
+
third: memorable_required(page)[2]
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def memorable_set(page, form)
|
78
|
+
letters = memorable_letters(page)
|
79
|
+
|
80
|
+
form.fields[2].value = ' ' + get_memorable_word_letter(letters.fetch(:first))
|
81
|
+
form.fields[3].value = ' ' + get_memorable_word_letter(letters.fetch(:second))
|
82
|
+
form.fields[4].value = ' ' + get_memorable_word_letter(letters.fetch(:third))
|
83
|
+
end
|
84
|
+
|
85
|
+
def memorable_required(page)
|
86
|
+
page.labels.collect { |char| cleaner(char.to_s) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def account_name(page)
|
90
|
+
page.search('div.accountDetails h2').collect {|a| a.content.downcase.gsub(/\s/, '_').to_sym }
|
91
|
+
end
|
92
|
+
|
93
|
+
def account_detail(page)
|
94
|
+
page.search('div.accountDetails p.numbers').collect {|n| n.content }
|
95
|
+
end
|
96
|
+
|
97
|
+
def account_balance(page)
|
98
|
+
page.search('div.balanceActionsWrapper p.balance').collect {|b| b.content }
|
99
|
+
end
|
100
|
+
|
101
|
+
def formatted_detail(page)
|
102
|
+
resp = account_detail(page).map { |detail| detail.split(',').map { |d| cleaner(d) } }
|
103
|
+
resp.map! do |r|
|
104
|
+
if r.length == 2
|
105
|
+
{ sort_code: r[0], account_number: r[1] }
|
106
|
+
elsif r.length == 1
|
107
|
+
{ card_number: r[0] }
|
108
|
+
else
|
109
|
+
STDERR.puts "[Error] It seems we got account details that we did not expect! - #{r.inspect}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def formatted_balance(page)
|
115
|
+
account_balance(page).map do |b|
|
116
|
+
resp = cleaner(b)
|
117
|
+
resp.empty? ? 0.00 : resp.to_i
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def account_return(page)
|
122
|
+
resp = []
|
123
|
+
account_name(page).zip(formatted_balance(page), formatted_detail(page)).each_with_index do |acc, index|
|
124
|
+
resp << { acc[0] => { balance: acc[1], details: acc[2] } }
|
125
|
+
end
|
126
|
+
resp
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Banker::Account do
|
4
|
+
let(:attr){{
|
5
|
+
name:'',
|
6
|
+
uid:'',
|
7
|
+
amount:'',
|
8
|
+
limit:''
|
9
|
+
}}
|
10
|
+
subject {Banker::Account}
|
11
|
+
|
12
|
+
it {subject.new(attr).should respond_to(:name)}
|
13
|
+
it {subject.new(attr).should respond_to(:uid)}
|
14
|
+
it {subject.new(attr).should respond_to(:amount)}
|
15
|
+
it {subject.new(attr).should respond_to(:limit)}
|
16
|
+
|
17
|
+
it "validates presence of name" do
|
18
|
+
expect{
|
19
|
+
subject.new
|
20
|
+
}.to raise_error(ArgumentError, "missing attribute `name`")
|
21
|
+
end
|
22
|
+
it "validates presence of uid" do
|
23
|
+
expect{
|
24
|
+
subject.new(name:'')
|
25
|
+
}.to raise_error(ArgumentError, "missing attribute `uid`")
|
26
|
+
end
|
27
|
+
it "validates presence of amount" do
|
28
|
+
expect{
|
29
|
+
subject.new(name:'',uid:'')
|
30
|
+
}.to raise_error(ArgumentError, "missing attribute `amount`")
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Banker::BarclaycardUK do
|
4
|
+
let(:support_files) {File.expand_path('../../support/barclaycard_uk/',__FILE__)}
|
5
|
+
|
6
|
+
LOGIN_ENDPOINT = 'https://bcol.barclaycard.co.uk/ecom/as2/initialLogon.do'
|
7
|
+
EXPORT_ENDPOINT = 'https://bcol.barclaycard.co.uk/ecom/as2/export.do?doAction=processRecentExportTransaction&type=OFX_2_0_2&statementDate=&sortBy=transactionDate&sortType=Dsc'
|
8
|
+
|
9
|
+
let(:mechanize) {mock('mechanize').as_null_object}
|
10
|
+
let(:node) {mock('node').as_null_object}
|
11
|
+
let(:form) {mock('form').as_null_object}
|
12
|
+
let(:button) {mock('button').as_null_object}
|
13
|
+
let(:ofx) {
|
14
|
+
f = File.open(File.expand_path('data.ofx',support_files), 'r:iso-8859-1')
|
15
|
+
f.read
|
16
|
+
}
|
17
|
+
|
18
|
+
subject { Banker::BarclaycardUK }
|
19
|
+
|
20
|
+
before do
|
21
|
+
subject.any_instance.stub(:params)
|
22
|
+
subject.any_instance.stub(:authenticate!)
|
23
|
+
subject.any_instance.stub(:get_data)
|
24
|
+
end
|
25
|
+
|
26
|
+
it {subject.new.should respond_to(:accounts)}
|
27
|
+
|
28
|
+
context "Parameters" do
|
29
|
+
before do
|
30
|
+
subject.any_instance.unstub(:params)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "raises InvalidParams when username is missing" do
|
34
|
+
expect{
|
35
|
+
subject.new
|
36
|
+
}.to raise_error(Banker::Error::InvalidParams,
|
37
|
+
"missing parameters `username` `password` `memorable_word` ")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises InvalidParams when password is missing" do
|
41
|
+
expect{
|
42
|
+
subject.new(username: "joe")
|
43
|
+
}.to raise_error(Banker::Error::InvalidParams,
|
44
|
+
"missing parameters `password` `memorable_word` ")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "raises InvalidParams when memorable_word is missing" do
|
48
|
+
expect{
|
49
|
+
subject.new(username: "joe", password: "123456")
|
50
|
+
}.to raise_error(Banker::Error::InvalidParams,
|
51
|
+
"missing parameters `memorable_word` ")
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
it "accepts username, password and memorable_word" do
|
56
|
+
expect{
|
57
|
+
|
58
|
+
subject.new(username: "joe", password: "123456",
|
59
|
+
memorable_word: "superduper")
|
60
|
+
}.to_not raise_error
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "calls" do
|
65
|
+
it "calls params" do
|
66
|
+
subject.any_instance.should_receive(:params)
|
67
|
+
subject.new
|
68
|
+
end
|
69
|
+
it "calls authenticate!" do
|
70
|
+
subject.any_instance.should_receive(:authenticate!)
|
71
|
+
subject.new
|
72
|
+
end
|
73
|
+
it "calls get_data" do
|
74
|
+
subject.any_instance.should_receive(:get_data)
|
75
|
+
subject.new
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "private" do
|
80
|
+
before do
|
81
|
+
Mechanize.stub(:new).and_return(mechanize)
|
82
|
+
mechanize.stub(:get).and_return(mechanize)
|
83
|
+
mechanize.stub(:form_with).and_return(form)
|
84
|
+
mechanize.stub(:at).and_return(node)
|
85
|
+
mechanize.stub(:submit).and_return(mechanize)
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#authenticate!" do
|
89
|
+
before do
|
90
|
+
subject.any_instance.unstub(:authenticate!)
|
91
|
+
subject.any_instance.stub(:get_letter).and_return("A")
|
92
|
+
end
|
93
|
+
it "gets #{LOGIN_ENDPOINT}" do
|
94
|
+
mechanize.should_receive(:get).
|
95
|
+
with("https://bcol.barclaycard.co.uk/ecom/as2/initialLogon.do").
|
96
|
+
and_return(mechanize)
|
97
|
+
subject.new
|
98
|
+
end
|
99
|
+
it "finds by form action" do
|
100
|
+
mechanize.should_receive(:form_with).
|
101
|
+
with(action: "/ecom/as2/initialLogon.do").
|
102
|
+
and_return(form)
|
103
|
+
subject.new
|
104
|
+
end
|
105
|
+
it "fills in form inputs" do
|
106
|
+
mechanize.should_receive(:at).
|
107
|
+
with("label[for='lettera']").
|
108
|
+
and_return(node)
|
109
|
+
|
110
|
+
mechanize.should_receive(:at).
|
111
|
+
with("label[for='letterb']").
|
112
|
+
and_return(node)
|
113
|
+
|
114
|
+
|
115
|
+
node.should_receive(:content).
|
116
|
+
at_least(:twice).
|
117
|
+
and_return("1")
|
118
|
+
|
119
|
+
subject.any_instance.unstub(:get_letter)
|
120
|
+
|
121
|
+
form.should_receive(:[]=).with("username", "joe")
|
122
|
+
form.should_receive(:[]=).with("password", "123456")
|
123
|
+
form.should_receive(:[]=).with("firstAnswer", "S")
|
124
|
+
form.should_receive(:[]=).with("secondAnswer", "S")
|
125
|
+
|
126
|
+
|
127
|
+
subject.new(username: "joe", password: "123456",
|
128
|
+
memorable_word: "superduper")
|
129
|
+
end
|
130
|
+
|
131
|
+
it "submits form" do
|
132
|
+
form.should_receive(:buttons).
|
133
|
+
twice.
|
134
|
+
and_return([button])
|
135
|
+
mechanize.should_receive(:submit).
|
136
|
+
with(form,button)
|
137
|
+
subject.new
|
138
|
+
end
|
139
|
+
|
140
|
+
it "parses html for account limit" do
|
141
|
+
mechanize.should_receive(:at).
|
142
|
+
with(".panelSummary .limit .figure").
|
143
|
+
and_return(node)
|
144
|
+
subject.new
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "#get_data" do
|
150
|
+
before do
|
151
|
+
subject.any_instance.unstub(:get_data)
|
152
|
+
mechanize.stub(get: stub(body: ofx))
|
153
|
+
end
|
154
|
+
|
155
|
+
it "gets #{EXPORT_ENDPOINT}" do
|
156
|
+
mechanize.should_receive(:get).
|
157
|
+
with(EXPORT_ENDPOINT).
|
158
|
+
and_return(stub(body: ofx))
|
159
|
+
subject.new
|
160
|
+
end
|
161
|
+
it "finds account balance" do
|
162
|
+
|
163
|
+
end
|
164
|
+
it "finds account limit"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|