Integrating Stripe’s Product API Into Your Rails API | by Bruno Feres | Apr, 2022

A hands-on guide on Stripe’s Product API Integration

Getting Started

Stripe APIs have amazing features for e-commerces and marketplaces. To make our developer’s life easier, they offer SDK for some programming languages, including Ruby.

We will start adding the Stripe SDK Gem to our Gemfile.

gem 'stripe'

Don’t forget to run bundle install in your terminal after that.

Now you will need to create a stripe initializer file in your application. For that, run the command below in your terminal:

touch config/initializers/stripe.rb

Then, open the stripe.rb file and write the content below:

Stripe.api_key = Rails.application.credentials.stripe_secret_key

We didn’t add that Stripe’s Secret Key in our credential file, so let’s do this.

To open your credential file, run in your terminal:

EDITOR=nano rails credentials:edit

Get your Stripe Secret Key here.

Your credential file must look like this:

# aws:
# access_key_id: 123
# secret_access_key: 345
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: [YOUR SECRET KEY BASE]
stripe_secret_key: [YOUR SECRET KEY HERE]

Note that the secret_key_base is autogenerated, you don’t need to edit.

The Product Logic

This section is about the product logic on our side, our API. It will consist of two models: Product and Price.

Board with Post-It

Our Product Model will have as many prices related as the application need. But why?

Let’s assume one of your products is an iPhone 13. As you may know, the iPhone 13 has a bunch of variants, including different colors and storage. We want to create a single Product record that represents the iPhone, and a Price record for each color variant.

The Product Code

Now we will code the Product based on the logic we saw above.

Run in your terminal:

rails g model Product name description:text stripe_id

Take a closer look at the Product attributes:

  • name will be a string that represents the name of the product;
  • description will be a string that represents a short text, describing the product;
  • stripe_id will be a string that represents a reference to our Product in the Stripe API.

Now, run:

rails g model Price title details:text amount_cents:integer product:references stripe_id

Taking a closer look to the Price attributes:

  • title will be a string that represents the variant of the product we are selling, it could be “Green” for example;
  • details will be a string that represents a short text, describing the product variant;
  • amount_cents will be an integer that represents the value of the product in cents.
  • product_id will be an integer that represents a reference to a Product.
  • stripe_id will be a string that represents a reference to our Price in the Stripe API.

The Product Model:

class Product < ApplicationRecord
validates :name, :stripe_id, presence: true, allow_blank: false
validates :stripe_id, uniqueness: true
has_many :prices, dependent: :destroy accepts_nested_attributes_for :prices before_validation :create_stripe_reference, on: :create
after_update :update_stripe_reference
def create_stripe_reference
response = Stripe::Product.create({ name: name })
self.stripe_id = response.id
end
def retrieve_stripe_reference
Stripe::Product.retrieve(stripe_id)
end
def update_stripe_reference
Stripe::Product.update(stripe_id, { name: name })
end
end
  • Validation to make sure that name and stripe_id attributes will be always present.
  • Validation to make sure that stripe_id will be unique in the database.
  • Setup for accepting nested attributes through the product JSON object on creating the endpoint.
  • Has many associations with prices.
  • The “after” callbacks will be triggered when one of the record handling actions is called. This is important to keep our stripe records up to date with our database.

The Price Model

class Price < ApplicationRecord
validates :title, :amount_cents, :stripe_id, presence: true, allow_blank: false
validates :stripe_id, uniqueness: true
belongs_to :product before_validation :create_stripe_reference, on: :create
after_update :update_stripe_reference
def create_stripe_reference
response = Stripe::Price.create({
unit_amount: amount_cents,
currency: 'usd',
product: product.stripe_id,
})
self.stripe_id = response.id
end
def retrieve_stripe_reference
Stripe::Price.retrieve(stripe_id)
end
def update_stripe_reference
response = Stripe::Price.create({
unit_amount: amount_cents,
currency: 'usd',
product: product.stripe_id,
})
self.stripe_id = response.id
end
end
  • Validation to make sure that title, stripe_id and amount_cents attributes will be always present.
  • Belongs To association with a product.
  • The “after” callbacks that will be triggered when one of the record handling actions will be called. This is important to keep our stripe records up to date with our database.

The Products Controller

Through this step we will create a controller for products. The actions will be a simple CRUD.

Run in your terminal:

rails g controller Api::V1::Products index show create update destroy

