braintree 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/lib/braintree.rb +36 -46
  2. data/lib/braintree/address.rb +14 -14
  3. data/lib/braintree/base_module.rb +3 -3
  4. data/lib/braintree/configuration.rb +16 -16
  5. data/lib/braintree/credit_card.rb +39 -30
  6. data/lib/braintree/credit_card_verification.rb +4 -3
  7. data/lib/braintree/customer.rb +22 -22
  8. data/lib/braintree/digest.rb +2 -8
  9. data/lib/braintree/error_codes.rb +16 -3
  10. data/lib/braintree/error_result.rb +5 -5
  11. data/lib/braintree/exceptions.rb +58 -0
  12. data/lib/braintree/http.rb +7 -7
  13. data/lib/braintree/paged_collection.rb +14 -14
  14. data/lib/braintree/ssl_expiration_check.rb +5 -1
  15. data/lib/braintree/subscription.rb +110 -0
  16. data/lib/braintree/successful_result.rb +3 -3
  17. data/lib/braintree/test/credit_card_numbers.rb +1 -1
  18. data/lib/braintree/test/transaction_amounts.rb +18 -0
  19. data/lib/braintree/transaction.rb +52 -25
  20. data/lib/braintree/transaction/address_details.rb +2 -2
  21. data/lib/braintree/transaction/credit_card_details.rb +12 -4
  22. data/lib/braintree/transaction/customer_details.rb +9 -1
  23. data/lib/braintree/transaction/status_details.rb +1 -1
  24. data/lib/braintree/transparent_redirect.rb +15 -15
  25. data/lib/braintree/util.rb +7 -7
  26. data/lib/braintree/validation_error.rb +1 -1
  27. data/lib/braintree/validation_error_collection.rb +6 -6
  28. data/lib/braintree/version.rb +3 -3
  29. data/lib/braintree/xml/generator.rb +2 -2
  30. data/lib/braintree/xml/libxml.rb +1 -1
  31. data/lib/braintree/xml/parser.rb +1 -1
  32. data/spec/integration/braintree/address_spec.rb +12 -12
  33. data/spec/integration/braintree/credit_card_spec.rb +189 -37
  34. data/spec/integration/braintree/customer_spec.rb +35 -35
  35. data/spec/integration/braintree/http_spec.rb +3 -3
  36. data/spec/integration/braintree/subscription_spec.rb +362 -0
  37. data/spec/integration/braintree/transaction_spec.rb +130 -58
  38. data/spec/integration/spec_helper.rb +1 -1
  39. data/spec/spec_helper.rb +4 -4
  40. data/spec/unit/braintree/address_spec.rb +6 -6
  41. data/spec/unit/braintree/base_module_spec.rb +18 -0
  42. data/spec/unit/braintree/configuration_spec.rb +10 -10
  43. data/spec/unit/braintree/credit_card_spec.rb +15 -15
  44. data/spec/unit/braintree/credit_card_verification_spec.rb +4 -2
  45. data/spec/unit/braintree/customer_spec.rb +8 -8
  46. data/spec/unit/braintree/digest_spec.rb +4 -0
  47. data/spec/unit/braintree/http_spec.rb +2 -2
  48. data/spec/unit/braintree/paged_collection_spec.rb +12 -12
  49. data/spec/unit/braintree/ssl_expiration_check_spec.rb +18 -9
  50. data/spec/unit/braintree/transaction/credit_card_details_spec.rb +15 -0
  51. data/spec/unit/braintree/transaction/customer_details_spec.rb +19 -0
  52. data/spec/unit/braintree/transaction_spec.rb +14 -14
  53. data/spec/unit/braintree/transparent_redirect_spec.rb +11 -11
  54. data/spec/unit/braintree/util_spec.rb +8 -8
  55. data/spec/unit/braintree/validation_error_collection_spec.rb +6 -6
  56. data/spec/unit/braintree/validation_error_spec.rb +2 -2
  57. data/spec/unit/braintree/xml/libxml_spec.rb +5 -5
  58. data/spec/unit/braintree/xml_spec.rb +16 -16
  59. data/spec/unit/braintree_spec.rb +11 -0
  60. metadata +8 -2
