dinero 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -0
- data/lib/dinero/account.rb +9 -0
- data/lib/dinero/banks/sdccu.rb +116 -0
- data/lib/dinero/banks.rb +1 -2
- data/lib/dinero/version.rb +1 -1
- data/lib/dinero.rb +3 -0
- data/spec/banks/sdccu_spec.rb +87 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00ee87276f3b118d80e13ae95262189b13f2f5ce
|
4
|
+
data.tar.gz: b9a357d02cc2517f326f7c15455713c3eacfb091
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe0825fa56a8d1c86758200c3632b711b8b2e75e82ca3b86b526a4da2527f4672d55659f15300075ad5a061ee0531fc84b889657395d681eb8b3f02cf84ff22c
|
7
|
+
data.tar.gz: c2a802e4c94235d4669539b4abbc56bbe063fe05909dea0767b24a2e3ce087a554298dfae8417ba74b8ed05b1533b1e8b6be951d71f4d08ff3067836bcbbf41c
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -29,6 +29,7 @@ The following banks are implemented:
|
|
29
29
|
* Capital One - https://capitalone.com (only U.S. credit card logins -- there's also banking, loans, investing, business, and Canada/UK credit cards)
|
30
30
|
* Capital One 360 - https://capitalone360.com (formerly Ing Direct).
|
31
31
|
* South State Bank - https://www.southstatebank.com/
|
32
|
+
* San Diego County Credit Union - https://www.sdccu.com/
|
32
33
|
|
33
34
|
Currently, only Accounts balances are implemented. The following properties are available on each Account:
|
34
35
|
|
data/lib/dinero/account.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Dinero
|
2
2
|
NUMERIC_REGEXP = /[\d|\-|\.]+/
|
3
|
+
|
3
4
|
class Account
|
4
5
|
attr_reader :account_type, :name, :name_other, :number, :balance, :available
|
5
6
|
def initialize account_type, name, number, balance, available
|
@@ -10,6 +11,14 @@ module Dinero
|
|
10
11
|
@number = number
|
11
12
|
@balance = balance.scan(NUMERIC_REGEXP).join.to_f
|
12
13
|
@available = available.scan(NUMERIC_REGEXP).join.to_f
|
14
|
+
@balance *= -1 if @account_type == :loan
|
15
|
+
end
|
16
|
+
|
17
|
+
%w(bank loan brokerage).each do |acct_type|
|
18
|
+
define_method "#{acct_type}_account?" do
|
19
|
+
self.account_type == acct_type.to_sym
|
20
|
+
end
|
13
21
|
end
|
14
22
|
end
|
23
|
+
|
15
24
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Dinero
|
2
|
+
module Bank
|
3
|
+
# San Diego County Credit Union
|
4
|
+
class Sdccu < Base
|
5
|
+
LOGIN_URL = "https://internetbranch.sdccu.com/SDCCU/Login.aspx"
|
6
|
+
ACCOUNTS_SUMMARY_PATH = "/accounts"
|
7
|
+
CONNECTION_TIMEOUT = 10
|
8
|
+
|
9
|
+
def default_options
|
10
|
+
{ timeout: CONNECTION_TIMEOUT, login_url: LOGIN_URL }
|
11
|
+
end
|
12
|
+
|
13
|
+
def post_username!
|
14
|
+
screenshot_on_error do
|
15
|
+
wait.until { connection.find_element(id: "ctlSignon_txtUserID") }
|
16
|
+
username_field = connection.find_element(id: "ctlSignon_txtUserID")
|
17
|
+
username_field.send_keys username
|
18
|
+
|
19
|
+
submit_button = connection.find_element(id: "ctlSignon_btnNext")
|
20
|
+
submit_button.click
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def post_password!
|
25
|
+
screenshot_on_error do
|
26
|
+
wait.until { connection.find_element(id: "ctlSignon_txtPassword") }
|
27
|
+
|
28
|
+
password_field = connection.find_element(id: "ctlSignon_txtPassword")
|
29
|
+
password_field.send_keys password
|
30
|
+
|
31
|
+
login_btn = connection.find_element(id: "ctlSignon_btnLogin")
|
32
|
+
login_btn.click
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_answer question
|
37
|
+
if q = security_questions.detect{ |qa| qa["question"] == question }
|
38
|
+
return q["answer"]
|
39
|
+
else
|
40
|
+
raise "Unknown security question: #{question.inspect}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def post_security_answer!
|
45
|
+
return if on_accounts_summary_page?
|
46
|
+
screenshot_on_error do
|
47
|
+
wait.until { connection.find_element(id: "lblChallengeQuestion") }
|
48
|
+
question_text = connection.find_element(id: "lblChallengeQuestion").text
|
49
|
+
answer = find_answer question_text
|
50
|
+
|
51
|
+
answer_field = connection.find_element(id: "QuestionAnswer")
|
52
|
+
answer_field.send_keys answer
|
53
|
+
|
54
|
+
submit_button = logon_form.find_element(id:"btnSubmitAnswer")
|
55
|
+
submit_button.click
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def post_credentials!
|
60
|
+
post_username!
|
61
|
+
post_password!
|
62
|
+
post_security_answer!
|
63
|
+
end
|
64
|
+
|
65
|
+
def after_successful_login
|
66
|
+
# the subdomain frequently changes, so capture the actual URL
|
67
|
+
# so we can return to the page if necessary.
|
68
|
+
@accounts_summary_url = connection.current_url
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_accounts_summary_page?
|
72
|
+
connection.page_source =~ /Account Balances/
|
73
|
+
end
|
74
|
+
|
75
|
+
def goto_accounts_summary_page
|
76
|
+
return if authenticated? && on_accounts_summary_page?
|
77
|
+
authenticated? ? connection.navigate.to(@accounts_summary_url) : login!
|
78
|
+
end
|
79
|
+
|
80
|
+
def account_tables
|
81
|
+
accounts_summary_document.xpath("//div[@id='accountBalancesContainer_accountBalancesModule_accountList']//table")
|
82
|
+
end
|
83
|
+
|
84
|
+
def bank_accounts_table
|
85
|
+
account_tables[0]
|
86
|
+
end
|
87
|
+
|
88
|
+
def loan_accounts_table
|
89
|
+
account_tables[1]
|
90
|
+
end
|
91
|
+
|
92
|
+
def account_rows table, type
|
93
|
+
rows = table.xpath(".//tr").map{|m| m.xpath(".//td").map{|m| m.text.strip}}
|
94
|
+
rows.select{|s| s.size == 7}.map{|m| m.reject(&:empty?) << type}
|
95
|
+
end
|
96
|
+
|
97
|
+
def account_table_rows
|
98
|
+
account_rows(bank_accounts_table, :bank) + account_rows(loan_accounts_table, :loan)
|
99
|
+
end
|
100
|
+
|
101
|
+
# extract account data from the account summary page
|
102
|
+
def accounts
|
103
|
+
return @accounts if @accounts
|
104
|
+
|
105
|
+
@accounts = account_table_rows.map do |row|
|
106
|
+
acct_type = row.pop
|
107
|
+
number = row.shift
|
108
|
+
name = row.shift
|
109
|
+
balance = row.shift.scan(NUMERIC_REGEXP).join
|
110
|
+
available = acct_type == :loan ? "0.0" : balance
|
111
|
+
Account.new(acct_type, name, number, balance, available)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/dinero/banks.rb
CHANGED
@@ -30,9 +30,8 @@ module Dinero
|
|
30
30
|
|
31
31
|
def establish_connection
|
32
32
|
capabilities = Selenium::WebDriver::Remote::Capabilities.phantomjs(
|
33
|
-
'acceptSslCerts' => true,
|
34
33
|
'phantomjs.page.settings.userAgent' => 'Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/33.0',
|
35
|
-
'service_args' => ['--ignore-ssl-errors=true']
|
34
|
+
'service_args' => ['--ignore-ssl-errors=true', '--ssl-protocol=any']
|
36
35
|
)
|
37
36
|
|
38
37
|
driver = Selenium::WebDriver.for :phantomjs, :desired_capabilities => capabilities
|
data/lib/dinero/version.rb
CHANGED
data/lib/dinero.rb
CHANGED
@@ -3,9 +3,12 @@ require 'nokogiri'
|
|
3
3
|
|
4
4
|
require_relative 'dinero/version'
|
5
5
|
|
6
|
+
# Supported Banks
|
6
7
|
require_relative 'dinero/banks'
|
7
8
|
require_relative 'dinero/banks/capital_one'
|
8
9
|
require_relative 'dinero/banks/capital_one_360'
|
9
10
|
require_relative 'dinero/banks/south_state_bank'
|
11
|
+
require_relative 'dinero/banks/sdccu'
|
10
12
|
|
13
|
+
# Models
|
11
14
|
require_relative 'dinero/account'
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
if bank_configured? :sdccu
|
4
|
+
|
5
|
+
RSpec.describe Dinero::Bank::Sdccu do
|
6
|
+
let(:bank_configuration) { bank_configurations[:sdccu] }
|
7
|
+
let(:account_types) { bank_configuration[:account_types].sort }
|
8
|
+
let(:accounts) { bank_configuration[:accounts] }
|
9
|
+
|
10
|
+
before(:all) do
|
11
|
+
VCR.use_cassette("accounts_sdccu", record: :new_episodes) do
|
12
|
+
@bank = Dinero::Bank::Sdccu.new(bank_configurations[:sdccu])
|
13
|
+
@bank.accounts
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has security questions" do
|
18
|
+
expect(@bank.security_questions.count).to eq 4
|
19
|
+
end
|
20
|
+
|
21
|
+
it "finds favorite hobby answer" do
|
22
|
+
expect(@bank.find_answer("What is your favorite hobby?")).to eq "tennis"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "posts credentials" do
|
26
|
+
expect(@bank.authenticated?).to be true
|
27
|
+
end
|
28
|
+
|
29
|
+
it "authenticates" do
|
30
|
+
@bank.login!
|
31
|
+
expect(@bank.authenticated?).to eq true
|
32
|
+
end
|
33
|
+
|
34
|
+
it "retrieves accounts_summary_document" do
|
35
|
+
expect(@bank.accounts_summary_document).to be_kind_of Nokogiri::HTML::Document
|
36
|
+
end
|
37
|
+
|
38
|
+
it "has account_tables" do
|
39
|
+
expect(@bank.account_tables.size).to eq 2
|
40
|
+
end
|
41
|
+
|
42
|
+
it "has bank_accounts_table" do
|
43
|
+
expect(@bank.bank_accounts_table).to be_a Nokogiri::XML::Element
|
44
|
+
end
|
45
|
+
|
46
|
+
it "has loan_accounts_table" do
|
47
|
+
expect(@bank.loan_accounts_table).to be_a Nokogiri::XML::Element
|
48
|
+
end
|
49
|
+
|
50
|
+
it "has 2 bank accounts" do
|
51
|
+
expect(@bank.account_rows(@bank.bank_accounts_table, :bank).size).to eq 2
|
52
|
+
end
|
53
|
+
|
54
|
+
it "has 1 loan account" do
|
55
|
+
expect(@bank.account_rows(@bank.loan_accounts_table, :loan).size).to eq 1
|
56
|
+
end
|
57
|
+
|
58
|
+
it "gets expected accounts" do
|
59
|
+
expect(@bank.accounts.size).to eq accounts
|
60
|
+
end
|
61
|
+
|
62
|
+
it "extracts account names" do
|
63
|
+
expect(@bank.accounts.map(&:name).select{|s| s =~ /Primary/}).to_not be_empty
|
64
|
+
end
|
65
|
+
|
66
|
+
it "extracts account numbers" do
|
67
|
+
expect(@bank.accounts.map(&:number).first).to eq "1"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "expects balances to be greater than zero" do
|
71
|
+
expect(@bank.accounts.map(&:balance).any?(&:zero?)).to eq false
|
72
|
+
end
|
73
|
+
|
74
|
+
it "expects availables to be greater than zero for bank accts" do
|
75
|
+
expect(@bank.accounts.select{|s| s.bank_account?}.map(&:available).any?(&:zero?)).to eq false
|
76
|
+
end
|
77
|
+
|
78
|
+
it "expects availables to be zero for loan accts" do
|
79
|
+
expect(@bank.accounts.select{|s| s.loan_account?}.map(&:available).all?(&:zero?)).to eq true
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sets account types" do
|
83
|
+
expect(@bank.accounts.map(&:account_type).uniq).to eq account_types
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dinero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Lang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -173,11 +173,13 @@ files:
|
|
173
173
|
- lib/dinero/banks.rb
|
174
174
|
- lib/dinero/banks/capital_one.rb
|
175
175
|
- lib/dinero/banks/capital_one_360.rb
|
176
|
+
- lib/dinero/banks/sdccu.rb
|
176
177
|
- lib/dinero/banks/south_state_bank.rb
|
177
178
|
- lib/dinero/version.rb
|
178
179
|
- log/.keep
|
179
180
|
- spec/banks/capital_one_360_spec.rb
|
180
181
|
- spec/banks/capital_one_spec.rb
|
182
|
+
- spec/banks/sdccu_spec.rb
|
181
183
|
- spec/banks/south_state_bank_spec.rb
|
182
184
|
- spec/fixtures/vcr_cassettes/.keep
|
183
185
|
- spec/spec_helper.rb
|
@@ -210,6 +212,7 @@ summary: Dinero automates the process of logging into banking and financial webs
|
|
210
212
|
test_files:
|
211
213
|
- spec/banks/capital_one_360_spec.rb
|
212
214
|
- spec/banks/capital_one_spec.rb
|
215
|
+
- spec/banks/sdccu_spec.rb
|
213
216
|
- spec/banks/south_state_bank_spec.rb
|
214
217
|
- spec/fixtures/vcr_cassettes/.keep
|
215
218
|
- spec/spec_helper.rb
|