How to display payment gateway response messages in view - Rails 4, Active Merchant - ruby

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

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.

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%>

Related

Why is my AASM state machine not triggered with Rails 7 Turbo patch links?

I just updated my Rails 6 app to Rails 7 and have problems updating my :patch and :delete links to Turbo.
For example, in one of my views I have this link...
link_to("Mark as sent", status_url(quote), :data => {:'turbo_method' => :patch})
... which is handled by this controller:
class StatusController < ApplicationController
def update
#quote = Quote.find(params[:id])
#quote.send_it! # Should trigger AASM
flash[:notice] = "Quote marked as sent."
redirect_to edit_quote_path(#quote)
end
end
In the model I am using AASM as a state machine:
class Quote < ApplicationRecord
include AASM
aasm :column => "status" do
state :draft, :initial => true
state :inquired
state :sent
state :downloaded
state :accepted
state :rejected
event :send_it do
transitions :from => [:draft, :inquired], :to => :sent
end
...
event :reset_it do
transitions :from => [:inquired, :sent, :downloaded, :accepted, :rejected], :to => :draft
end
end
end
The problem is that the state machine does not get triggered when I hit the link. The flash message and the redirect work but the state is not changed in the database. When I replace #quote.send_it! with #quote.update_column(:status, "sent")it works, however.
Can anybody tell me what I'm missing here?
I don't quite see how turbo is related. Except that I think your redirect isn't actually working:
Redirected to http://127.0.0.1:3000/quotes/1/edit
Completed 302 Found in 18ms (ActiveRecord: 4.3ms | Allocations: 7265)
Started PATCH "/quotes/1/edit" for 127.0.0.1 at 2022-08-12
ActionController::RoutingError (No route matches [PATCH] "/quotes/1/edit"):
# NOTE: ^ not quite a redirect
# v but it doesn't show on a page, it just refreshes the current one.
Started GET "/quotes" for 127.0.0.1 at 2022-08-12 17:51:28 -0400
# and if the current page were /quotes/1/edit then it would look like
# redirect worked, but I was submitting from /quotes.
Update your controller to actually show any errors:
def update
#quote = Quote.find(params[:id])
# NOTE: if transition fails, `send_it!` returns `false`
# (or raises an error for invalid transitions)
# when you run `#quote.update_column(:status, "sent")`
# validations and state machine are not triggered and it works.
if #quote.send_it!
flash.notice = "Quote marked as sent."
else
flash.notice = #quote.errors.full_messages.join(", ")
end
respond_to do |format|
# in case you want add a stream response later
# format.turbo_stream { # TODO }
format.html { redirect_to edit_quote_path(#quote), status: :see_other }
# NOTE: Redirect as a GET request instead of PATCH ^
end
end
Or just add whiny_persistence flag and check the logs, this will raise validation errors:
aasm column: :status, whiny_persistence: true do
Not sure where you got the mark_as_ from, change that to #quote.aasm.fire! status.
Edit
Sorry, not status, needs to be the event, just use the right event.

Best practice of error handling on controller and interactor

# users_show_controller.rb
class Controllers::Users::Show
include Hanami::Action
params do
required(:id).filled(:str?)
end
def call(params)
result = users_show_interactor(id: params[:id])
halt 404 if result.failure?
#user = result.user
end
end
# users_show_interactor.rb
class Users::Show::Interactor
include Hanami::Interactor
expose :user
def call(:id)
#user = UserRepository.find_by(:id)
end
end
I have a controller and a interactor like above.
And I'm considering the better way to distinguish ClientError from ServerError, on the controller.
I think It is nice if I could handle an error like below.
handle_exeption StandardError => :some_handler
But, hanami-interactor wraps errors raised inside themselves and so, controller receive errors through result object from interactor.
I don't think that re-raising an error on the controller is good way.
result = some_interactor.call(params)
raise result.error if result.failure
How about implementing the error handler like this?
I know the if statement will increase easily and so this way is not smart.
def call(params)
result = some_interactor.call(params)
handle_error(result.error) if result.faulure?
end
private
def handle_error(error)
return handle_client_error(error) if error.is_a?(ClientError)
return server_error(error) if error.is_a?(ServerError)
end
Not actually hanami-oriented way, but please have a look at dry-monads with do notation. The basic idea is that you can write the interactor-like processing code in the following way
def some_action
value_1 = yield step_1
value_2 = yield step_2(value_1)
return yield(step_3(value_2))
end
def step_1
if condition
Success(some_value)
else
Failure(:some_error_code)
end
end
def step_2
if condition
Success(some_value)
else
Failure(:some_error_code_2)
end
end
Then in the controller you can match the failures using dry-matcher:
matcher.(result) do |m|
m.success do |v|
# ok
end
m.failure :some_error_code do |v|
halt 400
end
m.failure :some_error_2 do |v|
halt 422
end
end
The matcher may be defined in the prepend code for all controllers, so it's easy to remove the code duplication.
Hanami way is validating input parameters before each request handler. So, ClientError must be identified always before actions logic.
halt 400 unless params.valid? #halt ClientError
#your code
result = users_show_interactor(id: params[:id])
halt 422 if result.failure? #ServerError
halt 404 unless result.user
#user = result.user
I normally go about by raising scoped errors in the interactor, then the controller only has to rescue the errors raised by the interactor and return the appropriate status response.
Interactor:
module Users
class Delete
include Tnt::Interactor
class UserNotFoundError < ApplicationError; end
def call(report_id)
deleted = UserRepository.new.delete(report_id)
fail_with!(UserNotFoundError) unless deleted
end
end
end
Controller:
module Api::Controllers::Users
class Destroy
include Api::Action
include Api::Halt
params do
required(:id).filled(:str?, :uuid?)
end
def call(params)
halt 422 unless params.valid?
Users::Delete.new.call(params[:id])
rescue Users::Delete::UserNotFoundError => e
halt_with_status_and_error(404, e)
end
end
end
fail_with! and halt_with_status_and_error are helper methods common to my interactors and controllers, respectively.
# module Api::Halt
def halt_with_status_and_error(status, error = ApplicationError)
halt status, JSON.generate(
errors: [{ key: error.key, message: error.message }],
)
end
# module Tnt::Interactor
def fail_with!(exception)
#__result.fail!
raise exception
end

cramp framework sync 'render' correct way using em-synchrony

To describe my problem I attach simple Cramp http://cramp.in/ class.
I add some modification but its mainly work like https://github.com/lifo/cramp-pub-sub-chat-demo/blob/master/app/actions/chat_action.rb
class ChatAction < Cramp::Websocket
use_fiber_pool
on_start :create_redis
on_finish :handle_leave, :destroy_redis
on_data :received_data
def create_redis
#redis = EM::Hiredis.connect('redis://127.0.0.1:6379/0')
end
def destroy_redis
#redis.pubsub.close_connection
#redis.close_connection
end
def received_data(data)
msg = parse_json(data)
case msg[:action]
when 'join'
handle_join(msg)
when 'message'
handle_message(msg)
else
# skip
end
end
def handle_join(msg)
#user = msg[:user]
subscribe
publish(:action => 'control', :user => #user, :message => 'joined the chat room')
end
def handle_leave
publish :action => 'control', :user => #user, :message => 'left the chat room'
end
def handle_message(msg)
publish(msg.merge(:user => #user))
# added only for inline sync tests
render_json(:action => 'message', :user => #user, :message => "this info should appear after published message")
end
private
def subscribe
#redis.pubsub.subscribe('chat') do |message|
render(message)
end
end
def publish(message)
#redis.publish('chat', encode_json(message))
end
def encode_json(obj)
Yajl::Encoder.encode(obj)
end
def parse_json(str)
Yajl::Parser.parse(str, :symbolize_keys => true)
end
def render_json(hash)
render encode_json(hash)
end
end
More about what i try to do is in handle_message method.
I try send messages to client in correct order. First publish message to all subscribers, second render some internal info only for current connected client.
For above code client receives:
{"action":"message","user":"user1","message":"this info should appear after published message"}
{"action":"message","message":"simple message","user":"user1"}
Its not synchronized, because of em-hiredis defferable responses, probably.
So I try to synchronized it this way:
def handle_message(msg)
EM::Synchrony.sync publish(msg.merge(:user => #user))
EM::Synchrony.next_tick do # if I comment this block messages order is still incorrect
render_json(:action => 'message', :user => #user, :message => "this info should appear after published message")
end
end
Now, client handle messages with correct order.
{"action":"message","message":"simple message","user":"user1"}
{"action":"message","user":"user1","message":"this info should appear after published message"}
My questions are:
When I comment EM::Synchrony.next_tick block, messages order is still incorrect. What meaning have EM::Synchrony.next_tick block in this example?
Is this good way to handle inline sync with Cramp or EventMachine ?
Is there a better, clearer way to handle it ?
Thank you!
I found solution of this problem, em-synchrony should work inline out of the box by requiring this library:
require 'em-synchrony/em-hiredis'
class ChatAction < Cramp::Websocket
Using EM::Synchrony.next_tick block is bad idea, with big help of em-synchrony community I add em-hiredis 0.2.1 compatibility patch on github
So now handle_message method looks like this:
def handle_message(msg)
publish(msg.merge(:user => #user))
render_json(:action => 'message', :user => #user, :message => "this info should appear after published message")
end
Don`t forget to take this gem from github
gem 'em-synchrony', :git=> 'git://github.com/igrigorik/em-synchrony.git'
Hope it helps someone.

Why is rspec saying this method is not received with should_receive?

My question is, why do I receive the following rspec error message? (code below) I've stubbed the :update_payment method on the StripeSubscription model. I've been at this for a couple of hours and am perplexed.
Failure/Error: #stripe_msub.should_receive(:update_payment).and_return(#stripe_msub)
(#<StripeSubscription:0xb879154>).update_payment(any args)
expected: 1 time
received: 0 times
###Rspec test###
describe "PUT 'update'" do
context "signed-in teacher" do
before(:each) do
#teacher = Factory(:teacher)
#teacher_upload = Factory(:teacher_upload,:teacher_id=>#teacher.id)
#stripe_mplan = Factory(:stripe_plan)
#new_stripe_card_token = 528
#stripe_msub = Factory(:stripe_subscription,:teacher_id=>#teacher.id,:stripe_plan_id=>#stripe_mplan.id, :email=>#teacher.email,:account_status=>Acemt::Application::STRIPE_SUBSCRIPTION_ACCOUNT_STATUS[:active])
#stripe_msub.stub!(:update_payment).and_return(#stripe_msub)
StripeSubscription.stub!(:update_payment).and_return(#stripe_msub)
StripeSubscription.stub!(:update_attributes).and_return(true)
#stripe_customer = mock('Stripe::Customer')
Stripe::Customer.stub!(:retrieve).with(#stripe_msub.stripe_customer_token).and_return(#stripe_customer)
#stripe_customer.stub(:card=).and_return(true)
#stripe_customer.stub(:save).and_return(true)
test_sign_in(#teacher)
end
it "should update credit card information" do
#stripe_msub.should_receive(:update_payment)
Stripe::Customer.should_receive(:retrieve).with(#stripe_msub.stripe_customer_token).and_return(#stripe_customer)
#stripe_customer.should_receive(:card=)
#stripe_customer.should_receive(:save).and_return(#stripe_customer)
put :update, :teacher_id=>#teacher.id, :stripe_subscription=>{:stripe_plan_id=>#stripe_msub.stripe_plan_id, :teacher_id=>#stripe_msub.teacher_id, :email=>#stripe_msub.email, :stripe_customer_token=>#stripe_msub.stripe_customer_token,:stripe_card_token=>#new_stripe_card_token,:account_status=>#stripe_msub.account_status}
#teacher.stripe_subscription.should == #stripe_msub
response.should redirect_to teacher_path(#teacher)
end
end #signed in teacher
end #PUT update
###controller###
class StripeSubscriptionsController < ApplicationController
before_filter :signed_in_teacher
before_filter :correct_teacher
:
def update
##stripe_subscription = StripeSubscription.find_by_id(params[:id])
#stripe_subscription = #teacher.stripe_subscription
if #stripe_subscription.update_payment(params[:stripe_subscription])
#handle successful update
flash[:success] = "Credit card updated"
sign_in #teacher
redirect_to #teacher
else
render 'edit'
end
end
:
end
###model###
class StripeSubscription < ActiveRecord::Base
#attr_accessible :email, :plan_id, :stripe_customer_token, :teacher_id, :account_status
validates_presence_of :stripe_plan_id
validates_presence_of :email
validates_presence_of :teacher_id
belongs_to :stripe_plan, :class_name=>"StripePlan"
belongs_to :teacher
attr_accessor :stripe_card_token
def save_with_payment
if valid?
customer = Stripe::Customer.create(description: email, plan: stripe_plan_id, card: stripe_card_token)
self.stripe_customer_token = customer.id
save!
end
rescue Stripe::InvalidRequestError => e
logger.error "Stripe error while creating customer: #{e.message}"
errors.add :base, "There was a problem with your credit card."
end
def update_payment(stripe_params)
if valid?
customer = Stripe::Customer.retrieve(self.stripe_customer_token)
customer.card = stripe_params[:stripe_card_token]
status = customer.save #update card info on Stripe
update_attributes(stripe_params) #save StripeSubscription object
end
rescue Stripe::InvalidRequestError => e
logger.error "Stripe error while updating your credit card: #{e.message}"
errors.add :base, "There was a problem with your credit card."
end
end
You are setting the expectation on an object in your spec (#stripe_msub). In the controller however, you probably load a teacher from the database and get its subscription (#stripe_subscription).
Due to the roundtrip through the database, this object is not the same one as the one you set your expectation on, therefore the one you set your expection on (#stripe_msub in the spec) never receives the method call and therefore rspec complains.
To fix this, you would have to stub away all database calls and make sure, that your object from the spec appears in the controller. I don't know where exactly #teacher is set in the controller (I guess in one of the filters) so I can't give you the exact solution, but it will be something like this:
# I assume this is the implentation of your filter in the controller
def signed_in_teacher
#teacher = Teacher.find_by_id(params[:teacher_id])
end
# Then you would have to add the following mocks/stubs to your spec
Teacher.should_receive(:find_by_id).once.with(#teacher.id).and_return(#teacher)

Accessing Sinatra scope from another class

I'm running a Sinatra application with a few extra classes pulled in for creating a User and a few others on the fly (no DB, it feeds in from a web service). I'm trying send out a flash notice (using https://github.com/nakajima/rack-flash) from within my User model but can't figure out how to get access to the flash method/variable because I'm out of scope.
Something like:
class User
def something
if true
flash[:notice] = 'Good job'
else
# nope
end
end
end
Which gets required into the Sinatra app by a simple require 'models/user'
This is an XY Problem[1]. Sinatra is responsible for sending out flash messages, not your User objects, so the code for setting the flash should be in your Sinatra app, not in your User class.
[1] http://www.perlmonks.org/index.pl?node_id=542341
You should not ask your User (model) to talk to the UI (view). That's bad/not MVC-clean. That's what a controller is for.
You can use either return values, exceptions, or throw/catch (which is not exception handling) to pass information from your model to your controller. For example, using return values:
post "/create_user" do
flash[:notice] = case User.something
when User then "User Created!"
when :nono then "That's not allowed"
when :later then "User queued to be created later."
end
end
class User
def self.something
if authorized
if can_create_now
new(...)
else
queue_create(...)
:later
end
else
:nono
end
end
end
Since I mentioned them above, following are examples using throw/catch and begin/rescue (exceptions). As the advisability of using either of these constructs is questionable, let us take a moment of silence to ponder if this is a good idea.
Here is an example using throw/catch:
post "/create_user" do
result = catch(:msg){ User.something }
flash[:notice] = case
when :nono then "That's not allowed"
when :later then "User queued to be created later."
else "User Created!"
end
end
class User
def self.something
throw :msg, :nono unless authorized
if can_create_now
new(...)
else
queue_create(...)
throw :msg, :later
end
end
end
Finally, here's an example using exceptions, though I'm not convinced that this will be appropriate for all (non-disastrous) cases where you want to flash unique messages to the user:
post "/create_user" do
flash[:notice] = "User Created!" # Assume all good
begin
User.something
rescue User::Trouble=>e
flash[:notice] = case e
when Unauthorized then "That's not allowed"
when DelayedCreate then "User queued to be created later."
else "Uh...Something bad happened."
end
end
end
class User
class Trouble < RuntimeError; end
class Unauthorized < Trouble; end
class DelayedCreate < Trouble; end
def self.something
raise Unauthorized unless authorized
if can_create_now
new(...)
else
queue_create(...)
raise DelayedCreate
end
end
end
Exceptions let you pass an additional data along (e.g. raise Unauthorized.new "No such account", or any custom properties you want to add to your class), and so may be more useful (when appropriate). Just remember to pass semantic results from your model to your controller, and let it translate them into user-facing messages.

Resources