Django E-Commerce Tutorial with Stripe

In this tutorial, we will build a basic e-commerce website in Django that uses Stripe to process single payments. The website will have a homepage for orders, a success page when an order goes through, and a cancel page if the order fails. This foundation can later be extended into full-featured e-commerce applications that store products and sales in a database and utilize webhooks to confirm payments, even subscriptions.

Table of Contents

Initial Setup

To start, open up your terminal and create a new Python virtual environment.

# Windows
$ python -m venv .venv
$ .venv\Scripts\Activate.ps1
(.venv) $

# macOS
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $

Then install Django, create a new project called django_project, and run migrate to initialize the new SQLite local database. Execute the runserver command to start the local web server and confirm everything works properly.

(.venv) $ python -m pip install django~=5.0.0
(.venv) $ django-admin startproject django_project .
(.venv) $ python manage.py migrate
(.venv) $ python manage.py runserver 

In your web browser, navigate to 127.0.0.1:8000 to see the familiar Django welcome page.

Django welcome page

Django Configuration

We will create a dedicated products app for our logic now using the startapp command.

(.venv) $ python manage.py startapp products

Don't forget to register the app immediately in our settings.py file so that Django knows to look for it.

# django_project/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "products",  # new
]

Let's start by creating the URL paths for the three pages: home, success, and cancel. We could do this in an app-level file like products/urls.py, but for simplicity, we'll place them in the project-level django_project/urls.py file.

# django_project/urls.py
from django.contrib import admin
from django.urls import path

from products import views


urlpatterns = [
    path("admin/", admin.site.urls),
    path("", views.home, name="home"),
    path("success/", views.success, name="success"),
    path("cancel/", views.cancel, name="cancel"),
]

Next up are our three views that display a template file.

# products/views.py
from django.shortcuts import render


def home(request):
    return render(request, "home.html")

def success(request):
    return render(request, "success.html")

def cancel(request):
    return render(request, "cancel.html")

And lastly, the template files. Create a project-level directory called templates.

(.venv) $ mkdir templates

Then, update django_project/settings.py so that Django's template loader knows to look for it.

# django_project/settings.py
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],  # new

Create three new template files--home.html, success.html, and cancel.html--within the templates folder.

<!-- templates/home.html -->
<h1>Homepage</h1>

<!-- templates/success.html -->
<h1>Success page</h1>

<!-- templates/cancel.html -->
<h1>Cancel page</h1>

That's it! Make sure runserver is running, and then confirm that all three webpages exist.

Home page

Success page

Cancel page

Configure Stripe

Log in to your existing Stripe account or register for a free one. If you only want to use test mode, where we can mock transactions, you don't need to complete the application with bank details. Once registered, head over to the dashboard.

Stripe Dashboard Page

Click on the "Developers" nav link in the upper right-hand corner.

Stripe Developers page

Make sure to activate "Test mode" and then click on the nav link for API keys.

Stripe API Keys page

Stripe provides a "Publishable key" and a "Secret key" that work together to process payment transactions. The publishable (or public) key is used to initiate a transaction and is visible to the client; the secret key is used on our server and should be kept confidential.

For this tutorial, we will hardcode both into the bottom of our django_project/settings.py file. For proper security, the live API keys should be stored in environment variables in production.

# django_project/settings.py
STRIPE_PUBLIC_KEY = "<your_test_public_api_key>"
STRIPE_SECRET_KEY = "<your_test_secret_api_key>"

Now, we need to sell a product. On the lefthand sidebar, click the link for "More +" and then "Product catalog," which takes us to the Products page.

Stripe Products Navbar Link

Click the "+ Add product" button in the upper right-hand corner to create a product to sell.

Stripe Products Page

The required fields are name, one-off or recurring, and the amount. We are processing a one-off payment here. Click the "Add Product" button.

Stripe Add a Product Page

We are redirected to the main "Product catalog" page, where our new product is now visible.

Stripe Product Catalog Page

Click on the new product to open up its page. Under the Pricing section, note the "API ID" which we will use shortly.

Stripe Product Page

Now that we have a product to sell, we will focus on the Django app.

Stripe Hosted Page

If you look at the Stripe docs, the Python example provided is for a Flask Stripe-hosted page. However, we can adapt this for our Django app. At a high level, we need to add an HTML form to the home.html template and then update our views.py file so that when a POST request is sent to Stripe, our server-side Stripe SDK and Stripe Private key are used to generate a new Checkout session. The user is redirected to this unique session and then, if payment is successful, redirected to our success.html page. Otherwise, they will be sent to the cancel.html page.

