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
data/lib/braintree.rb CHANGED
@@ -1,8 +1,10 @@
1
+ require 'bigdecimal'
1
2
  require "cgi"
2
3
  require "digest/sha1"
3
4
  require "logger"
4
5
  require "net/http"
5
6
  require "net/https"
7
+ require "openssl"
6
8
  require "stringio"
7
9
  require "time"
8
10
  require "zlib"
@@ -13,54 +15,42 @@ unless ::LibXML::XML.respond_to?(:default_keep_blanks=)
13
15
  raise LoadError, "The version of libxml that you're using is not compatible with the Braintree gem."
14
16
  end
15
17
 
16
- module Braintree # :nodoc:
17
- # Super class for all Braintree exceptions.
18
- class BraintreeError < ::StandardError; end
19
-
20
- # Raised when authentication fails. This may be caused by an incorrect <tt>Braintree::Configuration</tt>
21
- class AuthenticationError < BraintreeError; end
22
-
23
- # Raised when the API key being used is not authorized to perform the attempted action according
24
- # to the roles assigned to the user who owns the API key.
25
- class AuthorizationError < BraintreeError; end
26
-
27
- # Raised when the Braintree gem is not completely configured. See <tt>Braintree::Configuration</tt>.
28
- class ConfigurationError < BraintreeError
29
- def initialize(setting, message) # :nodoc:
30
- super "Braintree::Configuration.#{setting} #{message}"
31
- end
32
- end
33
-
34
- # Raised when the gateway is down for maintenance.
35
- class DownForMaintenanceError < BraintreeError; end
36
-
37
- # Raised from methods that confirm transparent request requests
38
- # when the given query string cannot be verified. This may indicate
39
- # an attempted hack on the merchant's transparent redirect
40
- # confirmation URL.
41
- class ForgedQueryString < BraintreeError; end
42
-
43
- # Raised when a record could not be found.
44
- class NotFoundError < BraintreeError; end
45
-
46
- # Raised when an unexpected server error occurs.
47
- class ServerError < BraintreeError; end
48
-
49
- # Raised when the SSL certificate fails verification.
50
- class SSLCertificateError < BraintreeError; end
51
-
52
- # Raised when an error occurs that the client library is not built to handle.
53
- # This shouldn't happen.
54
- class UnexpectedError < BraintreeError; end
55
-
56
- # Raised from bang methods when validations fail.
57
- class ValidationsFailed < BraintreeError; end
18
+ module Braintree
58
19
  end
59
20
 
21
+ require "braintree/exceptions"
60
22
  require "braintree/base_module"
61
23
 
62
- Dir.glob("#{File.dirname(__FILE__)}/braintree/**/*.rb").sort.each do |file|
63
- next if file =~ /base_module/
64
- load file
65
- end
24
+ require "braintree/address.rb"
25
+ require "braintree/configuration.rb"
26
+ require "braintree/credit_card.rb"
27
+ require "braintree/credit_card_verification.rb"
28
+ require "braintree/customer.rb"
29
+ require "braintree/digest.rb"
30
+ require "braintree/error_codes.rb"
31
+ require "braintree/error_result.rb"
32
+ require "braintree/errors.rb"
33
+ require "braintree/http.rb"
34
+ require "braintree/paged_collection.rb"
35
+ require "braintree/ssl_expiration_check.rb"
36
+ require "braintree/subscription"
37
+ require "braintree/successful_result.rb"
38
+ require "braintree/test/credit_card_numbers.rb"
39
+ require "braintree/test/transaction_amounts.rb"
40
+ require "braintree/transaction.rb"
41
+ require "braintree/transaction/address_details.rb"
42
+ require "braintree/transaction/credit_card_details.rb"
43
+ require "braintree/transaction/customer_details.rb"
44
+ require "braintree/transaction/status_details.rb"
45
+ require "braintree/transparent_redirect.rb"
46
+ require "braintree/util.rb"
47
+ require "braintree/validation_error.rb"
48
+ require "braintree/validation_error_collection.rb"
49
+ require "braintree/version.rb"
50
+ require "braintree/xml.rb"
51
+ require "braintree/xml/generator.rb"
52
+ require "braintree/xml/libxml.rb"
53
+ require "braintree/xml/parser.rb"
54
+
66
55
  Braintree::SSLExpirationCheck.check_dates
