plaid 3.0.0 → 4.0.0

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.
data/Rakefile CHANGED
@@ -9,8 +9,7 @@ RDoc::Task.new do |rdoc|
9
9
  rdoc.generator = 'sdoc'
10
10
  rdoc.main = 'README.md'
11
11
 
12
- rdoc.rdoc_files.include('README.md', 'LICENSE', 'UPGRADING.md',
13
- 'CONTRIBUTING.md', 'CHANGELOG.md', 'lib/**/*.rb')
12
+ rdoc.rdoc_files.include('README.md', 'LICENSE', 'UPGRADING.md', 'lib/**/*.rb')
14
13
  rdoc.markup = 'tomdoc'
15
14
  end
16
15
 
@@ -2,12 +2,13 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'plaid'
5
- require 'pry'
6
5
 
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
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
12
8
 
13
- Pry.start
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
@@ -0,0 +1,3 @@
1
+ machine:
2
+ ruby:
3
+ version: 2.2.2
@@ -1,64 +1,19 @@
1
- require 'plaid/version'
2
- require 'plaid/errors'
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
- require 'plaid/webhook'
12
-
13
- require 'uri'
1
+ require_relative 'plaid/client'
2
+ require_relative 'plaid/connect'
3
+ require_relative 'plaid/errors'
4
+ require_relative 'plaid/version'
5
+ require_relative 'plaid/products/accounts'
6
+ require_relative 'plaid/products/auth'
7
+ require_relative 'plaid/products/categories'
8
+ require_relative 'plaid/products/credit_details'
9
+ require_relative 'plaid/products/identity'
10
+ require_relative 'plaid/products/income'
11
+ require_relative 'plaid/products/institutions'
12
+ require_relative 'plaid/products/item'
13
+ require_relative 'plaid/products/processor'
14
+ require_relative 'plaid/products/sandbox'
15
+ require_relative 'plaid/products/transactions'
14
16
 
15
17
  # Public: The Plaid namespace.
16
18
  module Plaid
17
- # Public: Available Plaid products.
18
- PRODUCTS = %i(connect auth info income risk).freeze
19
-
20
- class <<self
21
- # Public: The default Client.
22
- attr_accessor :client
23
-
24
- # Public: The Integer read timeout for requests to Plaid HTTP API.
25
- # Should be specified in seconds. Default value is 120 (2 minutes).
26
- attr_accessor :read_timeout
27
-
28
- # Public: A helper function to ease configuration.
29
- #
30
- # Yields self.
31
- #
32
- # Examples
33
- #
34
- # Plaid.configure do |p|
35
- # p.client_id = 'Plaid provided client ID here'
36
- # p.secret = 'Plaid provided secret key here'
37
- # p.env = :tartan
38
- # p.read_timeout = 300 # it's 5 minutes, yay!
39
- # end
40
- #
41
- # Returns nothing.
42
- def config
43
- client = Client.new
44
- yield client
45
- self.client = client
46
- end
47
-
48
- # Internal: Symbolize keys (and values) for a hash.
49
- #
50
- # hash - The Hash with string keys (or nil).
51
- # values - The Boolean flag telling the function to symbolize values
52
- # as well.
53
- #
54
- # Returns a Hash with keys.to_sym (or nil if hash is nil).
55
- def symbolize_hash(hash, values: false)
56
- return unless hash
57
- return hash.map { |h| symbolize_hash(h) } if hash.is_a?(Array)
58
-
59
- hash.each_with_object({}) do |(k, v), memo|
60
- memo[k.to_sym] = values ? v.to_sym : v
61
- end
62
- end
63
- end
64
19
  end
@@ -1,69 +1,137 @@
1
+ # Public: The Plaid namespace.
1
2
  module Plaid
2
- # Public: A class encapsulating client_id, secret, and Plaid API URL.
3
+ # Internal: Map environment to Plaid environment URL
4
+ #
5
+ # env - The type of the environment in symbol form
6
+ #
7
+ # Returns a string representing an environment URL
8
+ def self.url_from_env(env)
9
+ case env
10
+ when :sandbox
11
+ 'https://sandbox.plaid.com/'
12
+ when :development
13
+ 'https://development.plaid.com/'
14
+ when :production
15
+ 'https://production.plaid.com/'
16
+ end
17
+ end
18
+
19
+ # Public: A class encapsulating client_id, secret, public key, and Plaid environment.
3
20
  class Client