@@ -0,0 +1,110 @@
1
+ module Braintree
2
+ class Subscription
3
+ include BaseModule
4
+
5
+ module Status
6
+ Active = 'Active'
7
+ Canceled = 'Canceled'
8
+ PastDue = 'Past Due'
9
+ end
10
+
11
+ module TrialDurationUnit
12
+ Day = "day"
13
+ Month = "month"
14
+ end
15
+
16
+ attr_reader :price, :plan_id, :id, :status, :payment_method_token
17
+ attr_reader :first_billing_date, :next_billing_date, :billing_period_start_date, :billing_period_end_date
18
+ attr_reader :trial_period, :trial_duration, :trial_duration_unit
19
+ attr_reader :failure_count
20
+ attr_reader :transactions
21
+
22
+ def self.cancel(subscription_id)
23
+ response = Http.put "/subscriptions/#{subscription_id}/cancel"
24
+ if response[:subscription]
25
+ SuccessfulResult.new(:subscription => new(response[:subscription]))
26
+ elsif response[:api_error_response]
27
+ ErrorResult.new(response[:api_error_response])
28
+ else
29
+ raise UnexpectedError, "expected :subscription or :api_error_response"
30
+ end
31
+ rescue NotFoundError
32
+ raise NotFoundError, "subscription with id #{subscription_id.inspect} not found"
33
+ end
34
+
35
+ def self.create(attributes)
36
+ Util.verify_keys(_create_signature, attributes)
37
+ _do_create "/subscriptions", :subscription => attributes
38
+ end
39
+
40
+ # Finds the subscription with the given id. Raises a Braintree::NotFoundError
41
+ # if the subscription cannot be found.
42
+ def self.find(id)
43
+ response = Http.get "/subscriptions/#{id}"
44
+ new(response[:subscription])
45
+ rescue NotFoundError
46
+ raise NotFoundError, "subscription with id #{id.inspect} not found"
47
+ end
48
+
49
+ def self.update(subscription_id, attributes)
50
+ Util.verify_keys(_update_signature, attributes)
51
+ response = Http.put "/subscriptions/#{subscription_id}", :subscription => attributes
52
+ if response[:subscription]
53
+ SuccessfulResult.new(:subscription => new(response[:subscription]))
54
+ elsif response[:api_error_response]
55
+ ErrorResult.new(response[:api_error_response])
56
+ else
57
+ raise UnexpectedError, "expected :subscription or :api_error_response"
58
+ end
59
+ end
60
+
61
+ def self._create_signature # :nodoc:
62
+ [
63
+ :id,
64
+ :payment_method_token,
65
+ :plan_id,
66
+ :price,
67
+ :trial_duration,
68
+ :trial_duration_unit,
69
+ :trial_period
70
+ ]
71
+ end
72
+
73
+ def initialize(attributes) # :nodoc:
74
+ _init attributes
75
+ transactions.map! {|attrs| Transaction._new(attrs) }
76
+ end
77
+
78
+
79
+ # True if <tt>other</tt> has the same id.
80
+ def ==(other)
81
+ id == other.id
82
+ end
83
+
84
+ def self._do_create(url, params) # :nodoc:
85
+ response = Http.post url, params
86
+ if response[:subscription]
87
+ SuccessfulResult.new(:subscription => new(response[:subscription]))
88
+ elsif response[:api_error_response]
89
+ ErrorResult.new(response[:api_error_response])
90
+ else
91
+ raise UnexpectedError, "expected :subscription or :api_error_response"
92
+ end
93
+ end
94
+
95
+ def _init(attributes) # :nodoc:
96
+ set_instance_variables_from_hash(attributes)
97
+ if self.price
98
+ instance_variable_set :@price, BigDecimal.new(self.price)
99
+ end
100
+ end
101
+
102
+ def self._update_signature # :nodoc:
103
+ [
104
+ :id,
105
+ :plan_id,
106
+ :price
107
+ ]
108
+ end
109
+ end
110
+ end
@@ -1,4 +1,4 @@
1
- module Braintree
1
+ module Braintree
2
2
  # A SuccessfulResult will be returned from non-bang methods when
3
3
  # validations pass. It will provide access to the created resource.
4
4
  # For example, when creating a customer, SuccessfulResult will
@@ -13,7 +13,7 @@ module Braintree
13
13
  # end
14
14
  class SuccessfulResult
15
15
  include BaseModule
16
-
16
+
17
17
  def initialize(attributes = {}) # :nodoc:
18
18
  @attrs = attributes.keys
