Last Update: May 10, 2017
TL;DR: Nothing is simple. Error messages are not always relevant. Dive deep to find the real error.
I’m a Ruby programmer at Kettul. My business partner Tim lives in Ireland. He’s a designer and front-end developer. It would be valuable for him and for our business if he could handle some of the programming tasks. So, we started pair programming. After trying some of the cloud-based IDEs, I eventually setup a Digital Ocean Ubuntu server with zsh, rvm, emacs, and all of my favorite plugins. We use tmate to share a terminal session. This allows us to see and edit the same files as if we were working side-by-side. I’d like the freedom to move to an iPad or Chromebook without losing any productivity. So, the move to Digital Ocean had multiple benefits.
Now, we can have Capybara click Stripe’s “pay” button, complete the form, and test that the code in the target controller action gets executed. I quickly learned how hard it is to debug in a headless browser, and the process of hooking up what was supposed to be extremely simple made me realize how complex it really is.
We wrote a spec that logged in a user and visited a page for a Tip record created by that user and not flagged as “paid”. The spec instructed Capybara to click Stripe’s payment button, fill in the modal payment form, and click the submit button before testing the outcome.
For this test to pass, the following had to be true:
- The user was logged in.
- The user had permission to access the page that contained the form.
- The spec visited the correct page.
- The Stripe modal form displayed.
- The spec correctly filled and submitted the form.
- The rails form had the correct target URL and method (a post request to /tips/:id/payment).
- A route was properly configured to map to the target controller action TipsController#payment.
- The user was authorized to pay for the tip record.
- The code in the controller action does what we want it to do.
Keep in mind, we’re not even at the step of charging a card via the Stripe API. We’re just testing that the Stripe form submits and that the code in the controller action executes.
When we ran the spec, it failed. It looked like it was able to find and complete the Stripe form, but it wasn’t getting to the controller action. In fact, it wasn’t submitting the form. I learned this by sticking a byebug call in a before_action in ApplicationController and inspecting the params of each request that took place in the spec.
At this point, Tim and I broke from out pairing session. We only started pairing a few days earlier. I had enough of a time convincing him of the merits of TDD. Now, I was about to subject him to potentially hours of yak shaving and debugging. I didn’t want to turn off his newfound interest in learning Rails.
Next, I looked at the output of the specs. Without Poltergeist configured to spew all of it’s debug messages to the terminal, I was left with a single, duplicated error that appeared to come from phantomjs. Stripe’s https iframe was blocked from accessing the app’s non-https page. I assumed this was preventing the Stripe token from being set and the form from being submitted. This made sense as - most of the time - the error messages in RSpec’s output actually point you to the problem.
I started looking into Poltergeist/PhantomJS configurations that would solve what appeared to be an SSL error. I turned off PhantomJS’s web security, set it to allow all protocols, etc. Same error.
I ran the specs again and jumped into byebug immediately after the form submission code. I found that Poltergeist has a network_traffic method that displays a running list of all requests that occur in the browser. The last request was a response from Stripe that the checkout form was invalid. Why? Because my public key wasn’t set. I looked at page.body and sure enough it was blank! But, I was sending it! We use dotenv in our test and dev environments. It’s in the Gemfile. We have a .env file with our stripe keys. We have a stripe initializer that moves the env vars to Rails.config.stripe. The code doesn’t raise any errors.
I added a byebug call in a controller action and checked the rails config. Sure enough, it was blank. I checked the ENV variables. They were nil. They weren’t being set. Was dotenv not doing it’s job? It turns out we were using the dotenv gem instead of the dotenv-rails gem. I swapped out the gems. Ran the spec. It passed!
I commented out the SSL server, still passed.
I deleted the key and crt files, still passed.
Now, we can move on to making sure we’re getting a Stripe token and actually hooking up the payment.
I felt dumb after spending so much time on this issue. However, I learned so much about dependencies, setting up an SSL server, testing, and just how much has to go right for what appears to be simple code to actually work. Some people knock rails for being too magical, but I actually appreciate it. Most of the time, everything just works. When it doesn’t, you get a free lesson in how complex web development actually is.