spree/spree — pull request #2616 by robertoles (2013-02-26 13:47:10+00:00)
Title: Fixing locked orders due to invalid card details for a payment

This fixes the locked order issue described in previous issue https://github.com/spree/spree/issues/1767

A brief review of the issue:
If a user tries to place an order, but their credit card details are invalid the payment will change its state to failed, however due to database transaction being owned by the order for that payment, the transaction is rolled back because the code raises an exception during the state transition for the order.

To reproduce:
1) Configure a payment method which uses a BogusSimple Gatway.
2) Populate a Basket in the store
3) Checkout the basket, during the payment step, enter an incorrect credit card number e.g. 1234123412341234
If you follow the process_payments! method through using the ruby debugger, you will see the state of the payment will move from checkout -> processing -> failed, however, when process_payments! returns false due to the payment being failed, the transition (Spree::Order::Checkout before_transition :to => :complete) will rollback the Order transaction, causing the child payments state to be rolled back to checkout as well.
4) The payment view will re render, with a blank credit card form, this time enter a valid credit card number e.g.
4111111111111111
At this point you would expect the order to complete, however the order attempts to process the initial payment, that is in the 'checkout' state due to the rollback, and once again this initial payment fails, therefore causing the whole Order transition to fail again.

To fix this I added an after_rollback hook to payment class which forces a save on the object if its state is failed.

Along side this fix, I have included some code to add a new 'invalid' state to the payment object. Payment#process! will invoke the invalidate! event if the payment_method does not support the payment source. I have implemented Gateway#supports? to check if the card brand is supported by the chosen gateway. I have done this because using an incorrect card with some gateways (for my case Worldpay) causes an exception to be thrown due to the card having no brand (due to an incorrectly formatted card number), I added the new state as I wasnt sure if setting the state to failed would imply a greater error / enables additional functionality elsewhere in the codebase.

Please note that although I have added some specs, and fixed any which I have broken, there were already some failing which I haven't been able to fix for you...

rspec ./spec/controllers/spree/orders_controller_extension_spec.rb:27 # Spree::OrdersController extension testing update specify symbol for handler instead of Proc POST has value success
rspec ./spec/controllers/spree/orders_controller_extension_spec.rb:44 # Spree::OrdersController extension testing update render POST has value success
rspec ./spec/controllers/spree/orders_controller_extension_spec.rb:61 # Spree::OrdersController extension testing update redirect POST has value success
rspec ./spec/controllers/spree/orders_controller_extension_spec.rb:78 # Spree::OrdersController extension testing update validation error POST has value success
rspec ./spec/controllers/spree/orders_controller_extension_spec.rb:94 # Spree::OrdersController extension testing update A different controllers respond_override. Regression test for #1301 POST should not effect the wrong controller
rspec ./spec/requests/admin/products/edit/taxons_spec.rb:11 # Product Taxons managing taxons should allow an admin to manage taxons