Design for Failure: Processing Payments with a Background Worker

Processing payments correctly is hard. This is one of the biggest lessons I've learned while writing my various SaaS projects. Stripe does everything they can to make it easy, with quick start guides and great documentation. One thing they really don't cover in the docs is what to do if your connection with their API fails for some reason. Processing payments inside a web request is asking for trouble, and the solution is to run them using a background job.

The Problem

Let's take Stripe's example code:

Stripe.api_key=ENV['STRIPE_API_KEY']# Get the credit card details submitted by the formtoken=params[:stripeToken]# Create the charge on Stripe's servers - this will charge the user's cardbegincharge=Stripe::Charge.create(:amount=>1000,# amount in cents, again:currency=>"usd",:card=>token,:description=>"payinguser@example.com")rescueStripe::CardError=>e# The card has been declinedend

Pretty straight-forward. Using the stripeToken that stripe.js inserted into your form, create a charge object. If this fails due to a CardError, you can safely assume that the customer's card got declined. Behind the scenes, Stripe::Charge makes an https call to Stripe's API. Typically, this completes almost immediately.

But what if it doesn't? The internet between your server and Stripe's could be slow or down. DNS resolution could be failing. There's a million reasons why this code could take awhile. Browsers typically have around a one minute timeout and application servers like Unicorn usually will kill the request after 30 seconds. That's a long time to keep the user waiting just to end up at an error page.

The Solution

The solution is to put the call to Stripe::Charge.create in a background job. This example is going to use a very simple background worker system named Sucker Punch. It runs in the same process as your web request but uses Celluloid to do things in a background thread.

Again, pretty straightforward. Sucker Punch will create an instance of your job class and call #perform on it with a hash of values that you pass in to the queue, which we'll get to in a second. We look up a Transaction record, initiate the charge, and capture any errors that happen along the way.

Transaction in this case is a simple ActiveRecord object with just a few attributes, just enough to capture what Stripe gives us:

The create method creates a new Transaction record, setting it's state to pending. It then queues the transaction to be processed by StripeCharger. The show method simply looks up the transaction and spits back some JSON. On your customer-facing page you'd do something like this: