Stripe payment gateway integration in django

Photo by rupixen.com on Unsplash

Stripe payment gateway integration in django

Stripe is integrated with Django via the stripe javascript SDK. Here is an overview of the flow:

  • A user clicks on the payment button.

  • A checkout session is generated and saved at the backend and is also passed to the front end.

  • Customer is redirected to the stripe payment gateway.

  • Once the payment is verified at the stripe end, the customer is redirected to the success page.

  • At this same time, stripe triggers a checkout.session.completed event along with the checkout session.

  • Webhook is an URL in Django, where payment for the particular checkout session is verified.

Overview

Stripe is one of the most used payment processing platforms. In this tutorial, you will learn how to integrate stripe checkout into a Django app. Stripe has very detailed and simple documentation which makes it developer-friendly.

In this tutorial, you will be using stripedocumentation as a guide.

You can refer to this GitHub repo for referencing code snippets used in this tutorial.

Setting up the Django project

Let's create a new Django project named StripeIntegrate.

[Optional] If you work with virtual environments, check out this tutorial.

django-admin startproject stripeIntegrate

Next, create a Django app. Make sure you are in the same directory as manage.py.

python manage.py startapp tutorial

Now, you need to register tutorial app. Add it to the INSTALLED_APPS list in the project settings.py.

Install the stripe python package.

pip install stripe

Setup Stripe

  • Create an account on stripe.

  • After account creation; go to the dashboard -> developers -> API keys.

  • Check the test mode and viewing test data to on.

  • Copy the publishable key and secret key.

Grab the keys and make two variables in the settings.py of the project and paste them there.

STRIPE_PUBLIC_KEY=''
STRIPE_PRIVATE_KEY=''

At last, make sure that you have set up an account name on the Stripe Dashboard.

Creating stripe checkout session

  • Now, open tutorial/views.py and create a view called create_checkout_session.

  • The checkout session represents the things that the customer sees on the checkout form produced by stripe.

  • The checkout session is unique for each checkout. It returns a session ID in response which is used by the stripe javascript module to redirect customers to a unique URL where they complete the transaction by filling in the necessary details like card number and email, etc.

  • If payment is successful, the customer gets redirected to a success URL, and if it fails customer gets redirected to the cancel URL.

import stripe
from django.shortcuts import render
from django.conf import settings
from django.http import JsonResponse,HttpResponse
from django.views.decorators.csrf import csrf_exempt

stripe.api_key = settings.STRIPE_PRIVATE_KEY
YOUR_DOMAIN = 'http://127.0.0.1:8000'

@csrf_exempt
def create_checkout_session(request):
 session = stripe.checkout.Session.create(
 payment_method_types=['card'],
 line_items=[{
 'price_data': {
 'currency': 'inr',
 'product_data': {
 'name': 'Intro to Django Course',
 },
 'unit_amount': 10000,
 },
 'quantity': 1,
 }],
 mode='payment',
 success_url=YOUR_DOMAIN + '/success.html',
 cancel_url=YOUR_DOMAIN + '/cancel.html',
 )
 return JsonResponse({'id': session.id})

In the same views.py file you also have to create three other views

#home view
def home(request):
 return render(request,'checkout.html')

#success view
def success(request):
 return render(request,'success.html')

 #cancel view
def cancel(request):
 return render(request,'cancel.html')

Now, add URLs for the views you have created above.

In the urls.py file of the project add an URL as follow to link the tutorial app first.

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

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

Now, create a urls.py file in the tutorial directory and add the URLs for the views you have created above as follows.

from django.urls import path
from . import views

urlpatterns = [
 path('', views.home, name='home'),
 path('create-checkout-session/', views.create_checkout_session, name='checkout'),
 path('success.html/', views.success,name='success'),
 path('cancel.html/', views.cancel,name='cancel'),
]

Next, you need to create a templates folder in the root directory where manage.py is located. Inside the templates folder, you need to create three files

Now, add the templates to the settings.py file.

Next, you need to add a checkout button in checkout.html and add an event listener to the checkout button.

When the customer clicks the button a request is sent to the endpoint to create a new Checkout Session and it returns a session ID which is used to redirect the customer to the stripe checkout page.

