braintree 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/LICENSE +22 -0
  2. data/README.rdoc +62 -0
  3. data/lib/braintree.rb +66 -0
  4. data/lib/braintree/address.rb +122 -0
  5. data/lib/braintree/base_module.rb +29 -0
  6. data/lib/braintree/configuration.rb +99 -0
  7. data/lib/braintree/credit_card.rb +231 -0
  8. data/lib/braintree/credit_card_verification.rb +31 -0
  9. data/lib/braintree/customer.rb +231 -0
  10. data/lib/braintree/digest.rb +20 -0
  11. data/lib/braintree/error_codes.rb +95 -0
  12. data/lib/braintree/error_result.rb +39 -0
  13. data/lib/braintree/errors.rb +29 -0
  14. data/lib/braintree/http.rb +105 -0
  15. data/lib/braintree/paged_collection.rb +55 -0
  16. data/lib/braintree/ssl_expiration_check.rb +28 -0
  17. data/lib/braintree/successful_result.rb +38 -0
  18. data/lib/braintree/test/credit_card_numbers.rb +50 -0
  19. data/lib/braintree/test/transaction_amounts.rb +10 -0
  20. data/lib/braintree/transaction.rb +360 -0
  21. data/lib/braintree/transaction/address_details.rb +15 -0
  22. data/lib/braintree/transaction/credit_card_details.rb +22 -0
  23. data/lib/braintree/transaction/customer_details.rb +13 -0
  24. data/lib/braintree/transparent_redirect.rb +110 -0
  25. data/lib/braintree/util.rb +94 -0
  26. data/lib/braintree/validation_error.rb +15 -0
  27. data/lib/braintree/validation_error_collection.rb +80 -0
  28. data/lib/braintree/version.rb +9 -0
  29. data/lib/braintree/xml.rb +12 -0
  30. data/lib/braintree/xml/generator.rb +80 -0
  31. data/lib/braintree/xml/libxml.rb +69 -0
  32. data/lib/braintree/xml/parser.rb +93 -0
  33. data/lib/ssl/securetrust_ca.crt +44 -0
  34. data/lib/ssl/valicert_ca.crt +18 -0
  35. data/spec/integration/braintree/address_spec.rb +352 -0
  36. data/spec/integration/braintree/credit_card_spec.rb +676 -0
  37. data/spec/integration/braintree/customer_spec.rb +664 -0
  38. data/spec/integration/braintree/http_spec.rb +201 -0
  39. data/spec/integration/braintree/test/transaction_amounts_spec.rb +29 -0
  40. data/spec/integration/braintree/transaction_spec.rb +900 -0
  41. data/spec/integration/spec_helper.rb +38 -0
  42. data/spec/script/httpsd.rb +27 -0
  43. data/spec/spec_helper.rb +41 -0
  44. data/spec/unit/braintree/address_spec.rb +86 -0
  45. data/spec/unit/braintree/configuration_spec.rb +190 -0
  46. data/spec/unit/braintree/credit_card_spec.rb +137 -0
  47. data/spec/unit/braintree/credit_card_verification_spec.rb +17 -0
  48. data/spec/unit/braintree/customer_spec.rb +103 -0
  49. data/spec/unit/braintree/digest_spec.rb +28 -0
  50. data/spec/unit/braintree/error_result_spec.rb +42 -0
  51. data/spec/unit/braintree/errors_spec.rb +81 -0
  52. data/spec/unit/braintree/http_spec.rb +42 -0
  53. data/spec/unit/braintree/paged_collection_spec.rb +128 -0
  54. data/spec/unit/braintree/ssl_expiration_check_spec.rb +92 -0
  55. data/spec/unit/braintree/successful_result_spec.rb +27 -0
  56. data/spec/unit/braintree/transaction/credit_card_details_spec.rb +22 -0
  57. data/spec/unit/braintree/transaction_spec.rb +136 -0
  58. data/spec/unit/braintree/transparent_redirect_spec.rb +154 -0
  59. data/spec/unit/braintree/util_spec.rb +142 -0
  60. data/spec/unit/braintree/validation_error_collection_spec.rb +128 -0
  61. data/spec/unit/braintree/validation_error_spec.rb +19 -0
  62. data/spec/unit/braintree/xml/libxml_spec.rb +51 -0
  63. data/spec/unit/braintree/xml_spec.rb +122 -0
  64. data/spec/unit/spec_helper.rb +1 -0
  65. metadata +118 -0