With the controller created you will need to edit it and make it look like this:

module Api
module V1
class ProductsController < ApplicationController
before_action :set_product, only: %i[show update destroy]
def index
@products = Product.includes(:prices).all
render json: @products, include: %i[prices], status: :ok
end
def show
render json: @product, include: %i[prices], status: :ok
end
def create
@product = Product.new(product_params)
if @product.save
render json: @product, status: :ok
else
render json: @product.errors, status: :unprocessable_entity
end
end
def update
if @product.update(product_params)
render json: @product, status: :ok
else
render json: @product.errors, status: :unprocessable_entity
end
end
def destroy
@product.destroy
head :no_content
end
private def set_product
@product = Product.includes(:prices).find(params[:id])
end
def product_params
params.require(:product).permit(:name, :description, prices_attributes: %i[title details amount_cents])
end
end
end
end

I will not describe this controller code once I consider you probably already know the logic of a Rails Controller. If you are not familiarized, I encourage you to take a read on this article.

The Prices Controller

Through this step we will create a controller for prices. The actions will be a simple CRUD.

rails g controller Api::V1::Prices index show create update destroy

With the controller created you will need to edit it and make it look like this:

module Api
module V1
class PricesController < ApplicationController
before_action :set_product, only: %i[index create]
before_action :set_price, only: %i[show update destroy]
def index
@prices = @product.prices
render json: @prices, status: :ok
end
def show
render json: @price, status: :ok
end
def create
@price = Price.new(price_params.merge(product_id: @product.id))
if @price.save
render json: @price, status: :ok
else
render json: @price.errors, status: :unprocessable_entity
end
end
def update
if @price.update(price_params)
render json: @price, status: :ok
else
render json: @price.errors, status: :unprocessable_entity
end
end
def destroy
@price.destroy
head :no_content
end
private def set_product
@product = Product.find(params[:product_id])
end
def set_price
@price = Price.find(params[:id])
end
def price_params
params.require(:price).permit(:title, :details, :amount_cents)
end
end
end
end

Our controllers look awesome!!

The Routes

We will have to edit our routes.rb file now.

It must look like this:

Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :products, except: %i[new edit] do
resources :prices, only: %i[index create]
end
resources :prices, only: %i[show update destroy]
end
end
end

Basically, we are setting up CRUD routes for products and prices, but the price routes will be under a product namespace. That means that the format for prices endpoint will be always:

/api/v1/products/:product_id/prices

Testing our API on an HTTP Client

To test our API we need to raise our server, run on your terminal:

rails s

Creating Product

Method: POST | URL: http://localhost:3000/api/v1/products

Headers: { “Content-Type”: “application/json” }

{
"product": {
"name": "iPhone 13 Mini",
"description": "Our most advanced dual‑camera system ever.",
"prices_attributes": [
{
"title": "Green - 128GB",
"details": "New Green Finish",
"amount_cents": 79900
},
{
"title": "Green - 256GB",
"details": "New Green Finish",
"amount_cents": 89900
}
]
}
}

Updating Product

Method: PUT | URL: http://localhost:3000/api/v1/products/1

{
"product": {
"name": "iPhone 13"
}
}

Listing All Products

Method: GET | URL: http://localhost:3000/api/v1/products

Retrieving Product

Method: GET | URL: http://localhost:3000/api/v1/products/1

Destroying Product

Method: DELETE | URL: http://localhost:3000/api/v1/products/1

Creating Price (for an existing product)

Method: POST | URL: http://localhost:3000/api/v1/products/1/prices

Headers: { “Content-Type”: “application/json” }

{
"price": {
"title": "Black - 128GB",
"details": "New Green Finish",
"amount_cents": 69900
}
}

Updating Price

Method: PUT | URL: http://localhost:3000/api/v1/prices/1

Headers: { “Content-Type”: “application/json” }

{
"price": {
"amount_cents": 59900
}
}

Retrieving Price

Method: GET | URL: http://localhost:3000/api/v1/prices/1

Destroying Price

Method: DELETE | URL: http://localhost:3000/api/v1/prices/1

Stripe make the integration with their API pretty simple and easy with the SDK.

There are a lot of resources on Stripe API that were not explored during this article. I mean to publish other ones in the future, related to charges payment intent, and webhooks.

You can check the complete Stripe Docs here.

Want to Connect?If you have any questions or suggestions, text me on Twitter

Leave a Comment