4
- # Public: The String Plaid account client ID to authenticate requests.
5
- attr_accessor :client_id
21
+ # Internal: Set Plaid environment to use
22
+ #
23
+ # Handles converting an env symbol into an environment URL
24
+ #
25
+ # env - The Symbol (:sandbox, :development, :production)
26
+ #
27
+ # Returns a string representing the environment URL or raises an error
28
+ def env_map(env)
29
+ (@env = Plaid.url_from_env(env)) || raise(ArgumentError, 'Invalid value for ' \
30
+ 'Plaid::Client.env ' \
31
+ "(#{env.inspect}): " \
32
+ 'must be :sandbox, '\
33
+ ':development, or :production ')
34
+ end
35
+
36
+ # Public: Memoized class instance to make requests from Plaid::Account
37
+ def accounts
38
+ @accounts ||= Plaid::Accounts.new(self)
39
+ end
40
+
41
+ # Public: Memoized class instance to make requests from Plaid::Auth
42
+ def auth
43
+ @auth ||= Plaid::Auth.new(self)
44
+ end
45
+
46
+ # Public: Memoized class instance to make requests from Plaid::Categories
47
+ def categories
48
+ @categories ||= Plaid::Categories.new(self)
49
+ end
50
+
51
+ # Public: Memoized class instance to make requests from Plaid::CreditDetails
52
+ def credit_details
53
+ @credit_details ||= Plaid::CreditDetails.new(self)
54
+ end
6
55
 
7
- # Public: The String Plaid account secret to authenticate requests.
8
- attr_accessor :secret
56
+ # Public: Memoized class instance to make requests from Plaid::Identity
57
+ def identity
58
+ @identity ||= Plaid::Identity.new(self)
59
+ end
9
60
 
10
- # Public: Plaid environment, i.e. String base URL of the API site.
61
+ # Public: Memoized class instance to make requests from Plaid::Income
62
+ def income
63
+ @income ||= Plaid::Income.new(self)
64
+ end
65
+
66
+ # Public: Memoized class instance to make requests from Plaid::Institutions
67
+ def institutions
68
+ @institutions ||= Plaid::Institutions.new(self)
69
+ end
70
+
71
+ # Public: Memoized class instance to make requests from Plaid::Item
72
+ def item
73
+ @item ||= Plaid::Item.new(self)
74
+ end
75
+
76
+ # Public: Memoized class instance to make requests from Plaid::Processor
77
+ def processor
78
+ @processor ||= Plaid::Processor.new(self)
79
+ end
80
+
81
+ # Public: Memoized class instance to make requests from Plaid::Sandbox
82
+ def sandbox
83
+ @sandbox ||= Plaid::Sandbox.new(self)
84
+ end
85
+
86
+ # Public: Memoized class instance to make requests from Plaid::Transactions
87
+ def transactions
88
+ @transactions ||= Plaid::Transactions.new(self)
89
+ end
90
+
91
+ # Public: Construct a Client instance
11
92
  #
12
- # E.g. 'https://tartan.plaid.com'.
13
- attr_reader :env
93
+ # env - The Symbol (:sandbox, :development, :production)
94
+ # client_id - The String Plaid account client ID to authenticate requests
95
+ # secret - The String Plaid account secret to authenticate requests
96
+ # public_key - The String Plaid account public key to authenticate requests
97
+ def initialize(env:, client_id:, secret:, public_key:)
98
+ @env = env_map(env)
99
+ @client_id = client_id
100
+ @secret = secret
101
+ @public_key = public_key
102
+ end
14
103
 
15
- # Public: Set Plaid environment to use.
104
+ # Public: Make a post request
16
105
  #
17
- # env - The Symbol (:tartan, :production), or a full String URL like
18
- # 'https://tartan.plaid.com'.
19
- def env=(env)
20
- case env
21
- when :tartan
22
- @env = 'https://tartan.plaid.com/'
23
- when :production
24
- @env = 'https://api.plaid.com/'
25
- when String
26
- begin
27
- URI.parse(env)
28
- @env = env
29
- rescue
30
- raise ArgumentError, 'Invalid URL in Plaid::Client.env' \
31
- " (#{env.inspect}). " \
32
- 'Specify either Symbol (:tartan, :production),' \
33
- " or a full URL, like 'https://tartan.plaid.com'"
34
- end
35
- else
36
- raise ArgumentError, 'Invalid value for Plaid::Client.env' \
37
- " (#{env.inspect}): " \
38
- 'must be :tartan, :production, or a full URL, ' \
39
- "e.g. 'https://tartan.plaid.com'"
40
- end
41
- end
42
-
43
- # Public: Construct a Client instance.
106
+ # path - Path or URL to make the request to
107
+ # payload - The payload or data to post
44
108
  #
