payjp 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rubocop.yml +82 -0
  4. data/.rubocop_todo.yml +35 -0
  5. data/.travis.yml +22 -0
  6. data/CONTRIBUTORS +0 -0
  7. data/Gemfile +8 -0
  8. data/History.txt +0 -0
  9. data/LICENSE +21 -0
  10. data/README.rdoc +43 -0
  11. data/Rakefile +8 -0
  12. data/VERSION +1 -0
  13. data/gemfiles/default-with-activesupport.gemfile +10 -0
  14. data/gemfiles/json.gemfile +12 -0
  15. data/gemfiles/yajl.gemfile +12 -0
  16. data/lib/payjp.rb +279 -0
  17. data/lib/payjp/account.rb +11 -0
  18. data/lib/payjp/api_operations/create.rb +16 -0
  19. data/lib/payjp/api_operations/delete.rb +11 -0
  20. data/lib/payjp/api_operations/list.rb +17 -0
  21. data/lib/payjp/api_operations/request.rb +39 -0
  22. data/lib/payjp/api_operations/update.rb +17 -0
  23. data/lib/payjp/api_resource.rb +35 -0
  24. data/lib/payjp/card.rb +18 -0
  25. data/lib/payjp/charge.rb +27 -0
  26. data/lib/payjp/customer.rb +8 -0
  27. data/lib/payjp/errors/api_connection_error.rb +4 -0
  28. data/lib/payjp/errors/api_error.rb +4 -0
  29. data/lib/payjp/errors/authentication_error.rb +4 -0
  30. data/lib/payjp/errors/card_error.rb +11 -0
  31. data/lib/payjp/errors/invalid_request_error.rb +10 -0
  32. data/lib/payjp/errors/payjp_error.rb +20 -0
  33. data/lib/payjp/event.rb +5 -0
  34. data/lib/payjp/list_object.rb +33 -0
  35. data/lib/payjp/payjp_object.rb +259 -0
  36. data/lib/payjp/plan.rb +8 -0
  37. data/lib/payjp/subscription.rb +37 -0
  38. data/lib/payjp/token.rb +5 -0
  39. data/lib/payjp/transfer.rb +5 -0
  40. data/lib/payjp/util.rb +121 -0
  41. data/lib/payjp/version.rb +3 -0
  42. data/payjp.gemspec +27 -0
  43. data/test/payjp/account_test.rb +17 -0
  44. data/test/payjp/api_resource_test.rb +445 -0
  45. data/test/payjp/charge_test.rb +83 -0
  46. data/test/payjp/customer_card_test.rb +61 -0
  47. data/test/payjp/customer_test.rb +35 -0
  48. data/test/payjp/event_test.rb +26 -0
  49. data/test/payjp/list_object_test.rb +16 -0
  50. data/test/payjp/metadata_test.rb +103 -0
  51. data/test/payjp/payjp_object_test.rb +28 -0
  52. data/test/payjp/plan_test.rb +48 -0
  53. data/test/payjp/subscription_test.rb +58 -0
  54. data/test/payjp/token_test.rb +34 -0
  55. data/test/payjp/transfer_test.rb +34 -0
  56. data/test/payjp/util_test.rb +34 -0
  57. data/test/test_data.rb +291 -0
  58. data/test/test_helper.rb +45 -0
  59. metadata +201 -0
@@ -0,0 +1,8 @@
1
+ module Payjp
2
+ class Plan < APIResource
3
+ include Payjp::APIOperations::Create
4
+ include Payjp::APIOperations::Delete
5
+ include Payjp::APIOperations::List
6
+ include Payjp::APIOperations::Update
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ module Payjp
2
+ class Subscription < APIResource
3
+ include Payjp::APIOperations::List
4
+ include Payjp::APIOperations::Create
5
+ include Payjp::APIOperations::Update
6
+ include Payjp::APIOperations::Delete
7
+
8
+ def pause(params = {}, opts = {})
9
+ response, opts = request(:post, pause_url, params, opts)
10
+ refresh_from(response, opts)
11
+ end
12
+
13
+ def resume(params = {}, opts = {})
14
+ response, opts = request(:post, resume_url, params, opts)
15
+ refresh_from(response, opts)
16
+ end
17
+
18
+ def cancel(params = {}, opts = {})
19
+ response, opts = request(:post, cancel_url, params, opts)
20
+ refresh_from(response, opts)
21
+ end
22
+
23
+ private
24
+
25
+ def pause_url
26
+ url + '/pause'
27
+ end
28
+
29
+ def resume_url
30
+ url + '/resume'
31
+ end
32
+
33
+ def cancel_url
34
+ url + '/cancel'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ module Payjp
2
+ class Token < APIResource
3
+ include Payjp::APIOperations::Create
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Payjp
2
+ class Transfer < APIResource
3
+ include Payjp::APIOperations::List
4
+ end
5
+ end
@@ -0,0 +1,121 @@
1
+ module Payjp
2
+ module Util
3
+ def self.objects_to_ids(h)
4
+ case h
5
+ when APIResource
6
+ h.id
7
+ when Hash
8
+ res = {}
9
+ h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
10
+ res
11
+ when Array
12
+ h.map { |v| objects_to_ids(v) }
13
+ else
14
+ h
15
+ end
16
+ end
17
+
18
+ def self.object_classes
19
+ @object_classes ||= {
20
+ # data structures
21
+ 'list' => ListObject,
22
+
23
+ # business objects
24
+ 'account' => Account,
25
+ 'card' => Card,
26
+ 'charge' => Charge,
27
+ 'customer' => Customer,
28
+ 'event' => Event,
29
+ 'plan' => Plan,
30
+ 'subscription' => Subscription,
31
+ 'token' => Token,
32
+ 'transfer' => Transfer
33
+ }
34
+ end
35
+
36
+ def self.convert_to_payjp_object(resp, opts)
37
+ case resp
38
+ when Array
39
+ resp.map { |i| convert_to_payjp_object(i, opts) }
40
+ when Hash
41
+ # Try converting to a known object class. If none available, fall back to generic PayjpObject
42
+ object_classes.fetch(resp[:object], PayjpObject).construct_from(resp, opts)
43
+ else
44
+ resp
45
+ end
46
+ end
47
+
48
+ def self.symbolize_names(object)
49
+ case object
50
+ when Hash
51
+ new_hash = {}
52
+ object.each do |key, value|
53
+ key = (key.to_sym rescue key) || key
54
+ new_hash[key] = symbolize_names(value)
55
+ end
56
+ new_hash
57
+ when Array
58
+ object.map { |value| symbolize_names(value) }
59
+ else
60
+ object
61
+ end
62
+ end
63
+
64
+ def self.url_encode(key)
65
+ URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
66
+ end
67
+
68
+ def self.flatten_params(params, parent_key = nil)
69
+ result = []
70
+ params.each do |key, value|
71
+ calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
72
+ if value.is_a?(Hash)
73
+ result += flatten_params(value, calculated_key)
74
+ elsif value.is_a?(Array)
75
+ result += flatten_params_array(value, calculated_key)
76
+ else
77
+ result << [calculated_key, value]
78
+ end
79
+ end
80
+ result
81
+ end
82
+
83
+ def self.flatten_params_array(value, calculated_key)
84
+ result = []
85
+ value.each do |elem|
86
+ if elem.is_a?(Hash)
87
+ result += flatten_params(elem, calculated_key)
88
+ elsif elem.is_a?(Array)
89
+ result += flatten_params_array(elem, calculated_key)
90
+ else
91
+ result << ["#{calculated_key}[]", elem]
92
+ end
93
+ end
94
+ result
95
+ end
96
+
97
+ # The secondary opts argument can either be a string or hash
98
+ # Turn this value into an api_key and a set of headers
99
+ def self.normalize_opts(opts)
100
+ case opts
101
+ when String
102
+ { :api_key => opts }
103
+ when Hash
104
+ check_api_key!(opts.fetch(:api_key)) if opts.has_key?(:api_key)
105
+ opts.clone
106
+ else
107
+ raise TypeError.new('normalize_opts expects a string or a hash')
108
+ end
109
+ end
110
+
111
+ def self.check_string_argument!(key)
112
+ raise TypeError.new("argument must be a string") unless key.is_a?(String)
113
+ key
114
+ end
115
+
116
+ def self.check_api_key!(key)
117
+ raise TypeError.new("api_key must be a string") unless key.is_a?(String)
118
+ key
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,3 @@
1
+ module Payjp
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'payjp/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'payjp'
7
+ s.version = Payjp::VERSION
8
+ s.summary = 'Ruby bindings for the Payjp API'
9
+ s.description = 'PAY.JP is the easiest way to accept payments online.'
10
+ s.authors = ['PAY.JP']
11
+ s.email = ['support@pay.jp']
12
+ s.homepage = 'https://pay.jp'
13
+ s.license = 'MIT'
14
+
15
+ s.add_dependency('rest-client', '~> 1.4')
16
+ s.add_dependency('json', '~> 1.8.1')
17
+
18
+ s.add_development_dependency('mocha', '~> 0.13.2')
19
+ s.add_development_dependency('shoulda', '~> 3.4.0')
20
+ s.add_development_dependency('test-unit')
21
+ s.add_development_dependency('rake')
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- test/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
26
+ s.require_paths = ['lib']
27
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ module Payjp
4
+ class AccountTest < Test::Unit::TestCase
5
+ should "be retrievable" do
6
+ resp = { :email => "test+bindings@pay.jp", :accounts_enabled => ['merchant', 'customer'], :merchant => { :bank_enabled => false } }
7
+ @mock.expects(:get).
8
+ once.
9
+ with("#{Payjp.api_base}/v1/accounts", nil, nil).
10
+ returns(test_response(resp))
11
+ a = Payjp::Account.retrieve
12
+ assert_equal "test+bindings@pay.jp", a.email
13
+ assert_equal ['merchant', 'customer'], a.accounts_enabled
14
+ assert !a.merchant.bank_enabled
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,445 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.expand_path('../../test_helper', __FILE__)
3
+
4
+ module Payjp
5
+ class ApiResourceTest < Test::Unit::TestCase
6
+ should "creating a new APIResource should not fetch over the network" do
7
+ @mock.expects(:get).never
8
+ Payjp::Customer.new("someid")
9
+ end
10
+
11
+ should "creating a new APIResource from a hash should not fetch over the network" do
12
+ @mock.expects(:get).never
13
+ Payjp::Customer.construct_from({
14
+ :id => "somecustomer",
15
+ :card => { :id => "somecard", :object => "card" },
16
+ :object => "customer"
17
+ })
18
+ end
19
+
20
+ should "setting an attribute should not cause a network request" do
21
+ @mock.expects(:get).never
22
+ @mock.expects(:post).never
23
+ c = Payjp::Customer.new("test_customer")
24
+ c.card = { :id => "somecard", :object => "card" }
25
+ end
26
+
27
+ should "accessing id should not issue a fetch" do
28
+ @mock.expects(:get).never
29
+ c = Payjp::Customer.new("test_customer")
30
+ c.id
31
+ end
32
+
33
+ should "not specifying api credentials should raise an exception" do
34
+ Payjp.api_key = nil
35
+ assert_raises Payjp::AuthenticationError do
36
+ Payjp::Customer.new("test_customer").refresh
37
+ end
38
+ end
39
+
40
+ should "using a nil api key should raise an exception" do
41
+ assert_raises TypeError do
42
+ Payjp::Customer.all({}, nil)
43
+ end
44
+ assert_raises TypeError do
45
+ Payjp::Customer.all({}, { :api_key => nil })
46
+ end
47
+ end
48
+
49
+ should "specifying api credentials containing whitespace should raise an exception" do
50
+ Payjp.api_key = "key "
51
+ assert_raises Payjp::AuthenticationError do
52
+ Payjp::Customer.new("test_customer").refresh
53
+ end
54
+ end
55
+
56
+ should "specifying invalid api credentials should raise an exception" do
57
+ Payjp.api_key = "invalid"
58
+ response = test_response(test_invalid_api_key_error, 401)
59
+ assert_raises Payjp::AuthenticationError do
60
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 401))
61
+ Payjp::Customer.retrieve("failing_customer")
62
+ end
63
+ end
64
+
65
+ should "AuthenticationErrors should have an http status, http body, and JSON body" do
66
+ Payjp.api_key = "invalid"
67
+ response = test_response(test_invalid_api_key_error, 401)
68
+ begin
69
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 401))
70
+ Payjp::Customer.retrieve("failing_customer")
71
+ rescue Payjp::AuthenticationError => e
72
+ assert_equal(401, e.http_status)
73
+ assert_equal(true, !!e.http_body)
74
+ assert_equal(true, !!e.json_body[:error][:message])
75
+ assert_equal(test_invalid_api_key_error[:error][:message], e.json_body[:error][:message])
76
+ end
77
+ end
78
+
79
+ should "send expand on fetch properly" do
80
+ @mock.expects(:get).once.
81
+ with("#{Payjp.api_base}/v1/charges/ch_test_charge?expand[]=customer", nil, nil).
82
+ returns(test_response(test_charge))
83
+
84
+ Payjp::Charge.retrieve({ :id => 'ch_test_charge', :expand => [:customer] })
85
+ end
86
+
87
+ should "preserve expand across refreshes" do
88
+ @mock.expects(:get).twice.
89
+ with("#{Payjp.api_base}/v1/charges/ch_test_charge?expand[]=customer", nil, nil).
90
+ returns(test_response(test_charge))
91
+
92
+ ch = Payjp::Charge.retrieve({ :id => 'ch_test_charge', :expand => [:customer] })
93
+ ch.refresh
94
+ end
95
+
96
+ should "send payjp account as header when set" do
97
+ payjp_account = "acct_0000"
98
+ Payjp.expects(:execute_request).with do |opts|
99
+ opts[:headers][:payjp_account] == payjp_account
100
+ end.returns(test_response(test_charge))
101
+
102
+ Payjp::Charge.create({ :card => { :number => '4242424242424242' } },
103
+ { :payjp_account => payjp_account, :api_key => 'sk_test_local' })
104
+ end
105
+
106
+ should "not send payjp account as header when not set" do
107
+ Payjp.expects(:execute_request).with do |opts|
108
+ opts[:headers][:payjp_account].nil?
109
+ end.returns(test_response(test_charge))
110
+
111
+ Payjp::Charge.create({ :card => { :number => '4242424242424242' } },
112
+ 'sk_test_local')
113
+ end
114
+
115
+ context "when specifying per-object credentials" do
116
+ context "with no global API key set" do
117
+ should "use the per-object credential when creating" do
118
+ api_key = 'sk_test_local'
119
+ Payjp.expects(:execute_request).with do |opts|
120
+ opts[:headers][:authorization] == encode_credentials(api_key)
121
+ end.returns(test_response(test_charge))
122
+
123
+ Payjp::Charge.create({ :card => { :number => '4242424242424242' } },
124
+ api_key)
125
+ end
126
+ end
127
+
128
+ context "with a global API key set" do
129
+ setup do
130
+ Payjp.api_key = "global"
131
+ end
132
+
133
+ teardown do
134
+ Payjp.api_key = nil
135
+ end
136
+
137
+ should "use the per-object credential when creating" do
138
+ api_key = 'local'
139
+ Payjp.expects(:execute_request).with do |opts|
140
+ opts[:headers][:authorization] == encode_credentials(api_key)
141
+ end.returns(test_response(test_charge))
142
+
143
+ Payjp::Charge.create({ :card => { :number => '4242424242424242' } },
144
+ api_key)
145
+ end
146
+
147
+ should "use the per-object credential when retrieving and making other calls" do
148
+ api_key = 'local'
149
+ Payjp.expects(:execute_request).with do |opts|
150
+ opts[:url] == "#{Payjp.api_base}/v1/charges/ch_test_charge" &&
151
+ opts[:headers][:authorization] == encode_credentials(api_key)
152
+ end.returns(test_response(test_charge))
153
+ Payjp.expects(:execute_request).with do |opts|
154
+ opts[:url] == "#{Payjp.api_base}/v1/charges/ch_test_charge/refund" &&
155
+ opts[:headers][:authorization] == encode_credentials(api_key)
156
+ end.returns(test_response(test_charge))
157
+
158
+ ch = Payjp::Charge.retrieve('ch_test_charge', api_key)
159
+ ch.refund
160
+ end
161
+ end
162
+ end
163
+
164
+ context "with valid credentials" do
165
+ should "send along the idempotency-key header" do
166
+ Payjp.expects(:execute_request).with do |opts|
167
+ opts[:headers][:idempotency_key] == 'bar'
168
+ end.returns(test_response(test_charge))
169
+
170
+ Payjp::Charge.create({ :card => { :number => '4242424242424242' } }, {
171
+ :idempotency_key => 'bar',
172
+ :api_key => 'local'
173
+ })
174
+ end
175
+
176
+ should "urlencode values in GET params" do
177
+ response = test_response(test_charge_array)
178
+ @mock.expects(:get).with("#{Payjp.api_base}/v1/charges?customer=test%20customer", nil, nil).returns(response)
179
+ charges = Payjp::Charge.all(:customer => 'test customer').data
180
+ assert charges.is_a? Array
181
+ end
182
+
183
+ should "a 400 should give an InvalidRequestError with http status, body, and JSON body" do
184
+ response = test_response(test_missing_id_error, 400)
185
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
186
+ begin
187
+ Payjp::Customer.retrieve("foo")
188
+ rescue Payjp::InvalidRequestError => e
189
+ assert_equal(400, e.http_status)
190
+ assert_equal(true, !!e.http_body)
191
+ assert_equal(true, e.json_body.is_a?(Hash))
192
+ end
193
+ end
194
+
195
+ should "a 401 should give an AuthenticationError with http status, body, and JSON body" do
196
+ response = test_response(test_missing_id_error, 401)
197
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
198
+ begin
199
+ Payjp::Customer.retrieve("foo")
200
+ rescue Payjp::AuthenticationError => e
201
+ assert_equal(401, e.http_status)
202
+ assert_equal(true, !!e.http_body)
203
+ assert_equal(true, e.json_body.is_a?(Hash))
204
+ end
205
+ end
206
+
207
+ should "a 402 should give a CardError with http status, body, and JSON body" do
208
+ response = test_response(test_missing_id_error, 402)
209
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
210
+ begin
211
+ Payjp::Customer.retrieve("foo")
212
+ rescue Payjp::CardError => e
213
+ assert_equal(402, e.http_status)
214
+ assert_equal(true, !!e.http_body)
215
+ assert_equal(true, e.json_body.is_a?(Hash))
216
+ end
217
+ end
218
+
219
+ should "a 404 should give an InvalidRequestError with http status, body, and JSON body" do
220
+ response = test_response(test_missing_id_error, 404)
221
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
222
+ begin
223
+ Payjp::Customer.retrieve("foo")
224
+ rescue Payjp::InvalidRequestError => e
225
+ assert_equal(404, e.http_status)
226
+ assert_equal(true, !!e.http_body)
227
+ assert_equal(true, e.json_body.is_a?(Hash))
228
+ end
229
+ end
230
+
231
+ should "setting a nil value for a param should exclude that param from the request" do
232
+ @mock.expects(:get).with do |url, _api_key, _params|
233
+ uri = URI(url)
234
+ query = CGI.parse(uri.query)
235
+ (url =~ %r{^#{Payjp.api_base}/v1/charges?} &&
236
+ query.keys.sort == ['offset', 'sad'])
237
+ end.returns(test_response({ :count => 1, :data => [test_charge] }))
238
+ Payjp::Charge.all(:count => nil, :offset => 5, :sad => false)
239
+
240
+ @mock.expects(:post).with do |url, api_key, params|
241
+ url == "#{Payjp.api_base}/v1/charges" &&
242
+ api_key.nil? &&
243
+ CGI.parse(params) == { 'amount' => ['50'], 'currency' => ['jpy'] }
244
+ end.returns(test_response({ :count => 1, :data => [test_charge] }))
245
+ Payjp::Charge.create(:amount => 50, :currency => 'jpy', :card => { :number => nil })
246
+ end
247
+
248
+ should "requesting with a unicode ID should result in a request" do
249
+ response = test_response(test_missing_id_error, 404)
250
+ @mock.expects(:get).once.with("#{Payjp.api_base}/v1/customers/%E2%98%83", nil, nil).raises(RestClient::ExceptionWithResponse.new(response, 404))
251
+ c = Payjp::Customer.new("☃")
252
+ assert_raises(Payjp::InvalidRequestError) { c.refresh }
253
+ end
254
+
255
+ should "requesting with no ID should result in an InvalidRequestError with no request" do
256
+ c = Payjp::Customer.new
257
+ assert_raises(Payjp::InvalidRequestError) { c.refresh }
258
+ end
259
+
260
+ should "making a GET request with parameters should have a query string and no body" do
261
+ params = { :limit => 1 }
262
+ @mock.expects(:get).once.with("#{Payjp.api_base}/v1/charges?limit=1", nil, nil).returns(test_response([test_charge]))
263
+ Payjp::Charge.all(params)
264
+ end
265
+
266
+ should "making a POST request with parameters should have a body and no query string" do
267
+ params = { :amount => 100, :currency => 'jpy', :card => 'sc_token' }
268
+ @mock.expects(:post).once.with do |_url, get, post|
269
+ get.nil? && CGI.parse(post) == { 'amount' => ['100'], 'currency' => ['jpy'], 'card' => ['sc_token'] }
270
+ end.returns(test_response(test_charge))
271
+ Payjp::Charge.create(params)
272
+ end
273
+
274
+ should "loading an object should issue a GET request" do
275
+ @mock.expects(:get).once.returns(test_response(test_customer))
276
+ c = Payjp::Customer.new("test_customer")
277
+ c.refresh
278
+ end
279
+
280
+ should "using array accessors should be the same as the method interface" do
281
+ @mock.expects(:get).once.returns(test_response(test_customer))
282
+ c = Payjp::Customer.new("test_customer")
283
+ c.refresh
284
+ assert_equal c.created, c[:created]
285
+ assert_equal c.created, c['created']
286
+ c['created'] = 12345
287
+ assert_equal c.created, 12345
288
+ end
289
+
290
+ should "updating an object should issue a POST request with only the changed properties" do
291
+ @mock.expects(:post).with do |url, api_key, params|
292
+ url == "#{Payjp.api_base}/v1/customers/c_test_customer" && api_key.nil? && CGI.parse(params) == { 'description' => ['another_mn'] }
293
+ end.once.returns(test_response(test_customer))
294
+ c = Payjp::Customer.construct_from(test_customer)
295
+ c.description = "another_mn"
296
+ c.save
297
+ end
298
+
299
+ should "updating should merge in returned properties" do
300
+ @mock.expects(:post).once.returns(test_response(test_customer))
301
+ c = Payjp::Customer.new("c_test_customer")
302
+ c.description = "another_mn"
303
+ c.save
304
+ assert_equal false, c.livemode
305
+ end
306
+
307
+ should "deleting should send no props and result in an object that has no props other deleted" do
308
+ @mock.expects(:get).never
309
+ @mock.expects(:post).never
310
+ @mock.expects(:delete).with("#{Payjp.api_base}/v1/customers/c_test_customer", nil, nil).once.returns(test_response({ "id" => "test_customer", "deleted" => true }))
311
+ c = Payjp::Customer.construct_from(test_customer)
312
+ c.delete
313
+ assert_equal true, c.deleted
314
+
315
+ assert_raises NoMethodError do
316
+ c.livemode
317
+ end
318
+ end
319
+
320
+ should "loading an object with properties that have specific types should instantiate those classes" do
321
+ @mock.expects(:get).once.returns(test_response(test_charge))
322
+ c = Payjp::Charge.retrieve("test_charge")
323
+ assert c.card.is_a?(Payjp::PayjpObject) && c.card.object == 'card'
324
+ end
325
+
326
+ should "loading all of an APIResource should return an array of recursively instantiated objects" do
327
+ @mock.expects(:get).once.returns(test_response(test_charge_array))
328
+ c = Payjp::Charge.all.data
329
+ assert c.is_a? Array
330
+ assert c[0].is_a? Payjp::Charge
331
+ assert c[0].card.is_a?(Payjp::PayjpObject) && c[0].card.object == 'card'
332
+ end
333
+
334
+ should "passing in a payjp_account header should pass it through on call" do
335
+ Payjp.expects(:execute_request).with do |opts|
336
+ opts[:method] == :get &&
337
+ opts[:url] == "#{Payjp.api_base}/v1/customers/c_test_customer" &&
338
+ opts[:headers][:payjp_account] == 'acct_abc'
339
+ end.once.returns(test_response(test_customer))
340
+ Payjp::Customer.retrieve("c_test_customer", { :payjp_account => 'acct_abc' })
341
+ end
342
+
343
+ should "passing in a payjp_account header should pass it through on save" do
344
+ Payjp.expects(:execute_request).with do |opts|
345
+ opts[:method] == :get &&
346
+ opts[:url] == "#{Payjp.api_base}/v1/customers/c_test_customer" &&
347
+ opts[:headers][:payjp_account] == 'acct_abc'
348
+ end.once.returns(test_response(test_customer))
349
+ c = Payjp::Customer.retrieve("c_test_customer", { :payjp_account => 'acct_abc' })
350
+
351
+ Payjp.expects(:execute_request).with do |opts|
352
+ opts[:method] == :post &&
353
+ opts[:url] == "#{Payjp.api_base}/v1/customers/c_test_customer" &&
354
+ opts[:headers][:payjp_account] == 'acct_abc' &&
355
+ opts[:payload] == 'description=FOO'
356
+ end.once.returns(test_response(test_customer))
357
+ c.description = 'FOO'
358
+ c.save
359
+ end
360
+
361
+ context "error checking" do
362
+ should "404s should raise an InvalidRequestError" do
363
+ response = test_response(test_missing_id_error, 404)
364
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
365
+
366
+ rescued = false
367
+ begin
368
+ Payjp::Customer.new("test_customer").refresh
369
+ assert false # shouldn't get here either
370
+ rescue Payjp::InvalidRequestError => e # we don't use assert_raises because we want to examine e
371
+ rescued = true
372
+ assert e.is_a? Payjp::InvalidRequestError
373
+ assert_equal "id", e.param
374
+ assert_equal "Missing id", e.message
375
+ end
376
+
377
+ assert_equal true, rescued
378
+ end
379
+
380
+ should "5XXs should raise an APIError" do
381
+ response = test_response(test_api_error, 500)
382
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 500))
383
+
384
+ rescued = false
385
+ begin
386
+ Payjp::Customer.new("test_customer").refresh
387
+ assert false # shouldn't get here either
388
+ rescue Payjp::APIError => e # we don't use assert_raises because we want to examine e
389
+ rescued = true
390
+ assert e.is_a? Payjp::APIError
391
+ end
392
+
393
+ assert_equal true, rescued
394
+ end
395
+
396
+ should "402s should raise a CardError" do
397
+ response = test_response(test_invalid_exp_year_error, 402)
398
+ @mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 402))
399
+
400
+ rescued = false
401
+ begin
402
+ Payjp::Customer.new("test_customer").refresh
403
+ assert false # shouldn't get here either
404
+ rescue Payjp::CardError => e # we don't use assert_raises because we want to examine e
405
+ rescued = true
406
+ assert e.is_a? Payjp::CardError
407
+ assert_equal "invalid_expiry_year", e.code
408
+ assert_equal "exp_year", e.param
409
+ assert_equal "Your card's expiration year is invalid", e.message
410
+ end
411
+
412
+ assert_equal true, rescued
413
+ end
414
+ end
415
+
416
+ should 'save nothing if nothing changes' do
417
+ ch = Payjp::Charge.construct_from({
418
+ :id => 'charge_id',
419
+ :customer => {
420
+ :object => 'customer',
421
+ :id => 'customer_id'
422
+ }
423
+ })
424
+
425
+ @mock.expects(:post).once.with("#{Payjp.api_base}/v1/charges/charge_id", nil, '').returns(test_response({ "id" => "charge_id" }))
426
+ ch.save
427
+ end
428
+
429
+ should 'not save nested API resources' do
430
+ ch = Payjp::Charge.construct_from({
431
+ :id => 'charge_id',
432
+ :customer => {
433
+ :object => 'customer',
434
+ :id => 'customer_id'
435
+ }
436
+ })
437
+
438
+ @mock.expects(:post).once.with("#{Payjp.api_base}/v1/charges/charge_id", nil, '').returns(test_response({ "id" => "charge_id" }))
439
+
440
+ ch.customer.description = 'Bob'
441
+ ch.save
442
+ end
443
+ end
444
+ end
445
+ end