banker 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|