45
- # env - The Symbol (:tartan, :production), or a full String URL like
46
- # 'https://tartan.plaid.com'.
47
- # client_id - The String Plaid account client ID to authenticate requests.
48
- # secret - The String Plaid account secret to authenticate requests.
49
- def initialize(env: nil, client_id: nil, secret: nil)
50
- env && self.env = env
51
- self.client_id = client_id
52
- self.secret = secret
109
+ # Returns the resulting parsed JSON of the request
110
+ def post(path, payload)
111
+ Plaid::Connect.post(File.join(@env, path), payload)
53
112
  end
54
113
 
55
- # Public: Check if client_id is configured.
114
+ # Public: Make a post request with appended authentication fields
115
+ #
116
+ # path - Path or URL to make the request to
117
+ # payload - The payload or data to post
56
118
  #
57
- # Returns true if it is.
58
- def client_id_configured?
59
- @client_id.is_a?(String) && !@client_id.empty?
119
+ # Returns the resulting parsed JSON of the request
120
+ def post_with_auth(path, payload)
121
+ auth = { client_id: @client_id,
122
+ secret: @secret }
123
+ Plaid::Connect.post(File.join(@env, path), payload.merge(auth))
60
124
  end
61
125
 
62
- # Public: Check if client_id is configured.
126
+ # Public: Make a post request with appended public key field.
127
+ #
128
+ # path - Path or URL to make the request to.
129
+ # payload - The payload or data to post.
63
130
  #
64
- # Returns true if it is.
65
- def secret_configured?
66
- @secret.is_a?(String) && !@secret.empty?
131
+ # Returns the resulting parsed JSON of the request.
132
+ def post_with_public_key(path, payload)
133
+ public_key = { public_key: @public_key }
134
+ Plaid::Connect.post(File.join(@env, path), payload.merge(public_key))
67
135
  end
68
136
  end
69
137
  end
@@ -0,0 +1,75 @@
1
+ require 'json'
2
+ require 'net/http'
3
+ require 'uri'
4
+
5
+ require_relative 'version'
6
+
7
+ module Plaid
8
+ # Internal: A module encapsulating HTTP post requests
9
+ module Connect
10
+ # Internal: Headers used for correct request and SDK tracking.
11
+ NETWORK_HEADERS = { 'User-Agent' => "Plaid Ruby v#{Plaid::VERSION}",
12
+ 'Content-Type' => 'application/json' }.freeze
13
+
14
+ # Internal: Default read timeout for HTTP calls in seconds.
15
+ NETWORK_TIMEOUT = 600
16
+
17
+ # Internal: Run POST request on path with payload.
18
+ #
19
+ # path - The path to send the request to.
20
+ # payload - The hash with data.
21
+ #
22
+ # Returns the parsed JSON response body.
23
+ def self.post(path, payload)
24
+ uri = URI.parse(path)
25
+
26
+ http = Net::HTTP.new(uri.host, uri.port)
27
+ http.use_ssl = true
28
+
29
+ http.read_timeout = Plaid::Connect::NETWORK_TIMEOUT
30
+
31
+ request = Net::HTTP::Post.new(uri.path, Plaid::Connect::NETWORK_HEADERS)
32
+ request.body = JSON.generate(payload)
33
+
34
+ Plaid::Connect.run http, request
35
+ end
36
+
37
+ # Internal: Run the request and process the response.
38
+ #
39
+ # http - Object created by Net::HTTP.new
40
+ # request - Object created by Net::HTTP::Post.new (for POST)
41
+ #
42
+ # Returns the parsed JSON body or raises an appropriate PlaidError
43
+ def self.run(http, request)
44
+ response = http.request(request)
45
+
46
+ if response.body.nil? || response.body.empty?
47
+ raise Plaid::PlaidError.new(0, 'Server error', 'Try to connect later')
48
+ end
49
+
50
+ # All responses are expected to have a JSON body, so we always parse,
51
+ # not looking at the status code.
52
+ body = JSON.parse(response.body)
53
+
54
+ case response
55
+ when Net::HTTPSuccess
56
+ body
57
+ else
58
+ raise_error(body)
59
+ end
60
+ end
61
+
62
+ # Internal: Raise an error with the class depending on reponse body.
63
+ #
64
+ # body - A parsed response body with error.
65
+ #
66
+ # Raises a PlaidError
67
+ def self.raise_error(body)
68
+ raise Plaid::Error.error_from_type(body['error_type']).new(body['error_type'],
69
+ body['error_code'],
70
+ body['error_message'],
71
+ body['display_message'],
72
+ body['request_id'])
73
+ end
74
+ end
75
+ end
@@ -1,37 +1,73 @@
1
1
  module Plaid
