Part 1/5 — Adding, Managing, and Keeping track of subscription payments using Stripe and Mongo for your SaaS app

Part 1 covers adding Basic and Pro subscription plans using Stripe

Image for post
Image for post

Setup the project

{
"name": "stripe-subscriptions-nodejs",
"version": "1.0.0",
"author": "sssaini",
"description": "Add Stripe Subscriptions Payments using Node.js",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.5",
"ejs": "^3.1.5",
"express": "^4.17.1",
"express-session": "^1.17.1",
"mongoose": "^5.10.11",
"stripe": "^8.114.0",
"memorystore": "^1.6.4"
}
}
npm install

Set up a basic Express Server

const bodyParser = require('body-parser')
const express = require('express')

const app = express()
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

app.use(express.static('public'))
app.engine('html', require('ejs').renderFile)

app.get('/', async function (
req,
res,
next
) {
res.send('Hello World!')
})

const port = 4242

app.listen(port, () => console.log(`Listening on port ${port}!`))
npm start

Building the UI

UI — Login Screen

// ...

app.get('/', async function (
req,
res,
next
) {
res.status(200).render('login.ejs')
})

// ...
<html lang="en">
<body class="text-center">
<form class="form-login" action="/login" method="post">

<h1>Log in</h1>
<input
type="email"
name="email"
placeholder="Email address"
required
/>
<button type="submit">
Sign in
</button>
</form>

<script
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"
></script>
</body>
</html>
Image for post
Image for post
npm start

Create a Stripe Customer

const stripe = require('stripe')
const STRIPE_SECRET_KEY = 'sk_test_xxx'

const Stripe = stripe(STRIPE_SECRET_KEY, {
apiVersion: '2020-08-27'
})

const addNewCustomer = async (email) => {
const customer = await Stripe.customers.create({
email,
description: 'New Customer'
})

return customer
}

const getCustomerByID = async (id) => {
const customer = await Stripe.customers.retrieve(id)
return customer
}

module.exports = {
addNewCustomer,
getCustomerByID
}
const Stripe = require('./src/connect/stripe')

//..

app.post('/login', async (req, res) => {
const { email } = req.body
const customer = await Stripe.addNewCustomer(email)
res.send('Customer created: ' + JSON.stringify(customer))
})
{"id":"cus_IaFpg44TGYsJNT","object":"customer","address":null,"balance":0,"created":1608145534,"currency":null,"default_source":null,"delinquent":false,"description":"SaaSBase Customer","discount":null,"email":"sukh@saasbase.dev","invoice_prefix":"756F8AF0","invoice_settings":{"custom_fields":null,"default_payment_method":null,"footer":null},"livemode":false,"metadata":{},"name":null,"next_invoice_sequence":1,"phone":null,"preferred_locales":[],"shipping":null,"tax_exempt":"none"}
Image for post
Image for post

Save the user session

const session = require('express-session')
var MemoryStore = require('memorystore')(session)
const UserService = require('./src/user')

app.use(session({
saveUninitialized: false,
cookie: { maxAge: 86400000 },
store: new MemoryStore({
checkPeriod: 86400000
}),
resave: false,
secret: 'keyboard cat'
}))

app.post('/login', async (req, res) => {
// ..
req.session.customerID = customer
res.send('customer created:' + JSON.stringify(customer))
})

Add products to the Stripe Dashboard

Image for post
Image for post
Image for post
Image for post
const productToPriceMap = {
BASIC: 'price_xxx',
PRO: 'price_xxx'
}

Create the Checkout Screen and add a Trial Period

<html>
<head>
</head>
<body>
<input type="radio" id="basic" name="product" value="basic" />
<label for="basic">Basic for $10</label><br />
<input type="radio" id="pro" name="product" value="pro" />
<label for="pro">Pro for $12</label><br />

<button class="btn btn-primary" id="checkout-button" type="submit">
Buy now
</button>

<script
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"
></script>
<script type="text/javascript" src="https://js.stripe.com/v3/"></script>
<script type="text/javascript" src="./js/account.js"></script>
</body>
<html>
$(document).ready(function () {
const PUBLISHABLE_KEY = 'pk_test_xxx'

const stripe = Stripe(
PUBLISHABLE_KEY)
const checkoutButton = $('#checkout-button')

checkoutButton.click(function () {
const product = $('input[name="product"]:checked').val()

fetch('/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
product
})
})
.then((result) => result.json())
.then(({ sessionId }) => stripe.redirectToCheckout({ sessionId }))
})
})
app.get('/account', async function (req, res) {
res.render('account.ejs')
})
app.post('/login', async function (req, res) {  
// ..
req.session.email = email
res.redirect('/account')
})
// ..
const createCheckoutSession = async (customer, price) => {
const session = await Stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
customer,
line_items: [
{
price,
quantity: 1
}
],
subscription_data: {
trial_period_days: 14
},

success_url: `http://localhost:4242/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `http://localhost:4242/failed`
})

return session
}

module.exports = {
addNewCustomer,
createCheckoutSession
}`
app.post('/checkout', async (req, res) => {
const { customer } = req.session
const session = await Stripe.createCheckoutSession(customer, productToPriceMap.BASIC)

console.log(session)
res.send({ sessionId: session.id })
})
Image for post
Image for post

Add Success and Fail endpoints

app.get('/success', (req, res) => {
res.send('Payment successful')
})

app.get('/failed', (req, res) => {
res.send('Payment failed')
})
npm start

Next Steps

Builds SaaS products — http://saasbase.dev/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store