I'm building a small proof of concept with Stripe and Ruby on Rails 3.2. So far I've watched the Railscast on how to implement Stripe in a RoR app and it's working really well.
I've built my app by following RailsCast #288 Billing with Stripe. Now my users can add and edit their credit cards and even register to classes and have their credit card billed upon completion.
Now I've been testing with Stripe's numerous test credit cards and I want to catch as many exceptions when raised. I'm using Stripe's example errors in my Registration model as show here:
class Registration < ActiveRecord::Base
belongs_to :user
belongs_to :session
attr_accessible :session_id, :user_id, :session, :user, :stripe_payment_id
validates :user_id, :uniqueness => {:scope => :session_id}
def save_with_payment(user, stripe_card_token)
if valid?
if user.stripe_customer_id.present?
charge = Stripe::Charge.create(
:customer => user.stripe_customer_id,
:amount => self.session.price.to_i * 100,
:description => "Registration for #{self.session.name} (Id:#{self.session.id})",
:currency => 'cad'
)
else
customer = Stripe::Customer.create(
:email => user.email,
:card => stripe_card_token,
:description => user.name
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => self.session.price.to_i * 100,
:description => "Registration for #{self.session.name} (Id:#{self.session.id})",
:currency => 'cad'
)
user.update_attribute(:stripe_customer_id, customer.id)
end
self.stripe_payment_id = charge.id
save!
end
rescue Stripe::CardError => e
body = e.json_body
err = body[:error]
logger.debug "Status is: #{e.http_status}"
logger.debug "Type is: #{err[:type]}"
logger.debug "Code is: #{err[:code]}"
logger.debug "Param is: #{err[:param]}"
logger.debug "Message is: #{err[:message]}"
rescue Stripe::InvalidRequestError => e
# Invalid parameters were supplied to Stripe's API
rescue Stripe::AuthenticationError => e
# Authentication with Stripe's API failed
# (maybe you changed API keys recently)
rescue Stripe::APIConnectionError => e
# Network communication with Stripe failed
rescue Stripe::StripeError => e
# Display a very generic error to the user, and maybe send
# yourself an email
rescue => e
# Something else happened, completely unrelated to Stripe
end
end
I'm merely rescuing from errors right now and not really taking action after one being raised and ultimately I would like to stop the current class registration from happening and redirect a user with a flash error.
I've read about rescure_from but I'm not sure what is the best way to handle of all the possible Stripe errors. I know can't redirect from the model, how would you experts handle this?
Here's my Registration controller:
class Classroom::RegistrationsController < ApplicationController
before_filter :authenticate_user!
def new
if params[:session_id]
#session = Session.find(params[:session_id])
#registration = Registration.new(user: current_user, session: #session)
else
flash[:error] = "Course session is required"
end
rescue ActiveRecord::RecordNotFound
render file: 'public/404', status: :not_found
end
def create
if params[:session_id]
#session = Session.find(params[:session_id])
#registration = Registration.new(user: current_user, session: #session)
if #registration.save_with_payment(current_user, params[:stripe_card_token])
flash[:notice] = "Course registration saved with success."
logger.debug "Course registration saved with success."
mixpanel.track 'Registered to a session', { :distinct_id => current_user.id,
:id => #session.id,
'Name' => #session.name,
'Description' => #session.description,
'Course' => #session.course.name
}
mixpanel.increment current_user.id, { :'Sessions Registered' => 1}
mixpanel.track_charge(current_user.id, #session.price.to_i)
else
flash[:error] = "There was a problem saving the registration."
logger.debug "There was a problem saving the registration."
end
redirect_to root_path
else
flash[:error] = "Session required."
redirect_to root_path
end
end
end
Thanks for taking the time to respond, much appreciated!
Francis
Have you thought of putting the actually Stripe call in a custom validator?
http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validate
That way you could add errors to the object with something like the following
The logic behind this is you only want to save successful transactions as 'transaction' anyway so why not just put the Stripe charge in the validator.
validate :card_validation
def card_validation
begin
charge = Stripe::Charge.create(
:customer => user.stripe_customer_id,
:amount => self.session.price.to_i * 100,
:description => "Registration for #{self.session.name} (Id:#{self.session.id})",
:currency => 'cad'
)
etc etc
rescue => e
errors.add(:credit_card, e.message)
#Then you might have a model to log the transaction error.
Error.create(charge, customer)
end
end
This way you can handle the errors like any other errors you would get from a entry not saving, instead of giving a blank error message, or having to handle every last error from Stripe.
class Classroom::RegistrationsController < ApplicationController
before_filter :authenticate_user!
def create
if params[:session_id]
#session = Session.find(params[:session_id])
params[:registration][:user] = current_user
params[:registration][:session] = #session
params[:registration][:stripe_card_token] = params[:stripe_card_token]
#registration = Registration.new(params[:registration])
respond_with(#registration) do |format|
if #registration.save
format.html {redirect_to root_path, :notice => "SOMETHING HERE TO TELL THEM SUC"}
else
format.html {render}
end
end
else
respond_with do |format|
format.html {redirect_to root_path, :error => "SOMETHING HERE TO TELL THEM GET SESSION"}
end
end
end
end
Related
I have multiple clients relying on my server that processes Stripe charge requests. When a charge is processed, I want to send my client back JSON of whether the charge was successfully created, and if it wasn't, the reasons why.
My server can be viewed here.
The code for my controller is the following:
class ChargesController < ApplicationController
protect_from_forgery
skip_before_action :verify_authenticity_token
def new
end
def create
# Amount in cents
#amount = 500
customer = Stripe::Customer.create(
:email => params[:stripeEmail],
:source => params[:stripeToken]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => #amount,
:description => 'Rails Stripe customer',
:currency => 'usd'
)
#*WHAT I TRIED DOING THAT DIDN'T WORK*
# respond_to do |format|
# msg = { :status => "ok", :message => "Success!"}
# format.json { render :json => msg }
# end
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to new_charge_path
end
end
I am trying to call my RESTful API with the following URL:
curl -XPOST https://murmuring-wave-13313.herokuapp.com/charges.json?stripeToken=tok_*****************&stripeEmail=rsheeler#gmail.com
I'm assuming I need to access some of the metadata, but I'm unsure how to.
Which results in a 500 Response
How can I properly structure my Charges controller in order to return JSON of Stripe's response?
Why doesnt this work?
#*WHAT I TRIED DOING THAT DIDN'T WORK*
respond_to do |format|
msg = { :status => "ok", :message => "Success!"}
format.json { render :json => msg } # don't do msg.to_json
format.html { render :template => "charges/create"}
end
What are the errors in your log?
So I'm smacking myself. What I realized was after you make the Stripe::Charge object, a JSON-serialized Charge object is assigned to it.
Because of this, you can access all the metadata in the Charge instance by simply calling charge.attribute_name. For instance, if it was a valid charge, charge.status would return "succeeded". Because what assigned back to charge is JSON you can simply return render charge if the requested format is JSON.
The working Charge controller looks like the following:
class ChargesController < ApplicationController
protect_from_forgery
skip_before_action :verify_authenticity_token
def new
end
def create
# Amount in cents
#amount = 500
customer = Stripe::Customer.create(
:email => params[:stripeEmail],
:source => params[:stripeToken]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => #amount,
:description => 'Rails Stripe customer',
:currency => 'usd'
)
# If in test mode, you can stick this here to inspect `charge`
# as long as you've imported byebug in your Gemfile
byebug
respond_to do |format|
format.json { render :json => charge }
format.html { render :template => "charges/create"}
end
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to new_charge_path
end
end
For example, if I purposely make the card decline, I get shown the error page. Like so:
Instead of an error page I want to be notified with a flash instead. I have the below code, but why am I not alerted using a flash?
class ChargesController < ApplicationController
def new
end
def create
# Amount in cents
#amount = 100
# Get the credit card details submitted by the form
customer = Stripe::Customer.create(
:email => params[:email],
:source => params[:stripeToken]
)
# Create the charge on Stripe's servers - this will charge the user's card
begin
Stripe::Charge.create(
:amount => #amount,
:currency => 'usd',
:customer => customer.id,
:description => 'Example charge custom form'
)
current_user.subscribed = true
current_user.stripe_id = customer.id
current_user.expiry_date = Date.today + 30.days
current_user.save
flash[:success] = "Thank you for subscribing. Your account has been unlocked."
redirect_to root_path
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to root_path
end
end
end
Can you please try this one, just to see the result.
Don't put the flash or redirection. Use raise or something else just to see where the code get caught.
begin
# Use Stripe's library to make requests...
rescue Stripe::CardError => e
# Since it's a decline, Stripe::CardError will be caught
rescue Stripe::RateLimitError => e
# Too many requests made to the API too quickly
rescue Stripe::InvalidRequestError => e
# Invalid parameters were supplied to Stripe's API
rescue Stripe::AuthenticationError => e
# Authentication with Stripe's API failed
# (maybe you changed API keys recently)
rescue Stripe::APIConnectionError => e
# Network communication with Stripe failed
rescue Stripe::StripeError => e
# Display a very generic error to the user, and maybe send
# yourself an email
rescue => e
# Something else happened, completely unrelated to Stripe
end
hi i am trying to get my braintree account to display errors when creating a transaction but it doesn't appear to be working
- flash.each do |key, value|
%div{:class => "alert alert-#{key}"}= value
def update
result = Braintree::Transaction.sale(
:amount => params[:amount_to_add].to_f,
# :order_id => "order id",
:customer_id => customer.customer_cim_id,
:tax_amount => (params[:amount_to_add].to_f / 11).round(2),
:options => {
:submit_for_settlement => true
}
)
if result.success?
logger.info "Added to #{params[:amount_to_add].to_f} to #{customer.first_name} #{customer.last_name} (#{customer.customer_cim_id})"
customer.store_credit.add_credit(params[:amount_to_add].to_f)
redirect_to myaccount_store_credit_path
# , :notice => "Successfully updated store credit."
else
result.errors.each do |error|
puts error.message
customer.errors.add(:base, error.message)
render :show, :notice => error.message
end
end
end
I believe the reason you're probably unable to see the errors is because of the :notice option on your render method, which is redundant because render doesn't seem to be using the :notice option, only redirect_to. You may just add the errors to your to your flash, but note that in your view you have to loop through the errors within the flash to render it.
Another way I would think you do this though is add the payment method to your User model
class User|Customer
...
def process_braintree_payment(amount)
result = Braintree::Transaction.sale(
:amount => amount.to_f,
# :order_id => "order id",
:customer_id => customer_cim_id,
:tax_amount => (amount.to_f / 11).round(2),
:options => {
:submit_for_settlement => true
}
)
add_braintree_errors(result.error) unless result.success?
end
def add_braintree_errors(error_object)
error_object.each do |error|
errors.add(:braintree, error.message)
end
end
end
class XController
def update
#customer.process_braintree_payment(params[:amount_to_add])
if #customer.errors.empty?
logger.info "Added to #{params[:amount_to_add].to_f} to #{#customer.first_name} #{#customer.last_name} (#{#customer.customer_cim_id})"
#customer.store_credit.add_credit(params[:amount_to_add].to_f)
redirect_to myaccount_store_credit_path
# , :notice => "Successfully updated store credit."
else
render :show
end
end
end
In your view, you have access to the #customer variable or better you could store the error object in the ActionController#flash, note however, using the same keys for your flash messages, would overwrite the previous value.
I've just started using Stripe and have followed their guide on setting up a checkout with Rails
In my case, i'm giving users to ability to upgrade an event listing.
In my event model I have a make_premium method:
def make_premium
self.update_attribute(:premium_event, true)
end
My event_controller's upgrade action is on Based on Stripe's create example, except i've added a repond_to block to take users back to the event they upgraded and also call the make_premium method.
def upgrade
#event = Event.find_by_slug(params[:id])
# Amount in pence
#amount = 299
customer = Stripe::Customer.create(
:email => current_user.email,
:card => params[:stripeToken]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => #amount,
:description => 'Premium Event Upgrade',
:currency => 'gbp',
:metadata => {'event_id' => #event.id, 'event_headline' => #event.headline}
)
respond_to do |format|
format.html { redirect_to #event, :notice => 'Event was successfully upgraded.' }
#event.make_premium
end
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to event_path(#event)
end
What I want to know, is a) is the respond to block ok to use here given the rescue that comes after and b) is it ok to call my make_premium method from within this block?
I've played around with using Stripe's webhooks and while the charge.suceeded hook works well, it seems like an extra step I could avoid if the above is sufficient.
Any insight from people more experienced with Stripe would be appreciated.
I'm almost positive that you can have a rescue block anywhere in a method and it will get called if the exception occurs.
The only potential change I would suggest is to add a check to make sure the attribute got updated successfully.
def upgrade
#event = Event.find_by_slug(params[:id])
# Amount in pence
#amount = 299
customer = Stripe::Customer.create(
:email => current_user.email,
:card => params[:stripeToken]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => #amount,
:description => 'Premium Event Upgrade',
:currency => 'gbp',
:metadata => {'event_id' => #event.id, 'event_headline' => #event.headline}
)
respond_to do |format|
if #event.make_premium
format.html { redirect_to #event, :notice => 'Event was successfully upgraded.' }
else
// handle error and redirect
end
end
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to event_path(#event)
end
I'm building an application that handles different types of transactions for different services such as subscriptions, gift subscriptions and purchases.
I have an issue with the gift transactions and activemerchant. Ill give you a brief overview of how it works.
The user creates a gift subscription and fills out the data for it, it is stored in the db and then shown back to the user for review in a custom "show_view", the user then proceeds to enter credit card information in a separate form and when he submits the data, a method from the controller is called to handle the transaction and here is where Im having issues.
This is the gift_subscription.rb model
def gift_purchase
response = GATEWAY.purchase(price, credit_card, gift_purchase_options)
GiftTransaction.create!(:action => "gift_purchase", :amount => price, :response => response)
response.success?
end
private
def gift_purchase_options
{
:ip => ip_address,
:billing_address => {
:name => name + last_name,
:address1 => address1,
:city => city,
:state => state,
:country => "Mexico",
:zip => zip
}
}
end
def validate_card
unless credit_card.valid?
credit_card.errors.full_messages.each do |message|
errors[:base] << message
end
end
end
def credit_card
#credit_card = ActiveMerchant::Billing::CreditCard.new(
:brand => card_type,
:number => card_number,
:verification_value => card_verification,
:month => card_expires_on.month,
:year => card_expires_on.year,
:first_name => name,
:last_name => last_name
)
And here is the gift_subscription_controller.rb
def review
#gift_subscription = GiftSubscription.find(params[:id])
end
def edit_review
#gift_subscription = GiftSubscription.find(params[:id])
end
def update_review
#gift_subscription = GiftSubscription.find(params[:id])
respond_to do |format|
if #gift_subscription.update_attributes(params[:gift_subscription])
format.html { redirect_to "gift_subscriptions/review/#{#gift_subscription.id}", :notice => 'Gift subscription was successfully updated.' }
format.json { head :no_content }
else
format.html { render :action => "edit_review" }
format.json { render :json => #gift_subscription.errors, :status => :unprocessable_entity }
end
end
end
def do_gift_transaction
#gift_subscription = GiftSubscription.find(params[:id])
if #gift_subscription.gift_purchase
redirect_to '/thank_you'
else
redirect_to "/gift_subscriptions/#{#gift_subscription.id}/failed_transaction"
end
end
def failed_transaction
#gift_subscription = GiftSubscription.find(params[:id])
#gift_transactions = #gift_subscription.gift_transactions
end
def transaction_details
#gift_subscription = GiftSubscription.find(params[:id])
end
To make things a little more clear, from the controller create method, it redirects users to the review action where there's an edit_review in case they want to change something, then they go to transaction_details where they enter creditcard info and finally the method do_gift_transaction is called to actually do the transaction.
The error I get is the following
NoMethodError in GiftSubscriptionsController#do_gift_transaction
undefined method `month' for nil:NilClass
Rails.root: /home/peanut/RubymineProjects/GiftBox
Application Trace | Framework Trace | Full Trace
app/models/gift_subscription.rb:44:in `credit_card'
app/models/gift_subscription.rb:12:in `gift_purchase'
app/controllers/gift_subscriptions_controller.rb:113:in `do_gift_transaction'
I've been looking around and I can't seem to find why it doesnt recognize the month... For other subscriptions I have basically the same model (a few diferences) but it works perfectly. Any help here would be much appreciated.
GiftSubscription model attributes
attr_accessible :response, :name, :last_name, :address1, :address2,:city,
:state, :zip, :card_type, :ip_address, :price,
:duration, :created_at, :card_expires_on, :card_number,
:card_verification, :message
has_one :gift_transactions, :class_name => "GiftTransaction"
attr_accessor :card_number, :card_verification
validate :validate_card, :on => :transaction_details
So after a few hours of banging my head it turned out to be quite simple... In the view to enter credit card data, the fields that were supposed to be saved to the db were not being saved because I only had a link_to button to continue and ergo when the credit_card method was called, it was empty.
Thanks to lander16 for pointing this out.