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
@@ -0,0 +1,168 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'multi_json'
4
+
5
+ module Plaid
6
+ # Internal: A class encapsulating HTTP requests to the Plaid API servers
7
+ class Connector
8
+ attr_reader :uri, :http, :request, :response, :body
9
+
10
+ # Internal: Default read timeout for HTTP calls.
11
+ DEFAULT_TIMEOUT = 120
12
+
13
+ # Internal: Prepare to run request.
14
+ #
15
+ # path - The String path without leading slash. E.g. 'connect'
16
+ # subpath - The String subpath. E.g. 'get'
17
+ # auth - The Boolean flag indicating that client_id and secret should be
18
+ # included into the request payload.
19
+ # client - The Plaid::Client instance used to connect
20
+ # (default: Plaid.client).
21
+ def initialize(path, subpath = nil, auth: false, client: nil)
22
+ @auth = auth
23
+ @client = client || Plaid.client
24
+ verify_configuration
25
+
26
+ path = File.join(@client.env, path.to_s)
27
+ path = File.join(path, subpath.to_s) if subpath
28
+
29
+ @uri = URI.parse(path)
30
+
31
+ @http = Net::HTTP.new(@uri.host, @uri.port)
32
+ @http.use_ssl = true
33
+
34
+ @http.read_timeout = Plaid.read_timeout || DEFAULT_TIMEOUT
35
+ end
36
+
37
+ # Internal: Run GET request.
38
+ #
39
+ # Returns the parsed JSON response body.
40
+ def get(payload = {})
41
+ payload = with_credentials(payload)
42
+
43
+ @uri.query = URI.encode_www_form(payload) unless payload.empty?
44
+
45
+ run Net::HTTP::Get.new(@uri)
46
+ end
47
+
48
+ # Internal: Run POST request.
49
+ #
50
+ # Adds client_id and secret to the payload if @auth is true.
51
+ #
52
+ # payload - The Hash with data.
53
+ #
54
+ # Returns the parsed JSON response body.
55
+ def post(payload)
56
+ post_like payload, Net::HTTP::Post.new(@uri.path)
57
+ end
58
+
59
+ # Internal: Run PATCH request.
60
+ #
61
+ # Adds client_id and secret to the payload if @auth is true.
62
+ #
63
+ # payload - The Hash with data.
64
+ #
65
+ # Returns the parsed JSON response body.
66
+ def patch(payload)
67
+ post_like payload, Net::HTTP::Patch.new(@uri.path)
68
+ end
69
+
70
+ # Internal: Run DELETE request.
71
+ #
72
+ # Adds client_id and secret to the payload if @auth is true.
73
+ #
74
+ # payload - The Hash with data.
75
+ #
76
+ # Returns the parsed JSON response body.
77
+ def delete(payload)
78
+ post_like payload, Net::HTTP::Delete.new(@uri.path)
79
+ end
80
+
81
+ # Internal: Check if MFA response received.
82
+ #
83
+ # Returns true if response has code 201.
84
+ def mfa?
85
+ @response.is_a?(Net::HTTPCreated)
86
+ end
87
+
88
+ private
89
+
90
+ # Internal: Run the request and process the response.
91
+ #
92
+ # Returns the parsed JSON body or raises an appropriate exception (a
93
+ # descendant of Plaid::PlaidError).
94
+ def run(request)
95
+ @request = request
96
+ @response = http.request(@request)
97
+
98
+ # All responses are expected to have a JSON body, so we always parse,
99
+ # not looking at the status code.
100
+ @body = MultiJson.load(@response.body)
101
+
102
+ case @response
103
+ when Net::HTTPSuccess, Net::HTTPCreated
104
+ @body
105
+ else
106
+ raise_error
107
+ end
108
+ end
109
+
110
+ # Internal: Run POST-like request.
111
+ #
112
+ # payload - The Hash with posted data.
113
+ # request - The Net::HTTPGenericRequest descendant instance.
114
+ def post_like(payload, request)
115
+ request.set_form_data(with_credentials(payload))
116
+
117
+ run request
118
+ end
119
+
120
+ # Internal: Raise an error with the class depending on @response.
121
+ #
122
+ # Returns an Array with arguments.
123
+ def raise_error
124
+ klass = case @response
125
+ when Net::HTTPBadRequest then Plaid::BadRequestError
126
+ when Net::HTTPUnauthorized then Plaid::UnauthorizedError
127
+ when Net::HTTPPaymentRequired then Plaid::RequestFailedError
128
+ when Net::HTTPNotFound then Plaid::NotFoundError
129
+ else
130
+ Plaid::ServerError
131
+ end
132
+
133
+ raise klass.new(body['code'], body['message'], body['resolve'])
134
+ end
135
+
136
+ # Internal: Verify that Plaid environment is properly configured.
137
+ #
138
+ # Raises NotConfiguredError if anything is wrong.
139
+ def verify_configuration
140
+ raise_not_configured(:env, auth: false) unless @client.env
141
+ return unless @auth
142
+
143
+ !@client.client_id_configured? && raise_not_configured(:client_id)
144
+ !@client.secret_configured? && raise_not_configured(:secret)
145
+ end
146
+
147
+ # Internal: Raise a NotConfiguredError exception with proper message.
148
+ def raise_not_configured(field, auth: true)
149
+ message = "You must set Plaid::Client.#{field} before using any methods"
150
+ message << ' which require authentication' if auth
151
+ message << "! It's current value is #{@client.send(field).inspect}. " \
152
+ 'E.g. add a Plaid.config do .. end block somewhere in the ' \
153
+ 'initialization code of your program.'
154
+
155
+ raise NotConfiguredError, message
156
+ end
157
+
158
+ # Internal: Merge credentials to the payload if needed.
159
+ def with_credentials(payload)
160
+ if @auth
161
+ payload.merge(client_id: @client.client_id,
162
+ secret: @client.secret)
163
+ else
164
+ payload
165
+ end
166
+ end
167
+ end
168
+ end
data/lib/plaid/errors.rb CHANGED
@@ -1,27 +1,37 @@
1
1
  module Plaid