19
19
  singleton_class.class_eval do
@@ -22,7 +22,7 @@ module Braintree
22
22
  value
23
23
  end
24
24
  end
25
- end
25
+ end
26
26
  end
27
27
 
28
28
  def inspect # :nodoc:
@@ -43,7 +43,7 @@ module Braintree
43
43
  Visa = "4222222222222"
44
44
  Numbers = [AmEx, Discover, MasterCard, Visa]
45
45
  end
46
-
46
+
47
47
  All = AmExes + Discovers + MasterCards + Visas
48
48
  end
49
49
  end
@@ -6,5 +6,23 @@ module Braintree
6
6
  Authorize = "1000.00"
7
7
  Decline = "2000.00"
8
8
  end
9
+
10
+ module Plans
11
+ TrialPlan = {
12
+ :description => "Plan for integration tests -- with trial",
13
+ :id => "integration_trial_plan",
14
+ :price => "43.21",
15
+ :trial_period => true,
16
+ :trial_duration => 2,
17
+ :trial_duration_unit => Subscription::TrialDurationUnit::Day
18
+ }
19
+
20
+ TriallessPlan = {
21
+ :description => "Plan for integration tests -- without a trial",
22
+ :id => "integration_trialless_plan",
23
+ :price => "12.34",
24
+ :trial_period => false
25
+ }
26
+ end
9
27
  end
10
28
  end
@@ -9,7 +9,7 @@ module Braintree
9
9
  # :number => "5105105105105100",
10
10
  # :expiration_date => "05/2012"
11
11
  # }
12
- # )
12
+ # )
13
13
  #
14
14
  # Full example:
15
15
  #
@@ -118,12 +118,12 @@ module Braintree
118
118
  # )
119
119
  class Transaction
120
120
  include BaseModule
121
-
121
+
122
122
  module Type # :nodoc:
123
123
  Credit = "credit" # :nodoc:
124
124
  Sale = "sale" # :nodoc:
125
125
  end
126
-
126
+
127
127
  attr_reader :avs_error_response_code, :avs_postal_code_response_code, :avs_street_address_response_code
128
128
  attr_reader :amount, :created_at, :credit_card_details, :customer_details, :id, :status
129
129
  attr_reader :custom_fields
@@ -132,6 +132,8 @@ module Braintree
132
132
  attr_reader :status_history
133
133
  # The response code from the processor.
134
134
  attr_reader :processor_response_code
135
+ # The response text from the processor.
136
+ attr_reader :processor_response_text
135
137
  # Will either be "sale" or "credit"
136
138
  attr_reader :type
137
139
  attr_reader :updated_at
@@ -140,7 +142,7 @@ module Braintree
140
142
  Util.verify_keys(_create_signature, attributes)
141
143
  _do_create "/transactions", :transaction => attributes
142
144
  end
143
-
145
+
144
146
  def self.create!(attributes)
145
147
  return_object_or_raise(:transaction) { create(attributes) }
146
148
  end
@@ -154,12 +156,12 @@ module Braintree
154
156
  def self.create_transaction_url
155
157
  "#{Braintree::Configuration.base_merchant_url}/transactions/all/create_via_transparent_redirect_request"
156
158
  end
157
-
158
- # Creates a credit transaction.
159
+
160
+ # Creates a credit transaction.
159
161
  def self.credit(attributes)
160
162
  create(attributes.merge(:type => 'credit'))
161
163
  end
162
-
164
+
163
165
  def self.credit!(attributes)
164
166
  return_object_or_raise(:transaction) { credit(attributes) }
165
167
  end
@@ -177,11 +179,11 @@ module Braintree
177
179
  def self.sale(attributes)
178
180
  create(attributes.merge(:type => 'sale'))
179
181
  end
180
-
182
+
181
183
  def self.sale!(attributes)
182
184
  return_object_or_raise(:transaction) { sale(attributes) }
183
185
  end
184
-
186
+
185
187
  # Returns a PagedCollection of transactions matching the search query.
186
188
  # If <tt>query</tt> is a string, the search will be a basic search.
187
189
  # If <tt>query</tt> is a hash, the search will be an advanced search.
@@ -231,21 +233,25 @@ module Braintree
231
233
  def initialize(attributes) # :nodoc:
232
234
  _init attributes
233
235
  end
