How to Create a Django Stripe SaaS Boilerplate

Let’s Create a SaaS Demo using Stripe and Django.

Getting Started

 


Install Django

Windows Command Prompt

C:\Users\Owner\desktop> py -m venv env

C:\Users\Owner\desktop> cd env

C:\Users\Owner\desktop\env> Scripts\activate

(env)C:\Users\Owner\desktop\env>pip install django

(env)C:\Users\Owner\desktop\env>django-admin startproject mysite

(env)C:\Users\Owner\desktop\env> cd mysite

(env)C:\Users\Owner\desktop\env\mysite> py manage.py startapp main

(env) C:\Users\Owner\Desktop\Code\env\mysite>py manage.py runserver

 

Install Django Crispy Forms

Windows Command Prompt

(env)C:\Users\Owner\desktop\env>pip install django-crispy-forms

 

 

Configure Django

env » mysite » mysite » settings.py

...

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'main',
    'crispy_forms',
]

CRISPY_TEMPLATE_PACK = 'bootstrap4'
...


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    BASE_DIR / "static",
    '/var/www/static/',
]

...

MESSAGE_TAGS = {
        messages.DEBUG: 'alert-secondary',
        messages.INFO: 'alert-info',
        messages.SUCCESS: 'alert-success',
        messages.WARNING: 'alert-warning',
        messages.ERROR: 'alert-danger',
 }

 

Add the custom stylesheet

env » my site » (NewFolder) static » (NewFolder) main » (NewFolder) ‘CSS’

   /*client images */
    .client {
      height:60px; 
      width:60px; 
      border-radius: 50%; 
      object-fit:cover;
    }

    /*quote svgs*/
    .bi-chat-square-quote{
      color:rgb(108,99,255, 0.3);
    }

    /*badge color*/
    .badge{
      background-color:rgb(108,99,255, 0.2) !important;
      color:rgb(108,99,255) !important;
    }

    /*stars color */
    .bi-star, .bi-star-fill, .bi-star-half{
      color:#ffc107;
    }

    /* cta text*/
    .cta{
      font-size: 70px;
      line-height: 75px;
      font-family: 'Roboto Slab', serif;
    }


    .navbar-brand{
      font-family: 'Roboto Slab', serif;
    }

    .cta-span{
      color:rgb(108,99,255, 0.8) !important;
    }

    .bg-primary{
      background:rgb(108,99,255) !important;
    }

    .border-primary {
      border: 1px solid rgb(108,99,255) !important;
    }


    .btn-primary, .btn-primary:visited {
        font-weight:600;
        background: rgb(255,86,120);
        border: 1px solid rgb(255,86,120);
        color: white !important;
        border-radius:0px;
    }
    
    .btn-primary:hover, .btn-primary:active, .btn-primary:focus, 
    .btn-outline-primary:hover, .btn-outline-primary:active, .btn-outline-primary:focus {
       background: rgb(255,61,100) !important;
       border: 1px solid rgb(255,61,100) !important;
       border-radius:0px;
       color:white !important;
    }

    .btn-outline-primary, .btn-outline-primary:visited {
        font-weight:600;
        background: transparent;
        border: 1px solid rgb(255,86,120);
        color: rgb(255,61,100);
        border-radius:0px;
    }
    
    .form-control{
      background:#F2F2F2;
      border-radius:0px;
      border:none;
      height:48px !important;
    }

    /*background circle for icons*/
    .numberCircle {
      border-radius: 50%;
      width: 60px;
      height: 60px;
      padding: 8px;
      background:rgb(255,86,120, 0.2);
      border: none;
      color: rgb(255,86,120);
      text-align: center;
      font-size: 25px;
    }

    p{
      line-height: 25px;
    }

    .nav-pills .nav-link.active, .nav-pills .show>.nav-link {
    color: #fff !important;
    background-color:rgb(138,131,255);
    }

    .nav-link:hover{
      color: rgb(138,131,255) !important;
    }

    .footer-social-link:hover {
      color:rgb(137,130,255) !important;
    }

    @media only screen and (max-width: 600px) {  
        .cta{
          font-size: 50px;
          line-height: 55px;
          font-family: 'Roboto Slab', serif;
        }

     }

    @media only screen and (max-width:768px){
      /*background circle for icons*/
        .numberCircle {
          border-radius: 50%;
          width: 60px;
          height: 60px;
          padding: 8px;
          background:rgb(255,86,120, 0.2);
          border: none;
          color: rgb(255,86,120);
          text-align: center;
          font-size: 25px;
          margin-left: auto;
          margin-right: auto;
        }
    }

