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