Development

Integrate Stripe Checkout in Ruby on Rails Web Application

Mislav Kvesic

Sep 09 27 min read

F5137c1e 0d01 41d9 b445 f0408274fd61

Stripe is a service where retailers can create products with multiple prices, record customer data, and can keep track of what the customers are buying. You can use Stripe as a checkout tool, making your life as a business owner a bit simpler. 

Why use an external checkout service? When you use an integration like Stripe, your web applications don’t have to record and manage sensitive data like credit card numbers. Stripe has great documentation, and developers don’t have to invest a lot of time and effort to get a working store up and running securely. 

If that’s the case, why am I writing this blog post? Isn’t everything so easy and documented? Well, there are two main reasons: 

  • The examples I’ll be covering focus on separate parts, as well as on a fully working example that can help you prevent errors
  • Stripe documentation uses the Sinatra framework, so Ruby on Rails examples are a  great addition

Before integrating Stripe, let's build a simple shop web application that will sell some products. This will enable you to implement Stripe checkout in your Ruby on Rails app.

The Idea Behind the Online Shop 

Normally, shops work like this: a provider is selling and a consumer is buying. In this example, let's create something different. One side will provide a technical solution. To be more precise, the provider will create a platform, and the consumer will use this platform to create his or her own products. When the product creator wishes to download the product, the platform charges a fee for this service. 

Specifically speaking, let's make a PWA maker. A PWA allows a web app to acquire native apps functionalities and access it through a web browser. This blog post will not cover the process of making a PWA, but it will use this shop idea to serve as a working example. 

The process of buying looks something like this:

  • The shop user makes a PWA and registers to the shop
  • The user clicks on downloads and gets redirected to Stripe checkout
  • If the payment is successful, the user is directed back to Ruby on Rails web application
  • The user can download the PWA
  • The shop user can subscribe to this PWA for deployment and maintenance (also a Stripe checkout)

Basically, one registered user can create multiple products. Maybe it is strange that the user will buy his or her own products. However, what’s even more strange is that the user will subscribe to his or her own product. 

Normally, subscriptions are related to users. In this example, a subscription will be related to products, and one user can subscribe to maintenance for multiple products.

Setting up the shop

Before starting to make models, controllers, views (MVC), and other elements, let’s add stripe gem and devise gem to the Gemfile:

gem 'stripe'
gem 'devise'

After adding the gems and running bundle install, a stripe configuration file should be made. It can be placed in config/initializers/stripe.rb, and it looks like this:

Stripe.api_key = ENV["SECRET_KEY"]

One simple line is adding your API key to Stripe. This api key should be kept secret and should not be pushed on Gitlab or Github. The key is stored in ENV variable in config/local_env.yml which looks like this:

SECRET_KEY: "sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxx..." 
PUBLISHABLE_KEY: "pk_test_yyyyyyyyyyyyyyyyyyyyy..."


Git (if you are using git) should ignore this file, so add it to .gitignore . The Ruby on Rails application should know that there is a new file and that this file should be loaded, so change your config/application.rb accordingly:

…
module PwaMaker
  class Application < Rails::Application
...
    config.before_configuration do
      env_file = File.join(Rails.root, 'config', 'local_env.yml')
      YAML.load(File.open(env_file)).each do |key, value|
        ENV[key.to_s] = value
      end if File.exists?(env_file)
    end
...
  end
end

If everything is fine, you should be able to see ENV variables in your rails console. You should also be able to make changes to the stripe dashboard (for example creating a new customer):

pwa_maker]$(master) rails c
2.3.1 :001 > ENV["SECRET_KEY"]
 => "sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..." 
2.3.1 :002 > Stripe::Customer.create(email: "some.email@gmail.com")
 => #<Stripe::Customer:0x2aff4c04803c id=cus_XXXXXXXXXXXXXXX> JSON: {
  "id": "cus_XXXXXXXXXXXXXXX",
  "object": "customer",
  "address": null,
  "balance": 0,
