Integrating Stripe in Django

It’s straightforward to integrate the Stripe payment gateway into your Django project. In addition, Stripe makes it easy to accept payments from your Django website, and it simply takes a few minutes.


Using an example, we’ll show you How to Integrate Stripe Payment Gateway with Django in this post.

How do you get Stripe to work on your Django project?

These are the steps we will take to incorporate Stripe into our Django app.

  • Create an account with Stripe.
  • Make sure your company is legitimate (to use it in production).
  • To accept payments or sell things, create a new Django project and add the appropriate pages.
  • Using pip install stripe, install the stripe package.
  • Set up a Stripe checkout.
  • Acceptance of Payments

Creating a new Django Project

Let’s get started with a new Django project.

We’ll be utilizing Python 3.9 and Django 3.2 for this project.

To begin, make a new folder called django-payment-app and browse to it.

mkdir django-payment-app && cd django-payment-app

Now you’ll need to set up a virtual environment for your app.

Although you can go without having a virtual environment, we recommend you do so for all of your projects because handling project dependencies without virtual environments will be an arduous endeavor.

virtualenv stripe_env

Run the following command to activate the virtual environment:

source stripe_env/bin/activate

The next step will entail installing the latest version of Django as of the time of this writing. In addition, we will create a new project using the following commands.

pip install django
django-admin startproject paymentsapp

After creating the project, create a new app named payments.

cd paymentsapp
python manage.py startapp payments

Add the new payments app to settings.py in the list of INSTALLED_APPS.

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

In this project, we’ll include all of the code relevant to Stripe payments.

Creating a Stripe account

To take Stripe payments, we’ll need a Stripe Publishable API key and a Secret Key. These elements can be found in the Stripe dashboard.

Your secret keys should not be shared with anyone else.

API Key for Stripe

Log in to the Stripe dashboard to get the API Keys. Then, we’ll need to get a couple of productions and test keys. The test keys will be utilized in the development environment, while the production keys will be used when the program is deployed in production. You must first activate your account to access the production keys.

The methods and papers required to validate your account differ depending on your country of origin. However, you can only get the test key and secret if you don’t have a confirmed account.

Log in to your Stripe dashboard and enable Test Mode to get the publishable test key and key secret. Then, expand the Get your test API keys area on the right side of the screen to get the publishable key and key secret.

Keep these keys in a safe place and never give them to anyone else. After turning off the test mode, get the live keys if you have a validated account.

Stripe configuration

Install stripe by running the following command.

pip install stripe

Now, Stripe should be installed successfully. Also, note that this is the official Python Stripe package.

Stripe configuration

We may begin integrating Stripe into our project after acquiring the Publishable key and secret. To do so, we must first add the stripe API key and secret to settings.py.

Adding the details to settings.py directly is not a secure technique because if you host your code publicly on GitHub, you may inadvertently leak the API secret to others.

To keep things simple, we’ll save these secrets in settings.py for this demo only. If you are doing it correctly, you should have similar to the piece of code below.

if DEBUG:
    STRIPE_PUBLISHABLE_KEY = 'test_publishable_key'
    STRIPE_SECRET_KEY = 'test_secret_key'
    
# Uncomment these lines if you have a live keys
 else:
     STRIPE_PUBLISHABLE_KEY = 'production_publishable_key'
     STRIPE_SECRET_KEY = 'production_secret_key'

Creating a Model for the Products

Add this code to the product app’s models.py file. The latter will create a Products table in the database, containing columns for id, name, description, and price.

from django.db import models
from django.core import validators

# Create your models here.
class Product(models.Model):
    id = models.BigAutoField(
        primary_key=True
    )

    name = models.CharField(
        max_length=70,
        verbose_name='Product Name'
    )

    description = models.TextField(
        max_length=800,
        verbose_name='Description'
    )

    price = models.FloatField(
        verbose_name='Price',
        validators=[
            validators.MinValueValidator(10),
            validators.MaxValueValidator(1000)
        ]
    )

Ensuring that the changes have been reflected in our database, run the following commands:

python manage.py makemigrations
python manage.py migrate

Creation of a View

