Integrating Recurring Payments to Your Rails API With Stripe
In this article I will go through a Subscription Integration in a Rails API, using the Stripe API it.

For this application, I will consider you already have a User’s model in your application. In case you don’t have it yet, don’t worry! I already published an article about that, you can read it here.
Getting Started
If you followed the steps of my other article, to create your User
model, or simply cloned it from GitHub, you will need to change the Ruby version on the Gemfile. For this article, I will be using Ruby 3.0.0, so your Gemfile must look like that:
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }ruby '3.0.0'...
Also, add the Stripe Gem to your 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 User Model
First of all, we need our users to have a Stripe Customer ID. We need to store that ID in our database, so run the command below to create a migration:
rails g migration AddStripeIdToUsers stripe_id
rails db:create db:migrate
We will also add a new validation to our User, to make sure the Stripe ID will be always present. Add to your User model:
validates :stripe_id, presence: true, uniqueness: true
To create a reference of our User on Stripe API, let’s add a callback to User
model.
Add the code below to the User model.
... before_validation :create_stripe_reference, on: :create def create_stripe_reference
response = Stripe::Customer.create(email: email)
self.stripe_id = response.id
end
end
Would be pretty interesting to have a method to retrieve the stripe information about the customer if we need it. Fortunately, Stripe SDK provides this method, which we will add to our User model too.
... def retrieve_stripe_reference
Stripe::Customer.retrieve(stripe_id)
end
end
Finally, your User
model should look like this:
class User < ApplicationRecord
has_secure_password validates :email, uniqueness: { case_sensitive: false }
validates :stripe_id, presence: true, uniqueness: true before_validation :create_stripe_reference, on: :create def create_stripe_reference
response = Stripe::Customer.create(email: email)
self.stripe_id = response.id
end def retrieve_stripe_reference
Stripe::Customer.retrieve(stripe_id)
end
end
Awesome!! Let’s try our code at the console, run in your terminal:
rails c
You can now create a User
, and the code will automatically contact Stripe API and create the reference.
User.create(email: "user@test.com", password: "123456")
You can check out the Stripe ID of this user with the commands below.
user = User.first
user.stripe_id
And to retrieve all the Stripe info for this user, run:
user.retrieve_stripe_reference
That looks fantastic.
The Plan Model
The Plan model will be responsible for representing and handling business rules for the subscription options we will offer.
Run-on your terminal:
rails g model Plan name description interval:integer price_cents:integer stripe_price_id
rails db:migrate
Take a closer look at the Plan
attributes:
Name
will be a string and represents the name of the plan;Description
will be a string and represents short text, describing what the plan offers;Interval
will be an integer and represents the interval of charging the subscriber;price_cents
will be an integer and represents the value for the subscription, it needs to be cents because Stripe works with cents values and it is easier to handle the value this way.stripe_price_id
will be a string and represents a reference to our Plan in the Stripe API.
That’s how the Plan
model will look like:
class Plan < ApplicationRecord
validates :name,
:stripe_price_id,
:price_cents, presence: true
validates :name,
:stripe_price_id, uniqueness: true enum interval: %i[month year] before_validation :create_stripe_reference, on: :create def create_stripe_reference
response = Stripe::Price.create({
unit_amount: price_cents,
currency: 'usd',
recurring: { interval: interval },
product_data: { name: name }
}) self.stripe_price_id = response.id
end def retrieve_stripe_reference
Stripe::Price.retrieve(stripe_price_id)
end
end
Take a closer look at the Plan
model:
- First, we defined some validations, to make sure
name
,stripe_price_id
andprice_cents
will be always present. We also defined a validation to make sure thename
and thestripe_price_id
will be unique. - After that, we defined our interval enum, rails will handle those options, so when we pass the value "month" for the interval, the framework will store 0 in the database and when we pass the value "year" it will store 1.
- Then we defined a callback to create a reference to our
Plan
on Stripe API. - The
create_stripe_reference
method will be responsible for the all the logic triggered on the callback. - The
retrieve_stripe_reference
will basically return the Stripe reference of ourPlan
.
Let’s try our new model on the console. Run in your terminal:
rails c
You can now create a Plan
, and the code will automatically contact Stripe API and create the reference:
# Month Subscription
Plan.create(
name: 'Month Subscription',
description: 'This subscription will be charged every month',
interval: 'month',
price_cents: 500
)# Year Subscription
Plan.create(
name: 'Year Subscription',
description: 'This subscription will be charged every year. Save $10 with this subscription.',
interval: 'year',
price_cents: 5000
)
To retrieve all the Stripe info for some plans, run:
# The 1 represents the ID of the record.
Plan.find(1).retrieve_stripe_reference
The Subscription Model
The Subscription model will be responsible for representing and handling business rules for a user’s subscription.
Run the following command on your terminal:
rails g model Subscription plan:references user:references active:boolean stripe_id
rails db:migrate
Taking a closer look to the Subscription attributes:
plan
will be a reference to the plan that is being purchased;user
will be a reference to the user that is purchasing a plan;active
will be a boolean that represents if a subscription is still valid.stripe_id
will be a string and represents a reference to the Subscription in the Stripe API.
Open the migration create_subscriptions and make sure to define the default value of the active attribute:
...
t.boolean :active, default: true
...
After that, let’s code the Subscription
model. That’s how it should look like:
class Subscription < ApplicationRecord
attr_accessor :card_number, :exp_month, :exp_year, :cvc belongs_to :plan
belongs_to :user validates :stripe_id, presence: true, uniqueness: true before_validation :create_stripe_reference, on: :create def create_stripe_reference
Stripe::Customer.create_source(
user.stripe_id,
{ source: generate_card_token }
) response = Stripe::Subscription.create({
customer: user.stripe_id,
items: [
{ price: plan.stripe_price_id }
]
}) self.stripe_id = response.id
end def generate_card_token
Stripe::Token.create({
card: {
number: card_number,
exp_month: exp_month,
exp_year: exp_year,
cvc: cvc
}
}).id
end
end
Take a closer look at the Subscription
model:
- First of all, we defined some virtual attributes. We will not store that data, because it is sensitive. So Stripe will handle it for us.
- After that, we defined the relations of the model, a belongs to Plan and User.
- The only attribute we will validate is
stripe_id
, it must be present and be unique. - We also have a callback, that will be triggered before the validation of the record creation.
- The
create_stripe_reference
will be responsible for creating the reference of our subscription on the Stripe API. Note that before creating the subscription on Stripe API, we create a source for the customer, that will be the payment method. - The
generate_card_token
will be responsible for passing the card data to Stripe API and return a token. Passing a token is more secure than passing the card data, that’s why Stripe handles the payment source this way.
Let’s try our new model on the console. Run in your terminal:
rails c
You can now create a Subscription
, and the code will automatically contact Stripe API and create the reference.
Subscription.create(
card_number: '5555555555554444',
exp_month: 06,
exp_year: 2029,
cvc: '123',
user_id: User.first.id,
plan_id: Plan.first.id,
active: true
)
Note that Stripe has pre-defined allowed test card numbers. You can check it out here: Stripe Test Cards.
That’s awesome!! Our Subscription is already running and you can see it on the Stripe Dashboard.

