Stripe Subscriptions with Koudoku

by Brian Rossetti on November 27, 2016

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.

Ruby On Rails

Let's Get In Touch!


Our best work gets done when we can work face-to-face with you.

770-317-4866