checkout.html

<html>
 <head>
 <title>Buy cool new product</title>
 <script src="https://js.stripe.com/v3/"></script>
 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
 </head>
 <body>
<div class="container">
<h1>Place an order here</h1>
 <button type="button" class="btn btn-lg btn-primary" id="checkout-button">Checkout</button>
</div>
 <script type="text/javascript">
 // Create an instance of the Stripe object with your publishable API key
 var stripe = Stripe('pk_test_51IvESHSEJXkgN9eEfXq71htC7r3iPvFiOKqvRaBDNqxOVsDoN1keJQz4Ry8xWDQLYdS6MXC1CvHq4YD7jqwF8Cms00n5aUeFev');
 var checkoutButton = document.getElementById('checkout-button');

 checkoutButton.addEventListener('click', function() {
 // Create a new Checkout Session using the server-side endpoint you
 // created in step 3.
 fetch('/create-checkout-session/', {
 method: 'POST',
 })
 .then(function(response) {
 return response.json();
 })
 .then(function(session) {
 return stripe.redirectToCheckout({ sessionId: session.id });
 })
 .then(function(result) {
 // If `redirectToCheckout` fails due to a browser or network
 // error, you should display the localized error message to your
 // customer using `error.message`.
 if (result.error) {
 alert(result.error.message);
 }
 })
 .catch(function(error) {
 console.error('Error:', error);
 });
 });
 </script>
 </body>
</html>

Finally, populate the success.html and cancel.html with the following code:

success.html

<html>
<head>
 <title>Thanks for your order!</title>
 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
</head>
<body>
 <div class="container">
 <p>
 <h3>Payment Successful</h3>
 <p>Thank you for your purchase!</p>
 <p> If you have any questions, please let us know through emails.</p>
 </p>
 </div>
</body>
</html>

cancel.html

<html>
<head>
 <title>Checkout canceled</title>
 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
</head>
<body>
 <div class="container">
 <h3>Forgot to add something to your cart? </h3>
 <h5>Shop around then come back to pay!</h5>
 </div>
</body>
</html>

Now run the server and navigate to http://127.0.0.1:8000/

python manage.py runserver

Click on the Checkout button. Fill in the necessary details with the test card information (4242 4242 4242 4242) and you should be redirected to the success page.

Webhook

Now, even if payments have been completed successfully at the stripe end but they still need to be verified at Django's end. We want to ensure that the user has actually paid and does not want to depend on customers being redirected to the success page. For this purpose, we need to set up webhooks.

Webhook is an end-point or an api in the app that gets called automatically as a result of some event happening in this case when the user makes the payment it will send a request to the app saying that a user has made a payment along with session id.

The simplest way to create webhooks locally is with the Stripe CLI.

Installing stripe CLI

As a first step install stripe CLI. To do that follow the stripe CLI installation guide. Then, you need to authenticate stripe CLI with your stripe account. To do that run the following command in your terminal.

stripe login

You will be prompted to open your default browser and log in to the Stripe Dashboard to give Stripe CLI access to your stripe account information. Once, you are done with giving CLI access to your account you will see a confirmation message in the terminal along with an account ID.

This pairing code verifies your authentication with Stripe.
Press Enter to open the browser (^C to quit)
> Done! The Stripe CLI is configured for YOUR ACCOUNT NAME with account id
 YOUR ACCOUNT ID

Now, you need to create a new view in your tutorial/views.py file called webhook.

@csrf_exempt
def webhook(request):
 endpoint_secret = ''
 payload = request.body
 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)

 # Handle the checkout.session.completed event
 if event['type'] == 'checkout.session.completed':
 print("Payment was successful.")
 # TODO: run some custom code here

 return HttpResponse(status=200)

Note, the endpoint_secret in the above view, nothing is assigned to it right now.

Now, let's create that endpoint secret. But before that update the urls.py file of the tutorial app and add the URL for the view we have created above.

from django.urls import path
from . import views

urlpatterns = [
 path('', views.home, name='home'),
 path('create-checkout-session/', views.create_checkout_session, name='checkout'),
 path('success.html/', views.success,name='success'),
 path('cancel.html/', views.cancel,name='cancel'),
 path('webhooks/stripe/', views.webhook,name='webhook'), #updated line
]

