plaid 1.7.1 → 2.0.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +15 -0
- data/LICENSE +20 -0
- data/README.md +215 -63
- data/Rakefile +50 -4
- data/UPGRADING.md +45 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/lib/plaid.rb +51 -88
- data/lib/plaid/account.rb +144 -0
- data/lib/plaid/category.rb +62 -0
- data/lib/plaid/client.rb +67 -0
- data/lib/plaid/connector.rb +168 -0
- data/lib/plaid/errors.rb +24 -14
- data/lib/plaid/income.rb +106 -0
- data/lib/plaid/info.rb +65 -0
- data/lib/plaid/institution.rb +240 -0
- data/lib/plaid/risk.rb +34 -0
- data/lib/plaid/transaction.rb +123 -0
- data/lib/plaid/user.rb +430 -0
- data/lib/plaid/version.rb +1 -1
- data/plaid.gemspec +20 -12
- metadata +58 -62
- data/.gitignore +0 -15
- data/.rspec +0 -2
- data/.travis.yml +0 -5
- data/LICENSE.txt +0 -22
- data/PUBLISHING.md +0 -21
- data/lib/plaid/config.rb +0 -19
- data/lib/plaid/connection.rb +0 -109
- data/lib/plaid/models/account.rb +0 -24
- data/lib/plaid/models/category.rb +0 -17
- data/lib/plaid/models/exchange_token_response.rb +0 -11
- data/lib/plaid/models/info.rb +0 -12
- data/lib/plaid/models/institution.rb +0 -22
- data/lib/plaid/models/transaction.rb +0 -24
- data/lib/plaid/models/user.rb +0 -189
- data/spec/plaid/config_spec.rb +0 -67
- data/spec/plaid/connection_spec.rb +0 -191
- data/spec/plaid/error_spec.rb +0 -10
- data/spec/plaid/models/account_spec.rb +0 -37
- data/spec/plaid/models/category_spec.rb +0 -16
- data/spec/plaid/models/institution_spec.rb +0 -19
- data/spec/plaid/models/transaction_spec.rb +0 -28
- data/spec/plaid/models/user_spec.rb +0 -172
- data/spec/plaid_spec.rb +0 -263
- data/spec/spec_helper.rb +0 -14
data/UPGRADING.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
## Upgrading from 1.x to 2.0.0
|
2
|
+
|
3
|
+
Make sure you use Ruby 2.0 or higher.
|
4
|
+
|
5
|
+
Update the `Plaid.config` block:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
Plaid.config do |p|
|
9
|
+
p.client_id = '<<< Plaid provided client ID >>>' # WAS: customer_id
|
10
|
+
p.secret = '<<< Plaid provided secret key >>>' # No change
|
11
|
+
p.env = :tartan # or :api for production # WAS: environment_location
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
Use `Plaid::User.create` instead of `Plaid.add_user` (**NOTE**: parameter order has changed!)
|
16
|
+
|
17
|
+
Use `Plaid::User.load` instead of `Plaid.set_user` (**NOTE**: parameter order has changed!)
|
18
|
+
|
19
|
+
Use `Plaid::User.exchange_token` instead of `Plaid.exchange_token` (**NOTE**: parameter list has changed!)
|
20
|
+
|
21
|
+
Use `Plaid::User.create` or (`.load`) and `Plaid::User#transactions` instead of `Plaid.transactions`.
|
22
|
+
|
23
|
+
Use `Plaid::Institution.all` and `Plaid::Institution.get` instead of `Plaid.institution`.
|
24
|
+
|
25
|
+
Use `Plaid::Category.all` and `Plaid::Category.get` instead of `Plaid.category`.
|
26
|
+
|
27
|
+
`Plaid::Account#institution_type` was renamed to `Plaid::Account#institution`.
|
28
|
+
|
29
|
+
`Plaid::Transaction#account` was renamed to `Plaid::Transaction#account_id`.
|
30
|
+
|
31
|
+
`Plaid::Transaction#date` is a Date, not a String object now.
|
32
|
+
|
33
|
+
`Plaid::Transaction#cat` was removed. Use `Plaid::Transaction#category_hierarchy` and `Plaid::Transaction#category_id` directly.
|
34
|
+
|
35
|
+
`Plaid::Transaction#category` was renamed to `Plaid::Transaction#category_hierarchy`.
|
36
|
+
|
37
|
+
`Plaid::Transaction#pending_transaction` was renamed to `Plaid::Transaction#pending_transaction_id`.
|
38
|
+
|
39
|
+
Use `Plaid::User#mfa_step` instead of `Plaid::User#select_mfa_method` and `Plaid::User#mfa_authentication`.
|
40
|
+
|
41
|
+
`Plaid::User#permit?` was removed. You don't need this.
|
42
|
+
|
43
|
+
`Plaid::User.delete_user` was renamed to `Plaid::User.delete`.
|
44
|
+
|
45
|
+
**NOTE** that Symbols are now consistently used instead of Strings as product names, keys in hashes, etc. Look at the docs, they have all the examples.
|
data/bin/console
ADDED
data/bin/setup
ADDED
data/lib/plaid.rb
CHANGED
@@ -1,100 +1,63 @@
|
|
1
1
|
require 'plaid/version'
|
2
|
-
require 'plaid/config'
|
3
2
|
require 'plaid/errors'
|
4
|
-
|
5
|
-
require 'plaid/
|
6
|
-
require 'plaid/
|
7
|
-
require 'plaid/
|
8
|
-
require 'plaid/
|
9
|
-
|
10
|
-
require '
|
11
|
-
|
3
|
+
require 'plaid/connector'
|
4
|
+
require 'plaid/category'
|
5
|
+
require 'plaid/institution'
|
6
|
+
require 'plaid/user'
|
7
|
+
require 'plaid/transaction'
|
8
|
+
require 'plaid/info'
|
9
|
+
require 'plaid/income'
|
10
|
+
require 'plaid/client'
|
11
|
+
|
12
|
+
require 'uri'
|
13
|
+
|
14
|
+
# Public: The Plaid namespace.
|
12
15
|
module Plaid
|
13
|
-
|
16
|
+
# Public: Available Plaid products.
|
17
|
+
PRODUCTS = %i(connect auth info income risk).freeze
|
14
18
|
|
15
|
-
class <<
|
16
|
-
#
|
17
|
-
|
19
|
+
class <<self
|
20
|
+
# Public: The default Client.
|
21
|
+
attr_accessor :client
|
18
22
|
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
# api_level, username, password, type
|
23
|
-
# TODO: Rename this to something more descriptive for 2.0, such as 'create_user`
|
24
|
-
def add_user(api_level, username, password, type, pin = nil, options = nil)
|
25
|
-
payload = { username: username, password: password, type: type }
|
26
|
-
|
27
|
-
payload[:pin] = pin if pin
|
28
|
-
payload[:options] = options.is_a?(Hash) ? JSON.generate(options) : options if options
|
29
|
-
|
30
|
-
res = Connection.post(api_level, payload)
|
31
|
-
User.build(res, api_level)
|
32
|
-
end
|
33
|
-
|
34
|
-
# API: public
|
35
|
-
# Exchange a Plaid Link public_token for a Plaid access_token
|
36
|
-
# Required parameters:
|
37
|
-
# public_token
|
38
|
-
# account_id (optional)
|
39
|
-
def exchange_token(public_token, account_id = nil)
|
40
|
-
payload = { public_token: public_token }
|
41
|
-
# include the account id, if set
|
42
|
-
payload[:account_id] = account_id if account_id
|
43
|
-
|
44
|
-
res = Connection.post('exchange_token', payload)
|
45
|
-
ExchangeTokenResponse.new(res)
|
46
|
-
end
|
47
|
-
|
48
|
-
# API: public
|
49
|
-
# Use this to restore a user from Plaid based upon the access token
|
50
|
-
# TODO: Rename this to something more descriptive for 2.0, such as 'get_user'
|
51
|
-
def set_user(token, api_levels=[], institution_type=nil)
|
52
|
-
_user = User.new
|
53
|
-
_user.access_token = fully_qualified_token(token, institution_type)
|
54
|
-
_user.permissions = api_levels
|
55
|
-
api_levels.each { |l| _user.get(l) }
|
56
|
-
_user
|
57
|
-
end
|
23
|
+
# Public: The Integer read timeout for requests to Plaid HTTP API.
|
24
|
+
# Should be specified in seconds. Default value is 120 (2 minutes).
|
25
|
+
attr_accessor :read_timeout
|
58
26
|
|
59
|
-
#
|
60
|
-
# Given an access code and query options, use this to get a dataset of
|
61
|
-
# transactions and accounts for # a given user. See /connect/get endpoint
|
27
|
+
# Public: A helper function to ease configuration.
|
62
28
|
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# Examples
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# API: public
|
80
|
-
# Returns the fully-qualified token based upon type
|
81
|
-
# Note: Don't see this documented in the Plaid API docs, need to investigate this
|
82
|
-
def fully_qualified_token(token, institution_type)
|
83
|
-
institution_type.nil? ? token : token + '_' + institution_type
|
84
|
-
end
|
85
|
-
|
86
|
-
# API: public
|
87
|
-
# Builds an institution object and returns when the institution details exist
|
88
|
-
def institution(id = nil)
|
89
|
-
res = Connection.get('institutions', id)
|
90
|
-
id.nil? ? Institution.all(res) : Institution.new(res)
|
29
|
+
# Yields self.
|
30
|
+
#
|
31
|
+
# Examples
|
32
|
+
#
|
33
|
+
# Plaid.configure do |p|
|
34
|
+
# p.client_id = 'Plaid provided client ID here'
|
35
|
+
# p.secret = 'Plaid provided secret key here'
|
36
|
+
# p.env = :tartan
|
37
|
+
# p.read_timeout = 300 # it's 5 minutes, yay!
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Returns nothing.
|
41
|
+
def config
|
42
|
+
client = Client.new
|
43
|
+
yield client
|
44
|
+
self.client = client
|
91
45
|
end
|
92
46
|
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
47
|
+
# Internal: Symbolize keys (and values) for a hash.
|
48
|
+
#
|
49
|
+
# hash - The Hash with string keys (or nil).
|
50
|
+
# values - The Boolean flag telling the function to symbolize values
|
51
|
+
# as well.
|
52
|
+
#
|
53
|
+
# Returns a Hash with keys.to_sym (or nil if hash is nil).
|
54
|
+
def symbolize_hash(hash, values: false)
|
55
|
+
return unless hash
|
56
|
+
return hash.map { |h| symbolize_hash(h) } if hash.is_a?(Array)
|
57
|
+
|
58
|
+
hash.each_with_object({}) do |(k, v), memo|
|
59
|
+
memo[k.to_sym] = values ? v.to_sym : v
|
60
|
+
end
|
98
61
|
end
|
99
62
|
end
|
100
63
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'plaid/risk'
|
2
|
+
|
3
|
+
module Plaid
|
4
|
+
# Public: Representation of user account data.
|
5
|
+
class Account
|
6
|
+
# Public: The String unique ID of the account. E.g.
|
7
|
+
# "QPO8Jo8vdDHMepg41PBwckXm4KdK1yUdmXOwK".
|
8
|
+
attr_reader :id
|
9
|
+
|
10
|
+
# Public: The String account ID unique to the accounts of a particular
|
11
|
+
# access token. E.g. "KdDjmojBERUKx3JkDd9RuxA5EvejA4SENO4AA".
|
12
|
+
attr_reader :item_id
|
13
|
+
|
14
|
+
# Public: The Float value of the current balance for the account.
|
15
|
+
attr_reader :current_balance
|
16
|
+
|
17
|
+
# Public: The Float value of the available balance for the account.
|
18
|
+
#
|
19
|
+
# The Available Balance is the Current Balance less any outstanding holds
|
20
|
+
# or debits that have not yet posted to the account. Note that not all
|
21
|
+
# institutions calculate the Available Balance. In the case that Available
|
22
|
+
# Balance is unavailable from the institution, Plaid will either return an
|
23
|
+
# Available Balance value of null or only return a Current Balance.
|
24
|
+
attr_reader :available_balance
|
25
|
+
|
26
|
+
# Public: The Symbol institution type, e.g. :wells.
|
27
|
+
attr_reader :institution
|
28
|
+
|
29
|
+
# Public: The Hash with additional information pertaining to the account
|
30
|
+
# such as the limit, name, or last few digits of the account number. E.g.
|
31
|
+
# {"name": "Plaid Savings", "number": "9606" }.
|
32
|
+
attr_reader :meta
|
33
|
+
|
34
|
+
# Public: The Symbol account type. One of :depository, :credit, :loan,
|
35
|
+
# :mortgage, :brokerage, and :other.
|
36
|
+
attr_reader :type
|
37
|
+
|
38
|
+
# Public: The String account subtype. E.g. "savings".
|
39
|
+
#
|
40
|
+
# Read more about subtypes in the Plaid API docs.
|
41
|
+
attr_reader :subtype
|
42
|
+
|
43
|
+
# Public: The Hash with account and routing numbers for the account.
|
44
|
+
#
|
45
|
+
# This attribute would be nil unless you used Auth product for the user.
|
46
|
+
#
|
47
|
+
# The Hash contains Symbol keys and String values. E.g.
|
48
|
+
# {routing: "021000021", account: "9900009606", wireRouting: "021000021"}.
|
49
|
+
attr_reader :numbers
|
50
|
+
|
51
|
+
# Public: The Risk information associated with the account.
|
52
|
+
#
|
53
|
+
# This attribute would be nil unless you used Risk product for the user.
|
54
|
+
attr_reader :risk
|
55
|
+
|
56
|
+
def initialize(hash)
|
57
|
+
@id = hash['_id']
|
58
|
+
@item_id = hash['_item']
|
59
|
+
@meta = hash['meta']
|
60
|
+
@type = hash['type'].to_sym
|
61
|
+
@subtype = hash['subtype']
|
62
|
+
@institution = hash['institution_type'].to_sym
|
63
|
+
|
64
|
+
unless (bal = hash['balance']).nil?
|
65
|
+
@available_balance = bal['available']
|
66
|
+
@current_balance = bal['current']
|
67
|
+
end
|
68
|
+
|
69
|
+
if (risk = hash['risk'])
|
70
|
+
@risk = Plaid::Risk.new(risk)
|
71
|
+
end
|
72
|
+
|
73
|
+
@numbers = Plaid.symbolize_hash(hash['numbers'])
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: Get a String representation of the account.
|
77
|
+
#
|
78
|
+
# Returns a String.
|
79
|
+
def inspect
|
80
|
+
"#<Plaid::Account id=#{id.inspect}, type=#{type.inspect}, " \
|
81
|
+
"name=#{name.inspect}, institution=#{institution.inspect}>"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: Get a String representation of the account.
|
85
|
+
#
|
86
|
+
# Returns a String.
|
87
|
+
alias to_s inspect
|
88
|
+
|
89
|
+
# Public: Get the account name.
|
90
|
+
#
|
91
|
+
# The name is obtained from #meta Hash.
|
92
|
+
#
|
93
|
+
# Returns the String name.
|
94
|
+
def name
|
95
|
+
meta && meta['name']
|
96
|
+
end
|
97
|
+
|
98
|
+
# Internal: Merge account information.
|
99
|
+
#
|
100
|
+
# accounts - The Array of Account instances.
|
101
|
+
# new_accounts - The Array of Account instances.
|
102
|
+
#
|
103
|
+
# Returns accounts.
|
104
|
+
def self.merge(accounts, new_accounts)
|
105
|
+
# Index accounts by their ID.
|
106
|
+
#
|
107
|
+
# Same as index = accounts.index_by(&:id) in ActiveSupport.
|
108
|
+
index = Hash[accounts.map { |a| [a.id, a] }]
|
109
|
+
|
110
|
+
new_accounts.each do |acc|
|
111
|
+
if (old_acc = index[acc.id])
|
112
|
+
old_acc.update_from(acc)
|
113
|
+
else
|
114
|
+
accounts << acc
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
accounts
|
119
|
+
end
|
120
|
+
|
121
|
+
# Internal: Update account information.
|
122
|
+
#
|
123
|
+
# All fields which are not nil in another are copied to self.
|
124
|
+
#
|
125
|
+
# another - The Account instance with new information.
|
126
|
+
#
|
127
|
+
# Returns self.
|
128
|
+
def update_from(another)
|
129
|
+
# A sanity check. Nobody would want to update information from totally
|
130
|
+
# different account!
|
131
|
+
if id != another.id
|
132
|
+
raise ArgumentError, 'Plaid::Account#update_from: id != another.id!'
|
133
|
+
end
|
134
|
+
|
135
|
+
%i(item_id meta name type subtype institution available_balance
|
136
|
+
current_balance numbers risk).each do |field|
|
137
|
+
value = another.send(field)
|
138
|
+
instance_variable_set("@#{field}", value) unless value.nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
self
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Plaid
|
2
|
+
# Public: A class which encapsulates information about a Plaid category.
|
3
|
+
class Category
|
4
|
+
# Public: The String category ID, e.g. "21010006".
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
# Public: The Symbol category type. One of :special, :place, :digital.
|
8
|
+
attr_reader :type
|
9
|
+
|
10
|
+
# Public: The Array of String hierarchy. E.g.
|
11
|
+
# ["Food and Drink", "Nightlife"].
|
12
|
+
attr_reader :hierarchy
|
13
|
+
|
14
|
+
# Internal: Initialize a Category with given fields.
|
15
|
+
def initialize(fields)
|
16
|
+
@type = fields['type'].to_sym
|
17
|
+
@hierarchy = fields['hierarchy']
|
18
|
+
@id = fields['id']
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Get a String representation of Category.
|
22
|
+
#
|
23
|
+
# Returns a String.
|
24
|
+
def inspect
|
25
|
+
"#<Plaid::Category id=#{id.inspect}, type=#{type.inspect}, " \
|
26
|
+
"hierarchy=#{hierarchy.inspect}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Get a String representation of Category.
|
30
|
+
#
|
31
|
+
# Returns a String.
|
32
|
+
alias to_s inspect
|
33
|
+
|
34
|
+
# Public: Get information about all available categories.
|
35
|
+
#
|
36
|
+
# Does a GET /categories call.
|
37
|
+
#
|
38
|
+
# client - The Plaid::Client instance used to connect
|
39
|
+
# (default: Plaid.client).
|
40
|
+
#
|
41
|
+
# Returns an Array of Category instances.
|
42
|
+
def self.all(client: nil)
|
43
|
+
Connector.new(:categories, client: client).get.map do |category_data|
|
44
|
+
new(category_data)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Get information about a given category.
|
49
|
+
#
|
50
|
+
# Does a GET /categories/:id call.
|
51
|
+
#
|
52
|
+
# id - the String category ID (e.g. "17001013").
|
53
|
+
# client - The Plaid::Client instance used to connect
|
54
|
+
# (default: Plaid.client).
|
55
|
+
#
|
56
|
+
# Returns a Category instance or raises Plaid::NotFoundError if category
|
57
|
+
# with given id is not found.
|
58
|
+
def self.get(id, client: nil)
|
59
|
+
new Connector.new(:categories, id, client: client).get
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/plaid/client.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module Plaid
|
2
|
+
# Public: A class encapsulating client_id, secret, and Plaid API URL.
|
3
|
+
class Client
|
4
|
+
# Public: The String Plaid account client ID to authenticate requests.
|
5
|
+
attr_accessor :client_id
|
6
|
+
|
7
|
+
# Public: The String Plaid account secret to authenticate requests.
|
8
|
+
attr_accessor :secret
|
9
|
+
|
10
|
+
# Public: Plaid environment, i.e. String base URL of the API site.
|
11
|
+
#
|
12
|
+
# E.g. 'https://tartan.plaid.com'.
|
13
|
+
attr_reader :env
|
14
|
+
|
15
|
+
# Public: Set Plaid environment to use.
|
16
|
+
#
|
17
|
+
# env - The Symbol (:tartan, :api), or a full String URL like
|
18
|
+
# 'https://tartan.plaid.com'.
|
19
|
+
def env=(env)
|
20
|
+
case env
|
21
|
+
when :tartan, :api
|
22
|
+
@env = "https://#{env}.plaid.com/"
|
23
|
+
when String
|
24
|
+
begin
|
25
|
+
URI.parse(env)
|
26
|
+
@env = env
|
27
|
+
rescue
|
28
|
+
raise ArgumentError, 'Invalid URL in Plaid::Client.env' \
|
29
|
+
" (#{env.inspect}). " \
|
30
|
+
'Specify either Symbol (:tartan, :api), or a ' \
|
31
|
+
"full URL, like 'https://tartan.plaid.com'"
|
32
|
+
end
|
33
|
+
else
|
34
|
+
raise ArgumentError, 'Invalid value for Plaid::Client.env' \
|
35
|
+
" (#{env.inspect}): " \
|
36
|
+
'must be :tartan, :api, or a full URL, ' \
|
37
|
+
"e.g. 'https://tartan.plaid.com'"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Construct a Client instance.
|
42
|
+
#
|
43
|
+
# env - The Symbol (:tartan, :api), or a full String URL like
|
44
|
+
# 'https://tartan.plaid.com'.
|
45
|
+
# client_id - The String Plaid account client ID to authenticate requests.
|
46
|
+
# secret - The String Plaid account secret to authenticate requests.
|
47
|
+
def initialize(env: nil, client_id: nil, secret: nil)
|
48
|
+
env && self.env = env
|
49
|
+
self.client_id = client_id
|
50
|
+
self.secret = secret
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Check if client_id is configured.
|
54
|
+
#
|
55
|
+
# Returns true if it is.
|
56
|
+
def client_id_configured?
|
57
|
+
@client_id.is_a?(String) && !@client_id.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: Check if client_id is configured.
|
61
|
+
#
|
62
|
+
# Returns true if it is.
|
63
|
+
def secret_configured?
|
64
|
+
@secret.is_a?(String) && !@secret.empty?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|