2
+ # Public: Exception to throw when there are configuration problems
3
+ class NotConfiguredError < StandardError; end
4
+
5
+ # Internal: Base class for Plaid errors
2
6
  class PlaidError < StandardError
3
- attr_reader :code
4
- attr_reader :resolve
5
-
7
+ attr_reader :code, :resolve
8
+
9
+ # Internal: Initialize a error with proper attributes.
10
+ #
11
+ # code - The Integer code (e.g. 1501).
12
+ # message - The String message, describing the error.
13
+ # resolve - The String description how to fix the error.
6
14
  def initialize(code, message, resolve)
7
- super(message)
8
15
  @code = code
9
16
  @resolve = resolve
17
+
18
+ super "Code #{@code}: #{message}. #{resolve}"
10
19
  end
11
20
  end
12
21
 
13
- class BadRequest < PlaidError
14
- end
22
+ # Public: Exception which is thrown when Plaid API returns a 400 response.
23
+ class BadRequestError < PlaidError; end
15
24
 
16
- class Unauthorized < PlaidError
17
- end
25
+ # Public: Exception which is thrown when Plaid API returns a 401 response.
26
+ class UnauthorizedError < PlaidError; end
18
27
 
19
- class RequestFailed < PlaidError
20
- end
28
+ # Public: Exception which is thrown when Plaid API returns a 402 response.
29
+ class RequestFailedError < PlaidError; end
21
30
 
22
- class NotFound < PlaidError
23
- end
31
+ # Public: Exception which is thrown when Plaid API returns a 404 response.
32
+ class NotFoundError < PlaidError; end
24
33
 
25
- class ServerError < PlaidError
26
- end
34
+ # Public: Exception which is thrown when Plaid API returns a response which
35
+ # is neither 2xx, nor 4xx. Presumably 5xx.
36
+ class ServerError < PlaidError; end
27
37
  end