Going back to creating the endpoint_secret key. Run the following command in the new terminal.

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

This will forward events to the webhook endpoint. And will generate a webhook signing secret key.

> Ready! Your webhook signing secret is YOUR WEBHOOK SECRET KEY (^C to quit)

Copy this key and make another variable inside the settings.py called STRIPE_ENDPOINT_SECRET and assign it the SECRET KEY generated. Once you are done with it update the endpoint_secret variable in the webhook view as follows.

endpoint_secret = settings.STRIPE_ENDPOINT_SECRET

We have now set up a webhook for our tutorial app. You can run the server now and check if it is working fine.

Saving the Customer Order

The last thing to do is to save customer orders for future reference and get informed at the Django level who has paid how much. For that, you need to create a new model with the following fields:

  • email,

  • paid (a boolean value),

  • amount, and

  • description (To store session-id).

Populate your models.py file inside the tutorial app with the code below.

from django.db import models

# Create your models here.

class Order(models.Model):
 email = models.EmailField(max_length=254)
 paid = models.BooleanField(default="False")
 amount = models.IntegerField(default=0)
 description = models.CharField(default=None,max_length=800)
 def __str__(self):
 return self.email
``

Next, register this model inside [admin.py](https://github.com/raturitechmedia/django-stripe-integration/blob/master/stripeIntegrate/tutorial/admin.py) file inside the tutorial app. Populate your admin.py file with the code below

```python
from django.contrib import admin
from .models import Order

# Register your models here.

admin.site.register(Order)

Lastly, run migrations and create a superuser. To do that type the following command in the terminal.

python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

Type necessary details like username, email, and password to create a superuser.

Now, in the create_checkout_session view in tutorial/views.py create an object of Order and pass an additional argument called metadata in the checkout session.

Metadata is a dictionary of custom information. In metadata, we are providing order_id which contains the id of the Order as key-value pair. This will help in the webhook while updating the Order object.

from .models import Order #new
@csrf_exempt
def create_checkout_session(request):
 #Updated- creating Order object
 order=Order(email=" ",paid="False",amount=0,description=" ")
 order.save()
 session = stripe.checkout.Session.create(
 client_reference_id=request.user.id if request.user.is_authenticated else None,
 payment_method_types=['card'],
 line_items=[{
 'price_data': {
 'currency': 'inr',
 'product_data': {
 'name': 'Intro to Django Course',
 },
 'unit_amount': 10000,
 },
 'quantity': 1,
 }],
 #Update - passing order ID in checkout to update the order object in webhook
 metadata={
 "order_id":order.id
 },
 mode='payment',
 success_url=YOUR_DOMAIN + '/success.html',
 cancel_url=YOUR_DOMAIN + '/cancel.html',
 )
 return JsonResponse({'id': session.id})

And, lastly, update your webhook view as follows to handle checkout.session.completed event.

checkout.session.completed event is triggered by stripe when payment is successful. On the basis of the we will set the paid attribute of order object to true.

@csrf_exempt
def webhook(request):
 print("Webhook")
 endpoint_secret = settings.STRIPE_ENDPOINT_SECRET
 payload = request.body
 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)

 # Handle the checkout.session.completed event
 if event['type'] == 'checkout.session.completed':
 #NEW CODE
 session = event['data']['object']
 #getting information of order from session
 customer_email = session["customer_details"]["email"]
 price = session["amount_total"] /100
 sessionID = session["id"]
 #grabbing id of the order created
 ID=session["metadata"]["order_id"]
 #Updating order
 Order.objects.filter(id=ID).update(email=customer_email,amount=price,paid=True,description=sessionID)

 return HttpResponse(status=200)

Testing out the tutorial app

  • Run the server and navigate to http://127.0.0.1:8000/.

  • Click the checkout button and fill the checkout form with the dummy data.

  • Go to 127.0.0.1:8000/admin and check if the order table has the customer details.

Refer to the repository for the source code.

Did you find this article valuable?

Support Nitin Raturi by becoming a sponsor. Any amount is appreciated!