...

Making the models

The shop itself is simple, so let's generate some models. Two models are crucial: user and product. User model will use the devise gem for registration and it looks like this:

# == Schema Information
#
# Table name: users
#
#  id                     :bigint           not null, primary key
#  email                  :string           default(""), not null
#  encrypted_password     :string           default(""), not null
#  reset_password_token   :string
#  reset_password_sent_at :datetime
#  remember_created_at    :datetime
#  sign_in_count          :integer          default("0"), not null
#  current_sign_in_at     :datetime
#  last_sign_in_at        :datetime
#  current_sign_in_ip     :inet
#  last_sign_in_ip        :inet
#  created_at             :datetime         not null
#  updated_at             :datetime         not null
#  customer_id            :string
#
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
    :recoverable, :rememberable, :trackable, :validatable
  default_scope { order(created_at: :desc ) }
  has_many :products

  after_save :assign_customer_id 
  def assign_customer_id
    if self.customer_id.blank? 
      customer = Stripe::Customer.create(email: email)
      self.update(customer_id: customer.id)
    end
  end

  def products_purchased
    self.products.where.not(paid_at: nil)
  end

  def products_subscribed_on
    self.products.where.not(subscribed_at: nil)
  end
end


The only extra field here is the customer_id. When the user signs up to the PWA maker, it will automatically create a customer in the Stripe dashboard


The method for creating the Stripe customer is “assign_customer_id”. This method will send a request to the Stripe API, and the response will contain the “customer_id”. The “customer_id” looks something like this: cus_XXXXXXXXXXXXXXX. If you remember, the same request and response was made earlier in this blog post, but through the rails console.  

The second important model is the Product model and it could look like this:

# == Schema Information
# Table name: products
#  id                    :bigint           not null, primary key
#  title                 :string
#  content               :text
#  user_id               :integer
#  created_at            :datetime         not null
#  updated_at            :datetime         not null
#  price                 :integer
#  price_id              :string
#  product_id            :string
#  paid_at               :datetime
#  zip_filename          :string
#  subscription_price_id :string
#  subscription_price    :integer
#  subscribed_at         :datetime
#  subscription_id       :string
#  unsubscribed_at       :datetime
#
class Product < ApplicationRecord
  validates :title, uniqueness: { case_sensitive: false }
  belongs_to :user, optional: true
  default_scope { order(created_at: :desc ) }

  before_save :set_price
  def set_price
    self.price = 300
    self.subscription_price = 1000
  end

  after_save :assign_price_id 
  def assign_price_id
    if self.price_id.blank? and self.product_id.blank?
      product = Stripe::Product.create(id: self.id, name: self.title, type: "service")
      price = Stripe::Price.create(product: product.id, currency: self.currency, unit_amount: self.price, recurring: nil)
      subscription_price = Stripe::Price.create(product: product.id, currency: self.currency, unit_amount: self.subscription_price, recurring: {interval: 'month'})

      self.update(price_id: price.id, product_id: product.id, subscription_price_id: subscription_price.id)
    end
  end

  def currency
    "EUR"
  end

  def in_main_currency(price)
    (price.to_f / 100.0).round(2)
  end

  def in_main_currency_humanized(price)
    sprintf("%.2f #{self.currency}",  self.in_main_currency(price) ) #.gsub('.00','')
  end

  def price_in_main_currency
    self.in_main_currency(self.price)
  end

  def price_humanized
    self.in_main_currency_humanized(self.price)
  end

  def subscription_price_in_main_currency
    self.in_main_currency(self.subscription_price)
  end

  def subscription_price_humanized
    self.in_main_currency_humanized(self.subscription_price) + " / month"
  end

  def is_subscribed
    !self.subscribed_at.blank? and self.unsubscribed_at.blank?
  end

  def is_unsubscribed
    self.subscribed_at.blank? and !self.unsubscribed_at.blank?
  end

  def was_never_subscribed
    self.subscribed_at.blank? and self.unsubscribed_at.blank?
  end
