plaid 1.4.3 → 1.5.0
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/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
|