syrup 0.0.3 → 0.0.4

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.
@@ -1,176 +1,176 @@
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'])
31
- new_account.name = 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'])
41
- new_account.name = 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
67
- /Prior Day Balance:\s*([^<]+)/.match(datapart.inner_html) do |match|
68
- account.prior_day_balance = parse_currency(match[1])
69
- end
70
- /Current Balance:\s*([^<]+)/.match(datapart.inner_html) do |match|
71
- account.current_balance = parse_currency(match[1])
72
- end
73
- /Available Balance:\s*([^<]+)/.match(datapart.inner_html) do |match|
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 = 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
- @agent = Mechanize.new
127
-
128
- # Enter the username
129
- page = agent.get('https://zionsbank.com')
130
- form = page.form('logonForm')
131
- form.publicCred1 = username
132
- page = form.submit
133
-
134
- # If the supplied username is incorrect, raise an exception
135
- raise InformationMissingError, "Invalid username" if page.title == "Error Page"
136
-
137
- # Go on to the next page
138
- page = page.links.first.click
139
-
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[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
-
156
- # Enter the password
157
- form = page.forms.first
158
- form.privateCred1 = password
159
- submit_button = form.button_with(:name => '_eventId_submit')
160
- page = form.submit(submit_button)
161
-
162
- # If the supplied password is incorrect, raise an exception
163
- raise InformationMissingError, "An invalid password was supplied" unless page.search('#errorComponent').empty?
164
-
165
- # Clicking this link logs us into the banking.zionsbank.com domain
166
- page = page.links.first.click
167
-
168
- raise "Unknown URL reached. Try logging in manually through a browser." if page.uri.to_s != "https://banking.zionsbank.com/ibuir/displayUserInterface.htm"
169
- end
170
-
171
- true
172
- end
173
-
174
- end
175
- 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 = 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 = 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
67
+ /Prior Day Balance:\s*([^<]+)/.match(datapart.inner_html) do |match|
68
+ account.prior_day_balance = parse_currency(match[1])
69
+ end
70
+ /Current Balance:\s*([^<]+)/.match(datapart.inner_html) do |match|
71
+ account.current_balance = parse_currency(match[1])
72
+ end
73
+ /Available Balance:\s*([^<]+)/.match(datapart.inner_html) do |match|
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 = 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
+ @agent = Mechanize.new
127
+
128
+ # Enter the username
129
+ page = agent.get('https://zionsbank.com')
130
+ form = page.form('logonForm')
131
+ form.publicCred1 = username
132
+ page = form.submit
133
+
134
+ # If the supplied username is incorrect, raise an exception
135
+ raise InformationMissingError, "Invalid username" if page.title == "Error Page"
136
+
137
+ # Go on to the next page
138
+ page = page.links.first.click
139
+
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
+
156
+ # Enter the password
157
+ form = page.forms.first
158
+ form.privateCred1 = password
159
+ submit_button = form.button_with(:name => '_eventId_submit')
160
+ page = form.submit(submit_button)
161
+
162
+ # If the supplied password is incorrect, raise an exception
163
+ raise InformationMissingError, "An invalid password was supplied" unless page.search('#errorComponent').empty?
164
+
165
+ # Clicking this link logs us into the banking.zionsbank.com domain
166
+ page = page.links.first.click
167
+
168
+ raise "Unknown URL reached. Try logging in manually through a browser." if page.uri.to_s != "https://banking.zionsbank.com/ibuir/displayUserInterface.htm"
169
+ end
170
+
171
+ true
172
+ end
173
+
174
+ end
175
+ end
176
176
  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.3"
3
- end
1
+ module Syrup
2
+ VERSION = "0.0.4"
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