stripe 1.36.1 → 1.36.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/History.txt +4 -0
  3. data/VERSION +1 -1
  4. data/lib/stripe/account.rb +25 -18
  5. data/lib/stripe/api_operations/create.rb +1 -1
  6. data/lib/stripe/api_operations/delete.rb +1 -1
  7. data/lib/stripe/api_operations/list.rb +1 -1
  8. data/lib/stripe/api_operations/update.rb +9 -13
  9. data/lib/stripe/api_resource.rb +4 -4
  10. data/lib/stripe/application_fee.rb +1 -1
  11. data/lib/stripe/application_fee_refund.rb +2 -2
  12. data/lib/stripe/balance_transaction.rb +1 -1
  13. data/lib/stripe/bank_account.rb +4 -4
  14. data/lib/stripe/bitcoin_receiver.rb +4 -4
  15. data/lib/stripe/bitcoin_transaction.rb +1 -1
  16. data/lib/stripe/card.rb +4 -4
  17. data/lib/stripe/charge.rb +6 -6
  18. data/lib/stripe/country_spec.rb +1 -1
  19. data/lib/stripe/customer.rb +3 -3
  20. data/lib/stripe/dispute.rb +1 -1
  21. data/lib/stripe/file_upload.rb +1 -1
  22. data/lib/stripe/invoice.rb +2 -2
  23. data/lib/stripe/list_object.rb +6 -1
  24. data/lib/stripe/order.rb +1 -1
  25. data/lib/stripe/product.rb +0 -9
  26. data/lib/stripe/reversal.rb +2 -2
  27. data/lib/stripe/singleton_api_resource.rb +3 -3
  28. data/lib/stripe/stripe_object.rb +128 -63
  29. data/lib/stripe/subscription.rb +3 -3
  30. data/lib/stripe/transfer.rb +1 -1
  31. data/lib/stripe/version.rb +1 -1
  32. data/test/stripe/account_test.rb +8 -8
  33. data/test/stripe/api_resource_test.rb +1 -1
  34. data/test/stripe/country_spec_test.rb +7 -7
  35. data/test/stripe/customer_card_test.rb +2 -2
  36. data/test/stripe/list_object_test.rb +35 -12
  37. data/test/stripe/recipient_card_test.rb +2 -2
  38. data/test/stripe/stripe_object_test.rb +83 -11
  39. data/test/test_data.rb +23 -23
  40. metadata +2 -2
@@ -3,8 +3,8 @@ module Stripe
3
3
  include Stripe::APIOperations::Update
4
4
  extend Stripe::APIOperations::List
5
5
 
6
- def url
7
- "#{Transfer.url}/#{CGI.escape(transfer)}/reversals/#{CGI.escape(id)}"
6
+ def resource_url
7
+ "#{Transfer.resource_url}/#{CGI.escape(transfer)}/reversals/#{CGI.escape(id)}"
8
8
  end
9
9
 
10
10
  def self.retrieve(id, opts={})
@@ -1,14 +1,14 @@
1
1
  module Stripe
2
2
  class SingletonAPIResource < APIResource
3
- def self.url
3
+ def self.resource_url
4
4
  if self == SingletonAPIResource
5
5
  raise NotImplementedError.new('SingletonAPIResource is an abstract class. You should perform actions on its subclasses (Account, etc.)')
6
6
  end
7
7
  "/v1/#{CGI.escape(class_name.downcase)}"
8
8
  end
9
9
 
10
- def url
11
- self.class.url
10
+ def resource_url
11
+ self.class.resource_url
12
12
  end
13
13
 
14
14
  def self.retrieve(opts={})
@@ -72,13 +72,21 @@ module Stripe
72
72
  #
73
73
  # * +values+ - Hash of values to use to update the current attributes of
74
74
  # the object.
75
+ # * +:opts+ Options for StripeObject like an API key.
75
76
  #
76
77
  # ==== Options
77
78
  #
