plaid 1.7.1 → 2.0.0.alpha

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