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