Payment Gateway Integration with Django

In this Python tutorial, I will show you payment gateway integration with Django, where you will understand how to use Razorpay as a payment getaway in your project.

This tutorial is based on the previous tutorial How to Add Items to Cart in Django in Python, where you learned how to add the items to the cart.

So in this tutorial, you will learn how to implement the checkout functionality for the items in the cart by adding the payment functionality to your project.

Also, you will learn about configuring your project for the Razorpay payment gateway and how to create orders in Razorpay and initiate the payment process for the items in the cart.

Payment Gateway Integration with Django

First, go to the website https://razorpay.com/ and click on the signup button to create a new account.

Payment Gateway Integration with Django Razorpay

After signing up, log in go to the Razorpay dashboard click on Account & Setting from the sidebar menu and then click on the API keys in the section Website and App Settings.

Payment Gateway Integration with Django Razorpay API

Then click on the button Generate Test Key as shown in the below picture.

Razorpay API Creation Payment Gateway Integration with Django

After clicking on the Generate Test Key, it creates an API key for you as shown in the below picture.

Payment Gateway Integration with Django API Key

Copy the Key ID and Key Secret or Download Key Details, You will use this key later in our project. Then click on the OK button, and you have successfully generated the API key to use Razorpay in your Django project.

Open the file models.py of your Django app ‘profile_app’ and add the two models Order and OrderItem.

Add the following code to create an Order model.