end

Keep in mind the title content, zip_filename, and user_id ofthe product model contains a lot of Stripe related fields:

price

The amount a customer has to pay to download the app (in fractional currency)

price_id

String by which the price can be found in the Stripe API (price_XXXXXXXXXXXX)

product_id

String by which the product can be found in the Stripe API (product_XXXXXXXXXXXX)

paid_at

The time the user paid for the product

subscription_price_id

String by which the subscription price can be found in the Stripe API dashboard (price_XXXXXXXXXXXX)

subscription_price

The amount the customer has to pay for maintaining the app (in fractional currency)

subscribed_at

The time the user has paid for the subscription

unsubscribed_at

The time the user cancelled the subscription

subscription_id

String by which the subscription can be found in the Stripe API (sub_XXXXXXXXXXXX)

When the user creates the product, the method “assign_price_id” will be called and it will create one product with two prices which can be seen in the Stripe dashboard:



Two prices have two different amounts (always in fractional currency in Ruby on Rails applications) and two different price IDs. The first price is the one-time purchase price, and the second price is the monthly recurring subscription price. In this example, the currency is  EUR (Euro) and could be replaced with USD or any other supported currency

Fields subscribed_at, unsubscribed_at, and paid_at are initially nil and it will be updated when the user successfully finishes the Stripe checkouts. Other methods except the “assign_price_id” methods are helpers that will display the amounts in readable form or check if the user has already paid for something. 

Other models could be an Image model that belongs to the Product or an Admin model that could be a person who manages the content on this project. There is no need to cover those models in this blog post. The next task is to create a user interface where the user can create and pay for the products.

User interface 

At this point, all functionalities can be accessed through the rails console or through the Stripe dashboard. Users should never have access to any of those two. This is why a custom interface is needed. Firstly, let’s take a look at the confing/routes.rb

Rails.application.routes.draw do
  devise_for :users
  devise_for :admins

  root "pages#home"
  resources :products,            only: [:create]

  %w( 404 422 500 ).each do |code|
    get code, :to => "errors#show", :code => code
  end

  namespace :user do
    root "products#index"
    resources :products, only: [:show]
    resources :charges, only: [:new, :create] do
      get "success", to: "charges#success"
      get "cancel", to: "charges#cancel"
    end

    resources :subscriptions, only: [:new, :create] do
      get "success", to: "subscriptions#success"
      get "cancel", to: "subscriptions#cancel"
      post "manage", to: "subscriptions#manage"
      get "manage-return", to: "subscriptions#manage_return"
    end
    resources :unsubscriptions, only: [:create]
  end

  namespace :admin do
  …
  end
end

The unregistered user can create a product, and they will then be redirected to the signup form. If the user signs up, the product will be associated with the user (the product will just get the user’s ID). 

This approach can have some hidden issues. For example, what will happen with the product if the user doesn’t sign up? Similarly, can the product be assigned to the wrong user? However, this blog post will leave these problems unresolved because they are not related to Stripe. 

Create the product

Before explaining the controller, let’s make clear what are actions and methods. Methods are similar to functions in other programming languages, and actions are specific methods in Ruby on Rails controllers. The most simple controller here is the app/controllers/products_controller.rb and it looks like this:

class ProductsController < ApplicationController 
  def create
    @product = Product.new(product_params)
    @product.user = current_user if current_user

    if @product.save
      if current_user
        redirect_to user_root_path, notice: t('saved_successfully')
      else
        cookies[:pwa_id] = { value: encode_id(@product.id), expires: 30.minutes.from_now }
        redirect_to new_user_registration_path, notice: "Sign up to download the PWA"
      end
    else
      render :new
    end
  end

  private

  def product_params
    params.require(:product).permit(:title, :content)
  end
end


The “create” action will create the product and associate it with the user if the user is registered. If not, the product ID will be encoded with the custom made “encode_id” method and stored in your cookies. There is no “new” action because the form is embedded on the root page. This form could look something like this:

