Question

I'm using Active Merchant with Stripe as the payment gateway. Everything works fine except that i don't know how to go about getting the gateway response error messages from Stripe (when a card is declined, invalid etc) to display on the checkout page to the user. I can get a StandardError to be raised that redirects to an error page with the response message but that's it.

ORDER MODEL

class Order < ActiveRecord::Base

has_many :order_products
has_many :products, through: :order_products

attr_accessor :card_number, :security_code, :card_expires_on

validate :validate_card, :on => :create

    def validate_card
    unless credit_card.valid? 
      credit_card.errors.full_messages.each do |message|
       errors[:base] << message

      end

   end
end

def purchase(basket)

    response = GATEWAY.purchase(Product.total_basket_price(basket)*100, credit_card, purchase_options)
        unless response.success? 
         raise StandardError, response.message



    end
end



def credit_card

    @credit_card ||= ActiveMerchant::Billing::CreditCard.new(
        :number                 => card_number,
        :first_name             => first_name,
        :last_name              => last_name,
        :verification_value     => security_code,
        :month                  => card_expires_on.month,
        :year                   => card_expires_on.year
        )
end

def purchase_options 
{
    :billing_address => {
        :address1 => address_1,
        :address2 => address_2,
        :city     => city,
        :country  => country_code,
        :zip      => postal_code 
        }
}

end


end

ORDERS CONTROLLER

class OrdersController < ApplicationController

def create



    @order = Order.new(order_params)

    # @product = basket.find(params[:product_id])


    basket.each do |item_id|
        @order.order_products.build(product: Product.find(item_id))
    end

    if @order.save
        if @order.purchase(basket)

            render "show"
        else
            render "failure"
        end
    else
        render "new"


    end
end

Can anyone lend a hand, please??

Many Thanks

Was it helpful?

Solution 2

After a lot of fiddling and help, the working solution was to search for an error key within the response params hash and if an error was present add the message to the object errors. Not particularly elegant but it now does what i want.

ORDER MODEL

def purchase(basket)

    response = GATEWAY.purchase(Product.total_basket_price(basket)*100, credit_card, purchase_options)

        if response.params.key?('error')

            self.errors.add :base, response.message

            false

        else 
            true

        end 

end

ORDERS CONTROLLER

Also switched the order of the if statements in the controller so that def purchase(basket) runs first before the order is saved, allowing the error message(s) from the response to be caught and displayed.

if @order.purchase(basket)
        if @order.save

            render "show"   
        else 

            render "new"


        end
    else
        render "new"


    end

VIEW

<%= if @order.errors.any?
           @order.errors[:base].to_sentence

end%>

OTHER TIPS

Easy peasy!

This is a simple matter of control flow. In Ruby, as in most languages, exceptions interrupt the normal program flow. As your code is written now, #purchase is raising an exception when it fails.

That's fine and a perfectly valid design decision. But the code interacting with #purchase is this:

if @order.purchase(basket)
    render "show"
else
    render "failure"
end

That code has no exception handling, so any exception will be caught by Rails, program flow will halt, and you'll get either a detailed error page (in development mode) or a generic 500 error page (in production mode).

Since you profess to be new to Ruby and Rails, a little code substitution might make this clearer:

# If #purchase is successful, it evaluates to true.
if true
  render "show"       # 'show' view is rendered as expected. Flow stops.
else
  render "failure"
end

# If #purchase fails, it raises an exception.
if raise StandardError, response.message
  # ^^^ Exception is raised, flow stops here.
  render "show"       # This code is never reached.
else                  # This code is never reached.
  render "failure"    # This code is never reached.
end

As I implied in the beginning, though, it's an easy fix once you know what the issue is. You can simply handle the exception with rescue. Where you currently have an if/else block, you can swap in an if block and a rescue block:

if @order.purchase(basket)
  render 'show'
end
rescue => e
  render 'failure'

There's room for improvement here depending on your needs—since you're raising and rescuing StandardError, for example, your can't easily distinguish between a network failure and a declined card—but it'll get you moving again.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top