78
- # * +:opts+ Options for StripeObject like an API key.
79
- def update_attributes(values, opts = {})
79
+ # * +:dirty+ Whether values should be initiated as "dirty" (unsaved) and
80
+ # which applies only to new StripeObjects being ininiated under this
81
+ # StripeObject. Defaults to true.
82
+ def update_attributes(values, opts = {}, method_options = {})
83
+ # Default to true. TODO: Convert to optional arguments after we're off
84
+ # 1.9 which will make this quite a bit more clear.
85
+ dirty = method_options.fetch(:dirty, true)
86
+
80
87
  values.each do |k, v|
81
88
  @values[k] = Util.convert_to_stripe_object(v, opts)
89
+ dirty_value!(@values[k]) if dirty
82
90
  @unsaved_values.add(k)
83
91
  end
84
92
  end
@@ -142,73 +150,51 @@ module Stripe
142
150
  end
143
151
  end
144
152
 
145
- def serialize_nested_object(key)
146
- new_value = @values[key]
147
- if new_value.is_a?(APIResource)
148
- return {}
149
- end
150
-
151
- if @unsaved_values.include?(key)
152
- # the object has been reassigned
153
- # e.g. as object.key = {foo => bar}
154
- update = new_value
155
- update = update.to_hash if update.is_a?(StripeObject)
156
- new_keys = update.keys.map(&:to_sym)
157
-
158
- # remove keys at the server, but not known locally
159
- if @original_values[key]
160
- keys_to_unset = @original_values[key].keys - new_keys
161
- keys_to_unset.each {|key| update[key] = ''}
162
- end
163
-
164
- update
165
- else
166
- # can be serialized normally
167
- self.class.serialize_params(new_value)
153
+ # Sets all keys within the StripeObject as unsaved so that they will be
154
+ # included with an update when #serialize_params is called. This method is
155
+ # also recursive, so any StripeObjects contained as values or which are
156
+ # values in a tenant array are also marked as dirty.
157
+ def dirty!
158
+ @unsaved_values = Set.new(@values.keys)
159
+ @values.each do |k, v|
160
+ dirty_value!(v)
168
161
  end
169
162
  end
170
163
 
171
- def self.serialize_params(obj, original_value=nil)
172
- case obj
173
- when nil
174
- ''
175
- when Array
176
- update = obj.map { |v| serialize_params(v) }
177
- if original_value != update
178
- update
179
- else
180
- nil
164
+ def serialize_params(options = {})
165
+ update_hash = {}
166
+
167
+ @values.each do |k, v|
168
+ # There are a few reasons that we may want to add in a parameter for
169
+ # update:
170
+ #
171
+ # 1. The `force` option has been set.
172
+ # 2. We know that it was modified.
173
+ # 3. Its value is a StripeObject. A StripeObject may contain modified
174
+ # values within in that its parent StripeObject doesn't know about.
175
+ #
176
+ unsaved = @unsaved_values.include?(k)
177
+ if options[:force] || unsaved || v.is_a?(StripeObject)
178
+ update_hash[k.to_sym] =
179
+ serialize_params_value(@values[k], @original_values[k], unsaved, options[:force])
181
180
  end
182
- when StripeObject
183
- unsaved_keys = obj.instance_variable_get(:@unsaved_values)
184
- obj_values = obj.instance_variable_get(:@values)
185
- update_hash = {}
181
+ end
186
182
 
187
- unsaved_keys.each do |k|
188
- update_hash[k] = serialize_params(obj_values[k])
189
- end
183
+ # a `nil` that makes it out of `#serialize_params_value` signals an empty
184
+ # value that we shouldn't appear in the serialized form of the object
185
+ update_hash.reject! { |_, v| v == nil }
190
186
 
191
- obj_values.each do |k, v|
192
- if v.is_a?(Array)
193
- original_value = obj.instance_variable_get(:@original_values)[k]
194
-
195
- # the conditional here tests whether the old and new values are
196
- # different (and therefore needs an update), or the same (meaning
197
- # we can leave it out of the request)
198
- if updated = serialize_params(v, original_value)
199
- update_hash[k] = updated
200
- else
201
- update_hash.delete(k)
202
- end
203
- elsif v.is_a?(StripeObject) || v.is_a?(Hash)
204
- update_hash[k] = obj.serialize_nested_object(k)
205
- end
206
- end
187
+ update_hash
188
+ end
207
189
 
