Skip to content

Blogging On Rails

Everything on Rails!

Using Stimulus to Manage Stripe Elements

If you use Stripe to accept payments, you’ve likely had to insert the little Javascript snippet needed to wire up their library with your page. With a little refactoring, it works really well with a Stimulus controller. This setup simplifies your whole development process, and keeps your Javascript neatly stored together.

Stripe’s JS & HTML

Stripe’s main library is a snippet you can include in your page’s head:

<script src="https://js.stripe.com/v3/"></script>

Then Stripe provides a small snippet that you can use to enable the credit card field on your page.

This snippet adds a couple event listeners, and manages the form submission. This is a perfect place to drop in our controller. The Stimulus controller will:

  • help keep all our JS code in one place,
  • be much more readable, and
  • be better organized with targets and the event callbacks.

The HTML is pretty simple, as their library does the work of creating the credit card form.

<form action="/charge" method="post" id="payment-form">
  <div class="form-row">
    <label for="card-element">
      Credit or debit card
    </label>
    <div id="card-element">
      <!-- A Stripe Element will be inserted here. -->
    </div>

    <!-- Used to display form errors. -->
    <div id="card-errors" role="alert"></div>
  </div>

  <button>Submit Payment</button>
</form>

Refactoring into a Stimulus Controller

The HTML needs to be updated for the targets that the controller will use to update the page, and submit the form.

The form tag will have the publishable key provided by Stripe. It will also be the element on the page attached to the controller.

<%= form_for charge_path(@donation.key), method: :patch, data: { controller: 'stripe', target: 'stripe.form', 'stripe-key': Rails.configuration.stripe[:publishable_key] }  do |f| %>

    <div class="form-row">
        <label for="card-element">Credit or debit card</label>
        <div data-target="stripe.cardElement">
              <!-- A Stripe Element will be inserted here. -->
        </div>

           <!-- Used to display form errors. -->
        <div data-target="stripe.cardErrors" role="alert"></div>
       </div>

    <%= f.submit "Submit Payment" %>
<% end %>

A target is added for stripe.cardElement, which will display the card entry form. Another target added is stripe.cardErrors, which will display any errors from the Stripe API.

The stripe_controller.js follows the provided Stripe example, but is refactored to match Stimulus’ conventions.

import {  Controller } from "stimulus"

export default class extends Controller {
    static targets = [ 'cardElement', 'cardErrors', 'form' ]

    connect() {
        var stripe = Stripe(this.data.get('key'));
        var elements = stripe.elements();
        var style = {
            base: {
                color: '#32325d',
                fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
                fontSmoothing: 'antialiased',
                fontSize: '16px',
                '::placeholder': {
                    color: '#aab7c4'
                }
            },
            invalid: {
                color: '#fa755a',
                iconColor: '#fa755a'
            }
        };

        var card = elements.create('card', {
            style: style
        });

        card.mount(this.cardElementTarget);

        // Handle real-time validation errors from the card Element.
        let controller = this;
        card.addEventListener('change', function (event) {
            var displayError = controller.cardErrorsTarget;
            if (event.error) {
                displayError.textContent = event.error.message;
            } else {
                displayError.textContent = '';
            }
        });

        // Handle form submission.
        var form = controller.formTarget;
        form.addEventListener('submit', function (event) {
            event.preventDefault();

            stripe.createToken(card).then(function (result) {
                if (result.error) {
                    // Inform the user if there was an error.
                    var errorElement = this.cardErrorsTarget;
                    errorElement.textContent = result.error.message;
                } else {
                    // Send the token to your server.
                    controller.stripeTokenHandler(result.token);
                }
            });
        });


    }

    // Submit the form with the token ID.
    stripeTokenHandler(token) {
        // Insert the token ID into the form so it gets submitted to the server
        var form = this.formTarget;
        var hiddenInput = document.createElement('input');
        hiddenInput.setAttribute('type', 'hidden');
        hiddenInput.setAttribute('name', 'stripeToken');
        hiddenInput.setAttribute('value', token.id);
        form.appendChild(hiddenInput);

        // Submit the form
        form.submit();
    }
}

The connect() function sets up the Stripe element, loading in the API passed in via a HTML data-attribute. A callback for real time validations is added, which will add any errors onto the cardErrorsTarget element. The form’s submission is watched, and stopped, so that Stripe can process the card and return a card token. The returned Stripe token is then inserted into the form, and the form is submitted normally.

The Possibilities are endless

I hope this example helps if you’re integrating Stripe payments into your web app, and want to keep all your Javascript in one spot. You can also see how many third party snippets can be refactored into a Stimulus controller, resulting in cleaner code organization.

Comments or Questions? Let me know how your experience went below.

Want To Learn More?

Try out some more of my Stimulus.js Tutorials.

Make Interactivity Default 

Make your web app interactive now with easy to use and simple Stimulus.js controllers. 

Enter your email and get a free sample of my Stimulus Tutorials ebook.

We won’t send you spam. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *

Copyright © 2024 John Beatty. All rights reserved.