Django E-Commerce Tutorial with Stripe
Updated
Table of Contents
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.
Initial Set Up
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 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.
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.
Click on the "Developers" nav link in the upper right-hand corner.
Make sure to activate "Test mode" and then click on the nav link for API keys.
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.
Click the "+ Add product" button in the upper right-hand corner to create a product to sell.
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.
We are redirected to the main "Product catalog" page, where our new product is now visible.
Click on the new product to open up its page. Under the Pricing section, note the "API ID" which we will use shortly.
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.
You'll be redirected to a unique Stripe Checkout session for the product.
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.
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.
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!