braintree 1.0.0 → 1.0.1

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.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Braintree Payment Solutions
1
+ Copyright (c) 2009-2010 Braintree Payment Solutions
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
@@ -58,7 +58,7 @@ module Braintree
58
58
  def self.port # :nodoc:
59
59
  case environment
60
60
  when :development
61
- 3000
61
+ ENV['GATEWAY_PORT'] || 3000
62
62
  when :production, :qa, :sandbox
63
63
  443
64
64
  end
@@ -3,7 +3,7 @@ module Braintree
3
3
  include BaseModule
4
4
 
5
5
  attr_reader :addresses, :company, :created_at, :credit_cards, :email, :fax, :first_name, :id, :last_name,
6
- :phone, :updated_at, :website
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.
@@ -92,6 +92,17 @@ module Braintree
92
92
  def self.sale!(customer_id, transaction_attributes)
93
93
  return_object_or_raise(:transaction){ sale(customer_id, transaction_attributes) }
94
94
  end
95
+
96
+ # Returns a PagedCollection of transactions for the customer with the given +customer_id+.
97
+ def self.transactions(customer_id, options = {})
98
+ page_number = options[:page] || 1
99
+ response = Http.get "/customers/#{customer_id}/transactions?page=#{page_number}"
100
+ attributes = response[:credit_card_transactions]
101
+ attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map do |transaction_attributes|
102
+ Transaction._new transaction_attributes
103
+ end
104
+ PagedCollection.new(attributes) { |page_number| Customer.transactions(customer_id, :page => page_number) }
105
+ end
95
106
 
96
107
  def self.update(customer_id, attributes)
97
108
  Util.verify_keys(_update_signature, attributes)
@@ -118,7 +129,7 @@ module Braintree
118
129
  end
119
130
 
120
131
  def credit(transaction_attributes)
121
- Customer.credit(self.id, transaction_attributes)
132
+ Customer.credit(id, transaction_attributes)
122
133
  end
123
134
 
124
135
  def credit!(transaction_attributes)
@@ -140,22 +151,16 @@ module Braintree
140
151
  end
141
152
 
142
153
  def sale(transaction_attributes)
143
- Customer.sale(self.id, transaction_attributes)
154
+ Customer.sale(id, transaction_attributes)
144
155
  end
145
156
 
146
157
  def sale!(transaction_attributes)
147
158
  return_object_or_raise(:transaction) { sale(transaction_attributes) }
148
159
  end
149
160
 
150
- # Finds transactions associated to the customer. Returns a PagedCollection.
161
+ # Returns a PagedCollection of transactions for the customer.
151
162
  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) }
163
+ Customer.transactions(id, options)
159
164
  end
160
165
 
161
166
  def update(attributes)
@@ -194,7 +199,8 @@ module Braintree
194
199
  credit_card_signature = CreditCard._create_signature - [:customer_id]
195
200
  [
196
201
  :company, :email, :fax, :first_name, :id, :last_name, :phone, :website,
197
- {:credit_card => credit_card_signature}
202
+ {:credit_card => credit_card_signature},
203
+ {:custom_fields => :_any_key_}
198
204
  ]
199
205
  end
200
206
 
@@ -225,7 +231,7 @@ module Braintree
225
231
  end
226
232
 
227
233
  def self._update_signature # :nodoc:
228
- [ :company, :email, :fax, :first_name, :id, :last_name, :phone, :website ]
234
+ [ :company, :email, :fax, :first_name, :id, :last_name, :phone, :website, {:custom_fields => :_any_key_} ]
229
235
  end
230
236
  end
231
237
  end
@@ -21,6 +21,7 @@ module Braintree
21
21
  module CreditCard
22
22
  BillingAddressConflict = "91701"
23
23
  BillingAddressIdIsInvalid = "91702"
24
+ CardholderNameIsTooLong = "81723"
24
25
  CreditCardTypeIsNotAccepted = "81703"
25
26
  CustomerIdIsRequired = "91704"
26
27
  CustomerIdIsInvalid = "91705"
@@ -77,12 +78,11 @@ module Braintree
77
78
  HasAlreadyBeenRefunded = "91512"
78
79
  MerchantAccountNameIsInvalid = "91513"
79
80
  MerchantAccountIsSuspended = "91514"
81
+ OrderIdIsTooLong = "91501"
80
82
  PaymentMethodConflict = "91515"
81
83
  PaymentMethodDoesNotBelongToCustomer = "91516"
82
84
  PaymentMethodTokenCardTypeIsNotAccepted = "91517"
83
85
  PaymentMethodTokenIsInvalid = "91518"
84
- ProcessorAuthorizationCodeCannotBeSet = "91519"
85
- ProcessorAuthorizationCodeIsInvalid = "81520"
86
86
  RefundAmountIsTooLarge = "91521"
87
87
  SettlementAmountIsTooLarge = "91522"
88
88
  TypeIsInvalid = "91523"
@@ -55,6 +55,9 @@ module Braintree
55
55
  # :region => "IL",
56
56
  # :postal_code => "60103",
57
57
  # :country_name => "United States of America"
58
+ # },
59
+ # :custom_fields => {
60
+ # :birthdate => "11/13/1954"
58
61
  # }
59
62
  # )
60
63
  #
@@ -64,7 +67,7 @@ module Braintree
64
67
  # a transaction can be stored in the vault by setting
65
68
  # <tt>transaction[options][store_in_vault]</tt> to true.
66
69
  #
67
- # transaction = Braintree::Transaction.create!(
70
+ # transaction = Braintree::Transaction.sale!(
68
71
  # :customer => {
69
72
  # :first_name => "Adam",
70
73
  # :last_name => "Williams"
@@ -82,6 +85,17 @@ module Braintree
82
85
  # transaction.credit_card_details.token
83
86
  # # => "6b6m"
84
87
  #
88
+ # To also store the billing address in the vault, pass the
89
+ # +add_billing_address_to_payment_method+ option.
90
+ #
91
+ # Braintree::Transaction.sale!(
92
+ # # ...
93
+ # :options => {
94
+ # :store_in_vault => true
95
+ # :add_billing_address_to_payment_method => true
96
+ # }
97
+ # )
98
+ #
85
99
  # == Submitting for Settlement
86
100
  #
87
101
  # This can only be done when the transction's
@@ -112,8 +126,10 @@ module Braintree
112
126
 
113
127
  attr_reader :avs_error_response_code, :avs_postal_code_response_code, :avs_street_address_response_code
114
128
  attr_reader :amount, :created_at, :credit_card_details, :customer_details, :id, :status
129
+ attr_reader :custom_fields
115
130
  attr_reader :order_id
116
131
  attr_reader :billing_details, :shipping_details
132
+ attr_reader :status_history
117
133
  # The response code from the processor.
118
134
  attr_reader :processor_response_code
119
135
  # Will either be "sale" or "credit"
@@ -345,7 +361,8 @@ module Braintree
345
361
  {:customer => [:id, :company, :email, :fax, :first_name, :last_name, :phone, :website]},