Attach a custom “stylesheet” to the project.

 

Add the images

env » my site » (NewFolder) static » (NewFolder) main » (NewFolder) image

 

Django Stripe SaaS Boilerplate

 

 

Create a navbar

env » my site »main »templates »main »(NewFolder) includes » (NewFile) navbar.html.

<nav class="navbar navbar-expand-lg navbar-light bg-transparent">
  <div class="container-fluid">
    <a class="navbar-brand" href="/">
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="color: rgb(137,130,255);" fill="currentColor" class="bi bi-bar-chart-fill" viewBox="0 0 16 16">
        <path d="M1 11a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-3zm5-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm5-5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V2z"/>
      </svg>
      Track Smart
    </a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarText">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
      {% if user.is_authenticated %}
        <li class="nav-item">
          <a class="nav-link" href="/dashboard">Dashboard</a>
        </li>
         <li class="nav-item">
          <a class="nav-link" href="/logout">Logout</a>
        </li>
         {% else %}
        <li class="nav-item">
          <a class="nav-link" href="#">Features</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="/pricing">Pricing</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="/login">Login</a>
        </li>
      {% endif %}
      </ul>
      {% if user.is_authenticated %}
        <p>Welcome, {{user.username}}</p>
      {% else %}
        <a href="/register" class="btn btn-outline-primary" type="button">Get Started</a>
      {% endif %}
    </div>
  </div>
</nav>

 

Create a footer

env »my site »main »templates »main »(New.Folder) includes »(NewFiles) footer.html.

<div class="container-fluid text-center p-5">
	<div class='row'>
		<div class='col-lg-6 col-md-12 text-center text-md-left'>
			<p>© Company</p>
		</div>
		<div class='col-lg-6 col-md-12 text-center text-md-left'>
			<a href="#" class="text-dark footer-social-link"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="mx-2" fill="currentColor" class="bi bi-facebook" viewBox="0 0 16 16">
				<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951z"/>
			</svg></a>
			<a href="#" class="text-dark footer-social-link"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="mx-2" fill="currentColor" class="bi bi-instagram" viewBox="0 0 16 16">
				<path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334z"/>
			</svg></a>
			<a href="#" class="text-dark footer-social-link"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="mx-2" fill="currentColor" class="bi bi-twitter" viewBox="0 0 16 16">
				<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/>
			</svg></a>

		</div>
	</div>
	
</div>

Let’s also attach a footer including any social media icon links.

 

 

Create a Django header.html

env »my site »main »templates »main »(New.File) header.html

<!DOCTYPE html>
<html lang="en">
<head>
	{% load static %}
	<!-- Required meta tags -->
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">

	<!-- Bootstrap CSS -->
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">

	<!--Custom CSS-->
	<link rel="stylesheet" href="{% static 'main/css/stylesheet.css'%}" type="text/css">

	<!--Google Fonts-->
	<link rel="preconnect" href="https://fonts.gstatic.com">
	<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@600&display=swap" rel="stylesheet">

	<title>Django + Stripe SaaS</title>
</head>
<body>
	{% include "main/includes/navbar.html" %}

	{% include 'main/includes/messages.html' %}

	<div class="my-5">
		{% block content %}

		{% endblock %} 
	</div>

	{% include 'main/includes/footer.html' %}

	<!-- Optional JavaScript; choose one of the two! -->

	<!-- Option 1: Bootstrap Bundle with Popper -->
	<script class="lazy" data-class="lazy" src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
</body>
</html>

 

Create a homepage

env » my site »main »templates »main »(New.File) home.html

{% extends 'main/header.html' %}
{% block content %}
{% load static %}
<!--CTA-->
<div class="container text-center p-lg-5 p-3">
	<div class="row p-lg-5 p-3">
		<div class="col-lg-6 col-md-12 mb-4">
			<h1 class="cta">Track your monthly spending <span class="cta-span">easily.</span></h1>
			<h5 class="text-muted lead my-3">Don't run away from your finances. Jump right in.</h5>
			<form class="my-5" method="GET" action="/register">
				<div class="row">
					<div class="col-xl-6 col-lg-12 mb-4">
						<div class="form-floating mb-3">
							<input type="text" name="email" class="form-control" id="floatingTextInput1" placeholder="email@example.com">
							<label for="floatingTextInput1">Enter email address...</label>
						</div>
					</div>
					<div class="col-xl-6 col-lg-12">
						<div class="d-grid">
							<button class="btn btn-primary btn-lg" type="submit">Join ➜</button>
						</div>
					</div>
				</div>
			</form>
		</div>
		<div class="col-lg-6 col-md-12 my-auto">
			<img class="img-fluid" class="lazy" src="{% static 'main/img/jumping.svg' %}" alt="jumping">
		</div>
	</div>
