dwolla_v2 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 347bf32bfbb4db49d6341c3db975cf696a97f492
4
- data.tar.gz: 38d265059cb4ddac927d141096bd145a76cf2c60
3
+ metadata.gz: 2db53d14d332c6a4e13f74f7a9986e361b8509e7
4
+ data.tar.gz: 2c61640b59008a32254807efcc050f6d4256b277
5
5
  SHA512:
6
- metadata.gz: 053be0c20b5da0da5c421dcb8979ca1aefcc4a6620c8383e2251b0e458042b222c5c76682051d08706fe2a9f9d8e043d8702bcd8781fc6a89397b9982bc532db
7
- data.tar.gz: 43695d6c3e63d61003cac1ba3b72735ebc9f7e53b7926b4cec186f01d4e5b0d34e466091a0405cb340a846511f788493d9bf7cbd779d8b9c18cfe6900e89c9be
6
+ metadata.gz: 47f74c7ce9469f1b9f46422ac6339c3091784da493892e2e2a68ea9a46c6cb25082513eb7455d0d404c22934d62c8c5b967929394c1588762d23fabf3eb6007b
7
+ data.tar.gz: e0117f0296365e9abec6b5caaa1ceaa3f716fb5e75c6824c35b49f851490abe48dc14fa5aa362e2490af89cbf7ca90d1c267db0d83e552859c3237ea01f851ce
data/README.md CHANGED
@@ -11,7 +11,7 @@ Dwolla V2 Ruby client. For the V1 Ruby client see [Dwolla/dwolla-ruby](https://g
11
11
  Add this line to your application's Gemfile:
12
12
 
13
13
  ```ruby
14
- gem 'dwolla_v2', '~> 0.4'
14
+ gem 'dwolla_v2', '~> 1.0'
15
15
  ```
16
16
 
17
17
  And then execute:
@@ -22,41 +22,122 @@ Or install it yourself as:
22
22
 
23
23
  $ gem install dwolla_v2
24
24
 
25
- ## Usage
25
+ ## `DwollaV2::Client`
26
26
 
27
- #### Creating a client
27
+ ### Basic usage
28
28
 
29
29
  ```ruby
30
- $dwolla = DwollaV2::Client.new(id: "CLIENT_ID", secret: "CLIENT_SECRET") do |optional_config|
31
- optional_config.environment = :sandbox
32
- optional_config.on_grant do |token|
33
- YourTokenData.create! token
34
- end
35
- optional_config.faraday do |faraday|
30
+ # config/initializers/dwolla.rb
31
+ $dwolla = DwollaV2::Client.new(id: ENV["DWOLLA_ID"], secret: ENV["DWOLLA_SECRET"])
32
+ ```
33
+
34
+ ### Using the sandbox environment (optional)
35
+
36
+ ```ruby
37
+ # config/initializers/dwolla.rb
38
+ $dwolla = DwollaV2::Client.new(id: ENV["DWOLLA_ID"], secret: ENV["DWOLLA_SECRET"]) do |config|
39
+ config.environment = :sandbox
40
+ end
41
+ ```
42
+
43
+ `environment=` defaults to `:production` and accepts either a String or a Symbol.
44
+
45
+ ### Configure an `on_grant` callback (optional)
46
+
47
+ An `on_grant` callback is useful for storing new tokens when they are granted. For example, you
48
+ may have a `YourTokenData` ActiveRecord model that you use to store your tokens. The `on_grant`
49
+ callback is called with the `DwollaV2::Token` that was just granted by the server. You can pass
50
+ this to the `create!` method of an ActiveRecord model to create a record using the token's data.
51
+
52
+ ```ruby
53
+ # config/initializers/dwolla.rb
54
+ $dwolla = DwollaV2::Client.new(id: ENV["DWOLLA_ID"], secret: ENV["DWOLLA_SECRET"]) do |config|
55
+ config.on_grant {|t| YourTokenData.create!(t) }
56
+ end
57
+ ```
58
+
59
+ It is highly recommended that you encrypt any token data you store using
60
+ [attr_encrypted][attr_encrypted] or [vault-rails][vault-rails] (if you use [Vault][vault]). These
61
+ are both configured in your ActiveRecord models.
62
+
63
+ [attr_encrypted]: https://github.com/attr-encrypted/attr_encrypted
64
+ [vault-rails]: https://github.com/hashicorp/vault-rails
65
+ [vault]: https://www.vaultproject.io/
66
+
67
+ ### Configure Faraday (optional)
68
+
69
+ DwollaV2 uses [Faraday][faraday] to make HTTP requests. You can configure your own
70
+ [Faraday middleware][faraday-middleware] and adapter when configuring your client. Remember to
71
+ always include an adapter last, even if you want to use the default adapter.
72
+
73
+ [faraday]: https://github.com/lostisland/faraday
74
+ [faraday-middleware]: https://github.com/lostisland/faraday_middleware
75
+
76
+ ```ruby
77
+ # config/initializers/dwolla.rb
78
+ $dwolla = DwollaV2::Client.new(id: ENV["DWOLLA_ID"], secret: ENV["DWOLLA_SECRET"]) do |config|
79
+ config.faraday do |faraday|
36
80
  faraday.response :logger
37
81
  faraday.adapter Faraday.default_adapter
38
82
  end
39
83
  end
40
84
  ```
41
85
 
42
- #### Authorization
86
+ ## `DwollaV2::Token`
43
87
 
44
- Get an application token:
88
+ Tokens can be used to make requests to the Dwolla V2 API. There are two types of tokens:
89
+
90
+ ### Application tokens
91
+
92
+ Application tokens are used to access the API on behalf of a consumer application. API resources that
93
+ belong to an application include: `webhook-subscriptions`, `events`, and `webhooks`. Application
94
+ tokens can be created using the [`client_credentials`][client_credentials] OAuth grant type:
95
+
96
+ [client_credentials]: https://tools.ietf.org/html/rfc6749#section-4.4
45
97
 
46
98
  ```ruby
47
- $dwolla.auths.client scope: "ManageCustomers|Funding"
99
+ application_token = $dwolla.auths.client
100
+ # => #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." expires_in=3600 scope="...">
48
101
  ```
49
102
 
50
- Get an account token:
103
+ *Application tokens do not include a `refresh_token`. When an application token expires, generate
104
+ a new one using `$dwolla.auths.client`.*
105
+
106
+ ### Account tokens
107
+
108
+ Account tokens are used to access the API on behalf of a Dwolla account. API resources that belong
109
+ to an account include `customers`, `funding-sources`, `documents`, `mass-payments`, `mass-payment-items`,
110
+ `transfers`, and `on-demand-authorizations`.
111
+
112
+ There are two ways to get an account token. One is by generating a token at
113
+ https://uat.dwolla.com/applications (sandbox) or https://www.dwolla.com/applications (production).
114
+
115
+ You can instantiate a generated token by doing the following:
116
+
117
+ ```ruby
118
+ account_token = $dwolla.tokens.new access_token: "...", refresh_token: "..."
119
+ # => #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." refresh_token="...">
120
+ ```
121
+
122
+ The other way to get an account token is using the [`authorization_code`][authorization_code]
123
+ OAuth grant type. This flow works by redirecting a user to dwolla.com in order to get authorization
124
+ and sending them back to your website with an authorization code which can be exchanged for a token.
125
+ For example:
126
+
127
+ [authorization_code]: https://tools.ietf.org/html/rfc6749#section-4.1
51
128
 
52
129
  ```ruby
53
130
  class YourAuthController < ApplicationController
131
+ # redirect the user to dwolla.com for authorization
54
132
  def authorize
55
133
  redirect_to auth.url
56
134
  end
57
135
 
136
+ # https://yoursite.com/callback
58
137
  def callback
59
- token = auth.callback params
138
+ # exchange the code for a token
139
+ token = auth.callback(params)
140
+ # => #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." refresh_token="..." expires_in=3600 scope="ManageCustomers|Funding" account_id="...">
60
141
  session[:account_id] = token.account_id
61
142
  end
62
143
 
@@ -70,31 +151,51 @@ class YourAuthController < ApplicationController
70
151
  end
71
152
  ```
72
153
 
73
- Refresh a token:
154
+ ### Refreshing tokens
155
+
156
+ Tokens with `refresh_token`s can be refreshed using `$dwolla.auths.refresh`, which takes a
157
+ `DwollaV2::Token` as its first argument and returns a new token.
74
158
 
75
159
  ```ruby
76
- token = $dwolla.auths.refresh expired_token
160
+ refreshed_token = $dwolla.auths.refresh(expired_token)
161
+ # => #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." refresh_token="..." expires_in=3600 scope="ManageCustomers|Funding" account_id="...">
77
162
  ```
78
163
 
79
- Initialize a token:
164
+ ### Initializing tokens:
165
+
166
+ `DwollaV2::Token`s can be initialized with the following attributes:
80
167
 
81
168
  ```ruby
82
- token_data = YourTokenData.find_by account_id: "ACCOUNT_ID"
83
- token = $dwolla.tokens.new token_data
169
+ $dwolla.tokens.new access_token: "...",
170
+ refresh_token: "...",
171
+ expires_in: 123,
172
+ scope: "...",
173
+ account_id: "..."
174
+ #<DwollaV2::Token client=#<DwollaV2::Client id="..." secret="..." environment=:sandbox> access_token="..." refresh_token="..." expires_in=123 scope="..." account_id="...">
84
175
  ```
85
176
 
86
- #### Making requests
177
+ ## Requests
178
+
179
+ `DwollaV2::Token`s can make requests using the `#get`, `#post`, `#put`, and `#delete` methods.
87
180
 
88
181
  ```ruby
89
- token.get "/resource", foo: "bar"
90
- token.post "/resource", foo: "bar"
91
- token.post "/resource", foo: Faraday::UploadIO.new("/path/to/bar.png", "image/png")
92
- token.put "/resource", foo: "bar"
93
- token.put "/resource", foo: Faraday::UploadIO.new("/path/to/bar.png", "image/png")
94
- token.delete "/resource"
182
+ # GET api.dwolla.com/resource?foo=bar
183
+ token.get "resource", foo: "bar"
184
+
185
+ # POST api.dwolla.com/resource {"foo":"bar"}
186
+ token.post "resource", foo: "bar"
187
+
188
+ # POST api.dwolla.com/resource multipart/form-data foo=...
189
+ token.post "resource", foo: Faraday::UploadIO.new("/path/to/bar.png", "image/png")
190
+
191
+ # PUT api.dwolla.com/resource {"foo":"bar"}
192
+ token.put "resource", foo: "bar"
193
+
194
+ # DELETE api.dwolla.com/resource
195
+ token.delete "resource"
95
196
  ```
96
197
 
97
- In parallel:
198
+ Requests can also be made in parallel:
98
199
 
99
200
  ```ruby
100
201
  foo, bar = nil
@@ -106,50 +207,52 @@ puts foo # only ready after `in_parallel` block has executed
106
207
  puts bar # only ready after `in_parallel` block has executed
107
208
  ```
108
209
 
109
- #### Responses
210
+ ## Responses
110
211
 
111
212
  Requests return a `DwollaV2::Response`.
112
213
 
113
- **Response status**:
114
-
115
214
  ```ruby
116
- res = token.post "/customers", customer_params
117
- res.status # => 201
118
- ```
215
+ res = token.get "/"
216
+ # => #<DwollaV2::Response status=200 headers={"server"=>"cloudflare-nginx", "date"=>"Mon, 28 Mar 2016 15:30:23 GMT", "content-type"=>"application/vnd.dwolla.v1.hal+json; charset=UTF-8", "content-length"=>"150", "connection"=>"close", "set-cookie"=>"__cfduid=d9dcd0f586c166d36cbd45b992bdaa11b1459179023; expires=Tue, 28-Mar-17 15:30:23 GMT; path=/; domain=.dwolla.com; HttpOnly", "x-request-id"=>"69a4e612-5dae-4c52-a6a0-2f921e34a88a", "cf-ray"=>"28ac1f81875941e3-MSP"} {"_links"=>{"events"=>{"href"=>"https://api-uat.dwolla.com/events"}, "webhook-subscriptions"=>{"href"=>"https://api-uat.dwolla.com/webhook-subscriptions"}}}>
119
217
 
120
- **Response headers**:
218
+ res.status
219
+ # => 200
121
220
 
122
- ```ruby
123
- customer = token.post "/customers", customer_params
124
- customer.headers[:location] # => "https://api.dwolla.com/customers/aa76ca27-1920-4a0a-9bbe-a22085c0010e"
221
+ res.headers
222
+ # => {"server"=>"cloudflare-nginx", "date"=>"Mon, 28 Mar 2016 15:30:23 GMT", "content-type"=>"application/vnd.dwolla.v1.hal+json; charset=UTF-8", "content-length"=>"150", "connection"=>"close", "set-cookie"=>"__cfduid=d9dcd0f586c166d36cbd45b992bdaa11b1459179023; expires=Tue, 28-Mar-17 15:30:23 GMT; path=/; domain=.dwolla.com; HttpOnly", "x-request-id"=>"69a4e612-5dae-4c52-a6a0-2f921e34a88a", "cf-ray"=>"28ac1f81875941e3-MSP"}
223
+
224
+ res._links.events.href
225
+ # => "https://api-uat.dwolla.com/events"
125
226
  ```
126
227
 
127
- **Response body**:
228
+ ## Errors
128
229
 
129
- [Enumerable methods](http://ruby-doc.org/core-2.3.0/Enumerable.html), `#==`, and `#[]` are
130
- forwarded to a response's body. For example:
230
+ If the server returns an error, a `DwollaV2::Error` (or one of its subclasses) will be raised.
231
+ `DwollaV2::Error`s are similar to `DwollaV2::Response`s.
131
232
 
132
233
  ```ruby
133
- customer = token.get "/customers/aa76ca27-1920-4a0a-9bbe-a22085c0010e"
134
-
135
- customer.collect {|k,v| k } # => [:_links, :id, ...]
136
- customer.body.collect {|k,v| k } # => [:_links, :id, ...]
137
-
138
- customer == customer.body # => true
139
-
140
- customer[:name] # => John Doe
141
- customer.body[:name] # => John Doe
234
+ begin
235
+ token.get "/not-found"
236
+ rescue DwollaV2::NotFoundError => e
237
+ e
238
+ # => #<DwollaV2::NotFoundError status=404 headers={"server"=>"cloudflare-nginx", "date"=>"Mon, 28 Mar 2016 15:35:32 GMT", "content-type"=>"application/vnd.dwolla.v1.hal+json; profile=\"http://nocarrier.co.uk/profiles/vnd.error/\"; charset=UTF-8", "content-length"=>"69", "connection"=>"close", "set-cookie"=>"__cfduid=da1478bfdf3e56275cd8a6a741866ccce1459179332; expires=Tue, 28-Mar-17 15:35:32 GMT; path=/; domain=.dwolla.com; HttpOnly", "access-control-allow-origin"=>"*", "x-request-id"=>"667fca74-b53d-43db-bddd-50426a011881", "cf-ray"=>"28ac270abca64207-MSP"} {"code"=>"NotFound", "message"=>"The requested resource was not found."}>
239
+
240
+ e.status
241
+ # => 404
242
+
243
+ e.headers
244
+ # => {"server"=>"cloudflare-nginx", "date"=>"Mon, 28 Mar 2016 15:35:32 GMT", "content-type"=>"application/vnd.dwolla.v1.hal+json; profile=\"http://nocarrier.co.uk/profiles/vnd.error/\"; charset=UTF-8", "content-length"=>"69", "connection"=>"close", "set-cookie"=>"__cfduid=da1478bfdf3e56275cd8a6a741866ccce1459179332; expires=Tue, 28-Mar-17 15:35:32 GMT; path=/; domain=.dwolla.com; HttpOnly", "access-control-allow-origin"=>"*", "x-request-id"=>"667fca74-b53d-43db-bddd-50426a011881", "cf-ray"=>"28ac270abca64207-MSP"}
245
+
246
+ e.code
247
+ # => "NotFound"
248
+ rescue DwollaV2::Error => e
249
+ # ...
250
+ end
142
251
  ```
143
252
 
144
- #### Errors
145
-
146
- All errors inherit from `DwollaV2::Error`
253
+ ### `DwollaV2::Error` subclasses:
147
254
 
148
- OAuth errors have `error`, `error_description`, and `error_uri` attributes.
149
-
150
- Dwolla API errors have `code`, `message`, `_links`, and `_embedded` attributes.
151
-
152
- **Error list:**
255
+ *See https://docsv2.dwolla.com/#errors for more info.*
153
256
 
154
257
  - `DwollaV2::AccessDeniedError`
155
258
  - `DwollaV2::InvalidCredentialsError`
@@ -176,6 +279,15 @@ Dwolla API errors have `code`, `message`, `_links`, and `_embedded` attributes.
176
279
  - `DwollaV2::MethodNotAllowedError`
177
280
  - `DwollaV2::ValidationError`
178
281
 
282
+ ## Sample code
283
+
284
+ The following gist contains some sample bootstrapping code:
285
+
286
+ https://gist.github.com/sausman/df58a196b3bc0381b0e8
287
+
288
+ If you have any questions regarding your specific implementation we'll do our best to help
289
+ at https://discuss.dwolla.com/.
290
+
179
291
  ## Development
180
292
 
181
293
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -192,6 +304,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
192
304
 
193
305
  ## Changelog
194
306
 
307
+ - **1.0.0** - Refactor `Error` class to be more like response, add ability to access keys using methods.
195
308
  - **0.4.0** - Refactor and document how `DwollaV2::Response` works
196
309
  - **0.3.1** - better `DwollaV2::Error` error messages
197
310
  - **0.3.0** - ISO8601 values in response body are converted to `Time` objects
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "rspec", "~> 3.0"
25
25
  spec.add_development_dependency "webmock", "~> 1.22"
26
26
 
27
+ spec.add_dependency "hashie", "~> 3.4"
27
28
  spec.add_dependency "faraday", "~> 0.9"
28
29
  spec.add_dependency "faraday_middleware", "~> 0.10"
29
30
  end
@@ -2,6 +2,9 @@ require "base64"
2
2
  require "uri"
3
3
  require "json"
4
4
  require "forwardable"
5
+ require "ostruct"
6
+
7
+ require "hashie"
5
8
 
6
9
  require "faraday"
7
10
  require "faraday_middleware"
@@ -14,9 +17,10 @@ require "dwolla_v2/token"
14
17
  require "dwolla_v2/response"
15
18
  require "dwolla_v2/error"
16
19
  require "dwolla_v2/util"
20
+ require "dwolla_v2/super_hash"
17
21
 
18
- require "dwolla_v2/middleware/symbolize_response_body"
19
- require "dwolla_v2/middleware/parse_iso8601_response_body"
22
+ require "dwolla_v2/middleware/deep_parse_iso8601_response_body"
23
+ require "dwolla_v2/middleware/deep_super_hasherize_response_body"
20
24
  require "dwolla_v2/middleware/handle_errors"
21
25
 
22
26
  # OAuth errors https://tools.ietf.org/html/rfc6749
@@ -46,11 +46,10 @@ module DwollaV2
46
46
 
47
47
  def self.request_token client, params
48
48
  res = client.conn.post client.token_url, params
49
- res_body = Util.deep_symbolize_keys res.body
50
- if res.status >= 400 || (res_body.is_a?(Hash) && res_body.has_key?(:error))
51
- Error.raise! res_body
49
+ if !res.body.is_a?(Hash) || res.body.has_key?(:error)
50
+ Error.raise! res
52
51
  else
53
- token = Token.new client, res_body
52
+ token = Token.new client, res.body
54
53
  client.on_grant.call token unless client.on_grant.nil?
55
54
  token
56
55
  end
@@ -1,7 +1,7 @@
1
1
  module DwollaV2
2
2
  class Client
3
3
  ENVIRONMENTS = {
4
- :default => {
4
+ :production => {
5
5
  :auth_url => "https://www.dwolla.com/oauth/v2/authenticate",
6
6
  :token_url => "https://www.dwolla.com/oauth/v2/token",
7
7
  :api_url => "https://api.dwolla.com"
@@ -28,13 +28,14 @@ module DwollaV2
28
28
  end
29
29
 
30
30
  def environment= env
31
+ env = :"#{env}"
31
32
  raise ArgumentError.new "invalid environment" unless ENVIRONMENTS.has_key? env
32
33
  @environment = env
33
34
  end
34
35
 
35
36
  def environment env = nil
36
37
  self.environment = env unless env.nil?
37
- @environment || :default
38
+ @environment || :production
38
39
  end
39
40
 
40
41
  def on_grant &callback
@@ -51,6 +52,9 @@ module DwollaV2
51
52
  @conn ||= Faraday.new do |f|
52
53
  f.request :basic_auth, id, secret
53
54
  f.request :url_encoded
55
+ f.use HandleErrors
56
+ f.use DeepSuperHasherizeResponseBody
57
+ f.use DeepParseIso8601ResponseBody
54
58
  f.response :json, :content_type => /\bjson$/
55
59
  faraday.call(f) if faraday
56
60
  f.adapter Faraday.default_adapter unless faraday
@@ -68,5 +72,9 @@ module DwollaV2
68
72
  def api_url
69
73
  ENVIRONMENTS[environment][:api_url]
70
74
  end
75
+
76
+ def inspect
77
+ Util.pretty_inspect self.class.name, id: id, secret: secret, environment: environment
78
+ end
71
79
  end
72
80
  end
@@ -1,38 +1,83 @@
1
1
  module DwollaV2
2
2
  class Error < StandardError
3
- def self.raise! error
4
- raise self.new unless error.is_a? Hash
5
- if klass = error_class(error[:error] || error[:code])
6
- raise klass.new error
3
+ extend Forwardable
4
+
5
+ delegate [:status] => :@response
6
+ delegate [:to_s, :to_json] => :response_body
7
+
8
+ def self.raise! response
9
+ unless response.respond_to? :body
10
+ response = turn_into_response(response)
11
+ end
12
+ if response.body.is_a?(Hash) && klass = error_class(response.body[:error] || response.body[:code])
13
+ raise klass, response, caller
7
14
  else
8
- raise self.new error
15
+ raise self, response, caller
9
16
  end
10
17
  end
11
18
 
12
- def initialize error = nil
13
- @error = error
19
+ def initialize response
20
+ @response = response
14
21
  end
15
22
 
16
- def message
17
- @error[:message]
23
+ def headers
24
+ if @response.respond_to? :response_headers
25
+ @response.response_headers
26
+ elsif @response.respond_to? :headers
27
+ @response.headers
28
+ end
29
+ end
30
+
31
+ def respond_to? method, include_private = false
32
+ super || response_body.respond_to?(method)
33
+ end
34
+
35
+ def is_a? klass
36
+ super || response_body.is_a?(klass)
37
+ end
38
+
39
+ def kind_of? klass
40
+ super || response_body.kind_of?(klass)
18
41
  end
19
42
 
20
- def [] key
21
- @error[key] rescue nil
43
+ def == other
44
+ super || response_body == other
22
45
  end
23
46
 
24
- def method_missing method
25
- @error[method] rescue nil
47
+ def method_missing method, *args, &block
48
+ if response_body.respond_to? method
49
+ response_body.public_send method, *args, &block
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ def message
56
+ to_s
26
57
  end
27
58
 
28
- def to_s
29
- @error.to_s
59
+ def to_str
60
+ nil
61
+ end
62
+
63
+ def inspect
64
+ Util.pretty_inspect self.class.name, { status: status, headers: headers }, response_body
30
65
  end
31
66
 
32
67
  private
33
68
 
69
+ def self.turn_into_response response
70
+ OpenStruct.new status: nil,
71
+ headers: nil,
72
+ body: Util.deep_super_hasherize(Util.deep_parse_iso8601_values response)
73
+ end
74
+
34
75
  def self.error_class error_code
35
76
  DwollaV2.const_get "#{Util.classify(error_code).chomp("Error")}Error" rescue false
36
77
  end
78
+
79
+ def response_body
80
+ @response.body
81
+ end
37
82
  end
38
83
  end
@@ -1,5 +1,5 @@
1
1
  module DwollaV2
2
- class ParseIso8601ResponseBody
2
+ class DeepParseIso8601ResponseBody
3
3
  def initialize app
4
4
  @app = app
5
5
  end
@@ -1,12 +1,12 @@
1
1
  module DwollaV2
2
- class SymbolizeResponseBody
2
+ class DeepSuperHasherizeResponseBody
3
3
  def initialize app
4
4
  @app = app
5
5
  end
6
6
 
7
7
  def call request_env
8
8
  @app.call(request_env).on_complete do |response_env|
9
- response_env.body = Util.deep_symbolize_keys(response_env.body)
9
+ response_env.body = Util.deep_super_hasherize(response_env.body)
10
10
  end
11
11
  end
12
12
  end
@@ -6,7 +6,7 @@ module DwollaV2
6
6
 
7
7
  def call request_env
8
8
  @app.call(request_env).on_complete do |response_env|
9
- Error.raise!(response_env.body) if response_env.status >= 400
9
+ Error.raise!(response_env) if response_env.status >= 400
10
10
  end
11
11
  end
12
12
  end
@@ -2,23 +2,49 @@ module DwollaV2
2
2
  class Response
3
3
  extend Forwardable
4
4
 
5
- # http://ruby-doc.org/core-2.3.0/Enumerable.html
6
- ENUMERABLE = \
7
- [:all?, :any?, :chunk, :chunk_while, :collect, :collect_concat, :count, :cycle,
8
- :detect, :drop, :drop_while, :each_cons, :each_entry, :each_slice, :each_with_index,
9
- :each_with_object, :entries, :find, :find_all, :find_index, :first, :flat_map, :grep,
10
- :grep_v, :group_by, :include?, :inject, :lazy, :map, :max, :max_by, :member?, :min,
11
- :min_by, :minmax, :minmax_by, :none?, :one?, :partition, :reduce, :reject,
12
- :reverse_each, :select, :slice_after, :slice_before, :slice_when, :sort, :sort_by,
13
- :take, :take_while, :to_a, :to_h, :zip]
14
-
15
- delegate [:status, :headers, :body] => :@response
16
- delegate [*ENUMERABLE, :==, :[]] => :response_body
5
+ delegate [:status] => :@response
6
+ delegate [:to_s, :to_json] => :response_body
17
7
 
18
8
  def initialize response
19
9
  @response = response
20
10
  end
21
11
 
12
+ def headers
13
+ if @response.respond_to? :response_headers
14
+ @response.response_headers
15
+ elsif @response.respond_to? :headers
16
+ @response.headers
17
+ end
18
+ end
19
+
20
+ def respond_to? method, include_private = false
21
+ super || response_body.respond_to?(method)
22
+ end
23
+
24
+ def is_a? klass
25
+ super || response_body.is_a?(klass)
26
+ end
27
+
28
+ def kind_of? klass
29
+ super || response_body.kind_of?(klass)
30
+ end
31
+
32
+ def == other
33
+ super || response_body == other
34
+ end
35
+
36
+ def method_missing method, *args, &block
37
+ if response_body.respond_to? method
38
+ response_body.public_send method, *args, &block
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def inspect
45
+ Util.pretty_inspect self.class.name, { status: status, headers: headers }, response_body
46
+ end
47
+
22
48
  private
23
49
 
24
50
  def response_body
@@ -0,0 +1,12 @@
1
+ module DwollaV2
2
+ class SuperHash < Hash
3
+ include Hashie::Extensions::KeyConversion
4
+ include Hashie::Extensions::MethodAccess
5
+ include Hashie::Extensions::IndifferentAccess
6
+ include Hashie::Extensions::DeepFetch
7
+
8
+ def == other
9
+ super(other) || super(self.class[other])
10
+ end
11
+ end
12
+ end
@@ -43,6 +43,12 @@ module DwollaV2
43
43
  end
44
44
  end
45
45
 
46
+ def inspect
47
+ Util.pretty_inspect self.class.name,
48
+ client: client, access_token: access_token, refresh_token: refresh_token,
49
+ expires_in: expires_in, scope: scope, app_id: app_id, account_id: account_id
50
+ end
51
+
46
52
  private
47
53
 
48
54
  def conn
@@ -52,8 +58,8 @@ module DwollaV2
52
58
  f.request :multipart
53
59
  f.request :json
54
60
  f.use HandleErrors
55
- f.use ParseIso8601ResponseBody
56
- f.use SymbolizeResponseBody
61
+ f.use DeepSuperHasherizeResponseBody
62
+ f.use DeepParseIso8601ResponseBody
57
63
  f.response :json, :content_type => /\bjson$/
58
64
  client.faraday.call(f) if client.faraday
59
65
  f.adapter Faraday.default_adapter unless client.faraday
@@ -1,10 +1,10 @@
1
1
  module DwollaV2
2
2
  module Util
3
- def self.deep_symbolize_keys obj
3
+ def self.deep_super_hasherize obj
4
4
  if obj.is_a? Hash
5
- Hash[obj.map{|k,v| [k.to_sym, deep_symbolize_keys(v)] }]
5
+ SuperHash[obj.map{|k,v| [k, deep_super_hasherize(v)] }]
6
6
  elsif obj.is_a? Array
7
- obj.map {|i| deep_symbolize_keys(i) }
7
+ obj.map {|i| deep_super_hasherize(i) }
8
8
  else
9
9
  obj
10
10
  end
@@ -27,5 +27,14 @@ module DwollaV2
27
27
  i.sub(/^(.)/) { $1.capitalize }
28
28
  end.join
29
29
  end
30
+
31
+ def self.pretty_inspect klass_name, attrs, append = nil
32
+ [
33
+ "#<#{klass_name}",
34
+ attrs.map {|k,v| " #{k}=#{v.inspect}" unless v.nil? },
35
+ (" #{append.is_a?(String) ? append.inspect : append}" unless append.nil?),
36
+ ">"
37
+ ].flatten.join
38
+ end
30
39
  end
31
40
  end
@@ -1,3 +1,3 @@
1
1
  module DwollaV2
2
- VERSION = "0.4.0"
2
+ VERSION = "1.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dwolla_v2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ausman
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-08 00:00:00.000000000 Z
11
+ date: 2016-03-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.22'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hashie
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: faraday
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -139,11 +153,12 @@ files:
139
153
  - lib/dwolla_v2/errors/unsupported_grant_type_error.rb
140
154
  - lib/dwolla_v2/errors/unsupported_response_type_error.rb
141
155
  - lib/dwolla_v2/errors/validation_error.rb
156
+ - lib/dwolla_v2/middleware/deep_parse_iso8601_response_body.rb
157
+ - lib/dwolla_v2/middleware/deep_super_hasherize_response_body.rb
142
158
  - lib/dwolla_v2/middleware/handle_errors.rb
143
- - lib/dwolla_v2/middleware/parse_iso8601_response_body.rb
144
- - lib/dwolla_v2/middleware/symbolize_response_body.rb
145
159
  - lib/dwolla_v2/portal.rb
146
160
  - lib/dwolla_v2/response.rb
161
+ - lib/dwolla_v2/super_hash.rb
147
162
  - lib/dwolla_v2/token.rb
148
163
  - lib/dwolla_v2/util.rb
149
164
  - lib/dwolla_v2/version.rb