How I Integrated Stripe Terminal with Ruby on Rails
Tags: stripe, stripeterminal, rails
Published: 2018-11-25 22:58:41

---
title: How I Integrated Stripe Terminal with Ruby on Rails
published: true
description: An in depth look at how I implemented Stripe Terminal within a multi-tenant Rails application.
tags: stripe, stripeterminal, rails
cover_image: https://thepracticaldev.s3.amazonaws.com/i/gby33jf3hmn6bd779o9o.jpg
---

Today we’re going to cover how I integrated [Stripe Terminal](https://stripe.com/terminal) into a multitenant Rails application. [Stripe Terminal](https://stripe.com/terminal) is a combination of SDK, and physical hardware to swipe credit cards. This is a huge moment in web development because Stripe Terminal offers a JS based SDK for use with web applications. 

It plays a key role in my software platform [Sport Keeper](https://sport-keeper.com/?utm_source=dev_blog&utm_medium=link&utm_campaign=blog&utm_content=stripe-terminal-guide) - a business management platform that provides automated billing and member management for places like climbing gyms and skate parks. It contains a P.O.S. component that utilizes Stripe Terminal.

Before we begin we’re going to start by assuming you have a rails app already setup. You’ll also want the [Developer Kit](https://stripe.com/docs/terminal/developer-kit/verifone) from Stripe which includes the Verifone P400 P.O.S. hardware unit. 

The developer kit costs $300. It was a purchase that was hard to stomach, especially being a bootstrapped startup. Terminal is in invitation only beta, so if you pester them enough times they may invite you and give you the instructions on how to purchase the developer kit.

Heads up! My implementation also utilizes [Stripe Connect](https://stripe.com/connect), so all API calls have the `stripe_account` as an additional parameter. If you're not utilizing Stripe Connect, just remove the added param. 

### Workflow Overview
It’s a good idea to get familiar with how Stripe Terminal works, and how this will integrate into a web app. It took me a couple days to really get my head around the workflow, because there are quite a few steps needed to take before actually swiping a card. I'm hoping the number of steps is reduced before it's moved out of beta. Essentially, this is what we’re looking at from a high level overview:

* Register the card reader.
* Fetch connection token
* Discover readers to connect to
* Connect to the reader
* Create Payment Intent
* Collect payment method
* Confirm Payment Intent
* Capture Payment Intent

You can also reference the [official documentation](https://stripe.com/docs/terminal/js). Let's dive in!

### Add JS SDK
This is the easiest part of the whole process. Add the script.

```html
<script src="https://js.stripe.com/terminal/v1/"></script>
```

### Register Reader
Make a call to the API endpoint with the registration code, and a label at minimum. In my app I did this outside the flow of the P.O.S. process because it only has to happen once. 

I created a CardReader model and I keep them in sync with Stripe. This lets the user create, edit readers (the current version of Stripes API does not allow for destroying readers yet), and set a specific reader as the preferred reader to connect to. When the user creates a new reader, we just send this off to the Stripe  API to register it.

```ruby
  class RegisterReader

    def initialize(params)
      @stripe_account       = params[:stripe_account]
      @reader_params        = params[:reader_params]
    end

    def call
      reader = Stripe::Terminal::Reader.create(@reader_params,{stripe_account:@stripe_account})
    rescue Stripe::StripeError => e
      OpenStruct.new({success?: false, error: e})
    else
      OpenStruct.new({success?: true, payload: reader})
    end

  end
```

Once registered you can call the following in a controller to list the available readers for reference. As of this writing, there isn't a U.I. yet for Terminal Readers within the Stripe Dashboard.

```ruby
  class RetrieveReaders

    def initialize(params)
      @stripe_account    = params[:stripe_account]
    end

    def call
      readers = Stripe::Terminal::Reader.list({},{stripe_account:@stripe_account})
    rescue Stripe::StripeError => e
      OpenStruct.new({success?: false, error: e})
    else
      OpenStruct.new({success?: true, payload: readers})
    end

  end
```

### Setup Token Endpoint
From this point forward we’ll be working in the context of a form. When Stripe Terminal completes the payment, we want to submit the form with the charge id.

I setup a separate controller to handle everything related to Stripe Terminal. This first method grabs a connection token from Stripe. It's a short-lived token that's used with a single Terminal session.

```ruby
  def fetch_connection_token
    result = StripeServices::FetchConnectionToken.new(stripe_account:current_tenant_stripe_account).call
    render json: result.payload
  end
```

`current_tenant_stripe_account` is a custom method in my app that looks up the Stripe Connect account ID based on the current tenant. The controller method above, calls the service method below, which in turn returns the token.

```ruby
module StripeServices

  class FetchConnectionToken

    def initialize(params)
      @stripe_account       = params[:stripe_account]
    end

    def call
      token = Stripe::Terminal::ConnectionToken.create({},{stripe_account:@stripe_account})
    rescue Stripe::StripeError => e
      OpenStruct.new({success?: false, error: e})
    else
      OpenStruct.new({success?: true, payload: token})
    end

  end

end
```

Again if you're not using Stripe Connect, don't pass the `stripe_account` param.

### Fetch Connection Token
In a JS file add the code below. We're calling our controller method above and returning the `client_secret` from the service.

```javascript
  function fetchConnectionToken() {
    return fetch('/terminal/fetch-connection-token.json').then(response => response.json()).then(response =>
      response.secret
    );
  }
```

How or when you fetch the token is up to you. There’s no documentation or guides on how to do this in the context of a web app yet. This is the first! For my app I have an invoice form with multiple payment methods. I added a new payment method of Terminal. When the cashier clicks the terminal payment option, we then fetch the connection token to begin the process. 

With the token in hand, we can instantiate terminal.

```javascript
  var terminal = StripeTerminal.create({
    // fetchConnectionToken must be a function that returns a promise
    onFetchConnectionToken: fetchConnectionToken,
    onUnexpectedReaderDisconnect: unexpectedDisconnect,
  });
```

Heads up, on May 3rd 2019 this function was updated to stay inline with Terminal docs. They now require the callback onUnexpectedReaderDisconnect to be in place, with the functions job to "notify the user that the reader disconnected. You can also include a way to attempt to reconnect to a reader."

### Discover Readers to Connect To
So again, when the cashier clicks the Terminal option, we fetch the connection token, instantiate Terminal, then list the available readers for the cashier to connect to in a side panel. This assumes that the facility where the P.O.S. unit is installed has multiple readers. 

If there’s only one card reader, you can connect to it automatically by stashing the id in local storage. See the Stripe docs for that. For the sake of this tutorial, we’ll have the cashier select the reader to connect to.

Add the following to your JS file.

```javascript
  terminal.discoverReaders().then(function(discoverResult) {
    if (discoverResult.error) {
      console.log('Failed to discover: ', discoverResult.error);
    } else if (discoverResult.discoveredReaders.length === 0) {
      console.log('No available readers.');
    } else {
      buildReaderList(discoverResult.discoveredReaders)
    }
  });
```

`buildReaderList()` is a custom JS function builds a list of readers. Each of them have a "connect" button. How you build that logic in JS is up to you. All the cool kids use React, Vue, and whatever else. I just use jQuery as it's already being loaded. Super simple. Here's what this process looks like visually:

![animation showing how readers are displayed](https://thepracticaldev.s3.amazonaws.com/i/3hr7hvp3im6xsv1itkjm.gif)

### Connect to Reader
When we click "connect" on a reader, we're going to call `connectReader()` in Stripe Terminal. Here's that click event.

```javascript
  $(document).delegate('.js--select-reader', 'click', function(e){
    e.preventDefault()
    connectReader($(this).data('reader'))
  })
```

I'm just stashing the reader object as a data attribute. When the connect button is clicked, we pass that to `connectReader()` to connect to the reader unit.

```javascript
  function connectReader(selectedReader) {
    terminal.connectReader(selectedReader).then(function(connectResult) {
      console.log(connectResult)
      if (connectResult.error) {
         console.log('Failed to connect: ', connectResult.error);
       } else {
         createPaymentIntent()
         //console.log('Connected to reader: ',connectResult.connection.reader.label);
       }
    });
  }
```

Heads up, I had some trouble initially connecting. The process would take 15 seconds or so, then timeout and I’d get hit back with an error. I was fairly certain the setup was correct, so I hopped onto the #stripe IRC channel and had a chat with their engineers. Super helpful group! 

Apparently Chrome has sporadic issues resolving DNS, so I was instructed to update my Macs local DNS to connect to Googles DNS. If you Google search "change Mac DNS Google DNS", you'll find out how to do it. 

### Create Payment Intent
After the reader connects, we immediately create an intent to collect a payment in the form of a [payment intent](https://stripe.com/docs/api/payment_intents). This includes the amount, and the customer (optional). If you’re going to associate it with a customer, you’ll need to spin off another service to get that customer id before creating the payment intent. I haven't even done this myself yet, but there's a @todo shown in the blocks below where that should happen. 

In my app I'm creating the payment intent via a POST call to our terminal controller. If successful it will send back the payment intent secret that we can use to collect the payment.

```javascript
  function createPaymentIntent() {
    $.rails.refreshCSRFTokens()
    $.ajax({
      url: '/terminal/create-payment-intent.json',
      type: 'POST',
      beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
      data: {charge_amount:$('#js--invoice-total').attr('data-amount')},
      success: function(response) {
        collectPaymentMethod(response.client_secret)
      }
    });
  }
```

I use Turbolinks in my apps, which means you often encounter issues with stale CSRF tokens when working with JS. This comes in the form of the error "can't verify CSRF token". To fix that I'm calling `$.rails.refreshCSRFTokens()` prior to POST'ing.

The POST call above hits our controller method below. You can also see where you would pass in the customer as an additional param to Stripe if needed.

```ruby
  # @todo pass customer here
  def create_payment_intent
    result = StripeServices::CreatePaymentIntent.new(intent_params:{
      amount: params[:charge_amount],
      currency: 'usd',
      payment_method_types: ['card_present'],
      capture_method: 'manual',
    }, stripe_account:current_tenant_stripe_account).call

    render json: result.payload
  end
```

Again with our service object pattern, the controller method above calls the service object below.

```ruby
module StripeServices

  class CreatePaymentIntent

    def initialize(params)
      @intent_params    = params[:intent_params]
      @stripe_account   = params[:stripe_account]
    end

    def call
      intent = Stripe::PaymentIntent.create(@intent_params,{stripe_account:@stripe_account})
    rescue Stripe::StripeError => e
      OpenStruct.new({success?: false, error: e})
    else
      OpenStruct.new({success?: true, payload: intent})
    end

  end

end
```

I know, it's a long process. Before we continue, let's recap what we did so far.

1. Registered the reader
2. Fetched connected token to instantiate Terminal.
3. Used the token to list the registered readers.
4. Used connectTerminal to connect to a card reader.
5. Once connected it immediate creates a payment intent
6. We now have the secret from the payment intent.

### Collect Payment Method
At this point we need to collect the payment from the customer. 

```javascript
  function collectPaymentMethod(secret){
    terminal.collectPaymentMethod(secret).then(function(result) {
      if (result.error) {
        console.error("Collect payment method failed: " + result.error.message);
      } else {
        //console.log("Payment method: ", result.paymentIntent.payment_method)
        confirmPaymentIntent(result.paymentIntent)
      }
    });
  }
```

The customer will now be prompted to swipe their card. For testing purposes if the amount ends in .00 it'll be considered a success. An amount ending in .05 will simulate a card declined error. On an error we display it back to the cashier, and then reset the process. 

Using the the result, we call `confirmPaymentIntent()`.

### Process Payment (previously Confirm Payment Intent )
In the JS file we add the `confirmPaymentIntent()` function.

```javascript
  function confirmPaymentIntent(paymentIntent) {
    return terminal.processPayment(paymentIntent).then(function(confirmResult) {
      if (confirmResult.error) {
        console.log(confirmResult.error.message)
      } else if (confirmResult.paymentIntent) {
        capturePaymentIntent(paymentIntent)
      }
    });
  }
```

When the confirmation is successful, the funds are authorized, but not yet captured. For that we need to capture the payment intent.

### Capture Payment Intent
Starting with JS, add the below to your JS file. We're passing the payment intent from our confirmation in the previous step.

```javascript
  function capturePaymentIntent(paymentIntent){
    $.rails.refreshCSRFTokens()
    $.ajax({
      url: '/terminal/capture-payment-intent',
      type: 'POST',
      beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
      data: {payment_intent_id:paymentIntent.id},
      success: function(response) {
        // Append Transaction ID
        terminalFormHandler(response.charges.data[0].id)
      }
    });
  }
```

We call a new method `capture_payment_intent` in our Terminal controller.

```ruby
  def capture_payment_intent
    result = StripeServices::RetrievePaymentIntent.new(intent_id:params[:payment_intent_id], stripe_account:current_tenant_stripe_account).call
    render json: result.payload
  end
```

Which in turn calls the service object below.

```ruby
module StripeServices

  class RetrievePaymentIntent

    def initialize(params)
      @intent_id        = params[:intent_id]
      @stripe_account   = params[:stripe_account]
    end

    def call
      intent = Stripe::PaymentIntent.retrieve(@intent_id,{stripe_account:@stripe_account})
      intent.capture
    rescue Stripe::StripeError => e
      OpenStruct.new({success?: false, error: e})
    else
      OpenStruct.new({success?: true, payload: intent})
    end

  end

end
```

It sounds like a lot, but it happens pretty fast. Capturing the payment is the final step. You technically have up to 24hours to capture the payment after confirming it, but I just have this process happen all at once, at the very end. 

After a successful capture, we call another custom JS function `terminalFormHandler()` to handle the response. We fill in the charge ID into a hidden field, and submit the form.

### Summary
It took me about a day to really nail down the process, and I'm pretty happy about the end result. Right now I think there are too many steps, but this *is* beta so maybe that will change. Have you implemented Stripe Terminal in your web app? What are your thoughts on the start of the ePOS movement?

### Updates
Feb 21 2019 - allowed_source_types renamed to payment_method_types per API update  2019-02-19 

May 3 2019 - Terminal updates: 
- updated the SDK from sdk-b1.js to sdk-rc1.js
- added onUnexpectedReaderDisconnect callback to the Terminal instantiation block
- terminal.confirmPaymentIntent renamed to terminal.processPayment 

Dec 15 2019 - Terminal Updates
Stripe Terminal is now out of beta. Only one change necessary per the [migration guide](https://stripe.com/docs/terminal/beta-migration-guide) and [SDK Update Changes](https://stripe.com/docs/terminal/js-api-reference#changelog).
- updated source of JS SDK script