class Order(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    products = models.ManyToManyField(Product, through='OrderItem')
    total_amount = models.DecimalField(max_digits=10, decimal_places=2)
    payment_id = models.CharField(max_length=100, null=True, blank=True)
    payment_status = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return f"Order {self.id} by {self.user.username}"

Also, add the following code for model OrderItem.

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField(default=1)
    item_total = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return f"{self.quantity} x {self.product.name} in Order {self.order.id}"
Payment Gateway Integration with Django Model Order and OrderItems

Then register the models in the file admin.py of your Django app ‘profile_app’ by adding the following code.

from django.contrib import admin
from .models import Profile, Product, Cart, CartItem, Order, OrderItem

admin.site.register(Profile)
admin.site.register(Product)
admin.site.register(CartItem)
admin.site.register(Cart)
admin.site.register(Order)
admin.site.register(OrderItem)
Registering App Payment Gateway Integration with Django Model Order and OrderItems

Now migrate the model by running the below commands one by one in your terminal.

python manage.py makemigrations
python manage.py migrate
Payment Gateway Integration with Django Migration Model Order and OrderItem

After migrating the model, install the package Razorpay using the below command.

pip install razorpay 
Installing Razorpay Payment Gateway Integration with Django

After installing Razorpay, open the setting.py file of your Django project ‘userprofile’ and add the key id and key secrete that you have generated above.

RAZOR_KEY_ID = 'KEY_ID'
RAZOR_KEY_SECRET = 'qKEY_SECRET'
Payment Gateway Integration with Django Adding RazorPay Key Id and Secret

After configuring your Django project for Razorpay, create a view create_order in the views.py file of your Django app ‘profile_app’ and add the following code.

from .models import Profile, Product, Cart, CartItem, Order, OrderItem
from django.http import JsonResponse
from django.conf import settings
import razorpay
import json
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def create_order(request):
    if request.method == 'POST':
        user = request.user
        cart = user.cart

        cart_items = CartItem.objects.filter(cart=cart)
        total_amount = sum(item.product.price * item.quantity for item in cart_items)

        try:
            order = Order.objects.create(user=user, total_amount=total_amount)
            for cart_item in cart_items:
                OrderItem.objects.create(
                    order=order,
                    product=cart_item.product,
                    quantity=cart_item.quantity,
                    item_total=cart_item.product.price * cart_item.quantity
                )

            client = razorpay.Client(auth=(settings.RAZOR_KEY_ID, settings.RAZOR_KEY_SECRET))
            payment_data = {
                'amount': int(total_amount * 100),
                'currency': 'INR',
                'receipt': f'order_{order.id}',
                'payment_capture': '1'
            }
            orderData = client.order.create(data=payment_data)
            order.payment_id = orderData['id']
            order.save()

            return JsonResponse({'order_id': orderData['id']})
        
        except Exception as e:
            print(str(e))
            return JsonResponse({'error': 'An error occurred. Please try again.'}, status=500)
Payment Gateway Integration with Django View Create_Order

In the above code, there is a decorator @csrf_exempt before the view create_order, this decorator ensures that the view can be accessed without CSRF protection.

Then view create_order checks if the incoming request is POST or not. If it is a POST request, it extracts the user and their associated cart information using user = request.user, cart = user.cart and store.

  • After this cart items are filtered using the CartItem.objects.filter(cart=cart) and stored in a variable cart_items. The total amount is computed for each item in the cart using the sum(item.product.price * item.quantity for item in cart_items) and stored in the variable total_amount.
  • Within the try block, a new order is created using the Order.objects.create(). Then at line number 216 using the for loop, it creates an OrderItem in the database for each item in the cart.
  • At line number 224, an instance of the Razorpay client is created using razorpay.Client(auth=(settings.RAZOR_KEY_ID, settings.RAZOR_KEY_SECRET)), the razorpay.Client() accepts the authentication parameter to interact with Razorpay API securely.
READ:  Django get all data from POST request

Then at line number 225, define the dictionary payment_data to hold the information that is required to create an order in Razorpay. It holds the following information:

  • amount: The total amount to be deducted, multiplied by 100 because Razorpay considers the amount in the smallest currency unit (i.e., in paisa for INR).
  • currency: The currency type, which is INR in this case.
  • receipt: A unique identifier for the order, consisting of a string “order_” followed by the order ID from the Django database.
  • payment_capture: This is set to ‘1’, telling that the payment should be automatically captured. If set to ‘0’, the payment would be authorized but not captured, which means need to capture the payment manually.

At line 231, a payment order is created using the Razorpay method client.order.create(data=payment_data) by passing the payment_data to it. The response from Razorpay about the order is stored in the variable orderData.

Finally, the order ID received from Razorpay is accessed using the orderData[‘id’] and saved to the order instance field payement_id as (order.payemnt_id) that you have created above. This created a link between the order in your database and the order on the Razorpay.

Then open the cart.html and add the following code.

{% extends "home.html" %}
{% load static %}
{% block content %}
<link rel="stylesheet" href="{% static 'css/cart.css' %}">
<div class="container">
    <div class="cart-header">
        <h1>Your Cart</h1>
    </div>
    <ul>
        {% for item in cart_items %}
        <li class="cart-item">
            <div class="cart-item-details">
                <div class="cart-item-name">{{ item.product.name }}</div>
                <form action="{% url 'remove-from-cart' item.product.id %}" method="post">
                    {% csrf_token %}
                    <button class="remove-from-cart-btn" type="submit">Remove</button>
                </form>
            </div>
            <div class="item-quantity">
                <form action="{% url 'increase-cart-item' item.product.id %}" method="post">
                    {% csrf_token %}
                    <button class="quantity-btn increase-quantity" type="submit">+</button>
                </form>
                <span class="cart-item-quantity" data-quantity="{{ item.quantity }}">{{ item.quantity }}</span>
                <form action="{% url 'decrease-cart-item' item.product.id %}" method="post">
                    {% csrf_token %}
                    <button class="quantity-btn decrease-quantity" type="submit">-</button>
                </form>
            </div>
            <div class="cart-item-price" data-price="{{ item.product.price }}">
                &#8377;{{ item.product.price }}
            </div>
        </li>
        {% endfor %}
    </ul>
    <p class="total-price-data"><span id="total-price"></span></p>


    <a class="continue-shopping-link" href="{% url 'product-list' %}">Continue Shopping</a>
    <a class="checkout-button" href="{% url 'checkout' %}">Checkout</a>
</div>


<script src="{% static 'js/cart.js' %}"></script>

{% endblock %}
Payment Gateway Integration with Django Cart Template

After this also update the cart.js by adding the following code.


document.addEventListener("DOMContentLoaded", function () {


    updateTotalPrice();

    const increaseButtons = document.querySelectorAll(".increase-quantity");
    const decreaseButtons = document.querySelectorAll(".decrease-quantity");

    increaseButtons.forEach((button, index) => {
        button.addEventListener("click", (event) => {
            event.preventDefault();
            const currentItem = event.target.closest(".cart-item");
            const quantityElement = currentItem.querySelector(".cart-item-quantity");
            const priceElement = currentItem.querySelector(".cart-item-price");
            const pricePerItem = parseFloat(priceElement.getAttribute("data-price"));
            const currentQuantity = parseInt(quantityElement.textContent);

            quantityElement.textContent = currentQuantity + 1;
            updateCartItemPrice(priceElement, pricePerItem, currentQuantity + 1);
            updateTotalPrice();
        });
    });

    decreaseButtons.forEach((button, index) => {
        button.addEventListener("click", (event) => {
            event.preventDefault();
            const currentItem = event.target.closest(".cart-item");
            const quantityElement = currentItem.querySelector(".cart-item-quantity");
            const priceElement = currentItem.querySelector(".cart-item-price");
            const pricePerItem = parseFloat(priceElement.getAttribute("data-price"));
            const currentQuantity = parseInt(quantityElement.textContent);

            if (currentQuantity > 1) {
                quantityElement.textContent = currentQuantity - 1;
                updateCartItemPrice(priceElement, pricePerItem, currentQuantity - 1);
                updateTotalPrice();
            }
        });
    });

    function updateCartItemPrice(priceElement, pricePerItem, quantity) {
        const totalPrice = (pricePerItem * quantity).toFixed(2);
        priceElement.innerHTML = "&#8377;" + totalPrice;
    }

    function updateTotalPrice() {
        const itemElements = document.querySelectorAll(".cart-item");

        itemElements.forEach(function (itemElement) {
            const price = parseFloat(itemElement.querySelector(".cart-item-price").getAttribute("data-price"));
            const quantity = parseInt(itemElement.querySelector(".cart-item-quantity").textContent);
            total += price * quantity;
        });

        const totalPriceElement = document.getElementById("total-price");
        totalPriceElement.textContent = total.toFixed(2);
    }
});

Payment Gateway Integration with Django Update Total Price

In the above code, the updateTotalPrice() updates the total price of the items in the cart whenever the number of items increases or decreases.

After creating the order, create a view ‘checkout’ in the views.py file of your Django app and add the following code.

def checkout(request):
    cart_items = CartItem.objects.filter(cart=request.user.cart)
    total_amount = sum(item.product.price * item.quantity for item in cart_items)

    cart_count = get_cart_count(request)
    email = request.user.email
    full_name = request.user.profile.full_name

    context = {
        'cart_count': cart_count,
        'cart_items': cart_items,
        'total_amount': total_amount,
        'email':email,
        'full_name': full_name
    }
    return render(request, 'checkout.html', context)
Checkout View Payment Gateway Integration with Django

The view checkout gets the user’s cart filters the cart items and computes the total amount of the cart items. Also, it retrieves the user’s email and name and then sends all the data as context to the template checkout.html.

Then create a template ‘checkout.html’ in the templates folder of your Django app and add the following code.

{% extends "home.html" %}
{% load static %}
{% block content %}
<link rel="stylesheet" href="{% static 'css/checkout.css' %}">
<div class="container">
    <div id="order-placed-section" style="display: none;">
        <h2>Order Placed</h2>
        <p id="order-success-message"></p>
    </div>
    <h1>Checkout</h1>
    <p>Order Summary :</p>
    <ul>
        {% for item in cart_items %}
        <li>{{ item.product.name }} x {{ item.quantity }}</li>
        {% endfor %}
    </ul>
    <p>Total Amount: &#8377<span id="total_item_amount">{{total_amount}}</span></p>

    <button id="rzp-button1">Pay with Razorpay</button>
</div>
<div id="checkout-data" data-email="{{ email }}" data-fullname="{{ full_name }}">
</div>
<script>
    var handlePaymentUrl = "{% url 'handle-payment' %}";
</script>
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
<script src="{% static 'js/checkout.js' %}"></script>
{% endblock %}
Checkout Template Payment Gateway Integration with Django

Create a checkout.js file in your js folder of the static directory to create and handle the order and payment and add the following code.

document.addEventListener('DOMContentLoaded', function () {
    const checkoutButton = document.getElementById('rzp-button1');
    const checkoutData = document.getElementById("checkout-data");
    const email = checkoutData.getAttribute("data-email");
    const fullName = checkoutData.getAttribute("data-fullname");
    const totalItemAmount = document.getElementById("total_item_amount");

    function getCookie(name) {
        let value = "; " + document.cookie;
        let parts = value.split("; " + name + "=");
        if (parts.length === 2) return parts.pop().split(";").shift();
    }

    checkoutButton.addEventListener('click', function (event) {
        event.preventDefault();

        const rawPrice = totalItemAmount.textContent;
        const totalPrice = parseFloat(rawPrice.replace('₹', '').trim());

        fetch("http://127.0.0.1:8000/create-order/", {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCookie('csrftoken')
            }
        })
            .then(response => {
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                return response.json();
            })
            .then(data => {
                var options = {
                    "key": "rzp_test_GPMkEmhjhK95lt",
                    "amount": totalPrice * 100,
                    "currency": "INR",
                    "name": "Ecommerce",
                    "description": "Order Payment",
                    "order_id": data['order_id'],
                    "prefill": {
                        "name": fullName,
                        "email": email
                    },
                    "handler": function (response) {
                        const razorpay_order_id = response.razorpay_order_id;
                        const payment_id = response.razorpay_payment_id;
                        console.log(razorpay_order_id, payment_id)

                        fetch("http://127.0.0.1:8000/handle-payment/", {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                                'X-CSRFToken': getCookie('csrftoken')
                            },
                            body: JSON.stringify({
                                'order_id': razorpay_order_id,
                                'payment_id': payment_id
                            })
                        })
                            .then(response => {
                                if (!response.ok) {
                                    throw new Error('Network response was not ok during payment handling');
                                }
                                return response.json();
                            })
                            .then(data => {
                                if (data.message === 'Payment successful') {
                                    const orderSection = document.getElementById('order-placed-section');
                                    const orderMessage = document.getElementById('order-success-message');

                                    orderMessage.textContent = "Successfully placed order!";
                                    orderSection.style.display = "block";
                                } else {
                                    alert('Payment failed');
                                }
                            })
                            .catch(error => {
                                console.error('An error occurred while processing the payment.', error);
                                alert('There was an issue processing your payment. Please try again.');
                            });
                    }
                };

                var rzp1 = new Razorpay(options);
                rzp1.open();
            })
            .catch(error => {
                console.error('Error creating order:', error);
                alert('There was an issue initiating your order. Please try again.');
            });
    });
});
Checkout JS Payment Gateway Integration with Django