208
- update_hash
209
- else
210
- obj
190
+ class << self
191
+ # This class method has been deprecated in favor of the instance method
192
+ # of the same name.
193
+ def serialize_params(obj, options = {})
194
+ obj.serialize_params(options)
211
195
  end
196
+ extend Gem::Deprecate
197
+ deprecate :serialize_params, "#serialize_params", 2016, 9
212
198
  end
213
199
 
214
200
  protected
@@ -249,7 +235,8 @@ module Stripe
249
235
  "We interpret empty strings as nil in requests. " \
250
236
  "You may set #{self}.#{k} = nil to delete the property.")
251
237
  end
252
- @values[k] = v
238
+ @values[k] = Util.convert_to_stripe_object(v, @opts)
239
+ dirty_value!(@values[k])
253
240
  @unsaved_values.add(k)
254
241
  end
255
242
 
@@ -328,7 +315,7 @@ module Stripe
328
315
  @unsaved_values.delete(k)
329
316
  end
330
317
 
331
- update_attributes(values, opts)
318
+ update_attributes(values, opts, :dirty => false)
332
319
  values.each do |k, _|
333
320
  @transient_values.delete(k)
334
321
  @unsaved_values.delete(k)
@@ -336,5 +323,83 @@ module Stripe
336
323
 
337
324
  self
338
325
  end
326
+
327
+ def serialize_params_value(value, original, unsaved, force)
328
+ case value
329
+ when nil
330
+ ''
331
+
332
+ # The logic here is that essentially any object embedded in another
333
+ # object that had a `type` is actually an API resource of a different
334
+ # type that's been included in the response. These other resources must
335
+ # be updated from their proper endpoints, and therefore they are not
336
+ # included when serializing even if they've been modified.
337
+ when APIResource
338
+ nil
339
+
340
+ when Array
341
+ update = value.map { |v| serialize_params_value(v, nil, true, force) }
342
+
343
+ # This prevents an array that's unchanged from being resent.
344
+ if update != serialize_params_value(original, nil, true, force)
345
+ update
346
+ else
347
+ nil
348
+ end
349
+
350
+ # Handle a Hash for now, but in the long run we should be able to
351
+ # eliminate all places where hashes are stored as values internally by
352
+ # making sure any time one is set, we convert it to a StripeObject. This
353
+ # will simplify our model by making data within an object more
354
+ # consistent.
355
+ #
356
+ # For now, you can still run into a hash if someone appends one to an
357
+ # existing array being held by a StripeObject. This could happen for
358
+ # example by appending a new hash onto `additional_owners` for an
359
+ # account.
360
+ when Hash
361
+ Util.convert_to_stripe_object(value, @opts).serialize_params
362
+
363
+ when StripeObject
364
+ update = value.serialize_params(:force => force)
365
+
366
+ # If the entire object was replaced, then we need blank each field of
367
+ # the old object that held a value. The new serialized values will
368
+ # override any of these empty values.
369
+ update = empty_values(original).merge(update) if original && unsaved
370
+
371
+ update
372
+
373
+ else
374
+ value
375
+ end
376
+ end
377
+
378
+ private
379
+
380
+ def dirty_value!(value)
381
+ case value
382
+ when Array
383
+ value.map { |v| dirty_value!(v) }
384
+ when StripeObject
385
+ value.dirty!
386
+ end
387
+ end
388
+
389
+ # Returns a hash of empty values for all the values that are in the given
390
+ # StripeObject.
391
+ def empty_values(obj)
392
+ values = case obj
393
+ when Hash then obj
394
+ when StripeObject then obj.instance_variable_get(:@values)
395
+ else
396
+ raise ArgumentError, "#empty_values got unexpected object type: #{obj.class.name}"
397
+ end
398
+
399
+ values.inject({}) do |update, (k, _)|
400
+ update[k] = ''
401
+ update
402
+ end
403
+ end
339
404
  end
