Stripe Creating Two Subscriptions - ruby-on-rails

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.

Related

undefined method `stripe_id' for nil:NilClass

I am having this issue and each time I just remove stripe_id then I don't have the error but I get back to the home page without the payment working... Some freelancer did this part and I can't figure out why it doesn't work on my computer but works fine on a server or the freelancer's computer.. the code won't work on any of my computers.. Anybody knows what's the issue I been trying to figure this one out. On the server the payment works...
This is my controller
class SubscriptionsController < ApplicationController
protect_from_forgery :except => :webhooks
before_action :authenticate_user!, except: [:webhooks]
def new
end
# this is for recursive
def subscription_payment
begin
stripe_id = Plan.find_by_plan_type(params[:plan_type]).stripe_id
# stripe_id = Plan.find(params[:plan_id]).stripe_id
#plan = Stripe::Plan.retrieve(stripe_id)
customer = Stripe::Customer.create(
:description => "Customer for #{params[:stripeEmail]}",
:source => params[:stripeToken],
:email => params[:stripeEmail]
)
stripe_subscription = customer.subscriptions.create(:plan => #plan.id)
#payment = current_user.payments.new(customer_id: customer.id, card_exp_month: customer.sources[:data][0]['exp_month'], card_exp_year: customer.sources[:data][0]['exp_year'], card_id: customer.sources[:data][0].id, customer_subscription_id: stripe_subscription.id, plan_id: #plan.id)
#payment.save!
if params[:plan_type] == "monthly"
current_user.build_user_plan(plan_id: #plan.id, plan_expiry: Date.today+1.months).save
elsif params[:plan_type] == "annual"
current_user.build_user_plan(plan_id: #plan.id, plan_expiry: Date.today+1.years).save
else
current_user.build_user_plan(plan_id: #plan.id).save
end
flash[:notice] = 'You have successfully got the premium.'
redirect_to root_path
rescue Stripe::StripeError => e
flash[:error] = e.message
redirect_to root_path
end
end
# Method responsbile for handling stripe webhooks
# reference https://stripe.com/docs/webhooks
def webhooks
begin
event_json = JSON.parse(request.body.read)
event_object = event_json['data']['object']
#refer event types here https://stripe.com/docs/api#event_types
case event_json['type']
# when 'invoice.payment_succeeded'
# handle_success_invoice event_object
# when 'invoice.payment_failed'
# handle_failure_invoice event_object
# when 'charge.failed'
# handle_failure_charge event_object
when 'customer.subscription.deleted'
when 'customer.subscription.updated'
end
rescue Exception => ex
render :json => {:status => 422, :error => "Webhook call failed"}
return
end
render :json => {:status => 200}
end
end
This is my the sign in button where the amount is charged.
<% if user_signed_in? %>
<%= form_tag subscription_payment_path, method: :post do %>
<%= hidden_field_tag :plan_type, "monthly" %>
<script class="stripe-button"
data-amount="1000"
data-currency="CAD"
data-email="<%= current_user.email %>"
data-key="<%= Rails.configuration.stripe[:publishable_key] %>"
src="https://checkout.stripe.com/checkout.js">
</script>
<% end %>
<% else %>
<a href="/users/sign_in" class="btn btn-neutral btn-round">
Subscribe
</a>
<% end %>
If you're seeing undefined method 'stripe_id' for nil:NilClass, then that means Plan.find_by_plan_type(params[:plan_type]) is likely returning nil. And you can't get the stripe_id of nil.
Are you sure that you have database records on your local machine, in particular Plan records with plan_type?
If your want to handle the scenario where a Plan is not found, given the plan_type, then you could try:
plan = Plan.find_by_plan_type(params[:plan_type])
raise SomeSortOfError if plan.nil? # make sure to rescue accordingly
stripe_id = plan.stripe_id

Looking to Submit Rails Form Only After Successful Stripe Checkout Payment

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.

How do I create friends?

I can't seem to get Amistad friendships to work correctly. I am getting the following error:
ActiveRecord::RecordNotFound in FriendshipsController#update
Couldn't find Friendship with id=29
I am also using devise and cancan. I followed the gem setup on the wiki pages and created my controller as described in this related post.
class FriendshipsController < ApplicationController
before_filter :authenticate_user!
def index
#friends = current_user.friends
#pending_invited_by = current_user.pending_invited_by
#pending_invited = current_user.pending_invited
end
def create
#friend = User.find(params[:user_id])
#friendship_created = current_user.invite(#friend)
if #friendship_created
redirect_to users_path, :notice => "Your friend request is pending"
end
end
def update
#friend = User.find(params[:user_id])
#friends = current_user.friends
#pending_invited_by = current_user.pending_invited_by
redirect_to users_path, :notice => "You are now friends!"
end
def destroy
#friend = User.find(params[:user_id])
#friendship = current_user.send(:find_any_friendship_with, #friend)
if #friendship
#friendship.delete
#removed = true
redirect_to users_path, :notice => "You are no longer friends!"
end
end
def createblock
#friend = User.find(params[:user_id])
current_user.block #friend
redirect_to users_path, :notice => "You have blocked #{#friend.first_name}"
end
end
I loop though my users in the following manner checking the current status of the user and offering appropriate actions.
<% if current_user.friend_with? user %>
<%= link_to "Unfriend", friend_path(user), :method => "delete", :class => 'btn btn-mini' %>
<% elsif current_user.invited? user %>
<span class="btn btn-mini disabled">Pending</span>
<% elsif user.invited? current_user %>
<%= link_to "Accept", friend_path(user), :method => "put", :class => 'request-approve btn btn-mini' %>
<%= link_to "Decline", friend_path(user), :method => "delete", :class => 'request-decline btn btn-mini' %>
<% else %>
<%= link_to "Add friend", friends_path(:user_id => user), :method => "post", :class => 'btn btn-mini' %>
<% end %>
Figured it would be useful to see what the friendships table looks like in my schema:
create_table "friendships", :force => true do |t|
t.integer "friendable_id"
t.integer "friend_id"
t.integer "blocker_id"
t.boolean "pending", :default => true
end
add_index "friendships", ["friendable_id", "friend_id"], :name => "index_friendships_on_friendable_id_and_friend_id", :unique => true
I understand the error just cannot figure out how this should change. I think my issue is that I am passing in a friend id and it is expecting a friendship id. My only problem with this solution is that every example or post I can find suggests passing user_id, like this post above where the answerer states the gem developer supplied the code he answers with.
What I feel like I need in my update method is to replace:
#friend = User.find(params[:id])
With this:
#friendship = Friendship.find_by_friend_id(params[:id])
EDIT
I can successfully request a friend, I just cannot accept or decline a friend. I a listing of users, clicking the "Add Friend" link creates the record in the friendships db correctly. If I log ins as that recently requested user and attempt to accept the request is when I get the above error. This also occurs if I attempt to decline the request.
The friends method you asked to see come with the amistad gem, here is the code for that method. As for my Ruby logs the section that displays the error was very long, so I have included it in this gist.
Given my current reputation, I can only post an answer instead of a comment to your question but as far as I can see from the controller sources you posted, you are not calling current_user.approve #friend in your update action.
I used this gem in one of my projects recently without running into any problems. The controller actions look like this:
def update
#friend = User.find_by_id(params[:id])
if current_user.approve #friend
redirect_to friendships_path, notice: t('.confirmation_successful')
else
redirect_to friendships_path, alert: t('.confirmation_unsuccessful')
end
end
def destroy
#friend = User.find_by_id(params[:id])
if current_user.remove_friendship #friend
redirect_to friendships_path, notice: t('.deletion_successful')
else
redirect_to friendships_path, alert: t('.deletion_unsuccessful')
end
end
I hope this helps.
The lookup problem is because you're passing ids inconsistently. In 3 of the links, you're passing the User object directly, which should automatically store the id in params[:id], which you can use in your action as User.find(params[:id]). But in your actions, you're extracting it from params[:user_id], which is empty. Not sure how you're getting an ID of 29 in your error message (or 32 or whatever), but...
If you change all your actions to expect params[:id] and switch the "Add friend" path link to pass in a User object the way the others already are, you should be passing the right data in the right parameter, and the lookup should straighten itself out.
Of course, as Wonky Business points out, you're not actually calling approve in your update method, so nothing will actually link, but at least you should be finding all your model objects.
As an aside, it appears from your paths you're remapping the friendship named routes to friend instead. That's muddling the issue because none of the RESTful routes are actually doing what their noun/verb combination implies: if you call friends_path(user) with a POST, there should be a new Friend object when you're done, but this controller is creating and destroying Friendship objects and leaving the Friend objects alone.
If you delete that alias and switch to friendship_path and so forth, then REST will actually be doing what it says it is: managing friendship objects.
Hope that helps!

Persisting form input over login

I have this app where a user can write a review for a school. A user must sign in with Facebook to save a review. The problem is if a user is unsigned and writes a review, then signs in with Facebook they have to write the same review again.
I am trying to fix this by storing the review data form in sessions, but I cant quite make it work.
What is the proper rails way to do this?
ReviewForm:
<%= form_for [#school, Review.new] do |f| %>
<%= f.text_area :content %>
<% if current_user %>
<%= f.submit 'Save my review', :class => "btn" %>
<% else %>
<%= f.submit 'Save my review and sign me into facebook', :class => "btn" %>
<% end %>
<%end %>
ReviewController
class ReviewsController < ApplicationController
before_filter :signed_in_user, only: [:create, :destroy]
def create
#school = School.find(params[:school_id])
#review = #school.reviews.new(params[:review])
#review.user_id = current_user.id
if #review.save
redirect_to #review.school, notice: "Review has been created."
else
render :new
end
end
def new
#school = School.find_by_id(params[:school_id])
#review = Review.new
end
def save_review(school, review, rating)
Review.create(:content => review, :school_id => school,
:user_id => current_user, :rating => rating)
end
private
def signed_in?
!current_user.nil?
end
def signed_in_user
unless signed_in?
# Save review data into sessions
session[:school] = School.find(params[:school_id])
session[:review] = params[:review]
session[:rating] = params[:rating]
# Login the user to facebook
redirect_to "/auth/facebook"
# After login save review data for user
save_review(session[:school], session[:review], session[:rating])
end
end
end
My understanding is that it's not "The Rails Way" to store things in the session besides really tiny stuff like a user token, etc. You can read more about that idea in The Rails 3 Way by Obie Fernandez.
I would recommend that you store reviews in the database right from the start and only "surface" the review after the review has been connected to a Facebook-authenticated user. If you have any curiosities regarding how to accomplish that, I'm happy to elaborate.
Edit: here's a little sample code. First I'd take care of associating users with reviews, for "permanent" storage. You could just add a user_id to the review table, but it would probably be null most of the time, and that seems sloppy to me:
$ rails g model UserReview review_id:references, user_id:references
Then I'd create a user_session_review table with a review_id and a user_session_token. This is for "temporary" storage:
$ rails g model UserSessionReview review_id:integer, user_session_token:string
Then when a user signs up, associate any "temporary" reviews with that user:
class User
has_many :user_reviews
has_many :reviews, through: :user_reviews
has_many :user_session_reviews
def associate_reviews_from_token(user_session_token)
temp_reviews = UserSessionReview.find_all_by_user_session_token(user_session_token)
temp_reviews.each do |temp_review|
user_reviews.create!(review_id: temp_review.review_id)
temp_review.destroy
end
end
end
So in your controller, you might do
class UsersController < ApplicationController
def create
# some stuff
#user.associate_reviews_from_token(cookies[:user_session_token])
end
end
You'll of course have to read between the lines a little bit, but I think that should get you going.
Edit 2: To delete old abandoned reviews, I'd do something like this:
class UserSessionReview
scope :old, -> { where('created_at < ?', Time.zone.now - 1.month) }
end
Then, in a cron job:
UserSessionReview.old.destroy_all
You should save the review in the create sessions action (which is not included in your question). Assuming you are using omniauth, you can add something on the action that handles the callback
# review controller
def signed_in_user
unless signed_in?
# Save review data into sessions
session[:school] = School.find(params[:school_id])
session[:review] = params[:review]
session[:rating] = params[:rating]
# Login the user to facebook
redirect_to "/auth/facebook"
end
end
# callback to login the user
def handle_callback
# do your thing here to login the user
# once you have the user logged in
if signed_in?
if session[:school] && session[:review] && session[:rating] # or just 1 check
Review.create(
content: session.delete(:review),
school_id: session.delete(:school),
user_id: current_user.id,
rating: session.delete(:rating)
)
#redirect_to somewhere
end
end
end
I used delete so the session will be cleared of these values.
UPDATE: since you're using a session controller
class SessionsController < ApplicationController
def create
if user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
if session[:school] && session[:review] && session[:rating] # or just 1 check
review = Review.new
review.content = session.delete(:review)
review.school_id = session.delete(:school)
review.user_id = user.id
review.rating = session.delete(:rating)
review.save
end
end
redirect_to :back
end

Multi-step form, but redirect to another site in between?

I'm trying to implement the same concept Ryan Bates discussed in his Railscast here: http://railscasts.com/episodes/217-multistep-forms into my user sign up form.
Basically the 3 steps are as follows:
Step 1. Fill out user details
Step 2: Go to an external URL to complete billing details, which will redirect them back to this user sign up form when they're done.
Step 3: Confirm and submit data
Currently my workaround is to create the user first, then redirect to the external URL to complete payment after #user.save, but I would like to use this multistep form to prevent the user from getting saved if they don't complete payment first.
Would appreciate it if you could point me towards a direction.. thanks.
UPDATE:
My user controller:
def new
session[:user_params] ||= {}
if params[:plan_id].present?
#user = User.new(session[:user_params] && :plan_id => params[:plan_id])
else
#user = User.new(session[:user_params])
end
#user.current_step = session[:user_step]
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #user }
end
end
def create
session[:user_params].deep_merge!(params[:user]) if params[:user]
#user = User.new(session[:user_params])
#user.current_step = session[:user_step]
if #user.valid?
if params[:back_button]
#user.previous_step
elsif #user.last_step?
#user.save
elsif #user.billing_step?
#user.next_step
redirect_to("external.com") and return
else
#user.next_step
end
session[:user_step] = #user.current_step
end
if #user.new_record?
render "new"
else
session[:user_step] = session[:user_params] = nil
flash[:success] = "User saved"
redirect_to dashboard_url
end
end
My user model:
validates_presence_of :username, :first_name, :last_name, :if => lambda { |u| u.current_step == "initial" }
attr_writer :current_step
def current_step
#current_step || steps.first
end
def steps
%w[initial billing confirmation]
end
def next_step
self.current_step = steps[steps.index(current_step)+1]
end
def previous_step
self.current_step = steps[steps.index(current_step)-1]
end
def first_step?
current_step == steps.first
end
def last_step?
current_step == steps.last
end
def billing_step?
current_step == steps.second
end
My new user view:
<%= form_for #user, :url => {:action => :create, :plan_id => params[:plan_id] } do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= render "#{#user.current_step}_step", :f => f %>
<% end %>
The problem is in the user controller with the #user.billing_step?
what I need to happen after hitting the submit button is: 1.) add a step (using #user.next_step) AND 2.) redirect to an external URL.
That way, when users go back to "users/new" they're already at the final step of confirmation.
If I don't add the "and return" command at the end of the redirect, I get the "Render and/or redirect were called multiple times in this action" error. If I do, Rails doesn't add a new step and takes me back to the billing step (step 2) of the whole thing.
You could store your user's currently input form data into a session for while they are visiting the third party service to complete their payment.
If the payment service is good, they will provide you with a hash of information (or something of the sort) with details on the user's actions (whether they paid or not) and then you can use the saved session information to complete the registration if everything is ok.
Let user fill out mandatory fields
Have a button titled "Proceed to Payment"
Have the button save the user information (via ajax or submit) into the session, and redirect to the payment gateway if all required fields are so far ok
Redirect back to the order form and and check for the session values and then the payment details that the service returned (which they usually do in the return callback url)
Create user if everything is OK
Update:
I would definitely do something like this with your code:
elsif #user.billing_step?
#user.next_step
session[:user_step] = #user.current_step
return redirect_to("external.com")
else
This sets the session to the correct step and redirects, and when the user comes back they will be taken to another conditional (thus it should work?), and keeps updating the step value on the other conditions as well, since the lower session variable isn't removed.
Does this help at all? This is probably how I would do it.

Resources