56
+
@@ -4,10 +4,10 @@ module Braintree
4
4
  # as the shipping address when creating a Transaction.
5
5
  class Address
6
6
  include BaseModule # :nodoc:
7
-
7
+
8
8
  attr_reader :company, :country_name, :created_at, :customer_id, :extended_address, :first_name, :id,
9
9
  :last_name, :locality, :postal_code, :region, :street_address, :updated_at
10
-
10
+
11
11
  def self.create(attributes)
12
12
  Util.verify_keys(_create_signature, attributes)
13
13
  unless attributes[:customer_id]
@@ -25,17 +25,17 @@ module Braintree
25
25
  raise UnexpectedError, "expected :address or :api_error_response"
26
26
  end
27
27
  end
28
-
28
+
29
29
  def self.create!(attributes)
30
30
  return_object_or_raise(:address) { create(attributes) }
31
31
  end
32
-
32
+
33
33
  def self.delete(customer_or_customer_id, address_id)
34
34
  customer_id = _determine_customer_id(customer_or_customer_id)
35
35
  Http.delete("/customers/#{customer_id}/addresses/#{address_id}")
36
36
  SuccessfulResult.new
37
37
  end
38
-
38
+
39
39
  # Finds the address with the given +address_id+ that is associated to the given +customer_or_customer_id+.
40
40
  # If the address cannot be found, a NotFoundError will be raised.
41
41
  def self.find(customer_or_customer_id, address_id)
@@ -45,7 +45,7 @@ module Braintree
45
45
  rescue NotFoundError
46
46
  raise NotFoundError, "address for customer #{customer_id.inspect} with id #{address_id.inspect} not found"
47
47
  end
48
-
48
+
49
49
  def self.update(customer_or_customer_id, address_id, attributes)
50
50
  Util.verify_keys(_update_signature, attributes)
51
51
  customer_id = _determine_customer_id(customer_or_customer_id)
@@ -58,20 +58,20 @@ module Braintree
58
58
  raise UnexpectedError, "expected :address or :api_error_response"
59
59
  end
60
60
  end
61
-
61
+
62
62
  def self.update!(customer_or_customer_id, address_id, attributes)
63
63
  return_object_or_raise(:address) { update(customer_or_customer_id, address_id, attributes) }
64
64
  end
65
-
65
+
66
66
  def initialize(attributes) # :nodoc:
67
67
  set_instance_variables_from_hash(attributes)
68
68
  end
69
-
69
+
70
70
  def ==(other) # :nodoc:
71
71
  return false unless other.is_a?(Address)
72
72
  id == other.id && customer_id == other.customer_id
73
73
  end
74
-
74
+
75
75
  # Deletes the address.
76
76
  def delete
77
77
  Address.delete(customer_id, self.id)
@@ -89,7 +89,7 @@ module Braintree
89
89
  raise UnexpectedError, "expected :address or :api_error_response"
90
90
  end
91
91
  end
92
-
92
+
93
93
  def update!(attributes)
94
94
  return_object_or_raise(:address) { update(attributes) }
95
95
  end
@@ -97,12 +97,12 @@ module Braintree
97
97
  class << self
98
98
  protected :new
99
99
  end
100
-
100
+
101
101
  def self._create_signature # :nodoc:
102
102
  [:company, :country_name, :customer_id, :extended_address, :first_name,
103
103
  :last_name, :locality, :postal_code, :region, :street_address]
104
104
  end
105
-
105
+
106
106
  def self._determine_customer_id(customer_or_customer_id) # :nodoc:
107
107
  customer_id = customer_or_customer_id.is_a?(Customer) ? customer_or_customer_id.id : customer_or_customer_id