340
405
  end
@@ -3,8 +3,8 @@ module Stripe
3
3
  include Stripe::APIOperations::Update
4
4
  include Stripe::APIOperations::Delete
5
5
 
6
- def url
7
- "#{Customer.url}/#{CGI.escape(customer)}/subscriptions/#{CGI.escape(id)}"
6
+ def resource_url
7
+ "#{Customer.resource_url}/#{CGI.escape(customer)}/subscriptions/#{CGI.escape(id)}"
8
8
  end
9
9
 
10
10
  def self.retrieve(id, opts=nil)
@@ -19,7 +19,7 @@ module Stripe
19
19
  private
20
20
 
21
21
  def discount_url
22
- url + '/discount'
22
+ resource_url + '/discount'
23
23
  end
24
24
  end
25
25
  end
@@ -10,7 +10,7 @@ module Stripe
10
10
  end
11
11
 
12
12
  def cancel_url
13
- url + '/cancel'
13
+ resource_url + '/cancel'
14
14
  end
15
15
 
16
16
  end
@@ -1,3 +1,3 @@
1
1
  module Stripe
2
- VERSION = '1.36.1'
2
+ VERSION = '1.36.2'
3
3
  end
@@ -121,7 +121,7 @@ module Stripe
121
121
  :id => 'acct_1234',
122
122
  :external_accounts => {
123
123
  :object => "list",
124
- :url => "/v1/accounts/acct_1234/external_accounts",
124
+ :resource_url => "/v1/accounts/acct_1234/external_accounts",
125
125
  :data => [],
126
126
  }
127
127
  }
@@ -140,7 +140,7 @@ module Stripe
140
140
  :id => 'acct_1234',
141
141
  :external_accounts => {
142
142
  :object => "list",
143
- :url => "/v1/accounts/acct_1234/external_accounts",
143
+ :resource_url => "/v1/accounts/acct_1234/external_accounts",
144
144
  :data => [{
145
145
  :id => "ba_1234",
146
146
  :object => "bank_account",
@@ -155,8 +155,8 @@ module Stripe
155
155
  should "#serialize_params an a new additional_owners" do
156
156
  obj = Stripe::Util.convert_to_stripe_object({
157
157
  :object => "account",
158
- :legal_entity => {
159
- },
158
+ :legal_entity => Stripe::StripeObject.construct_from({
159
+ }),
160
160
  }, {})
161
161
  obj.legal_entity.additional_owners = [
162
162
  { :first_name => "Joe" },
@@ -171,7 +171,7 @@ module Stripe
171
171
  }
172
172
  }
173
173
  }
174
- assert_equal(expected, obj.class.serialize_params(obj))
174
+ assert_equal(expected, obj.serialize_params)
175
175
  end
176
176
 
177
177
  should "#serialize_params on an partially changed additional_owners" do
@@ -197,7 +197,7 @@ module Stripe
197
197
  }
198
198
  }
199
199
  }
200
- assert_equal(expected, obj.class.serialize_params(obj))
200
+ assert_equal(expected, obj.serialize_params)
201
201
  end
202
202
 
203
203
  should "#serialize_params on an unchanged additional_owners" do
@@ -220,7 +220,7 @@ module Stripe
220
220
  :additional_owners => {}
221
221
  }
222
222
  }
223
- assert_equal(expected, obj.class.serialize_params(obj))
223
+ assert_equal(expected, obj.serialize_params)
224
224
  end
225
225
 
226
226
  # Note that the empty string that we send for this one has a special
@@ -246,7 +246,7 @@ module Stripe
246
246
  :additional_owners => ""
247
247
  }
248
248
  }
249
- assert_equal(expected, obj.class.serialize_params(obj))
249
+ assert_equal(expected, obj.serialize_params)
250
250
  end
251
251
  end
252
252
  end
