syrup 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,178 +1,181 @@
1
- require 'date'
2
-
3
- module Syrup
4
- module Institutions
5
- class ZionsBank < InstitutionBase
6
-
7
- class << self
8
- def name
9
- "Zions Bank"
10
- end
11
-
12
- def id
13
- "zions_bank"
14
- end
15
- end
16
-
17
- def fetch_account(account_id)
18
- fetch_accounts
19
- end
20
-
21
- def fetch_accounts
22
- ensure_authenticated
23
-
24
- # List accounts
25
- page = agent.get('https://banking.zionsbank.com/ibuir/displayAccountBalance.htm')
26
- json = MultiJson.decode(page.body)
27
-
28
- accounts = []
29
- json['accountBalance']['depositAccountList'].each do |account|
30
- new_account = Account.new(:id => account['accountId'], :institution => self)
31
- new_account.name = unescape_html(account['name'])
32
- new_account.account_number = account['number']
33
- new_account.current_balance = parse_currency(account['currentAmt'])
34
- new_account.available_balance = parse_currency(account['availableAmt'])
35
- new_account.type = :deposit
36
-
37
- accounts << new_account
38
- end
39
- json['accountBalance']['creditAccountList'].each do |account|
40
- new_account = Account.new(:id => account['accountId'], :institution => self)
41
- new_account.name = unescape_html(account['name'])
42
- new_account.account_number = account['number']
43
- new_account.current_balance = parse_currency(account['balanceDueAmt'])
44
- new_account.type = :credit
45
-
46
- accounts << new_account
47
- end
48
-
49
- accounts
50
- end
51
-
52
- def fetch_transactions(account_id, starting_at, ending_at)
53
- ensure_authenticated
54
-
55
- transactions = []
56
-
57
- post_vars = { "actAcct" => account_id, "dayRange.searchType" => "dates", "dayRange.startDate" => starting_at.strftime('%m/%d/%Y'), "dayRange.endDate" => ending_at.strftime('%m/%d/%Y'), "submit_view.x" => 11, "submit_view.y" => 11, "submit_view" => "view" }
58
-
59
- page = agent.post("https://banking.zionsbank.com/zfnb/userServlet/app/bank/user/register_view_main?reSort=false&actAcct=#{account_id}", post_vars)
60
-
61
- # Get all the transactions
62
- page.search('tr').each do |row_element|
63
- # Look for the account information first
64
- account = find_account_by_id(account_id)
65
- datapart = row_element.css('.acct')
66
- if datapart && datapart.inner_html.size > 0
67
- if match = /Prior Day Balance:\s*([^<]+)/.match(datapart.inner_html)
68
- account.prior_day_balance = parse_currency(match[1])
69
- end
70
- if match = /Current Balance:\s*([^<]+)/.match(datapart.inner_html)
71
- account.current_balance = parse_currency(match[1])
72
- end
73
- if match = /Available Balance:\s*([^<]+)/.match(datapart.inner_html)
74
- account.available_balance = parse_currency(match[1])
75
- end
76
- end
77
-
78
- data = []
79
- datapart = row_element.css('.data')
80
- if datapart
81
- data += datapart
82
- datapart = row_element.css('.curr')
83
- data += datapart if datapart
84
- end
85
-
86
- datapart = row_element.css('.datagrey')
87
- if datapart
88
- data += datapart
89
- datapart = row_element.css('.currgrey')
90
- data += datapart if datapart
91
- end
92
-
93
- if data.size == 7
94
- data.map! {|cell| cell.inner_html.strip.gsub(/[^ -~]/, '') }
95
-
96
- transaction = Transaction.new
97
-
98
- transaction.posted_at = Date.strptime(data[0], '%m/%d/%Y')
99
- transaction.payee = unescape_html(data[2])
100
- transaction.status = data[3].include?("Posted") ? :posted : :pending
101
- unless data[4].empty?
102
- transaction.amount = -parse_currency(data[4])
103
- end
104
- unless data[5].empty?
105
- transaction.amount = parse_currency(data[5])
106
- end
107
-
108
- transactions << transaction
109
- end
110
- end
111
-
112
- transactions
113
- end
114
-
115
- private
116
-
117
- def ensure_authenticated
118
-
119
- # Check to see if already authenticated
120
- page = agent.get('https://banking.zionsbank.com/ibuir/')
121
- if page.body.include?("SessionTimeOutException")
122
-
123
- raise InformationMissingError, "Please supply a username" unless self.username
124
- raise InformationMissingError, "Please supply a password" unless self.password
125
-
126
- # Enter the username
127
- page = agent.get('https://www.zionsbank.com')
128
- form = page.form('logonForm')
129
- form.publicCred1 = username
130
- page = form.submit
131
-
132
- # If the supplied username is incorrect, raise an exception
133
- raise InformationMissingError, "Invalid username" if page.title == "Error Page"
134
-
135
- # Go on to the next page
136
- page = page.links.first.click
137
-
138
- # Find the secret question
139
- question = page.search('div.form_field')[2].css('div').inner_text
140
-
141
- # If the answer to this question was not supplied, raise an exception
142
- raise InformationMissingError, "Please answer the question, \"#{question}\"" unless secret_questions && secret_questions[question]
143
-
144
- # Enter the answer to the secret question
145
- form = page.forms.first
146
- form["challengeEntry.answerText"] = secret_questions[question]
147
- form.radiobutton_with(:value => 'false').check
148
- submit_button = form.button_with(:name => '_eventId_submit')
149
- page = form.submit(submit_button)
150
-
151
- # If the supplied answer is incorrect, raise an exception
152
- raise InformationMissingError, "\"#{secret_questions[question]}\" is not the correct answer to, \"#{question}\"" unless page.search('#errorComponent').empty?
153
-
154
- # Enter the password
155
- form = page.forms.first
156
- form.privateCred1 = password
157
- submit_button = form.button_with(:name => '_eventId_submit')
158
- page = form.submit(submit_button)
159
-
160
- # If the supplied password is incorrect, raise an exception
161
- raise InformationMissingError, "An invalid password was supplied" unless page.search('#errorComponent').empty?
162
-
163
- # Clicking this link logs us into the banking.zionsbank.com domain
164
- page = page.links.first.click
165
-
166
- if page.uri.to_s != "https://banking.zionsbank.com/ibuir/displayUserInterface.htm"
167
- page = agent.get('https://banking.zionsbank.com/zfnb/userServlet/app/bank/user/viewaccountsbysubtype/viewAccount')
168
-
169
- raise "Unknown URL reached. Try logging in manually through a browser." if page.body.include?("SessionTimeOutException")
170
- end
171
- end
172
-
173
- true
174
- end
175
-
176
- end
177
- end
178
- end
1
+ require 'date'
2
+
3
+ module Syrup
4
+ module Institutions
5
+ class ZionsBank < InstitutionBase
6
+
7
+ class << self
8
+ def name
9
+ "Zions Bank"
10
+ end
11
+
12
+ def id
13
+ "zions_bank"
14
+ end
15
+ end
16
+
17
+ def fetch_account(account_id)
18
+ fetch_accounts
19
+ end
20
+
21
+ def fetch_accounts
22
+ ensure_authenticated
23
+
24
+ # List accounts
25
+ page = agent.get('https://banking.zionsbank.com/ibuir/displayAccountBalance.htm')
26
+ json = MultiJson.decode(page.body)
27
+
28
+ accounts = []
29
+ json['accountBalance']['depositAccountList'].each do |account|
30
+ new_account = Account.new(:id => account['accountId'], :institution => self)
31
+ new_account.name = unescape_html(account['name'])
32
+ new_account.account_number = account['number']
33
+ new_account.current_balance = parse_currency(account['currentAmt'])
34
+ new_account.available_balance = parse_currency(account['availableAmt'])
35
+ new_account.type = :deposit
36
+
37
+ accounts << new_account
38
+ end
39
+ json['accountBalance']['creditAccountList'].each do |account|
40
+ new_account = Account.new(:id => account['accountId'], :institution => self)
41
+ new_account.name = unescape_html(account['name'])
42
+ new_account.account_number = account['number']
43
+ new_account.current_balance = parse_currency(account['balanceDueAmt'])
44
+ new_account.type = :credit
45
+
46
+ accounts << new_account
47
+ end
48
+
49
+ accounts
50
+ end
51
+
52
+ def fetch_transactions(account_id, starting_at, ending_at)
53
+ ensure_authenticated
54
+
55
+ transactions = []
56
+
57
+ post_vars = { "actAcct" => account_id, "dayRange.searchType" => "dates", "dayRange.startDate" => starting_at.strftime('%m/%d/%Y'), "dayRange.endDate" => ending_at.strftime('%m/%d/%Y'), "submit_view.x" => 11, "submit_view.y" => 11, "submit_view" => "view" }
58
+
59
+ page = agent.post("https://banking.zionsbank.com/zfnb/userServlet/app/bank/user/register_view_main?reSort=false&actAcct=#{account_id}", post_vars)
60
+
61
+ # Get all the transactions
62
+ page.search('tr').each do |row_element|
63
+ # Look for the account information first
64
+ account = find_account_by_id(account_id)
65
+ datapart = row_element.css('.acct')
66
+ if datapart && datapart.inner_html.size > 0
67
+ if match = /Prior Day Balance:\s*([^<]+)/.match(datapart.inner_html)
68
+ account.prior_day_balance = parse_currency(match[1])
69
+ end
70
+ if match = /Current Balance:\s*([^<]+)/.match(datapart.inner_html)
71
+ account.current_balance = parse_currency(match[1])
72
+ end
73
+ if match = /Available Balance:\s*([^<]+)/.match(datapart.inner_html)
74
+ account.available_balance = parse_currency(match[1])
75
+ end
76
+ end
77
+
78
+ data = []
79
+ datapart = row_element.css('.data')
80
+ if datapart
81
+ data += datapart
82
+ datapart = row_element.css('.curr')
83
+ data += datapart if datapart
84
+ end
85
+
86
+ datapart = row_element.css('.datagrey')
87
+ if datapart
88
+ data += datapart
89
+ datapart = row_element.css('.currgrey')
90
+ data += datapart if datapart
91
+ end
92
+
93
+ if data.size == 7
94
+ data.map! {|cell| cell.inner_html.strip.gsub(/[^ -~]/, '') }
95
+
96
+ transaction = Transaction.new
97
+
98
+ transaction.posted_at = Date.strptime(data[0], '%m/%d/%Y')
99
+ transaction.payee = unescape_html(data[2])
100
+ transaction.status = data[3].include?("Posted") ? :posted : :pending
101
+ unless data[4].empty?
102
+ transaction.amount = -parse_currency(data[4])
103
+ end
104
+ unless data[5].empty?
105
+ transaction.amount = parse_currency(data[5])
106
+ end
107
+
108
+ transactions << transaction
109
+ end
110
+ end
111
+
112
+ transactions
113
+ end
114
+
115
+ private
116
+
117
+ def ensure_authenticated
118
+
119
+ # Check to see if already authenticated
120
+ page = agent.get('https://banking.zionsbank.com/ibuir/')
121
+ if page.body.include?("SessionTimeOutException")
122
+
123
+ raise InformationMissingError, "Please supply a username" unless self.username
124
+ raise InformationMissingError, "Please supply a password" unless self.password
125
+
126
+ # Enter the username
127
+ page = agent.get('https://www.zionsbank.com')
128
+ form = page.form('logonForm')
129
+ form.publicCred1 = username
130
+ page = form.submit
131
+
132
+ # If the supplied username is incorrect, raise an exception
133
+ raise InformationMissingError, "Invalid username" if page.title == "Error Page"
134
+
135
+ # Go on to the next page
136
+ page = page.links.first.click
137
+
138
+ # Skip the secret question if we are prompted for the password
139
+ unless page.body.include?("Site Validation and Password")
140
+ # Find the secret question
141
+ question = page.search('div.form_field')[2].css('div').inner_text
142
+
143
+ # If the answer to this question was not supplied, raise an exception
144
+ raise InformationMissingError, "Please answer the question, \"#{question}\"" unless secret_questions && secret_questions[question]
145
+
146
+ # Enter the answer to the secret question
147
+ form = page.forms.first
148
+ form["challengeEntry.answerText"] = secret_questions[question]
149
+ form.radiobutton_with(:value => 'false').check
150
+ submit_button = form.button_with(:name => '_eventId_submit')
151
+ page = form.submit(submit_button)
152
+
153
+ # If the supplied answer is incorrect, raise an exception
154
+ raise InformationMissingError, "\"#{secret_questions[question]}\" is not the correct answer to, \"#{question}\"" unless page.search('#errorComponent').empty?
155
+ end
156
+
157
+ # Enter the password
158
+ form = page.forms.first
159
+ form.privateCred1 = password
160
+ submit_button = form.button_with(:name => '_eventId_submit')
161
+ page = form.submit(submit_button)
162
+
163
+ # If the supplied password is incorrect, raise an exception
164
+ raise InformationMissingError, "An invalid password was supplied" unless page.search('#errorComponent').empty?
165
+
166
+ # Clicking this link logs us into the banking.zionsbank.com domain
167
+ page = page.links.first.click
168
+
169
+ if page.uri.to_s != "https://banking.zionsbank.com/ibuir/displayUserInterface.htm"
170
+ page = agent.get('https://banking.zionsbank.com/zfnb/userServlet/app/bank/user/viewaccountsbysubtype/viewAccount')
171
+
172
+ raise "Unknown URL reached. Try logging in manually through a browser." if page.body.include?("SessionTimeOutException")
173
+ end
174
+ end
175
+
176
+ true
177
+ end
178
+
179
+ end
180
+ end
181
+ end
@@ -1,20 +1,20 @@
1
- module Syrup
2
- class Transaction
3
- ##
4
- # :attr_accessor: status
5
- # Currently, the only valid types are :posted and :pending
6
-
7
- #
8
- attr_accessor :id, :payee, :amount, :posted_at, :status
9
-
10
- # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
11
- # attributes (pass a hash with key names matching the associated attribute names).
12
- def initialize(attr_hash = nil)
13
- if attr_hash
14
- attr_hash.each do |k, v|
15
- instance_variable_set "@#{k}", v
16
- end
17
- end
18
- end
19
- end
1
+ module Syrup
2
+ class Transaction
3
+ ##
4
+ # :attr_accessor: status
5
+ # Currently, the only valid types are :posted and :pending
6
+
7
+ #
8
+ attr_accessor :id, :payee, :amount, :posted_at, :status
9
+
10
+ # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
11
+ # attributes (pass a hash with key names matching the associated attribute names).
12
+ def initialize(attr_hash = nil)
13
+ if attr_hash
14
+ attr_hash.each do |k, v|
15
+ instance_variable_set "@#{k}", v
16
+ end
17
+ end
18
+ end
19
+ end
20
20
  end
