Table of content

  • Overview
  • Setting up Django project and app
  • Setup Stripe
  • Creating Checkout Session
  • Webhook
  • Saving the customer order in the database
  • Testing our app

Overview:

Stripe is one of the most used payment processing platforms. In this tutorial, we 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, we will be using its documentation as a guide.

You can refer to the Github repo for the source code: https://github.com/raturitechmedia/django-stripe-integration

Setting up Django project :

First thing first we will create a new Django project called StripeIntegrate.

django-admin startproject stripeIntegrate

Next, we need to create an app. Before typing a command to make an app, make sure you are in the same directory as manage.py.

We will create an app called tutorial.

python manage.py startapp tutorial

Now, we need to register the tutorial app in our project. Add it to theINSTALLED_APPSlist in the project settings.py.

Setup Stripe:

Install the stripe python package.

pip install stripe

Once, all of this is done. We need to create an account onstripeand go to the dashboard. And navigate to developers.

Next, Click on API keys.

Copy the publishable key and secret key. Make sure you are in a test environment by checking the toggle below developers.

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

Do not forget to put your keys in quotes ' '.

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

Creating stripe checkout session:

Now, open views.pyand create a view calledcreate_checkout_session.The checkout session represents the things that customer sees on the checkout form produced by stripe. Checkout session is unique for each checkout. It returns asession IDin 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 the payment was successful customer get redirected to asuccess URLand if it fails customer gets redirected to thecancel URL.Make sure to import stripe in your views.py file. 

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 sameviews.pyfile we will also create three other views calledhome, success, and cancel.

#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, we need to add URLs for the views we have created above.

In the urls.py file of the project add a URL as follow:

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

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

Now, create aurls.pyfile in the tutorial directory. And add the URLs for the views we 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, we need to create a templates folder in our root directory. It should be created in the same directory asmanage.py. Inside the templates folder, we will create three filescheckout.html, success.html, and cancel.html.

Now, add the templates to thesettings.pyfile.

Next, we need to add the 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 on your server 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:

<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, we have completed the payment but they still need to be verified. 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 in our 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 our app saying that a user has made a payment.

Stripe CLI:

The simplest way to create webhooks locally is with theStripe CLI.

Installing stripe CLI:

As a first step install stripe CLI. To do that follow the stripe CLI install guide.

Then, we need to pair stripe CLI with our stripe account. To do that run the following command.

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, we will create a new view in ourviews.pyfile 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 theurls.pyfile 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 thesettings.pycalled STRIPE_ENDPOINT_SECRET and assign it the SECRET KEY. Once you are done with it update the endpoint_secret variable in the webhook view as follow:

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, we need to create a model with the following fields email, paid (a boolean value), amount, and description (To store session-id). Populate yourmodels.pyfile 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, we need to register this model in ouradmin.pyfile inside the tutorial. Populate your admin.py file with the code below:

from django.contrib import admin
from .models import Order

# Register your models here.

admin.site.register(Order)

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

python manage.py makemigrations

followed by the command below:

python manage.py migrate

and lastly, run the command to create a superuser.

python manage.py createsuperuser

Type necessary details like username, email, and password.

Now, inside the create_checkout_session view in views.py create an object of Order. And pass an additional argument called metadata in the checkout session, it 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})

Notice everything is assigned to null initially and now inside the webhook view ascheckout.session.completedevent will trigger we will update the attributes of the order object. This will let us know that the customer has actually paid because we are updating the object inside ourcheckout.session.completedevent. And then we can fulfill the purchase according to the requirements.

And, lastly update your webhook view as follow:

@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:

Now 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 http://127.0.0.1:8000/admin/ and check if the order table has the customer details.

You can refer to the Github repo for the source code: https://github.com/raturitechmedia/django-stripe-integration

HAPPY CODING