Recently we were working on a project in which we needed a subscription based payment system to manage different user plans. This was an MVP so we did not necessarily want to roll our own subscription system if we didnt have to. Enter Koudoku, a Rails based subscription engine that integrates into Stripe out of the box and uses Stripe Event to hook into Stripe’s beatifully simple subscription plan system.
Here’s the description from the Koudoku repo:
Koudoku Description - Robust subscription support for Ruby on Rails apps using Stripe, including out-of-the-box pricing pages, payment pages, and subscription management for your customers. Also makes it easy to manage logic related to new subscriptions, upgrades, downgrades, cancellations, payment failures, and streamlines hooking up notifications, metrics logging
If you look under the hood, there is not much to this gem. It comes with an excellent generator and a SubscriptionsController for handling the different scenarios around managing a subscription (upgrades/downgrades/new/cancelations/etc). The bulk of the handling in the gem happens in the concern Koudoku::Subscription within the processing!
method.
module Koudoku::Subscription
extend ActiveSupport::Concern
included do
def processing!
# if their package level has changed ..
if changing_plans?
prepare_for_plan_change
# and a customer exists in stripe ..
if stripe_id.present?
# fetch the customer.
customer = Stripe::Customer.retrieve(self.stripe_id)
# if a new plan has been selected
if self.plan.present?
..
end
..
end
end
this is essentially a 125 line method that is the culmination of a bunch of if statements. One interesting thing you can see here are access methods that can be defined on the model to add custom behavior to each type of user model that includes the Koudoku::Subscription
. As seen in the docs:
#####Implementing Logging, Notifications, etc.
The included module defines the following empty "template methods" which you're able to provide an implementation for in Subscription:
prepare_for_plan_change
prepare_for_new_subscription
prepare_for_upgrade
prepare_for_downgrade
prepare_for_cancelation
prepare_for_card_update
finalize_plan_change!
finalize_new_subscription!
finalize_upgrade!
finalize_downgrade!
finalize_cancelation!
finalize_card_update!
card_was_declined
Be sure to include a call to super in each of your implementations, especially if you're using multiple concerns to break all this logic into smaller pieces.
Between prepare_for_* and finalize_*, so far I've used finalize_* almost exclusively. The difference is that prepare_for_* runs before we settle things with Stripe, and finalize_* runs after everything is settled in Stripe. For that reason, please be sure not to implement anything in finalize_* implementations that might cause issues with ActiveRecord saving the updated state of the subscription.
so if you want to access the engine inside your app to inject custom behavior, koudoku allows this through these different prepare_for
and finalize
methods, which you would define on the individual user model that the concern is included in.
##koudoku Implementation
The authors of this gem made it quiet simple to implement subscriptions through the use of this gem. simply follow the docs:
add the gem to your gemfile.rb
gem 'koudoku'
run the install generator below where vendor is the user model you want to add subscriptions. in the app we worked on, we needed subscriptions for the vendor model
that creates multiple models, files, and migrations:
> rails g koudoku:install vealer
create config/initializers/koudoku.rb
generate model
invoke active_record
create db/migrate/20161110123404_create_subscriptions.rb
create app/models/subscription.rb
invoke rspec
create spec/models/subscription_spec.rb
invoke factory_girl
create spec/factories/subscriptions.rb
conflict app/models/subscription.rb
Overwrite /Users/brianrossetti/RailsProjects/LD_Studios/chrome_capital/app/models/subscription.rb? (enter "h" for help) [Ynaqdh] y
force app/models/subscription.rb
generate model
invoke active_record
create db/migrate/20161110123422_create_plans.rb
create app/models/plan.rb
invoke rspec
create spec/models/plan_spec.rb
invoke factory_girl
create spec/factories/plans.rb
conflict app/models/plan.rb
Overwrite /Users/brianrossetti/RailsProjects/LD_Studios/chrome_capital/app/models/plan.rb? (enter "h" for help) [Ynaqdh] y
force app/models/plan.rb
generate model coupon code:string free_trial_length:string
invoke active_record
create db/migrate/20161110123439_create_coupons.rb
create app/models/coupon.rb
invoke rspec
create spec/models/coupon_spec.rb
invoke factory_girl
create spec/factories/coupons.rb
conflict app/models/coupon.rb
Overwrite /Users/brianrossetti/RailsProjects/LD_Studios/chrome_capital/app/models/coupon.rb? (enter "h" for help) [Ynaqdh] y
force app/models/coupon.rb
insert app/models/dealer.rb
create app/views/koudoku/subscriptions/_social_proof.html.erb
route
# Added by Koudoku.
mount Koudoku::Engine, at: 'koudoku'
scope module: 'koudoku' do
get 'pricing' => 'subscriptions#index', as: 'pricing'
end
> rake db:migrate
== 20161110123404 CreateSubscriptions: migrating ==============================
-- create_table(:subscriptions)
-> 0.0460s
== 20161110123404 CreateSubscriptions: migrated (0.0461s) =====================
== 20161110123422 CreatePlans: migrating ======================================
-- create_table(:plans)
-> 0.0058s
== 20161110123422 CreatePlans: migrated (0.0059s) =============================
== 20161110123439 CreateCoupons: migrating ====================================
-- create_table(:coupons)
-> 0.0037s
== 20161110123439 CreateCoupons: migrated (0.0038s) ===========================
This generator did alot! lets look at our Vendor model and Subscription models:
class Vendor < ApplicationRecord
# Added by Koudoku.
has_one :subscription
..
end
class Subscription < ActiveRecord::Base
include Koudoku::Subscription
belongs_to :vendor
belongs_to :coupon
end
in app/views/layouts/application.html.erb
add the following tag at the end of your head tag:
<%= yield :koudoku %>
this line allows koudoku to inject stripe.js into your app so the payment page can implement Stripe in a PCI compliant way. if we look at the generated views, we can see the use of stripe:
in koudoku/subscriptions/_card.html.erb
<% content_for :koudoku do %>
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<% end %>
<%= form_for @subscription, url: url, html: {id: 'payment-form', class: 'form-horizontal'} do |f| %>
..
<% end %>
<script type="text/javascript">
// All this code taken from Stripe's own examples at:
// https://stripe.com/docs/tutorials/forms .
function stripeResponseHandler(status, response) {
if (response.error) {
// show the errors on the form
$(".payment-errors").text(response.error.message).show();
$(".submit-button").removeAttr("disabled");
} else {
var form$ = $("#payment-form");
// token contains id, last4, and card type
// insert the token into the form so it gets submitted to the server
form$.append("<input type='hidden' name='subscription[credit_card_token]' value='" + response['id'] + "'/>");
form$.append("<input type='hidden' name='subscription[last_four]' value='" + response['last4'] + "'/>");
form$.append("<input type='hidden' name='subscription[card_type]' value='" + response['card_type'] + "'/>");
// and submit
form$.get(0).submit();
}
}
$(document).ready(function() {
Stripe.setPublishableKey("<%= Koudoku.stripe_publishable_key %>");
// By default, don't show errors.
$(".payment-errors").hide()
$("#payment-form").submit(function(event) {
// disable the submit button to prevent repeated clicks
$('.submit-button').attr("disabled", "disabled");
Stripe.createToken({
number: $('.card-number').val(),
cvc: $('.card-cvc').val(),
exp_month: $('.card-expiry-month').val(),
exp_year: $('.card-expiry-year').val()
}, stripeResponseHandler);
// prevent the form from submitting with the default action
return false;
});
});
</script>
This bit of javascript takes the card number, converts it to a Stripe Token, and inserts the credit_card_token
into the form to be submitted to your server for processing.
next add the stripe test keys to your environment variables, the docs show this step from the terminal:
export STRIPE_PUBLISHABLE_KEY=pk_0CJwDH9sdh98f79FDHDOjdiOxQob0
export STRIPE_SECRET_KEY=sk_0CJwFDIUshdfh97JDJOjZ5OIDjOCH
the generator will create the config file which you need to call the environment variable in /config/initializers/koudoku.rb
:
Koudoku.setup do |config|
config.subscriptions_owned_by = :user
config.stripe_publishable_key = ENV['STRIPE_PUBLISHABLE_KEY']
config.stripe_secret_key = ENV['STRIPE_SECRET_KEY']
# add webhooks
config.subscribe 'charge.failed', YourChargeFailed
end
now we have to create the Subscription Plan in Stripe:
The we have to mirror those details in the Plan
Model of our rails app. from the terminal run:
Plan.create({
name: 'vendor-silver',
price: 20.00,
interval: 'month',
stripe_id: '1',
features: ['1 Project', '1 Page', '1 User', '1 Organization'].join("\n\n"),
display_order: 1
})
link the user to the subscriptions screen by placing the following path in the relative part of your app where you want the user to access the subscription plans to choose:
<%= link_to 'Pricing', main_app.pricing_path %>
##Drawing Routes
You may run into routing errors when using koudoku, something like:
ActionView::Template::Error: undefined local variable or method `root_path' for #<#<Class:0x007fdbe84faa58>:0x007fdbe84f9810>
The ActionView::Template::Error
occurs becasue koudoku is an engine inside your rails app, so you must tell the rails app which application the routes your accessing belongs to by adding the namespace before calling the route. Mounting an Engine gives you access to route namespaces of main_app
and my_engine
to correct this error. for instance, we used the koudoku.subscription_path
in the RegistrationController
like so:
class Vendor::RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def after_sign_up_path_for(resource)
koudoku.new_subscription_path(resource, plan: Plan.first.id)
end
end
This redirected any vendor that registers to the new subscription path which gives them an option of paying for a plan or continuing with a free account.
For example, the navagation on the our layout looks like (haml syntax):
.container
/ Brand and toggle get grouped for better mobile display
.row{style: "position: relative;"}
.white-text.pull-right{style: "position: absolute; right: 0;"}
.col-lg-12=render "shared/login_display"
=link_to main_app.root_path, class: "navbar-brand" do
%i.fa.fa-coffee
EventGig
/ Collect the nav links, forms, and other content for toggling
%ul.menu
=render partial: "shared/dashboard_link"
-elsif current_vendor
%li=link_to "Gigs", main_app.gigs_path
%li=link_to "Settings", main_app.edit_vendor_path(current_vendor)
%li=link_to "Conversations", main_app.conversations_path
%li=render "shared/logout"
-else
%li=link_to "Login", main_app.user_login_index_path
%li=link_to "Register", main_app.user_signup_index_path
/.ul.menu
/.container
after you have implemented the routing to fit your app, you can then style the views and pricing table via the generated views in the koudoku
subdirectory under view, and you should be good to accept subscription payments via your stripe account.
Customizing Views
you can generate and customize the subscription views by running:
> rails g koudoku:views
create app/views/koudoku/subscriptions/_card.html.erb
create app/views/koudoku/subscriptions/_pricing_table.html.erb
create app/views/koudoku/subscriptions/_social_proof.html.erb
create app/views/koudoku/subscriptions/_stripe_js.html.erb
create app/views/koudoku/subscriptions/edit.html.erb
create app/views/koudoku/subscriptions/index.html.erb
create app/views/koudoku/subscriptions/new.html.erb
create app/views/koudoku/subscriptions/show.html.erb
create app/views/koudoku/subscriptions/unauthorized.html.erb
By adding the views directly to your app, you can add whatever customization you want to and style the views according to your sites specific css.