dwolla-ruby 2.9.0 → 3.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.
- checksums.yaml +4 -4
- data/.gitignore +8 -8
- data/.travis.yml +11 -6
- data/Gemfile +1 -1
- data/README.md +225 -222
- data/Rakefile +8 -8
- data/dwolla-ruby.gemspec +27 -27
- data/examples/balance.rb +15 -15
- data/examples/contacts.rb +32 -32
- data/examples/fundingSources.rb +39 -39
- data/examples/oauth.rb +59 -59
- data/examples/offsiteGateway.rb +31 -31
- data/examples/transactions.rb +72 -72
- data/examples/users.rb +30 -30
- data/gemfiles/json.gemfile +2 -2
- data/lib/dwolla.rb +319 -327
- data/lib/dwolla/accounts.rb +27 -27
- data/lib/dwolla/balance.rb +15 -15
- data/lib/dwolla/contacts.rb +30 -30
- data/lib/dwolla/errors/api_connection_error.rb +3 -3
- data/lib/dwolla/errors/api_error.rb +3 -3
- data/lib/dwolla/errors/authentication_error.rb +3 -3
- data/lib/dwolla/errors/dwolla_error.rb +19 -19
- data/lib/dwolla/errors/invalid_request_error.rb +10 -10
- data/lib/dwolla/errors/missing_parameter_error.rb +3 -3
- data/lib/dwolla/exceptions.rb +4 -4
- data/lib/dwolla/funding_sources.rb +65 -65
- data/lib/dwolla/json.rb +20 -20
- data/lib/dwolla/masspay.rb +52 -52
- data/lib/dwolla/oauth.rb +84 -84
- data/lib/dwolla/offsite_gateway.rb +152 -152
- data/lib/dwolla/requests.rb +56 -56
- data/lib/dwolla/transactions.rb +108 -108
- data/lib/dwolla/users.rb +39 -39
- data/lib/dwolla/version.rb +3 -3
- data/test/test_accounts.rb +18 -18
- data/test/test_balance.rb +9 -9
- data/test/test_contacts.rb +19 -19
- data/test/test_dwolla.rb +62 -0
- data/test/test_funding_sources.rb +64 -64
- data/test/test_masspay.rb +47 -47
- data/test/test_oauth.rb +36 -36
- data/test/test_offsite_gateway.rb +57 -57
- data/test/test_requests.rb +29 -29
- data/test/test_transactions.rb +117 -117
- data/test/test_users.rb +33 -33
- metadata +5 -3
data/lib/dwolla/oauth.rb
CHANGED
@@ -1,85 +1,85 @@
|
|
1
|
-
module Dwolla
|
2
|
-
class OAuth
|
3
|
-
def self.get_auth_url(redirect_uri=nil, scope=Dwolla::scope, verified_account=false)
|
4
|
-
raise AuthenticationError.new('No Api Key Provided.') unless Dwolla::api_key
|
5
|
-
|
6
|
-
params = {
|
7
|
-
:scope => scope,
|
8
|
-
:response_type => 'code',
|
9
|
-
:client_id => Dwolla::api_key,
|
10
|
-
:verified_account => verified_account
|
11
|
-
}
|
12
|
-
|
13
|
-
params['redirect_uri'] = redirect_uri unless redirect_uri.nil?
|
14
|
-
|
15
|
-
uri = Addressable::URI.new
|
16
|
-
uri.query_values = params
|
17
|
-
|
18
|
-
if Dwolla::debug and Dwolla::sandbox
|
19
|
-
puts "[DWOLLA SANDBOX MODE OPERATION]"
|
20
|
-
end
|
21
|
-
|
22
|
-
return auth_url + '?' + uri.query
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.get_token(code=nil, redirect_uri=nil)
|
26
|
-
raise MissingParameterError.new('No Code Provided.') if code.nil?
|
27
|
-
|
28
|
-
params = {
|
29
|
-
:grant_type => 'authorization_code',
|
30
|
-
:code => code
|
31
|
-
}
|
32
|
-
|
33
|
-
# I realize this is ugly, but the unit tests fail
|
34
|
-
# if the key is accessed["like_this"] because the
|
35
|
-
# hash is compared with "quotes" and not :like_this.
|
36
|
-
|
37
|
-
# It may very well be my Ruby version
|
38
|
-
# TODO: Revisit this
|
39
|
-
(params = params.merge({:redirect_uri => redirect_uri})) unless redirect_uri.nil?
|
40
|
-
|
41
|
-
resp = Dwolla.request(:post, token_url, params, {}, false, false, true)
|
42
|
-
|
43
|
-
# TODO: Revisit this to make it more unit test friendly, fails ['error_description'] due to
|
44
|
-
# key not existing, same on L58
|
45
|
-
return "No data received." unless resp.is_a?(Hash)
|
46
|
-
raise APIError.new(resp['error_description']) unless resp.has_key?('access_token') and resp.has_key?('refresh_token')
|
47
|
-
|
48
|
-
return resp
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.refresh_auth(refresh_token=nil)
|
52
|
-
raise MissingParameterError.new('No Refresh Token Provided') if refresh_token.nil?
|
53
|
-
|
54
|
-
params = {
|
55
|
-
:grant_type => 'refresh_token',
|
56
|
-
:refresh_token => refresh_token
|
57
|
-
}
|
58
|
-
|
59
|
-
resp = Dwolla.request(:post, token_url, params, {}, false, false, true)
|
60
|
-
|
61
|
-
return "No data received." unless resp.is_a?(Hash)
|
62
|
-
raise APIError.new(resp['error_description']) unless resp.has_key?('access_token') and resp.has_key?('refresh_token')
|
63
|
-
|
64
|
-
return resp
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.catalog(token=nil)
|
68
|
-
resp = Dwolla.request(:get, '/catalog', {}, {}, token, false, false)
|
69
|
-
|
70
|
-
return "No data received." unless resp.is_a?(Hash)
|
71
|
-
raise APIError.new(resp['Message']) unless resp.has_key?('_links')
|
72
|
-
return resp['_links']
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def self.auth_url
|
78
|
-
Dwolla.hostname + '/oauth/v2/authenticate'
|
79
|
-
end
|
80
|
-
|
81
|
-
def self.token_url
|
82
|
-
Dwolla.hostname + '/oauth/v2/token'
|
83
|
-
end
|
84
|
-
end
|
1
|
+
module Dwolla
|
2
|
+
class OAuth
|
3
|
+
def self.get_auth_url(redirect_uri=nil, scope=Dwolla::scope, verified_account=false)
|
4
|
+
raise AuthenticationError.new('No Api Key Provided.') unless Dwolla::api_key
|
5
|
+
|
6
|
+
params = {
|
7
|
+
:scope => scope,
|
8
|
+
:response_type => 'code',
|
9
|
+
:client_id => Dwolla::api_key,
|
10
|
+
:verified_account => verified_account
|
11
|
+
}
|
12
|
+
|
13
|
+
params['redirect_uri'] = redirect_uri unless redirect_uri.nil?
|
14
|
+
|
15
|
+
uri = Addressable::URI.new
|
16
|
+
uri.query_values = params
|
17
|
+
|
18
|
+
if Dwolla::debug and Dwolla::sandbox
|
19
|
+
puts "[DWOLLA SANDBOX MODE OPERATION]"
|
20
|
+
end
|
21
|
+
|
22
|
+
return auth_url + '?' + uri.query
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.get_token(code=nil, redirect_uri=nil)
|
26
|
+
raise MissingParameterError.new('No Code Provided.') if code.nil?
|
27
|
+
|
28
|
+
params = {
|
29
|
+
:grant_type => 'authorization_code',
|
30
|
+
:code => code
|
31
|
+
}
|
32
|
+
|
33
|
+
# I realize this is ugly, but the unit tests fail
|
34
|
+
# if the key is accessed["like_this"] because the
|
35
|
+
# hash is compared with "quotes" and not :like_this.
|
36
|
+
|
37
|
+
# It may very well be my Ruby version
|
38
|
+
# TODO: Revisit this
|
39
|
+
(params = params.merge({:redirect_uri => redirect_uri})) unless redirect_uri.nil?
|
40
|
+
|
41
|
+
resp = Dwolla.request(:post, token_url, params, {}, false, false, true)
|
42
|
+
|
43
|
+
# TODO: Revisit this to make it more unit test friendly, fails ['error_description'] due to
|
44
|
+
# key not existing, same on L58
|
45
|
+
return "No data received." unless resp.is_a?(Hash)
|
46
|
+
raise APIError.new(resp['error_description']) unless resp.has_key?('access_token') and resp.has_key?('refresh_token')
|
47
|
+
|
48
|
+
return resp
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.refresh_auth(refresh_token=nil)
|
52
|
+
raise MissingParameterError.new('No Refresh Token Provided') if refresh_token.nil?
|
53
|
+
|
54
|
+
params = {
|
55
|
+
:grant_type => 'refresh_token',
|
56
|
+
:refresh_token => refresh_token
|
57
|
+
}
|
58
|
+
|
59
|
+
resp = Dwolla.request(:post, token_url, params, {}, false, false, true)
|
60
|
+
|
61
|
+
return "No data received." unless resp.is_a?(Hash)
|
62
|
+
raise APIError.new(resp['error_description']) unless resp.has_key?('access_token') and resp.has_key?('refresh_token')
|
63
|
+
|
64
|
+
return resp
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.catalog(token=nil)
|
68
|
+
resp = Dwolla.request(:get, '/catalog', {}, {}, token, false, false)
|
69
|
+
|
70
|
+
return "No data received." unless resp.is_a?(Hash)
|
71
|
+
raise APIError.new(resp['Message']) unless resp.has_key?('_links')
|
72
|
+
return resp['_links']
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def self.auth_url
|
78
|
+
Dwolla.hostname + '/oauth/v2/authenticate'
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.token_url
|
82
|
+
Dwolla.hostname + '/oauth/v2/token'
|
83
|
+
end
|
84
|
+
end
|
85
85
|
end
|
@@ -1,152 +1,152 @@
|
|
1
|
-
module Dwolla
|
2
|
-
class OffsiteGateway
|
3
|
-
@products = []
|
4
|
-
@discount = 0
|
5
|
-
@tax = 0
|
6
|
-
@shipping = 0
|
7
|
-
@notes = nil
|
8
|
-
@facilitator_amount = nil
|
9
|
-
@test_mode = false
|
10
|
-
@allow_funding_sources = true
|
11
|
-
@additional_funding_sources = true
|
12
|
-
@order_id = nil
|
13
|
-
|
14
|
-
def self.clear_session
|
15
|
-
@products = []
|
16
|
-
@discount = 0
|
17
|
-
@tax = 0
|
18
|
-
@shipping = 0
|
19
|
-
@notes = nil
|
20
|
-
@facilitator_amount = nil
|
21
|
-
@test_mode = false
|
22
|
-
@allow_funding_sources = true
|
23
|
-
@additional_funding_sources = true
|
24
|
-
@order_id = nil
|
25
|
-
end
|
26
|
-
|
27
|
-
class << self
|
28
|
-
attr_writer :tax
|
29
|
-
attr_writer :shipping
|
30
|
-
attr_writer :notes
|
31
|
-
attr_writer :order_id
|
32
|
-
attr_writer :redirect
|
33
|
-
attr_writer :callback
|
34
|
-
attr_writer :test_mode
|
35
|
-
attr_writer :allow_funding_sources
|
36
|
-
attr_writer :additional_funding_sources
|
37
|
-
attr_writer :facilitator_amount
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.add_product(name=nil, description=nil, price=nil, quantity=1)
|
41
|
-
@products.push({
|
42
|
-
:name => name,
|
43
|
-
:description => description,
|
44
|
-
:price => price,
|
45
|
-
:quantity => quantity
|
46
|
-
})
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.set_customer_info(first_name=nil, last_name=nil, email=nil, city=nil, state=nil, zip=nil)
|
50
|
-
@customerInfo = {
|
51
|
-
:firstName => first_name,
|
52
|
-
:lastName => last_name,
|
53
|
-
:email => email,
|
54
|
-
:city => city,
|
55
|
-
:state => state,
|
56
|
-
:zip => zip
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.discount=(discount)
|
61
|
-
@discount = -(discount.abs)
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.get_checkout_url(destinationId)
|
65
|
-
params = {
|
66
|
-
:key => Dwolla::api_key,
|
67
|
-
:secret => Dwolla::api_secret,
|
68
|
-
:allowFundingSources => @allow_funding_sources,
|
69
|
-
:additionalFundingSources => @additional_funding_sources,
|
70
|
-
:test => @test_mode,
|
71
|
-
:callback => @callback,
|
72
|
-
:redirect => @redirect,
|
73
|
-
:orderId => @order_id,
|
74
|
-
:notes => @notes,
|
75
|
-
:purchaseOrder => {
|
76
|
-
:customerInfo => @customerInfo,
|
77
|
-
:destinationId => destinationId,
|
78
|
-
:orderItems => @products,
|
79
|
-
:facilitatorAmount => @facilitator_amount,
|
80
|
-
:discount => @discount,
|
81
|
-
:shipping => @shipping,
|
82
|
-
:tax => @tax,
|
83
|
-
:total => self.calculate_total
|
84
|
-
}
|
85
|
-
}
|
86
|
-
|
87
|
-
resp = Dwolla.request(:post, request_url, params, {}, false, false, true)
|
88
|
-
|
89
|
-
return "No data received." unless resp.is_a?(Hash)
|
90
|
-
raise APIError.new(resp['Message']) unless resp.has_key?('Result') and resp['Result'] == 'Success'
|
91
|
-
|
92
|
-
return checkout_url + resp['CheckoutId']
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.read_callback(body)
|
96
|
-
data = JSON.load(body)
|
97
|
-
verify_callback_signature(data['signature'], data['checkoutId'], data['amount'])
|
98
|
-
|
99
|
-
return data
|
100
|
-
end
|
101
|
-
|
102
|
-
def self.validate_webhook(signature, body)
|
103
|
-
verify_webhook_signature(signature, body)
|
104
|
-
end
|
105
|
-
|
106
|
-
private
|
107
|
-
def self.verify_callback_signature(candidate=nil, checkout_id=nil, amount=nil)
|
108
|
-
key = "#{checkout_id}&#{amount}"
|
109
|
-
digest = OpenSSL::Digest.new('sha1')
|
110
|
-
signature = OpenSSL::HMAC.hexdigest(digest, Dwolla::api_secret, key)
|
111
|
-
|
112
|
-
raise APIError.new("Invalid callback signature (#{candidate} vs #{signature})") unless candidate == signature
|
113
|
-
end
|
114
|
-
|
115
|
-
def self.verify_webhook_signature(candidate=nil, body=nil)
|
116
|
-
digest = OpenSSL::Digest.new('sha1')
|
117
|
-
signature = OpenSSL::HMAC.hexdigest(digest, Dwolla::api_secret, body)
|
118
|
-
|
119
|
-
raise APIError.new("Invalid Webhook signature (#{candidate} vs #{signature})") unless candidate == signature
|
120
|
-
end
|
121
|
-
|
122
|
-
def self.request_url
|
123
|
-
if Dwolla::sandbox
|
124
|
-
return 'https://
|
125
|
-
else
|
126
|
-
return 'https://www.dwolla.com/payment/request'
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def self.checkout_url
|
131
|
-
if Dwolla::sandbox
|
132
|
-
return 'https://
|
133
|
-
else
|
134
|
-
return 'https://www.dwolla.com/payment/checkout/'
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.calculate_total
|
139
|
-
total = 0.0
|
140
|
-
|
141
|
-
@products.each { |product|
|
142
|
-
total += product[:price] * product[:quantity]
|
143
|
-
}
|
144
|
-
|
145
|
-
total += @shipping
|
146
|
-
total += @tax
|
147
|
-
total += @discount
|
148
|
-
|
149
|
-
return total.round(2)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
1
|
+
module Dwolla
|
2
|
+
class OffsiteGateway
|
3
|
+
@products = []
|
4
|
+
@discount = 0
|
5
|
+
@tax = 0
|
6
|
+
@shipping = 0
|
7
|
+
@notes = nil
|
8
|
+
@facilitator_amount = nil
|
9
|
+
@test_mode = false
|
10
|
+
@allow_funding_sources = true
|
11
|
+
@additional_funding_sources = true
|
12
|
+
@order_id = nil
|
13
|
+
|
14
|
+
def self.clear_session
|
15
|
+
@products = []
|
16
|
+
@discount = 0
|
17
|
+
@tax = 0
|
18
|
+
@shipping = 0
|
19
|
+
@notes = nil
|
20
|
+
@facilitator_amount = nil
|
21
|
+
@test_mode = false
|
22
|
+
@allow_funding_sources = true
|
23
|
+
@additional_funding_sources = true
|
24
|
+
@order_id = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
attr_writer :tax
|
29
|
+
attr_writer :shipping
|
30
|
+
attr_writer :notes
|
31
|
+
attr_writer :order_id
|
32
|
+
attr_writer :redirect
|
33
|
+
attr_writer :callback
|
34
|
+
attr_writer :test_mode
|
35
|
+
attr_writer :allow_funding_sources
|
36
|
+
attr_writer :additional_funding_sources
|
37
|
+
attr_writer :facilitator_amount
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.add_product(name=nil, description=nil, price=nil, quantity=1)
|
41
|
+
@products.push({
|
42
|
+
:name => name,
|
43
|
+
:description => description,
|
44
|
+
:price => price,
|
45
|
+
:quantity => quantity
|
46
|
+
})
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.set_customer_info(first_name=nil, last_name=nil, email=nil, city=nil, state=nil, zip=nil)
|
50
|
+
@customerInfo = {
|
51
|
+
:firstName => first_name,
|
52
|
+
:lastName => last_name,
|
53
|
+
:email => email,
|
54
|
+
:city => city,
|
55
|
+
:state => state,
|
56
|
+
:zip => zip
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.discount=(discount)
|
61
|
+
@discount = -(discount.abs)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.get_checkout_url(destinationId)
|
65
|
+
params = {
|
66
|
+
:key => Dwolla::api_key,
|
67
|
+
:secret => Dwolla::api_secret,
|
68
|
+
:allowFundingSources => @allow_funding_sources,
|
69
|
+
:additionalFundingSources => @additional_funding_sources,
|
70
|
+
:test => @test_mode,
|
71
|
+
:callback => @callback,
|
72
|
+
:redirect => @redirect,
|
73
|
+
:orderId => @order_id,
|
74
|
+
:notes => @notes,
|
75
|
+
:purchaseOrder => {
|
76
|
+
:customerInfo => @customerInfo,
|
77
|
+
:destinationId => destinationId,
|
78
|
+
:orderItems => @products,
|
79
|
+
:facilitatorAmount => @facilitator_amount,
|
80
|
+
:discount => @discount,
|
81
|
+
:shipping => @shipping,
|
82
|
+
:tax => @tax,
|
83
|
+
:total => self.calculate_total
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
resp = Dwolla.request(:post, request_url, params, {}, false, false, true)
|
88
|
+
|
89
|
+
return "No data received." unless resp.is_a?(Hash)
|
90
|
+
raise APIError.new(resp['Message']) unless resp.has_key?('Result') and resp['Result'] == 'Success'
|
91
|
+
|
92
|
+
return checkout_url + resp['CheckoutId']
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.read_callback(body)
|
96
|
+
data = JSON.load(body)
|
97
|
+
verify_callback_signature(data['signature'], data['checkoutId'], data['amount'])
|
98
|
+
|
99
|
+
return data
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.validate_webhook(signature, body)
|
103
|
+
verify_webhook_signature(signature, body)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def self.verify_callback_signature(candidate=nil, checkout_id=nil, amount=nil)
|
108
|
+
key = "#{checkout_id}&#{amount}"
|
109
|
+
digest = OpenSSL::Digest.new('sha1')
|
110
|
+
signature = OpenSSL::HMAC.hexdigest(digest, Dwolla::api_secret, key)
|
111
|
+
|
112
|
+
raise APIError.new("Invalid callback signature (#{candidate} vs #{signature})") unless candidate == signature
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.verify_webhook_signature(candidate=nil, body=nil)
|
116
|
+
digest = OpenSSL::Digest.new('sha1')
|
117
|
+
signature = OpenSSL::HMAC.hexdigest(digest, Dwolla::api_secret, body)
|
118
|
+
|
119
|
+
raise APIError.new("Invalid Webhook signature (#{candidate} vs #{signature})") unless candidate == signature
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.request_url
|
123
|
+
if Dwolla::sandbox
|
124
|
+
return 'https://sandbox.dwolla.com/payment/request'
|
125
|
+
else
|
126
|
+
return 'https://www.dwolla.com/payment/request'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.checkout_url
|
131
|
+
if Dwolla::sandbox
|
132
|
+
return 'https://sandbox.dwolla.com/payment/checkout/'
|
133
|
+
else
|
134
|
+
return 'https://www.dwolla.com/payment/checkout/'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.calculate_total
|
139
|
+
total = 0.0
|
140
|
+
|
141
|
+
@products.each { |product|
|
142
|
+
total += product[:price] * product[:quantity]
|
143
|
+
}
|
144
|
+
|
145
|
+
total += @shipping
|
146
|
+
total += @tax
|
147
|
+
total += @discount
|
148
|
+
|
149
|
+
return total.round(2)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|