<%= form_for @product, url: products_path do |f| %>
    <%=render partial: 'layouts/error_messages', as: :object, object: @product %>
      <div class="form__field">
        <%= f.label :title, class: 'label' %>
        <%= f.text_field :title, class: 'input' %>
      </div>
    
      <div class="form__field">
        <%= f.label :content, class: 'label' %>
        <%= f.text_area :content, class: 'input' %>
      </div>
  <%=f.submit 'save', class: 'btn btn--full', data: {disable_with: 'please_wait ...'} %>

<% end %>

After the product is created, the devise gem will handle unregistered users and when the user is registered it will be redirected to the “index” action in app/controllers/user/products_controller.rb:

class User::ProductsController < User::BaseController 
  before_action :check_for_products
  #include CreatePwa

  def index
    @products = current_user.products
  end

  def show
    @product = Product.find(params[:id])
    #make_pwa if @product.zip_filename.blank?
    #redirect_to root_path + @product.zip_filename
    redirect_to user_products_path
  end

  private

  def check_for_products
    if current_user and !cookies[:pwa_id].blank?
      product = Product.find( decode_id(cookies[:pwa_id]) )
      if product.created_at >= 30.minutes.ago and product.user_id.blank?
        current_user.products << product 
        cookies[:pwa_id] = nil
      end
    end
  end
end

The methods in “CreatePwa” are not included in this blog post, so some steps are commented out. Also, the “check_for_products” method uses “decode_id” which is the counterpart of “encode_id” and could be refactored in the future. 

At this point, feel free to redesign your product creation and user registration. Until now, none of the above mentioned steps use the Stripe checkout. 

In this example, the app/views/user/products/index.html.erb has four links for each users product that lead to interactions with the Stripe API:

      <ul>
        <% @products.each do |product| %>
          <li> 
            <%= product.title %> | 
             <% if product.paid_at.blank? %> 
              <%= link_to "Pay #{product.price_humanized} to download", new_user_charge_path(charge_id: product.id) %>
            <% else %>
              <%= link_to "Download", user_product_path(product) %> |
              <% if product.was_never_subscribed or product.is_unsubscribed %> 
                <%= link_to "Subscribe for maintenance", new_user_subscription_path(subscription_id: product.id) %>
              <% elsif product.is_subscribed %> 
                <%= link_to "Cancel subscrption for maintenance", user_unsubscriptions_path(subscription_id: product.product_id), method: :post %>
              <% end %>
            <% end %>
          </li>
        <% end %>
      </ul>

One-time payment

The first link from the list above is “new_user_charge_path(charge_id: product.id)”, and it leads to the “new” action in app/controllers/user/charges_controller.rb that looks like this:

class User::ChargesController < User::BaseController 
  before_action :set_product
  before_action :redirect_if_paid
  layout "stripe/application"
  include StripeMethods 

  def new 
  end

  def create 
    session = Stripe::Checkout::Session.create({
      line_items: [{
        price: @product.price_id,
        quantity: 1,
      }],
      mode: 'payment',
      customer: current_user.customer_id,
      client_reference_id: @product.product_id,
      success_url: user_charge_success_url(charge_id: @product.id) + "?session_id={CHECKOUT_SESSION_ID}",
      cancel_url: user_charge_cancel_url(charge_id: @product.id),
    })

    redirect_to session.url
  end

  def success
    session = Stripe::Checkout::Session.retrieve(params[:session_id])
    customer = Stripe::Customer.retrieve(session.customer)
    # is_paid is in concerns/stripe_methods.rb
    @product.update(paid_at: Time.now) if is_paid(session, customer)
  end

  def cancel
  end

  private

  def set_product
    @product = current_user.products.find(params["charge_id"])
  end

  def redirect_if_paid
    redirect_to user_root_path unless @product.paid_at.blank?
  end
end

This controller uses the app/views/layouts/stripe/application.html.erb as a layout that has no other JavaScript scripts except those which are necessary for Stripe:

<!DOCTYPE html>
<html>
  <head>
    <title>PWA Maker</title>
    <%= stylesheet_link_tag 'stripe', media: 'all' %>
    <script src="https://polyfill.io/v3/polyfill.min.js?version=3.52.1&features=fetch"></script>
    <script src="https://js.stripe.com/v3/"></script>

    <%# two js files must me added to application.rb for precompiling %>
    <% if controller_name == "subscriptions" and action_name == "success" %>
      <%= javascript_include_tag 'stripe/client.js' %>
    <% end %>

    <%= javascript_include_tag 'stripe/helpers.js' %>
  </head>

  <body>
    <section>
      <%= yield %>
    </section>
  </body>
</html>

Most of the JavaScript scripts come from Stripe.com, but client.js and helpers.js are in the Ruby on Rails application pipeline. Subscriptions will use client.js that look like this:

// In production, this should check CSRF, and not pass the session ID.
// The customer ID for the portal should be pulled from the
// authenticated user on the server.
document.addEventListener('DOMContentLoaded', async () => {
  console.log("client is loaded")
  let searchParams = new URLSearchParams(window.location.search);
  if (searchParams.has('session_id')) {
    const session_id = searchParams.get('session_id');
    document.getElementById('session-id').setAttribute('value', session_id);
  }
});

Helper.js will just run a simple countdown and redirect the user to the root page:

var secondsLeft = 0;
function runCountdown(id = "js-count") {
  var initSec = document.getElementById(id).innerHTML; 
  secondsLeft = parseInt(initSec) - 1;
  setInterval(function() {
    document.getElementById("js-count").innerHTML = secondsLeft;
    if (secondsLeft>1) secondsLeft--
  }, 1000);
};

function runRedirect(url) {
  setTimeout(function() { 
    window.location.href = url 
  }, (secondsLeft+1)*1000); // 1000 is 1 sec
}

Besides the custom layout, the charges controller includes “is_paid” method from the “StripeMethods” (app/controllers/concerns/stripe_methods.rb) and looks like this:

module StripeMethods 
  def is_paid(session, customer)
    return false unless current_user.customer_id == customer.id 
    return false unless session.customer == current_user.customer_id
    return false unless session.client_reference_id.to_i == @product.id
    return false unless session.payment_status == "paid" 
    return true
  end
end

This method should check if all requirements are fulfilled and if a product can be considered paid. 

The charges controller is pretty simple now that all the additional stuff is explained. The user clicks on the "Pay 3 EUR to download" link and the “new” method in the charges controller will be called. The template for this action looks like this:

<div class="product">
  <img src="https://i.imgur.com/EHyR2nP.png" alt="The cover of Stubborn Attachments" />
  <%= image_tag "logo.png" %>
  <div class="description">
    <h3> <%= @product.title %> </h3>
    <h5> <%= @product.price_humanized %> </h5>
  </div>
</div>

<%= form_for [:user, @product], url: user_charges_path(charge_id: @product.id), method: :post , :html => {role: 'form', class: ''} do |f| %>
  <button type="submit" id="checkout-button">Checkout</button>
<% end %>

The user sees this:

Clicking on the “Checkout” button will make a call to the “create” action in the charges controller. This action creates a Stripe session, which redirects the user to Stripe.com. 

The rails application will get this new stripe url from Stripe::Checkout::Session.create action. The user is now on Stripe.com, which indicates that the rails application won’t handle the payments at all. 



In this step, the user should enter the required data. In this example, the Ruby on Rails application can’t and shouldn’t save the credit card numbers. 

When the user completes the payment, Stripe will redirect the user back to the Ruby on Rails application. It will redirect the user back to “user_charge_success_url” or to “user_charge_cancel_url”, which depends on if the payment was successful or not. 

