Rootstrap Get Started →

Insights

How to Accept Payments With Stripe (React, Rails Examples)

How to Accept Payments With Stripe (React, Rails Examples)

Here at Rootstrap, we don't believe in reinventing the wheel. We believe in making things simple and high quality of course.

In this blog post, you’ll learn how to start working with Stripe and quickly have fully functioning online payments in your apps.

WHY STRIPE?

Pros

  • Easy to implement and use
  • Fast to develop, so your client will be happy
  • Solves most of your usual payment problems, so you don't lose time or clients (even worst)
  • Amazing dashboard with a lot of capabilities so your clients financial team can work along with you

Cons

  • Expensive (high % fee)

INSTALLATION

This post assumes you already created a Stripe account and so you have access to dashboard and its configuration. These examples are for Rails and React, but this also works for many other web and custom mobile app frameworks.

RAILS

  • Add these two gems:
  • Stripe to achieve the integration
  • Stripe Testing to test your integration, you don't want to end up writing lots of mocking classes, right?
  • Configure your keys & version from the Stripe dashboard
# config/intializers/stripe.rb
Rails.configuration.stripe = {
  publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
  secret_key: ENV['STRIPE_SECRET_KEY']
}
Stripe.api_key = Rails.configuration.stripe[:secret_key]

REACT

  • Add this package Stripe
  • Configure your App to use the same api key as for rails (make sure it is the same, as you start moving between envs you may forget it). Remember that there is a testing key and a live one.

Add an env file to store your keys

# .env.dev

STRIPE_KEY="pk_test_TYooMQauvdEDq54NiTphI7jx"

Add your Stripe wrapper

import React from 'react';
import { Elements, StripeProvider } from 'react-stripe-elements';

const withStripe = (WrappedComponent) => {
  const Stripe = props => (
    <StripeProvider apiKey={process.env.stripe_key}>
      <Elements
        fonts={[{
          cssSrc: 'https://fonts.googleapis.com/css?family=Roboto:300,300i,400,500,600'
        }]}
      >
        <WrappedComponent {...props} />
      </Elements>
    </StripeProvider>
  );

  return Stripe;
};

export default withStripe;

START USING PAYMENTS WITH STRIPE

CREDIT CARDS

REACT - DO YOURSELF A FAVOR AND USE THE EXISTING COMPONENT

I'm not a fan of reinventing the wheel by any means, the design these components provide is more than enough for 99% of the apps you will be building. But if you insist, be prepared to spend 2 weeks dealing with details instead of 2 days.

import {
  CardNumberElement,
  CardExpiryElement,
  CardCVCElement,
  injectStripe
} from 'react-stripe-elements';
import uuid from 'uuid/v1';

/* Your other imports for a usual form */

class BillingForm extends Component {
  constructor() {
    super();
    this.state = {
        cardInputKey: uuid()
      };
    this.onSubmit = this.onSubmit.bind(this);
  }
  async onSubmit(result) {
    const { stripe, submitBilling, shopId, initialValues } = this.props;
    const data = result.toJS();

    /* AT THIS POINT THE CC IS CREATED AT STRIPE AND YOU NEED TO TELL YOUR BACKEND ABOUT IT */
    const { token, error } = await stripe.createToken(decamelizeKeys(data));

    if (error) {
      throw new SubmissionError({
        _error: error.message
      });
    }

    /* HERE WE WERE SUBMITING AND LENDING THE INFO TO THE BACKEND */
    await submitBilling(shopId, camelizeKeys(token), initialValues.get('name'));
  }

  render() {
    /* all your consts */
    return (
      ....
      <form onSubmit={handleSubmit(this.onSubmit)} className="p-3">
        /* The rest of your user profile form */
        /* CC real info */
        <div className="col-lg-3 offset-1">
          <div className="form-group">
            <label className="form-control-label">Card On File</label>
            <div>{brand && last4 ? `${brand} ending in ${last4}` : 'none'}</div>
          </div>
          <div className="form-group">
            <label className="form-control-label">Card Number</label>
            <CardNumberElement key={`cardNumber${cardInputKey}`} className="form-control" />
          </div>
          <div className="form-group">
            <label className="form-control-label">Expiry</label>
            <CardExpiryElement key={`cardExpiry${cardInputKey}`} className="form-control wd-80" />
          </div>
          <div className="form-group">
            <label className="form-control-label">CVC</label>
            <CardCVCElement key={`cardCvc${cardInputKey}`} className="form-control wd-80" />
          </div>
        </div>
      </form>
    )
  }
}

export default injectStripe(reduxForm({
  ...
})(BillingForm));
RAILS - DON'T TRY TO STORE ALL THE INFO (IT'S ILEGAL)

You will tend to store more credit card info that you need. The only info that you need to store in your database (for basic usage) is:

  • [.c-inline-code]customer_id[.c-inline-code] : Stripe customer identifier that you will store in your User for example
  • [.c-inline-code]card_id[.c-inline-code] : Stripe card identifier

The [.c-inline-code]token_id[.c-inline-code] you will get from your frontend is a short lived token that is only needed for an atomic operation.

Add a [.c-inline-code]customer_id[.c-inline-code] field to your user (or Shop in next example). Add a [.c-inline-code]card_id[.c-inline-code] to your user (or Shop in next example).

Now take this service example (Shopify page example):

# app/services/stripe_service.rb

require 'stripe'

