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
@@ -2,8 +2,8 @@ module Braintree
2
2
  class CreditCardVerification
3
3
  include BaseModule
4
4
 
5
- attr_reader :avs_error_response_code, :avs_postal_code_response_code,
6
- :avs_street_address_response_code, :cvv_response_code, :status
5
+ attr_reader :avs_error_response_code, :avs_postal_code_response_code, :avs_street_address_response_code,
6
+ :cvv_response_code, :processor_response_code, :processor_response_text, :status
7
7
 
8
8
  def initialize(attributes) # :nodoc:
9
9
  set_instance_variables_from_hash(attributes)
@@ -11,7 +11,8 @@ module Braintree
11
11
 
12
12
  def inspect # :nodoc:
13
13
  attr_order = [
14
- :status, :cvv_response_code, :avs_error_response_code,
14
+ :status, :processor_response_code, :processor_response_text,
15
+ :cvv_response_code, :avs_error_response_code,
15
16
  :avs_postal_code_response_code, :avs_street_address_response_code
16
17
  ]
17
18
  formatted_attrs = attr_order.map do |attr|
@@ -1,10 +1,10 @@
1
1
  module Braintree
2
2
  class Customer
3
3
  include BaseModule
4
-
4
+
5
5
  attr_reader :addresses, :company, :created_at, :credit_cards, :email, :fax, :first_name, :id, :last_name,
6
6
  :phone, :updated_at, :website, :custom_fields
7
-
7
+
8
8
  # Returns a PagedCollection of all customers stored in the vault. Due to race conditions, this method
9
9
  # may not reliably return all customers stored in the vault.
10
10
  #
@@ -25,7 +25,7 @@ module Braintree
25
25
  end
26
26
  PagedCollection.new(attributes) { |page_number| Customer.all(:page => page_number) }
27
27
  end
28
-
28
+
29
29
  # Creates a customer using the given +attributes+. If <tt>:id</tt> is not passed,
30
30
  # the gateway will generate it.
31
31
  #
@@ -47,7 +47,7 @@ module Braintree
47
47
  Util.verify_keys(_create_signature, attributes)
48
48
  _do_create "/customers", :customer => attributes
49
49
  end
50
-
50
+
51
51
  def self.create!(attributes = {})
52
52
  return_object_or_raise(:customer) { create(attributes) }
53
53
  end
@@ -60,15 +60,15 @@ module Braintree
60
60
  params = TransparentRedirect.parse_and_validate_query_string query_string
61
61
  _do_create("/customers/all/confirm_transparent_redirect_request", :id => params[:id])
62
62
  end
63
-
63
+
64
64
  def self.create_customer_transparent_redirect_url
65
65
  "#{Braintree::Configuration.base_merchant_url}/customers"
66
66
  end
67
-
67
+
68
68
  def self.credit(customer_id, transaction_attributes)
69
69
  Transaction.credit(transaction_attributes.merge(:customer_id => customer_id))
70
70
  end
71
-
71
+
72
72
  def self.credit!(customer_id, transaction_attributes)
73
73
  return_object_or_raise(:transaction){ credit(customer_id, transaction_attributes) }
74
74
  end
@@ -77,18 +77,18 @@ module Braintree
77
77
  Http.delete("/customers/#{customer_id}")
78
78
  SuccessfulResult.new
79
79
  end
80
-
80
+
81
81
  def self.find(customer_id)
82
82
  response = Http.get("/customers/#{customer_id}")
83
83
  new(response[:customer])
84
84
  rescue NotFoundError
85
85
  raise NotFoundError, "customer with id #{customer_id.inspect} not found"
86
86
  end
87
-
87
+
88
88
  def self.sale(customer_id, transaction_attributes)
89
89
  Transaction.sale(transaction_attributes.merge(:customer_id => customer_id))
90
90
  end
91
-
91
+
92
92
  def self.sale!(customer_id, transaction_attributes)
93
93
  return_object_or_raise(:transaction){ sale(customer_id, transaction_attributes) }
94
94
  end
@@ -103,12 +103,12 @@ module Braintree
103
103
  end
104
104
  PagedCollection.new(attributes) { |page_number| Customer.transactions(customer_id, :page => page_number) }
105
105
  end
106
-
106
+
107
107
  def self.update(customer_id, attributes)
108
108
  Util.verify_keys(_update_signature, attributes)
109
109
  _do_update(:put, "/customers/#{customer_id}", :customer => attributes)
110
110
  end
111
-
111
+
112
112
  def self.update!(customer_id, attributes)
113
113
  return_object_or_raise(:customer) { update(customer_id, attributes) }
114
114
  end