108
108
  unless customer_id =~ /\A[\w_-]+\z/
@@ -114,7 +114,7 @@ module Braintree
114
114
  def self._new(*args) # :nodoc:
115
115
  self.new *args
116
116
  end
117
-
117
+
118
118
  def self._update_signature # :nodoc:
119
119
  _create_signature
120
120
  end
@@ -6,7 +6,7 @@ module Braintree
6
6
  if result.success?
7
7
  result.send object_to_return
8
8
  else
9
- raise ValidationsFailed
9
+ raise ValidationsFailed.new(result)
10
10
  end
11
11
  end
12
12
 
@@ -15,12 +15,12 @@ module Braintree
15
15
  instance_variable_set "@#{key}", value
16
16
  end
17
17
  end
18
-
18
+
19
19
  def singleton_class
20
20
  class << self; self; end
21
21
  end
22
22
  end
23
-
23
+
24
24
  def self.included(klass)
25
25
  klass.extend Methods
26
26
  end
@@ -14,7 +14,7 @@ module Braintree
14
14
  attr_accessor :logger
15
15
  attr_writer :merchant_id, :public_key, :private_key
16
16
  end
17
-
17
+
18
18
  def self.expectant_reader(*attributes) # :nodoc:
19
19
  attributes.each do |attribute|
20
20
  (class << self; self; end).send(:define_method, attribute) do
@@ -25,11 +25,11 @@ module Braintree
25
25
  end
26
26
  end
27
27
  expectant_reader :environment, :merchant_id, :public_key, :private_key
28
-
28
+
29
29
  def self.base_merchant_url # :nodoc:
30
30
  "#{protocol}://#{server}:#{port}#{base_merchant_path}"
31
31
  end
32
-
32
+
33
33
  def self.base_merchant_path # :nodoc:
34
34
  "/merchants/#{Braintree::Configuration.merchant_id}"
35
35
  end
@@ -42,19 +42,19 @@ module Braintree
42
42
  File.expand_path(File.join(File.dirname(__FILE__), "..", "ssl", "securetrust_ca.crt"))
43
43
  end
44
44
  end
45
-
46
- # Sets the Braintree environment to use. Valid values are <tt>:sandbox</tt> and <tt>:production</tt>
45
+
46
+ # Sets the Braintree environment to use. Valid values are <tt>:sandbox</tt> and <tt>:production</tt>
47
47
  def self.environment=(env)
48
48
  unless [:development, :qa, :sandbox, :production].include?(env)
49
49
  raise ArgumentError, "#{env.inspect} is not a valid environment"
50
50
  end
51
51
  @environment = env
52
52
  end
53
-
53
+
54
54
  def self.logger # :nodoc:
55
55
  @logger ||= _default_logger
56
56
  end
57
-
57
+
58
58
  def self.port # :nodoc:
59
59
  case environment
60
60
  when :development
@@ -63,11 +63,11 @@ module Braintree
63
63
  443
64
64
  end
65
65
  end
66
-
66
+
67
67
  def self.protocol # :nodoc:
68
68
  ssl? ? "https" : "http"
69
69
  end
70
-
70
+
71
71
  def self.server # :nodoc:
72
72
  case environment
73
73
  when :development
@@ -75,25 +75,25 @@ module Braintree
75
75
  when :production
76
76
  "www.braintreegateway.com"
77
77
  when :qa
78
- "qa.braintreegateway.com"
78
+ "qa-master.braintreegateway.com"
79
79
  when :sandbox
80
80
  "sandbox.braintreegateway.com"
81
- end
82
- end
83
-
81
+ end
82
+ end
83
+
84
84
  def self.ssl? # :nodoc:
85
85
  case environment
86
86
  when :development
87
87
  false
88
88
  when :production, :qa, :sandbox
89
89
  true
90
- end
90
+ end
91
91
  end
92
-
92
+
93
93
  def self._default_logger # :nodoc:
