I am currently using rails-stripe-membership-saas to set up my SaaS site. I currently have plans set up in Stripe that corresponds with rails to sign users up for a certain plan, all of which have a 14-day trial.
I need help figuring out how to not require the user to input their credit card on sign-up and then use a Stripe webhook to send them a request or somehow alert the user that they need to put their credit card in to continue their subscription or else they won't be able to log back in.
Whenever I remove the code from the form for requiring the cards, the form stops working - and if even if it worked, I'm not sure how to implement the stripe webhooks, which I believe I may have to use event-stripe gem for.
If anyone could point me in the right direction it would be much appreciated. If any other code/information is required, just let me know and I'll be sure to post it.
Here is the form I am using to sign users up: registrations/new.html.erb
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :role => 'form',
:class => 'payola-onestep-subscription-form',
'data-payola-base-path' => payola_path,
'data-payola-plan-type' => resource.plan.plan_class,
'data-payola-plan-id' => resource.plan.id}) do |f| %>
<div>
<span id="error_explanation" class="payola-payment-error"></span>
</div>
<div class="form-group">
<h3 class="text-center"><i>Try Free for 14 Days!</i></h3> <br>
<%= f.label 'Subscription plan' %>
<%= f.collection_select(:plan_id, Plan.all, :id, :name) %>
</div>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, :autofocus => true, class: 'form-control', data: { payola: 'email' } %>
</div>
<div class="form-group">
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
</div>
<div class="form-group">
<%= label_tag :card_number, "Credit Card Number" %>
<%= text_field_tag :card_number, nil, name: nil, class: 'form-control', data: { stripe: 'number' } %>
</div>
<div class="form-group">
<%= label_tag :card_code, "Card Security Code" %>
<%= text_field_tag :card_code, nil, name: nil, class: 'form-control', data: { stripe: 'cvc' } %>
</div>
<br />
<div class="form-group">
<%= label_tag :card_month, "Card Expiry" %>
<%= select_month nil, { use_two_digit_numbers: true}, { name: nil, data: { stripe: 'exp-month' } } %>
<%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+10}, { name: nil, data: { stripe: 'exp-year' } } %>
</div>
<div class="text-center">
<%= f.submit 'Sign up', :class => 'button right' %>
</div>
<% end %>
Here are the plans that I currently have laid out.
class CreatePlanService
def call
p1 = Plan.where(name: 'Yearly').first_or_initialize do |p|
p.amount = 36000
p.interval = 'year'
p.stripe_id = 'yearly'
end
p1.save!(:validate => false)
p2 = Plan.where(name: 'Monthly').first_or_initialize do |p|
p.amount = 3000
p.interval = 'month'
p.stripe_id = 'monthly'
end
p2.save!(:validate => false)
end
end
User model
class User < ActiveRecord::Base
enum role: [:user, :admin, :yearly, :monthly]
after_initialize :set_default_role, :if => :new_record?
after_initialize :set_default_plan, :if => :new_record?
# after_create :sign_up_for_mailing_list
belongs_to :plan
validates_associated :plan
has_many :dashboards
has_many :cardtools
def set_default_role
self.role ||= :user
end
def set_default_plan
self.plan ||= Plan.last
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def sign_up_for_mailing_list
MailingListSignupJob.perform_later(self)
end
def subscribe
mailchimp = Gibbon::Request.new(api_key: Rails.application.secrets.mailchimp_api_key)
list_id = Rails.application.secrets.mailchimp_list_id
result = mailchimp.lists(list_id).members.create(
body: {
email_address: self.email,
status: 'subscribed'
})
Rails.logger.info("Subscribed #{self.email} to MailChimp") if result
end
end
Registrations controller
class RegistrationsController < Devise::RegistrationsController
include Payola::StatusBehavior
before_action :cancel_subscription, only: [:destroy]
def new
build_resource({})
unless params[:plan].nil?
# If broken, follow console https://github.com/RailsApps/rails-stripe-membership-saas/issues/127
@plan = Plan.find_by!(stripe_id: params[:plan])
resource.plan = @plan
end
yield resource if block_given?
respond_with self.resource
end
def create
build_resource(sign_up_params)
plan = Plan.find_by!(id: params[:user][:plan_id].to_i)
resource.role = User.roles[plan.stripe_id] unless resource.admin?
resource.save
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
subscribe
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
subscribe
end
else
clean_up_passwords resource
render json:
{error: resource.errors.full_messages.to_sentence},
status: 400
end
end
def change_plan
plan = Plan.find_by!(id: params[:user][:plan_id].to_i)
unless plan == current_user.plan
role = User.roles[plan.stripe_id]
if current_user.update_attributes!(plan: plan, role: role)
subscription = Payola::Subscription.find_by!(owner_id: current_user.id)
Payola::ChangeSubscriptionPlan.call(subscription, plan)
redirect_to edit_user_registration_path, :notice => "Plan changed."
else
flash[:alert] = 'Unable to change plan.'
build_resource
render :edit
end
end
end
private
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, :plan_id)
end
def subscribe
return if resource.admin?
params[:plan] = current_user.plan
subscription = Payola::CreateSubscription.call(params, current_user)
current_user.save
render_payola_status(subscription)
end
def cancel_subscription
subscription = Payola::Subscription.find_by!(owner_id: current_user.id, state: 'active')
Payola::CancelSubscription.call(subscription)
end
end
Removing the Card Attribute from that form definite will make it crash.
Payola assumes you want to create an immediately charging subscription. This means free plans and plans with free trials require a credit card at sign up. So getting around this might be:
stripe-rails
gem instead of the payola library. Stripe doesn't require a card for free plans or plans with a trial.OR
As for webhooks, I see that possible. When a credit card expires or a monthly transaction is declined, Stripe will automatically retry a recurring payment after it fails. After a number of attempts (set in your Stripe account settings), Stripe will cancel the subscription. Your application needs to know to deny access for a subscriber with an expired account. Stripe provides webhooks to communicate events to you (for details, see the Stripe Webhooks Documentation).
A Stripe webhook is an HTTP request from Stripe’s servers to your site, containing JSON data that provides data about the event, plus an event id that can be used to retrieve the data from the Stripe server. The example application responds to Stripe webhooks, using an implementation provided by Danny Whalen’s
stripe_event
gem, which is provided with the Payola gem. The application responds to webhook requests at https://www.example.com/payola/events.The example application only responds to
“customer.subscription.deleted”
events. You can customize the application to respond to other events (such as sending a thank you email in response to an“invoice.payment_succeeded”
event).For webhooks to work, you must visit your Stripe dashboard at https://manage.stripe.com/#account/webhooks and add the URL for your application, such as https://www.example.com/payola/events.