In the above code, at line number 1, set up the event listener to listen to the event ‘DOMContentLoaded’ using document.addEventListener(‘DOMContentLoaded’, function () means the code within this script will run after the HTML document gets loaded completely.

  • Then from lines number 2 to 6, access the elements and data of the template checkout.html. After this function getcookie() is defined, this function gets the value of the cookie given its name and this is helpful for getting the CSRF token value.
  • The CSRF token value is required whenever a POST request is made to Django backends. Again listening for the event that occurs whenever the checkout Button is clicked using checkoutButton.addEventListener(‘click’, function (event).
READ:  How to parse JSON in Python Django

When the checkout button is clicked, the POST request is sent to the server endpoint ‘http://127.0.0.1:8000/create-order/’ to initiate the order using the fetch() method in JavaScript. Then server sends the response as a response object and this response is converted into JSON data using the response.json().

Payment Gateway Integration with Django Payment Options and Handler

The response contains the data order_id which is necessary for setting up the Razorpay payment options and initiating the payment process, look at line number 34 to 44.

  • The handler function within the payment options is a callback that is activated after the payment process. It sends the payment details back to the server at the endpoint “http://127.0.0.1:8000/handle-payment/” to finalize the payment.
  • Based on the server’s response after handling the payment, it either displays a success message or an error alert.

Then create a view called ‘handle_payment’ in your views.py file of your Django app by adding the following code.

@csrf_exempt
def handle_payment(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        razorpay_order_id = data.get('order_id')
        payment_id = data.get('payment_id')

        try:
            order = Order.objects.get(payment_id=razorpay_order_id)

            client = razorpay.Client(auth=(settings.RAZOR_KEY_ID, settings.RAZOR_KEY_SECRET))
            payment = client.payment.fetch(payment_id)

            if payment['status'] == 'captured':
                order.payment_status = True
                order.save()
                user = request.user
                user.cart.cartitem_set.all().delete()
                return JsonResponse({'message': 'Payment successful'})
            else:
                return JsonResponse({'message': 'Payment failed'})

        except Order.DoesNotExist:
            return JsonResponse({'message': 'Invalid Order ID'})
        except Exception as e:

            print(str(e))
            return JsonResponse({'message': 'Server error, please try again later.'})
Payment Gateway Integration with Django Handle Payment View

The above view handle_payment handles the POST request which is sent by the handler function. It first decodes the JSON data from the request body using the json.loads(request.body) and stores it in the variable data.

  • Then the order ID and payment ID are extracted from the data using the data.get() method.
  • The function retrieves the order from the database using the provided Razorpay order ID using Order.objects.get(payment_id=razorpay_order_id). A new instance of the Razorpay client is created by providing the API credentials to the method razorpay.Client().
  • After this, get the payment details from Razorpay using client.payment.fetch(payment_id).
  • from line number 274 to 281, If the payment status from Razorpay is ‘captured’, this means that the payment was successful. The order’s payment_status is set to True and saved to the database.
  • All items in the user’s cart are deleted, implying that the order is complete and the cart has been emptied. A JSON response with a success message is returned.
READ:  Python filter not in Django

Now define the URLs for the views in the urls.py file of your Django app by adding the following code.

path('fetch-cart-count/', fetch_cart_count, name='fetch-cart-count'),
    path('create-order/', create_order, name='create-order'),
    path('handle-payment/', handle_payment, name='handle-payment'),
    path('checkout/', checkout, name='checkout'),
URL Payment Gateway Integration with Django

Finally, add the styling to the template checkout.html by creating a new file called checkout.css in the folder css of the static directory and adding the following code.

body {
    font-family: 'Roboto', sans-serif;
    background-color: #f8f9fa;
    margin: 0;
    padding: 0;
    line-height: 1.6;
}

.container {
    max-width: 700px;
    margin: 50px auto;
    padding: 40px;
    background-color: #ffffff;
    border-radius: 12px;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}

h1 {
    text-align: center;
    margin-bottom: 30px;
    color: #2c3e50;
    font-weight: 600;
}

p {
    color: #7f8c8d;
    font-size: 1.1em;
}

ul {
    list-style-type: none;
    padding: 0;
}

li {
    padding: 15px 0;
    border-bottom: 2px solid #ecf0f1;
    display: flex;
    justify-content: space-between;
    font-size: 1.1em;
}

li:last-child {
    border-bottom: none;
}

button#rzp-button1 {
    background-color: #e74c3c;
    color: white;
    border: none;
    padding: 15px 25px;
    font-size: 18px;
    border-radius: 8px;
    cursor: pointer;
    display: block;
    margin: 30px auto;
    transition: background-color 0.3s ease, transform 0.2s ease;
}

button#rzp-button1:hover {
    background-color: #c0392b;
    transform: scale(1.05);
}

button#rzp-button1:active {
    transform: scale(1);
}