@@ -0,0 +1,31 @@
1
+ module Braintree
2
+ class CreditCardVerification
3
+ include BaseModule
4
+
5
+ attr_reader :avs_error_response_code, :avs_postal_code_response_code,
6
+ :avs_street_address_response_code, :cvv_response_code, :status
7
+
8
+ def initialize(attributes) # :nodoc:
9
+ set_instance_variables_from_hash(attributes)
10
+ end
11
+
12
+ def inspect # :nodoc:
13
+ attr_order = [
14
+ :status, :cvv_response_code, :avs_error_response_code,
15
+ :avs_postal_code_response_code, :avs_street_address_response_code
16
+ ]
17
+ formatted_attrs = attr_order.map do |attr|
18
+ "#{attr}: #{send(attr).inspect}"
19
+ end
20
+ "#<#{self.class} #{formatted_attrs.join(", ")}>"
21
+ end
22
+
23
+ class << self
24
+ protected :new
25
+ end
26
+
27
+ def self._new(*args) # :nodoc:
28
+ self.new *args
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,231 @@
1
+ module Braintree
2
+ class Customer
3
+ include BaseModule
4
+
5
+ attr_reader :addresses, :company, :created_at, :credit_cards, :email, :fax, :first_name, :id, :last_name,
6
+ :phone, :updated_at, :website
7
+
8
+ # Returns a PagedCollection of all customers stored in the vault. Due to race conditions, this method
9
+ # may not reliably return all customers stored in the vault.
10
+ #
11
+ # page = Braintree::Customer.all
12
+ # loop do
13
+ # page.each do |customer|
14
+ # puts "Customer #{customer.id} email is #{customer.email}"
15
+ # end
16
+ # break if page.last_page?
17
+ # page = page.next_page
18
+ # end
19
+ def self.all(options = {})
20
+ page_number = options[:page] || 1
21
+ response = Http.get("/customers?page=#{page_number}")
22
+ attributes = response[:customers]
23
+ attributes[:items] = Util.extract_attribute_as_array(attributes, :customer).map do |customer_attributes|
24
+ new customer_attributes
25
+ end
26
+ PagedCollection.new(attributes) { |page_number| Customer.all(:page => page_number) }
27
+ end
28
+
29
+ # Creates a customer using the given +attributes+. If <tt>:id</tt> is not passed,
30
+ # the gateway will generate it.
31
+ #
32
+ # result = Braintree::Customer.create(
33
+ # :first_name => "John",
34
+ # :last_name => "Smith",
35
+ # :company => "Smith Co.",
36
+ # :email => "john@smith.com",
37
+ # :website => "www.smithco.com",
38
+ # :fax => "419-555-1234",
39
+ # :phone => "614-555-1234"
40
+ # )
41
+ # if result.success?
42
+ # puts "Created customer #{result.customer.id}
43
+ # else
44
+ # puts "Could not create customer, see result.errors"
45
+ # end
46
+ def self.create(attributes = {})
47
+ Util.verify_keys(_create_signature, attributes)
48
+ _do_create "/customers", :customer => attributes
49
+ end
50
+
51
+ def self.create!(attributes = {})
52
+ return_object_or_raise(:customer) { create(attributes) }
53
+ end
54
+
55
+ def self.create_customer_url
56
+ "#{Braintree::Configuration.base_merchant_url}/customers/all/create_via_transparent_redirect_request"
57
+ end
58
+
59
+ def self.create_from_transparent_redirect(query_string)
60
+ params = TransparentRedirect.parse_and_validate_query_string query_string
61
+ _do_create("/customers/all/confirm_transparent_redirect_request", :id => params[:id])
62
+ end
63
+
64
+ def self.create_customer_transparent_redirect_url
65
+ "#{Braintree::Configuration.base_merchant_url}/customers"
66
+ end
67
+
68
+ def self.credit(customer_id, transaction_attributes)
69
+ Transaction.credit(transaction_attributes.merge(:customer_id => customer_id))
70
+ end
71
+
72
+ def self.credit!(customer_id, transaction_attributes)
73
+ return_object_or_raise(:transaction){ credit(customer_id, transaction_attributes) }
74
+ end
75
+
76
+ def self.delete(customer_id)
77
+ Http.delete("/customers/#{customer_id}")
78
+ SuccessfulResult.new
79
+ end
80
+
81
+ def self.find(customer_id)
82
+ response = Http.get("/customers/#{customer_id}")
83
+ new(response[:customer])
84
+ rescue NotFoundError
85
+ raise NotFoundError, "customer with id #{customer_id.inspect} not found"
86
+ end
87
+
88
+ def self.sale(customer_id, transaction_attributes)
89
+ Transaction.sale(transaction_attributes.merge(:customer_id => customer_id))
90
+ end
91
+
92
+ def self.sale!(customer_id, transaction_attributes)
93
+ return_object_or_raise(:transaction){ sale(customer_id, transaction_attributes) }
94
+ end
95
+
96
+ def self.update(customer_id, attributes)
97
+ Util.verify_keys(_update_signature, attributes)
98
+ _do_update(:put, "/customers/#{customer_id}", :customer => attributes)
99
+ end
100
+
101
+ def self.update!(customer_id, attributes)
102
+ return_object_or_raise(:customer) { update(customer_id, attributes) }
103
+ end
104
+
105
+ def self.update_customer_url
106
+ "#{Braintree::Configuration.base_merchant_url}/customers/all/update_via_transparent_redirect_request"
107
+ end
108
+
109
+ def self.update_from_transparent_redirect(query_string)
110
+ params = TransparentRedirect.parse_and_validate_query_string(query_string)
111
+ _do_update(:post, "/customers/all/confirm_transparent_redirect_request", :id => params[:id])
112
+ end
113
+
114
+ def initialize(attributes) # :nodoc:
115
+ set_instance_variables_from_hash(attributes)
116
+ @credit_cards = (@credit_cards || []).map { |pm| CreditCard._new pm }
117
+ @addresses = (@addresses || []).map { |addr| Address._new addr }
118
+ end
119
+
120
+ def credit(transaction_attributes)
121
+ Customer.credit(self.id, transaction_attributes)
122
+ end
123
+
124
+ def credit!(transaction_attributes)
125
+ return_object_or_raise(:transaction) { credit(transaction_attributes) }
126
+ end
127
+
128
+ def delete
129
+ Customer.delete(id)
130
+ end
131
+
132
+ def inspect # :nodoc:
133
+ first = [:id]
134
+ last = [:addresses, :credit_cards]
135
+ order = first + (self.class._attributes - first - last) + last
136
+ nice_attributes = order.map do |attr|
137
+ "#{attr}: #{send(attr).inspect}"
138
+ end
139
+ "#<#{self.class} #{nice_attributes.join(', ')}>"
140
+ end
141
+
142
+ def sale(transaction_attributes)
143
+ Customer.sale(self.id, transaction_attributes)
144
+ end
145
+
146
+ def sale!(transaction_attributes)
147
+ return_object_or_raise(:transaction) { sale(transaction_attributes) }
148
+ end
149
+
150
+ # Finds transactions associated to the customer. Returns a PagedCollection.
151
+ def transactions(options = {})
152
+ page_number = options[:page] || 1
153
+ response = Http.get "/customers/#{id}/transactions?page=#{page_number}"
154
+ attributes = response[:credit_card_transactions]
155
+ attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map do |transaction_attributes|
156
+ Transaction._new transaction_attributes
157
+ end
158
+ PagedCollection.new(attributes) { |page_number| self.transactions(:page => page_number) }
159
+ end
160
+
161
+ def update(attributes)
162
+ response = Http.put "/customers/#{id}", :customer => attributes
163
+ if response[:customer]
164
+ set_instance_variables_from_hash response[:customer]
165
+ SuccessfulResult.new(:customer => self)
166
+ elsif response[:api_error_response]
167
+ ErrorResult.new(response[:api_error_response])
168
+ else
169
+ raise "expected :customer or :errors"
170
+ end
171
+ end
172
+
173
+ def update!(attributes)
174
+ return_object_or_raise(:customer) { update(attributes) }
175
+ end
176
+
177
+ def ==(other)
178
+ return false unless other.is_a?(Customer)
179
+ id == other.id
180
+ end
181
+
182
+ class << self
183
+ protected :new
184
+ end
185
+
186
+ def self._attributes # :nodoc:
187
+ [
188
+ :addresses, :company, :credit_cards, :email, :fax, :first_name, :id, :last_name, :phone, :website,
189
+ :created_at, :updated_at
190
+ ]
191
+ end
192
+
193
+ def self._create_signature # :nodoc:
194
+ credit_card_signature = CreditCard._create_signature - [:customer_id]
195
+ [
196
+ :company, :email, :fax, :first_name, :id, :last_name, :phone, :website,
197
+ {:credit_card => credit_card_signature}
198
+ ]
199
+ end
200
+
201
+ def self._do_create(url, params) # :nodoc:
202
+ response = Http.post url, params
203
+ if response[:customer]
204
+ SuccessfulResult.new(:customer => new(response[:customer]))
205
+ elsif response[:api_error_response]
206
+ ErrorResult.new(response[:api_error_response])
207
+ else
208
+ raise "expected :customer or :api_error_response"
209
+ end
210
+ end
211
+
212
+ def self._do_update(http_verb, url, params) # :nodoc:
213
+ response = Http.send http_verb, url, params
214
+ if response[:customer]
215
+ SuccessfulResult.new(:customer => new(response[:customer]))
216
+ elsif response[:api_error_response]
217
+ ErrorResult.new(response[:api_error_response])
218
+ else
219
+ raise UnexpectedError, "expected :customer or :api_error_response"
220
+ end
221
+ end
222
+
223
+ def self._new(*args) # :nodoc:
224
+ self.new *args
225
+ end
226
+
227
+ def self._update_signature # :nodoc:
228
+ [ :company, :email, :fax, :first_name, :id, :last_name, :phone, :website ]
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,20 @@
1
+ module Braintree
2
+ module Digest # :nodoc:
3
+ def self.hexdigest(string)
4
+ _hmac_sha1(Configuration.private_key, string)
5
+ end
6
+
7
+ def self._hmac_sha1(key, message)
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)
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,95 @@
1
+ module Braintree
2
+ # The ErrorCodes module provides constants for validation errors.
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.
5
+ module ErrorCodes
6
+ module Address
7
+ CannotBeBlank = "81801"
8
+ CompanyIsTooLong = "81802"
9
+ CountryNameIsNotAccepted = "91803"
10
+ ExtendedAddressIsTooLong = "81804"
11
+ FirstNameIsTooLong = "81805"
12
+ LastNameIsTooLong = "81806"
13
+ LocalityIsTooLong = "81807"
14
+ PostalCodeIsRequired = "81808"
15
+ PostalCodeIsTooLong = "81809"
16
+ RegionIsTooLong = "81810"
17
+ StreetAddressIsRequired = "81811"
18
+ StreetAddressIsTooLong = "81812"
19
+ end
20
+
21
+ module CreditCard
22
+ BillingAddressConflict = "91701"
23
+ BillingAddressIdIsInvalid = "91702"
24
+ CreditCardTypeIsNotAccepted = "81703"
25
+ CustomerIdIsRequired = "91704"
26
+ CustomerIdIsInvalid = "91705"
27
+ CvvIsRequired = "81706"
28
+ CvvIsInvalid = "81707"
29
+ ExpirationDateConflict = "91708"
30
+ ExpirationDateIsRequired = "81709"
31
+ ExpirationDateIsInvalid = "81710"
32
+ ExpirationDateYearIsInvalid = "81711"
33
+ ExpirationMonthIsInvalid = "81712"
34
+ ExpirationYearIsInvalid = "81713"
35
+ NumberIsRequired = "81714"
36
+ NumberIsInvalid = "81715"
37
+ NumberInvalidLength = "81716"
38
+ NumberMustBeTestNumber = "81717"
39
+ TokenInvalid = "91718"
40
+ TokenIsInUse = "91719"
41
+ TokenIsTooLong = "91720"
42
+ TokenIsNotAllowed = "91721"
43
+ TokenIsRequired = "91722"
44
+ end
45
+
46
+ module Customer
47
+ CompanyisTooLong = "81601"
48
+ CustomFieldIsInvalid = "91602"
49
+ CustomFieldIsTooLong = "81603"
50
+ EmailIsInvalid = "81604"
51
+ EmailIsTooLong = "81605"
52
+ EmailIsRequired = "81606"
53
+ FaxIsTooLong = "81607"
54
+ FirstNameIsTooLong = "81608"
55
+ IdIsInUse = "91609"
56
+ IdIsInvaild = "91610"
57
+ IdIsNotAllowed = "91611"
58
+ IdIsTooLong = "91612"
59
+ LastNameIsTooLong = "81613"
60
+ PhoneIsTooLong = "81614"
61
+ WebsiteIsTooLong = "81615"
62
+ WebsiteIsInvalid = "81616"
63
+ end
64
+
65
+ module Transaction
66
+ AmountCannotBeNegative = "81501"
67
+ AmountIsRequired = "81502"
68
+ AmountIsInvalid = "81503"
69
+ CannotBeVoided = "91504"
70
+ CannotRefundCredit = "91505"
71
+ CannotRefundUnlessSettled = "91506"
72
+ CannotSubmitForSettlement = "91507"
73
+ CreditCardIsRequired = "91508"
74
+ CustomerDefaultPaymentMethodCardTypeIsNotAccepted = "81509"
75
+ CustomerIdIsInvalid = "91510"
76
+ CustomerDoesNotHaveCreditCard = "91511"
77
+ HasAlreadyBeenRefunded = "91512"
78
+ MerchantAccountNameIsInvalid = "91513"
79
+ MerchantAccountIsSuspended = "91514"
80
+ PaymentMethodConflict = "91515"
81
+ PaymentMethodDoesNotBelongToCustomer = "91516"
82
+ PaymentMethodTokenCardTypeIsNotAccepted = "91517"
83
+ PaymentMethodTokenIsInvalid = "91518"
84
+ ProcessorAuthorizationCodeCannotBeSet = "91519"
85
+ ProcessorAuthorizationCodeIsInvalid = "81520"
86
+ RefundAmountIsTooLarge = "91521"
87
+ SettlementAmountIsTooLarge = "91522"
88
+ TypeIsInvalid = "91523"
89
+ TypeIsRequired = "91524"
90
+ module Options
91
+ VaultIsDisabled = "91525"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,39 @@
1
+ module Braintree
2
+ # An ErrorResult will be returned from non-bang methods when
3
+ # validations fail. It will provide access to the params passed
4
+ # to the server. The params are primarily useful for re-populaing
5
+ # web forms when using transparent redirect. ErrorResult also
6
+ # provides access to the validation errors.
7
+ #
8
+ # result = Braintree::Customer.create(:email => "invalid.email.address")
9
+ # if result.success?
10
+ # # have a SuccessfulResult
11
+ # else
12
+ # # have an ErrorResult
13
+ # puts "Validations failed when attempting to create customer."
14
+ # result.errors.for(:customer).each do |error|
15
+ # puts error.message
16
+ # end
17
+ # end
18
+ class ErrorResult
19
+
20
+ attr_reader :credit_card_verification, :errors, :params
21
+
22
+ def initialize(data) # :nodoc:
23
+ @params = data[:params]
24
+ if data[:verification]
25
+ @credit_card_verification = CreditCardVerification._new(data[:verification])
26
+ end
27
+ @errors = Errors.new(data[:errors])
28
+ end
29
+
30
+ def inspect # :nodoc:
31
+ "#<#{self.class} params:{...} errors:<#{@errors._inner_inspect}>>"
32
+ end
33
+
34
+ # Always returns false.
35
+ def success?
36
+ false
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ module Braintree
2
+ # Provides access to errors from an ErrorResult.
3
+ class Errors
4
+ def initialize(data = {}) # :nodoc:
5
+ @errors = ValidationErrorCollection.new(data.merge(:errors => []))
6
+ end
7
+
8
+ # Accesses validation errors for the given +scope+.
9
+ def for(scope)
10
+ @errors.for(scope)
11
+ end
12
+
13
+ def inspect # :nodoc:
14
+ "#<#{self.class} #{_inner_inspect}>"
15
+ end
16
+
17
+ # Returns the total number of validation errors at all levels of nesting. For example,
18
+ # if creating a customer with a credit card and a billing address, and each of the customer,
19
+ # credit card, and billing address has 1 error, this method will return 3.
20
+ def size
21
+ @errors.deep_size
22
+ end
23
+
24
+ def _inner_inspect # :nodoc:
25
+ @errors._inner_inspect
26
+ end
27
+ end
28
+ end
29
+