gocardless 0.1.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 +10 -0
- data/.rspec +1 -0
- data/Gemfile +12 -0
- data/Guardfile +6 -0
- data/LICENSE +22 -0
- data/README.md +15 -0
- data/Rakefile +6 -0
- data/gocardless.gemspec +22 -0
- data/lib/gocardless.rb +32 -0
- data/lib/gocardless/bill.rb +44 -0
- data/lib/gocardless/client.rb +352 -0
- data/lib/gocardless/errors.rb +30 -0
- data/lib/gocardless/merchant.rb +47 -0
- data/lib/gocardless/payment.rb +9 -0
- data/lib/gocardless/pre_authorization.rb +21 -0
- data/lib/gocardless/resource.rb +191 -0
- data/lib/gocardless/subscription.rb +15 -0
- data/lib/gocardless/user.rb +8 -0
- data/lib/gocardless/utils.rb +36 -0
- data/lib/gocardless/version.rb +3 -0
- data/spec/bill_spec.rb +27 -0
- data/spec/client_spec.rb +376 -0
- data/spec/gocardless_spec.rb +41 -0
- data/spec/merchant_spec.rb +31 -0
- data/spec/resource_spec.rb +395 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/utils_spec.rb +67 -0
- metadata +195 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module GoCardless
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class ApiError < Error
|
9
|
+
attr_reader :response, :code, :description
|
10
|
+
|
11
|
+
def initialize(response)
|
12
|
+
@response = response
|
13
|
+
@code = response.status
|
14
|
+
body = JSON.parse(response.body) rescue nil
|
15
|
+
if body.is_a? Hash
|
16
|
+
@description = body["error"] || response.body.strip || "Unknown error"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"#{super} [#{self.code}] #{self.description}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ClientError < Error
|
26
|
+
end
|
27
|
+
|
28
|
+
class SignatureError < Error
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module GoCardless
|
2
|
+
class Merchant < Resource
|
3
|
+
self.endpoint = '/merchants/:id'
|
4
|
+
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :description
|
7
|
+
attr_accessor :email
|
8
|
+
attr_accessor :first_name
|
9
|
+
attr_accessor :last_name
|
10
|
+
date_accessor :created_at
|
11
|
+
|
12
|
+
def subscriptions(params = {})
|
13
|
+
path = "/merchants/#{self.id}/subscriptions"
|
14
|
+
@client.api_get(path, params).map do |attrs|
|
15
|
+
GoCardless::Subscription.new_with_client(@client, attrs)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def pre_authorizations(params = {})
|
20
|
+
path = "/merchants/#{self.id}/pre_authorizations"
|
21
|
+
@client.api_get(path, params).map do |attrs|
|
22
|
+
GoCardless::PreAuthorization.new_with_client(@client, attrs)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def users(params = {})
|
27
|
+
path = "/merchants/#{self.id}/users"
|
28
|
+
@client.api_get(path, params).map do |attrs|
|
29
|
+
GoCardless::User.new_with_client(@client, attrs)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def bills(params = {})
|
34
|
+
path = "/merchants/#{self.id}/bills"
|
35
|
+
@client.api_get(path, params).map do |attrs|
|
36
|
+
GoCardless::Bill.new_with_client(@client, attrs)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def payments(params = {})
|
41
|
+
path = "/merchants/#{self.id}/payments"
|
42
|
+
@client.api_get(path, params).map do |attrs|
|
43
|
+
GoCardless::Payment.new_with_client(@client, attrs)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module GoCardless
|
2
|
+
class PreAuthorization < Resource
|
3
|
+
self.endpoint = '/pre_authorizations/:id'
|
4
|
+
|
5
|
+
attr_accessor :max_amount, :currency, :amount, :interval_length,
|
6
|
+
:interval_unit, :description
|
7
|
+
|
8
|
+
reference_accessor :merchant_id, :user_id
|
9
|
+
date_accessor :expires_at, :created_at
|
10
|
+
|
11
|
+
# Create a new bill under this pre-authorization. Similar to
|
12
|
+
# {Client#create_bill}, but only requires the amount to be specified.
|
13
|
+
#
|
14
|
+
# @option attrs [amount] amount the bill amount in pence
|
15
|
+
# @return [Bill] the created bill object
|
16
|
+
def create_bill(attrs)
|
17
|
+
Bill.new_with_client(client, attrs.merge(:source => self)).save
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module GoCardless
|
4
|
+
class Resource
|
5
|
+
def initialize(hash = {})
|
6
|
+
# Handle sub resources
|
7
|
+
sub_resource_uris = hash.delete('sub_resource_uris')
|
8
|
+
unless sub_resource_uris.nil?
|
9
|
+
# Need to define a method for each sub resource
|
10
|
+
sub_resource_uris.each do |name,uri|
|
11
|
+
uri = URI.parse(uri)
|
12
|
+
|
13
|
+
# Convert the query string to a hash
|
14
|
+
default_query = if uri.query.nil? || uri.query == ''
|
15
|
+
nil
|
16
|
+
else
|
17
|
+
Hash[CGI.parse(uri.query).map { |k,v| [k,v.first] }]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Strip api prefix from path
|
21
|
+
path = uri.path.sub(%r{^/api/v\d+}, '')
|
22
|
+
|
23
|
+
# Modify the instance's metaclass to add the method
|
24
|
+
metaclass = class << self; self; end
|
25
|
+
metaclass.send(:define_method, name) do |*args|
|
26
|
+
# 'name' will be something like 'bills', convert it to Bill and
|
27
|
+
# look up the resource class with that name
|
28
|
+
class_name = Utils.camelize(Utils.singularize(name.to_s))
|
29
|
+
klass = GoCardless.const_get(class_name)
|
30
|
+
# Convert the results to instances of the looked-up class
|
31
|
+
params = args.first || {}
|
32
|
+
query = default_query.nil? ? nil : default_query.merge(params)
|
33
|
+
client.api_get(path, query).map do |attrs|
|
34
|
+
klass.new(attrs).tap { |m| m.client = client }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set resource attribute values
|
41
|
+
hash.each { |key,val| send("#{key}=", val) if respond_to?("#{key}=") }
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_writer :client
|
45
|
+
|
46
|
+
class << self
|
47
|
+
attr_accessor :endpoint
|
48
|
+
|
49
|
+
def new_with_client(client, attrs = {})
|
50
|
+
self.new(attrs).tap { |obj| obj.client = client }
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_with_client(client_obj, id)
|
54
|
+
path = endpoint.gsub(':id', id.to_s)
|
55
|
+
data = client_obj.api_get(path)
|
56
|
+
obj = self.new(data)
|
57
|
+
obj.client = client_obj
|
58
|
+
obj
|
59
|
+
end
|
60
|
+
|
61
|
+
def find(id)
|
62
|
+
message = "Merchant details not found, set GoCardless.account_details"
|
63
|
+
raise Error, message unless GoCardless.client
|
64
|
+
self.find_with_client(GoCardless.client, id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def date_writer(*args)
|
68
|
+
args.each do |attr|
|
69
|
+
define_method("#{attr.to_s}=".to_sym) do |date|
|
70
|
+
date = date.is_a?(String) ? DateTime.parse(date) : date
|
71
|
+
instance_variable_set("@#{attr}", date)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def date_accessor(*args)
|
77
|
+
attr_reader *args
|
78
|
+
date_writer *args
|
79
|
+
end
|
80
|
+
|
81
|
+
def reference_reader(*args)
|
82
|
+
attr_reader *args
|
83
|
+
|
84
|
+
args.each do |attr|
|
85
|
+
if !attr.to_s.end_with?('_id')
|
86
|
+
raise ArgumentError, 'reference_reader args must end with _id'
|
87
|
+
end
|
88
|
+
|
89
|
+
name = attr.to_s.sub(/_id$/, '')
|
90
|
+
define_method(name.to_sym) do
|
91
|
+
obj_id = instance_variable_get("@#{attr}")
|
92
|
+
klass = GoCardless.const_get(Utils.camelize(name))
|
93
|
+
klass.find_with_client(client, obj_id)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def reference_writer(*args)
|
99
|
+
attr_writer *args
|
100
|
+
|
101
|
+
args.each do |attr|
|
102
|
+
if !attr.to_s.end_with?('_id')
|
103
|
+
raise ArgumentError, 'reference_writer args must end with _id'
|
104
|
+
end
|
105
|
+
|
106
|
+
name = attr.to_s.sub(/_id$/, '')
|
107
|
+
define_method("#{name}=".to_sym) do |obj|
|
108
|
+
klass = GoCardless.const_get(Utils.camelize(name))
|
109
|
+
if !obj.is_a?(klass)
|
110
|
+
raise ArgumentError, "Object must be an instance of #{klass}"
|
111
|
+
end
|
112
|
+
|
113
|
+
instance_variable_set("@#{attr}", obj.id)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def reference_accessor(*args)
|
119
|
+
reference_reader *args
|
120
|
+
reference_writer *args
|
121
|
+
end
|
122
|
+
|
123
|
+
def creatable(val = true)
|
124
|
+
@creatable = val
|
125
|
+
end
|
126
|
+
|
127
|
+
def updatable(val = true)
|
128
|
+
@updatable = val
|
129
|
+
end
|
130
|
+
|
131
|
+
def creatable?
|
132
|
+
!!@creatable
|
133
|
+
end
|
134
|
+
|
135
|
+
def updatable?
|
136
|
+
!!@updatable
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# @macro [attach] resource.property
|
142
|
+
# @return [String] the $1 property of the object
|
143
|
+
attr_accessor :id
|
144
|
+
attr_accessor :uri
|
145
|
+
|
146
|
+
def to_hash
|
147
|
+
attrs = instance_variables.map { |v| v.to_s.sub(/^@/, '') }
|
148
|
+
attrs.delete 'client'
|
149
|
+
Hash[attrs.select { |v| respond_to? v }.map { |v| [v.to_sym, send(v)] }]
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_json
|
153
|
+
to_hash.to_json
|
154
|
+
end
|
155
|
+
|
156
|
+
def inspect
|
157
|
+
"#<#{self.class} #{to_hash.map { |k,v| "#{k}=#{v.inspect}" }.join(', ')}>"
|
158
|
+
end
|
159
|
+
|
160
|
+
def persisted?
|
161
|
+
!id.nil?
|
162
|
+
end
|
163
|
+
|
164
|
+
# Save a resource on the API server. If the resource already exists (has a
|
165
|
+
# non-null id), it will be updated with a PUT, otherwise it will be created
|
166
|
+
# with a POST.
|
167
|
+
def save
|
168
|
+
save_data self.to_hash
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
protected
|
173
|
+
|
174
|
+
def client
|
175
|
+
@client || GoCardless.client
|
176
|
+
end
|
177
|
+
|
178
|
+
def save_data(data)
|
179
|
+
method = if self.persisted?
|
180
|
+
raise "#{self.class} cannot be updated" unless self.class.updatable?
|
181
|
+
'put'
|
182
|
+
else
|
183
|
+
raise "#{self.class} cannot be created" unless self.class.creatable?
|
184
|
+
'post'
|
185
|
+
end
|
186
|
+
path = self.class.endpoint.gsub(':id', id.to_s)
|
187
|
+
response = client.send("api_#{method}", path, data)
|
188
|
+
response.each { |key,val| send("#{key}=", val) if respond_to?("#{key}=") } if response.is_a? Hash
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module GoCardless
|
2
|
+
class Subscription < Resource
|
3
|
+
|
4
|
+
self.endpoint = '/subscriptions/:id'
|
5
|
+
|
6
|
+
attr_accessor :amount, :currency, :interval_length, :interval_unit,
|
7
|
+
:description, :setup_fee, :trial_length, :trial_unit
|
8
|
+
|
9
|
+
reference_accessor :merchant_id, :user_id
|
10
|
+
|
11
|
+
date_accessor :expires_at, :created_at
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module GoCardless
|
3
|
+
module Utils
|
4
|
+
class << self
|
5
|
+
|
6
|
+
# String Helpers
|
7
|
+
def camelize(str)
|
8
|
+
str.split('_').map(&:capitalize).join
|
9
|
+
end
|
10
|
+
|
11
|
+
def underscore(str)
|
12
|
+
str.gsub(/(.)([A-Z])/) { "#{$1}_#{$2.downcase}" }.downcase
|
13
|
+
end
|
14
|
+
|
15
|
+
def singularize(str)
|
16
|
+
# This should probably be a bit more robust
|
17
|
+
str.sub(/s$/, '').sub(/i$/, 'us')
|
18
|
+
end
|
19
|
+
|
20
|
+
# Hash Helpers
|
21
|
+
def symbolize_keys(hash)
|
22
|
+
symbolize_keys! hash.dup
|
23
|
+
end
|
24
|
+
|
25
|
+
def symbolize_keys!(hash)
|
26
|
+
hash.keys.each do |key|
|
27
|
+
sym_key = key.to_s.to_sym rescue key
|
28
|
+
hash[sym_key] = hash.delete(key) unless hash.key?(sym_key)
|
29
|
+
end
|
30
|
+
hash
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
data/spec/bill_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoCardless::Bill do
|
4
|
+
before :each do
|
5
|
+
@app_id = 'abc'
|
6
|
+
@app_secret = 'xyz'
|
7
|
+
GoCardless.account_details = {:app_id => @app_id, :app_secret => @app_secret,
|
8
|
+
:token => 'xxx manage_merchant:1'}
|
9
|
+
@client = GoCardless.client
|
10
|
+
end
|
11
|
+
|
12
|
+
it "source getter works" do
|
13
|
+
b = GoCardless::Bill.new(:source_type => :subscription, :source_id => 123)
|
14
|
+
@client.access_token = 'TOKEN manage_merchant:123'
|
15
|
+
stub_get(@client, :id => 123)
|
16
|
+
source = b.source
|
17
|
+
source.should be_a GoCardless::Subscription
|
18
|
+
source.id.should == 123
|
19
|
+
end
|
20
|
+
|
21
|
+
it "source setter works" do
|
22
|
+
b = GoCardless::Bill.new
|
23
|
+
b.source = GoCardless::Subscription.new(:id => 123)
|
24
|
+
b.source_id.should == 123
|
25
|
+
b.source_type.should.to_s == 'subscription'
|
26
|
+
end
|
27
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoCardless::Client do
|
4
|
+
before :each do
|
5
|
+
@app_id = 'abc'
|
6
|
+
@app_secret = 'xyz'
|
7
|
+
@redirect_uri = 'http://test.com/cb'
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".base_url" do
|
11
|
+
it "returns the correct url for the production environment" do
|
12
|
+
GoCardless.environment = :production
|
13
|
+
GoCardless::Client.base_url.should == 'https://gocardless.com'
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns the correct url for the sandbox environment" do
|
17
|
+
GoCardless.environment = :sandbox
|
18
|
+
GoCardless::Client.base_url.should == 'https://sandbox.gocardless.com'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns the correct url when it's set manually" do
|
22
|
+
GoCardless::Client.base_url = 'https://abc.gocardless.com'
|
23
|
+
GoCardless::Client.base_url.should == 'https://abc.gocardless.com'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#new" do
|
28
|
+
it "without an app id should raise an error" do
|
29
|
+
lambda do
|
30
|
+
GoCardless::Client.new({:app_secret => @app_secret})
|
31
|
+
end.should raise_exception(GoCardless::ClientError)
|
32
|
+
end
|
33
|
+
it "without an app_secret should raise an error" do
|
34
|
+
lambda do
|
35
|
+
GoCardless::Client.new({:app_id => @app_id})
|
36
|
+
end.should raise_exception(GoCardless::ClientError)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
before do
|
41
|
+
@client = GoCardless::Client.new({:app_id => @app_id, :app_secret => @app_secret})
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#authorize_url" do
|
45
|
+
it "fails without a redirect uri" do
|
46
|
+
lambda { @client.authorize_url }.should raise_exception(ArgumentError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "generates the authorize url correctly" do
|
50
|
+
url = URI.parse(@client.authorize_url(:redirect_uri => @redirect_uri))
|
51
|
+
query = CGI.parse(url.query)
|
52
|
+
query['response_type'].first.should == 'code'
|
53
|
+
query['redirect_uri'].first.should == @redirect_uri
|
54
|
+
query['client_id'].first.should == @app_id
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#fetch_access_token" do
|
59
|
+
access_token_url = "#{GoCardless::Client.base_url}/oauth/access_token"
|
60
|
+
|
61
|
+
it "fails without a redirect uri" do
|
62
|
+
lambda do
|
63
|
+
@client.fetch_access_token('code', {})
|
64
|
+
end.should raise_exception(ArgumentError)
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "with valid params" do
|
68
|
+
it "calls correct method with correct args" do
|
69
|
+
auth_code = 'fakecode'
|
70
|
+
access_token = mock
|
71
|
+
|
72
|
+
@client.instance_variable_get(:@access_token).should be_nil
|
73
|
+
|
74
|
+
oauth_client = @client.instance_variable_get(:@oauth_client)
|
75
|
+
oauth_client.auth_code.expects(:get_token).with(
|
76
|
+
auth_code, has_entry(:redirect_uri => @redirect_uri)
|
77
|
+
)
|
78
|
+
|
79
|
+
@client.fetch_access_token(auth_code, {:redirect_uri => @redirect_uri})
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sets @access_token" do
|
83
|
+
access_token = mock
|
84
|
+
access_token.stubs(:params).returns('scope' => '')
|
85
|
+
access_token.stubs(:token).returns('')
|
86
|
+
|
87
|
+
oauth_client = @client.instance_variable_get(:@oauth_client)
|
88
|
+
oauth_client.auth_code.expects(:get_token).returns(access_token)
|
89
|
+
|
90
|
+
@client.instance_variable_get(:@access_token).should be_nil
|
91
|
+
@client.fetch_access_token('code', {:redirect_uri => @redirect_uri})
|
92
|
+
@client.instance_variable_get(:@access_token).should == access_token
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#access_token" do
|
98
|
+
it "serializes access token correctly" do
|
99
|
+
oauth_client = @client.instance_variable_get(:@oauth_client)
|
100
|
+
token = OAuth2::AccessToken.new(oauth_client, 'TOKEN123')
|
101
|
+
token.params['scope'] = 'a:1 b:2'
|
102
|
+
@client.instance_variable_set(:@access_token, token)
|
103
|
+
|
104
|
+
@client.access_token.should == 'TOKEN123 a:1 b:2'
|
105
|
+
end
|
106
|
+
|
107
|
+
it "returns nil when there's no token" do
|
108
|
+
@client.access_token.should be_nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#access_token=" do
|
113
|
+
it "deserializes access token correctly" do
|
114
|
+
@client.access_token = 'TOKEN123 a:1 b:2'
|
115
|
+
token = @client.instance_variable_get(:@access_token)
|
116
|
+
token.token.should == 'TOKEN123'
|
117
|
+
token.params['scope'].should == 'a:1 b:2'
|
118
|
+
end
|
119
|
+
|
120
|
+
it "handles invalid values correctly" do
|
121
|
+
token = 'TOKEN123' # missing scope
|
122
|
+
expect { @client.access_token = token }.to raise_exception ArgumentError
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#api_get" do
|
127
|
+
it "uses the correct path prefix" do
|
128
|
+
@client.access_token = 'TOKEN123 a:1 b:2'
|
129
|
+
token = @client.instance_variable_get(:@access_token)
|
130
|
+
r = mock
|
131
|
+
r.stubs(:parsed)
|
132
|
+
token.expects(:get).with { |p,o| p =~ %r|/api/v1/test| }.returns(r)
|
133
|
+
@client.api_get('/test')
|
134
|
+
end
|
135
|
+
|
136
|
+
it "fails without an access_token" do
|
137
|
+
expect { @client.api_get '/' }.to raise_exception GoCardless::ClientError
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#api_post" do
|
142
|
+
it "encodes data to json" do
|
143
|
+
@client.access_token = 'TOKEN123 a:1 b:2'
|
144
|
+
token = @client.instance_variable_get(:@access_token)
|
145
|
+
r = mock
|
146
|
+
r.stubs(:parsed)
|
147
|
+
token.expects(:post).with { |p,opts| opts[:body] == '{"a":1}' }.returns(r)
|
148
|
+
@client.api_post('/test', {:a => 1})
|
149
|
+
end
|
150
|
+
|
151
|
+
it "fails without an access_token" do
|
152
|
+
expect { @client.api_get '/' }.to raise_exception GoCardless::ClientError
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "#merchant" do
|
157
|
+
it "looks up the correct merchant" do
|
158
|
+
@client.access_token = 'TOKEN a manage_merchant:123 b'
|
159
|
+
response = mock
|
160
|
+
response.expects(:parsed)
|
161
|
+
|
162
|
+
token = @client.instance_variable_get(:@access_token)
|
163
|
+
merchant_url = '/api/v1/merchants/123'
|
164
|
+
token.expects(:get).with { |p,o| p == merchant_url }.returns response
|
165
|
+
|
166
|
+
GoCardless::Merchant.stubs(:new_with_client)
|
167
|
+
|
168
|
+
@client.merchant
|
169
|
+
end
|
170
|
+
|
171
|
+
it "creates a Merchant object" do
|
172
|
+
@client.access_token = 'TOKEN manage_merchant:123'
|
173
|
+
response = mock
|
174
|
+
response.expects(:parsed).returns({:name => 'test', :id => 123})
|
175
|
+
|
176
|
+
token = @client.instance_variable_get(:@access_token)
|
177
|
+
token.expects(:get).returns response
|
178
|
+
|
179
|
+
merchant = @client.merchant
|
180
|
+
merchant.should be_an_instance_of GoCardless::Merchant
|
181
|
+
merchant.id.should == 123
|
182
|
+
merchant.name.should == 'test'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
%w{subscription pre_authorization user bill payment}.each do |resource|
|
187
|
+
describe "##{resource}" do
|
188
|
+
it "returns the correct #{GoCardless::Utils.camelize(resource)} object" do
|
189
|
+
@client.access_token = 'TOKEN manage_merchant:123'
|
190
|
+
stub_get(@client, {:id => 123})
|
191
|
+
obj = @client.send(resource, 123)
|
192
|
+
obj.should be_a GoCardless.const_get(GoCardless::Utils.camelize(resource))
|
193
|
+
obj.id.should == 123
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "#encode_params" do
|
199
|
+
it "correctly encodes hashes" do
|
200
|
+
params = {:a => {:b => :c}, :x => :y}
|
201
|
+
result = 'a%5Bb%5D=c&x=y'
|
202
|
+
@client.send(:encode_params, params).should == result
|
203
|
+
end
|
204
|
+
|
205
|
+
it "correctly encodes arrays" do
|
206
|
+
params = {:a => [1,2]}
|
207
|
+
result = 'a%5B%5D=1&a%5B%5D=2'
|
208
|
+
@client.send(:encode_params, params).should == result
|
209
|
+
end
|
210
|
+
|
211
|
+
it "sorts params by key" do
|
212
|
+
params = {:b => 1, :a => 2}
|
213
|
+
result = 'a=2&b=1'
|
214
|
+
@client.send(:encode_params, params).should == result
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
it "#sign_params signs pararmeter hashes correctly" do
|
219
|
+
@client.instance_variable_set(:@app_secret, 'testsecret')
|
220
|
+
params = {:test => true}
|
221
|
+
sig = '6e4613b729ce15c288f70e72463739feeb05fc0b89b55d248d7f259b5367148b'
|
222
|
+
@client.send(:sign_params, params)[:signature].should == sig
|
223
|
+
end
|
224
|
+
|
225
|
+
describe "#signature_valid?" do
|
226
|
+
before(:each) { @params = { :x => 'y', :a => 'b' } }
|
227
|
+
|
228
|
+
it "succeeds with a valid signature" do
|
229
|
+
params = @client.send(:sign_params, @params)
|
230
|
+
@client.send(:signature_valid?, params).should be_true
|
231
|
+
end
|
232
|
+
|
233
|
+
it "fails with an invalid signature" do
|
234
|
+
params = {:signature => 'invalid'}.merge(@params)
|
235
|
+
@client.send(:signature_valid?, params).should be_false
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "#confirm_resource" do
|
240
|
+
before :each do
|
241
|
+
@params = {
|
242
|
+
:resource_id => '1',
|
243
|
+
:resource_uri => 'a',
|
244
|
+
:resource_type => 'subscription',
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
[:resource_id, :resource_uri, :resource_type].each do |param|
|
249
|
+
it "fails when :#{param} is missing" do
|
250
|
+
p = @params.tap { |d| d.delete(param) }
|
251
|
+
expect { @client.confirm_resource p }.to raise_exception ArgumentError
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
it "doesn't confirm the resource when the signature is invalid" do
|
256
|
+
@client.expects(:request).never
|
257
|
+
@client.confirm_resource({:signature => 'xxx'}.merge(@params)) rescue nil
|
258
|
+
end
|
259
|
+
|
260
|
+
it "fails when the signature is invalid" do
|
261
|
+
expect do
|
262
|
+
@client.confirm_resource({:signature => 'xxx'}.merge(@params))
|
263
|
+
end.to raise_exception GoCardless::SignatureError
|
264
|
+
end
|
265
|
+
|
266
|
+
it "confirms the resource when the signature is valid" do
|
267
|
+
# Once for confirm, once to fetch result
|
268
|
+
@client.expects(:request).twice.returns(stub(:parsed => {}))
|
269
|
+
@client.confirm_resource(@client.send(:sign_params, @params))
|
270
|
+
end
|
271
|
+
|
272
|
+
it "returns the correct object when the signature is valid" do
|
273
|
+
@client.stubs(:request).returns(stub(:parsed => {}))
|
274
|
+
subscription = GoCardless::Subscription.new_with_client @client
|
275
|
+
GoCardless::Subscription.expects(:find_with_client).returns subscription
|
276
|
+
|
277
|
+
# confirm_resource should use the Subcription class because
|
278
|
+
# the :response_type is set to subscription
|
279
|
+
resource = @client.confirm_resource(@client.send(:sign_params, @params))
|
280
|
+
resource.should be_a GoCardless::Subscription
|
281
|
+
end
|
282
|
+
|
283
|
+
it "includes valid http basic credentials" do
|
284
|
+
GoCardless::Subscription.stubs(:find_with_client)
|
285
|
+
auth = 'Basic YWJjOnh5eg=='
|
286
|
+
@client.expects(:request).once.with do |type, path, opts|
|
287
|
+
opts.should include :headers
|
288
|
+
opts[:headers].should include 'Authorization'
|
289
|
+
opts[:headers]['Authorization'].should == auth
|
290
|
+
end
|
291
|
+
@client.confirm_resource(@client.send(:sign_params, @params))
|
292
|
+
end
|
293
|
+
|
294
|
+
it "works with string params" do
|
295
|
+
@client.stubs(:request)
|
296
|
+
GoCardless::Subscription.stubs(:find_with_client)
|
297
|
+
params = Hash[@params.dup.map { |k,v| [k.to_s, v] }]
|
298
|
+
params.keys.each { |p| p.should be_a String }
|
299
|
+
# No ArgumentErrors should be raised
|
300
|
+
@client.confirm_resource(@client.send(:sign_params, params))
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
it "#generate_nonce should generate a random string" do
|
305
|
+
@client.send(:generate_nonce).should_not == @client.send(:generate_nonce)
|
306
|
+
end
|
307
|
+
|
308
|
+
describe "#new_limit_url" do
|
309
|
+
before(:each) do
|
310
|
+
@merchant_id = '123'
|
311
|
+
@client.access_token = "TOKEN manage_merchant:#{@merchant_id}"
|
312
|
+
end
|
313
|
+
|
314
|
+
def get_params(url)
|
315
|
+
Hash[CGI.parse(URI.parse(url).query).map{ |k,v| [k, v.first] }]
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should use the correct path" do
|
319
|
+
url = @client.send(:new_limit_url, :test_limit, {})
|
320
|
+
URI.parse(url).path.should == '/connect/test_limits/new'
|
321
|
+
end
|
322
|
+
|
323
|
+
it "should include the params in the URL query" do
|
324
|
+
params = { 'a' => '1', 'b' => '2' }
|
325
|
+
url = @client.send(:new_limit_url, :subscription, params)
|
326
|
+
url_params = get_params(url)
|
327
|
+
params.each do |key, value|
|
328
|
+
url_params["subscription[#{key}]"].should == value
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
it "should add merchant_id to the limit" do
|
333
|
+
url = @client.send(:new_limit_url, :subscription, {})
|
334
|
+
get_params(url)['subscription[merchant_id]'].should == @merchant_id
|
335
|
+
end
|
336
|
+
|
337
|
+
it "should include a valid signature" do
|
338
|
+
params = get_params(@client.send(:new_limit_url, :subscription, :x => 1))
|
339
|
+
params.key?('signature').should be_true
|
340
|
+
sig = params.delete('signature')
|
341
|
+
sig.should == @client.send(:sign_params, params.clone)[:signature]
|
342
|
+
end
|
343
|
+
|
344
|
+
it "should include a nonce" do
|
345
|
+
params = get_params(@client.send(:new_limit_url, :subscription, :x => 1))
|
346
|
+
params['nonce'].should be_a String
|
347
|
+
end
|
348
|
+
|
349
|
+
it "should include a client_id" do
|
350
|
+
params = get_params(@client.send(:new_limit_url, :subscription, :x => 1))
|
351
|
+
params['client_id'].should == @client.instance_variable_get(:@app_id)
|
352
|
+
end
|
353
|
+
|
354
|
+
it "should include a timestamp" do
|
355
|
+
# Time.now returning Pacific time
|
356
|
+
time = Time.parse('Sat Jan 01 00:00:00 -0800')
|
357
|
+
Time.expects(:now).returns time
|
358
|
+
params = get_params(@client.send(:new_limit_url, :subscription, :x => 1))
|
359
|
+
# Check that timezone is ISO formatted UTC
|
360
|
+
params['timestamp'].should == "2011-01-01T08:00:00Z"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
describe "#merchant_id" do
|
365
|
+
it "returns the merchant id when an access token is set" do
|
366
|
+
@client.access_token = 'TOKEN manage_merchant:123'
|
367
|
+
@client.send(:merchant_id).should == '123'
|
368
|
+
end
|
369
|
+
|
370
|
+
it "fails if there's no access token" do
|
371
|
+
expect do
|
372
|
+
@client.send(:merchant_id)
|
373
|
+
end.to raise_exception GoCardless::ClientError
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|