braintree 1.0.1 → 1.1.0

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 (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)