@@ -671,7 +671,7 @@ module Stripe
671
671
 
672
672
  err = assert_raises Stripe::APIConnectionError do
673
673
  Stripe::Charge.create(:amount => 50, :currency => 'usd', :card => { :number => nil })
674
- end
674
+ end
675
675
  assert_match(/Request was retried 2 times/, err.message)
676
676
  end
677
677
 
@@ -6,8 +6,8 @@ module Stripe
6
6
  @mock.expects(:get).once.
7
7
  returns(make_response(country_spec_array))
8
8
  c = Stripe::CountrySpec.list
9
-
10
- assert_equal('/v1/country_specs', c.url)
9
+
10
+ assert_equal('/v1/country_specs', c.resource_url)
11
11
  assert_equal('list', c.object)
12
12
  assert(c.data.kind_of?(Array))
13
13
  assert_equal('US', c.data[0].id)
@@ -20,17 +20,17 @@ module Stripe
20
20
  with('https://api.stripe.com/v1/country_specs/US', nil, nil).
21
21
  returns(make_response(resp))
22
22
  s = Stripe::CountrySpec.retrieve('US')
23
-
24
- assert_equal('/v1/country_specs/US', s.url)
23
+
24
+ assert_equal('/v1/country_specs/US', s.resource_url)
25
25
  assert_equal('country_spec', s.object)
26
26
  assert(s.kind_of?(Stripe::CountrySpec))
27
-
27
+
28
28
  s.supported_bank_account_currencies.map{ |k,v| assert v.kind_of?(Array) }
29
29
  assert_equal(['US'], s.supported_bank_account_currencies['usd'])
30
30
  assert(s.supported_payment_currencies.include?('usd'))
31
31
  assert s.supported_payment_currencies.kind_of?(Array)
32
32
  assert s.supported_payment_methods.kind_of?(Array)
33
-
33
+
34
34
  ['individual', 'company'].map{ |type|
35
35
  item = s.verification_fields[type]
36
36
  assert item.minimum.include?('external_account')
@@ -40,4 +40,4 @@ module Stripe
40
40
  }
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -17,14 +17,14 @@ module Stripe
17
17
  assert cards[0].kind_of? Stripe::Card
18
18
  end
19
19
 
20
- should "customer cards should have the correct url" do
20
+ should "customer cards should have the correct resource url" do
21
21
  c = customer
22
22
  @mock.expects(:get).once.returns(make_response(make_card(
23
23
  :id => 'test_card',
24
24
  :customer => 'test_customer'
25
25
  )))
26
26
  card = c.sources.retrieve('card')
27
- assert_equal CUSTOMER_CARD_URL, card.url
27
+ assert_equal CUSTOMER_CARD_URL, card.resource_url
28
28
  end
29
29
 
30
30
  should "customer cards should be deletable" do
@@ -31,7 +31,11 @@ module Stripe
31
31
  ]
32
32
  expected = Util.convert_to_stripe_object(arr, {})
33
33
 
34
- list = TestListObject.construct_from({ :data => [{ :id => 1 }], :has_more => true })
34
+ list = TestListObject.construct_from({
35
+ :data => [{ :id => 1 }],
36
+ :has_more => true,
37
+ :url => "/things",
38
+ })
35
39
  @mock.expects(:get).once.with("#{Stripe.api_base}/things?starting_after=1", nil, nil).
36
40
  returns(make_response({ :data => [{ :id => 2 }, { :id => 3}], :has_more => false }))
37
41
 
@@ -46,7 +50,11 @@ module Stripe
46
50
  ]
47
51
  expected = Util.convert_to_stripe_object(arr, {})
48
52
 
49
- list = TestListObject.construct_from({ :data => [{ :id => 1 }], :has_more => true })
53
+ list = TestListObject.construct_from({
54
+ :data => [{ :id => 1 }],
55
+ :has_more => true,
56
+ :url => "/things",
57
+ })
50
58
  @mock.expects(:get).once.with("#{Stripe.api_base}/things?starting_after=1", nil, nil).
