DEV Community

Cover image for Online payments made SIMPLE - How to work with Stripe
fedeagripa
fedeagripa

Posted on

Online payments made SIMPLE - How to work with Stripe

Online payments made SIMPLE - How to work with Stripe

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

1) 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)

2) INSTALLATION

This post assumes you already created a Stripe account and so you have access to dashboard and its configuration.
Installation

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]
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

3) START USING PAYMENTS WITH STRIPE

Start

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));
Enter fullscreen mode Exit fullscreen mode
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:

  • customer_id : Stripe customer identifier that you will store in your User for example
  • card_id : Stripe card identifier

The token_id you will get from your frontend is a short lived token that is only needed for an atomic operation.

Add a customer_id field to your user (or Shop in next example).
Add a card_id 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

In that model you will store attributes like: expires_at, type or even provider 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
Enter fullscreen mode Exit fullscreen mode

COUPONS

Coupons are the abstract concept of 30% off for example, when you apply that coupon to a user that's called a discount.
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 percentage & fixed amount, 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.

4) SPECIAL 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.

5) CONCLUSIONS

Easy
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! or even text me :)

Top comments (1)

Collapse
 
techt01ia profile image
Techtolia • Edited

If you are looking for a solution to increase your international sales by accepting Bancontact, EPS, iDEAL and up to 25+ payment methods with a single integration, check out our demo store techtolia-dev.myshopify.com/ - techtolia.com/ShopifyStripe/