I’m nearing completion on my latest project, Training Sleuth, and I’ve once again decided to use Stripe as my payment processor. I used Stripe on my previous project, Rhino SchoolTracker, and have absolutely nothing negative to say about it :).
While with Rhino SchoolTracker I tested my stripe payments manually, I decided I wanted to write some automated integration tests this time around (particularly since I’m using Scala for this project which has a decidedly slower and more unwieldy edit-compile-test loop than Ruby does).
The Stripe API is actually split into two parts, a client side javascript library (stripe.js), and a server-side API available in several of the popular server-side web languages (php, java, ruby, python, and javascript (for node.js) last time I checked). Anyway, the basic concept goes like this:
- You serve an HTML payment form (with fields for CC number, etc.) from your server to the client.
- When the client submits the form, instead of sending it to your sever, you use stripe.js to grab the form data and sends it to Stripe’s servers, which will validate the card and return a unique token (or an error message in case of invalid/expired credit cards, etc.) via an ajax request.
- Once you have the stripe card token, you send it up to your server, do whatever processing you need to do on your end (grant access to your site, record an order, etc.), and then submit a charge to Stripe using the Stripe API.
The key feature of all of this is that the user’s credit card information never touches your server, so you don’t need to worry about PCI compliance and all the headaches that go with it (yes, stripe does require you to use SSL, and despite their best efforts it is possible to mis-configure your server in such a way as to expose user payment info if you don’t know what you’re doing).
Now Stripe offers a test mode, which is what we’ll be using here, with a variety of test card numbers to simulate various conditions (successful charge, declined card, expired card, etc.). The main problem I ran into writing automated tests in Scala was that I needed to use stripe.js to generate a card token before I could interact with the server-side (Java) API.
Enter Rhino, a Javascript interpreter for Java. Using Rhino, I was able to whip up some quick-and-dirty javascript to generate a stripe token and call it from Scala. Of course, Rhino alone wasn’t enough — I also needed to bring in Envjs and create some basic HTML to simulate a browser environment for stripe.js.
First, here’s my stripetest.js:
Packages.org.mozilla.javascript.Context.getCurrentContext().setOptimizationLevel(-1); load("resources/env.rhino.js"); load("https://js.stripe.com/v2/"); //Stripe REALLY wants some sort of HTML loaded, so here you go: window.location = "resources/stripetest.html" Stripe.setPublishableKey('pk_test_PUT-YOUR-STRIPE-TEST-KEY-HERE'); var cardNumber var token = "" Stripe.card.createToken({ number: cardNumber, cvc: '123', exp_month: '12', exp_year: '2016' },function(status, response){ this.token = response['id']; });
And you need to provide some basic HTML, I created a file called ‘stripetest.html’ which merely contained this:
<!DOCTYPE html> <html> <head> <title></title> </head> <body> </body> </html>
Simple, but this was enough to get things working.
I dropped these files (along with env.rhino.js which I obtained from the Envjs website) into my test/resources folder.
With all of that in place, I was able to write some specs2 tests:
import org.mozilla.javascript.{Context, ContextFactory} import org.mozilla.javascript.tools.shell.{Main, Global} import org.specs2.mutable._ class SubscriptionServiceSpec extends Specification { //Get Stripe token via Stripe.js val cx: Context = ContextFactory.getGlobal.enterContext() cx.setOptimizationLevel(-1) cx.setLanguageVersion(Context.VERSION_1_5) val global: Global = Main.getGlobal global.init(cx) "'SubscriptionServiceActor'" should{ "handle Create Subscription Request with bad credit card" in{ //stripe test pattern to simulate bad card number global.put("cardNumber", global, "4000000000000002") Main.processSource(cx, "resources/stripetest.js") val badToken: String = global.get("token", global).toString //Now test and make sure you handle a bad credit card correctly } "handle Create Subscription Request with valid credit card" in{ //stripe test pattern to simulate valid credit card number global.put("cardNumber", global, "4242424242424242") Main.processSource(cx, "resources/stripetest.js") val stripeToken: String = global.get("token", global).toString //Now test that you can actually take money from customers 🙂 } }
There you go, kind of painful to get set up, but definitely nice to have.