346
362
  {:billing => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
347
363
  {:shipping => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
348
- {:options => [:store_in_vault, :submit_for_settlement]}
364
+ {:options => [:store_in_vault, :submit_for_settlement, :add_billing_address_to_payment_method]},
365
+ {:custom_fields => :_any_key_}
349
366
  ]
350
367
  end
351
368
 
@@ -355,6 +372,7 @@ module Braintree
355
372
  @customer_details = CustomerDetails.new(@customer)
356
373
  @billing_details = AddressDetails.new(@billing)
357
374
  @shipping_details = AddressDetails.new(@shipping)
375
+ @status_history = attributes[:status_history] ? attributes[:status_history].map { |s| StatusDetails.new(s) } : []
358
376
  end
359
- end
377
+ end
360
378
  end
@@ -0,0 +1,13 @@
1
+ module Braintree
2
+ class Transaction
3
+ class StatusDetails # :nodoc:
4
+ include BaseModule
5
+
6
+ attr_reader :amount, :status, :timestamp, :transaction_source, :user
7
+
8
+ def initialize(attributes)
9
+ set_instance_variables_from_hash attributes unless attributes.nil?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -59,7 +59,9 @@ module Braintree
59
59
  end
60
60
 
61
61
  def self.verify_keys(valid_keys, hash)
62
- invalid_keys = _flatten_hash_keys(hash) - _flatten_valid_keys(valid_keys)
62
+ flattened_valid_keys = _flatten_valid_keys(valid_keys)
63
+ invalid_keys = _flatten_hash_keys(hash) - flattened_valid_keys
64
+ invalid_keys = _remove_wildcard_keys(flattened_valid_keys, invalid_keys)
63
65
  if invalid_keys.any?
64
66
  sorted = invalid_keys.sort_by { |k| k.to_s }.join(", ")
65
67
  raise ArgumentError, "invalid keys: #{sorted}"
@@ -71,7 +73,12 @@ module Braintree
71
73
  if key.is_a?(Hash)
72
74
  full_key = key.keys[0]
73
75
  full_key = (namespace ? "#{namespace}[#{full_key}]" : full_key)
74
- result += _flatten_valid_keys(key.values[0], full_key)
76
+ nested_keys = key.values[0]
77
+ if nested_keys.is_a?(Array)
78
+ result += _flatten_valid_keys(nested_keys, full_key)
79
+ else
80
+ result << "#{full_key}[#{nested_keys}]"
81
+ end
75
82
  else
76
83
  result << (namespace ? "#{namespace}[#{key}]" : key.to_s)
77
84
  end
@@ -90,5 +97,16 @@ module Braintree
90
97
  result
91
98
  end.sort
92
99
  end
100
+
101
+ def self._remove_wildcard_keys(valid_keys, invalid_keys)
102
+ wildcard_keys = valid_keys.select { |k| k.include? "[_any_key_]" }
103
+ return invalid_keys if wildcard_keys.empty?
104
+ wildcard_keys.map! { |wk| wk.sub "[_any_key_]", "" }
105
+ invalid_keys.select do |invalid_key|
106
+ wildcard_keys.all? do |wildcard_key|
107
+ invalid_key.index(wildcard_key) != 0
108
+ end
109
+ end
110
+ end
93
111
  end
94
112
  end
@@ -2,8 +2,8 @@ module Braintree
2
2
  module Version
3
3
  Major = 1
4
4
  Minor = 0
5
- Tiny = 0
5
+ Tiny = 1
6
6
 
7
7
  String = "#{Major}.#{Minor}.#{Tiny}"
8
8
  end
9
- end
9
+ end
@@ -0,0 +1,18 @@
1
+ require 'timeout'
2
+ require 'socket'
3
+
4
+ TCPSocket.class_eval do
5
+ def self.wait_for_service(options)
6
+ Timeout::timeout(options[:timeout] || 20) do
7
+ loop do
8
+ begin
9
+ socket = TCPSocket.new(options[:host], options[:port])
10
+ socket.close
11
+ return
12
+ rescue Errno::ECONNREFUSED
13
+ sleep 0.5
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -644,7 +644,7 @@ describe Braintree::CreditCard do
644
644
 
645
645
  def create_credit_card_via_tr(regular_params, tr_params = {})
646
646
  response = nil
647
- Net::HTTP.start("localhost", 3000) do |http|
647
+ Net::HTTP.start("localhost", Braintree::Configuration.port) do |http|
648
648
  request = Net::HTTP::Post.new("/" + Braintree::CreditCard.create_credit_card_url.split("/", 4)[3])
649
649
  request.add_field "Content-Type", "application/x-www-form-urlencoded"
650
650
  tr_data = Braintree::TransparentRedirect.create_credit_card_data({:redirect_url => "http://example.com"}.merge(tr_params))
@@ -660,7 +660,7 @@ describe Braintree::CreditCard do
660
660
 
661
661
  def update_credit_card_via_tr(regular_params, tr_params = {})
662
662
  response = nil
663
- Net::HTTP.start("localhost", 3000) do |http|
663
+ Net::HTTP.start("localhost", Braintree::Configuration.port) do |http|
664
664
  request = Net::HTTP::Post.new("/" + Braintree::CreditCard.update_credit_card_url.split("/", 4)[3])
665
665
  request.add_field "Content-Type", "application/x-www-form-urlencoded"
666
666
  tr_data = Braintree::TransparentRedirect.update_credit_card_data({:redirect_url => "http://example.com"}.merge(tr_params))
@@ -134,6 +134,18 @@ describe Braintree::Customer do
134
134
  result.customer.addresses[0].country_name.should == "United States of America"
135
135
  end
136
136
 
137
+ it "stores custom fields when valid" do
138
+ result = Braintree::Customer.create(
139
+ :first_name => "Bill",
140
+ :last_name => "Gates",
141
+ :custom_fields => {
142
+ :store_me => "custom value"
143
+ }
144
+ )
145
+ result.success?.should == true
146
+ result.customer.custom_fields[:store_me].should == "custom value"
147
+ end
148
+
137
149
  it "returns nested errors if credit card and/or billing address are invalid" do
138
150
  result = Braintree::Customer.create(
139
151
  :email => "invalid",
@@ -149,6 +161,18 @@ describe Braintree::Customer do
149
161
  result.errors.for(:customer).for(:credit_card).on(:number)[0].message.should == "Credit card number is invalid."
150
162
  result.errors.for(:customer).for(:credit_card).for(:billing_address).on(:country_name)[0].message.should == "Country name is not an accepted country."
151
163
  end
164
+
165
+ it "returns errors if custom_fields are not registered" do
166
+ result = Braintree::Customer.create(
167
+ :first_name => "Jack",
168
+ :last_name => "Kennedy",
169
+ :custom_fields => {
170
+ :spouse_name => "Jacqueline"
171
+ }
172
+ )
173
+ result.success?.should == false
174
+ result.errors.for(:customer).on(:custom_fields)[0].message.should == "Custom field is invalid: spouse_name."
175
+ end
152
176
  end
153
177
 
154
178
  describe "self.create!" do
@@ -252,6 +276,22 @@ describe Braintree::Customer do
252
276
  end
253
277
  end
254
278
 
279
+ describe "self.transactions" do
280
+ it "finds transactions for the given customer id" do
281
+ customer = Braintree::Customer.create!(
282
+ :credit_card => {
283
+ :number => Braintree::Test::CreditCardNumbers::Visa,
284
+ :expiration_date => "05/2010"
285
+ }
286
+ )
287
+ transaction = customer.sale!(:amount => "100.00")
288
+ collection = Braintree::Customer.transactions(customer.id)
289
+ collection.current_page_number.should == 1
290
+ collection.total_items.should == 1
291
+ collection[0].should == transaction
292
+ end
293
+ end
294
+
255
295
 
256
296
  describe "sale" do
257
297
  it "creates a sale transaction using the customer, returning a result object" do
@@ -453,14 +493,18 @@ describe Braintree::Customer do
453
493
  result = Braintree::Customer.update(
454
494
  customer.id,
455
495
  :first_name => "Mr. Joe",
456
- :last_name => "Super Cool"
496
+ :last_name => "Super Cool",
497
+ :custom_fields => {
498
+ :store_me => "a value"
499
+ }
457
500
  )
458
501
  result.success?.should == true
459
502
  result.customer.id.should == customer.id
460
503
  result.customer.first_name.should == "Mr. Joe"
461
504
  result.customer.last_name.should == "Super Cool"
505
+ result.customer.custom_fields[:store_me].should == "a value"
462
506
  end
463
-
507
+
464
508
  it "returns an error response if invalid" do
465
509
  customer = Braintree::Customer.create!(:email => "valid@email.com")
466
510
  result = Braintree::Customer.update(
@@ -0,0 +1,16 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Braintree::ErrorCodes do
4
+ describe Braintree::ErrorCodes::CreditCard do
5
+ it "returns CardholderNameIsTooLong when cardholder name is too long" do
6
+ result = Braintree::Customer.create(
7
+ :credit_card => {
8
+ :cardholder_name => "x" * 256
9
+ }
10
+ )
11
+ result.success?.should == false
12
+ result.errors.for(:customer).for(:credit_card).map { |e| e.code }.should \
13
+ include(Braintree::ErrorCodes::CreditCard::CardholderNameIsTooLong)
14
+ end
15
+ end
16
+ end
@@ -20,7 +20,39 @@ describe Braintree::Transaction do
20
20
  result.transaction.credit_card_details.expiration_date.should == "05/2009"
21
21
  end
22
22
 
23
- it "returns an error result if validations fail" do
23
+ it "can create custom fields" do
24
+ result = Braintree::Transaction.create(
25
+ :type => "sale",
26
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
27
+ :credit_card => {
28
+ :number => Braintree::Test::CreditCardNumbers::Visa,
29
+ :expiration_date => "05/2009"
30
+ },
31
+ :custom_fields => {
32
+ :store_me => "custom value"
33
+ }
34
+ )
35
+ result.success?.should == true
36
+ result.transaction.custom_fields.should == {:store_me => "custom value"}
37
+ end
38
+
39
+ it "returns an error if custom_field is not registered" do
40
+ result = Braintree::Transaction.create(
41
+ :type => "sale",
42
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
43
+ :credit_card => {
44
+ :number => Braintree::Test::CreditCardNumbers::Visa,
45
+ :expiration_date => "05/2009"
46
+ },
47
+ :custom_fields => {
48
+ :invalid_key => "custom value"
49
+ }
50
+ )
51
+ result.success?.should == false
52
+ result.errors.for(:transaction).on(:custom_fields)[0].message.should == "Custom field is invalid: invalid_key."
53
+ end
54
+
55
+ it "returns the given params if validations fail" do
24
56
  params = {
25
57
  :transaction => {
26
58
  :type => "sale",
@@ -34,7 +66,94 @@ describe Braintree::Transaction do
34
66
  result = Braintree::Transaction.create(params[:transaction])
35
67
  result.success?.should == false
36
68
  result.params.should == {:transaction => {:type => 'sale', :amount => nil, :credit_card => {:expiration_date => "05/2009"}}}
37
- result.errors.for(:transaction).on(:amount)[0].message.should == "Amount is required."
69
+ end
70
+
71
+ it "returns errors if validations fail (tests many errors at once for spec speed)" do
72
+ params = {
73
+ :transaction => {
74
+ :type => "pants",
75
+ :amount => nil,
76
+ :credit_card => {
77
+ :number => Braintree::Test::CreditCardNumbers::Visa,
78
+ :expiration_date => "05/2009"
79
+ },
80
+ :customer_id => "invalid",
81
+ :order_id => "too long" * 250,
82
+ :payment_method_token => "too long and doesn't belong to customer" * 250
83
+ }
84
+ }
85
+ result = Braintree::Transaction.create(params[:transaction])
86
+ result.success?.should == false
87
+ result.errors.for(:transaction).on(:base).map{|error| error.code}.should include(Braintree::ErrorCodes::Transaction::PaymentMethodConflict)
88
+ result.errors.for(:transaction).on(:base).map{|error| error.code}.should include(Braintree::ErrorCodes::Transaction::PaymentMethodDoesNotBelongToCustomer)
89
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountIsRequired
90
+ result.errors.for(:transaction).on(:customer_id)[0].code.should == Braintree::ErrorCodes::Transaction::CustomerIdIsInvalid
91
+ result.errors.for(:transaction).on(:order_id)[0].code.should == Braintree::ErrorCodes::Transaction::OrderIdIsTooLong
92
+ result.errors.for(:transaction).on(:payment_method_token)[0].code.should == Braintree::ErrorCodes::Transaction::PaymentMethodTokenIsInvalid
93
+ result.errors.for(:transaction).on(:type)[0].code.should == Braintree::ErrorCodes::Transaction::TypeIsInvalid
94
+
95
+ end
96
+
97
+ it "returns an error if amount is negative" do
98
+ params = {
99
+ :transaction => {
100
+ :type => "credit",
101
+ :amount => "-1"
102
+ }
103
+ }
104
+ result = Braintree::Transaction.create(params[:transaction])
105
+ result.success?.should == false
106
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountCannotBeNegative
107
+ end
108
+
109
+ it "returns an error if amount is invalid format" do
110
+ params = {
111
+ :transaction => {
112
+ :type => "sale",
113
+ :amount => "shorts"
114
+ }
115
+ }
116
+ result = Braintree::Transaction.create(params[:transaction])
117
+ result.success?.should == false
118
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountIsInvalid
119
+ end
120
+
121
+ it "returns an error if type is not given" do
122
+ params = {
123
+ :transaction => {
124
+ :type => nil
125
+ }
126
+ }
127
+ result = Braintree::Transaction.create(params[:transaction])
128
+ result.success?.should == false
129
+ result.errors.for(:transaction).on(:type)[0].code.should == Braintree::ErrorCodes::Transaction::TypeIsRequired
130
+ end
131
+
132
+ it "returns an error if no credit card is given" do
133
+ params = {
134
+ :transaction => {
135
+ }
136
+ }
137
+ result = Braintree::Transaction.create(params[:transaction])
138
+ result.success?.should == false
139
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CreditCardIsRequired
140
+ end
141
+
142
+ it "returns an error if the given payment method token doesn't belong to the customer" do
143
+ customer = Braintree::Customer.create!(
144
+ :credit_card => {
145
+ :number => Braintree::Test::CreditCardNumbers::Visa,
146
+ :expiration_date => "05/2010"
147
+ }
148
+ )
149
+ result = Braintree::Transaction.create(
150
+ :type => "sale",
151
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
152
+ :customer_id => customer.id,
153
+ :payment_method_token => customer.credit_cards[0].token + "x"
154
+ )
155
+ result.success?.should == false
156
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::PaymentMethodDoesNotBelongToCustomer
38
157
  end
39
158
  end
40
159
 
@@ -196,6 +315,49 @@ describe Braintree::Transaction do
196
315
  transaction.vault_credit_card.token.should == transaction.credit_card_details.token
197
316
  end
198
317
 
318
+ it "associates a billing address with a credit card in the vault" do
319
+ result = Braintree::Transaction.sale(
320
+ :amount => "100",
321
+ :customer => {
322
+ :first_name => "Adam",
323
+ :last_name => "Williams"
324
+ },
325
+ :credit_card => {
326
+ :number => "5105105105105100",
327
+ :expiration_date => "05/2012"
328
+ },
329
+ :billing => {
330
+ :first_name => "Carl",
331
+ :last_name => "Jones",
332
+ :company => "Braintree",
333
+ :street_address => "123 E Main St",
334
+ :extended_address => "Suite 403",
335
+ :locality => "Chicago",
336
+ :region => "IL",
337
+ :postal_code => "60622",
338
+ :country_name => "United States of America"
339
+ },
340
+ :options => {
341
+ :store_in_vault => true,
342
+ :add_billing_address_to_payment_method => true,
343
+ }
344
+ )
345
+ result.success?.should == true
346
+ transaction = result.transaction
347
+ transaction.customer_details.id.should =~ /\A\d{6,7}\z/
348
+ transaction.vault_customer.id.should == transaction.customer_details.id
349
+ credit_card = Braintree::CreditCard.find(transaction.vault_credit_card.token)
350
+ credit_card.billing_address.first_name.should == "Carl"
351
+ credit_card.billing_address.last_name.should == "Jones"
352
+ credit_card.billing_address.company.should == "Braintree"
353
+ credit_card.billing_address.street_address.should == "123 E Main St"
354
+ credit_card.billing_address.extended_address.should == "Suite 403"
355
+ credit_card.billing_address.locality.should == "Chicago"
356
+ credit_card.billing_address.region.should == "IL"
357
+ credit_card.billing_address.postal_code.should == "60622"
358
+ credit_card.billing_address.country_name.should == "United States of America"
359
+ end
360
+
199
361
  it "submits for settlement if given transaction[options][submit_for_settlement]" do
200
362
  result = Braintree::Transaction.sale(
201
363
  :amount => "100",
@@ -251,7 +413,7 @@ describe Braintree::Transaction do
251
413
  result = Braintree::Transaction.sale(params[:transaction])
252
414
  result.success?.should == false
253
415
  result.params.should == {:transaction => {:type => 'sale', :amount => nil, :credit_card => {:expiration_date => "05/2009"}}}
254
- result.errors.for(:transaction).on(:amount)[0].message.should == "Amount is required."
416
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountIsRequired
255
417
  end
256
418
  end
257
419
 
@@ -314,7 +476,7 @@ describe Braintree::Transaction do
314
476
  result.transaction.updated_at.between?(Time.now - 5, Time.now).should == true
315
477
  end
316
478
 
317
- it "returns an error result if unsuccessful" do
479
+ it "returns an error result if settlement is too large" do
318
480
  transaction = Braintree::Transaction.sale!(
319
481
  :amount => Braintree::Test::TransactionAmounts::Authorize,
320
482
  :credit_card => {
@@ -325,9 +487,22 @@ describe Braintree::Transaction do
325
487
  transaction.amount.should == "1000.00"
326
488
  result = Braintree::Transaction.submit_for_settlement(transaction.id, "1000.01")
327
489
  result.success?.should == false
328
- result.errors.for(:transaction).on(:amount)[0].message.should == "Settlement amount cannot be more than the authorized amount."
490
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::SettlementAmountIsTooLarge
329
491
  result.params[:transaction][:amount].should == "1000.01"
330
492
  end
493
+
494
+ it "returns an error result if status is not authorized" do
495
+ transaction = Braintree::Transaction.sale!(
496
+ :amount => Braintree::Test::TransactionAmounts::Decline,
497
+ :credit_card => {
498
+ :number => Braintree::Test::CreditCardNumbers::Visa,
499
+ :expiration_date => "06/2009"
500
+ }
501
+ )
502
+ result = Braintree::Transaction.submit_for_settlement(transaction.id)
503
+ result.success?.should == false
504
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotSubmitForSettlement
505
+ end
331
506
  end
332
507
 
333
508
  describe "self.submit_for_settlement!" do
@@ -390,7 +565,7 @@ describe Braintree::Transaction do
390
565
  result = Braintree::Transaction.credit(params[:transaction])
391
566
  result.success?.should == false
392
567
  result.params.should == {:transaction => {:type => 'credit', :amount => nil, :credit_card => {:expiration_date => "05/2009"}}}
393
- result.errors.for(:transaction).on(:amount)[0].message.should == "Amount is required."
568
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountIsRequired
394
569
  end
395
570
  end
396
571
 
@@ -450,7 +625,99 @@ describe Braintree::Transaction do
450
625
  transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Visa[-4..-1]
451
626
  transaction.credit_card_details.expiration_date.should == "05/2009"
452
627
  end
453
-
628
+
629
+ it "can put any param in tr_data" do
630
+ params = {
631
+
632
+ }
633
+ tr_data_params = {
634
+ :transaction => {
635
+ :amount => "100.00",
636
+ :order_id => "123",
637
+ :type => "sale",
638
+ :credit_card => {
639
+ :number => "5105105105105100",
640
+ :expiration_date => "05/2011",
641
+ :cvv => "123"
642
+ },
643
+ :customer => {
644
+ :first_name => "Dan",
645
+ :last_name => "Smith",
646
+ :company => "Braintree Payment Solutions",
647
+ :email => "dan@example.com",
648
+ :phone => "419-555-1234",
649
+ :fax => "419-555-1235",
650
+ :website => "http://braintreepaymentsolutions.com"
651
+ },
652
+ :billing => {
653
+ :first_name => "Carl",
654
+ :last_name => "Jones",
655
+ :company => "Braintree",
656
+ :street_address => "123 E Main St",
657
+ :extended_address => "Suite 403",
658
+ :locality => "Chicago",
659
+ :region => "IL",
660
+ :postal_code => "60622",
661
+ :country_name => "United States of America"
662
+ },
663
+ :shipping => {
664
+ :first_name => "Andrew",
665
+ :last_name => "Mason",
666
+ :company => "Braintree",
667
+ :street_address => "456 W Main St",
668
+ :extended_address => "Apt 2F",
669
+ :locality => "Bartlett",
670
+ :region => "IL",
671
+ :postal_code => "60103",
672
+ :country_name => "United States of America"
673
+ }
674
+ }
675
+ }
676
+ query_string_response = create_transaction_via_tr(params, tr_data_params)
677
+ result = Braintree::Transaction.create_from_transparent_redirect(query_string_response)
678
+ transaction = result.transaction
679
+ transaction.id.should =~ /\A\w{6}\z/
680
+ transaction.type.should == "sale"
681
+ transaction.status.should == "authorized"
682
+ transaction.amount.should == "100.00"
683
+ transaction.order_id.should == "123"
684
+ transaction.processor_response_code.should == "1000"
685
+ transaction.created_at.between?(Time.now - 5, Time.now).should == true
686
+ transaction.updated_at.between?(Time.now - 5, Time.now).should == true
687
+ transaction.credit_card_details.bin.should == "510510"
688
+ transaction.credit_card_details.last_4.should == "5100"
689
+ transaction.credit_card_details.masked_number.should == "510510******5100"
690
+ transaction.credit_card_details.card_type.should == "MasterCard"
691
+ transaction.avs_error_response_code.should == nil
692
+ transaction.avs_postal_code_response_code.should == "M"
693
+ transaction.avs_street_address_response_code.should == "M"
694
+ transaction.customer_details.first_name.should == "Dan"
695
+ transaction.customer_details.last_name.should == "Smith"
696
+ transaction.customer_details.company.should == "Braintree Payment Solutions"
697
+ transaction.customer_details.email.should == "dan@example.com"
698
+ transaction.customer_details.phone.should == "419-555-1234"
699
+ transaction.customer_details.fax.should == "419-555-1235"
700
+ transaction.customer_details.website.should == "http://braintreepaymentsolutions.com"
701
+ transaction.billing_details.first_name.should == "Carl"
702
+ transaction.billing_details.last_name.should == "Jones"
703
+ transaction.billing_details.company.should == "Braintree"
704
+ transaction.billing_details.street_address.should == "123 E Main St"
705
+ transaction.billing_details.extended_address.should == "Suite 403"
706
+ transaction.billing_details.locality.should == "Chicago"
707
+ transaction.billing_details.region.should == "IL"
708
+ transaction.billing_details.postal_code.should == "60622"
709
+ transaction.billing_details.country_name.should == "United States of America"
710
+ transaction.shipping_details.first_name.should == "Andrew"
711
+ transaction.shipping_details.last_name.should == "Mason"
712
+ transaction.shipping_details.company.should == "Braintree"
713
+ transaction.shipping_details.street_address.should == "456 W Main St"
714
+ transaction.shipping_details.extended_address.should == "Apt 2F"
715
+ transaction.shipping_details.locality.should == "Bartlett"
716
+ transaction.shipping_details.region.should == "IL"
717
+ transaction.shipping_details.postal_code.should == "60103"
718
+ transaction.shipping_details.country_name.should == "United States of America"
719
+ end
720
+
454
721
  it "returns an error result if validations fail" do
455
722
  params = {
456
723
  :transaction => {
@@ -470,7 +737,7 @@ describe Braintree::Transaction do
470
737
  result = Braintree::Transaction.create_from_transparent_redirect(query_string_response)
471
738
  result.success?.should == false
472
739
  result.params[:transaction].should == {:amount => "", :type => "sale", :credit_card => {:expiration_date => "05/2009"}}
473
- result.errors.for(:transaction).on(:amount)[0].message.should == "Amount is required."
740
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountIsRequired
474
741
  end
475
742
  end
476
743
 
@@ -522,7 +789,7 @@ describe Braintree::Transaction do
522
789
  )
523
790
  result = Braintree::Transaction.void(transaction.id)
524
791
  result.success?.should == false
525
- result.errors.for(:transaction).on(:base)[0].message.should == "Transaction can only be voided if status is authorized or submitted_for_settlement."
792
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotBeVoided
526
793
  end
527
794
  end
528
795
 
@@ -562,7 +829,16 @@ describe Braintree::Transaction do
562
829
  result.new_transaction.type.should == "credit"
563
830
  end
564
831
 
565
- it "returns an error result if unsuccessful" do
832
+ it "returns an error if already refunded" do
833
+ transaction = find_transaction_to_refund
834
+ result = transaction.refund
835
+ result.success?.should == true
836
+ result = transaction.refund
837
+ result.success?.should == false
838
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::HasAlreadyBeenRefunded
839
+ end
840
+
841
+ it "returns an error result if unsettled" do
566
842
  transaction = Braintree::Transaction.create!(
567
843
  :type => "sale",
568
844
  :amount => Braintree::Test::TransactionAmounts::Authorize,
@@ -573,7 +849,7 @@ describe Braintree::Transaction do
573
849
  )
574
850
  result = transaction.refund
575
851
  result.success?.should == false
576
- result.errors.for(:transaction).on(:base)[0].message.should == "Cannot refund a transaction unless it is settled."
852
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotRefundUnlessSettled
577
853
  end
578
854
  end
579
855
 
@@ -631,7 +907,7 @@ describe Braintree::Transaction do
631
907
  transaction.amount.should == "1000.00"
632
908
  result = transaction.submit_for_settlement("1000.01")
633
909
  result.success?.should == false
634
- result.errors.for(:transaction).on(:amount)[0].message.should == "Settlement amount cannot be more than the authorized amount."
910
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::SettlementAmountIsTooLarge
635
911
  result.params[:transaction][:amount].should == "1000.01"
636
912
  end
637
913
  end
@@ -762,6 +1038,22 @@ describe Braintree::Transaction do
762
1038
  end
763
1039
  end
764
1040
  end
1041
+
1042
+ describe "status_history" do
1043
+ it "returns an array of StatusDetail" do
1044
+ transaction = Braintree::Transaction.sale!(
1045
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
1046
+ :credit_card => {
1047
+ :number => Braintree::Test::CreditCardNumbers::Visa,
1048
+ :expiration_date => "05/2009"
1049
+ }
1050
+ )
1051
+ transaction.submit_for_settlement!
1052
+ transaction.status_history.size.should == 2
1053
+ transaction.status_history[0].status.should == "authorized"
1054
+ transaction.status_history[1].status.should == "submitted_for_settlement"
1055
+ end
1056
+ end
765
1057
 
766
1058
  describe "vault_credit_card" do
767
1059
  it "returns the Braintree::CreditCard if the transaction credit card is stored in the vault" do
@@ -843,7 +1135,7 @@ describe Braintree::Transaction do
843
1135
  transaction.status.should == "processor_declined"
844
1136
  result = transaction.void
845
1137
  result.success?.should == false
846
- result.errors.for(:transaction).on(:base)[0].message.should == "Transaction can only be voided if status is authorized or submitted_for_settlement."
1138
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotBeVoided
847
1139
  end
848
1140
  end
849
1141
 
@@ -1,38 +1,44 @@
1
- require File.dirname(__FILE__) + "/../spec_helper"
1
+ unless defined?(INTEGRATION_SPEC_HELPER_LOADED)
2
+ INTEGRATION_SPEC_HELPER_LOADED = true
2
3
 
3
- require 'timeout'
4
- require 'socket'
4
+ require File.dirname(__FILE__) + "/../spec_helper"
5
+ require File.dirname(__FILE__) + "/../hacks/tcp_socket"
5
6
 
7
+ Spec::Runner.configure do |config|
8
+ CLIENT_LIB_ROOT = File.expand_path(File.dirname(__FILE__) + "/../..")
9
+ GATEWAY_ROOT = File.expand_path("#{CLIENT_LIB_ROOT}/../gateway")
10
+ GATEWAY_SERVER_PORT = 3000
11
+ GATEWAY_PID_FILE = "/tmp/gateway_server_#{Braintree::Configuration.port}.pid"
12
+ SPHINX_PID_FILE = "#{GATEWAY_ROOT}/log/searchd.integration.pid"
13
+
14
+ gateway_already_started = File.exist?(GATEWAY_PID_FILE)
15
+ sphinx_already_started = File.exist?(SPHINX_PID_FILE)
16
+ config.before(:suite) do
17
+ Dir.chdir(CLIENT_LIB_ROOT) do
18
+ system "rake start_gateway" or raise "rake start_gateway failed" unless gateway_already_started
19
+ system "rake start_sphinx" or raise "rake start_sphinx failed" unless sphinx_already_started
20
+ end
21
+ end
6
22
 
7
- def start_ssl_server
8
- web_server_pid_file = File.expand_path(File.join(File.dirname(__FILE__), "..", "httpsd.pid"))
9
-
10
- TCPSocket.class_eval do
11
- def self.wait_for_service(options)
12
- Timeout::timeout(options[:timeout] || 20) do
13
- loop do
14
- begin
15
- socket = TCPSocket.new(options[:host], options[:port])
16
- socket.close
17
- return
18
- rescue Errno::ECONNREFUSED
19
- sleep 0.5
20
- end
21
- end
23
+ config.after(:suite) do
24
+ Dir.chdir(CLIENT_LIB_ROOT) do
25
+ system "rake stop_gateway" or raise "rake stop_gateway failed" unless gateway_already_started
26
+ system "rake stop_sphinx" or raise "rake stop_sphinx failed" unless sphinx_already_started
22
27
  end
23
28
  end
24
29
  end
25
30
 
26
- FileUtils.rm(web_server_pid_file) if File.exist?(web_server_pid_file)
27
- command = File.expand_path(File.join(File.dirname(__FILE__), "..", "script", "httpsd.rb"))
28
- #puts command
29
- `#{command} #{web_server_pid_file}`
30
- #puts "== waiting for web server - port: #{8433}"
31
- TCPSocket.wait_for_service :host => "127.0.0.1", :port => 8443
31
+ def start_ssl_server
32
+ web_server_pid_file = File.expand_path(File.join(File.dirname(__FILE__), "..", "httpsd.pid"))
32
33
 
33
- yield
34
+ FileUtils.rm(web_server_pid_file) if File.exist?(web_server_pid_file)
35
+ command = File.expand_path(File.join(File.dirname(__FILE__), "..", "script", "httpsd.rb"))
36
+ `#{command} #{web_server_pid_file}`
37
+ TCPSocket.wait_for_service :host => "127.0.0.1", :port => 8443
34
38
 
35
- 10.times { unless File.exists?(web_server_pid_file); sleep 1; end }
36
- #puts "\n== killing web server - pid: #{File.read(web_server_pid_file).to_i}"
37
- Process.kill "INT", File.read(web_server_pid_file).to_i
39
+ yield
40
+
41
+ 10.times { unless File.exists?(web_server_pid_file); sleep 1; end }
42
+ Process.kill "INT", File.read(web_server_pid_file).to_i
43
+ end
38
44
  end
data/spec/spec_helper.rb CHANGED
@@ -16,9 +16,6 @@ unless defined?(SPEC_HELPER_LOADED)
16
16
  Braintree::Configuration.logger = Logger.new("/dev/null")
17
17
  Braintree::Configuration.logger.level = Logger::INFO
18
18
 
19
- Spec::Runner.configure do |config|
20
- end
21
-
22
19
  module SpecHelper
23
20
  def self.stub_time_dot_now(desired_time)
24
21
  Time.class_eval do
@@ -76,6 +76,24 @@ describe Braintree::Transaction do
76
76
  transaction.credit_card_details.issuer_location.should == "US"
77
77
  end
78
78
 
79
+ it "sets up history attributes in status_history" do
80
+ time = Time.utc(2010,1,14)
81
+ transaction = Braintree::Transaction._new(
82
+ :status_history => [
83
+ { :timestamp => time, :amount => "12.00", :transaction_source => "API",
84
+ :user => "larry", :status => "authorized" },
85
+ { :timestamp => Time.utc(2010,1,15), :amount => "12.00", :transaction_source => "API",
86
+ :user => "curly", :status => "scheduled_for_settlement"}
87
+ ])
88
+ transaction.status_history.size.should == 2
89
+ transaction.status_history[0].user.should == "larry"
90
+ transaction.status_history[0].amount.should == "12.00"
91
+ transaction.status_history[0].status.should == "authorized"
92
+ transaction.status_history[0].transaction_source.should == "API"
93
+ transaction.status_history[0].timestamp.should == time
94
+ transaction.status_history[1].user.should == "curly"
95
+ end
96
+
79
97
  it "handles receiving custom as an empty string" do
80
98
  transaction = Braintree::Transaction._new(
81
99
  :custom => "\n "
@@ -28,7 +28,33 @@ describe Braintree::Util do
28
28
  )
29
29
  end.to raise_error(ArgumentError, "invalid keys: nested[nested_invalid], top_level_invalid")
30
30
  end
31
-
31
+
32
+ it "does not raise an exception for wildcards" do
33
+ expect do
34
+ Braintree::Util.verify_keys(
35
+ [:allowed, {:custom_fields => :_any_key_}],
36
+ :allowed => "ok",
37
+ :custom_fields => {
38
+ :custom_allowed => "ok",
39
+ :custom_allowed2 => "also ok",
40
+ }
41
+ )
42
+ end.to_not raise_error
43
+ end
44
+
45
+ it "raise an exception for wildcards at different nesting" do
46
+ expect do
47
+ Braintree::Util.verify_keys(
48
+ [:allowed, {:custom_fields => :_any_key_}],
49
+ :allowed => {
50
+ :custom_fields => {
51
+ :bad_nesting => "very bad"
52
+ }
53
+ }
54
+ )
55
+ end.to raise_error(ArgumentError, "invalid keys: allowed[custom_fields][bad_nesting]")
56
+ end
57
+
32
58
  it "raises an exception if a deeply nested hash contains an invalid key" do
33
59
  expect do
34
60
  Braintree::Util.verify_keys(
@@ -65,6 +91,12 @@ describe Braintree::Util do
65
91
  [:top_level, {:nested => [:nested_allowed, :nested_allowed2]}]
66
92
  ).should == ["nested[nested_allowed2]", "nested[nested_allowed]", "top_level"]
67
93
  end
94
+
95
+ it "allows wildcards with the :_any_key_ symbol" do
96
+ Braintree::Util._flatten_valid_keys(
97
+ [:top_level, {:nested => :_any_key_}]
98
+ ).should == ["nested[_any_key_]", "top_level"]
99
+ end
68
100
  end
69
101
 
70
102
  describe "self.extract_attribute_as_array" do
@@ -92,6 +124,11 @@ describe Braintree::Util do
92
124
  hash = {:foo => {:key_one => "value_one", :key_two => "value_two"}}
93
125
  Braintree::Util.hash_to_query_string(hash).should == "foo%5Bkey_one%5D=value_one&foo%5Bkey_two%5D=value_two"
94
126
  end
127
+
128
+ it "works for nesting 2 levels deep" do
129
+ hash = {:foo => {:nested => {:key_one => "value_one", :key_two => "value_two"}}}
130
+ Braintree::Util.hash_to_query_string(hash).should == "foo%5Bnested%5D%5Bkey_one%5D=value_one&foo%5Bnested%5D%5Bkey_two%5D=value_two"
131
+ end
95
132
  end
96
133
 
97
134
  describe "self.parse_query_string" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: braintree
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Braintree Payment Solutions
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-18 00:00:00 -06:00
12
+ date: 2010-01-20 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -43,6 +43,7 @@ files:
43
43
  - lib/braintree/transaction/address_details.rb
44
44
  - lib/braintree/transaction/credit_card_details.rb
45
45
  - lib/braintree/transaction/customer_details.rb
46
+ - lib/braintree/transaction/status_details.rb
46
47
  - lib/braintree/transaction.rb
47
48
  - lib/braintree/transparent_redirect.rb
48
49
  - lib/braintree/util.rb
@@ -54,9 +55,11 @@ files:
54
55
  - lib/braintree/xml/parser.rb
55
56
  - lib/braintree/xml.rb
56
57
  - lib/braintree.rb
58
+ - spec/hacks/tcp_socket.rb
57
59
  - spec/integration/braintree/address_spec.rb
58
60
  - spec/integration/braintree/credit_card_spec.rb
59
61
  - spec/integration/braintree/customer_spec.rb
62
+ - spec/integration/braintree/error_codes_spec.rb
60
63
  - spec/integration/braintree/http_spec.rb
61
64
  - spec/integration/braintree/test/transaction_amounts_spec.rb
62
65
  - spec/integration/braintree/transaction_spec.rb