plaid-legacy 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ module Plaid
2
+ # Public: Representation of risk data (per account).
3
+ class Risk
4
+ # Public: The Float comprehensive risk score associated with the account,
5
+ # where 0 is the lowest risk and 1 is the highest. E.g. 0.5.
6
+ attr_reader :score
7
+
8
+ # Public: The Hash with Symbol reasons and associated Float subscores
9
+ # contributing to the overall risk score.
10
+ #
11
+ # E.g. { transaction_amounts: 0.78, foreign_fees: 0.96, ... }.
12
+ attr_reader :reason
13
+
14
+ # Internal: Construct a Risk object.
15
+ #
16
+ # fields - The Hash with fields.
17
+ def initialize(fields)
18
+ @score = fields['score']
19
+ @reason = Plaid.symbolize_hash(fields['reason'])
20
+ end
21
+
22
+ # Public: Get a String representation of Risk.
23
+ #
24
+ # Returns a String.
25
+ def inspect
26
+ "#<Plaid::Risk score=#{score.inspect}, ..."
27
+ end
28
+
29
+ # Public: Get a String representation of Risk.
30
+ #
31
+ # Returns a String.
32
+ alias to_s inspect
33
+ end
34
+ end
@@ -0,0 +1,128 @@
1
+ # coding: utf-8
2
+ require 'date'
3
+
4
+ module Plaid
5
+ # Public: Representation of a transaction.
6
+ class Transaction
7
+ # Public: The String unique ID of the transaction. E.g.
8
+ # "0AZ0De04KqsreDgVwM1RSRYjyd8yXxSDQ8Zxn".
9
+ attr_reader :id
10
+
11
+ # Public: The String ID of the account in which this transaction occurred.
12
+ # E.g. "XARE85EJqKsjxLp6XR8ocg8VakrkXpTXmRdOo".
13
+ attr_reader :account_id
14
+
15
+ # Public: The Date that the transaction took place.
16
+ # E.g. #<Date: 2016-04-28 ...>
17
+ attr_reader :date
18
+
19
+ # Public: The settled dollar value as Float. Positive values when
20
+ # money moves out of the account; negative values when money moves
21
+ # in. E.g. 0.10 (10 cents).
22
+ attr_reader :amount
23
+
24
+ # Public: The String descriptive name of the transaction. E.g.
25
+ # "Golden Crepes".
26
+ attr_reader :name
27
+
28
+ # Public: The Hash with additional information regarding the
29
+ # transaction. The keys are Symbols. E.g.
30
+ # { location: { "city": "San Francisco", "state": "CA" } }
31
+ attr_reader :meta
32
+
33
+ # Public: The Hash with location information obtained from the
34
+ # meta field. The keys are Strings.
35
+ #
36
+ # E.g. {"address" => "262 W 15th St",
37
+ # "city" => "New York",
38
+ # "state" => "NY",
39
+ # "zip" => "10011",
40
+ # "coordinates" => {
41
+ # "lat" => 40.740352,
42
+ # "lon" => -74.001761
43
+ # }}
44
+ attr_reader :location
45
+
46
+ # Public: The Boolean flag which identifies the transaction as
47
+ # pending or unsettled.
48
+ attr_reader :pending
49
+
50
+ # Public: The Hash with numeric representation of Plaid confidence
51
+ # in the meta data attached to the transaction. In the case of a
52
+ # score <.9 Plaid will default to guaranteed and known
53
+ # information. E.g.
54
+ #
55
+ # {location: {
56
+ # "address" => 1,
57
+ # "city" => 1,
58
+ # "state" => 1
59
+ # }, name: 0.9}.
60
+ attr_reader :score
61
+
62
+ # Public: The Hash transaction type.
63
+ #
64
+ # E.g. {:primary => :place}.
65
+ attr_reader :type
66
+
67
+ # Public: The Array of String category hierarchy.
68
+ #
69
+ # E.g. ["Food and Drink", "Restaurants"].
70
+ attr_reader :category_hierarchy
71
+
72
+ # Public: The String Category ID.
73
+ #
74
+ # E.g. "13005000".
75
+ attr_reader :category_id
76
+
77
+ # Public: A String attribute that is used by the bank/payment
78
+ # processor to identify transactions — where applicable.
79
+ attr_reader :reference_number
80
+
81
+ # Public: The String ID of a posted transaction's associated
82
+ # pending transaction - where applicable.
83
+ attr_reader :pending_transaction_id
84
+
85
+ # Public: Initialize a Transaction instance.
86
+ #
87
+ # fields - The Hash with fields.
88
+ def initialize(fields)
89
+ @id = fields['_id']
90
+ @account_id = fields['_account']
91
+
92
+ @date = fields['date'] && Date.parse(fields['date'])
93
+ @amount = fields['amount']
94
+ @name = fields['name']
95
+ @meta = Plaid.symbolize_hash(fields['meta'])
96
+ @location = (@meta && @meta[:location]) || {}
97
+ @pending = fields['pending']
98
+ @reference_number = fields['_reference_number']
99
+ @pending_transaction_id = fields['_pendingTransaction']
100
+ @score = Plaid.symbolize_hash(fields['score'])
101
+
102
+ @type = Plaid.symbolize_hash(fields['type'], values: true)
103
+ @category_hierarchy = fields['category']
104
+ @category_id = fields['category_id']
105
+ end
106
+
107
+ # Public: Detect if the transaction is pending or unsettled.
108
+ #
109
+ # Returns true if it is.
110
+ def pending?
111
+ pending
112
+ end
113
+
114
+ # Public: Get a String representation of the transaction.
115
+ #
116
+ # Returns a String.
117
+ def inspect
118
+ "#<Plaid::Transaction id=#{id.inspect}, account_id=#{account_id.inspect}, " \
119
+ "date=#{date}, amount=#{amount.inspect}, name=#{name.inspect}, " \
120
+ "pending=#{pending.inspect}>"
121
+ end
122
+
123
+ # Public: Get a String representation of the transaction.
124
+ #
125
+ # Returns a String.
126
+ alias to_s inspect
127
+ end
128
+ end
@@ -0,0 +1,507 @@
1
+ require_relative 'account'
2
+
3
+ module Plaid
4
+ # Public: A class which encapsulates the authenticated user for all Plaid
5
+ # products.
6
+ class User
7
+ # Public: The access token for authenticated user.
8
+ attr_reader :access_token
9
+
10
+ # Public: The processor token for a given account and authenticated user.
11
+ attr_reader :processor_token
12
+
13
+ # Public: The current product. Provides a context for #update and #delete
14
+ # calls. See Plaid::PRODUCTS.
15
+ attr_reader :product
16
+
17
+ # Public: The Array of Account instances providing accounts information
18
+ # for the user.
19
+ attr_reader :accounts
20
+
21
+ # Public: The Array of Transactions provided by initial call to User.create.
22
+ #
23
+ # If the :login_only option of User.create is set to false, the initial
24
+ # 30-day transactional data are returned during the API call. This attribute
25
+ # contains them.
26
+ attr_reader :initial_transactions
27
+
28
+ # Public: The Symbol MFA type to be used (or nil, if no MFA required).
29
+ #
30
+ # E.g. :questions, :list, or :device.
31
+ attr_reader :mfa_type
32
+
33
+ # Public: The MFA data (Hash or Array of Hash) or nil, if no MFA required.
34
+ #
35
+ # E.g. [{ question: "What was the name of your first pet?" }]
36
+ # or
37
+ # [{ mask: 't..t@plaid.com', type: 'email' },
38
+ # { mask: 'xxx-xxx-5309', type: 'phone' }]
39
+ # or
40
+ # { message: 'Code sent to xxx-xxx-5309' }
41
+ attr_reader :mfa
42
+
43
+ # Public: The String stripe bank account token.
44
+ #
45
+ # This field is set when you use User.exchange_token to convert Link
46
+ # public_token into an access token suitable for Plaid API.
47
+ attr_reader :stripe_bank_account_token
48
+
49
+ # Internal: The Plaid::Client instance used to make queries.
50
+ attr_reader :client
51
+
52
+ # Public: Create (add) a user.
53
+ #
54
+ # product - The Symbol product name you are adding the user to, one of
55
+ # Plaid::PRODUCTS (e.g. :info, :connect, etc.).
56
+ # institution - The String/Symbol financial institution type that you
57
+ # want to access (e.g. :wells).
58
+ # username - The String username associated with the financial
59
+ # institution.
60
+ # password - The String password associated with the financial
61
+ # institution.
62
+ # pin - The String PIN number associated with the financial
63
+ # institution (default: nil).
64
+ # options - the Hash options (default: {}):
65
+ # :list - The Boolean flag which would request the
66
+ # available send methods if the institution
67
+ # requires code-based MFA credential (default:
68
+ # false).
69
+ # :webhook - The String webhook URL. Used with :connect,
70
+ # :income, and :risk products (default: nil).
71
+ # :pending - The Boolean flag requesting to return
72
+ # pending transactions. Used with :connect
73
+ # product (default: false).
74
+ # :login_only - The Boolean option valid for initial
75
+ # authentication only. If set to false, the
76
+ # initial request will return transaction data
77
+ # based on the start_date and end_date.
78
+ # :start_date - The start Date from which to return
79
+ # transactions (default: 30 days ago).
80
+ # :end_date - The end Date to which transactions
81
+ # will be collected (default: today).
82
+ # client - The Plaid::Client instance used to connect to the API
83
+ # (default is to use global Plaid client - Plaid.client).
84
+ #
85
+ # Returns a Plaid::User instance.
86
+ def self.create(product, institution, username, password,
87
+ pin: nil, options: nil, client: nil)
88
+ check_product product
89
+
90
+ payload = { username: username, password: password,
91
+ type: institution.to_s }
92
+ payload[:pin] = pin if pin
93
+ payload[:options] = MultiJson.dump(options) if options
94
+
95
+ conn = Connector.new(product, auth: true, client: client)
96
+ resp = conn.post(payload)
97
+
98
+ new product, response: resp, mfa: conn.mfa?, client: client
99
+ end
100
+
101
+ # Public: Get User instance in case user access token is known.
102
+ #
103
+ # No requests are made, but the returned User instance is ready to be
104
+ # used.
105
+ #
106
+ # product - The Symbol product name you want to use, one of
107
+ # Plaid::PRODUCTS (e.g. :info, :connect, etc.).
108
+ # token - The String access token for the user.
109
+ # client - The Plaid::Client instance used to connect to the API
110
+ # (default is to use global Plaid client - Plaid.client).
111
+ #
112
+ # Returns a Plaid::User instance.
113
+ def self.load(product, token, client: nil)
114
+ new check_product(product), access_token: token, client: client
115
+ end
116
+
117
+ # Public: Exchange a Link public_token for an API access_token.
118
+ #
119
+ # The account_id parameter is required if you wish to receive a Stripe bank
120
+ # account token.
121
+ #
122
+ # public_token - The String Link public_token.
123
+ # account_id - The String account ID.
124
+ # product - The Symbol product name (default: :connect).
125
+ # client - The Plaid::Client instance used to connect to the API
126
+ # (default is to use global Plaid client - Plaid.client).
127
+ #
128
+ # Returns a new User with access token obtained from Plaid and default
129
+ # product set to product. User#stripe_bank_account_token for this user
130
+ # instance will contain the Stripe token.
131
+ def self.exchange_token(public_token, account_id = nil,
132
+ product: :connect, client: nil)
133
+ check_product product
134
+
135
+ payload = { public_token: public_token }
136
+ payload[:account_id] = account_id if account_id
137
+
138
+ response = Connector.new(:exchange_token, auth: true, client: client)
139
+ .post(payload)
140
+
141
+ stripe_token = account_id && response['stripe_bank_account_token']
142
+ new product, response: response, client: client,
143
+ stripe_token: stripe_token
144
+ end
145
+
146
+ # Internal: Initialize a User instance.
147
+ #
148
+ # product - The Symbol product name.
149
+ # access_token - The String access token obtained from Plaid.
150
+ # response - The Hash response body to parse.
151
+ # mfa - The Boolean flag indicating that response body
152
+ # contains an MFA response.
153
+ # stripe_token - The String stripe bank account token.
154
+ # client - The Plaid::Client instance used to connect to the API
155
+ # (default is to use global Plaid client - Plaid.client).
156
+ def initialize(product, access_token: nil, response: nil, mfa: nil,
157
+ stripe_token: nil, client: nil)
158
+ @product = product
159
+ @client = client
160
+ @access_token = access_token if access_token
161
+ @mfa_required = mfa
162
+ @stripe_bank_account_token = stripe_token
163
+ @accounts = @initial_transactions = @info = @risk = @income = nil
164
+
165
+ parse_response(response) if response
166
+ end
167
+
168
+ # Public: Find out if MFA is required based on last request.
169
+ #
170
+ # After calling e.g. User.create you might need to make an additional
171
+ # authorization step if MFA is required by the financial institution.
172
+ #
173
+ # Returns true if this step is needed, a falsey value otherwise.
174
+ def mfa?
175
+ @mfa_required
176
+ end
177
+
178
+ # Public: Submit MFA information.
179
+ #
180
+ # info - The String with MFA information (default: nil).
181
+ # send_method - The Hash with code send method information.
182
+ # E.g. { type: 'phone' } or { mask: '123-...-4321' }.
183
+ # Default is first available email.
184
+ # options - the Hash options (default: {}):
185
+ # :list - The Boolean flag which would request the
186
+ # available send methods if the institution
187
+ # requires code-based MFA credential (default:
188
+ # false).
189
+ # :webhook - The String webhook URL. Used with :connect,
190
+ # :income, and :risk products (default: nil).
191
+ # :pending - The Boolean flag requesting to return
192
+ # pending transactions. Used with :connect
193
+ # product (default: false).
194
+ # :login_only - The Boolean option valid for initial
195
+ # authentication only. If set to false, the
196
+ # initial request will return transaction data
197
+ # based on the start_date and end_date.
198
+ # :start_date - The start Date from which to return
199
+ # transactions (default: 30 days ago).
200
+ # :end_date - The end Date to which transactions
201
+ # will be collected (default: today).
202
+ #
203
+ # Returns true if whole MFA process is completed, false otherwise.
204
+ def mfa_step(info = nil, send_method: nil, options: nil)
205
+ payload = { access_token: access_token }
206
+ payload[:mfa] = info if info
207
+ if options || send_method
208
+ options = {} unless options
209
+ options[:send_method] = send_method if send_method
210
+ payload[:options] = MultiJson.dump(options)
211
+ end
212
+ conn = Connector.new(product, :step, auth: true)
213
+
214
+ # Use PATCH if we are in context of User#update.
215
+ response = if @mfa_patch
216
+ conn.patch(payload)
217
+ else
218
+ conn.post(payload)
219
+ end
220
+
221
+ @mfa_required = conn.mfa?
222
+ parse_response(response)
223
+ end
224
+
225
+ # Public: Get transactions.
226
+ #
227
+ # Does a /connect/get call. Updates self.accounts with latest information.
228
+ #
229
+ # pending - the Boolean flag requesting to return pending transactions.
230
+ # account_id - the String Account ID (default: nil). If this argument is
231
+ # present, only transactions for given account will be
232
+ # requested.
233
+ # start_date - The start Date (inclusive).
234
+ # end_date - The end Date (inclusive).
235
+ #
236
+ # Returns an Array of Transaction records.
237
+ def transactions(pending: false, account_id: nil,
238
+ start_date: nil, end_date: nil)
239
+ options = { pending: pending }
240
+ options[:account] = account_id if account_id
241
+ options[:gte] = start_date.to_s if start_date
242
+ options[:lte] = end_date.to_s if end_date
243
+
244
+ response = Connector.new(:connect, :get, auth: true, client: client)
245
+ .post(access_token: access_token,
246
+ options: MultiJson.dump(options))
247
+ update_accounts(response)
248
+ build_objects(response['transactions'], Transaction)
249
+ end
250
+
251
+ # Public: Update user credentials.
252
+ #
253
+ # Updates the user credentials for the current product. See
254
+ # User#for_product.
255
+ #
256
+ # username - The String username associated with the financial
257
+ # institution.
258
+ # password - The String password associated with the financial
259
+ # institution.
260
+ # pin - The String PIN number associated with the financial
261
+ # institution (default: nil).
262
+ #
263
+ # Returns self.
264
+ def update(username, password, pin = nil)
265
+ payload = {
266
+ access_token: access_token,
267
+ username: username,
268
+ password: password
269
+ }
270
+
271
+ payload[:pin] = pin if pin
272
+
273
+ conn = Connector.new(product, auth: true, client: client)
274
+ resp = conn.patch(payload)
275
+
276
+ if conn.mfa?
277
+ @mfa_required = true
278
+ end
279
+
280
+ parse_response(resp)
281
+
282
+ # A note for User#mfa_step to send PATCH request too
283
+ @mfa_patch = true
284
+
285
+ self
286
+ end
287
+
288
+ # Public: Create or update the webhook for Connect.
289
+ #
290
+ # Does a PATCH /connect request.
291
+ #
292
+ # webhook - The String with webhook URL.
293
+ #
294
+ # Returns self.
295
+ def update_webhook(webhook)
296
+ raise ArgumentError, 'User#update_webhook only supported by Connect!' \
297
+ unless product == :connect
298
+
299
+ payload = {
300
+ access_token: access_token,
301
+ options: MultiJson.dump(webhook: webhook)
302
+ }
303
+
304
+ parse_response(Connector.new(:connect, auth: true, client: client)
305
+ .patch(payload))
306
+ self
307
+ end
308
+
309
+ # Public: Delete the user.
310
+ #
311
+ # Makes a delete request and freezes self to prevent further modifications
312
+ # to the object.
313
+ #
314
+ # Returns self.
315
+ def delete
316
+ Connector.new(product, auth: true, client: client)
317
+ .delete(access_token: access_token)
318
+
319
+ freeze
320
+ end
321
+
322
+ # Public: Upgrade the user.
323
+ #
324
+ # For an existing user that has been added via any of products (:connect,
325
+ # :auth, :income, :info, or :risk), you can upgrade that user to have
326
+ # functionality with other products.
327
+ #
328
+ # Does a POST /upgrade request.
329
+ #
330
+ # See also User#for_product.
331
+ #
332
+ # product - The Symbol product name you are upgrading the user to, one of
333
+ # Plaid::PRODUCTS.
334
+ #
335
+ # Returns another User record with the same access token, but tied to the
336
+ # new product.
337
+ def upgrade(product)
338
+ payload = { access_token: access_token, upgrade_to: product.to_s }
339
+ response = Connector.new(:upgrade, auth: true, client: client)
340
+ .post(payload)
341
+
342
+ User.new product, response: response, client: client
343
+ end
344
+
345
+ # Public: Get the current user tied to another product.
346
+ #
347
+ # No API request is made, just the current product is changed.
348
+ #
349
+ # product - The Symbol product you are selecting, one of Plaid::PRODUCTS.
350
+ #
351
+ # See also User#upgrade.
352
+ #
353
+ # Returns a new User instance.
354
+ def for_product(product)
355
+ User.load product, access_token, client: client
356
+ end
357
+
358
+ # Public: Get auth information for the user (routing numbers for accounts).
359
+ #
360
+ # Not only this method returns the new data, but it updates self.accounts as
361
+ # well.
362
+ #
363
+ # The method does a POST /auth/get request.
364
+ #
365
+ # sync - The Boolean flag which, if true, causes auth information to be
366
+ # rerequested from the server. Otherwise cached version is returned,
367
+ # if it exists.
368
+ #
369
+ # Returns an Array of Account with numbers baked in.
370
+ def auth(sync: false)
371
+ if sync || !@accounts || !@accounts[0] || !@accounts[0].numbers
372
+ response = Connector.new(:auth, :get, auth: true, client: client)
373
+ .post(access_token: access_token)
374
+
375
+ update_accounts(response)
376
+ end
377
+
378
+ accounts
379
+ end
380
+
381
+ # Public: Get info for the user.
382
+ #
383
+ # Does a POST /info/get request.
384
+ #
385
+ # sync - The Boolean flag which, if true, causes information to be
386
+ # rerequested from the server. Otherwise cached version is returned,
387
+ # if it exists.
388
+ #
389
+ # Returns a Plaid::Info instance.
390
+ def info(sync: false)
391
+ if sync || !@info
392
+ parse_response(Connector.new(:info, :get, auth: true, client: client)
393
+ .post(access_token: access_token))
394
+ end
395
+
396
+ @info
397
+ end
398
+
399
+ # Public: Get income information for the user.
400
+ #
401
+ # Does a POST /income/get request.
402
+ #
403
+ # sync - The Boolean flag which, if true, causes income information to be
404
+ # rerequested from the server. Otherwise cached version is returned,
405
+ # if it exists.
406
+ #
407
+ # Returns a Plaid::Income instance.
408
+ def income(sync: false)
409
+ if sync || !@income
410
+ parse_response(Connector.new(:income, :get, auth: true, client: client)
411
+ .post(access_token: access_token))
412
+ end
413
+
414
+ @income
415
+ end
416
+
417
+ # Public: Get risk data for the user's accounts.
418
+ #
419
+ # Does a POST /risk/get request.
420
+ #
421
+ # sync - The Boolean flag which, if true, causes risk information to be
422
+ # rerequested from the server. Otherwise cached version is returned,
423
+ # if it exists.
424
+ #
425
+ # Returns an Array of accounts with risk attribute set.
426
+ def risk(sync: false)
427
+ if sync || !@accounts || !@accounts[0] || !@accounts[0].risk
428
+ parse_response(Connector.new(:risk, :get, auth: true, client: client)
429
+ .post(access_token: access_token))
430
+ end
431
+
432
+ @accounts
433
+ end
434
+
435
+ # Public: Get current account balance.
436
+ #
437
+ # Does a POST /balance request.
438
+ #
439
+ # Returns an Array of Plaid::Account.
440
+ def balance
441
+ response = Connector.new(:balance, auth: true, client: client)
442
+ .post(access_token: access_token)
443
+
444
+ update_accounts(response)
445
+ end
446
+
447
+ private
448
+
449
+ # Internal: Validate the product name.
450
+ def self.check_product(product)
451
+ if Plaid::PRODUCTS.include?(product)
452
+ product
453
+ else
454
+ raise ArgumentError, "product (#{product.inspect}) must be one of " \
455
+ "Plaid products (#{Plaid::PRODUCTS.inspect})"
456
+ end
457
+ end
458
+
459
+ private_class_method :check_product
460
+
461
+ # Internal: Set up attributes from Add User response.
462
+ def parse_response(response)
463
+ @access_token = response['access_token']
464
+ @processor_token = response['processor_token']
465
+
466
+ return parse_mfa_response(response) if mfa?
467
+
468
+ @mfa_type = @mfa = nil
469
+
470
+ update_accounts(response) if response['accounts']
471
+
472
+ if (trans = response['transactions'])
473
+ @initial_transactions = build_objects(trans, Transaction)
474
+ end
475
+
476
+ if (income = response['income'])
477
+ @income = Plaid::Income.new(income)
478
+ end
479
+
480
+ return unless (i = response['info'])
481
+ @info = Plaid::Info.new(i)
482
+ end
483
+
484
+ # Internal: Parse an MFA response
485
+ def parse_mfa_response(response)
486
+ @mfa_type = response['type'].to_sym
487
+ @mfa = Plaid.symbolize_hash(response['mfa'])
488
+ end
489
+
490
+ # Internal: Convert an array of data into an array of objects, encapsulating
491
+ # that data.
492
+ def build_objects(data, klass)
493
+ data ? data.map { |element| klass.new(element) } : []
494
+ end
495
+
496
+ # Internal: Update account data from the response.
497
+ def update_accounts(response)
498
+ new_accounts = build_objects(response['accounts'], Account)
499
+
500
+ if @accounts
501
+ Account.merge @accounts, new_accounts
502
+ else
503
+ @accounts = new_accounts
504
+ end
505
+ end
506
+ end
507
+ end