braintree 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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