These two URLs are added to the Stripe session in the “create” action in the charges controller. Notice the param “?session_id={CHECKOUT_SESSION_ID}”. “{CHECKOUT_SESSION_ID}” is not a placeholder for some number, It is literally the given string. When the payment is done, Stripe redirects the user to “user_charge_success_url”. It will also send the “params[:session_id]” which will be used to retrieve the session and to convince the “success” action in the charges controller that the payment is valid. 

This “success” action will check if the payment is valid with the “is_paid” method, update the product record in the database, and it will render the app/views/user/charges/success.html.erb template, that looks like this:

<p> We appreciate your business! </p>
<p> Redirect to dashboard in &nbsp;&nbsp; <span id="js-count"> 5 </span> &nbsp;&nbsp; or click to &nbsp; <%= link_to "Dashboard", user_root_path %> </p>

<script>
  runCountdown();
  runRedirect("<%= user_root_path %>");
</script>


Calls on two JavaScript functions in the script tag are unnecessary but can be found in app/assets/javascripts/stripe/helpers.js. The user, or to be more clear, the customer, will see something like this:

 

 

Stripe will redirect the user to the “user_charge_cancel_url” if the payment was unsuccessful. The Ruby on Rails application could render a “Try again” template for these situations. 

If the product is marked as paid, or, to be precise, that the paid_at field is not nil (it carries now the time and date the product was paid on), the user won’t see the “Pay 3 EUR to download” link in app/views/user/products/index.html.erb anymore,. Instead, they’ll see the “Download” link which can lead to the show action or which can trigger the download process. 

Subscription 

The third url on the list on app/views/user/products/index.html.erb is “new_user_subscription_path(subscription_id: product.id)” with the text “Subscribe for maintenance”. A click on this link will lead to a “new” action in the subscription’s controller. This controller in the file app/controllers/user/subscriptions_controller.rb looks like this:

class User::SubscriptionsController < User::BaseController 
  before_action :set_product
  before_action :redirect_if_subscribed
  layout "stripe/application"
  include StripeMethods 

  def new 
  end

  def create 
    session = Stripe::Checkout::Session.create({
      line_items: [{
        # Provide the exact Price ID (e.g. pr_1234) of the product you want to sell
        price: @product.subscription_price_id,
        quantity: 1,
      }],
      mode: 'subscription',
      customer: current_user.customer_id,
      client_reference_id: @product.product_id,
      success_url: user_subscription_success_url(subscription_id: @product.id) + "?session_id={CHECKOUT_SESSION_ID}",
      cancel_url: user_subscription_cancel_url(subscription_id: @product.id),
    })

    redirect_to session.url
  end

  def manage
    # This will make an error: 
    # Stripe::InvalidRequestError (You can't create a portal session in test mode until you save your customer portal settings in test mode at https://dashboard.stripe.com/test/settings/billing/portal.)
    #
    # Allow customer to update the following billing information
    checkout_session_id = params['session_id']
    checkout_session = Stripe::Checkout::Session.retrieve(checkout_session_id)

    session = Stripe::BillingPortal::Session.create({ 
      customer: checkout_session.customer,
      return_url: user_subscription_manage_return_url(subscription_id: @product.id)
    })

    redirect_to(session.url, format: :json)
  end

  def manage_return
  end

  def success
    session = Stripe::Checkout::Session.retrieve(params[:session_id])
    customer = Stripe::Customer.retrieve(session.customer)

    # is_paid is in concerns/stripe_methods.rb
    @product.update(subscribed_at: Time.now, subscription_id: session.subscription, unsubscribed_at: nil) if is_paid(session, customer)
  end

  def cancel
    # This is not unsubscribe - this is when subscription create fails
  end

  private

  def set_product
    @product = current_user.products.find(params["subscription_id"])
  end

  def redirect_if_subscribed
    redirect_to user_root_path unless @product.subscribed_at.blank?
  end
end