</div>
<!--End CTA-->


<!--Value Prop-->

<div class="container p-lg-5 p-3">
	<div class="row p-lg-5 p-3 my-5">
		<div class="col-lg-6 col-md-12 order-lg-2 order-md-1 my-auto">
			<span class="badge rounded-pill">Track monthly spending</span>
			<h1 class="my-3">Reach your monthly budget</h1>
			<p class="text-muted">Set weekly and monthly spending habits for your entire team. Get alerts when your close to your limit.</p>
		</div>
		<div class="col-lg-6 col-md-12 order-lg-1 order-md-2 my-auto">
			<img class="img-fluid" class="lazy" src="{% static 'main/img/sprinting.gif' %}" alt="sprinting">
		</div>
	</div>
</div>

<div class="container p-lg-5 p-3">
	<div class="row p-lg-5 p-3 my-5">
		<div class="col-lg-6 col-md-12 my-auto">
			<span class="badge rounded-pill">Upload images and PDFs</span>
			<h1 class="my-3">Spending made easy</h1>
			<p class="text-muted">No need to keep physical copies of receipts or bills. Upload your images and PDFs with a click of a button to your account.</p>
		</div>
		<div class="col-lg-6 col-md-12 my-auto">
			<img class="img-fluid" class="lazy" src="{% static 'main/img/clumsy.svg' %}" alt="clumsy">
		</div>
	</div>
</div>
<!--End Value Prop-->



<!--Features-->
<div class="container p-lg-5 p-3 text-center text-md-start">
	<h1 class="cta mt-5 text-center">Healthy spending made easy</h1>
	<h5 class="text-muted lead mt-3 mb-5 text-center">We build a platform designed for small businesses. Track your spending, marketing budget and more all one a simple dashboard.</h5>
	<div class="row p-lg-5 px-3 py-5">
		<div class="col-sm-12 col-md-6 col-lg-3 pb-4">
			<div class="numberCircle my-3">
				<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-phone" viewBox="0 0 16 16">
					<path d="M11 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6zM5 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H5z"/>
					<path d="M8 14a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
				</svg>
			</div>
			<h5 class="card-title">Mobile Friendly</h5>
			<p class="card-text">Monitor your expenses on the go</p>
		</div>
		<div class="col-sm-12 col-md-6 col-lg-3 pb-4"> 
			<div class="numberCircle my-3">
				<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16">
				  <path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
				</svg>
			</div>
			<h5 class="card-title">Archived Files</h5>
			<p class="card-text">Refer to archived files forever</p>

		</div>
		<div class="col-sm-12 col-md-6 col-lg-3 pb-4">
			<div class="numberCircle my-3">
				<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-bag-plus" viewBox="0 0 16 16">
				  <path fill-rule="evenodd" d="M8 7.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0v-1.5H6a.5.5 0 0 1 0-1h1.5V8a.5.5 0 0 1 .5-.5z"/>
				  <path d="M8 1a2.5 2.5 0 0 1 2.5 2.5V4h-5v-.5A2.5 2.5 0 0 1 8 1zm3.5 3v-.5a3.5 3.5 0 1 0-7 0V4H1v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-3.5zM2 5h12v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5z"/>
				</svg>
			</div>
			<h5 class="card-title">Upload Receipts</h5>
			<p class="card-text">Upload an image of receipt of your purchases</p>

		</div>
		<div class="col-sm-12 col-md-6 col-lg-3 pb-4">
			<div class="numberCircle my-3">
				<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-alarm" viewBox="0 0 16 16">
				  <path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/>
				  <path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/>
				</svg>
			</div>
			<h5 class="card-title">Set Alerts</h5>
			<p class="card-text">Monitor your spending habits with automatic alerts</p>
		</div>
	</div>
</div>
<!--End Features-->



<!--Value Prop #2-->
<div class="container p-lg-5 p-3">
	<div class="row p-lg-5 p-3 my-5">
		<div class="col-lg-6 col-md-12 order-lg-2 order-md-1 my-auto">
			<span class="badge rounded-pill">Save your time</span>
			<h1 class="my-3">Efficiently allocate time</h1>
			<p class="text-muted">Don't send hours looking over your finances every month. Manage expenses as they happen so you can free up your time.</p>
		</div>
		<div class="col-lg-6 col-md-12 order-lg-1 order-md-2 my-auto">
			<img class="img-fluid" class="lazy" src="{% static 'main/img/sitting-reading.svg' %}" alt="sitting-reading">
		</div>
	</div>