@@ -0,0 +1,106 @@
1
+ module Plaid
2
+ # Public: Representation of Income data.
3
+ class Income
4
+ # Public: The class encapsulating an income stream.
5
+ class Stream
6
+ # Public: The Float monthly income associated with the income stream.
7
+ # E.g. 5700.
8
+ attr_reader :monthly_income
9
+
10
+ # Public: The Float representation of Plaid's confidence in the income
11
+ # data associated with this particular income stream, with 0 being
12
+ # the lowest confidence and 1 being the highest. E.g. 0.9.
13
+ attr_reader :confidence
14
+
15
+ # Public: The Integer extent of data found for this income stream.
16
+ # E.g. 314.
17
+ attr_reader :days
18
+
19
+ # Public: The String name of the entity associated with this income
20
+ # stream. E.g. "APPLE INC".
21
+ attr_reader :name
22
+
23
+ # Internal: Initialize a Stream.
24
+ #
25
+ # fields - The Hash with fields (keys are Strings).
26
+ def initialize(fields)
27
+ @monthly_income = fields['monthly_income']
28
+ @confidence = fields['confidence']
29
+ @days = fields['days']
30
+ @name = fields['name']
31
+ end
32
+
33
+ # Public: Get a String representation of the Stream.
34
+ #
35
+ # Returns a String.
36
+ def inspect
37
+ "#<Plaid::Income::Stream name=#{name.inspect}, " \
38
+ "monthly_income=#{monthly_income.inspect}, " \
39
+ "confidence=#{confidence.inspect}, " \
40
+ "days=#{days.inspect}>"
41
+ end
42
+
43
+ # Public: Get a String representation of the Stream.
44
+ #
45
+ # Returns a String.
46
+ alias to_s inspect
47
+ end
48
+
49
+ # Public: The Array of Stream.
50
+ attr_reader :income_streams
51
+
52
+ # Public: The Numeric sum of user's income over the past 365 days. If Plaid
53
+ # has less than 365 days of data this will be less than a full year income.
54
+ # E.g. 67000.
55
+ attr_reader :last_year_income
56
+
57
+ # Public: The last_year_income interpolated to value before taxes. This is
58
+ # the minimum pre-tax salary that assumes a filing status of single with
59
+ # zero dependents. E.g. 73700.
60
+ attr_reader :last_year_income_before_tax
61
+
62
+ # Public: The Numeric user's income extrapolated over a year based on
63
+ # current, active income streams. Income streams become inactive if they
64
+ # have not recurred for more than two cycles. For example, if a weekly
65
+ # paycheck hasn't been seen for the past two weeks, it is no longer active.
66
+ # E.g. 69800.
67
+ attr_reader :projected_yearly_income
68
+
69
+ # Public: The projected_yearly_income interpolated to value before taxes.
70
+ # This is the minimum pre-tax salary that assumes a filing status of single
71
+ # with zero dependents. E.g. 75600.
72
+ attr_reader :projected_yearly_income_before_tax
73
+
74
+ # Public: The Integer max number of income streams present at the same time
75
+ # over the past 365 days. E.g. 314.
76
+ attr_reader :max_number_of_overlapping_income_streams
77
+
78
+ # Public: The Integer total number of distinct income streams received over
79
+ # the past 365 days. E.g. 2.
80
+ attr_reader :number_of_income_streams
81
+
82
+ def initialize(fields)
83
+ @income_streams = fields['income_streams'].map { |is| Stream.new(is) }
84
+
85
+ %w(last_year_income last_year_income_before_tax projected_yearly_income
86
+ projected_yearly_income_before_tax number_of_income_streams
87
+ max_number_of_overlapping_income_streams).each do |field|
88
+ instance_variable_set "@#{field}", fields[field]
89
+ end
90
+ end
91
+
92
+ # Public: Get a String representation of Income.
93
+ #
94
+ # Returns a String.
95
+ def inspect
96
+ "#<Plaid::Income last_year_income=#{last_year_income.inspect}, " \
97
+ "projected_yearly_income=#{projected_yearly_income.inspect}, " \
98
+ "number_of_income_streams=#{number_of_income_streams.inspect}, ...>"
99
+ end
100
+
101
+ # Public: Get a String representation of Income.
102
+ #
103
+ # Returns a String.
104
+ alias to_s inspect
105
+ end
106
+ end
data/lib/plaid/info.rb ADDED
@@ -0,0 +1,65 @@
1
+ module Plaid
2
+ # Public: Representation of user information.
3
+ class Info
4
+ # Public: The Array of String user names.
5
+ #
6
+ # E.g. ["Frodo Baggins", "Samwise Gamgee"].
7
+ attr_reader :names
8
+
9
+ # Public: The Array of Hash user emails information.
10
+ #
11
+ # E.g. [{data: "frodo.baggins@plaid.com", type: :primary}, ...]
12
+ attr_reader :emails
13
+
14
+ # Public: The Array of Hash user phone number information.
15
+ #
16
+ # E.g. [{data: "111-222-3456", type: :work, primary: false},
17
+ # {data: "123-456-7891", type: :mobile, primary: true}]
18
+ attr_reader :phone_numbers
19
+
20
+ # Public: The Array of Hash user address information.
21
+ #
22
+ # E.g. [{ primary: true, data: {
23
+ # "street" => "1 Hobbit Way",
24
+ # "city" => "The Shire",
25
+ # "state" => "CA",
26
+ # "zip" => "94108"}}]
27
+ attr_reader :addresses
28
+
29
+ # Internal: Construct the Info object.
30
+ def initialize(fields)
31
+ @names = fields['names']
32
+ @emails = fields['emails'].map do |h|
33
+ symbolize_values Plaid.symbolize_hash(h), :type
34
+ end
35
+
36
+ @phone_numbers = fields['phone_numbers'].map do |h|
37
+ symbolize_values Plaid.symbolize_hash(h), :type
38
+ end
39
+
40
+ @addresses = fields['addresses'].map do |h|
41
+ Plaid.symbolize_hash(h)
42
+ end
43
+ end
44
+
45
+ # Public: Get a String representation of Info object.
46
+ #
47
+ # Returns a String.
48
+ def inspect
49
+ "#<Plaid::Info names=#{names.inspect}, ...>"
50
+ end
51
+
52
+ # Public: Get a String representation of Info object.
53
+ #
54
+ # Returns a String.
55
+ alias to_s inspect
56
+
57
+ private
58
+
59
+ def symbolize_values(hash, *keys)
60
+ hash.each do |k, v|
61
+ hash[k] = v.to_sym if keys.include?(k)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,240 @@
1
+ module Plaid
2
+ # Public: A class encapsulating information about a Financial Institution
3
+ # supported by Plaid.
4
+ class Institution
5
+ # Public: The String institution ID. E.g. "5301a93ac140de84910000e0".
6
+ attr_reader :id
7
+
8
+ # Public: The String institution name. E.g. "Bank of America".
9
+ attr_reader :name
10
+
11
+ # Public: The String institution shortname, or "type" per Plaid API docs.
12
+ # E.g. "bofa".
13
+ attr_reader :type
14
+
15
+ # Public: The Boolean flag telling if the institution requires MFA.
16
+ attr_reader :has_mfa
17
+
18
+ # Public: Returns true if the institution requires MFA.
19
+ alias has_mfa? has_mfa
20
+
21
+ # Public: The Hash with MFA options available. E.g. ["code", "list",
22
+ # "questions(3)"]. This means that you are allowed to request a list of
23
+ # possible MFA options, use code-based MFA and questions MFA (there are 3
24
+ # questions).
25
+ attr_reader :mfa
26
+
27
+ # Public: The Hash with credential labels, how they are exactly named by
28
+ # the institution. E.g. {"username": "Online ID", "password": "Password"}.
29
+ attr_reader :credentials
30
+
31
+ # Public: An Array with Symbol product names supported by the institution.
32
+ # E.g. [:connect, :auth, :balance, :info, :income, :risk]. See
33
+ # Plaid::PRODUCTS.
34
+ attr_reader :products
35
+
36
+ # Internal: Initialize an Institution with given fields.
37
+ def initialize(fields)
38
+ @id = fields['id']
39
+ @name = fields['name']
40
+ @type = fields['type']
41
+ @has_mfa = fields['has_mfa']
42
+ @mfa = fields['mfa']
43
+ @credentials = fields['credentials']
44
+ @products = fields['products'].map(&:to_sym)
45
+ end
46
+
47
+ # Public: Get a String representation of the institution.
48
+ #
49
+ # Returns a String.
50
+ def inspect
51
+ "#<Plaid::Institution id=#{id.inspect}, type=#{type.inspect}, " \
52
+ "name=#{name.inspect}>"
53
+ end
54
+
55
+ # Public: Get a String representation of the institution.
56
+ #
57
+ # Returns a String.
58
+ alias to_s inspect
59
+
60
+ # Public: Get information about the Financial Institutions currently
61
+ # supported by Plaid.
62
+ #
63
+ # Does a GET /institutions call.
64
+ #
65
+ # client - The Plaid::Client instance used to connect
66
+ # (default: Plaid.client).
67
+ #
68
+ # Returns an Array of Institution instances.
69
+ def self.all(client: nil)
70
+ Connector.new(:institutions, client: client).get.map do |idata|
71
+ new(idata)
72
+ end
73
+ end
74
+
75
+ # Public: Get information about a given Financial Institution.
76
+ #
77
+ # Does a GET /institutions/:id call.
78
+ #
79
+ # id - the String institution ID (e.g. "5301a93ac140de84910000e0").
80
+ # client - The Plaid::Client instance used to connect
81
+ # (default: Plaid.client).
82
+ #
83
+ # Returns an Institution instance or raises Plaid::NotFoundError if
84
+ # institution with given id is not found.
85
+ def self.get(id, client: nil)
86
+ new Connector.new(:institutions, id, client: client).get
87
+ end
88
+ end
89
+
90
+ ##############################################################################
91
+
92
+ # Public: A class encapsulating information about a "long tail" Financial
93
+ # Institution supported by Plaid.
94
+ class LongTailInstitution
95
+ # Public: The String ID of the institution. Same as type. E.g. "bofa".
96
+ attr_reader :id
97
+
98
+ # Public: The String short name ("type") of the institution. E.g. "bofa".
99
+ attr_reader :type
100
+
101
+ # Public: The String institution name. E.g. "Bank of America".
102
+ attr_reader :name
103
+
104
+ # Public: The Hash with supported products as keys and booleans as values.
105
+ #
106
+ # E.g. { auth: true, balance: true, connect: true, info: true }.
107
+ attr_reader :products
108
+
109
+ # Public: The String "forgotten password" URL.
110
+ attr_reader :forgotten_password_url
111
+
112
+ # Public: The String "account locked" URL.
113
+ attr_reader :account_locked_url
114
+
115
+ # Public: The String "account setup" URL.
116
+ attr_reader :account_setup_url
117
+
118
+ # Public: The String video (???).
119
+ attr_reader :video
120
+
121
+ # Public: The Array of Hashes with login fields information.
122
+ #
123
+ # E.g. [{ name: 'username', label: 'Online ID', type: 'text' },
124
+ # { name: 'password', label: 'Password', type: 'password' }].
125
+ attr_reader :fields
126
+
127
+ # Public: The Hash with color information for the institution.
128
+ #
129
+ # E.g. { primary: 'rgba(220,20,48,1)',
130
+ # darker: 'rgba(180,11,35,1)',
131
+ # gradient: ['rgba(250,20,51,1)', 'rgba(227,24,55,1)'] }.
132
+ attr_reader :colors
133
+
134
+ # Public: The String with base64 encoded logo.
135
+ attr_reader :logo
136
+
137
+ # Public: ???.
138
+ attr_reader :name_break
139
+
140
+ # Internal: Initialize the LongTailInstitution instance.
141
+ def initialize(hash)
142
+ %w(id type name video logo).each do |f|
143
+ instance_variable_set "@#{f}", hash[f]
144
+ end
145
+
146
+ @products = Plaid.symbolize_hash(hash['products'])
147
+ @forgotten_password_url = hash['forgottenPassword']
148
+ @account_locked_url = hash['accountLocked']
149
+ @account_setup_url = hash['accountSetup']
150
+ @fields = hash['fields'].map { |fld| Plaid.symbolize_hash(fld) }
151
+ @colors = Plaid.symbolize_hash(hash['colors'])
152
+ @name_break = hash['nameBreak']
153
+ end
154
+
155
+ # Public: Get a String representation of the institution.
156
+ #
157
+ # Returns a String.
158
+ def inspect
159
+ "#<Plaid::LongTailInstitution id=#{id.inspect}, name=#{name.inspect}, " \
160
+ '...>'
161
+ end
162
+
163
+ # Public: Get a String representation of the institution.
164
+ #
165
+ # Returns a String.
166
+ alias to_s inspect
167
+
168
+ # Public: Lookup a "long tail" Financial Institution by ID.
169
+ #
170
+ # Does a GET /institutions/search call with id param.
171
+ #
172
+ # id - the String institution ID (e.g. 'bofa').
173
+ # client - The Plaid::Client instance used to connect
174
+ # (default: Plaid.client).
175
+ #
176
+ # Returns an Institution instance or nil if institution with given id is not
177
+ # found.
178
+ def self.get(id, client: nil)
179
+ client ||= Plaid.client
180
+
181
+ # If client_id is set, use it, no authentication otherwise
182
+ auth = client && !client.client_id.nil?
183
+ conn = Connector.new(:institutions, :search, auth: auth, client: client)
184
+ resp = conn.get(id: id)
185
+
186
+ case resp
187
+ when Hash
188
+ new resp
189
+ when Array
190
+ raise 'Non-empty array returned by /institutions/search with id' \
191
+ unless resp.empty?
192
+
193
+ nil
194
+ else
195
+ raise 'Unexpected result returned by /institutions/search with id: ' \
196
+ "#{resp.inspect}"
197
+ end
198
+ end
199
+
200
+ # Public: Get information about the "long tail" institutions supported
201
+ # by Plaid via partnerships.
202
+ #
203
+ # Does a POST /institutions/longtail call.
204
+ #
205
+ # count - The Integer number of results to retrieve (default: 50).
206
+ # offset - The Integer number of results to skip forward from the
207
+ # beginning of the list (default: 0).
208
+ # client - The Plaid::Client instance used to connect
209
+ # (default: Plaid.client).
210
+ #
211
+ # Returns an Array of LongTailInstitution instances.
212
+ def self.all(count: 50, offset: 0, client: nil)
213
+ conn = Connector.new(:institutions, :longtail, auth: true, client: client)
214
+ resp = conn.post(count: count, offset: offset)
215
+ resp.map { |lti_data| new(lti_data) }
216
+ end
217
+
218
+ # Public: Search "long tail" institutions.
219
+ #
220
+ # query - The String search query to match against the full list of
221
+ # institutions. Partial matches are returned making this useful
222
+ # for autocompletion purposes.
223
+ # product - The Symbol product name to filter by, one of Plaid::PRODUCTS
224
+ # (e.g. :info, :connect, etc.). Only valid when query is
225
+ # specified. If nil, results are not filtered by product
226
+ # (default: nil).
227
+ # client - The Plaid::Client instance used to connect
228
+ # (default: Plaid.client).
229
+ def self.search(query: nil, product: nil, client: nil)
230
+ raise ArgumentError, 'query must be specified' \
231
+ unless query.is_a?(String) && !query.empty?
232
+
233
+ payload = { q: query }
234
+ payload[:p] = product.to_s if product
235
+
236
+ resp = Connector.new(:institutions, :search, client: client).get(payload)
237
+ resp.map { |lti_data| new(lti_data) }
238
+ end
239
+ end
240
+ end