2
- # Public: Exception to throw when there are configuration problems
3
- class NotConfiguredError < StandardError; end
4
-
5
2
  # Internal: Base class for Plaid errors
6
3
  class PlaidError < StandardError
7
- attr_reader :code, :resolve
4
+ attr_reader :error_type, :error_code, :error_message, :display_message, :request_id
8
5
 
9
- # Internal: Initialize a error with proper attributes.
6
+ # Internal: Initialize an error with proper attributes
10
7
  #
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.
14
- def initialize(code, message, resolve)
15
- @code = code
16
- @resolve = resolve
17
-
18
- super "Code #{@code}: #{message}. #{resolve}"
8
+ # error_type - A broad categorization of the error
9
+ # error_code - The particular error code
10
+ # error_message - A developer-friendly representation of the error message
11
+ # display_message - A user-friendly representation of the error message
12
+ # request_id - The ID of the request you made, can be used to escalate problems
13
+ def initialize(error_type, error_code, error_message, display_message, request_id)
14
+ @error_type = error_type
15
+ @error_code = error_code
16
+ @error_message = error_message
17
+ @display_message = display_message
18
+ @request_id = request_id
19
+
20
+ super "\n"\
21
+ "Error Type : #{@error_type}\n"\
22
+ "Error Code : #{@error_code}\n"\
23
+ "Error Message : #{@error_message}\n"\
24
+ "Display Message : #{@display_message}\n"\
25
+ "Request ID : #{@request_id}\n"
19
26
  end
20
27
  end
21
28
 
22
- # Public: Exception which is thrown when Plaid API returns a 400 response.
23
- class BadRequestError < PlaidError; end
29
+ # Public: returned when the request is malformed and cannot be processed.
30
+ class InvalidRequestError < PlaidError; end
24
31
 
25
- # Public: Exception which is thrown when Plaid API returns a 401 response.
26
- class UnauthorizedError < PlaidError; end
32
+ # Public: returned when all fields are provided and are in the correct format,
33
+ # but the values provided are incorrect in some way.
34
+ class InvalidInputError < PlaidError; end
27
35
 
28
- # Public: Exception which is thrown when Plaid API returns a 402 response.
29
- class RequestFailedError < PlaidError; end
36
+ # Public: returned when the request is valid but has exceeded established rate limits.
37
+ class RateLimitExceededError < PlaidError; end
30
38
 
31
- # Public: Exception which is thrown when Plaid API returns a 404 response.
32
- class NotFoundError < PlaidError; end
39
+ # Public: returned during planned maintenance windows and
40
+ # in response to API internal server errors.
41
+ class APIError < PlaidError; end
33
42
 
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
43
+ # Public: indicates that information provided for the item (such as credentials or MFA)
44
+ # may be invalid or that the item is not supported on Plaid's platform.
45
+ class ItemError < PlaidError; end
46
+
47
+ # Internal: A module that provides utilities for errors.
48
+ module Error
49
+ # Internal: Map error_type to PlaidError
50
+ #
51
+ # Maps an error_type from an error HTTP response to an actual PlaidError class instance
52
+ #
53
+ # error_type - The type of the error as indicated by the error response body
54
+ #
55
+ # Returns an error class mapped from error_type
56
+ def self.error_from_type(error_type)
57
+ case error_type
58
+ when 'INVALID_REQUEST'
59
+ Plaid::InvalidRequestError
60
+ when 'INVALID_INPUT'
61
+ Plaid::InvalidInputError
62
+ when 'RATE_LIMIT_EXCEEDED_ERROR'
63
+ Plaid::RateLimitExceededError
64
+ when 'API_ERROR'
65
+ Plaid::APIError
66
+ when 'ITEM_ERROR'
67
+ Plaid::ItemError
68
+ else
69
+ Plaid::PlaidError
70
+ end
71
+ end
72
+ end
37
73
  end