</div>

<div class="container p-lg-5 p-3">
	<div class="row p-lg-5 p-3 my-5">
		<div class="col-lg-6 col-md-12 my-auto">
			<span class="badge rounded-pill">Setup made easy</span>
			<h1>Out of the box configuration</h1>
			<p class="text-muted">Create an account and get going. Everything is already configured for easy file upload and tracking on the dashboard.</p>
		</div>
		<div class="col-lg-6 col-md-12 my-auto">
			<img class="img-fluid" class="lazy" src="{% static 'main/img/unboxing.svg' %}" alt="unboxing">
		</div>
	</div>
</div>
<!--End Value Prop #2-->


<!--Testimonials-->
<div class="container p-lg-5 p-3 text-center text-md-start">
	<div class="row p-lg-5 p-3 my-5">
		<div class="col-lg-6 col-md-12 my-5">
			<svg xmlns="http://www.w3.org/2000/svg" width="3em" height="3em" fill="currentColor" class="my-3 bi bi-chat-square-quote" viewBox="0 0 16 16">
				<path d="M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-2.5a2 2 0 0 0-1.6.8L8 14.333 6.1 11.8a2 2 0 0 0-1.6-.8H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2.5a1 1 0 0 1 .8.4l1.9 2.533a1 1 0 0 0 1.6 0l1.9-2.533a1 1 0 0 1 .8-.4H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
				<path d="M7.066 4.76A1.665 1.665 0 0 0 4 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112zm4 0A1.665 1.665 0 0 0 8 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112z"/>
			</svg>
			<figure>
				<blockquote class="blockquote">
					<p>Great product. Love the easy-to-use interface. Saves my employees at least 2 hours by not having to constantly check up with one another and reference a spreadsheet.</p>
				</blockquote>
						<img class="lazy" src="{% static 'main/img/ben.png' %}" class="img-fluid client"><br>
						<b>Ben Smith</b>
						<br><small>Founder, Clean Paws</small><br>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
							<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
						</svg>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
							<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
						</svg>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
							<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
						</svg>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
							<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
						</svg>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star" viewBox="0 0 16 16">
							<path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288L8 2.223l1.847 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.565.565 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z"/>
						</svg>
			</figure>
		</div>
		<div class="col-lg-6 col-md-12 my-5">
			<svg xmlns="http://www.w3.org/2000/svg" width="3em" height="3em" fill="currentColor" class="my-3 bi bi-chat-square-quote" viewBox="0 0 16 16">
				<path d="M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-2.5a2 2 0 0 0-1.6.8L8 14.333 6.1 11.8a2 2 0 0 0-1.6-.8H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2.5a1 1 0 0 1 .8.4l1.9 2.533a1 1 0 0 0 1.6 0l1.9-2.533a1 1 0 0 1 .8-.4H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
				<path d="M7.066 4.76A1.665 1.665 0 0 0 4 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112zm4 0A1.665 1.665 0 0 0 8 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112z"/>
			</svg>
			<figure>
				<blockquote class="blockquote">
					<p>I never need to worry about keeping track of receipts or spending. Everything is gets uploaded directly to the site.</p>
				</blockquote>
						<img class="lazy" src="{% static 'main/img/karen.png' %}" class="img-fluid client"><br>
						<b>Karen Adams</b>
						<br><small>Owner, Restaurant Cafe</small><br>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
							<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
						</svg>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
							<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
						</svg>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
							<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
						</svg>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
							<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
						</svg>
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star" viewBox="0 0 16 16">
							<path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288L8 2.223l1.847 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.565.565 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z"/>
						</svg>   
			</figure>
		</div>
	</div>
</div>
<!--End Testimonials-->


<!--Second CTA-->
<div class="container text-center p-lg-5 p-3">
	<h1 class="cta my-5">Get started today</h1>
	<h5 class="text-muted lead my-4">Stay on top of your small business's finances. Jump right in.</h5>
	<form  method="GET" action="/register">
		<div class="form-floating mb-3 mx-auto" style="max-width: 40rem">
			<input type="text" name="email" class="form-control" id="floatingTextInput1" placeholder="email@example.com">
			<label for="floatingTextInput1">Enter email address...</label>
			<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
		</div>
		<button class="btn btn-primary btn-lg my-4" type="submit">Join ➜</button>
	</form>