data/lib/syrup/version.rb CHANGED
@@ -1,3 +1,3 @@
1
- module Syrup
2
- VERSION = "0.0.9"
3
- end
1
+ module Syrup
2
+ VERSION = "0.0.10"
3
+ end
data/lib/syrup.rb CHANGED
@@ -1,46 +1,46 @@
1
- require 'date'
2
- require 'mechanize'
3
- require 'multi_json'
4
- require 'syrup/information_missing_error'
5
- require 'syrup/account'
6
- require 'syrup/transaction'
7
-
8
- # require all institutions
9
- require 'syrup/institutions/institution_base'
10
- Dir[File.dirname(__FILE__) + '/syrup/institutions/*.rb'].each {|file| require file }
11
-
12
- module Syrup
13
- extend self
14
-
15
- # Returns an array of institutions.
16
- #
17
- # Syrup.institutions.each do |institution|
18
- # puts "name: #{institution.name}, id: #{institution.id}"
19
- # end
20
- def institutions
21
- Institutions::InstitutionBase.subclasses
22
- end
23
-
24
- # Returns a new institution object with the specified +institution_id+.
25
- # If you pass in a block, you can use it to setup the username, password, and secret_questions.
26
- #
27
- # Syrup.setup_institution('zions_bank') do |config|
28
- # config.username = "my_user"
29
- # config.password = "my_password"
30
- # config.secret_questions = {
31
- # 'How long is your beard?' => '6in'
32
- # }
33
- # end
34
- def setup_institution(institution_id)
35
- institution = institutions.find { |i| i.id == institution_id }
36
-
37
- if institution
38
- i = institution.new
39
- if block_given?
40
- i.setup { |config| yield config }
41
- else
42
- i
43
- end
44
- end
45
- end
46
- end
1
+ require 'date'
2
+ require 'mechanize'
3
+ require 'multi_json'
4
+ require 'syrup/information_missing_error'
5
+ require 'syrup/account'
6
+ require 'syrup/transaction'
7
+
8
+ # require all institutions
9
+ require 'syrup/institutions/institution_base'
10
+ Dir[File.dirname(__FILE__) + '/syrup/institutions/*.rb'].each {|file| require file }
11
+
12
+ module Syrup
13
+ extend self
14
+
15
+ # Returns an array of institutions.
16
+ #
17
+ # Syrup.institutions.each do |institution|
18
+ # puts "name: #{institution.name}, id: #{institution.id}"
19
+ # end
20
+ def institutions
21
+ Institutions::InstitutionBase.subclasses
22
+ end
23
+
24
+ # Returns a new institution object with the specified +institution_id+.
25
+ # If you pass in a block, you can use it to setup the username, password, and secret_questions.
26
+ #
27
+ # Syrup.setup_institution('zions_bank') do |config|
28
+ # config.username = "my_user"
29
+ # config.password = "my_password"
30
+ # config.secret_questions = {
31
+ # 'How long is your beard?' => '6in'
32
+ # }
33
+ # end
34
+ def setup_institution(institution_id)
35
+ institution = institutions.find { |i| i.id == institution_id }
36
+
37
+ if institution
38
+ i = institution.new
39
+ if block_given?
40
+ i.setup { |config| yield config }
41
+ else
42
+ i
43
+ end
44
+ end
45
+ end
46
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,15 +1,15 @@
1
- require 'rubygems'
2
- require 'bundler/setup'
3
- require 'syrup'
4
-
5
- RSpec.configure do |config|
6
- include Syrup
7
-
8
- config.filter_run_excluding :bank_integration => true
9
- end
10
-
11
- module Syrup
12
- def spec_resource_path
13
- File.dirname(__FILE__) + '/resources/'
14
- end
15
- end
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'syrup'
4
+
5
+ RSpec.configure do |config|
6
+ include Syrup
7
+
8
+ config.filter_run_excluding :bank_integration => true
9
+ end
10
+
11
+ module Syrup
12
+ def spec_resource_path
13
+ File.dirname(__FILE__) + '/resources/'
14
+ end
15
+ end
@@ -1,53 +1,53 @@
1
- require "spec_helper"
2
-
3
- describe Account do
4
- before(:each) do
5
- @institution = double()
6
- @institution.stub(:populate_account) do
7
- @account.populated = true
8
- @account.instance_variable_set :@name, 'my name'
9
- end
10
- @institution.stub(:fetch_transactions) do
11
- [
12
- Transaction.new(:id => 1, :payee => 'Wal-Mart', :posted_at => Date.today - 1, :amount => 30.14),
13
- Transaction.new(:id => 2, :payee => 'Pizza Hut', :posted_at => Date.today - 2, :amount => 10.23)
14
- ]
15
- end
16
- @account = Account.new :id => 1, :institution => @institution
17
- end
18
-
19
- it "has lots of useful properties" do
20
- account = Account.new
21
-
22
- account.should respond_to(:id)
23
- account.should respond_to(:name)
24
- account.should respond_to(:type)
25
- account.should respond_to(:account_number)
26
- account.should respond_to(:current_balance)
27
- account.should respond_to(:available_balance)
28
- account.should respond_to(:prior_day_balance)
29
- end
30
-
31
- it "is populated when properties are accessed" do
32
- @account.instance_variable_get(:@name).should be_nil
33
- @account.name.should == "my name"
34
- end
35
-
36
- it "is considered == if the id is the same" do
37
- account1 = Account.new :id => 1, :name => "checking"
38
- account2 = Account.new :id => 1, :name => "savings"
39
- account3 = Account.new :id => 2, :name => "trash"
40
-
41
- account1.should == account2
42
- account1.should_not == account3
43
- end
44
-
45
- context "given a date range" do
46
- it "gets transactions" do
47
- @account.find_transactions(Date.today - 30)
48
- end
49
- #it "only fetches transactions when needed"
50
- end
51
-
52
- #it "doesn't allow there to be too many cached transactions"
1
+ require "spec_helper"
2
+
3
+ describe Account do
4
+ before(:each) do
5
+ @institution = double()
6
+ @institution.stub(:populate_account) do
7
+ @account.populated = true
8
+ @account.instance_variable_set :@name, 'my name'
9
+ end
10
+ @institution.stub(:fetch_transactions) do
11
+ [
12
+ Transaction.new(:id => 1, :payee => 'Wal-Mart', :posted_at => Date.today - 1, :amount => 30.14),
13
+ Transaction.new(:id => 2, :payee => 'Pizza Hut', :posted_at => Date.today - 2, :amount => 10.23)
14
+ ]
15
+ end
16
+ @account = Account.new :id => 1, :institution => @institution
17
+ end
18
+
19
+ it "has lots of useful properties" do
20
+ account = Account.new
21
+
22
+ account.should respond_to(:id)
23
+ account.should respond_to(:name)
24
+ account.should respond_to(:type)
25
+ account.should respond_to(:account_number)
26
+ account.should respond_to(:current_balance)
27
+ account.should respond_to(:available_balance)
28
+ account.should respond_to(:prior_day_balance)
29
+ end
30
+
31
+ it "is populated when properties are accessed" do
32
+ @account.instance_variable_get(:@name).should be_nil
33
+ @account.name.should == "my name"
34
+ end
35
+
36
+ it "is considered == if the id is the same" do
37
+ account1 = Account.new :id => 1, :name => "checking"
38
+ account2 = Account.new :id => 1, :name => "savings"
39
+ account3 = Account.new :id => 2, :name => "trash"
40
+
41
+ account1.should == account2
42
+ account1.should_not == account3
43
+ end
44
+
45
+ context "given a date range" do
46
+ it "gets transactions" do
47
+ @account.find_transactions(Date.today - 30)
48
+ end
49
+ #it "only fetches transactions when needed"
50
+ end
51
+
52
+ #it "doesn't allow there to be too many cached transactions"
53
53
  end