I am using Rails 4.2, and trying to integrate Stripe Checkout (https://stripe.com/docs/checkout/guides/rails) in my rails app and have a scenario I haven't seen outlined anywhere. Note: I tried custom form integration from a number of online resources but couldn't get it to work so opted to give checkout a go.
In my rails app, I have an orders table, and the main thing I'm trying to accomplish is having a form where the user can submit their personal information (non-payment) to place an order. Then, the stripe checkout integration will allow them to pay; however, a record of the order will not be created in the database without a stripe charge being logged. I've been unable to accomplish this with using the separate "charges" controller that stripe suggests, and also tried incorporating the stripe code into my orders controller (see below).
I should note that I HAVE been able to get checkout button to submit to stripe and the charges are processed, but HAVE NOT been able to get a order record to be created in my database.
I have searched far and wide for an answer to this question (currently waiting on response from stripe support). Any suggestions would be much appreciated!
orders_controller.rb
(this is the example where I tried combining the stripe code from the charges controller they suggest into my own orders controller. i'm not sure now what to do after the charge is processed to get it to submit the form)
def create
#order = Order.new(order_params)
customer = Stripe::Customer.create(
:email => 'example#stripe.com',
:card => params[:stripeToken]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => 5000,
:description => 'Rails Stripe customer',
:currency => 'usd'
)
rescue Stripe::CardError => e
flash[:error] = e.message
render 'new'
end
orders/new.html.erb
(I am leaving out code for all the other fields in my form, this just shows my form submit button and the stripe checkout button. Ideally I could combine the actions into one button, or only have the submit go through when the payment successfully processes through stripe)
<%= form_for #order do |f| %>
// lots of form fields
<%= f.submit %>
<script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="<%= Rails.configuration.stripe[:publishable_key] %>"
data-description="A month's subscription"
data-amount="500"></script>
Typically you would do...
def create
#order = Order.new(order_params)
charge_error = nil
if #order.valid?
begin
customer = Stripe::Customer.create(
:email => 'example#stripe.com',
:card => params[:stripeToken])
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => 5000,
:description => 'Rails Stripe customer',
:currency => 'usd')
rescue Stripe::CardError => e
charge_error = e.message
end
if charge_error
flash[:error] = charge_error
render :new
else
#order.save
redirect_to (successful page)
end
else
flash[:error] = 'one or more errors in your order'
render :new
end
end
This way the charge isn't made unless the #order is validated, and the #order isn't saved unless the charge is successful.
Related
I have a simple checkout with Rails using Stripe.
Based on option selected/button clicked the user will be charged a different amount, change description and listing id e.g.
<%= link_to "Pay To Activate",
new_charge_path(:id => listing.id, :amount => 123, :desc => "Option A"),
class: "btn btn-primary btn-sm", :method=> :get %>
When I send this to the ChargesController I'm getting amount, description and id from the parameters:
http://localhost:3000/charges/new?amount=123&desc=Option+A&id=45
Obviously this is not secure because the user can change the amount in URL.
create action looks as below:
def create
customer = Stripe::Customer.create(
:email => params[:stripeEmail],
:source => params[:stripeToken]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => #amount,
:description => #description,
:currency => 'eur'
)
redirect_to thankyou_path
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to new_charge_path
end
How should I changed my code to make it more secure?
Based on experts comment payment process function have to model.
On the payment model I don't know what model actually using for payment, by the way, anything like that
#=> model.rb
def process_payment
customer = Stripe::Customer.create email: email, card: token
Stripe::Charge.create customer: customer.id, amount: 1000, description: 'Premium', currency: 'usd' #=> 1000 means 10 USD
end
Controller
#payment = Payment.new({ email: params[:stripeEmail],
token: params[:stripeToken]
})
begin
#payment.process_payment
#payment.save
redirect_to thankyou_path
rescue Exception => e
flash[:error] = e.message
#payment.destroy
redirect_to new_charge_path
end
I think to create a problem when you try to implement this formula that's why to make best common sense and create a secure payment. I need to say this is not accurate for your use because I don't know your project structure this is just a formula for a secure payment.
Thanks
Hope to help
I just converted a functional model from using Stripe to create a charge to creating a subscription and for some reason now it is creating two subscriptions instead of one. The code on my new view hasn't changed since it worked, so the problem isn't here (in my opinion), but since this SO post had a problem with the js I wanted to show it anyway:
<%= form_tag charges_path do %>
<article>
<% if flash[:error].present? %>
<div id="error_explanation">
<p><%= flash[:error] %></p>
</div>
<% end %>
<label class="amount">
<span>Amount: $7.99/month</span>
</label>
</article>
<script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="<%= Rails.configuration.stripe[:publishable_key] %>"
data-description="Generator Subscription"
data-amount="799"
data-locale="auto"></script>
<% end %>
Here's my controller, where I believe the problem must lie:
class ChargesController < ApplicationController
def new
unless current_user
flash[:error] = "Step one is to create an account!"
redirect_to new_user_registration_path
end
if current_user.access_generator
flash[:notice] = "Silly rabbit, you already have access to the generator!"
redirect_to controller: 'generators', action: 'new'
end
end
def create
customer = Stripe::Customer.create(
:email => params[:stripeEmail],
:source => params[:stripeToken],
:plan => "generator_access"
)
subscription = Stripe::Subscription.create(
:customer => customer.id,
:plan => "generator_access"
)
if subscription
current_user.update_attributes(access_generator: true)
current_user.update_attributes(stripe_customer_id: subscription.customer)
current_user.update_attributes(stripe_sub_id_generator: subscription.id)
flash[:notice] = "You have been granted almighty powers of workout generation! Go forth and sweat!"
redirect_to controller: 'generators', action: 'new'
end
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to new_charge_path
end
def cancel_subscription
#user = current_user
customer = Stripe::Customer.retrieve(#user.stripe_customer_id)
subscription = Stripe::Subscription.retrieve(#user.stripe_sub_id_generator)
if customer.cancel_subscription(params[:customer_id])
#user.update_attributes(stripe_customer_id: nil, access_generator: false, stripe_sub_id_generator: nil)
flash[:notice] = "Your subscription has been cancelled."
redirect_to user_path(#user)
else
flash[:error] = "There was an error canceling your subscription. Please notify us."
redirect_to user_path(#user)
end
end
end
The cancel_subscription method works perfectly (once I manually delete the duplicate subscription via the stripe dashboard), so it really has to be something in the 'create' method. I also checked my console and the information for the User attributes is being correctly updated to match the second of the two duplicate subscriptions being created.
Can anyone see why this code is yielding two subscriptions?
When you create a Customer with a plan, corresponding subscription is created automatically. You don't need to create it manually.
For Stripe V3, This is how you can create and check subscription to avoid multiple values for same customer - in below code, i create a trial subscription for a customer.
## method to create/fetch customer
def create_or_retrieve_customer(user)
customer = retrieve_stripe_customer(user)
if customer.nil? or customer.deleted?
customer = Stripe::Customer.create({
email: user.email,
description: "Created on #{Time.zone.now.to_date}",
name: user.username,
})
end
customer
end
## get the customer
customer = create_or_retrieve_customer(self)
##setup the subscription
#subscription = Stripe::Subscription.create({
customer: customer.id,
items: [
{
price: "prod_aksfdklajfl",
},
],
trial_end: 30.days.from_now.to_date.to_time.to_i,
cancel_at_period_end: true,
billing_cycle_anchor: 30.days.from_now.to_date.to_time.to_i
})
Once you have the subscription id, store it in the DB in the user table, maybe and then validate it whenever neeeded.
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
I want to know if I am understanding the begin/rescue construct in Ruby correctly. I read the Ruby docs and I am still not sure if I am clear. I'm implementing Stripe for payment in a Rails site I am building. Stripe recommends using begin/rescue. I have the following code in my payments controller based on stripe.com's docs:
begin
charge = Stripe::Charge.create(
:amount => #amount,
:card => token,
:description => 'Rails Stripe customer',
:currency => 'usd'
)
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to charges_path
end
#payment = Payment.new(params[:payment])
if #payment.save
flash[:notice] = "Payment taken for #{number_to_currency(#amount/100)}."
else
flash[:notice] = "Payment record not created."
redirect_to charges_path
end
I don't want the section starting with #payment that comes after the end of the begin/rescue to run if the charge to stripe fails. It looks to me that upon failure of the stripe charge, the rescue code will run, causing rails to redirect to the charges_path and the following #payment code will not run, which is the behavior I want. Am I understanding this correctly?
Add a return statement after the redirect_to, i.e.
return redirect_to(charges_path)
OR
redirect_to(charges_path)
return
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