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