Stripe payments for your Rails app
17 Nov 2014
Today is release day for Payola v1.2.0 and the big watch word is subscriptions. So now that they’re here, how do you use subscriptions with Payola? It’s easy:
Here’s the steps that Payola goes through when your customer clicks your Subscribe button:
SubscriptionPlan#redirect_path
(defaulting to /
).Let’s go through how you setup Payola subscriptions in more detail:
Add payola-payments
to your Gemfile
:
gem 'payola-payments', '>= 1.2.4'
Now run bundler and install the migrations:
$ bundle install
$ rails g payola:install
$ rake db:migrate
Payola assumes you have your Stripe keys in environment variables named STRIPE_SECRET_KEY
and STRIPE_PUBLISHABLE_KEY
. Make sure to set those up or configure them in Payola’s initializer.
Payola tracks everything about a subscription for you, but you have to tell it about your plans. For that, create a Plan
model and include the appropriate Payola module:
$ rails g model Plan \
stripe_id:string \
name:string \
amount:integer \
interval:string
$ rake db:migrate
Now open up app/models/plan.rb
and add the concern:
class Plan < ActiveRecord::Base
include Payola::Plan
end
At this point you should be able to open up a console and add a plan:
$ rails console
irb(main):001:0> Plan.create(name: 'Test Plan', stripe_id: 'test-plan', amount: 100, interval: 'month', interval_count: 1)
This will create a Plan
object as well as create a plan within Stripe.
Payola currently only supports custom forms for subscriptions but it makes it as easy as possible to do. Let’s create a simple controller first at app/controllers/subscriptions_controller.rb
:
class SubscriptionsController < ApplicationController
# bring in the `render_payola_status` helper.
include Payola::StatusBehavior
def new
@plan = Plan.first
end
def create
# do any required setup here, including finding or creating the owner object
owner = current_user # this is just an example for Devise
# set your plan in the params hash
params[:plan] = SubscriptionPlan.find_by(id: params[:plan_id])
# call Payola::CreateSubscription
subscription = Payola::CreateSubscription.call(params, owner)
# Render the status json that Payola's javascript expects
render_payola_status(subscription)
end
end
This is what our form is going to look like:
Here’s what the view looks like, using Bootstrap 3 for layout:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.payment/1.2.1/jquery.payment.min.js"></script>
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<div class="well">
<%= form_tag('/subscriptions',
role: 'form',
class: 'payola-onestep-subscription-form',
'data-payola-base-path' => '/payola',
'data-payola-error-selector' => '.payola-error',
'data-payola-plan-type' => @plan.plan_class,
'data-payola-plan-id' => @plan.id) do %>
<div class="form-group">
<label>Email Address</label>
<input type="email"
name="email"
data-payola="email"
placeholder="you@example.com"
class="form-control"></input>
</div>
<div class="form-group">
<label>Card number</label>
<input type="text"
size="20"
data-stripe="number"
class="card-number form-control"
placeholder="**** **** **** ****"></input>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Exp</label>
<input type="text"
size="8"
class="exp-date form-control"
placeholder="MM / YY"></input>
<input type="hidden" data-stripe="exp_month"></input>
<input type="hidden" data-stripe="exp_year"></input>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>CVC</label>
<input type="text"
size="4"
data-stripe="cvc"
class="form-control"
placeholder="***"></input>
</div>
</div>
</div>
<div class="text-center">
<input type="submit" value="Subscribe" class="btn btn-info btn-lg"></input>
</div>
<div class="alert alert-warning payola-error" style="display: none"></div>
<% end %>
</div>
</div>
</div>
Payola’s subscription form behavior is triggered by your form having the class payola-onestep-subscription-form
. After that, you just mark up the data destined for Stripe with data-stripe
attributes.
This particular form has a few additional niceties provided by the jquery.payment library from Stripe. Here’s the javascript for the form, in app/assets/javascripts/form.js
:
$(function() {
$('.exp-date').payment('formatCardExpiry');
$('input[data-stripe="number"]').payment('formatCardNumber');
$('input[data-stripe="cvc"]').payment('formatCardCVC');
$('.exp-date').on('keyup', function() {
var e = $('.exp-date').first();
var out = $.payment.cardExpiryVal(e.val());
$('input[data-stripe="exp_month"]').val(out.month);
$('input[data-stripe="exp_year"]').val(out.year);
});
$('.card-number').on('keyup', function() {
var e = $('.card-number').first();
var type = $.payment.cardType(e.val());
var img = "card.png";
switch(type) {
case "visa":
img = "visa.png";
break;
case "mastercard":
img = "mastercard.png";
break;
case "discover":
img = "discover.png";
break;
case "amex":
img = "amex.png";
break;
}
e.css('background-image', 'url(/images/' + img + ')');
});
});
This does three separate things. First, it sets up formatters on the form’s expiration date, card, and cvc number fields. Next, the JS sets up another event handler that uses jquery.payment
’s cardExpiryVal
function to parse the card expiration date into a month and a year, and sets the hidden fields to that value.
Finally, it sets up an event handler that changes the credit card icon based on what type of card the customer is entering. The particular images that I’m using are from a very nice set of flat icons off of Creative Market. Shopify put out a free set a while back as well.
In order to make the icon actually show up in the right place, you need to add this small CSS snippet:
.card-number {
background-image: url(/images/card.png);
background-repeat: no-repeat;
background-size: 30px;
background-position: right 10px center;
}
(card.png
is the default green card image.)
Ok, so now we have a form, but what exactly happens here? Payola Subscriptions works like this:
data-stripe
to Stripe to get a card token./subscriptions
, which creates a Payola::Subscription
using the service class Payola::CreateSubscription
. This automatically launches a background job that attempts to create a Stripe::Customer
. The controller then returns a JSON status hash back to Payola’s javascript handler.redirect_path
on Plan
returns, or if it’s not implemented /
. Payola will set flash[:notice]
appropriately.At this point you have a functional subscription system. Payola provides all sorts of hooks and notifications that you can use to trigger additional application-specific behavior, which you can read all about in the README.
P.S.: Payola Pro is an add-on to Payola that provides priority email support, pre-built integrations with several 3rd party services, Stripe Connect marketplace support, along with a lawyer-friendly commercial license. You can read all about it at payola.io/pro, and all of the modules have documentation on the Payola wiki.
P.P.S.: I want to thank Jeremy Green who has been instrumental in driving this forward. Subscriptions would probably be another month away without Jeremy’s help.