@@ -121,25 +121,25 @@ module Braintree
121
121
  params = TransparentRedirect.parse_and_validate_query_string(query_string)
122
122
  _do_update(:post, "/customers/all/confirm_transparent_redirect_request", :id => params[:id])
123
123
  end
124
-
124
+
125
125
  def initialize(attributes) # :nodoc:
126
126
  set_instance_variables_from_hash(attributes)
127
127
  @credit_cards = (@credit_cards || []).map { |pm| CreditCard._new pm }
128
128
  @addresses = (@addresses || []).map { |addr| Address._new addr }
129
129
  end
130
-
130
+
131
131
  def credit(transaction_attributes)
132
132
  Customer.credit(id, transaction_attributes)
133
133
  end
134
-
134
+
135
135
  def credit!(transaction_attributes)
136
136
  return_object_or_raise(:transaction) { credit(transaction_attributes) }
137
137
  end
138
-
138
+
139
139
  def delete
140
140
  Customer.delete(id)
141
141
  end
142
-
142
+
143
143
  def inspect # :nodoc:
144
144
  first = [:id]
145
145
  last = [:addresses, :credit_cards]
@@ -149,11 +149,11 @@ module Braintree
149
149
  end
150
150
  "#<#{self.class} #{nice_attributes.join(', ')}>"
151
151
  end
152
-
152
+
153
153
  def sale(transaction_attributes)
154
154
  Customer.sale(id, transaction_attributes)
155
155
  end
156
-
156
+
157
157
  def sale!(transaction_attributes)
158
158
  return_object_or_raise(:transaction) { sale(transaction_attributes) }
159
159
  end
@@ -172,9 +172,9 @@ module Braintree
172
172
  ErrorResult.new(response[:api_error_response])
173
173
  else
174
174
  raise "expected :customer or :errors"
175
- end
175
+ end
176
176
  end
177
-
177
+
178
178
  def update!(attributes)
179
179
  return_object_or_raise(:customer) { update(attributes) }
180
180
  end
@@ -228,7 +228,7 @@ module Braintree
228
228
 
229
229
  def self._new(*args) # :nodoc:
230
230
  self.new *args
231
- end
231
+ end
232
232
 
233
233
  def self._update_signature # :nodoc:
234
234
  [ :company, :email, :fax, :first_name, :id, :last_name, :phone, :website, {:custom_fields => :_any_key_} ]
@@ -6,14 +6,8 @@ module Braintree
6
6
 
7
7
  def self._hmac_sha1(key, message)
8
8
  key_digest = ::Digest::SHA1.digest(key)
9
- inner_padding = "\x36" * 64
10
- outer_padding = "\x5c" * 64
11
- 0.upto(19) do |i|
12
- inner_padding[i] ^= key_digest[i]
13
- outer_padding[i] ^= key_digest[i]
14
- end
15
- inner_hash = ::Digest::SHA1.digest(inner_padding + message.to_s)
16
- ::Digest::SHA1.hexdigest(outer_padding + inner_hash)
9
+ sha1 = OpenSSL::Digest::Digest.new("sha1")
10
+ OpenSSL::HMAC.hexdigest(sha1, key_digest, message.to_s)
17
11
  end
18
12
  end
19
13
  end
@@ -1,7 +1,7 @@
1
1
  module Braintree
2
2
  # The ErrorCodes module provides constants for validation errors.
3
3
  # The constants should be used to check for a specific validation error in a ValidationErrorCollection.
4
- # The error messages returned from the server may change, but the codes will remain the same.
4
+ # The error messages returned from the server may change, but the codes will remain the same.
5
5
  module ErrorCodes
6
6
  module Address
7
7
  CannotBeBlank = "81801"
@@ -35,7 +35,7 @@ module Braintree
35
35
  ExpirationYearIsInvalid = "81713"
36
36
  NumberIsRequired = "81714"
37
37
  NumberIsInvalid = "81715"
38
- NumberInvalidLength = "81716"
38
+ NumberHasInvalidLength = "81716"
39
39
  NumberMustBeTestNumber = "81717"
40
40
  TokenInvalid = "91718"
41
41
  TokenIsInUse = "91719"
@@ -62,7 +62,19 @@ module Braintree
62
62
  WebsiteIsTooLong = "81615"
63
63
  WebsiteIsInvalid = "81616"
64
64
  end
65
-
65
+
66
+ module Subscription
67
+ CannotEditCanceledSubscription = "81901"
68
+ IdIsInUse = "81902"
69
+ PriceCannotBeBlank = "81903"
70
+ PriceFormatIsInvalid = "81904"
71
+ StatusIsCanceled = "81905"
72
+ TokenFormatIsInvalid = "81906"
73
+ TrialDurationFormatIsInvalid = "81907"
74
+ TrialDurationIsRequired = "81908"
75
+ TrialDurationUnitIsInvalid = "81909"
76
+ end
77
+
66
78
  module Transaction