The following step involves generating all of the necessary views. Therefore, the respective views that we’ll be creating are listed below.

  • ProductCreateView -To create a new Product, use the ProductCreateView method.
  • ProductListView – The ProductListView is our application’s main page. Here’s where we’ll show you a list of products from the database.
  • ProductDetailView – As the name implies, this view will be used to show product details. This page will be integrated with Stripe Payment Gateway.
  • create_checkout_session- This view acts as a payment gateway initialization API.
  • PaymentSuccessView – After a successful payment, customers will be sent to this page.
  • PaymentFailedView – If the payment fails, users will be sent to this page.
  • OrderHistoryView – This page will show all past orders and their status.

When done, then we will add these views to payments/views.py. However, the business logic of these views will be added later.

from django.http.response import HttpResponseNotFound, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse, reverse_lazy
from .models import *
from django.views.generic import ListView, CreateView, DetailView, TemplateView
import stripe
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
import json

# Create your views here.

class ProductListView(ListView):
    pass


class ProductCreateView(CreateView):
    pass


class ProductDetailView(DetailView):
    pass

@csrf_exempt
def create_checkout_session(request, id):
	pass


class PaymentSuccessView(TemplateView):
    pass

class PaymentFailedView(TemplateView):
    pass

class OrderHistoryListView(ListView):
    pass

Creating Templates

This section will create a specific layout page, a navigation bar, and other essential templates before writing the code for these views.

In the payments templates folder, please create a new folder called payments and add the files base.html, navbar.html, product list.html, product_create.html, product_detail.html, payment_success.html, payment_failed.html, and order_history.html to it.

Include the following code in paymentsapp/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('payments.urls')),
]



Also, add the following code in the payments/urls.py (app):

from django.urls import path
from .views import *

urlpatterns = [
    path('', ProductListView.as_view(), name='home'),
    path('create/', ProductCreateView.as_view(), name='create'),
    path('detail/<id>/', ProductDetailView.as_view(), name='detail'),
    path('success/', PaymentSuccessView.as_view(), name='success'),
    path('failed/', PaymentFailedView.as_view(), name='failed'),
    path('history/', OrderHistoryListView.as_view(), name='history'),

    path('api/checkout-session/<id>/', create_checkout_session, name='api_checkout_session'),
]

]

Subsequently, make template changes to the following files.

payments/templates/payments/base.html

<!doctype html>
<html lang="en">

<head>
	<title>Django Payments App</title>
	<!-- 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://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
		integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>

<body>
	{%% include 'payments/navbar.html' %%}

	{%% block content %%}

	{%% endblock content %%}

	<!-- Optional JavaScript -->
	<!-- jQuery first, then Popper.js, then Bootstrap JS -->
	<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>

</html>

payments/templates/payments/navbar.html

<nav class="navbar navbar-expand-sm navbar-light bg-light">
    <a class="navbar-brand" href="#">Navbar</a>
    <button class="navbar-toggler d-lg-none" type="button" data-toggle="collapse" data-target="#collapsibleNavId" aria-controls="collapsibleNavId"
        aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="collapsibleNavId">
        <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
            <li class="nav-item active">
                <a class="nav-link" href="{%% url 'home' %%}">Home</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="{%% url 'create' %%}">New Product</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="{%% url 'history' %%}">Order History</a>
            </li>
        </ul>
        <form class="form-inline my-2 my-lg-0">
            <input class="form-control mr-sm-2" type="text" placeholder="Search">
            <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
        </form>
    </div>
</nav>

ProductCreate View

We will Modify ProductCreateView’s code to allow us to create products and save them to the database.

class ProductCreateView(CreateView):
    model = Product
    fields = '__all__'
    template_name = "payments/product_create.html"
    success_url = reverse_lazy("home")

The code snippet above can be a little complicated if you’re new to Django’s class-based views.

Django will handle the basic activities like creating and listing data from the database via class-based views and generic views.

The ProductCreateView is derived from CreateView in this case.

Django will generate a form for the supplied model (or a custom form using the form class property), handle the post method, validate the data, and save the data to the database using a CreateView.

It abstracts all of the code that you’d write in a function-based view otherwise.

