lyang / braintree-rails

MIT License
65 stars 22 forks source link

CreditCard#errors is empty upon verification failure #33

Open n0nick opened 8 years ago

n0nick commented 8 years ago

When a credit card save fails due to a verification issue, the result includes an empty errors object:

I, [2016-05-09T12:45:32.308159 #97469]  INFO -- : [Braintree] [09/May/2016 09:45:32 UTC] POST /merchants/9pqw8ftzqf9kt5vz/payment_methods 422
D, [2016-05-09T12:45:32.308333 #97469] DEBUG -- : [Braintree] [09/May/2016 09:45:32 UTC] 422 Unprocessable Entity
D, [2016-05-09T12:45:32.308565 #97469] DEBUG -- : [Braintree] <?xml version="1.0" encoding="UTF-8"?>
[Braintree] <api-error-response>
[Braintree]   <errors>
[Braintree]     <errors type="array"/>
[Braintree]   </errors>
...

(see full log.)

When parsing the Braintree::ErrorResult object, BraintreeRails only looks at the `errors attribute: persistence.rb#L122.

Therefore, in such case, the save fails (returns false) but no errors are stored on the object, and there's no relevant error message to show to the user. I've asked Braintree support about this and they said the client code should look into the response object's credit_card_verification attribute.

Here's an example using Braintree's Sandbox environment, trying to save a credit card number guaranteed to fail (see Unsuccessful credit card verification).

$ bundle show braintree-rails
/Users/sagie/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/braintree-rails-1.4.1

$ bundle show braintree
/Users/sagie/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/braintree-2.60.0

$ bundle exec rails console
Loading development environment (Rails 4.2.5.2)
irb(main):001:0> c = BraintreeRails::CreditCard.new(customer_id: 34764032, number: '4000111111111115', cvv: '666', expiration_date: '06 / 2020')
=> #<BraintreeRails::CreditCard:0x007ffd4e1969c0 @transactions=nil, @subscriptions=nil, @customer=nil, @billing_address=nil, @persisted=false, @customer_id=34764032, @expiration_date="06 / 2020", @number="4000111111111115", @cvv="666", @raw_object=#<OpenStruct customer_id=34764032, number="4000111111111115", cvv="666", expiration_date="06 / 2020">>
irb(main):002:0> c.save
=> false
irb(main):003:0> c.errors
=> #<ActiveModel::Errors:0x007ffd4e154fc0 @base=#<BraintreeRails::CreditCard:0x007ffd4e1969c0 @transactions=nil, @subscriptions=nil, @customer=nil, @billing_address=nil, @persisted=false, @customer_id=34764032, @expiration_date="06/2020", @number=nil, @cvv=nil, @raw_object=#<OpenStruct customer_id=34764032, number="4000111111111115", cvv="666", expiration_date="06/2020">, @validation_context=nil, @errors=#<ActiveModel::Errors:0x007ffd4e154fc0 ...>>, @messages={}>
irb(main):004:0> c.errors.empty?
=> true
BenTalagan commented 7 years ago

I have seen it also on transactions :

t = card.transactions.build(:amount => amount, :options => {:submit_for_settlement => true})
t.save # is false
t.errors.any? # is false too

This is really dangerous : doing it the rails way, you often check errors, not the result of the save. Thus, you will consider as good some transactions that have been declined !!