This controller is similar to the charges controller from the last chapter, but it has two extra actions. Those actions are “manage” and “manage_return”. The first method will lead the user to Stripe.com so the user can change subscription information. The second method, “manage_return”, acts as a place where Stripe.com will redirect the user back to this Ruby on Rails application. It looks like managing subscriptions works only in production and not when the test mode is active.  

This checkout works the same as the one-time payment. The action “new” will render the template app/views/user/subscriptions/new.html.erb that could look like this:

<div class="product">
  <img src="https://i.imgur.com/EHyR2nP.png" alt="The cover of Stubborn Attachments" />
  <%= image_tag "logo.png" %>

  <div class="description">
    <h3> Maintenance </h3>
    <h5> <%= @product.subscription_price_humanized %> </h5>
  </div>
</div>

<%= form_for [:user, @product], url: user_subscriptions_path(subscription_id: @product.id), method: :post , :html => {role: 'form', class: ''} do |f| %>
  <input type="hidden" name="lookup_key" value="{{PRICE_LOOKUP_KEY}}" />
  <button id="checkout-and-portal-button" type="submit">Checkout</button>
<% end %>


The only exception is the “PRICE_LOOKUP_KEY” in a hidden field which is used as a lookup key for retrieving prices dynamically from a static string. This topic is better covered in the Stripe documentation

However, pay special attention to the “cancel” action. Some developers could make a mistake by trying to use this action to cancel the subscription, but this is wrong. This method is used as a place where Stripe will redirect the user after the subscription has failed for some reason. To cancel a subscription, a new controller should be made. 

This unsubscription’s controller can be app/controllers/user/unsubscriptions_controller.rb which looks like this:

class User::UnsubscriptionsController < User::BaseController 
  before_action :set_product
  layout "stripe/application"

  def create 
    if @product.is_subscribed 
      @product.update(subscribed_at: nil, unsubscribed_at: Time.now)
      # Make the subscription least until next payment - customer can activate subscription if the period is not over
      # https://stripe.com/docs/billing/subscriptions/cancel?dashboard-or-api=api
      Stripe::Subscription.update(@product.subscription_id, { 
        cancel_at_period_end: true  # if the customer subscribes again before the end of period he will be charged again  
      })

      Stripe::Subscription.delete(@product.subscription_id)
    end

    redirect_to user_root_path, notice: "Unsubscription done!"
  end

  private

  def set_product
    @product = current_user.products.find(params["subscription_id"])
  end
end

Before the user cancels the subscription, this controller will update the subscription plan with the attribute “cancel_at_period_end”. This will unsubscribe the user at the end of the month or at the end of another period. The Stripe::Subscription could get this attribute at any time, but for the sake of this blog post, let’s not overwhelm other controllers with new information. 

The link that triggers the “create” action in the unsubscriptions controller is the fourth link on the list on the app/views/user/products/index.html.erb template. Unsubscriptions controller works without rendering any template. 

The Stripe admin has a nice overview of all one-time payments and subscriptions by accessing the Stripe dashboard.


Also, the Stripe admin can find the individual user on the Stripe dashboard and see all the actions the user has made by clicking on the Ruby on Rails application. 

Let's summarize

To summarise, Stripe is pretty simple and well-documented. There are a lot of examples in many programming languages that cover the integrations of this payment service. First, the developer needs a working application that offers a product or service. Secondly, the developer should decide how the user will be charged for this product or service. Once that’s accomplished, Stripe can be included in the application. 

Each action made by the user will be saved on the Stripe dashboard, and developers should think about what Ruby on Rails should record. Maybe it's better to store more keys provided by the Stripe API than fewer keys, but it depends on the application's task. My advice is not to save credit card numbers and other sensitive data in the application database. Let some external service such as Stripe take care of these for added user security.




Development

Easy integration of MyPOS Checkout in Ruby on Rails Apps

Follow our step-by-step guide and learn how to integrate the MyPOS checkout embed form in the Ruby on Rails app.

Mislav Kvesic

Backend Developer • 13 min read