fedeagripa
Posted on June 9, 2020
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.
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.
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;
3) 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:
-
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
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 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
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 documentationcustomer.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
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 :)
Posted on June 9, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.