class StripeService
  class StripeException < StandardError
  end

  attr_reader :shop

  def initialize(shop)
    @shop = shop
  end

  def add_card(token_id, email, name)
    create_customer(email, name) unless customer.present?
    card = customer.sources.create(source: token_id)
    shop.update!(card_id: card.id)
  end

  def credit_card_info
    card = shop.stripe_token
    customer.sources.retrieve(card) if card
  end

  def update_credit_card(token_id)
    card = customer.sources.create(source: token_id)
    card.save
    card_id = card.id
    customer.default_source = card_id
    customer.save
    shop.update!(card_id: card_id)
  end

  def customer
    customer_id = shop.customer_id
    return unless customer_id.present?

    @customer ||= Stripe::Customer.retrieve(customer_id)
  end

  private

  def create_customer(email, name)
    customer_params = {
      email: email,
      description: "#{shop.name} #{name}"
    }
    @customer = Stripe::Customer.create(customer_params)
    shop.update!(customer_id: @customer.id)
  end
end

And this simple controller:

# app/controllers/api/v1/credit_cards_controller.rb
module Api
  module V1
    class CreditCardsController < Api::V1::ApiController
      helper_method :shop

      def index
        service = StripeService.new(shop)
        @card = service.credit_card_info
        @customer = service.customer
      end

      def create
        StripeService.new(shop).add_card(token_id, email, customer_name) if token_id
        head :no_content
      end

      def update
        begin
          StripeService.new(shop).update_credit_card(token_id)
        rescue StripeService::StripeException => ex
          return render_not_found(ex.message)
        end
        head :no_content
      end

      private

      def shop
        @shop ||= current_shop
      end

      def token_json
        params[:token]
      end

      def token_id
        token_json['id']
      end

      def email
        token_json['email']
      end

      def customer_name
        token_json['name']
      end
    end
  end
end

And that's all! You can start charging your users now! All fraud detections and customer service actions can be managed directly from Stripe's dashboard.

SUBSCRIPTIONS

To create a subscription you need to define it, then create a product in Stripe (this last one is really clear looking at the dashboard, so I'm not going to explain it)

CREATING THE SUBSCRIPTION
# app/models/subscription.rb
class Subscription < ActiveRecord::Base
  belongs_to :user
  belongs_to :purchase_plan # this can be optional if you have annual or monthly plans for example
  has_many :subscription_items, dependent: :destroy # I'm going to explain this later

  enum status: ['define_your_possible_statuses']
end

In that model you will store attribures like: [.c-inline-code]expires_at[.c-inline-code], [.c-inline-code]type[.c-inline-code] or even [.c-inline-code]provider[.c-inline-code] if later you want to extend to other providers like PayPal or Apple Pay

Finally to create them on Stripe is quite simple:

# app/services/stripe_service.rb

def create_subscription
  Stripe::Subscription.create(
    customer: customer.id,
    plan: subs_plan_id, # this is the id of your plan (eg: monthly, annual, etc)
    coupon: discount_code # if you have any (check COUPONS section below to understand them in more detail)
  )
end

COUPONS

Coupons are the abstract concept of [.c-inline-code]30% off[.c-inline-code] for example, when you apply that coupon to a user that's called a [.c-inline-code]discount[.c-inline-code]. So you should define some discounts on Stripe and store their ids in your database to apply them to users. There are two types of coupons [.c-inline-code]percentage[.c-inline-code] & [.c-inline-code]fixed amount[.c-inline-code], and any of them can be one time only or have the capability to be applied multiple times. So when you try to apply a coupon to a subscription, for example, remember that it can fail if you reached the maximum usage number.

Another useful case that is worth mentioning is to apply a coupon to a user, this means that they will have a positive balance for any future invoice (be careful if you charge users with multiple products)

SUBSCRIPTION ITEMS

These are your billing items, so for the case of a web subscription, you will just have 1 subscription item. For specific cases like an amazon cart or any complicated use case (where you have multiple items being added to purchase) is where you have to start considering adding some specific logic to your app. I won't get really into detail about this, I just wanted to show the general concept behind this, maybe I will write more in detail in a future post.

RENEWALS

Don't overthink it, there is a webhook for most of your use cases. But for this specific need you can configure the following events:

  • customer.subscription.updated This event happens every time a susbscription is updated according to this documentation
  • customer.subscription.deleted As simple as it sounds, it tells you when a subscription is canceled so you can take the actions needed in your app (possibly disable the associated account)
  • invoice.payment_succeeded This is a really important one! It tells us when payment is actually accepted by the credit card provider (some times there can be fraud or the payment could get declined)

WEBHOOKS

There are a lot of them and they will solve most of your problems, the only downcase is the headache trying to understand which exactly to use. I'm sorry to disappoint you if you reached here trying to answer this question but up to now I only know this page that explains the different existing webhooks and what they do. The other option is when you go to create a webhook from the developer's Stripe dashboard, they explain a bit more in detail what each event does.

RECOMMENDATIONS FOR FURTHER PAYMENT IMPLEMENTATION

Keep these Stripe documentation pages as your friends:

Sometimes there are two or even three ways of solving a problem, so consider this and take your time to analyze each requirement properly before you start coding.

CONCLUSION

You can easily add online payments to your app and test it in just 1 week (or so), that's amazing! The other amazing thing is that you can start managing most of the daily based situations like fraud of disputes just from the dashboard (you don't need to keep coding).

The difficult part of this is when you start adding more concrete and detailed transactions and supporting multiple transfer types (like bank account transfers instead of just Visa or MasterCard). So if you liked this post and want to know more don't hesitate to leave some comments asking for it!

← All insights