94
94
  logger = Logger.new(STDOUT)
95
95
  logger.level = Logger::INFO
96
96
  logger
97
- end
97
+ end
98
98
  end
99
99
  end
@@ -1,10 +1,10 @@
1
1
  module Braintree
2
2
  class CreditCard
3
3
  include BaseModule # :nodoc:
4
-
4
+
5
5
  attr_reader :billing_address, :bin, :card_type, :cardholder_name, :created_at, :customer_id, :expiration_month,
6
6
  :expiration_year, :last_4, :token, :updated_at
7
-
7
+
8
8
  def self.create(attributes)
9
9
  if attributes.has_key?(:expiration_date) && (attributes.has_key?(:expiration_month) || attributes.has_key?(:expiration_year))
10
10
  raise ArgumentError.new("create with both expiration_month and expiration_year or only expiration_date")
@@ -12,31 +12,31 @@ module Braintree
12
12
  Util.verify_keys(_create_signature, attributes)
13
13
  _do_create("/payment_methods", :credit_card => attributes)
14
14
  end
15
-
15
+
16
16
  def self.create!(attributes)
17
17
  return_object_or_raise(:credit_card) { create(attributes) }
18
18
  end
19
-
19
+
20
20
  # The transparent redirect URL to use to create a credit card.
21
21
  def self.create_credit_card_url
22
22
  "#{Braintree::Configuration.base_merchant_url}/payment_methods/all/create_via_transparent_redirect_request"
23
23
  end
24
-
24
+
25
25
  def self.create_from_transparent_redirect(query_string)
26
26
  params = TransparentRedirect.parse_and_validate_query_string query_string
27
27
  _do_create("/payment_methods/all/confirm_transparent_redirect_request", :id => params[:id])
28
28
  end
29
-
29
+
30
30
  def self.credit(token, transaction_attributes)
31
31
  Transaction.credit(transaction_attributes.merge(
32
32
  :payment_method_token => token
33
33
  ))
34
34
  end
35
-
35
+
36
36
  def self.credit!(token, transaction_attributes)
37
37
  return_object_or_raise(:transaction) { credit(token, transaction_attributes) }
38
38
  end
39
-
39
+
40
40
  # Returns a PagedCollection of expired credit cards.
41
41
  def self.expired(options = {})
42
42
  page_number = options[:page] || 1
@@ -47,7 +47,7 @@ module Braintree
47
47
  end
48
48
  PagedCollection.new(attributes) { |page_number| CreditCard.expired(:page => page_number) }
49
49
  end
50
-
50
+
51
51
  # Returns a PagedCollection of credit cards expiring between +start_date+ and +end_date+ inclusive.
52
52
  # Only the month and year of the start and end dates are used.
53
53
  def self.expiring_between(start_date, end_date, options = {})
@@ -59,7 +59,7 @@ module Braintree
59
59
  end
60
60
  PagedCollection.new(attributes) { |page_number| CreditCard.expiring_between(start_date, end_date, :page => page_number) }
61
61
  end
62
-
62
+
63
63
  # Finds the credit card with the given +token+. Raises a NotFoundError if it cannot be found.
64
64
  def self.find(token)
65
65
  response = Http.get "/payment_methods/#{token}"
@@ -67,17 +67,26 @@ module Braintree
67
67
  rescue NotFoundError
68
68
  raise NotFoundError, "payment method with token #{token.inspect} not found"
69
69
  end
70
-
70
+
71
71
  def self.sale(token, transaction_attributes)
72
72
  Transaction.sale(transaction_attributes.merge(
73
73
  :payment_method_token => token
74
74
  ))
75
75
  end
76
-
76
+
77
77
  def self.sale!(token, transaction_attributes)
78
78
  return_object_or_raise(:transaction) { sale(token, transaction_attributes) }
79
79
  end
80
80
 