Important Characteristics

  • model – The model to be used as a target.
  • fields – These are the fields that will appear in the form.
  • The value all denotes that the form should have fields for all model properties. Auto fields and primary keys will be excluded.
  • The name of the template that should be rendered is template_name. Django will try to use a default template if this attribute is not set.
  • success_url – The URL to which the user should be routed after submitting the form.

Next, access the product_create.html page and append the following code.

{%% extends 'payments/base.html' %%}

{%% block content %%}
<h1 class="text-center">Create Product</h1>
<div class="container-fluid">
    <div class="row">
        <div class="col-sm-6 offset-sm-3">
            <form method="post">
                {{ form.as_p }}
                {%% csrf_token %%}
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">Save</button>
                </div>
            </form>
        </div>
    </div>
</div>
<script>
    document.querySelectorAll('input, textarea').forEach(e => {
        e.classList.add('form-control');
    })
</script>
{%% endblock content %%}

ProductList View

The ProductList view’s work is to show the databases’ products so that the user can interact with them in a friendly manner.

#views.py

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

The name of the context object that will hold the product list is the context_object_name. As a result, Django will automatically retrieve the product list, eliminating the need to build a query to retrieve products from the database.

product_list.html

product_list.html

{%% extends 'payments/base.html' %%}

{%% block content %%}
<h1 class="text-center">Product List</h1>
<div class="container">
    {%% if product_list %%}
    <div class="row">


        {%% for p in product_list %%}
        <div class="col-sm-4">
            <div class="card">
                <img class="card-img-top" src="https://dummyimage.com/200x150.jpg?text={{ p.name }}" alt="">
                <div class="card-body">
                    <h4 class="card-title">{{ p.name }}</h4>
                    <p class="card-text">{{ p.description }}</p>
                </div>
                <div class="card-footer d-flex">
                    <a href="{%% url 'detail' id=p.id %%}" class="btn btn-success ml-auto">Buy Now</a>
                </div>
            </div>
        </div>
        {%% endfor %%}

    </div>
    {%% else %%}
    <div class="alert alert-info text-center mt-5">
        The product list is empty. Please add some products first.
    </div>
    {%% endif %%}
</div>
{%% endblock content %%}

Use python manage.py runserver to run the project and add some products to the database.

Setting up Stripe Payments on the Product Detail Page

It is our project’s most important section because the information of a selected product will be displayed on the page, along with a checkout button to purchase the product.

Further, on this page, we’ll also incorporate the Stripe payment gateway.

Modify ProductDetailPage’s code as shown below.

class ProductDetailView(DetailView):
    model = Product
    template_name = "payments/product_detail.html"
    pk_url_kwarg = 'id'

    def get_context_data(self, **kwargs):
        context = super(ProductDetailView, self).get_context_data(**kwargs)
        context['stripe_publishable_key'] = settings.STRIPE_PUBLISHABLE_KEY
        return context  

In this code, we’re altering the get_context_data() method to add the publishable key as data to the template context.

We set pk_url_kwarg = ‘id’ to tell Django to obtain product details using the id argument as a URL parameter.

The Stripe Publishable key should be passed to the client as a template content object.

This publishable key is required by the Stripe JavaScript SDK, which we will include in the template of this view, to authenticate our request and redirect the user to the payment page.

<!-- product_detail.html -->

{%% extends 'payments/base.html' %%}

{%% block content %%}
<h1 class="text-center">Product Detail</h1>
<div class="container">

    <div class="card">
        <div class="card-header">
            <h2>Product Detail</h2>
        </div>
        <div class="card-body">
            <div class="container row">
                <div class="col-md-2">
                    <img src="https://dummyimage.com/150x150.gif?text={{ object.name }}" alt="">
                </div>
                <div class="col-md-10">
                    <h1>Name: {{ object.name }}</h1>
                    <p>Description: {{ object.description }}</p>
                    <p>Price: {{ object.price }}</p>

                    <div class="form-group">
                        <label for="email">Email: </label>
                        <input type="email" name="email" id="email" class="form-control" placeholder="Email">
                        <small>Please enter your email address</small>
                    </div>
                </div>
            </div>
        </div>
        <div class="card-footer d-flex">
            <button class="btn btn-success ml-auto" id="checkout-button">Checkout</button>
        </div>
    </div>
</div>
<!-- Add JavaScript Here-->
{%% endblock content %%}

The code above includes a bootstrap card that displays information about the product the customer wishes to buy. In addition, we’ve included an email field in the template. The email will be used to pre-fill the payment gateway page and used as user identification.

If we only allow authenticated visitors to purchase things from our website, we can hide this email box from visibility.

Dissecting the OrderDetails Model

The OrderDetails Model is a model that represents the details of an order. After hitting the checkout button, the user should be led to a Stripe payment page.

It will necessitate that we pass a payment session id and the request to redirect a user to the payment page.

Because it requires our secret key, we can only create this session id from the server. As a result, we’ll save the checkout session-id, along with the order details, in the database after we create it. To do so, create a new model in the database, as demonstrated in the code below.

products/models.py

from django.db import models
from django.core import validators


class Product(models.Model):
    # Code removed for brevity

class OrderDetail(models.Model):

    id = models.BigAutoField(
        primary_key=True
    )

    # You can change as a Foreign Key to the user model
    customer_email = models.EmailField(
        verbose_name='Customer Email'
    )

    product = models.ForeignKey(
        to=Product,
        verbose_name='Product',
        on_delete=models.PROTECT
    )

    amount = models.IntegerField(
        verbose_name='Amount'
    )

    stripe_payment_intent = models.CharField(
        max_length=200
    )

    # This field can be changed as status
    has_paid = models.BooleanField(
        default=False,
        verbose_name='Payment Status'
    )

    created_on = models.DateTimeField(
        auto_now_add=True
    )

    updated_on = models.DateTimeField(
        auto_now_add=True
    )

Save the model and run the following commands:

python manage.py makemigrations
python manage.py migrate

The create checkout session view will be used to generate the session id.

Unlike the other views we used in this project, it is a function-based view, and it should only accept post methods.

Stripe Checkout Session Creation

To the create checkout session view, add the following code.

@csrf_exempt
def create_checkout_session(request, id):

    request_data = json.loads(request.body)
    product = get_object_or_404(Product, pk=id)

    stripe.api_key = settings.STRIPE_SECRET_KEY
    checkout_session = stripe.checkout.Session.create(
        # Customer Email is optional,
        # It is not safe to accept email directly from the client side
        customer_email = request_data['email'],
        payment_method_types=['card'],
        line_items=[
            {
                'price_data': {
                    'currency': 'usd',
                    'product_data': {
                    'name': product.name,
                    },
                    'unit_amount': int(product.price * 100),
                },
                'quantity': 1,
            }
        ],
        mode='payment',
        success_url=request.build_absolute_uri(
            reverse('success')
        ) + "?session_id={CHECKOUT_SESSION_ID}",
        cancel_url=request.build_absolute_uri(reverse('failed')),
    )

    # OrderDetail.objects.create(
    #     customer_email=email,
    #     product=product, ......
    # )

    order = OrderDetail()
    order.customer_email = request_data['email']
    order.product = product
    order.stripe_payment_intent = checkout_session['payment_intent']
    order.amount = int(product.price * 100)
    order.save()

    # return JsonResponse({'data': checkout_session})
    return JsonResponse({'sessionId': checkout_session.id})

We are doing three critical actions in this view:

  • Using the Stripe Library, create a Stripe checkout session.
  • Saving the order data, as well as the payment intent retrieved via the Stripe session. In this case, the payment intent can be thought of as a unique identifier for each payment.
  • Returning the session ID in the form of JSON.

These are the details we’ll need to start a new checkout session.

  • customer_email – This is a field that can be left blank. This email will be used to identify a customer in Stripe if it is specified. Further, this email will be visible on the payment page as well.
  • payment_method_types – The many payment options available to the user. More information about payment types can be found here.
  • line_items – Information about the products being purchased by the customer. In case you need additional information on adjusting line items, check out the documentation.
  • unit_amount is the product’s price multiplied by 100. Note that the unit_amount should be a positive integer.
  • quantity – An integer value indicating the number of orders in the queue.
  • success_url – The complete URL to which the user should be forwarded after making a successful payment. Further, this page can be used to show a success message and mark the order as complete.
  • cancel_url – If the payment fails for some reason, this is the full URL to which the user should be forwarded.
  • mode – Indicates the payment method. It could be a one-time payment or a monthly subscription.

User redirection

The Stripe JavaScript SDK should be added to the client-side to redirect the user to a payment page. Just before the percent endblock content percent, add this code at the end of product detail.html.

This code will send an AJAX request to the create_checkout_session view to retrieve the checkout session id. Subsequently, the user will be led to a Stripe-hosted payment page after receiving the id.

Managing Successful Transactions

The user will be returned to our website’s success page after successful payment.

payments/views.py

class PaymentSuccessView(TemplateView):
    template_name = "payments/payment_success.html"

    def get(self, request, *args, **kwargs):
        session_id = request.GET.get('session_id')
        if session_id is None:
            return HttpResponseNotFound()
        
        stripe.api_key = settings.STRIPE_SECRET_KEY
        session = stripe.checkout.Session.retrieve(session_id)

        order = get_object_or_404(OrderDetail, stripe_payment_intent=session.payment_intent)
        order.has_paid = True
        order.save()
        return render(request, self.template_name)

We use the session id retrieved from the URL to indicate the order as finished in the get handler of PaymentSuccessView.

payment_success.html

{%% extends 'payments/base.html' %%}

{%% block content %%}
<div class="container">
    <div class="jumbotron text-center mt-5">
        <h1 class="text-success">Payment Success</h1>
        <p>We received your order. You can now close this window or use the
            link below to visit
            <a href="{%% url 'home' %%}">home page</a>.
        </p>
    </div>
</div>
{%% endblock content %%}

The Payment Failure Page

Make the necessary changes to the view and template files as described below.

#payments/views.py

class PaymentFailedView(TemplateView):
    template_name = "payments/payment_failed.html"
{%% extends 'payments/base.html' %%}

{%% block content %%}
<div class="container">
    <div class="jumbotron text-center mt-5">
        <h1 class="text-danger">Payment Failed</h1>
        <p>We received your order. You can now close this window or use the
            link below to visit
            <a href="{%% url 'home' %%}">home page</a>.
        </p>
    </div>
</div>
{%% endblock content %%}

Order History Page

On this page, we can view the payment history.

payments/views.py

class OrderHistoryListView(ListView):
    model = OrderDetail
    template_name = "payments/order_history.html"
order_history.html

{%% extends 'payments/base.html' %%}

{%% block content %%}
    <h1 class="text-center">Order History</h1>
    <div class="container">
        
        {%% if object_list %%}
        <table class="table table-striped table-bordered table-hover">
            <thead>
                <tr>
                    <th>Product</th>
                    <th>Customer Email</th>
                    <th>Amount</th>
                    <th>Status</th>
                    <th>Date</th>
                </tr>
            </thead>
            <tbody>
                
                {%% for i in object_list %%}
                    <tr>
                        <td>{{ i.product.name }}</td>
                        <td>{{ i.customer_email }}</td>
                        <td>{{ i.amount }}</td>
                        <td>
                            {%% if i.has_paid %%}
                                <b class="text-success">Success</b>
                            {%% else %%}
                                <b class="text-danger">Failed</b>
                            {%% endif %%}
                        </td>
                        <td>{{ i.created_on }}</td>
                    </tr>
                {%% endfor %%}
                    
            </tbody>
        </table>
        {%% else %%}
        <div class="alert alert-info">
            Payment history is empty.
        </div>
        {%% endif %%}
            
    </div>
{%% endblock content %%}

Conclusion

Stripe Payments is a platform for accepting payments. It lets you use a credit or debit card transaction to move money from a customer’s bank account to your business’s account. Aside from eCommerce, the company has added support for credit card terminals, point-of-sale systems, and mobile processing solutions to its portfolio.

While none of Stripe’s goods or services are genuinely unique, its solutions continue to stand out in various ways from those of its competitors. Consider the following features, which are just a few of the many benefits of using Stripe’s payment system: A simple and quick onboarding process, an integrated payment processing system, a wide range of customization options, and a comprehensive set of security features are all available.

We have comprehensively covered how we integrate our Django application to the Stripe Payment Gateway in this article.
If you run into any problems, consult the Stripe manual or post them in the comments section.

Similar Posts

Leave a Reply

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