As one of the largest online payment processors, Stripe allows developers to quickly set up payment gateways for their applications. If you ever wanted to create your own e-commerce store or subscription-based service, integrating Stripe with your web app is an easy way to get started.
Django setup
Integrating Stripe with Django is simple. First, create a checkout page for your web app. The page should display the products from a user's cart. The "cart" can be a model field under the profile model if you are using a one-to-one link to extend the user.
env > mysite > main > models.py
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
class Product(models.Model):
product_name = models.CharField(max_length=150)
product_type = models.CharField(max_length=25)
product_description = models.TextField()
product_price = models.IntegerField()
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
cart = models.ManyToManyField(Product)
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
env > mysite > main > views.py
from django.shortcuts import render, redirect
from .models import Product, Profile
...
def checkout(request)
if request.user.is_authenticated:
cart = Profile.objects.get(user= request.user).cart
total = cart.aggregate(Sum('product_price'))['product_price__sum']
return render(request,"main/checkout.html", {"cart":cart, "total":total})
else:
redirect("main:homepage")
env > mysite > main > templates > main > checkout.html
{% extends "main/header.html" %}
{% block content %}
<div>
<br><br>
<h2> Checkout </h2>
<br><br>
<table style="width:100%">
<tr style=" font-size: 20px">
<th>Name</th>
<th>Price</th>
</tr>
{% for c in cart.all %}
<tr>
<td>{{ c.product_name }}</td>
<td>${{ c.product_price }}</td>
</tr>
{% endfor %}
<tr>
<td style="padding-top:30px">Total</td>
<td>${{ total }}</td>
</tr>
</table>
</div>
{% endblock %}
If you are interested in using the Django template tags extend
and block content
, learn how to extend the header.html.

Create a Stripe account
Now that we have a basic checkout page that lists the products in a user's cart, let's integrate with Stripe. First, create a Stripe account.

Stripe installation
Next, install the Stripe package to have access to the official libraries that give access to Stripe's API.
CLI
pip install --upgrade stripe
Django integration
Import stripe
, @csrf_exempt
, json
, and JsonResponse
at the top of the views.py page and then create a new function that creates a payment intent. Basically, a payment intent is setting up the data we need to create a purchase. This function will be called from the checkout template using a JavaScript function provided by Stripe.
The first value we need is the total payment amount. While we could get this from our checkout page, it's unsafe to do so given a user could change the amount on the page. In this case, we will calculate the total the same way as before.
Next plugin in your test secret key from Stripe. You'll notice we check for a POST
request method. We will create this in the checkout.html page soon. Notice that for the amount
we use total
. Finally, enter your test publishable key for the JSON Response.
env > mysite > main > views.py
from django.shortcuts import render, redirect
from .models import Product, Profile
from django.views.decorators.csrf import csrf_exempt
import stripe
import json
from django.http import JsonResponse
...
def checkout(request)
...
@csrf_exempt
def createpayment(request):
if request.user.is_authenticated:
cart = Profile.objects.get(user=request.user).products
total = cart.aggregate(Sum('product_price'))['product_price__sum']
total = total * 100
stripe.api_key = 'your test secret key'
if request.method=="POST":
data = json.loads(request.body)
# Create a PaymentIntent with the order amount and currency
intent = stripe.PaymentIntent.create(
amount=total,
currency=data['currency'],
metadata={'integration_check': 'accept_a_payment'},
)
try:
return JsonResponse({'publishableKey':
'your test publishable key', 'clientSecret': intent.client_secret})
except Exception as e:
return JsonResponse({'error':str(e)},status= 403)
Alright, now that we have a payment intent, we need to configure checkout.html to retrieve and then process the payment intent. First, let's add the credit card form and the hidden form that will be submitted when the payment is complete. Add just below the table. Feel free to add the CSS as well.
env > mysite > main > templates > main > checkout.html
{% extends "main/header.html" %}
{% block content %}
<div>
<br><br>
<h2> Checkout </h2>
<br><br>
<table style="width:100%">
...
</table>
<h4 class="font-weight-bold my-3">Billing</h4>
<div class="card mx-5">
<div class="card-body">
<div class="sr-root">
<div class="sr-main">
<form id="payment-form" class="sr-payment-form">
{% csrf_token %}
<div class="sr-combo-inputs-row">
<div class="sr-input sr-card-element" id="card-element"></div>
</div>
<div class="sr-field-error" id="card-errors" role="alert"></div>
<button id="submit" class="btn">
<div class="spinner-border spinner-border-sm text-light hidden" id="spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
<span id="button-text">Pay</span><span id="order-amount"></span>
</button>
</form>
<div class="sr-result hidden">
<p>Payment completed<br></p>
<pre>
<code></code>
</pre>
</div>
</div>
</div>
</div>
</div>
<form id="payload" class="hidden" action="/payment-complete" method="post">
{% csrf_token %}
<input id ="data-payload" type="hidden" name="payload"/>
</form>
</div>
<style>
/**
* The CSS shown here will not be introduced in the Quickstart guide, but shows
* how you can use CSS to style your Element's container.
*/
.StripeElement {
box-sizing: border-box;
height: 40px;
padding: 10px 12px;
border: 1px solid transparent;
border-radius: 4px;
background-color: white;
box-shadow: 0 1px 3px 0 #e6ebf1;
-webkit-transition: box-shadow 150ms ease;
transition: box-shadow 150ms ease;
}
.StripeElement--focus {
box-shadow: 0 1px 3px 0 #cfd7df;
}
.StripeElement--invalid {
border-color: #fa755a;
}
.StripeElement--webkit-autofill {
background-color: #fefde5 !important;
}
.hidden {
display: none;
}
#submit:hover {
filter: contrast(120%);
}
#submit {
font-feature-settings: "pnum";
--body-color: #f7fafc;
--button-color: #556cd6;
--accent-color: #556cd6;
--gray-border: #e3e8ee;
--link-color: #fff;
--font-color: #697386;
--body-font-family: -apple-system,BlinkMacSystemFont,sans-serif;
--radius: 4px;
--form-width: 400px;
-webkit-box-direction: normal;
word-wrap: break-word;
box-sizing: border-box;
font: inherit;
overflow: visible;
-webkit-appearance: button;
-webkit-font-smoothing: antialiased;
margin: 0;
font-family: inherit;
-webkit-tap-highlight-color: transparent;
font-size: 16px;
padding: 0 12px;
line-height: 32px;
outline: none;
text-decoration: none;
text-transform: none;
margin-right: 8px;
height: 36px;
border-radius: var(--radius);
color: #fff;
border: 0;
margin-top: 16px;
font-weight: 600;
cursor: pointer;
transition: all .2s ease;
display: block;
box-shadow: 0 4px 5.5px 0 rgba(0,0,0,.07);
width: 100%;
background: var(--button-color);
}
</style>
{% endblock %}