81
+ def self.update(token, attributes)
82
+ Util.verify_keys(_update_signature, attributes)
83
+ _do_update(:put, "/payment_methods/#{token}", :credit_card => attributes)
84
+ end
85
+
86
+ def self.update!(token, attributes)
87
+ return_object_or_raise(:credit_card) { update(token, attributes) }
88
+ end
89
+
81
90
  def self.update_from_transparent_redirect(query_string)
82
91
  params = TransparentRedirect.parse_and_validate_query_string query_string
83
92
  _do_update(:post, "/payment_methods/all/confirm_transparent_redirect_request", :id => params[:id])
@@ -87,7 +96,7 @@ module Braintree
87
96
  def self.update_credit_card_url
88
97
  "#{Braintree::Configuration.base_merchant_url}/payment_methods/all/update_via_transparent_redirect_request"
89
98
  end
90
-
99
+
91
100
  def initialize(attributes) # :nodoc:
92
101
  _init attributes
93
102
  end
@@ -98,33 +107,33 @@ module Braintree
98
107
  :payment_method_token => self.token
99
108
  ))
100
109
  end
101
-
110
+
102
111
  def credit!(transaction_attributes)
103
112
  return_object_or_raise(:transaction) { credit(transaction_attributes) }
104
113
  end
105
-
114
+
106
115
  def delete
107
116
  Http.delete("/payment_methods/#{token}")
108
117
  end
109
-
118
+
110
119
  # Returns true if this credit card is the customer's default.
111
120
  def default?
112
121
  @default
113
122
  end
114
-
123
+
115
124
  # Expiration date formatted as MM/YYYY
116
125
  def expiration_date
117
126
  "#{expiration_month}/#{expiration_year}"
118
127
  end
119
-
120
- # Returns true if the credit card is expired.
128
+
129
+ # Returns true if the credit card is expired.
121
130
  def expired?
122
131
  if expiration_year.to_i == Time.now.year
123
132
  return expiration_month.to_i < Time.now.month
124
133
  end
125
- expiration_year.to_i < Time.now.year
134
+ expiration_year.to_i < Time.now.year
126
135
  end
127
-
136
+
128
137
  def inspect # :nodoc:
129
138
  first = [:token]
130
139
  order = first + (self.class._attributes - first)
@@ -133,20 +142,20 @@ module Braintree
133
142
  end
134
143
  "#<#{self.class} #{nice_attributes.join(', ')}>"
135
144
  end
136
-
145
+
137
146
  def masked_number
138
147
  "#{bin}******#{last_4}"
139
148
  end
140
-
141
- # Creates a sale transaction for this credit card.
149
+
150
+ # Creates a sale transaction for this credit card.
142
151
  def sale(transaction_attributes)
143
152
  CreditCard.sale(self.token, transaction_attributes)
144
153
  end
145
-
154
+
146
155
  def sale!(transaction_attributes)
147
156
  return_object_or_raise(:transaction) { sale(transaction_attributes) }
148
157
  end
149
-
158
+
150
159
  def update(attributes)
151
160
  Util.verify_keys(self.class._update_signature, attributes)
152
161
  response = Http.put "/payment_methods/#{token}", :credit_card => attributes
@@ -157,9 +166,9 @@ module Braintree
157
166
  ErrorResult.new(response[:api_error_response])
158
167
  else
159
168
  raise UnexpectedError, "expected :credit_card or :api_error_response"
160
- end
169
+ end
161
170
  end
162
-
171
+
163
172
  def update!(attributes)
164
173
  return_object_or_raise(:credit_card) { update(attributes) }
165
174
  end
@@ -191,7 +200,7 @@ module Braintree
191
200
 
192
201
  def self._new(*args) # :nodoc:
193
202
  self.new *args
194
- end
203
+ end
195
204
 
196
205
  def self._do_create(url, params) # :nodoc:
197
206
  response = Http.post url, params
@@ -228,4 +237,4 @@ module Braintree
228
237
  @billing_address = attributes[:billing_address] ? Address._new(attributes[:billing_address]) : nil
229
238
  end
230
239
  end
231
- end
240
+ end