51
59
  returns(make_response({ :data => [{ :id => 2 }, { :id => 3}], :has_more => false }))
52
60
 
@@ -70,7 +78,11 @@ module Stripe
70
78
  #
71
79
 
72
80
  should "fetch a next page through #next_page" do
73
- list = TestListObject.construct_from({ :data => [{ :id => 1 }], :has_more => true })
81
+ list = TestListObject.construct_from({
82
+ :data => [{ :id => 1 }],
83
+ :has_more => true,
84
+ :url => "/things",
85
+ })
74
86
  @mock.expects(:get).once.with("#{Stripe.api_base}/things?starting_after=1", nil, nil).
75
87
  returns(make_response({ :data => [{ :id => 2 }], :has_more => false }))
76
88
  next_list = list.next_page
@@ -78,7 +90,11 @@ module Stripe
78
90
  end
79
91
 
80
92
  should "fetch a next page through #next_page and respect limit" do
81
- list = TestListObject.construct_from({ :data => [{ :id => 1 }], :has_more => true })
93
+ list = TestListObject.construct_from({
94
+ :data => [{ :id => 1 }],
95
+ :has_more => true,
96
+ :url => "/things",
97
+ })
82
98
  list.filters = { :expand => ['data.source'], :limit => 3 }
83
99
  @mock.expects(:get).with do |url, _, _|
84
100
  u = URI.parse(url)
@@ -94,7 +110,11 @@ module Stripe
94
110
  end
95
111
 
96
112
  should "fetch an empty page through #next_page" do
97
- list = TestListObject.construct_from({ :data => [{ :id => 1 }], :has_more => false })
113
+ list = TestListObject.construct_from({
114
+ :data => [{ :id => 1 }],
115
+ :has_more => false,
116
+ :url => "/things",
117
+ })
98
118
  next_list = list.next_page
99
119
  assert_equal Stripe::ListObject.empty_list, next_list
100
120
  end
@@ -104,7 +124,10 @@ module Stripe
104
124
  #
105
125
 
106
126
  should "fetch a next page through #previous_page" do
107
- list = TestListObject.construct_from({ :data => [{ :id => 2 }] })
127
+ list = TestListObject.construct_from({
128
+ :data => [{ :id => 2 }],
129
+ :url => "/things",
130
+ })
108
131
  @mock.expects(:get).once.with("#{Stripe.api_base}/things?ending_before=2", nil, nil).
109
132
  returns(make_response({ :data => [{ :id => 1 }] }))
110
133
  next_list = list.previous_page
@@ -112,7 +135,10 @@ module Stripe
112
135
  end
113
136
 
114
137
  should "fetch a next page through #previous_page and respect limit" do
115
- list = TestListObject.construct_from({ :data => [{ :id => 2 }] })
138
+ list = TestListObject.construct_from({
139
+ :data => [{ :id => 2 }],
140
+ :url => "/things",
141
+ })
116
142
  list.filters = { :expand => ['data.source'], :limit => 3 }
117
143
  @mock.expects(:get).with do |url, _, _|
118
144
  # apparently URI.parse in 1.8.7 doesn't support query parameters ...
@@ -139,10 +165,10 @@ module Stripe
139
165
  @mock.expects(:get).twice.returns(make_response(make_charge_array))
140
166
  c = Stripe::Charge.all
141
167
  assert c.kind_of?(Stripe::ListObject)
142
- assert_equal('/v1/charges', c.url)
168
+ assert_equal('/v1/charges', c.resource_url)
143
169
  all = c.all
144
170
  assert all.kind_of?(Stripe::ListObject)
145
- assert_equal('/v1/charges', all.url)
171
+ assert_equal('/v1/charges', all.resource_url)
146
172
  assert all.data.kind_of?(Array)
147
173
  end
148
174
  end
@@ -150,7 +176,4 @@ end
150
176
 
151
177
  # A helper class with a URL that allows us to try out pagination.
152
178
  class TestListObject < Stripe::ListObject
153
- def url
154
- "/things"
155
- end
156
179
  end