67
79
  AmountCannotBeNegative = "81501"
68
80
  AmountIsRequired = "81502"
@@ -73,6 +85,7 @@ module Braintree
73
85
  CannotSubmitForSettlement = "91507"
74
86
  CreditCardIsRequired = "91508"
75
87
  CustomerDefaultPaymentMethodCardTypeIsNotAccepted = "81509"
88
+ CustomFieldIsInvalid = "91526"
76
89
  CustomerIdIsInvalid = "91510"
77
90
  CustomerDoesNotHaveCreditCard = "91511"
78
91
  HasAlreadyBeenRefunded = "91512"
@@ -1,4 +1,4 @@
1
- module Braintree
1
+ module Braintree
2
2
  # An ErrorResult will be returned from non-bang methods when
3
3
  # validations fail. It will provide access to the params passed
4
4
  # to the server. The params are primarily useful for re-populaing
@@ -16,9 +16,9 @@ module Braintree
16
16
  # end
17
17
  # end
18
18
  class ErrorResult
19
-
20
- attr_reader :credit_card_verification, :errors, :params
21
-
19
+
20
+ attr_reader :credit_card_verification, :errors, :params
21
+
22
22
  def initialize(data) # :nodoc:
23
23
  @params = data[:params]
24
24
  if data[:verification]
@@ -30,7 +30,7 @@ module Braintree
30
30
  def inspect # :nodoc:
31
31
  "#<#{self.class} params:{...} errors:<#{@errors._inner_inspect}>>"
32
32
  end
33
-
33
+
34
34
  # Always returns false.
35
35
  def success?
36
36
  false
@@ -0,0 +1,58 @@
1
+ module Braintree # :nodoc:
2
+ # Super class for all Braintree exceptions.
3
+ class BraintreeError < ::StandardError; end
4
+
5
+ # Raised when authentication fails. This may be caused by an incorrect <tt>Braintree::Configuration</tt>
6
+ class AuthenticationError < BraintreeError; end
7
+
8
+ # Raised when the API key being used is not authorized to perform the attempted action according
9
+ # to the roles assigned to the user who owns the API key.
10
+ class AuthorizationError < BraintreeError; end
11
+
12
+ # Raised when the Braintree gem is not completely configured. See <tt>Braintree::Configuration</tt>.
13
+ class ConfigurationError < BraintreeError
14
+ def initialize(setting, message) # :nodoc:
15
+ super "Braintree::Configuration.#{setting} #{message}"
16
+ end
17
+ end
18
+
19
+ # Raised when the gateway is down for maintenance.
20
+ class DownForMaintenanceError < BraintreeError; end
21
+
22
+ # Raised from methods that confirm transparent request requests
23
+ # when the given query string cannot be verified. This may indicate
24
+ # an attempted hack on the merchant's transparent redirect
25
+ # confirmation URL.
26
+ class ForgedQueryString < BraintreeError; end
27
+
28
+ # Raised when a record could not be found.
29
+ class NotFoundError < BraintreeError; end
30
+
31
+ # Raised when an unexpected server error occurs.
32
+ class ServerError < BraintreeError; end
33
+
34
+ # Raised when the SSL certificate fails verification.
35
+ class SSLCertificateError < BraintreeError; end
36
+
37
+ # Raised when an error occurs that the client library is not built to handle.
38
+ # This shouldn't happen.
39
+ class UnexpectedError < BraintreeError; end
40
+
41
+ # Raised from bang methods when validations fail.
42
+ class ValidationsFailed < BraintreeError
43
+ attr_reader :error_result
44
+
45
+ def initialize(error_result)
46
+ @error_result = error_result
47
+ end
48
+
49
+ def inspect
50
+ "#<#{self.class} error_result: #{@error_result.inspect}>"
51
+ end
52
+
53
+ def to_s
54
+ inspect
55
+ end
56
+ end
57
+ end
58
+
@@ -1,6 +1,6 @@
1
1
  module Braintree
2
2
  module Http # :nodoc:
3
-
3
+
4
4
  def self.delete(path)
5
5
  response = _http_do Net::HTTP::Delete, path
6
6
  if response.code.to_i == 200
@@ -9,7 +9,7 @@ module Braintree
9
9
  Util.raise_exception_for_status_code(response.code)
10
10
  end
11
11
  end
12
-
12
+
13
13
  def self.get(path)
14
14
  response = _http_do Net::HTTP::Get, path
15
15
  if response.code.to_i == 200
