Integrate Stripe Payments with Django By Building a Digital Products Selling App
Tags: python, django, webdev, tutorial
Published: 2022-12-26 07:00:00

One of the great features of online selling is being able to process payments online. With the ever-growing online businesses, having a good payment processing system is necessary. Online selling has a lot of advantages among which are; low start-up and operating costs, high flexibility, a wide selection of available products, and improved customer experience.

In this tutorial, you will learn how to integrate Stripe to accept one-time payments for your digital products through a Django app. A digital product can be a book, for example, to which the buyer will be given access after completing the payment.

**Stripe** is a suite of APIs powering online payment processing and commerce solutions for internet businesses of all sizes. It is one of the most widely used tools for handling payments due to its ease of integration, convenience of use, and nice [documentation](https://stripe.com/docs).

There are two ways to accept one-time payments using Stripe. These are:

- [Stripe Checkout](https://stripe.com/docs/payments/checkout): This is a low-code payment integration that you can use to quickly integrate payments solution into your application.
- [Payment Intent](https://stripe.com/docs/payments/payment-intents): This provides a more custom experience to end users by storing necessary information about the payment and linking to a Stripe customer. It handles complex payment flows as well.

This tutorial is going to focus on **Stripe Checkout**. We are going to build an application where you as an admin can upload your products, and your customers (end users) will be able to purchase them.

_**Note:-** If you encounter any issues throughout the tutorial, you can check out the code in the [GitHub repository](https://github.com/earthcomfy/django-stripe)._

## Table of Contents

1. [Prerequisite](#prerequisite)
2. [Project Configuration](#config)
3. [Building the Model](#model)
4. [Setting Up the Views](#views)
5. [Stripe Configuration](#stripe)
6. [Stripe Checkout](#checkout)
7. [Payment Confirmation Using WebHook](#webhook)
8. [Give the Customer Access to the Product](#access)
9. [Create Payment History](#history)
10. [Conclusion](#conclusion)

## Prerequisite <a name="prerequisite"></a>

This guide assumes that you are familiar with Django.

## Project Configuration <a name="config"></a>

First, create a virtual environment and activate it:

```bash
python3 -m venv .venv
source .venv/bin/activate
```

Next, install Django and create a new Django project:

```bash
pip install django==4.1.4
django-admin startproject config .
```

Run migrations:

```bash
python manage.py migrate
```

Create a superuser:

```bash
python manage.py createsuperuser
```

Run the server to ensure the installation worked correctly:

```bash
python manage.py runserver
```

Great, now let’s create a new app called `products`. This app will contain the model of the product we are selling, the views, and URL configurations that are related to it.

```bash
py manage.py startapp products
```

Add it to the installed apps list in the settings:

```python
# config/settings.py

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

    # Local apps
    'products',
]
```

## Building the Model <a name="model"></a>

To keep it simple for the main scope of this tutorial, we are going to build a simple product model. A product can have tags. In addition, we also want to create a price model so that we can track the changing prices of a product over time. Go to the `models.py` file of the `products` app and add the following code:

```python
# products/models.py

from django.conf import settings
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.translation import gettext_lazy as _

User = settings.AUTH_USER_MODEL

def get_image_filename(instance, filename):
    name = instance.name
    slug = slugify(name)
    return f"products/{slug}-{filename}"

class ProductTag(models.Model):
    name = models.CharField(
        max_length=100, help_text=_("Designates the name of the tag.")
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self) -> str:
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=200)
    tags = models.ManyToManyField(ProductTag, blank=True)
    desc = models.TextField(_("Description"), blank=True)
    thumbnail = models.ImageField(upload_to=get_image_filename, blank=True)
    url = models.URLField()
    quantity = models.IntegerField(default=1)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ("-created_at",)

    def __str__(self):
        return self.name

class Price(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    price = models.DecimalField(decimal_places=2, max_digits=10)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self) -> str:
        return f"{self.product.name} {self.price}"
```

- Note that the product has a `url` field that is going to be used to send the customer a link to the digital product you are selling (book).
- The `Product` model also contains an image field for the thumbnail so let’s go ahead and install [Pillow](https://pypi.org/project/Pillow/).

```bash
pip install pillow==9.3.0
```

Then, let’s register the models as follows:

```python
# products/admin.py

from django.contrib import admin

from .models import Price, Product, ProductTag

class PriceAdmin(admin.StackedInline):
    model = Price

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = (PriceAdmin,)

    class Meta:
        model = Product

admin.site.register(ProductTag)
admin.site.register(Price)
```

- The `Price` model is registered inline with the `Product` model so that we can add the prices of a product while creating the product inside the admin panel.

Since we are working with user-uploaded images, we need to set `MEDIA_URL` and `MEDIA_ROOT` in the settings:

```python
# config/settings.py

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
```

Next, configure the project's `urls.py` to serve user-uploaded media files during **development.**

```python
# config/urls.py

from django.conf import settings
from django.conf.urls.static import static

# Media Assets
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```

Great, let’s now test the functionality we have so far:

```bash
py manage.py makemigrations
py manage.py migrate
py manage.py runserver
```

Go to the admin panel and you can now create products

![Create Products](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/inj7bc5k3i6ptjmalssg.png)

## Setting Up the Views <a name="views"></a>

Let’s now create views and templates to list our available products and view them in detail.

```python
# products/views.py

from django.views import View
from django.views.generic import DetailView, ListView

from .models import Price, Product

class ProductListView(ListView):
    model = Product
    context_object_name = "products"
    template_name = "products/product_list.html"

class ProductDetailView(DetailView):
    model = Product
    context_object_name = "product"
    template_name = "products/product_detail.html"

    def get_context_data(self, **kwargs):
        context = super(ProductDetailView, self).get_context_data()
        context["prices"] = Price.objects.filter(product=self.get_object())
        return context
```

Then, update the project’s `urls.py` file to include the URL configuration for `products` app:

```python
# config/urls.py

from django.urls import include, path

urlpatterns = [
    #...
    path("", include("products.urls", namespace="products")),
]
```

Create a `urls.py` file inside the `products` app and add the URL configuration for the product list and detail views:

```python
# products/urls.py

from django.urls import path

from .views import (
    ProductDetailView,
    ProductListView,
)

app_name = "products"

urlpatterns = [
    path("", ProductListView.as_view(), name="product-list"),
    path("<int:pk>/", ProductDetailView.as_view(), name="product-detail"),
]
```

Then, create a `templates` folder at the root of your project:

```bash
mkdir templates
```

Add it to the settings:

```python
# config/settings.py

TEMPLATES = [
    {
        "DIRS": [BASE_DIR / "templates"],
        ...
    },
]
```

Inside the templates directory, create a file named `base.html` that will be used as a base template for all the other templates we are going to create. Note that we are using Bootstrap for styling.

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />

    <!-- Bootstrap CSS -->
    <link
      rel="stylesheet"
      href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
      integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
      crossorigin="anonymous"
    />

    <!--Font awesome icons -->
    <link
      rel="stylesheet"
      href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
      integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay"
      crossorigin="anonymous"
    />

    {% block script %}{% endblock %}

    <title>{% block title %} {% endblock %}</title>
  </head>
  <body>
    <div class="container p-3 my-3">
      <div class="row">
        <div class="col-md-12">
          <!--Any flash messages pop up in any page because this is the base template-->
          {% if messages %}
          <div class="alert alert-dismissible" role="alert">
            {% for message in messages %}
            <div class="alert alert-{{ message.tags }}">{{ message }}</div>
            {% endfor %}
            <button
              type="button"
              class="close"
              data-dismiss="alert"
              aria-label="Close"
            >
              <span aria-hidden="true">&times;</span>
            </button>
          </div>
          {% endif %} {% block content %}{% endblock %}
        </div>
      </div>
    </div>

    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script
      src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
      integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
      integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
      integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
      crossorigin="anonymous"
    ></script>
  </body>
</html>
```

Next, create a folder named `products` inside the `templates` folder, and inside it, create 2 files named `product_list.html` and `product_detail.html`

```html
{% comment %} templates/products/product_list.html {% endcomment %}

{% extends "base.html" %} {% block title %} Products {% endblock title%} {% block content %} {% for product in products %}
<div class="card" style="width: 18rem">
  <img
    class="card-img-top"
    src="{{product.thumbnail.url}}"
    alt="Card image cap"
  />
  <div class="card-body">
    <h5 class="card-title">{{product.name}}</h5>
    <p class="card-text">{{product.desc}}</p>
    <a
      href="{% url 'products:product-detail' product.id %}"
      class="btn btn-primary"
      >View Detail</a
    >
  </div>
</div>
{% endfor %} {% endblock content %}
```

```html
{% comment %} templates/products/product_detail.html {% endcomment %}

{% extends "base.html" %} 
{% block title %} Buy Product {% endblock title%} 
{% block content %}
<div class="card-deck">
  {% for price in prices %}
  <div class="card">
    <img
      class="card-img-top"
      src="{{product.thumbnail.url}}"
      alt="Card image cap"
    />
    <div class="card-body">
      <h5 class="card-title">{{product.name}}</h5>
      <p class="card-text">{{product.desc}}</p>
      <div class="card-text">
        <h5 class="card-text">${{ price.price }}</h5>
        <form
          action=""
          method="POST"
        >
          {% csrf_token %}
          <button type="submit" class="btn btn-primary">Checkout</button>
        </form>
      </div>
    </div>
  </div>
  {% endfor %}
</div>
{% endblock content %}
```

- On the product detail page, we are looping through and displaying the different prices of the product with a checkout button attached.
- In this tutorial, the only variation for a product is its price. However, you can configure the models to include and update the structure as it seems fit to your requirements.

Go to [http://localhost:8000/admin/](http://localhost:8000/admin/) and create a sample product. Then go to the index page  [http://localhost:8000](http://localhost:8000/admin/) to see the product and its detail.

## Stripe Configuration <a name="stripe"></a>

First, install the Stripe package which provides convenient access to the Stripe API from Python applications:

```bash
pip install stripe==5.0.0
```

Next, log in to your Stripe account or create a new one and head over to the [dashboard](https://dashboard.stripe.com/test/dashboard)

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

Click on “Developers” then on the left sidebar click on “API keys”:


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

Grab the public and secret keys for use in your project. Note that you will get a different pair in production (when test mode is disabled).

Then, create a `.env` file at the root of your project:

```bash
touch .env
```

and store the keys in your environment file:

```
# Stripe
STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
```

In order to access these keys in the project, we need to install Python Decouple:

```bash
pip install python-decouple==3.6
```

Then, update the settings to include the public and secret keys you grabbed from the Stripe dashboard:

```python
# config/settings.py

from decouple import config

# Stripe
STRIPE_PUBLISHABLE_KEY = config('STRIPE_PUBLISHABLE_KEY')
STRIPE_SECRET_KEY = config('STRIPE_SECRET_KEY')
```

Finally, make sure that you have set up an account name on the Stripe Dashboard [https://dashboard.stripe.com/settings/account](https://dashboard.stripe.com/settings/account)

## Stripe Checkout <a name="checkout"></a>

In this section, we are going to create a Stripe checkout session. Normally, you would want to put payment-related logic into its own app, however, for the scope of this tutorial, we will use the `products` app we created earlier to create the views for a Stripe checkout.

Before proceeding, let’s first understand what the flow is going to look like:

- We will create a Checkout session when the “Checkout” button is pressed.
- The Checkout Session is unique with different properties among which is a URL that redirects customers to a Stripe-hosted payment page. This is done by using Stripe’s JavaScript module.
- Customers will fill in their payment details like card number and complete the transaction.
- Customers will be redirected to a success page for successful payments and to a cancellation page for canceled payments.
- After the transaction is completed, payment is confirmed using webhook.

Head over to `products/views.py` and create the Stripe checkout session view as follows:

```python
# products/views.py

import stripe
from django.conf import settings
from django.shortcuts import redirect
from django.views import View

from .models import Price

stripe.api_key = settings.STRIPE_SECRET_KEY

class CreateStripeCheckoutSessionView(View):
    """
    Create a checkout session and redirect the user to Stripe's checkout page
    """

    def post(self, request, *args, **kwargs):
        price = Price.objects.get(id=self.kwargs["pk"])

        checkout_session = stripe.checkout.Session.create(
            payment_method_types=["card"],
            line_items=[
                {
                    "price_data": {
                        "currency": "usd",
                        "unit_amount": int(price.price) * 100,
                        "product_data": {
                            "name": price.product.name,
                            "description": price.product.desc,
                            "images": [
                                f"{settings.BACKEND_DOMAIN}/{price.product.thumbnail}"
                            ],
                        },
                    },
                    "quantity": price.product.quantity,
                }
            ],
            metadata={"product_id": price.product.id},
            mode="payment",
            success_url=settings.PAYMENT_SUCCESS_URL,
            cancel_url=settings.PAYMENT_CANCEL_URL,
        )
        return redirect(checkout_session.url)
```

Here are a couple of things to note from the above code:

- The view will be called with a price id URL parameter.
- The view creates a new Checkout Session by calling the `stripe.checkout.Session.create` method with the appropriate parameters.
- One of the parameters is `line_items`. It provides a list of items the customer is purchasing.
- Stripe allows you to create [product and price](https://dashboard.stripe.com/test/products/create) objects in the dashboard. However, in our case, we are managing the product and prices ourselves so we need to create the Stripe [Price](https://stripe.com/docs/api/prices) object inline. To do so, we used the `price_data` parameter of `line_items`  by passing in the currency, unit amount, product name, description, and image. For a list of fields and parameters, you can use, check out the [doc](https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-line_items-price_data).
- The `metadata` parameter defines a set of key-value pairs that you can attach to an object. You can store additional information about the object using it. We will use the metadata value later when we create a webhook handler.
- We are implementing a one-time payment so we used `mode` with `payment` value.
- The success and cancel URLs define the URLs the customer will be redirected to when the transaction is completed, or canceled respectively.

Now, update the `.env` file to include the following environment variables:

```
BACKEND_DOMAIN=http://127.0.0.1:8000
PAYMENT_SUCCESS_URL=http://127.0.0.1:8000/success/
PAYMENT_CANCEL_URL=http://127.0.0.1:8000/cancel/
```

Then, update the settings:

```python
# config/settings.py

BACKEND_DOMAIN = config("BACKEND_DOMAIN")
PAYMENT_SUCCESS_URL = config("PAYMENT_SUCCESS_URL")
PAYMENT_CANCEL_URL = config("PAYMENT_CANCEL_URL")
```

Add the URL configuration for the `CreateStripeCheckoutSessionView`

```python
# products/urls.py

from django.urls import path

from .views import CreateStripeCheckoutSessionView

app_name = "products"

urlpatterns = [
    path(
        "create-checkout-session/<int:pk>/",
        CreateStripeCheckoutSessionView.as_view(),
        name="create-checkout-session",
    ),
]
```

Great, we now need to update the `product_detail` template so that the Checkout form is functional:

```html
{% comment %} templates/products/product_detail.html {% endcomment %}

{% extends "base.html" %} 
{% block title %} Buy Product {% endblock title%} 
{% block script %} <script src="https://js.stripe.com/v3/"></script> {% endblock script%} 
{% block content %}
<div class="card-deck">
  {% for price in prices %}
  <div class="card">
    <img
      class="card-img-top"
      src="{{product.thumbnail.url}}"
      alt="Card image cap"
    />
    <div class="card-body">
      <h5 class="card-title">{{product.name}}</h5>
      <p class="card-text">{{product.desc}}</p>
      <div class="card-text">
        <h5 class="card-text">${{ price.price }}</h5>
        <form
          action="{% url 'products:create-checkout-session' price.id %}"
          method="POST"
        >
          {% csrf_token %}
          <button type="submit" class="btn btn-primary">Checkout</button>
        </form>
      </div>
    </div>
  </div>
  {% endfor %}
</div>
{% endblock content %} 
```

In the above code:

- We added the Stripe.js library at the top.
- We updated the form `action` attribute to send form data to the `CreateStripeCheckoutSessionView`

Let’s also add payment success and cancel views:

```python
# products/views.py

from django.views.generic import TemplateView

class SuccessView(TemplateView):
    template_name = "products/success.html"

class CancelView(TemplateView):
    template_name = "products/cancel.html"
```

Create the templates (`success.html` and `cancel.html`) for the above 2 views inside `templates/products` directory.

Success.html

```html
{% comment %} templates/products/success.html {% endcomment %}

{% extends "base.html" %} 
{% block title %} Success {% endblock title%} 
{% block content %}
<div class="alert alert-success" role="alert">
  Thanks for purchasing our product
  <a
      href="{% url 'products:product-list' %}"
      >Go to Products</a
    >
</div>
{% endblock content %}
```

Cancel.html

```html
{% comment %} templates/products/cancel.html {% endcomment %}

{% extends "base.html" %} 
{% block title %} Cancel {% endblock title%} 
{% block content %}
<div class="alert alert-info" role="alert">
  Forgot to add something to your cart?
  <a
      href="{% url 'products:product-list' %}"
      >Go to Products</a
    >
</div>
{% endblock content %}
```

Finally, configure the routes for success and cancel views:

```python
# products/urls.py

from django.urls import path

from .views import CancelView SuccessView

app_name = "products"

urlpatterns = [
    path("success/", SuccessView.as_view(), name="success"),
    path("cancel/", CancelView.as_view(), name="cancel"),
]
```

Time to test the functionality, head over to the product detail page (here I have one product with three different prices):

![Product detail page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k205urcvk0v7gtwpe2rr.png)

Then click the Checkout button on one of the products and you will get redirected to the Stripe checkout page:

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

As mentioned in the [docs](https://www.notion.so/3ba824557f1a42c5b871429ea08ba8a8) too, use the following form fields for testing:

- For card number use `4242 4242 4242 4242`
- For Date use a valid future date, such as **12/34**
- Use any three-digit CVC

You can use any value you like for the other fields.

Great, after you click the Pay button, you will be redirected to the success page:

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

To make sure everything worked correctly, head over to [https://dashboard.stripe.com/test/payments](https://dashboard.stripe.com/test/payments) and you will see your recent payment listed:

![Stripe recent payments](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aikq42spn0m563ib8w5z.png)

Everything seems to be working fine now, However, even if the payment failed, the user will be redirected to the success page i.e. being redirected to the success page doesn’t guarantee a successful payment. Therefore, we need a way to ensure the process went smoothly. This is where WebHooks come into play.

## Payment Confirmation Using WebHook <a name="webhook"></a>

Stripe uses webhooks to notify your application when an event happens. Although there are a couple of events that you can handle, the event we are interested in right now is the `checkout.session.completed` event that occurs when a Checkout Session has been successfully completed.

First and foremost install the [Stripe CLI](https://stripe.com/docs/stripe-cli#install) tool for your OS.

Then, run the following command to send all Stripe events in test mode to a local webhook endpoint called `/webhooks/stripe/`

```bash
stripe listen --forward-to localhost:8000/webhooks/stripe/
```

Copy the webhook secret key generated by running the above command and paste it into the `.env` file found at the root of your project:

```
STRIPE_WEBHOOK_SECRET=
```

Update the settings as well:

```python
STRIPE_WEBHOOK_SECRET = config("STRIPE_WEBHOOK_SECRET")
```

Great, now head over to `products/views.py` and create the webhook handler as an HTTP endpoint:

```python
# products/views.py

import stripe
from django.conf import settings
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from .models import Price, Product

stripe.api_key = settings.STRIPE_SECRET_KEY

@method_decorator(csrf_exempt, name="dispatch")
class StripeWebhookView(View):
    """
    Stripe webhook view to handle checkout session completed event.
    """

    def post(self, request, format=None):
        payload = request.body
        endpoint_secret = settings.STRIPE_WEBHOOK_SECRET
        sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
        event = None

        try:
            event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)
        except ValueError as e:
            # Invalid payload
            return HttpResponse(status=400)
        except stripe.error.SignatureVerificationError as e:
            # Invalid signature
            return HttpResponse(status=400)

        if event["type"] == "checkout.session.completed":
            print("Payment successful")

        # Can handle other events here.

        return HttpResponse(status=200)
```

- Note that we have used the `csrf_exempt` decorator to make the view exempt from the CSRF protection ensured by Django’s middleware This allows us to receive data from another domain (Stripe) even if it has no CSRF token.
- Stripe can optionally sign the webhook events it sends to your endpoints by including a signature in each event’s `Stripe-Signature` header. By adding this, we are verifying that the events were indeed sent by Stripe and not by a third party.

Add the URL configuration: 

```python
# products/urls.py

from django.urls import path

from .views import StripeWebhookView

app_name = "products"

urlpatterns = [
    path("webhooks/stripe/", StripeWebhookView.as_view(), name="stripe-webhook"),
]
```

- Since we have configured Stripe earlier in this section to send all events to a local endpoint named `webhooks/stripe/`, make sure the name of the URL is also `/webhooks/stripe/`

Let's test it. Now when you make a payment, you will see the payment successful output on the console:

![Stripe WebHooks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3i3sk9ofjhfird4lgct.png)

## Give the Customer Access to the Product <a name="access"></a>

We now need to give the customer access to the product he/she purchased upon successful payment. Since we are selling a digital product (book), we can do so by sending the user an email containing a link to the product.

Go to `products/views.py` and update the webhook as follows:

```python
# products/views.py

import stripe
from django.conf import settings
from django.core.mail import send_mail # Add this
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from .models import Price, Product

stripe.api_key = settings.STRIPE_SECRET_KEY

@method_decorator(csrf_exempt, name="dispatch")
class StripeWebhookView(View):
    """
    Stripe webhook view to handle checkout session completed event.
    """

    def post(self, request, format=None):
        payload = request.body
        endpoint_secret = settings.STRIPE_WEBHOOK_SECRET
        sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
        event = None

        try:
            event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)
        except ValueError as e:
            # Invalid payload
            return HttpResponse(status=400)
        except stripe.error.SignatureVerificationError as e:
            # Invalid signature
            return HttpResponse(status=400)

        if event["type"] == "checkout.session.completed":
            print("Payment successful")
            
            # Add this
            session = event["data"]["object"]
            customer_email = session["customer_details"]["email"]
            product_id = session["metadata"]["product_id"]
            product = get_object_or_404(Product, id=product_id)

            send_mail(
                subject="Here is your product",
                message=f"Thanks for your purchase. The URL is: {product.url}",
                recipient_list=[customer_email],
                from_email="your@email.com",
            )

        # Can handle other events here.

        return HttpResponse(status=200)
```

- Remember the `product_id` we passed as `metadata` in the `CreateStripeCheckoutSessionView`? we are using that to get the product URL and send it to the user through his/her email.

For testing purposes, we can use the console email backend so update the settings as follows:

```python
# config/settings.py

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
```

Now, test the payment process again and you will get an email on your console with a link to the product.

## Create Payment History <a name="history"></a>

Aside from making sure the payment went smoothly, you may also want to keep a user’s payment history in your database. To do so, let’s first create a `PaymentHistory` model with the following fields:

```python
# products/models.py

class PaymentHistory(models.Model):
    PENDING = "P"
    COMPLETED = "C"
    FAILED = "F"

    STATUS_CHOICES = (
        (PENDING, _("pending")),
        (COMPLETED, _("completed")),
        (FAILED, _("failed")),
    )

    email = models.EmailField(unique=True)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    payment_status = models.CharField(
        max_length=1, choices=STATUS_CHOICES, default=PENDING
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.product.name
```

Then, in the webhook view, create a payment history object upon a successful payment:

```python
# products/views.py

from .models import PaymentHistory # Add this

@method_decorator(csrf_exempt, name="dispatch")
class StripeWebhookView(View):
    """
    Stripe webhook view to handle checkout session completed event.
    """

    def post(self, request, format=None):
        payload = request.body
        endpoint_secret = settings.STRIPE_WEBHOOK_SECRET
        sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
        event = None

        try:
            event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)
        except ValueError as e:
            # Invalid payload
            return HttpResponse(status=400)
        except stripe.error.SignatureVerificationError as e:
            # Invalid signature
            return HttpResponse(status=400)

        if event["type"] == "checkout.session.completed":
            print("Payment successful")
            session = event["data"]["object"]
            customer_email = session["customer_details"]["email"]
            product_id = session["metadata"]["product_id"]
            product = get_object_or_404(Product, id=product_id)

            send_mail(
                subject="Here is your product",
                message=f"Thanks for your purchase. The URL is: {product.url}",
                recipient_list=[customer_email],
                from_email="test@gmail.com",
            )

            PaymentHistory.objects.create(
                email=customer_email, product=product, payment_status="completed"
            ) # Add this

        # Can handle other events here.

        return HttpResponse(status=200)
```

## Conclusion <a name="conclusion"></a>

In this tutorial, we have created a minimal product listing and checkout functionality using Stripe. Your next steps can be integrating Celery for sending emails upon successful payment,  registration/login system for your customers, updating the user interface, expanding the model structure, and so on.

If you got lost somewhere throughout the guide, check out the project on [GitHub](https://github.com/earthcomfy/django-stripe)

Happy coding! 🖤