payjp 0.0.2

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.
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