</div>
<!-- End Second CTA-->

{% endblock %}

 

 

 

Create a register page

env »my site »main » templates »main » (New File) login.html

{% extends 'main/header.html' %}
	{% block content %}
	{% load crispy_forms_tags %}
	{% load static %}
<div class="container p-md-3 p-sm-2">
	<div class="row g-0">
		<div class="col d-none d-lg-block my-auto">
			<img class="img-fluid" class="lazy" src="{% static 'main/img/sitting-reading.svg' %}" alt="sitting-reading">
		</div>
		<div class="col my-auto">
	<div class="mx-auto" style="max-width:30rem">
	<h1 class="cta my-5 text-center">Register</h1>
	<form method="POST">
		{% csrf_token %}
		{{ register_form|crispy }}                    
		<button class="btn btn-primary my-5" type="submit">Register</button>
	</form>
	</div>
	</div>
</div>
</div>
	{% endblock %}

 

Create a login page

env > my site > main > templates > main > (New File) login.html

{% extends 'main/header.html' %}
	{% block content %}
	{% load crispy_forms_tags %}
	{% load static %}
<div class="container p-md-3 p-sm-2">
	<div class="row g-0">
		<div class="col d-none d-lg-block my-auto">
			<img class="img-fluid" class="lazy" src="{% static 'main/img/reading-side.svg' %}" alt="reading-side">
		</div>
		<div class="col my-auto">
	<div class="mx-auto" style="max-width:30rem">
	<h1 class="cta my-5 text-center">Login</h1>
	<form method="POST">
		{% csrf_token %}
		{{ login_form|crispy }}                    
		<button class="btn btn-primary my-5" type="submit">Login</button>
		<p class="text-center">Don't have an account? <a href="/register">Create an account</a>.</p>
	</form>
	</div>
	</div>
</div>
</div>
	{% endblock %}

 

Install Stripe

» Windows Command Prompt

(env)C:\Users\Owner\desktop\env>pip install --upgrade stripe

» Install Stripe Now.

Create a subscription modal

env »my site » main »models.py

from django.db import models

# Create your models here.
class Subscription(models.Model):
	name = models.CharField(max_length=100)
	amount = models.DecimalField(decimal_places=2, max_digits=5)
	stripe_product_id = models.CharField(max_length=100)

	def __str__(self):
		return self.name

See Also Kotlin Time Date picker Dialog using JR TimeDate Picker Complet project for learner

 

Create a pricing page

env »my site »main » templates » main »(NewFile) pricing.html

{% extends 'main/header.html' %}
{% block content %}
{% load static %}

<script class="lazy" src="https://polyfill.io/v3/polyfill.min.js?version=3.52.1&features=fetch"></script>
<script class="lazy" src="https://js.stripe.com/v3/"></script>

<!--Pricing-->
    <div class="container p-lg-5 p-3">
    	<h1 class="cta text-center">Pricing</span></h1>
      <h5 class="text-muted lead my-3 text-center">Don't run away from your finances. Jump right in.</h5>
      <div class="row p-lg-5 p-3">


        {% for s in subscription %}
        <div class="col-lg-4 col-md-12 mb-4">
          <div class="card h-100 shadow-lg text-center mx-auto sub-cards" style="max-width:20rem">
          	 <div class="card-header sub-header">
			    <h4>{{s.name}}</h4>
			  </div>
            <div class="card-body">    
               <h1>${{s.amount}}<span class="text-muted" style="font-weight: 300">/mo</span></h1>
            
        	<p class="my-3">
             {{s.description|safe|linebreaks}}
          </p>
            
            <div class="d-grid">

              {% if user.is_authenticated %}
            	<button class="btn btn-outline-primary btn-block btn-lg sub-button" onclick="selectPlan('{{s.stripe_api_id}}')">Select</button>
              {% else %}
              <a class="btn btn-outline-primary btn-block btn-lg sub-button" href="/register">Select</a>
              {% endif %}
            </div>
          </div>
          </div>
        </div>
        {% endfor %}


      </div>
    </div>

    <script type="text/javascript">

    //grabs csrftoken
    let cookie = document.cookie
    let cookies = cookie.substring(cookie.indexOf('csrftoken=') + 10)
    let csrfToken = cookies.split(";")[0]

    // Create an instance of the Stripe object with your publishable API key
    var stripe = Stripe("pk_test"); //replace with your Stripe API key

    function selectPlan(pid) {
      fetch("/create-checkout-session", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-CSRFToken": csrfToken, 
        },
        body: JSON.stringify({
          product_id: pid
        })
      })
        .then(function (response) {
          return response.json();
        })
        .then(function (session) {
          return stripe.redirectToCheckout({ sessionId: session.id });
        })
        .then(function (result) {
          // If redirectToCheckout fails due to a browser or network
          // error, you should display the localized error message to your
          // customer using error.message.
          if (result.error) {
            alert(result.error.message);
          }
        })
        .catch(function (error) {
          console.error("Error:", error);
        });
    } 
    </script>