#total_item_amount {
    color: #e74c3c;
    font-weight: 600;
}
#order-placed-section {
    border: 2px solid green;
    padding: 10px;
    background-color: #e8f5e9;
    margin-top: 20px;
}

#order-success-message {
    color: green;
}
Styling for Checkout Payment Gateway Integration with Django

Run the below commands one by one.

python manage.py makemigrations
python manage.py migrate

Then run the Django server using the below command.

python manage.py runserver

After running the server open the URL ‘http://127.0.0.1:8000/login/’ and click on the button Login. you see the page like this.

Payment Gateway Integration with Django Login Page

If you don’t see your page like the above because I have done some modifications to styling, to get the output like me, download this static file and copy everything to the static folder of your Django project.

After login, go to the product page and add the product to your cart as shown in the below picture.

Adding Items to Cart Payment Gateway Integration with Django

Then on the cart page click on the button Checkout.

Payment Gateway Integration with Django Checkout

After you see the checkout page, then proceed by clicking on the button Pay with Razorpay.

Paying with Razorpay Payment Gateway Integration with Django

When you click on the button Pay with Razorpay, the Razorpay interface appears where you can choose any payment method for example, I am choosing the UPI method.

Payment Gateway Integration with Django Razorpay Interface

After choosing the UPI, enter the UPI ID.

Payment Gateway Integration with Django Razorpay Interface UPI

When you click on Pay Now, the amount is deducted from your account but it is test mode, so the amount will not be deducted from your bank account, and after paying the amount you see a page like this.

Payment Gateway Integration with Django Order Placed

The above output shows that you have successfully placed the order. Now you have successfully integrated the Razorypay Payment Gateway in your Django project.

Conclusion

In this Python tutorial, you learned how to implement the functionality of the Razorpay payment gateway. Also, you learned about how to create an order in Razorpay for the items in the cart and handle the payment process using the handler function.

You may like to read: