braintree 1.0.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 (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
+