Introducing Payola
I released an open source Rails engine named Payola that you can drop into any application to have robust, reliable self-hosted Stripe payments up and running with just a little bit of fuss.
When you're setting up Stripe in a Rails application there are a lot of choices you have to make. What should you use for webhooks? Do you even need webhooks? How much information should you keep in your database? Should you use Checkout or do you need to design your own form? Amongst all of these choices, you also have to decide what libraries you want to use, and boy howdy are there even more options here. Koudoku, StripeEvent, Stripe::Rails, not to mention commercial options like Gumroad, Plasso, and Cargo.
One of the reasons why I wrote my book Mastering Modern Payments: Using Stripe with Rails is to help you narrow down that set of choices to something reasonable, and I think it does a very good job of it. That said, even if you're using my book you still have to actually write the code to implement Stripe. Not that it's a lot of code, but it's basically always the same.
For the recent relaunch of MMP I decided to actually sit down and formalize the "hows" laid out in the book into a Rails engine. Anyone can drop Payola into an application and have payments going without too much drama, and notably none of the choices outlined above.
What Payola Does
Payola provides a complete solution for accepting Stripe payments within a Rails application. It is focused on selling items one at a time and includes a drop-in partial for setting up a Stripe Checkout button, along with a complete server-side asynchronous processing system for completing payments with Stripe.
To see a demo, click on or inspect one of the buttons in the Packages section on the MMP website.
Design
I designed Payola to be robust in the face of failure, whether that means network failure, bugs, Stripe API slowness, or anything in between. It consists of a few moving pieces:
- User-facing Javascript that gets a token from Stripe and sends it, along with other Payment-related information, to the backend.
- An async backend that uses a state machine to track a payment through every stage.
- Integration with your application via a model concern and ActiveSupport notifications when interesting things happen.
Payola has built-in support for Sidekiq and Sucker Punch, but it's easy to add new backend worker systems which makes it even easier to adapt to your current system.
Payola should also be transparent to your customers. There should never be a time when they actually see a Payola URL in their address bar, nor should they ever see something Payola branded. From a buyer's perspective it should be your site selling the product, not Payola.
How A Click Becomes A Charge
Here are all of the steps in a successful charge:
- Buyer clicks a checkout button, which spawns a Stripe Checkout lightbox.
- Buyer enters their card information and clicks the Pay button.
- Stripe validates their card information and creates a token.
- Token is passed the Payola's javascript, which in turn POSTs it to the backend.
- Payola creates a
Payola::Sale
object with the token and sets it topending
state. - Payola queues a background job to create a Stripe charge for the corresponding sale and passes the sale's
guid
attribute back to the JS. - The buyer's browser disables the button and polls Payola every 500ms asking for the state of their charge.
- The background job calls the
Payola.charge_verifier
callback, then creates the charge with Stripe, then sends thepayola.<product>.sale.finished
notification to your application. - The background job finally sets the sale's state to
finished
, which is picked up by the JS. - The user's browser redirects to
/payola/confirm/<guid>
and then is immediately redirected to whatever the product'sredirect_url
returns, defaulting to/
.
A charge will typically fail in the background job (step 8), either because the charge_verifier
rejects it or Stripe rejects it. In that case, the sale is set to errored
, the error message is set in the error
column, and the Payola JS shows it in a (customizable) div
after re-enabling the button. Your application will also receive a payola.<product>.sale.errored
notification.
Installation
Installing Payola in your app is just a few steps. First, add the gem:
gem 'payola-payments'
Then, run the installer and install the migrations:
$ rails g payola:install
$ rake db:migrate
Next add the Payola::Sellable
concern to the models you want to sell:
class SomeProduct < ActiveRecord::Base
include Payola::Sellable
end
Your model needs three attributes:
permalink
: a unique, human readable namename
: a short descriptionprice
: the price for the sellable in whatever format Stripe expects. For USD this is cents, for other currencies it could be different.
By default Payola will use USD but you can change that by adding an optional currency
method to your sellable model. This can either be a fixed method if you're only using one currency, or it can be a column in the database if your products come in multiple currencies.
Optionally, you can provide a method named redirect_path
. This method takes a Payola::Sale
instance and returns a path where Payola should redirect the browser after a successful purchase. If you don't provide this Payola will redirect to '/'.
Finally, use the checkout
partial to render a checkout button:
<%= render 'payola/transactions/checkout',
sellable: SomeProduct.first %>
While the checkout
partial has reasonable defaults for getting off the ground, you can customize basically every aspect of it. See the documentation for details.
Event Handling
Stripe has excellent support for webhook events and the StripeEvent gem does an excellent job handling them. Payola thinly wraps StripeEvent and adds a bit of behavior. To receive events, just set up a webhook url in your Stripe account settings that points at https://www.example.com/payola/events
. Then, configure an event listener in config/initializers/payola.rb
:
Payola.configure do |config|
config.subscribe 'charge.succeeded' do |event|
puts "whoohoo!"
end
end
Payola adds deduplification to StripeEvent. It records every event_id
that comes in and will only ever process an event once. If you'd like to further filter events, you can set event_filter
, which should either return a Stripe::Event
or nil
if you'd like to stop processing.
In addition to Stripe's webhooks, you can listen for three special events:
payola.<underscored product class>.payment.finished
payola.<underscored product class>.payment.failed
payola.<underscored product class>.payment.refunded
These are invoked with the corresponding Payola::Sale
, not a Stripe::Event
and are executed in-line with the async processing chain, which means you can do things like create a user or send an email before the user-facing javascript returns.
What's Next
Currently Payola does not handle subscriptions or marketplaces, so those will be next on the list. Along with those I'll be adding support for custom forms instead of the Checkout button. I'm also planning on building out a Pro version that will include priority support and a bunch of pre-built integrations for external systems like Mailchimp, Mixpanel, Infusionsoft, and more.
Here's some more links to Payola stuff:
- Payola GitHub
- Submit an issue for bug reports or feature requests
- Google group for annoucements (very low traffic)
Send me an email if you'd like to talk about Payola or Payola Pro.