plaid 1.4.3 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +66 -2
- data/lib/plaid/config.rb +8 -2
- data/lib/plaid/connection.rb +121 -0
- data/lib/plaid/models/account.rb +24 -0
- data/lib/plaid/models/category.rb +17 -0
- data/lib/plaid/models/exchange_token_response.rb +9 -0
- data/lib/plaid/models/info.rb +12 -0
- data/lib/plaid/models/institution.rb +20 -0
- data/lib/plaid/models/transaction.rb +23 -0
- data/lib/plaid/models/user.rb +192 -0
- data/lib/plaid/version.rb +1 -1
- data/lib/plaid.rb +77 -35
- data/plaid.gemspec +3 -1
- data/spec/account_spec.rb +35 -0
- data/spec/category_spec.rb +5 -14
- data/spec/config_spec.rb +57 -28
- data/spec/institution_spec.rb +6 -14
- data/spec/plaid_spec.rb +258 -0
- data/spec/spec_helper.rb +8 -1
- data/spec/transaction_spec.rb +30 -0
- data/spec/user_spec.rb +98 -109
- metadata +44 -12
- data/lib/plaid/add_user.rb +0 -24
- data/lib/plaid/category/category.rb +0 -29
- data/lib/plaid/institution/institution.rb +0 -29
- data/lib/plaid/user/account/account.rb +0 -35
- data/lib/plaid/user/info/info.rb +0 -23
- data/lib/plaid/user/transaction/transaction.rb +0 -19
- data/lib/plaid/user/user.rb +0 -173
- data/lib/plaid/util.rb +0 -120
- data/spec/add_user_spec.rb +0 -113
data/lib/plaid/user/user.rb
DELETED
@@ -1,173 +0,0 @@
|
|
1
|
-
require_relative 'account/account'
|
2
|
-
require_relative 'transaction/transaction'
|
3
|
-
require_relative 'info/info'
|
4
|
-
require 'plaid/util'
|
5
|
-
require 'json'
|
6
|
-
module Plaid
|
7
|
-
class Plaid::User
|
8
|
-
include Plaid::Util
|
9
|
-
|
10
|
-
# Define user vars
|
11
|
-
attr_accessor(:accounts, :transactions, :access_token, :type, :permissions, :api_res, :pending_mfa_questions, :info, :information)
|
12
|
-
|
13
|
-
def initialize
|
14
|
-
self.accounts = [], self.transactions = [], self.permissions = [], self.access_token = '', self.api_res = '', self.info = {}, self.information = Information.new
|
15
|
-
end
|
16
|
-
|
17
|
-
# Instantiate a new user with the results of the successful API call
|
18
|
-
# Build an array of nested accounts, and return self if successful
|
19
|
-
def new(res,api_level=nil)
|
20
|
-
build_user(res,api_level)
|
21
|
-
end
|
22
|
-
|
23
|
-
def mfa_authentication(auth,type=nil)
|
24
|
-
type = self.type if type.nil?
|
25
|
-
auth_path = self.permissions.last + '/step'
|
26
|
-
res = Plaid.post(auth_path,{mfa:auth,access_token:self.access_token,type:type})
|
27
|
-
self.accounts = [], self.transactions = []
|
28
|
-
build_user(res)
|
29
|
-
end
|
30
|
-
|
31
|
-
def select_mfa_method(selection,type=nil)
|
32
|
-
type = self.type if type.nil?
|
33
|
-
auth_path = self.permissions.last + '/step'
|
34
|
-
res = Plaid.post(auth_path,{options:{send_method: selection}.to_json, access_token:self.access_token,type:type})
|
35
|
-
build_user(res,self.permissions.last)
|
36
|
-
end
|
37
|
-
|
38
|
-
def get_auth
|
39
|
-
if self.permissions.include? 'auth'
|
40
|
-
res = Plaid.post('auth/get',{access_token:self.access_token})
|
41
|
-
build_user(res)
|
42
|
-
else
|
43
|
-
false
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def get_connect(options=nil)
|
48
|
-
if self.permissions.include? 'connect'
|
49
|
-
payload = {access_token:self.access_token}
|
50
|
-
payload.merge!(options) if options
|
51
|
-
res = Plaid.post('connect/get',payload)
|
52
|
-
build_user(res)
|
53
|
-
else
|
54
|
-
false
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def get_info
|
59
|
-
if self.permissions.include? 'info'
|
60
|
-
res = Plaid.secure_get('info',self.access_token)
|
61
|
-
build_user(res)
|
62
|
-
else
|
63
|
-
false
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def update_info(username,pass,pin=nil)
|
68
|
-
if self.permissions.include? 'info'
|
69
|
-
payload = {username:username,password:pass,access_token:self.access_token}
|
70
|
-
payload.merge!({pin:pin}) if pin
|
71
|
-
res = Plaid.patch('info',payload)
|
72
|
-
puts res
|
73
|
-
build_user(res)
|
74
|
-
else
|
75
|
-
false
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def update_balance
|
80
|
-
res = Plaid.post('balance',{access_token:self.access_token})
|
81
|
-
build_user(res)
|
82
|
-
end
|
83
|
-
|
84
|
-
def upgrade(api_level=nil)
|
85
|
-
if api_level.nil?
|
86
|
-
api_level = 'auth' unless self.permissions.include? 'auth'
|
87
|
-
api_level = 'connect' unless self.permissions.include? 'connect'
|
88
|
-
end
|
89
|
-
res = Plaid.post('upgrade',{access_token:self.access_token,upgrade_to:api_level})
|
90
|
-
self.accounts = [], self.transactions = []
|
91
|
-
build_user(res)
|
92
|
-
end
|
93
|
-
|
94
|
-
def delete_user
|
95
|
-
Plaid.delete('info',{access_token:self.access_token})
|
96
|
-
end
|
97
|
-
|
98
|
-
protected
|
99
|
-
|
100
|
-
def build_user(res,api_level=nil)
|
101
|
-
begin
|
102
|
-
if res[:msg].nil?
|
103
|
-
populate_user(self,res,api_level)
|
104
|
-
clean_up_user(self)
|
105
|
-
else
|
106
|
-
set_mfa_request(self,res,api_level)
|
107
|
-
end
|
108
|
-
rescue => e
|
109
|
-
error_handler(e)
|
110
|
-
else
|
111
|
-
self
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# Instantiate and build a new account object, return this to the accounts array
|
116
|
-
def new_account(res)
|
117
|
-
account = Account.new
|
118
|
-
account.new(res)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Instantiate and build a new account object, return this to the accounts array
|
122
|
-
def new_transaction(res)
|
123
|
-
transaction = Transaction.new
|
124
|
-
transaction.new(res)
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
def clean_up_user(user)
|
130
|
-
user.accounts.reject! { |c| !c.instance_of? Plaid::Account }
|
131
|
-
user
|
132
|
-
end
|
133
|
-
|
134
|
-
def set_mfa_request(user,res,api_level)
|
135
|
-
user.access_token = res[:body]['access_token']
|
136
|
-
user.pending_mfa_questions = res[:body]
|
137
|
-
user.api_res = res[:msg]
|
138
|
-
user.permissions << api_level unless self.permissions.include? api_level && api_level.nil?
|
139
|
-
end
|
140
|
-
|
141
|
-
def populate_user(user,res,api_level)
|
142
|
-
res['accounts'].each do |account|
|
143
|
-
if user.accounts.any? { |h| h == account['_id'] }
|
144
|
-
owned_account = user.accounts.find { |h| h == account['_id'] }
|
145
|
-
owned_account.new(account)
|
146
|
-
else
|
147
|
-
user.accounts << new_account(account)
|
148
|
-
end
|
149
|
-
end if res['accounts']
|
150
|
-
|
151
|
-
res['transactions'].each do |transaction|
|
152
|
-
if user.transactions.any? { |t| t == transaction['_id'] }
|
153
|
-
owned_transaction = user.transactions.find { |h| h == transaction['_id'] }
|
154
|
-
owned_transaction.new(transaction)
|
155
|
-
else
|
156
|
-
user.transactions << new_transaction(transaction)
|
157
|
-
end
|
158
|
-
end if res['transactions']
|
159
|
-
|
160
|
-
user.permissions << api_level unless user.permissions.include? api_level && api_level.nil?
|
161
|
-
user.pending_mfa_questions = ''
|
162
|
-
user.information.update_info(res['info']) if res['info']
|
163
|
-
user.api_res = 'success'
|
164
|
-
|
165
|
-
# TODO: Remove the following line when upgrading to V-2
|
166
|
-
user.info.merge!(res['info']) if res['info']
|
167
|
-
# End TODO
|
168
|
-
user.access_token = res['access_token'].split[0]
|
169
|
-
user.type = res['access_token'].split[1]
|
170
|
-
end
|
171
|
-
|
172
|
-
end
|
173
|
-
end
|
data/lib/plaid/util.rb
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
require 'net/http'
|
2
|
-
require 'json'
|
3
|
-
require 'uri'
|
4
|
-
module Plaid
|
5
|
-
module Util
|
6
|
-
|
7
|
-
def post(path,options={})
|
8
|
-
uri = build_uri(path)
|
9
|
-
options.merge!({client_id: self.instance_variable_get(:'@customer_id') ,secret: self.instance_variable_get(:'@secret')})
|
10
|
-
res = Net::HTTP.post_form(uri,options)
|
11
|
-
parse_response(res)
|
12
|
-
end
|
13
|
-
|
14
|
-
def get(path,id=nil)
|
15
|
-
uri = build_uri(path,id)
|
16
|
-
res = Net::HTTP.get(uri)
|
17
|
-
parse_get_response(res)
|
18
|
-
end
|
19
|
-
|
20
|
-
def secure_get(path,access_token,options={})
|
21
|
-
uri = build_uri(path)
|
22
|
-
options.merge!({access_token:access_token})
|
23
|
-
req = Net::HTTP::Get.new(uri)
|
24
|
-
req.body = URI.encode_www_form(options) if options
|
25
|
-
req.content_type = 'multipart/form-data'
|
26
|
-
res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') { |http| http.request(req) }
|
27
|
-
parse_response(res)
|
28
|
-
end
|
29
|
-
|
30
|
-
def patch(path,options={})
|
31
|
-
uri = build_uri(path)
|
32
|
-
options.merge!({client_id: self.instance_variable_get(:'@customer_id') ,secret: self.instance_variable_get(:'@secret')})
|
33
|
-
req = Net::HTTP::Patch.new(uri)
|
34
|
-
req.body = URI.encode_www_form(options) if options
|
35
|
-
req.content_type = 'multipart/form-data'
|
36
|
-
res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') { |http| http.request(req) }
|
37
|
-
parse_response(res)
|
38
|
-
end
|
39
|
-
|
40
|
-
def delete(path,options={})
|
41
|
-
uri = build_uri(path)
|
42
|
-
options.merge!({client_id: self.instance_variable_get(:'@customer_id') ,secret: self.instance_variable_get(:'@secret')})
|
43
|
-
req = Net::HTTP::Delete.new(uri)
|
44
|
-
req.body = URI.encode_www_form(options) if options
|
45
|
-
req.content_type = 'multipart/form-data'
|
46
|
-
Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') { |http| http.request(req) }
|
47
|
-
end
|
48
|
-
|
49
|
-
def error_handler(err,res=nil)
|
50
|
-
case err
|
51
|
-
when 'Bad Request'
|
52
|
-
puts res.body
|
53
|
-
raise 'The request was malformed. Did you check the API docs?'
|
54
|
-
when 'Unauthorized'
|
55
|
-
raise 'Access denied: Try using the correct credentials.'
|
56
|
-
when 'Request Failed'
|
57
|
-
raise 'Request Failed'
|
58
|
-
when 'Not Found'
|
59
|
-
raise 'Not Found'
|
60
|
-
when 'Institution not supported'
|
61
|
-
raise 'Institution not supported'
|
62
|
-
when 'Corrupted token'
|
63
|
-
raise 'It appears that the access token has been corrupted'
|
64
|
-
else
|
65
|
-
raise err
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
protected
|
70
|
-
|
71
|
-
def build_uri(path,option=nil)
|
72
|
-
path = path + '/' + option unless option.nil?
|
73
|
-
URI.parse(self.instance_variable_get(:'@environment_location') + path)
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def parse_response(res)
|
79
|
-
body = JSON.parse(res.body)
|
80
|
-
case res.code.delete('.').to_i
|
81
|
-
when 200
|
82
|
-
return body
|
83
|
-
when 201
|
84
|
-
return { msg: 'Requires further authentication', body: body}
|
85
|
-
when 400
|
86
|
-
error_handler('Bad Request',res)
|
87
|
-
when 401
|
88
|
-
error_handler('Institution not supported',res) if body['code'] == 1108
|
89
|
-
error_handler('Corrupted token',res) if body['code'] == 1105
|
90
|
-
error_handler('Not Found',res) if body['code'] == 1501
|
91
|
-
error_handler('Unauthorized',res)
|
92
|
-
when 402
|
93
|
-
return {msg: 'User account is locked', body: body} if body['code'] == 1205
|
94
|
-
error_handler('Request Failed', res)
|
95
|
-
when 404
|
96
|
-
error_handler('Not Found',res)
|
97
|
-
else
|
98
|
-
error_handler('Server Error',res)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def parse_get_response(res)
|
103
|
-
body = JSON.parse(res)
|
104
|
-
if body.class == Array
|
105
|
-
body
|
106
|
-
else
|
107
|
-
if body['code'].nil?
|
108
|
-
body
|
109
|
-
else
|
110
|
-
if body['code'] == 1301 || body['code'] == 1401 || body['code'] == 1501 || body['code'] == 1601
|
111
|
-
error_handler('Not Found',body)
|
112
|
-
else
|
113
|
-
body
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
end
|
120
|
-
end
|
data/spec/add_user_spec.rb
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
require 'spec_helper.rb'
|
2
|
-
# Authentication flow specs - returns Plaid::User
|
3
|
-
describe '.add_user' do
|
4
|
-
|
5
|
-
Plaid.config do |p|
|
6
|
-
p.customer_id = 'test_id'
|
7
|
-
p.secret = 'test_secret'
|
8
|
-
p.environment_location = 'https://tartan.plaid.com/'
|
9
|
-
end
|
10
|
-
|
11
|
-
context 'has correct credentials for single factor auth, authenticates to the connect level of api access' do
|
12
|
-
user = Plaid.add_user('connect','plaid_test','plaid_good','wells')
|
13
|
-
it { expect(user.accounts.empty?).to be_falsey }
|
14
|
-
end
|
15
|
-
|
16
|
-
context 'has correct credentials for single factor auth, authenticates to the auth level of api access' do
|
17
|
-
user = Plaid.add_user('auth','plaid_test','plaid_good','wells')
|
18
|
-
it { expect(user.accounts[0].numbers.nil?).to be_falsey }
|
19
|
-
end
|
20
|
-
|
21
|
-
context 'has correct credentials for single factor auth, authenticates to the info level of api access' do
|
22
|
-
user = Plaid.add_user('info','plaid_test','plaid_good','wells')
|
23
|
-
it { expect(user.info).to be_truthy }
|
24
|
-
end
|
25
|
-
|
26
|
-
context 'has correct username, but incorrect password for single factor auth under auth level of api access' do
|
27
|
-
it { expect{Plaid.add_user('auth','plaid_test','plaid_bad','wells')}.to raise_error }
|
28
|
-
end
|
29
|
-
|
30
|
-
context 'has incorrect username under auth level of api access' do
|
31
|
-
it { expect{Plaid.add_user('auth','plaid_bad','plaid_bad','wells')}.to raise_error }
|
32
|
-
end
|
33
|
-
|
34
|
-
context 'has correct username, but incorrect password for single factor auth under connect level of api access' do
|
35
|
-
it { expect{Plaid.add_user('connect','plaid_test','plaid_bad','wells')}.to raise_error }
|
36
|
-
end
|
37
|
-
|
38
|
-
context 'has incorrect username under connect level of api access' do
|
39
|
-
it { expect{Plaid.add_user('connect','plaid_bad','plaid_bad','wells')}.to raise_error }
|
40
|
-
end
|
41
|
-
|
42
|
-
context 'has correct username, but incorrect password for single factor auth under info level of api access' do
|
43
|
-
it { expect{Plaid.add_user('info','plaid_test','plaid_bad','wells')}.to raise_error }
|
44
|
-
end
|
45
|
-
|
46
|
-
context 'has incorrect username under info level of api access' do
|
47
|
-
it { expect{Plaid.add_user('info','plaid_bad','plaid_bad','wells')}.to raise_error }
|
48
|
-
end
|
49
|
-
|
50
|
-
context 'enters pin for extra parameter authentication required by certain institutions' do
|
51
|
-
user = Plaid.add_user('connect','plaid_test','plaid_good','usaa','1234')
|
52
|
-
it { expect(user.api_res).to eq 'Requires further authentication' }
|
53
|
-
end
|
54
|
-
|
55
|
-
context 'enters incorrect pin for extra parameter authentication required by certain institutions' do
|
56
|
-
it { expect{Plaid.add_user('connect','plaid_test','plaid_good','usaa','0000')}.to raise_error }
|
57
|
-
end
|
58
|
-
|
59
|
-
context 'has to enter MFA credentials' do
|
60
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_good','bofa')
|
61
|
-
it { expect(user.api_res).to eq 'Requires further authentication' }
|
62
|
-
end
|
63
|
-
|
64
|
-
context 'enters correct information with locked account' do
|
65
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_locked','wells')
|
66
|
-
it { expect(user.api_res).to eq 'User account is locked' }
|
67
|
-
end
|
68
|
-
|
69
|
-
context 'enters webhook option as part of standard call' do
|
70
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_good','wells',{login_only: true, webhook: 'test.com/test.endpoint.aspx'})
|
71
|
-
it { expect(user.accounts.empty?).to be_falsey }
|
72
|
-
end
|
73
|
-
|
74
|
-
context 'enters webhook option as part of mfa required institution authentication' do
|
75
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_good','bofa',{login_only: true, webhook: 'test.com/test.endpoint.aspx'})
|
76
|
-
it { expect(user.api_res).to eq 'Requires further authentication' }
|
77
|
-
end
|
78
|
-
|
79
|
-
context 'requests pending transactions from an institution' do
|
80
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_good','wells',{pending: true})
|
81
|
-
it { expect(user.accounts.empty?).to be_falsey }
|
82
|
-
end
|
83
|
-
|
84
|
-
context 'sets the login only option to true' do
|
85
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_good','wells',{login_only:true})
|
86
|
-
it { expect(user.accounts.empty?).to be_falsey }
|
87
|
-
end
|
88
|
-
|
89
|
-
context 'requests a list of options for code based MFA' do
|
90
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_good','citi',{list: true})
|
91
|
-
it { expect(user.pending_mfa_questions.nil?).to be_falsey }
|
92
|
-
end
|
93
|
-
|
94
|
-
context 'sets a start date for transactions' do
|
95
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_good','wells',{login_only:true, start_date:'10 days ago'})
|
96
|
-
it { expect(user.accounts.empty?).to be_falsey }
|
97
|
-
end
|
98
|
-
|
99
|
-
context 'sets an end date for transactions' do
|
100
|
-
user = Plaid.add_user('connect','plaid_test', 'plaid_good','wells',{login_only:true, end_date: '10 days ago'})
|
101
|
-
it { expect(user.accounts.empty?).to be_falsey }
|
102
|
-
end
|
103
|
-
|
104
|
-
context 'sets start and end dates for transactions' do
|
105
|
-
user = Plaid.add_user('connect','plaid_test','plaid_good','wells',"{'gte':'05/10/2014' , 'lte':'06/10/2014'}")
|
106
|
-
it{ expect(user.transactions).to be_truthy }
|
107
|
-
end
|
108
|
-
|
109
|
-
context 'sets a user with an existing access token' do
|
110
|
-
user = Plaid.set_user('test')
|
111
|
-
it{ expect(user.access_token).to eq('test')}
|
112
|
-
end
|
113
|
-
end
|