Let's start by installing the Python library for Stripe, which is available on Github.

(.venv) $ python -m pip install stripe==7.10.0

In our home.html file, add a basic form using the POST method and Django's CSRF protection.

<!-- templates/home.html -->
<h1>Homepage</h1>
<form method="post">
  {% csrf_token %}
  <button type="submit">Purchase!</button>
</form>

Most of the magic happens in our views.py file. The updated code is below, and we'll work through the changes line-by-line. But before getting into specifics, it's essential to understand that at a high level, we are loading in the Stripe Private Key from our settings.py file and using it to create a new Checkout session. The user will be redirected to a custom URL for the Stripe-hosted checkout page and then, after submitting payment, either sent to a success or cancel page.

# products/views.py
from django.shortcuts import render, redirect  # new
from django.conf import settings  # new
from django.urls import reverse  # new

import stripe  # new


def home(request):  # new
    stripe.api_key = settings.STRIPE_SECRET_KEY
    if request.method == "POST":
        checkout_session = stripe.checkout.Session.create(
            line_items=[
                {
                    "price": "<price_API_ID_HERE>",  # enter yours here!!!
                    "quantity": 1,
                }
            ],
            mode="payment",
            success_url=request.build_absolute_uri(reverse("success")),
            cancel_url=request.build_absolute_uri(reverse("cancel")),
        )
        return redirect(checkout_session.url, code=303)
    return render(request, "home.html")


def success(request):
    return render(request, "success.html")


def cancel(request):
    return render(request, "cancel.html")

At the top, we've imported redirect, settings, reverse, and stripe. The first step in our home function-based view is to set the Stripe api_key to the Stripe Private Key for our account; it is stored in our settings.py file for security and not visible to the user client-side. Next, if a POST request is made a new checkout_session is created and requires, at a minimum, the following attributes: line_items, mode, success_url, and cancel_url. Other attributes are available but are not necessary at this stage.

We define a product to sell with the line_items attribute, using the exact Price ID for our product set on the Stripe website. Later on we might want to have the option of storing multiple IDs in our database to load in, but hardcoding it is fine for example purposes. We also specify the quantity of 1.

Next, we set a mode to "payment" for one-time purchases, but we could also do "subscription" for subscriptions or "setup" to collect customer payment details to reuse later.

After the user places an order, they are sent to either a success_url or a cancel_url. Django's reverse() function is used alongside the URL name of each. We also take advantage of Django's [build_absolute_uri] method to send an absolute URL to Stripe.

The final bit is redirecting to Checkout. After our POST request is sent to Stripe, authenticated, and a new Checkout session is created, Stripe's API will send us a unique checkout_session.url that the user is redirected to.

The full Stripe Checkout flow can take a while to sink in, but that's all the code we need!

Ensure the server is running, and go to the homepage at 127.0.0.1:8000 and click on the "Purchase" button.

Purchase Button Page

You'll be redirected to a unique Stripe Checkout session for the product.

Stripe Checkout Session Page

Fill in the form. For the credit card information, use the number 4242 4242 4242 4242, any date in the future for expiration, and any three digits for the CVC. Then click the "Pay" button at the bottom.

Stripe Checkout Session Page Filled Out

After successful payment, you will be redirected to the success page at http://127.0.0.1:8000/success/. You can view the confirmation of the payment on your Stripe dashboard. The Payments section, available from the left sidebar, provides further payment details.

Stripe Payments Page

If you want to customize the Checkout page, you can pass in additional attributes to the Checkout session.

Conclusion

What are the next steps to building out an e-commerce application? Next up would be switching from a hardcoded view and Stripe product ID to using variables in our templates and retrieving items from the database. The traditional format of a list view displaying all items and then a detail view with individual items is usually used. Only after a user make a purchase will they have access to a specific item.

You also need to add user accounts as a way to track activity and provide access. For example, if we had a Product model containing all products, it would be Foreign Key related to User and there would be a boolean field like has_access that can be toggled to turn access on or off to a product.

Subscriptions are another popular form of e-commerce and require slightly different configuration with Stripe but is essentially the same pattern. Instead of toggling access to individual items, you turn it on or off to all items and configure monthly recurring payments at different levels of access.

It can take a while to wrap your head around e-commerce patterns in Django, but once you do you'll find they are remarkably similar. I am working on a future e-commerce course that will demonstrate all of these concepts progressively through multiple projects. If you are interested, make sure to sign up for my newsletter to be notified when it is available!

Join My Newsletter

Subscribe to get the latest tutorials/writings by email.

    No spam. Unsubscribe at any time.