Build a payment system with Django 3.1 and Stripe from scratch. Part 3
By: Alberto Naranjo
Tags: Software Development, Python3, Python, Stripe, Django

Photo by Anne Preble on Unsplash

In this third part of our article series we will learn how to code the secure session checkout creator for Stripe.

The full code of this project is here however I recommend you to follow the step-by-step instructions instead of copying the whole project in your local environment.

What we will learn today:

Register new products and prices in Stripe

Code the checkout session creator with Stripe

Register new products in Stripe

In our previous article we made possible for the user to check the available products in our website and choose one between the options: Regular, Pro and Platinum. Now before continuing defining the payment workflow we need to register this products with associated prices in Stripe

Go to Stripe Dashboard → Products → + Add product and fill the basic information for a product. For the pricing choose one time and fill the price we defined in our front store page:

Pricing model definition for a product

We need to create 3 products in total with a one-time pricing:

Regular Product: $5

Pro Product: $15

Platinum Product: $29

Then go to every product detail page and copy the product’s price API ID to use in the next section:

We need the Price API ID not the product id!

Again make sure you are creating the products in the with the testing data mode and not in the live version of your account!

If you are wondering why we are copying the price id and not the product id is because every product can have different pricing models for example depending on the quantity of the items. The Stripe checkout session we are going to setup doesn’t really care about what product details you are selling but how much they need to charge the user for the products.

The Session Creator View

As we discussed previously this component is one of the most important view in our project. It will handle the secure checkout session creation with Stripe. After a customer chooses the products/services they want to buy they will be redirected to payment page hosted by Stripe to pay for them with several payment methods available. The checkout session will fit most of the selling workflow cases but Stripe also offers another workflow where you would be in charge of showing the components and do more processing in server-side and frontend for the project. However for this tutorial we are going to stay with the checkout session workflow which is the easiest and most secure way to handle payments.

Go to our views declaration file inside our Stripe app stripe_shop/views.py and paste the next lines filling you price id for every product:

PRODUCTS_STRIPE_PRICING_ID = {

'product_regular': 'price_123 ...',

'product_pro': 'price_123 ...',

'product_platinum': 'price_123 ...',

}

We are creating a constant dictionary to associate our products to the prices in Stripe. If you remember from the previous article we were passing the a product name in a url parameter for the future session creator view. Hard-coding these values is probably not the best way to handle this workflow but it will work for the tutorial. For a real project I would recommend you to setup an additional product management app where to can modify your products and update the prices anytime without touching your code base.

Now we are going to code the view itself. Let’s update stripe_shop/views.py . First you need to import some libraries and initialize your secret code to Stripe:

from django.core.exceptions import SuspiciousOperation

from django.http import JsonResponse

from django.contrib.auth.decorators import login_required

from django.views.decorators.csrf import csrf_exempt

from myshop import settings

import stripe

stripe.api_key = settings.STRIPE_API_KEY

Now let’s work the checkout session creator view function:

@login_required

@csrf_exempt

def create_stripe_checkout_session(request, product_name):



try:



checkout_session = stripe.checkout.Session.create(

payment_method_types=['card'],

customer=request.user.customerprofile.stripe_customer_id,

client_reference_id=request.user.id,

metadata={'product_name': product_name, },

line_items=[

{'price': PRODUCTS_STRIPE_PRICING_ID[product_name], 'quantity': 1, },

],

mode='payment',

success_url='http://localhost:8000/success_payment.html',

cancel_url='http://localhost:8000/cancelled_payment.html', )



return JsonResponse({'id': checkout_session.id})



except Exception as e:

print(e)

raise SuspiciousOperation(e)

We are using two important decorators before the function definition:

@login_required is forcing the user to be registered and logged before even processing the view, if the user wasn’t logged in he will be redirected to the Django’s default login page. This is critical because we can not process sessions for anonymous users in our case, since we are associating our users to Stripes customer objects. Furthermore after a successful payment we are planning to take some business action with the user account as activating extra features or shipping an order.

@csrf_exempt is disabling a default security feature in Django when sending forms and POST queries. This is required as the query we are sending from the javascript code is not handling the secret token. We could render the template passing the server-side generated token and injecting it as a javascript variable, however our current workflow is safe enough and in order to simplify the tutorial we will move forward without it.

When we are calling our view function we are passing the product name variable from a url path parameter (that we haven’t set it up yet). If you remember our last article this product name parameter was coming from a buy button’s value property. Next we initialize a stripe.checkout.Session instance, the constructor accepts several parameters that will define the behavior and handling of the payment screen. You can check all the Session object properties in the official documentation. Note we are initializing customer to the customer id we saved in the CustomerProfile object of the current user. We fill as well customer_email and customer_reference_id with information from the user. Finally we include just only one item in line_items with the price id associated to the product name we have received as a parameter.

Finally if every goes right Stripe will return a secure session id to the front end and he will be redirected to a payment screen hosted by Stripe. If anything goes wrong we have a catch-all handler defined to raise a Django’s default SuspiciousOperation exception that is usually associated to a HTTP error 400. We are also defining where to redirect the user after the payment is successful or cancelled however these views are still not defined. Also if you are thinking we will handle all the post-payment action in these view you are wrong. Payments will be made asynchronously and we need later to handle a web hook where we will receive Stripe events. But we will explore this webhook a little later. Now we need to finish our checkout workflow.

Modifying our project URL Path

Now that we have our view ready we need to call it from an endpoint. We will got to our main project app and modify the url file my_shop/urls.py :

...

from stripe_shop.views import create_stripe_checkout_session urlpatterns = [

...

path('create-checkout-session/<str:product_name>', create_stripe_checkout_session),

]

We defining the path we used in the products page and passing the product name in the url. Our view will be called using the request and product_name parameter. We also need to add the path to views (we can render the templates directly as views) for the urls we defined in the checkout session creation for successful and cancelled payments, however we are not going to cover this again as we learnt how to do it in the last article.

In order to test our workflow I recommend you to add the staff permission to the test user we created in the first article. And then use the admin login panel to authenticate the user in the system.