dwolla-ruby 1.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/.gitignore +7 -0
- data/Gemfile +4 -0
- data/README.md +130 -0
- data/Rakefile +5 -0
- data/dwolla.gemspec +29 -0
- data/examples/_keys.rb +5 -0
- data/examples/accountInfo.rb +31 -0
- data/examples/contacts.rb +27 -0
- data/examples/fundingSources.rb +26 -0
- data/examples/oauth.rb +35 -0
- data/examples/offsiteGateway.rb +1 -0
- data/examples/send.rb +23 -0
- data/lib/dwolla.rb +43 -0
- data/lib/dwolla/client.rb +50 -0
- data/lib/dwolla/connection.rb +47 -0
- data/lib/dwolla/exceptions.rb +4 -0
- data/lib/dwolla/funding_source.rb +18 -0
- data/lib/dwolla/response/follow_redirects.rb +44 -0
- data/lib/dwolla/response/guard_server_error.rb +32 -0
- data/lib/dwolla/response/parse_json.rb +27 -0
- data/lib/dwolla/transaction.rb +55 -0
- data/lib/dwolla/user.rb +98 -0
- data/lib/dwolla/version.rb +3 -0
- data/spec/dwolla/client_spec.rb +27 -0
- data/spec/dwolla/response/follow_redirects_spec.rb +38 -0
- data/spec/dwolla/transaction_spec.rb +181 -0
- data/spec/dwolla/user_spec.rb +260 -0
- data/spec/dwolla_spec.rb +19 -0
- data/spec/fixtures/account_information.json +13 -0
- data/spec/fixtures/balance.json +5 -0
- data/spec/fixtures/basic_information.json +6 -0
- data/spec/fixtures/contacts.json +18 -0
- data/spec/fixtures/error.json +5 -0
- data/spec/fixtures/request_transaction.json +3 -0
- data/spec/fixtures/send_transaction.json +3 -0
- data/spec/fixtures/sources.json +13 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/helpers.rb +29 -0
- metadata +212 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Dwolla
|
2
|
+
class FundingSource
|
3
|
+
attr_accessor :id, :name, :type, :verified
|
4
|
+
def verified?
|
5
|
+
!!verified
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.from_json(options)
|
9
|
+
source = FundingSource.new
|
10
|
+
source.id = options["Id"]
|
11
|
+
source.name = options["Name"]
|
12
|
+
source.type = options["Type"]
|
13
|
+
source.verified = options["Verified"]
|
14
|
+
source
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Dwolla
|
2
|
+
module Response
|
3
|
+
class RedirectLimitReached < Faraday::Error::ClientError
|
4
|
+
attr_reader :response
|
5
|
+
|
6
|
+
def initialize(response)
|
7
|
+
super "too many redirects; last one to: #{response['location']}"
|
8
|
+
@response = response
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class FollowRedirects < Faraday::Middleware
|
13
|
+
REDIRECTS = [301, 302, 303, 307]
|
14
|
+
# default value for max redirects followed
|
15
|
+
FOLLOW_LIMIT = 3
|
16
|
+
|
17
|
+
def initialize(app, options = {})
|
18
|
+
super(app)
|
19
|
+
@options = options
|
20
|
+
@follow_limit = options[:limit] || FOLLOW_LIMIT
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
process_response(@app.call(env), @follow_limit)
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_response(response, follows)
|
28
|
+
response.on_complete do |env|
|
29
|
+
if redirect? response
|
30
|
+
raise RedirectLimitReached, response if follows.zero?
|
31
|
+
env[:url] += response['location']
|
32
|
+
env[:method] = :get
|
33
|
+
response = process_response(@app.call(env), follows - 1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
response
|
37
|
+
end
|
38
|
+
|
39
|
+
def redirect?(response)
|
40
|
+
REDIRECTS.include? response.status
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Dwolla
|
2
|
+
module Response
|
3
|
+
class InternalServerError < Faraday::Error::ClientError
|
4
|
+
attr_reader :response
|
5
|
+
|
6
|
+
def initialize(response)
|
7
|
+
super "Internal Server Error"
|
8
|
+
@response = response
|
9
|
+
end
|
10
|
+
end
|
11
|
+
class AccessDeniedError < Faraday::Error::ClientError
|
12
|
+
attr_reader :response
|
13
|
+
|
14
|
+
def initialize(response)
|
15
|
+
super "Access was denied."
|
16
|
+
@response = response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class GuardServerError < Faraday::Response::Middleware
|
21
|
+
def on_complete(env)
|
22
|
+
if env[:status] == 500
|
23
|
+
if env[:body].match /Access is denied/
|
24
|
+
raise AccessDeniedError, env[:body]
|
25
|
+
else
|
26
|
+
raise InternalServerError, env[:body]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Dwolla
|
2
|
+
module Response
|
3
|
+
class ParseJson < Faraday::Response::Middleware
|
4
|
+
def on_complete(env)
|
5
|
+
if respond_to? :parse
|
6
|
+
env[:body] = parse(env[:body]) unless [204,302,304,307].index env[:status]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse(body)
|
11
|
+
case body
|
12
|
+
when ''
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
response_hash = ::MultiJson.load(body)
|
16
|
+
|
17
|
+
raise Dwolla::RequestException, response_hash["Message"] if response_hash["Success"] == false
|
18
|
+
|
19
|
+
response_hash["Response"] ||
|
20
|
+
response_hash["SendResult"] ||
|
21
|
+
response_hash["RequestResult"] ||
|
22
|
+
response_hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Dwolla
|
2
|
+
class Transaction
|
3
|
+
include Dwolla::Connection
|
4
|
+
@test_mode = false
|
5
|
+
def self.test_mode
|
6
|
+
@test_mode
|
7
|
+
end
|
8
|
+
def self.test_mode=(m)
|
9
|
+
@test_mode = m
|
10
|
+
end
|
11
|
+
|
12
|
+
ENDPOINTS = { :send => 'transactions/send',
|
13
|
+
:request => 'transactions/request' }
|
14
|
+
TEST_ENDPOINTS = { :send => 'testapi/send',
|
15
|
+
:request => 'testapi/request' }
|
16
|
+
|
17
|
+
attr_accessor :origin, :destination, :destination_type, :type, :amount, :pin, :id, :source, :source_type, :description, :funds_source
|
18
|
+
|
19
|
+
def initialize(attrs = {})
|
20
|
+
attrs.each do |key, value|
|
21
|
+
send("#{key}=".to_sym, value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute
|
26
|
+
if self.class.test_mode
|
27
|
+
end_point = TEST_ENDPOINTS[type]
|
28
|
+
else
|
29
|
+
end_point = ENDPOINTS[type]
|
30
|
+
end
|
31
|
+
self.id = post(end_point, to_payload)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def auth_params
|
37
|
+
{ :oauth_token => origin.oauth_token }
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_payload
|
41
|
+
payload = {
|
42
|
+
:amount => amount,
|
43
|
+
:pin => pin
|
44
|
+
}
|
45
|
+
payload[:destinationId] = destination if destination
|
46
|
+
payload[:destinationType] = destination_type if destination_type
|
47
|
+
payload[:sourceId] = source if source
|
48
|
+
payload[:sourceType] = source_type if source_type
|
49
|
+
payload[:notes] = description if description
|
50
|
+
payload[:fundsSource] = funds_source if funds_source
|
51
|
+
|
52
|
+
payload
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/dwolla/user.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
module Dwolla
|
2
|
+
class User
|
3
|
+
include Dwolla::Connection
|
4
|
+
|
5
|
+
attr_accessor :id,
|
6
|
+
:name,
|
7
|
+
:latitude,
|
8
|
+
:longitude,
|
9
|
+
:city,
|
10
|
+
:state,
|
11
|
+
:type,
|
12
|
+
:contact_type,
|
13
|
+
:image,
|
14
|
+
:oauth_token
|
15
|
+
|
16
|
+
def initialize(attrs={})
|
17
|
+
update_attributes(attrs)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.me(access_token)
|
21
|
+
User.new(:oauth_token => access_token)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch
|
25
|
+
user_attributes = get('users')
|
26
|
+
update_attributes(user_attributes)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_attributes(attrs)
|
31
|
+
attrs.each do |key, value|
|
32
|
+
key_string = key.is_a?(String) ? key : key.to_s
|
33
|
+
send("#{key_string.downcase}=".to_sym, value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def balance
|
38
|
+
get('balance')
|
39
|
+
end
|
40
|
+
|
41
|
+
def funding_sources
|
42
|
+
sources = get('fundingsources')
|
43
|
+
sources.map{|s| FundingSource.from_json(s)}
|
44
|
+
end
|
45
|
+
|
46
|
+
def funding_source(funding_id)
|
47
|
+
sources = get('fundingsources?fundingid=' + funding_id)
|
48
|
+
sources.map{|s| FundingSource.from_json(s)}
|
49
|
+
end
|
50
|
+
|
51
|
+
def contacts(options = {})
|
52
|
+
contacts_url = 'contacts'
|
53
|
+
contacts = get(contacts_url, options)
|
54
|
+
|
55
|
+
instances_from_contacts(contacts)
|
56
|
+
end
|
57
|
+
|
58
|
+
def send_money_to(destination, amount, pin, type='dwolla', description='', funds_source=nil)
|
59
|
+
transaction = Transaction.new(:origin => self,
|
60
|
+
:destination => destination,
|
61
|
+
:destination_type => type,
|
62
|
+
:description => description,
|
63
|
+
:type => :send,
|
64
|
+
:amount => amount,
|
65
|
+
:pin => pin,
|
66
|
+
:funds_source => funds_source)
|
67
|
+
|
68
|
+
transaction.execute
|
69
|
+
end
|
70
|
+
|
71
|
+
def request_money_from(source, amount, pin, source_type='dwolla', description='')
|
72
|
+
transaction = Transaction.new(:origin => self,
|
73
|
+
:source => source,
|
74
|
+
:source_type => source_type,
|
75
|
+
:description => description,
|
76
|
+
:type => :request,
|
77
|
+
:amount => amount,
|
78
|
+
:pin => pin)
|
79
|
+
transaction.execute
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def instances_from_contacts(contacts)
|
85
|
+
user_instances = []
|
86
|
+
contacts.each do |contact|
|
87
|
+
contact["Contact_Type"] = contact["Type"]
|
88
|
+
contact.delete("Type")
|
89
|
+
user_instances << User.new(contact)
|
90
|
+
end
|
91
|
+
user_instances
|
92
|
+
end
|
93
|
+
|
94
|
+
def auth_params
|
95
|
+
{ :oauth_token => self.oauth_token }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dwolla::Client do
|
4
|
+
subject { Dwolla::Client.new('sample_client_id', 'sample_client_secret') }
|
5
|
+
let(:query_params) { "client_id=sample_client_id&client_secret=sample_client_secret" }
|
6
|
+
|
7
|
+
describe "getting user basic information" do
|
8
|
+
before do
|
9
|
+
stub_get('/users/812-111-1111', query_params).
|
10
|
+
to_return(:body => fixture("basic_information.json"))
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should request the correct resource' do
|
14
|
+
subject.user('812-111-1111')
|
15
|
+
a_get('/users/812-111-1111', query_params).should have_been_made
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should return extended information of a given user' do
|
19
|
+
user = subject.user('812-111-1111')
|
20
|
+
user.should be_a Dwolla::User
|
21
|
+
user.id.should == '812-111-1111'
|
22
|
+
user.name.should == 'Test User'
|
23
|
+
user.latitude.should == 41.584546
|
24
|
+
user.longitude.should == -93.634167
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
describe Dwolla::Response::FollowRedirects do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@conn = Faraday.new do |b|
|
8
|
+
b.use Dwolla::Response::FollowRedirects
|
9
|
+
b.adapter :test do |stub|
|
10
|
+
stub.get('/') { [301, {'Location' => '/found'}, ''] }
|
11
|
+
stub.post('/create') { [302, {'Location' => '/'}, ''] }
|
12
|
+
stub.get('/found') { [200, {'Content-Type' => 'text/plain'}, 'fin'] }
|
13
|
+
stub.get('/loop') { [302, {'Location' => '/loop'}, ''] }
|
14
|
+
stub.get('/temp') { [307, {'Location' => '/found'}, ''] }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
extend Forwardable
|
20
|
+
def_delegators :@conn, :get, :post
|
21
|
+
|
22
|
+
it 'follow one redirect' do
|
23
|
+
get('/').body.should == 'fin'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'follow twice redirect' do
|
27
|
+
post('/create').body.should == 'fin'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'follows 307 redirects' do
|
31
|
+
get('/temp').body.should == 'fin'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'has a redirect limit' do
|
35
|
+
expect { get('/loop') }.to raise_error(Dwolla::Response::RedirectLimitReached)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dwolla::Transaction do
|
4
|
+
describe "send transaction" do
|
5
|
+
context "to a dwolla account" do
|
6
|
+
before do
|
7
|
+
@origin = double(:oauth_token => '1')
|
8
|
+
@destination = '2'
|
9
|
+
@destination_type = "dwolla"
|
10
|
+
@payload = { :amount => 200,
|
11
|
+
:pin => '1234',
|
12
|
+
:destinationId => '2',
|
13
|
+
:destinationType => 'dwolla',
|
14
|
+
:notes => "Sending a transaction",
|
15
|
+
:oauth_token => '1' }
|
16
|
+
|
17
|
+
stub_post('/transactions/send').with(:body => MultiJson.dump(@payload)).to_return(
|
18
|
+
:body => fixture('send_transaction.json'))
|
19
|
+
end
|
20
|
+
it "should request the correct resource" do
|
21
|
+
transaction = Dwolla::Transaction.new(:origin => @origin,
|
22
|
+
:destination => @destination,
|
23
|
+
:destination_type => @destination_type,
|
24
|
+
:description => "Sending a transaction",
|
25
|
+
:type => :send,
|
26
|
+
:amount => 200,
|
27
|
+
:pin => '1234')
|
28
|
+
transaction.execute
|
29
|
+
|
30
|
+
a_post('/transactions/send').
|
31
|
+
with(:body => MultiJson.dump(@payload)).should have_been_made
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should fetch the id if transaction succesfull" do
|
35
|
+
transaction = Dwolla::Transaction.new(:origin => @origin,
|
36
|
+
:destination => @destination,
|
37
|
+
:destination_type => @destination_type,
|
38
|
+
:description => "Sending a transaction",
|
39
|
+
:type => :send,
|
40
|
+
:amount => 200,
|
41
|
+
:pin => '1234')
|
42
|
+
|
43
|
+
transaction.execute.should == 12345
|
44
|
+
transaction.id.should == 12345
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "to an email address" do
|
49
|
+
before do
|
50
|
+
@origin = double(:oauth_token => '1')
|
51
|
+
@destination = "user@example.com"
|
52
|
+
@destination_type = "email"
|
53
|
+
@payload = { :amount => 200,
|
54
|
+
:pin => '1234',
|
55
|
+
:destinationId => 'user@example.com',
|
56
|
+
:destinationType => 'email',
|
57
|
+
:notes => "Sending a transaction",
|
58
|
+
:oauth_token => '1' }
|
59
|
+
stub_post('/transactions/send').with(:body => MultiJson.dump(@payload)).to_return(
|
60
|
+
:body => fixture('send_transaction.json'))
|
61
|
+
end
|
62
|
+
it "should request the correct resource" do
|
63
|
+
transaction = Dwolla::Transaction.new(:origin => @origin,
|
64
|
+
:destination => @destination,
|
65
|
+
:destination_type => @destination_type,
|
66
|
+
:description => "Sending a transaction",
|
67
|
+
:type => :send,
|
68
|
+
:amount => 200,
|
69
|
+
:pin => '1234')
|
70
|
+
|
71
|
+
transaction.execute
|
72
|
+
|
73
|
+
a_post('/transactions/send').
|
74
|
+
with(:body => MultiJson.dump(@payload)).should have_been_made
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should fetch the id if transaction succesfull" do
|
78
|
+
transaction = Dwolla::Transaction.new(:origin => @origin,
|
79
|
+
:destination => @destination,
|
80
|
+
:destination_type => @destination_type,
|
81
|
+
:description => "Sending a transaction",
|
82
|
+
:type => :send,
|
83
|
+
:amount => 200,
|
84
|
+
:pin => '1234')
|
85
|
+
|
86
|
+
transaction.execute.should == 12345
|
87
|
+
transaction.id.should == 12345
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "request transaction" do
|
94
|
+
context "from a dwolla account" do
|
95
|
+
before do
|
96
|
+
@origin = double(:oauth_token => '1')
|
97
|
+
@source = '2'
|
98
|
+
@source_type = 'dwolla'
|
99
|
+
@payload = { :amount => 200,
|
100
|
+
:pin => '1234',
|
101
|
+
:sourceId => '2',
|
102
|
+
:sourceType => 'dwolla',
|
103
|
+
:notes => "Sending a transaction",
|
104
|
+
:oauth_token => '1' }
|
105
|
+
|
106
|
+
stub_post('/transactions/request').with(:body => MultiJson.dump(@payload)).to_return(
|
107
|
+
:body => fixture('request_transaction.json'))
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should request the correct resource" do
|
111
|
+
transaction = Dwolla::Transaction.new(:origin => @origin,
|
112
|
+
:source => @source,
|
113
|
+
:source_type => @source_type,
|
114
|
+
:description => "Sending a transaction",
|
115
|
+
:type => :request,
|
116
|
+
:amount => 200,
|
117
|
+
:pin => '1234')
|
118
|
+
transaction.execute
|
119
|
+
|
120
|
+
a_post('/transactions/request').
|
121
|
+
with(:body => MultiJson.dump(@payload)).should have_been_made
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should fetch the id if transaction succesfull" do
|
125
|
+
transaction = Dwolla::Transaction.new(:origin => @origin,
|
126
|
+
:source => @source,
|
127
|
+
:source_type => @source_type,
|
128
|
+
:description => "Sending a transaction",
|
129
|
+
:type => :request,
|
130
|
+
:amount => 200,
|
131
|
+
:pin => '1234')
|
132
|
+
|
133
|
+
transaction.execute.should == 12345
|
134
|
+
transaction.id.should == 12345
|
135
|
+
end
|
136
|
+
end
|
137
|
+
context "from an email address" do
|
138
|
+
before do
|
139
|
+
@origin = double(:oauth_token => '1')
|
140
|
+
@source = 'user@example.com'
|
141
|
+
@source_type = "email"
|
142
|
+
@payload = { :amount => 200,
|
143
|
+
:pin => '1234',
|
144
|
+
:sourceId => 'user@example.com',
|
145
|
+
:sourceType => 'email',
|
146
|
+
:notes => "Sending a transaction",
|
147
|
+
:oauth_token => '1' }
|
148
|
+
|
149
|
+
stub_post('/transactions/request').with(:body => MultiJson.dump(@payload)).to_return(
|
150
|
+
:body => fixture('request_transaction.json'))
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should request the correct resource" do
|
154
|
+
transaction = Dwolla::Transaction.new(:origin => @origin,
|
155
|
+
:source => @source,
|
156
|
+
:source_type => @source_type,
|
157
|
+
:description => "Sending a transaction",
|
158
|
+
:type => :request,
|
159
|
+
:amount => 200,
|
160
|
+
:pin => '1234')
|
161
|
+
transaction.execute
|
162
|
+
|
163
|
+
a_post('/transactions/request').
|
164
|
+
with(:body => MultiJson.dump(@payload)).should have_been_made
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should fetch the id if transaction succesfull" do
|
168
|
+
transaction = Dwolla::Transaction.new(:origin => @origin,
|
169
|
+
:source => @source,
|
170
|
+
:source_type => @source_type,
|
171
|
+
:description => "Sending a transaction",
|
172
|
+
:type => :request,
|
173
|
+
:amount => 200,
|
174
|
+
:pin => '1234')
|
175
|
+
|
176
|
+
transaction.execute.should == 12345
|
177
|
+
transaction.id.should == 12345
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|