Django Stripe Tutorial
Published: 2024-01-06 02:02:20

In this tutorial, we will create a Stripe-powered Django website to handle purchases. The final site will have a homepage for orders, a success page when an order goes through, and a cancel page if the order fails.

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iysiic8qxt2b816nl87z.png)

## 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.

```python 
# 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.

```python
# 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.

```python
# 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.

```python
# 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.

```html
<!-- 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c43tbf16rx7cl6b1m9yv.png)

![Success page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju3ovac80i32w8tvv71s.png)

![Cancel page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqi0mcke826uw4m4p67h.png)

## Configure Stripe
Log in to your existing Stripe account or [register](https://dashboard.stripe.com/register) for a free one. If you only want to use test mode, where we can mock transactions, there is no need to complete the application with bank details. Once registered, head over to the [dashboard](https://dashboard.stripe.com/test/dashboard). 

![Stripe Dashboard Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnzjuzwst5b3k0xeyr16.png)

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

![Stripe Developers page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tctqqhx9v7vq26jy7s2k.png)

Make sure to activate "Test mode" and then click on the nav link for [API keys](https://dashboard.stripe.com/test/apikeys).

![Stripe API Keys page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6pbb68mzb6e9fzbomqky.png)

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. 

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

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

Now we need a product to sell. On the lefthand sidebar, click the link for "More +" and then "Product catalog," which takes us to the [Products](https://dashboard.stripe.com/test/products?active=true) page.

![Stripe Products Navbar Link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnduuk3bberqe8bjmo8a.png)

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

![Stripe Products Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s025jh5sck0w5vtbjomw.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8je7ezhl3bqo0irw5xlj.png)

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

![Stripe Product Catalog Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y1x614kshca7wrnetpof.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0yzeets98hs8jid1g5ky.png)

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](https://stripe.com/docs/checkout/quickstart), 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](https://github.com/stripe/stripe-python).

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

<!-- Stripe also has a [JavaScript library](https://stripe.com/docs/js/including) that we will use in later examples but it is not needed at this point. -->

In our `home.html` file, add a basic form using the POST method and Django's [CSRF protection](https://docs.djangoproject.com/en/5.0/howto/csrf/).

```html
<!-- 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.

```python
# 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](https://stripe.com/docs/api/checkout/sessions/object) but are not necessary at this stage.

We [define a product to sell](https://stripe.com/docs/checkout/quickstart#mode) 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](https://stripe.com/docs/checkout/quickstart#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()](https://docs.djangoproject.com/en/5.0/ref/urlresolvers/#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](https://stripe.com/docs/checkout/quickstart#redirect). 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n2s65vxva73sqi9oha52.png)

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

![Stripe Checkout Session Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9s0ac3t72haxtk4hrs33.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyqx2d7tgn0jazk8oepx.png)

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](https://dashboard.stripe.com/test/payments) section, available from the left sidebar, provides further payment details.

![Stripe Payments Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekprbxkpntdmxein4g2t.png)

If you want to [customize the Checkout page](https://stripe.com/docs/checkout/quickstart#customize-toggle), you can pass in additional attributes to the Checkout session.

## Next Steps
For more free tutorials and premium courses on Django, check out [LearnDjango.com](https://learndjango.com).