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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/CONTRIBUTING.md +15 -0
  4. data/LICENSE +20 -0
  5. data/README.md +215 -63
  6. data/Rakefile +50 -4
  7. data/UPGRADING.md +45 -0
  8. data/bin/console +13 -0
  9. data/bin/setup +8 -0
  10. data/lib/plaid.rb +51 -88
  11. data/lib/plaid/account.rb +144 -0
  12. data/lib/plaid/category.rb +62 -0
  13. data/lib/plaid/client.rb +67 -0
  14. data/lib/plaid/connector.rb +168 -0
  15. data/lib/plaid/errors.rb +24 -14
  16. data/lib/plaid/income.rb +106 -0
  17. data/lib/plaid/info.rb +65 -0
  18. data/lib/plaid/institution.rb +240 -0
  19. data/lib/plaid/risk.rb +34 -0
  20. data/lib/plaid/transaction.rb +123 -0
  21. data/lib/plaid/user.rb +430 -0
  22. data/lib/plaid/version.rb +1 -1
  23. data/plaid.gemspec +20 -12
  24. metadata +58 -62
  25. data/.gitignore +0 -15
  26. data/.rspec +0 -2
  27. data/.travis.yml +0 -5
  28. data/LICENSE.txt +0 -22
  29. data/PUBLISHING.md +0 -21
  30. data/lib/plaid/config.rb +0 -19
  31. data/lib/plaid/connection.rb +0 -109
  32. data/lib/plaid/models/account.rb +0 -24
  33. data/lib/plaid/models/category.rb +0 -17
  34. data/lib/plaid/models/exchange_token_response.rb +0 -11
  35. data/lib/plaid/models/info.rb +0 -12
  36. data/lib/plaid/models/institution.rb +0 -22
  37. data/lib/plaid/models/transaction.rb +0 -24
  38. data/lib/plaid/models/user.rb +0 -189
  39. data/spec/plaid/config_spec.rb +0 -67
  40. data/spec/plaid/connection_spec.rb +0 -191
  41. data/spec/plaid/error_spec.rb +0 -10
  42. data/spec/plaid/models/account_spec.rb +0 -37
  43. data/spec/plaid/models/category_spec.rb +0 -16
  44. data/spec/plaid/models/institution_spec.rb +0 -19
  45. data/spec/plaid/models/transaction_spec.rb +0 -28
  46. data/spec/plaid/models/user_spec.rb +0 -172
  47. data/spec/plaid_spec.rb +0 -263
  48. 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
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'plaid'
5
+ require 'pry'
6
+
7
+ Plaid.config do |p|
8
+ p.env = :tartan
9
+ p.client_id = ENV['PLAID_CLIENT_ID']
10
+ p.secret = ENV['PLAID_SECRET']
11
+ end
12
+
13
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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/models/user'
6
- require 'plaid/models/institution'
7
- require 'plaid/models/category'
8
- require 'plaid/models/exchange_token_response'
9
-
10
- require 'json'
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
- autoload :Connection, 'plaid/connection'
16
+ # Public: Available Plaid products.
17
+ PRODUCTS = %i(connect auth info income risk).freeze
14
18
 
15
- class << self
16
- # Configures the gem with the public, private, and environment vars
17
- include Configure
19
+ class <<self
20
+ # Public: The default Client.
21
+ attr_accessor :client
18
22
 
19
- # API: public
20
- # Use this to create a new Plaid user
21
- # Required parameters:
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
- # API: public
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
- # Returns a User object with accounts and transactions within
64
- # the date range given
65
- # Examples:
66
- # Plaid.transactions 'test_wells', account: 'QPO8Jo8vdDHMepg41PBwckXm4KdK1yUdmXOwK'
67
- def transactions(token, options = {})
68
- _user = User.new
69
- _user.access_token = token
70
- _user.permit! 'connect'
71
-
72
- # TODO: For 2.0, submit all data as JSON
73
- options = JSON.generate(options) if options.kind_of?(Hash)
74
-
75
- _user.get_connect(options: options)
76
- _user
77
- end
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
- # API: public
94
- # Builds an category object and returns when the category details exist
95
- def category(id = nil)
96
- res = Connection.get('categories', id)
97
- id.nil? ? Category.all(res) : Category.new(res)
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
@@ -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