stripe 1.36.1 → 1.36.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 (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