- create a new rails application named
subscription-app
rails new subscription-app -d mysql
- Enter your application
cd subscription-app
If you setup MySQL or Postgres with a username/password, modify the
config/database.yml
file to contain the username/password that you specified
- Make sure that MySQL server is running. Just type
mysql
. If you get an error like this:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
It's most likely that MySQL server is not running. You can start it with:
sudo service mysql start
- Generate the database
rake db:create
- Open your project in Visual Studio Code. You can type
explorer.exe .
to open the project on windows (for BashOnUbuntuOnWindows).
- Devise provides a number of user routes automatically.
- Session routes for Authenticatable (default):
User/scope | Method | Route | Controller Action |
---|---|---|---|
new_user_session | GET | /users/sign_in | devise/sessions#new |
destroy_user_session | DELETE | /users/sign_out | devise/sessions#destroy |
new_user_registration | GET | /users/sign_up | devise/registrations#new |
For other available routes, look at SessionsController and RegistrationsController
- URL helpers are provided to be used with resource/scope. They act like proxies to the generated routes created by devise. Example:
User/scope | URL helper |
---|---|
new_user_session | new_user_session_path |
destroy_user_session | destroy_user_session_path |
new_user_registration | new_user_registration_path |
- Devise provides a number of user helpers automatically.
Roles: User Admin
Generated methods: authenticate_user! # Signs user in or redirect authenticate_admin! # Signs admin in or redirect user_signed_in? # Checks whether there is a user signed in or not admin_signed_in? # Checks whether there is an admin signed in or not current_user # Current signed in user current_admin # Current signed in admin user_session # Session data available only to the user scope admin_session # Session data available only to the admin scope
Use: before_action :authenticate_user! # Tell devise to use :user map before_action :authenticate_admin! # Tell devise to use :admin map
- Users have an email and password
- Use Devise for drop-in authentication
- Devise includes modules for additional functionality: Encrypt password, OmniAuth, Email confirmation, Recover password, Lock account, Expire session.
- Devise configures the user table with same default columns.
Name | Type |
---|---|
id | integer |
character varying | |
encrypted_password | character varying |
sign_in_count | integer |
current_sign_in_at | timestamp without time zone |
created_at | timestamp without time zone |
updated_at | timestamp without time zone |
- For instructions on installing Devise go to getting started
Small recap:
gem 'devise'
inGemfile
$ bundle install
$ rails generate devise:install
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
inconfig/environments/development.rb
root to: "home#index"
inconfig/routes.rb
- Create and update
home_controller.rb
- Create and update
views/home/index.html.erb
rails server
(ensure that MySQL server is running)- Update
views/layouts/application.html.erb
to include<p class="notice"><%= notice %></p>
and<p class="alert"><%= alert %></p>
$ rails generate devise User
$ rake db:migrate
rails server
if your application is nor running yet
http://localhost:3000/users/sign_in
andhttp://localhost:3000/users/sign_up
should be available and functional.
- Add Bootstrap CDN and
<%= render 'nav' %>
inviews/layouts/application.html.erb
- Create and update
views/application/_nav.html.erb
- The Publication Model:
- The digital file subscribers will have access to
- "Lightweight" model as it is not very relevant to implementing payments
- Should be customized based on your needs
- Publication scheme:
Name | Type |
---|---|
id | integer |
title | character varying |
file_url | character varying |
description | character varying |
created_at | timestamp without time zone |
updated_at | timestamp without time zone |
-
Administrators:
- Administrators are users with
is_admin
set to true - A initial administrator will be created, with the option to add more via the console
- Many Ruby gems exist for handling administrators :
cancan
,pundit
,rolify
- Administrators are users with
-
Administrator and Subscriber views:
- Administrators can create, edit, and update publications
- Subscribers can only view publications
- Non-subscribers can see publications but not access their details
Publication Routes (Admin):
Method | Route | Controller Action |
---|---|---|
GET | /admin/publications | admin/publications#index |
GET | /admin/publications/:id | admin/publications#show |
GET | /admin/publications/new | admin/publications#new |
POST | /admin/publications | admin/publications#create |
GET | /admin/publications/:id/edit | admin/publications#edit |
PUT | /admin/publications/:id | admin/publications#update |
DELETE | /admin/publications/:id | admin/publications#destroy |
Publication Routes (Subscriber):
Method | Route | Controller Action |
---|---|---|
GET | /publications | publications#index |
GET | /publications/:id | publications#show |
Publication Routes (Non-subscriber):
Method | Route | Controller Action |
---|---|---|
GET | /publications/:id | publications#show |
- Create the publication model
rails g model publication title:string description:text file_url:string
- Migrate the database to generate the publication model:
rake db:migrate
- Add routes in
config/routes.rb
:
resources :publications, only: [:index, :show]
- Create the
publications_controller.rb
and theindex
andshow
views. - To add a publication for test you can type:
rails c
p = Publication.new(title: 'My first publication', description: 'This is my first publication', file_url: 'http://myfilelocation.com')
p.save
exit
- Create a migration file to add the
is_admin
column to theusers
table to allow users to become admin:
rails g migration add_is_admin_to_users is_admin:boolean
- Modify the generated migration file for
add_is_admin_to_users
to set null to false and default to false.
add_column :users, :is_admin, :boolean, null: false, default: false
- Run the migration:
rake db:migrate
- You can test everything with the rails console:
rails c
User.last
User.last.is_admin
User.last.update(is_admin: true)
- If you wish, you can set an admin in the
seeds.rb
(so you have one by default):
User.create(
email: '[email protected]',
password: 'password',
is_admin: true
)
- Update the
routes.rb
to add the admin routes. - Create the
admin_controller.rb
under controllers. - Add the
admin
folder under controllers, and add thepublications_controller.rb
. - Add the
admin
folder under views and, underadmin
folder, thepublications
folder and add theindex.html.erb
,edit.html.erb
,show.html.erb
,new.html.erb
. - Run
rails s
and navigate tolocalhost:3000/admin/publications
to test the admin routes.
- We already did at
localhost:3000/publications
. You can check that everything works fine.
- Payment processors:
PayPal
,Authorize.Net
,Stripe
. - Why Stripe?
- PCI compliance is hard
- Powerful API
- Low cost
- Great support
- Stripe's Subscription API
- Allows recurring charges on a schedule
- Handles storing credit card information
- Additional features like coupons and trial periods are also available
-
Adding Stripe to Our application
- Add the Stripe API gem
- Find our Stripe API keys
- Use
dotenv
to securely store API keys - Test the API in the console
-
Go to Stripe and open an account.
-
Create a new product in the
Products
section. Example:
Subscription App Bronze Plan 9.00 $ / month
- Install Stripe libraries and tools
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
- Run
bundle install
- Create
config/initializers/stripe.rb
and paste the Stripe.api_key
Stripe.api_key = 'sk_test_XXXXXXXXXXXXXXXXXXXXXX'
- To retrieve the subscription, you need to retrieve the plan using the prices API ID.
- You can test that everything works fine using the rails console:
rails c
Stripe::Plan.retrieve('price_XXXXXXXXXXXXXXXXXX')
exit
Notice: be sure to use your
API key
and theprice_id
of your product (if you are logged in, the provided examples should already have your keys and price_id).
- Add
dotenv
. A.env
file can keep the API keys out of the code. - Add the API key in a dotenv file.
- Add
gem 'dotenv-rails', groups: [:development, :test]
to Gemfile and runbundle install
. - Create
.env
file and addSTRIPE_API_KEY="XXXXXXXXXXX"
. Make sure that.env
is in.gitignore
. - Update
stripe.rb
:Stripe.api_key = ENV["STRIPE_API_KEY"]
. - Test again in your console that everything is still working:
rails c
Stripe.api_key
exit
-
Tied to a user
-
active
boolean -
Stores Stripe user_id for retrieval
-
Create the subscription model:
rails generate model subscription stripe_user_id:string active:boolean user:references
- Update
create_subscriptions.rb
migration file to ensureactive
can't be null and is false by default:
t.boolean :active, null: false, default: false
- Add the subscription relation to
user.rb
model:
has_one :subscription
- Make sure that the user relation is set in
subscription.rb
model:
belongs_to :user
-
Run
rake db:migrate
-
Add an active record callback in the
user.rb
model to generate a default subscription to every new user:
after_create :create_subscription
def create_subscription
Subscription.create(user_id: id) if subscription.nil?
end
- Add a new migration to ensure previous users also have a subscription:
rails g migration AddSubscriptionToPreviousUsers
- Update the
add_subscription_to_previous_users.rb
migration file with theup
function. More details at active record migrations:
def up
User.all.each do |user|
user.create_subscription
end
end
- Run
rake db:migrate
We should see that a subscription is created for every existing user.
- You can also verify with rails console:
rails c
User.last.subscription
exit
- Generate a users controller as well as the
info
route where users can manage their account information:
rails generate controller users info
- Update
routes.rb
to indicate the controller's actionusers#info
:
get '/users/info', to: 'users#info'
- Update
users_controller.rb
to add the devise helperauthenticate_user
and the@subscription
parameter to use it in the view:
before_action :authenticate_user!
def info
@subscription = current_user.subscription
end
- Update
_nav.html.erb
to add a link tousers_info_path
:
<% if user_signed_in? %>
<li class="nav-item">
<%= link_to current_user.email, users_info_path %>
</li>
- Update
app/views/users/info.html.erb
to show if the user issubscribed
orunsubscribed
:
<%= current_user.email %>
<% if @subscription.active %>
subscribed
<% else %>
unsubscribed
<% end %>
- Navigate to
localhost:3000/users/info
to see the new changes.
- Client-side JavaScript library
- Credit card information never touches our servers
- A Stripe
token
is used to communicate back and forth - Gather credit card information in a form
- Send it directly to Stripe from the browser
- Recommended by Stripe
- Local Subscription model becomes super light-weight
- Using the Stripe token, we create a subscription with the Stripe Ruby library
- The corresponding user_id from Stripe is stored locally for easy retrieval
Have a look at Create fixed-price subscriptions for subscriptions implementation.