234
-
236
+
235
237
  # True if <tt>other</tt> has the same id.
236
238
  def ==(other)
237
239
  id == other.id
238
240
  end
239
-
241
+
240
242
  def inspect # :nodoc:
241
243
  first = [:id, :type, :amount, :status]
242
244
  order = first + (self.class._attributes - first)
243
245
  nice_attributes = order.map do |attr|
244
- "#{attr}: #{send(attr).inspect}"
246
+ if attr == :amount
247
+ self.amount ? "amount: #{self.amount.to_s("F").inspect}" : "amount: nil"
248
+ else
249
+ "#{attr}: #{send(attr).inspect}"
250
+ end
245
251
  end
246
252
  "#<#{self.class} #{nice_attributes.join(', ')}>"
247
253
  end
248
-
254
+
249
255
  # Creates a credit transaction that refunds this transaction.
250
256
  def refund
251
257
  response = Http.post "/transactions/#{id}/refund"
@@ -258,7 +264,7 @@ module Braintree
258
264
  raise UnexpectedError, "expected :transaction or :api_error_response"
259
265
  end
260
266
  end
261
-
267
+
262
268
  # Returns true if the transaction has been refunded. False otherwise.
263
269
  def refunded?
264
270
  !@refund_id.nil?
@@ -280,7 +286,16 @@ module Braintree
280
286
  def submit_for_settlement!(amount = nil)
281
287
  return_object_or_raise(:transaction) { submit_for_settlement(amount) }
282
288
  end
283
-
289
+
290
+ # If this transaction was stored in the vault, or created from vault records,
291
+ # vault_billing_address will return the associated Braintree::Address. Because the
292
+ # vault billing address can be updated after the transaction was created, the attributes
293
+ # on vault_billing_address may not match the attributes on billing_details.
294
+ def vault_billing_address
295
+ return nil if billing_details.id.nil?
296
+ Address.find(customer_details.id, billing_details.id)
297
+ end
298
+
284
299
  # If this transaction was stored in the vault, or created from vault records,
285
300
  # vault_credit_card will return the associated Braintree::CreditCard. Because the
286
301
  # vault credit card can be updated after the transaction was created, the attributes
@@ -289,7 +304,7 @@ module Braintree
289
304
  return nil if credit_card_details.token.nil?
290
305
  CreditCard.find(credit_card_details.token)
291
306
  end
292
-
307
+
293
308
  # If this transaction was stored in the vault, or created from vault records,
294
309
  # vault_customer will return the associated Braintree::Customer. Because the
295
310
  # vault customer can be updated after the transaction was created, the attributes
@@ -298,7 +313,16 @@ module Braintree
298
313
  return nil if customer_details.id.nil?
299
314
  Customer.find(customer_details.id)
300
315
  end
301
-
316
+
317
+ # If this transaction was stored in the vault, or created from vault records,
318
+ # vault_shipping_address will return the associated Braintree::Address. Because the
319
+ # vault shipping address can be updated after the transaction was created, the attributes
320
+ # on vault_shipping_address may not match the attributes on shipping_details.
321
+ def vault_shipping_address
322
+ return nil if shipping_details.id.nil?
323
+ Address.find(customer_details.id, shipping_details.id)
324
+ end
325
+
302
326
  # Voids the transaction.
303
327
  def void
304
328
  response = Http.put "/transactions/#{id}/void"
@@ -320,9 +344,9 @@ module Braintree
320
344
  protected :new
321
345
  def _new(*args) # :nodoc:
322
346
  self.new *args
323
- end
347
+ end
324
348
  end
325
-
349
+
326
350
  def self._do_create(url, params) # :nodoc:
327
351
  response = Http.post url, params
328
352
  if response[:transaction]
@@ -333,19 +357,19 @@ module Braintree
333
357
  raise UnexpectedError, "expected :transaction or :api_error_response"
334
358
  end
335
359
  end
336
-
360
+
337
361
  def self._advanced_search(query, options) # :nodoc:
338
362
  page = options[:page] || 1
339
363
  response = Http.post "/transactions/advanced_search?page=#{Util.url_encode(page)}", :search => query
340
364
  attributes = response[:credit_card_transactions]
341
365
  attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
342
- PagedCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
366
+ PagedCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
343
367
  end
344
368
 
345
369
  def self._attributes # :nodoc:
346
370
  [:amount, :created_at, :credit_card_details, :customer_details, :id, :status, :type, :updated_at]
347
371
  end
348
-
372
+
349
373
  def self._basic_search(query, options) # :nodoc:
350
374
  page = options[:page] || 1
351
375
  response = Http.get "/transactions/all/search?q=#{Util.url_encode(query)}&page=#{Util.url_encode(page)}"
@@ -353,7 +377,7 @@ module Braintree
353
377
  attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
354
378
  PagedCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
355
379
  end
356
-
380
+
357
381
  def self._create_signature # :nodoc:
358
382
  [
359
383
  :amount, :customer_id, :order_id, :payment_method_token, :type,
@@ -361,13 +385,16 @@ module Braintree
361
385
  {:customer => [:id, :company, :email, :fax, :first_name, :last_name, :phone, :website]},
362
386
  {:billing => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
363
387
  {:shipping => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
364
- {:options => [:store_in_vault, :submit_for_settlement, :add_billing_address_to_payment_method]},
388
+ {:options => [:store_in_vault, :submit_for_settlement, :add_billing_address_to_payment_method, :store_shipping_address_in_vault]},
365
389
  {:custom_fields => :_any_key_}
366
390
  ]
367
391
  end
368
-
392
+
369
393
  def _init(attributes) # :nodoc:
370
394
  set_instance_variables_from_hash(attributes)
395
+ if self.amount
396
+ instance_variable_set :@amount, BigDecimal.new(self.amount)
397
+ end
371
398
  @credit_card_details = CreditCardDetails.new(@credit_card)
372
399
  @customer_details = CustomerDetails.new(@customer)
373
400
  @billing_details = AddressDetails.new(@billing)
@@ -2,8 +2,8 @@ module Braintree
2
2
  class Transaction
3
3
  class AddressDetails # :nodoc:
4
4
  include BaseModule
5
-
6
- attr_reader :first_name, :last_name, :company,
5
+
6
+ attr_reader :id, :first_name, :last_name, :company,
7
7
  :street_address, :extended_address, :locality, :region,
8
8
  :postal_code, :country_name
9
9
 
@@ -2,14 +2,22 @@ module Braintree
2
2
  class Transaction
3
3
  class CreditCardDetails # :nodoc:
4
4
  include BaseModule
5
-
6
- attr_reader :bin, :card_type, :expiration_month,
7
- :expiration_year, :issuer_location, :last_4, :token
5
+
6
+ attr_reader :bin, :card_type, :customer_location, :expiration_month,
7
+ :expiration_year, :last_4, :token
8
8
 
9
9
  def initialize(attributes)
10
10
  set_instance_variables_from_hash attributes unless attributes.nil?
11
11
  end
12
-
12
+
13
+ def inspect
14
+ attr_order = [:token, :bin, :last_4, :card_type, :expiration_date, :customer_location]
15
+ formatted_attrs = attr_order.map do |attr|
16
+ "#{attr}: #{send(attr).inspect}"
17
+ end
18
+ "#<#{formatted_attrs.join(", ")}>"
19
+ end
20
+
13
21
  def expiration_date
14
22
  "#{expiration_month}/#{expiration_year}"
15
23
  end
@@ -2,12 +2,20 @@ module Braintree
2
2
  class Transaction
3
3
  class CustomerDetails # :nodoc:
4
4
  include BaseModule
5
-
5
+
6
6
  attr_reader :company, :email, :fax, :first_name, :id, :last_name, :phone, :website
7
7
 
8
8
  def initialize(attributes)
9
9
  set_instance_variables_from_hash attributes unless attributes.nil?
10
10
  end
11
+
12
+ def inspect
13
+ attr_order = [:id, :first_name, :last_name, :email, :company, :website, :phone, :fax]
14
+ formatted_attrs = attr_order.map do |attr|
15
+ "#{attr}: #{send(attr).inspect}"
16
+ end
17
+ "#<#{formatted_attrs.join(", ")}>"
18
+ end
11
19
  end
12
20
  end
13
21
  end
@@ -2,7 +2,7 @@ module Braintree
2
2
  class Transaction
3
3
  class StatusDetails # :nodoc:
4
4
  include BaseModule
5
-
5
+
6
6
  attr_reader :amount, :status, :timestamp, :transaction_source, :user
7
7
 
8
8
  def initialize(attributes)