Have you ever wondered βhow do I receive online payments on a new cool app that I have built using my favorite framework, Djangoβ? β‘
I did too and after a bit of digging about what are the available payment gateways that work in India I came up with the list of 5β6 payment gateways.
Out of those, Razorpay stood out to me with the range of financial products and solutions it provides. Also, their APIs are really well built. So, I chose to use Razorpay as my payment gateway.
So fellow Pythonistas, in this article I will give the basic outline of how to integrate a Razorpay payment gateway into your website.
Thinking about how the Razorpay payment gateway worksπ€ Letβs see
source: razorpay.com
Steps to integrate Razorpay payment gateway π
STEP 1 - Setting up Razorpay account
To use Razorpay you will have to first configure it. Following are the steps:
- Go to razorpay.com β Sign up β Go to the settings.
source: razorpay.com
- Now click on the API Keys tab.
source: razorpay.com
- Now, Click on Generate API keys button.
source: razorpay.com
- This will generate a new key and show you the
Key Id
andKey Secret
in a popup. Below these fields, you will see a link to Download Key Detail, click on that and let the download complete.
source: razorpay.com
STEP 2 - Store the downloaded API keys in your settings.py file
RAZORPAY_KEY_ID = YOUR_KEY_ID | |
RAZORPAY_KEY_SECRET = YOUR_KEY_SECRET |
Note: Do not store these secrets in the setting file of your production projects rather read it through Env variables or some secret-vault like AWS Secret Manager or Hashicorp Vault.
STEP 3 - Install Razorpay library
pip install razorpay
STEP 4 - Create the Order Schema
from django.db import models | |
from django.db.models.fields import CharField | |
from django.utils.translation import gettext_lazy as _ | |
from .constants import PaymentStatus | |
class Order(models.Model): | |
name = CharField(_("Customer Name"), max_length=254, blank=False, null=False) | |
amount = models.FloatField(_("Amount"), null=False, blank=False) | |
status = CharField( | |
_("Payment Status"), | |
default=PaymentStatus.PENDING, | |
max_length=254, | |
blank=False, | |
null=False, | |
) | |
provider_order_id = models.CharField( | |
_("Order ID"), max_length=40, null=False, blank=False | |
) | |
payment_id = models.CharField( | |
_("Payment ID"), max_length=36, null=False, blank=False | |
) | |
signature_id = models.CharField( | |
_("Signature ID"), max_length=128, null=False, blank=False | |
) | |
def __str__(self): | |
return f"{self.id}-{self.name}-{self.status}" |
- We are importing
PaymentStatus
from constants.py file.
class PaymentStatus: | |
SUCCESS = "Success" | |
FAILURE = "Failure" | |
PENDING = "Pending" |
STEP 5 - Creating Razorpay order
- First, letβs create the template to take input from the user.
{% extends 'base.html' %} | |
{% block content %} | |
<div class="col-6 mx-auto" style="margin-top:10%;"> | |
<form action="{% url 'payment' %}" method='POST'> | |
{% csrf_token %} | |
<div class="mb-3"> | |
<label for="exampleInputEmail1" class="form-label">Name</label> | |
<input type="name" name="name" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"> | |
</div> | |
<div class="mb-3"> | |
<label for="exampleInputPassword1" class="form-label">Amount</label> | |
<input type="number" name="amount" class="form-control" id="exampleInputPassword1"> | |
</div> | |
<div> | |
<button type="submit" class="btn btn-primary">Pay Now</button> | |
</div> | |
</form> | |
</div> | |
{% endblock %} |
- Now we will create an order_payment view to create order at the backend.
- And after that will create a Razorpay order using the API keys.
π‘ For every payment, a Razorpay order needs to be created. It is compulsory to create orders to capture payments automatically.
- We are creating Razorpay order using the Orders API. It is a server-to-server call that is secured by basic auth using your API keys.
def home(request): | |
return render(request, "index.html") | |
def order_payment(request): | |
if request.method == "POST": | |
name = request.POST.get("name") | |
amount = request.POST.get("amount") | |
client = razorpay.Client(auth=(settings.RAZORPAY_KEY_ID, settings.RAZORPAY_KEY_SECRET)) | |
razorpay_order = client.order.create( | |
{"amount": int(amount) * 100, "currency": "INR", "payment_capture": "1"} | |
) | |
order = Order.objects.create( | |
name=name, amount=amount, provider_order_id=payment_order["id"] | |
) | |
order.save() | |
return render( | |
request, | |
"payment.html", | |
{ | |
"callback_url": "http://" + "127.0.0.1:8000" + "/razorpay/callback/", | |
"razorpay_key": RAZORPAY_KEY_ID, | |
"order": order, | |
}, | |
) | |
return render(request, "payment.html") |
π‘ Amount in Razorpay works in subunits of currency i.e Rs 700 would become 70000 paise. Thatβs why we multiplied the amount by 100.
- π§Letβs see the response object of Orders API that we are storing in razorpay_order
{
"id": "order_IX37nLMvHfLsSO",
"entity": "order",
"amount": 789600,
"amount_paid": 0,
"amount_due": 789600,
"currency": "INR",
"receipt": null,
"offer_id": null,
"status": "created",
"attempts": 0,
"notes": [],
"created_at": 1639418188
}
Wondering why we passed callback_urlπ€
After payment, users will be redirected to this URL on successful payment and failed payment.
- Letβs create a template to load Razorpayβs javascript code that will render the payment window.
{% extends 'base.html' %} | |
{% block content %} | |
<form method="POST"> | |
<script src="https://checkout.razorpay.com/v1/checkout.js"></script> | |
<script> | |
var options = { | |
key: "{{razorpay_key}}", | |
amount: "{{order.amount}}", | |
currency: "INR", | |
name: "{{order.name}}", | |
description: "Test Transaction", | |
image: "https://example.com/your_logo", | |
order_id: "{{order.provider_order_id}}", | |
callback_url: "{{callback_url}}", | |
redirect: true, | |
prefill: { | |
"name": "Gaurav Kumar", | |
"email": "[email protected]", | |
"contact": "9999999999" | |
}, | |
notes: { | |
"address": "Razorpay Corporate Office" | |
}, | |
theme: { | |
"color": "#3399cc" | |
} | |
}; | |
var rzp1 = new Razorpay(options); | |
rzp1.open(); | |
</script> | |
<input type="hidden" custom="Hidden Element" name="hidden"> | |
</form> | |
{% endblock %} |
π‘ Significance of redirect parameter
- The redirect parameter will determine whether to POST response to the event handler post-payment completion or redirect to the callback URL.
a. When a redirect is true user is redirected to the specified callback URL in case of payment failure.
b. When a redirect is false user is shown the Checkout popup to retry the payment. - Setup urls.py file
from django.urls import path | |
from django.contrib import admin | |
from . import views | |
urlpatterns = [ | |
path("admin/", admin.site.urls), | |
path("", views.home, name="home"), | |
path("payment/", views.order_payment, name="payment"), | |
path("callback/", views.callback, name="callback"), | |
] |
STEP 6 - Making Payment
- As the Razorpay javascript loads, the user can make payments with test credentials.
- Handling the successful and failed payment is done with the help of the callback URL.
STEP 7 - Handling Successful and Failed payment.
- Letβs create a callback view to handle successful and failed payments.
@csrf_exempt | |
def callback(request): | |
def verify_signature(response_data): | |
client = razorpay.Client(auth=(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET)) | |
return client.utility.verify_payment_signature(response_data) | |
if "razorpay_signature" in request.POST: | |
payment_id = request.POST.get("razorpay_payment_id", "") | |
provider_order_id = request.POST.get("razorpay_order_id", "") | |
signature_id = request.POST.get("razorpay_signature", "") | |
order = Order.objects.get(provider_order_id=provider_order_id) | |
order.payment_id = payment_id | |
order.signature_id = signature_id | |
order.save() | |
if not verify_signature(request.POST): | |
order.status = PaymentStatus.SUCCESS | |
order.save() | |
return render(request, "callback.html", context={"status": order.status}) | |
else: | |
order.status = PaymentStatus.FAILURE | |
order.save() | |
return render(request, "callback.html", context={"status": order.status}) | |
else: | |
payment_id = json.loads(request.POST.get("error[metadata]")).get("payment_id") | |
provider_order_id = json.loads(request.POST.get("error[metadata]")).get( | |
"order_id" | |
) | |
order = Order.objects.get(provider_order_id=provider_order_id) | |
order.payment_id = payment_id | |
order.status = PaymentStatus.FAILURE | |
order.save() | |
return render(request, "callback.html", context={"status": order.status}) |
π‘ As POST request will be made by Razorpay and it wonβt have the csrf token, so we need to
csrf_exempt
this url.
- Successful Payment π
1] In the case of successful payment, Razorpay will make a POST call to the callback URL that we have provided, with therazorpay_payment_id
,razorpay_order_id
, andrazorpay_signature
in the response object.
{
"razorpay_payment_id": [
"pay_IXFfPM0ZmuZCZO"
],
"razorpay_order_id": [
"order_IXFfAUmWgMbCIS"
],
"razorpay_signature": [
"2b5ea94bed0391ab682df3799667d784a8b99a05cb76f6024669d32775207c99"
]
}
- 2] For successful payment, the Payment signature verification step is mandatory, it allows to confirm the authenticity of the details returned to the checkout for successful payments. If the Payment signature verification is successful, the method will return None.
- Failed Payment π₯
1] In the case of failed payment, the Checkout Form is displayed again to retry the payment when a callback URL is not provided at the backend.
2] But when a callback URL is provided, Razorpay will make a POST call to the callback URL that we have provided, with theerror[code], error[description], error[source], error[metadata], error[reason], error[step] in the response object.
{
"error[code]": [
"BAD_REQUEST_ERROR"
],
"error[description]": [
"Payment failed"
],
"error[source]": [
"gateway"
],
"error[step]": [
"payment_authorization"
],
"error[reason]": [
"payment_failed"
],
"error[metadata]": [
{\"payment_id\":\"pay_IXFh8UJEl2qWhf\",\"order_id\":\"order_IXFgz9yst1G3uw\"}"
]
}
β We can check all these payments on Razorpay dashboard
Thatβs all folks π. We have successfully implemented the Razorpay payment gateway on our website. These were the minimal configurations to get work Razorpay payment gateway in our Django project.
At Scalereal We believe in Sharing and Open Source.
So, If you found this helpful please give some claps π and share it with everyone.
Sharing is Caring!
Thank You :) π
If you need any help, you can contact me on LinkedIn, Github, Twitter.