Canceling a Subscription
Sometimes we need to save money and some subscriptions must be canceled, let’s program the application to allow cancelations.
What we will do is basically update the record and the reference on Stripe API when active is false. Let's code it.
Add a callback to cancel the subscription on Stripe API, with activeness conditional.
...before_update :cancel_stripe_subscription, if: :subscription_inactive?...
We also need to add the method that the callback call and the method that define the condition.
... def cancel_stripe_subscription
Stripe::Subscription.delete(stripe_id)
end def subscription_inactive?
!active
end
end
To test if it works, go to your rails console and run the commands below:
Subscription.first.update(active: false)
Now the subscription is canceled, and you can check it on your Stripe Dashboard.

What about the Controllers?
For this post, we will have controllers only to show, create and update subscriptions. Once the Plans CRUD is more an administrative thing, I will not create a controller for it now.
The Subscriptions Controller
To create the subscriptions controller, run the command below in your terminal:
rails g controller subscriptions show create update
The command above will add some routes to our routes file, but we need to change it, once we want the routes inside the namespace api/v1
.
So your routes may look like this:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
post '/signup', to: 'users#signup'
post '/signin', to: 'users#signin'
get '/signedin', to: 'users#signedin' resources :subscriptions, only: %i[show create update]
end
end
end
After generating the controller, let’s write it. Since that’s a very generic rails controller, I will not explain the code of it in this article.
Your controller should look like this:
module Api
module V1
class SubscriptionsController < ApplicationController
before_action :set_subscription, except: %i[create] def show
render json: @subscription
end def create
@subscription = Subscription.new(subscription_params) if @subscription.save
render json: @subscription, status: :created
else
render json: @subscription.errors, status: :unprocessable_entity
end
end def update
if @subscription.update(subscription_params)
render json: @subscription
else
render json: @subscription.errors, status: :unprocessable_entity
end
end private def set_subscription
@subscription = Subscription.find(params[:id])
end def subscription_params
params.require(:data).permit(:card_number, :exp_month, :exp_year, :cvc, :user_id, :plan_id, :active)
end
end
end
end
Testing the Controller — Requests
I will use Paw to run the requests and test the controller, but you will achieve the same results with any HTTP Client, like Postman or Insomnia.
Up your local server with the command below:
rails s
The Create Request
This request will trigger a subscription creation.
Action: POST
Endpoint:
http://localhost:3000/api/v1/subscriptions
Headers:
Content-Type: application/json
Body:
{
"data": {
"card_number": "5555555555554444",
"exp_month": "09",
"exp_year": "2029",
"cvc": "123",
"user_id": "1",
"plan_id": "1"
}
}
The response to this request should match the format below:

The Update Request
This request will cancel a subscription.
Action: PUT
or PATCH
Endpoint:
http://localhost:3000/api/v1/subscriptions/3
The number 3 on the endpoint above, represents the ID of the record we want to update, you may change it related to the ID of your subscription.
Headers:
Content-Type: application/json
Body:
{
"data": {
"active": false
}
}
The response of this request should match the format below:

The Show Request
This request will retrieve a subscription.
Action: GET
Endpoint:
http://localhost:3000/api/v1/subscriptions/3
The number 3 on the endpoint above, represents the ID of the record we want to update, you may change it related to the ID of your subscription.
The response of this request should match the format below:

Conclusion
Stripe make the payment system integration be pretty simple and easy.
There are a lot of resources on Stripe API that were not explored during this article. I mean to publish another one in the future, related to payment intent and webhooks for receiving the intent to update the status of a subscription.
You can check the complete Stripe Docs here.
Want to Connect?Connect on Twitter.