Table of Contents
This project is a boilerplate for any online subscription business. It handles user registration, authentication, and billing through Stripe and can be utilized for pretty much any online business from this point. It utilizes Phoenix PubSub
and GenServer
and to keep the data stored in the local database in sync with Stripe.
- Pricing plans page
- Payment form page
- Active subscriptions only page
- User credentials update page
- Login page
- Register page
- Forgot Password? page
- Postgres - 14.2.0
- erlang - 24.2.1
- Elixir - 1.13.3-otp-24
- Phoenix - 1.6.6
- Phoenix LiveView - 0.1.75
- NodeJS - 16.2.0
- TailwindCSS - 3.0.23
- Stripe JS
- stripity_stripe - 2.13.0
To get a local copy up and running follow these simple steps.
-
Install
erlang
,Elixir
,NodeJS
,Postgres
andstripe/stripe-cli/stripe
.- With homebrew the commands are:
brew update brew install erlang elixir nodejs postgres stripe/stripe-cli/stripe
- Or if you prefer
asdf
brew update brew install asdf postgres stripe/stripe-cli/stripe asdf plugin-add erlang asdf plugin-add elixir asdf plugin-add nodejs asdf install erlang latest asdf install elixir latest asdf install nodejs latest asdf global erlang latest asdf global elixir latest asdf global nodejs latest
-
Sign up for a Stripe developer's account here here to get your API keys.
- From there you will have to create 2 different products each with two prices. This can be done by going to the Stripe UI dashboard and clicking on Products and then clicking on the blue "Add Product" button. I used what is below.
Product 1 Name: Standard Price 1: $150/year Price 2: $15/month Product 2 Name: Premium Price 1: $300/year Price 2: $30/month
-
Login to the Stripe CLI -
stripe login
-
Run the command
stripe listen --forward-to localhost:4000/webhooks/stripe
, in a terminal window.- Note: this window will have to be kept open the entire time your app is running, you should see a similar message to what is below in your terminal.
⡿ Getting ready... > Ready! You are using Stripe API Version [XXX]. Your webhook signing secret is <YOUR KEY> (^C to quit)
-
Clone this Repo and enter the directory.
git clone https://github.com/herbedev/live_view_stripe && cd live_view_stripe
-
Install dependencies with
mix deps.get
. -
Install npm assets.
cd assets && npm ci && cd ../
-
Create and migrate your database with
mix ecto.setup
-
Setup env variables by running the commands provided in
.env.example
in your terminal with your keys.STRIPE_WEBHOOK_SIGNING_KEY
comes from step 1.STRIPE_SECRET
andSTRIPE_PUBLISHABLE
come from the Stripe Dashboard.
-
Start Phoenix server with
iex -S mix phx.server
- Now you can visit
localhost:4000
from your browser.
- Now you can visit
-
Sync Products and Plans by running these commands inside of the IEX session for the server. (Same terminal you ran the previous step) Order is important.
LiveViewStripe.SyncProducts.run
LiveViewStripe.SyncPlans.run
-
Now you can visit
http://localhost:4000
in your browser and you will see the app running.
You can run unit tests with the command
mix test
erDiagram
users {
bigint id
character email
character hashed_password
timestamp confirmed_at
timestamp inserted_at
timestamp updated_at
}
users_tokens {
bigint id
bigint user_id
bytea token
character context
character sent_to
timestamp inserted_at
}
billing_products {
bigint id
character stripe_id
character stripe_product_name
timestamp inserted_at
timestamp updated_at
}
billing_plans {
bigint id
character stripe_id
character stripe_plan_name
integer amount
bigint billing_product_id
timestamp inserted_at
timestamp updated_at
}
billing_customers {
bigint id
character stripe_id
bigint user_id
timestamp inserted_at
timestamp updated_at
}
billing_subscriptions {
bigint id
character stripe_id
character status
timestamp current_period_end_at
timestamp cancel_at
bigint plan_id
bigint customer_id
timestamp inserted_at
timestamp updated_at
}
billing_customers }o--|| users : ""
users_tokens }o--|| users : ""
billing_plans }o--|| billing_products : ""
billing_subscriptions }o--|| billing_plans : ""
billing_subscriptions }o--|| billing_customers : ""
Made with Mermaid.js
graph TD;
A[1. Create Stripe Customer] --> B[2. Create Stripe SetupIntent] --> C[3. Confirm Stripe SetupIntent with Payment Method - <br> done with Stripe.js] --> D[4. Create Stripe Subscription]
- Stripe Customer have a 1:1 relationship with users in your app, on user registration a Stripe Customer is generated.
- On mount of the payment form LiveView, a Stripe setupIntent is created for the current user.
- On submit of the payment form; Stripe JS is used to confirm that setupIntent and stay PCI compliant.
- Once the the setupIntent is confirmed, Stripe will attach the payment method the corresponding customer for the setup intent.
- An event is forwarded from the Stripe CLI and handled on the
/webooks
post route. - Based on the event pushed to the post route, a variety of actions are taken.
Made with Mermaid.js
I grouped my commits into 10 sections to make it easier to follow. Each commit message is prefaced with a number,
ex: [ 1 ] Commit Message Goes Here
that number corresponds with a step number below.
- I used the commands:
mix phx.new LiveViewStripe
andmix phx.gen.auth Accounts User users
- To generate the project that we are going to use along with the user authentication skeleton.
- Since I want to sync my local database with what is on Stripe - I used the following commands to generate schemas, migrations, contexts and tests for
Customer
,Plan
,Product
andSubscription
with only a few small adjustments made.
# Command 1
mix phx.gen.context Billing Product products --table billing_products stripe_id stripe_product_name
# Command 2
mix phx.gen.context Billing Plan plans --table billing_plans billing_product_id:references:billing_products stripe_id stripe_plan_name amount:integer
# Command 3
mix phx.gen.context Billing Customer customers --table billing_customers user_id:references users
# Command 4
mix phx.gen.context Billing Subscription subscriptions --table billing_subscriptions plan_id:references:billing_plans customer_id:references:billing_customers stripe_id status current_period_end_at:naive_datetime cancel_at:naive_datetime
-
I am working with a service that will not be available when my tests are running; therefore, I have to create a
MockStripe
service that will return equivalent responses to what Stripe JS orstripity_stripe
would return. -
Next I need a few services to sync both
Products
andPlans
with my local database in the caseproducts/prices
are modified or additionalprices/products
are created through the Stripe Developer's Dashboard.This is also needed for the initial setup as well.
-
My local database holds all the important of each Stripe object, I have already created functions to sync plans and products; so it only makes sense to create a Stripe customer on successful user registration. This service takes care of creating the stripe customer.
-
This is where the terminal forwarding webhooks from stripe to our server comes in to play.
- I will need to create a
post
route to handle the information being forwarded to our app from the Stripe CLI. - I also need to
construct
aStripe.Event
and notify all subscribers to the channel of the event.
- I will need to create a
-
The billing context got a little out of hand; I need to refactor the context and the tests to break everything out into one of
Customer
,Plan
,Product
orSubscription
modules. -
I need a way to display different subscriptions plans to the public and let them choose which one is perfect for them.
- Create a parent LiveView
page_live
. - Create a LiveComponent
product_component
- is the contents of the pricing page and was made a LiveComponent for easy reuse. - Add the route for this LiveView to the router.
- Create a parent LiveView
-
I need a payment form to collect and verify the payment information for our customer, I also need a page that is only viewable to members with an active subscription..
- Create the Members only LiveView and route.
- Create
Plug
:require_active_subscription
for members only route. - Create New Subscription LiveView and route.
- Phoenix hook to handle Stripe payments with Stripe.JS in order to stay PCI compliant.
-
The default phoenix styles aren't that great, lets implement Tailwind CSS and give these pages a facelift.
Distributed under the MIT License. See LICENSE.txt
for more information.
John Herbener - herbener.net - john@herbener.cloud | Project Link: https://github.com/herbedev/live_view_stripe
Loosely based off the tutorial from https://fullstackphoenix.com/