@@ -18,7 +18,7 @@ module Braintree
18
18
  Util.raise_exception_for_status_code(response.code)
19
19
  end
20
20
  end
21
-
21
+
22
22
  def self.post(path, params = nil)
23
23
  response = _http_do Net::HTTP::Post, path, _build_xml(params)
24
24
  if response.code.to_i == 200 || response.code.to_i == 201 || response.code.to_i == 422
@@ -27,7 +27,7 @@ module Braintree
27
27
  Util.raise_exception_for_status_code(response.code)
28
28
  end
29
29
  end
30
-
30
+
31
31
  def self.put(path, params = nil)
32
32
  response = _http_do Net::HTTP::Put, path, _build_xml(params)
33
33
  if response.code.to_i == 200 || response.code.to_i == 201 || response.code.to_i == 422
@@ -41,7 +41,7 @@ module Braintree
41
41
  return nil if params.nil?
42
42
  Braintree::Xml.hash_to_xml params
43
43
  end
44
-
44
+
45
45
  def self._http_do(http_verb, path, body = nil)
46
46
  connection = Net::HTTP.new(Configuration.server, Configuration.port)
47
47
  if Configuration.ssl?
@@ -80,11 +80,11 @@ module Braintree
80
80
  raise UnexpectedError, "expected a gzip'd response"
81
81
  end
82
82
  end
83
-
83
+
84
84
  def self._current_time
85
85
  Time.now.utc.strftime("%d/%b/%Y %H:%M:%S %Z")
86
86
  end
87
-
87
+
88
88
  def self._format_and_sanitize_body_for_log(input_xml)
89
89
  formatted_xml = input_xml.gsub(/^/, "[Braintree] ")
90
90
  formatted_xml = formatted_xml.gsub(/<number>(.{6}).+?(.{4})<\/number>/, '<number>\1******\2</number>')
@@ -2,9 +2,9 @@ module Braintree
2
2
  class PagedCollection
3
3
  include BaseModule
4
4
  include Enumerable
5
-
5
+
6
6
  attr_reader :current_page_number, :items, :next_page_number, :page_size, :previous_page_number, :total_items
7
-
7
+
8
8
  def initialize(attributes, &block) # :nodoc:
9
9
  set_instance_variables_from_hash attributes
10
10
  @paging_block = block
@@ -14,11 +14,11 @@ module Braintree
14
14
  def [](index)
15
15
  @items[index]
16
16
  end
17
-
18
- # Yields each item on the current page.
17
+
18
+ # Yields each item on the current page.
19
19
  def each(&block)
20
20
  @items.each(&block)
21
- end
21
+ end
22
22
 
23
23
  # Returns the first item from the current page.
24
24
  def first
@@ -29,27 +29,27 @@ module Braintree
29
29
  def last_page?
30
30
  current_page_number == total_pages
31
31
  end
32
-
33
- # Retrieves the next page of records.
32
+
33
+ # Retrieves the next page of records.
34
34
  def next_page
35
35
  if last_page?
36
36
  return nil
37
37
  end
38
38
  @paging_block.call(next_page_number)
39
39
  end
40
-
41
- # The next page number. Returns +nil+ if on the last page.
40
+
41
+ # The next page number. Returns +nil+ if on the last page.
42
42
  def next_page_number
43
43
  last_page? ? nil : current_page_number + 1
44
44
  end
45
-
46
- # Returns the total number of pages.
45
+
46
+ # Returns the total number of pages.
47
47
  def total_pages
48
- total = total_items / page_size
48
+ total = total_items / page_size
49
49
  if total_items % page_size != 0
50
50
  total += 1
51
- end
51
+ end
52
52
  total
53
- end
53
+ end
54
54
  end
55
55
  end
@@ -7,7 +7,8 @@ module Braintree
7
7
  def self.check_dates # :nodoc:
8
8
  {
9
9
  "QA" => qa_expiration_date,
10
- "Sandbox" => sandbox_expiration_date
10
+ "Sandbox" => sandbox_expiration_date,
11
+ "Production" => production_expiration_date
11
12
  }.each do |host, expiration_date|
12
13
  if Date.today + (3 * 30) > expiration_date
13
14
  Configuration.logger.warn "[Braintree] The SSL Certificate for the #{host} environment will expire on #{expiration_date}. Please check for an updated client library."
@@ -16,6 +17,9 @@ module Braintree
16
17
  @ssl_expiration_dates_checked = true
17
18
  end
18
19
 
20
+ def self.production_expiration_date # :nodoc:
21
+ Date.new(2012, 1, 8)
22
+ end
19
23
 
20
24
  def self.sandbox_expiration_date # :nodoc:
21
25
  Date.new(2010, 12, 1)