Now let's add the following scripts to checkout.html to get then process the payment intent. The first belongs at the top of the page and the second can be placed right before the endblock
tag.
env > mysite > main > templates > main > checkout.html
{% extends "main/header.html" %}
{% block content %}
<script src="https://js.stripe.com/v3/"></script>
...
{% block content %}
env > mysite > main > templates > main > checkout.html
{% extends "main/header.html" %}
{% block content %}
...
<script type="text/javascript">
// A reference to Stripe.js
var orderData = {
items: [{ id: "products" }],
currency: "usd",
};
// Disable the button until we have Stripe set up on the page
document.getElementById("submit").disabled = true;
fetch("/create-payment-intent", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(orderData)
})
.then(function(result) {
return result.json();
})
.then(function(data) {
return setupElements(data);
})
.then(function({ stripe, card, clientSecret }) {
document.getElementById("submit").disabled = false;
// Handle form submission.
var form = document.getElementById("payment-form");
form.addEventListener("submit", function(event) {
event.preventDefault();
// Initiate payment when the submit button is clicked
pay(stripe, card, clientSecret);
});
});
// Set up Stripe.js and Elements to use in checkout form
var setupElements = function(data) {
stripe = Stripe(data.publishableKey);
var elements = stripe.elements();
var style = {
base: {
color: "#32325d",
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#aab7c4"
}
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a"
}
};
var card = elements.create("card", { style: style });
card.mount("#card-element");
return {
stripe: stripe,
card: card,
clientSecret: data.clientSecret
};
};
/*
* Calls stripe.confirmCardPayment which creates a pop-up modal to
* prompt the user to enter extra authentication details without leaving your page
*/
var pay = function(stripe, card, clientSecret) {
changeLoadingState(true);
// Initiate the payment.
// If authentication is required, confirmCardPayment will automatically display a modal
stripe
.confirmCardPayment(clientSecret, {
payment_method: {
card: card
}
})
.then(function(result) {
if (result.error) {
// Show error to your customer
showError(result.error.message);
} else {
// The payment has been processed!
orderComplete(clientSecret);
}
});
};
/* ------- Post-payment helpers ------- */
/* Shows a success / error message when the payment is complete */
var orderComplete = function(clientSecret) {
// Just for the purpose of the sample, show the PaymentIntent response object
stripe.retrievePaymentIntent(clientSecret).then(function(result) {
var paymentIntent = result.paymentIntent;
var paymentIntentJson = JSON.stringify(paymentIntent, null, 2);
// post data and show new page
var form2 =document.getElementById("payload");
var input = document.getElementById("data-payload")
input.value = paymentIntentJson;
form2.submit();
changeLoadingState(false);
});
};
var showError = function(errorMsgText) {
changeLoadingState(false);
var errorMsg = document.querySelector(".sr-field-error");
errorMsg.textContent = errorMsgText;
setTimeout(function() {
errorMsg.textContent = "";
}, 4000);
};
// Show a spinner on payment submission
var changeLoadingState = function(isLoading) {
if (isLoading) {
document.getElementById("submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.getElementById("submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
};
</script>
{% endblock %}
Although a little complicated, this script fetches the payment intent from the function we just created, enables the Stripe payment form, and handles the payment submission.
Add "create-payment-intent" to urls.py so we can fetch the payment intent from the view.
env > mysite > main > urls.py
from django.urls import path
from . import views
app_name = "main"
urlpatterns = [
...
path("create-payment-intent", views.createpayment, name="create-payment-intent"),
]
Looks good.

Also, notice the second form's action is "/payment-complete". Make sure to add this to your urls.py and add the final view that sends the user to a page informing them of their purchase. If you want to send them an email of their purchase, you could do so in this view or configure a webhook to handle it.
env > mysite > main > urls.py
from django.urls import path
from . import views
app_name = "main"
urlpatterns = [
...
path("create-payment-intent", views.createpayment, name="create-payment-intent"),
path("payment-complete", views.paymentcomplete, name="payment-complete"),
]
env > mysite > main > views.py
def paymentcomplete(request):
if request.method=="POST":
data = json.loads(request.POST.get("payload"))
if data["status"] == "succeeded":
# save purchase here/ setup email confirmation
return render(request, "main/payment-complete.html")
You could also add a Purchase model that contains information about a purchase. In any case, you can get the payment amount from "data".
Testing
To test the purchase form, use 4242 4242 4242 4242 as the test credit card. Then go to your Stripe dashboard to check if the payment succeeded.

Production
In production, add your live secret key and publishable key. Make sure to secure this sensitive information with environment variables. For more information on securing sensitive data, check out Securing Sensitive Django Variables using Python Decouple.