dinero 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.
- 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
|