<!--End Pricing-->

{% endblock %}

See Also Time Picker Dialog Window for Android System

Update the urls.py

env »mysite »main » urls.py

from django.urls import path
from . import views

app_name = "main"   


urlpatterns = [
    path("", views.homepage, name="homepage"),
    path("dashboard", views.dashboard, name="dashboard"),
    path("register", views.register_request, name="register"),
    path("login", views.login_request, name="login"),
    path("logout", views.logout_request, name= "logout"),
    path("create-checkout-session", views.create_checkout_session, name="create checkout session"),
    path('success', views.success_request, name="success"),
    path('cancel', views.cancel_request, name="cancel"),
    path('pricing', views.pricing, name="pricing"),

]

See also Make Basic Snake Game in Browser Using HTML

 

Update the views.py

env »my site »main »views.py

from django.shortcuts import  render, redirect
from .forms import NewUserForm
from django.contrib.auth import login, authenticate, logout
from django.contrib import messages
from django.contrib.auth.forms import AuthenticationForm
from .models import Subscription
from django.http import HttpResponse, JsonResponse
import stripe
import json


# Create your views here.
def homepage(request):
	return render(request = request, template_name="main/home.html")

def pricing(request):
	subscription = Subscription.objects.all()
	return render(request = request, template_name="main/pricing.html", context={"subscription":subscription})

def dashboard(request):
	return render(request = request, template_name="main/dashboard.html")


def register_request(request):
	if request.method == "POST":
		form = NewUserForm(request.POST)
		if form.is_valid():
			user = form.save()
			login(request, user)
			messages.success(request, "Registration successful." )
			return redirect("main:pricing")
		messages.error(request, "Unsuccessful registration. Invalid information.")
	email = request.GET.get("email")
	data = {"email":email}
	form = NewUserForm(initial=data)
	return render (request=request, template_name="main/register.html", context={"register_form":form})

def login_request(request):
	if request.method == "POST":
		form = AuthenticationForm(request, data=request.POST)
		if form.is_valid():
			username = form.cleaned_data.get('username')
			password = form.cleaned_data.get('password')
			user = authenticate(username=username, password=password)
			if user is not None:
				login(request, user)
				return redirect("main:dashboard")
			else:
				messages.error(request,"Invalid username or password.")
		else:
			messages.error(request,"Invalid username or password.")
	form = AuthenticationForm()
	return render(request=request, template_name="main/login.html", context={"login_form":form})

def logout_request(request):
	logout(request)
	messages.info(request, "You have successfully logged out.") 
	return redirect("main:homepage")

def create_checkout_session(request):
	if request.method == "POST":
		YOUR_DOMAIN = 'http://127.0.0.1:8000'
		stripe.api_key = 'sk_test' #replace with your Stripe API key
		data = json.loads(request.body)

		customer = stripe.Customer.create(
			email=request.user.email)
		try:
			checkout_session = stripe.checkout.Session.create(
				payment_method_types=['card'],
				line_items=[
					{
						'price': data["product_id"],
						'quantity': 1,
					}
				],
				mode='subscription',
				success_url=YOUR_DOMAIN + '/success',
				cancel_url=YOUR_DOMAIN + '/cancel',
				customer_email = customer.email,
				)
			return JsonResponse({'id': checkout_session.id})
		except Exception as e:
			return JsonResponse({'error': (e.args[0])}, status =400)
	return JsonResponse({'error':'No GET request allowed.'})

def success_request(request):
	messages.info(request, "You have successfully subscribed.") 
	return redirect("main:dashboard")

def cancel_request(request):
	messages.info(request, "Payment Failed. Please try again.") 
	return redirect("main:pricing")

Read Vue.js Mozilla PDF Document Viewer, PDF.js Library Using pdf.js – dist in Js Complete Project For learner

Leave a Comment

Your email address will not be published. Required fields are marked *