- From Scratch
- Editing models.py
- Editing urls.py
- Editing forms.py
- Editing settings.py
- Editing views.py
- Editing templates
We will be using a premade python package to ease our process which can be installed using
pip install django-billdesk
I won't talk much talk about the package. You can read it's documnetation at here.This package basically gives us the message to be sent and unpacks the response recieved from billdesk for the message sent to it.I will straight away dive into the integration part. If you dont trust the package ,you can visit to its github repo, look at its code, take inspiration and write your own code.
We will add a table called 'Transaction' in out models.py to hold data about each transaction done
class Transaction(models.Model):
owner = models.ForeignKey(User, on_delete=models.DO_NOTHING, blank=False,null=True)
order_id = models.CharField(max_length=30, blank=False, null=True)
txn_id = models.CharField(max_length=50, blank=True, null=True)
email = models.EmailField(max_length=30, blank=True, null=True)
amount_initiated = models.FloatField(blank=True, null=True)
was_success = models.BooleanField(default=False)
status = models.CharField(max_length=30, blank=True, null=True)
log = models.TextField(null=True, blank=True)
registered_for = models.TextField(null=True, blank=True)
txn_date = models.DateTimeField(default=timezone.now, blank=True)
ru_date = models.DateTimeField(blank=True, null=True)
s2s_date = models.DateTimeField(blank=True, null=True)
def __str__(self):
return f'{self.owner.id} [{self.order_id}]'
Explanation (You can skip this portion if you understood the above written code)
- owner = This is foreignkey field which will signify the (already resistered on your site) person who made this transaction.
- order_id = This field will contain the unique id of the transaction request made. Make sure this is completely unique. I will show how to ensure this and populate this field.
- txn_id = This field will contain the unique taxation number recieved in response from billdesk
order_id is created by us whereas txn_id is created by billdesk or respective bank. - email = Conatins email of the user
- amount_initiated = Contains amount for which transaction request has been made.
- was_success = A boolean field which will say whether the transaction was successful or not.
- status = A field that contains the current status of transaction.
was_success and status field might seem similar but have a difference which we will see as we move forward. - log = Conatains log of the transaction no matter the transaction was successful or not.
- registered_for = Contains data about the items for which the payment was made.For ex- Your checkout list names at a online shopping site.
- txn_date = Conatins date and time when the request of payment was made.
- ru_date = Conatins date and time when the response was recieved at the return Url.
- s2s_date = Conatins date and time when the response was recieved at Server to Server URL.
Return URL is the url where you want the user to be redirected in your domain after the payment procedure at billdesk is over.Billdesk also sends a response here at this url.
S2S is also a url in your urls.py but it doesn't return or render any page.Rather it is put in place to ensure that our server recieves the payment successful or failure info irrespective of the clients net connectivity.(To use S2S you need to provide it to billdesk,so that they can configure it in their system). The response at return url and s2s are same and are recieved at the same time,its just that we use the response at return url to show the transaction info to user whereas use the response at s2s to update our database.
Since, to use S2S you first need to give it to billdesk for configuration,we will using response at Return url to see the results and will see how a S2S function and url looks like, and how to use it IF we get the S2S configured.
from django.contrib import admin
from django.urls import path
from <ur app> import views
urlpatterns = [
path('admin/', admin.site.urls),
path('payments/', views.payment_request, name='paymentpage'),
path('handleResp/', views.handleResponse, name='RU'), #this will your return url where the user will be redirected after payment
path('s2sresp/', views.server_to_server, name='s2s'),
]
Now this urls.py file might have given you a rough idea about the name of functions that we are going to have in our views.py file.
We will have a form where a user can enter his email,and things that he wants to purchase.
class checkoutForm(forms.Form):
email = forms.EmailField(label='', required=True, widget=forms.TextInput(attrs={'placeholder': 'email'))
OPTIONS = (
("Watch", "Watch"),
("Dress", "Dress"),
)
choice = forms.MultipleChoiceField(widget=forms.SelectMultiple(attrs={'class':"selectpicker form-control",'data-selected-text-format':"count", 'OnChange':'myFunction();'}),
choices=OPTIONS, required=True)
We will define some of our variables here and then import it in our code for security reasons. And by editing,I meant appending to the existing settings.py file.
MID = '<merchant-id>'
SEC_ID = '<sercet-id>'
BILL_URL = 'https://uat.billdesk.com/pgidsk/PGIMerchantPayment'
CONF_BILL_URL = 'https://uat.billdesk.com/pgidsk/PGIQueryController'
CHECKSUM_KEY = '<checksum-key>'
REVERSE_URL = '<ur-domain>/handleResp/'
amt_watch = 400
amt_dress = 1200
from .forms import checkoutForm
from .models import Transaction,User
from django_billdesk import ResponseMessage, GetMessage
import uuid
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
#import other things as required like render,request
#function to get unique order_id
def get_order_id(id):
return id+str(uuid.uuid4())[:8]
#function to generate message and send to billdesk
def payment_request():
if request.method == 'POST':
form = checkoutForm(request.POST)
if form.is_valid():
amount = 0
email_id = form.cleaned_data['email_id']
usr_details = User.objects.filter(email=email_id)
if usr_details:
usr_details = usr_details[0]
fname = usr_details.first_name
id = usr_details.id
mnumber = usr_details.mob_number #assuming u have a another table called User with mob_number as a field
while True:
oid = get_order_id(id)
#we use a combo of user's id and uuid to generate a unique order_id
#we use this in a loop and check the genrated the order_i with existing ones in the db
#to make sure its unique
trans = Transaction.objects.filter(order_id=oid)
if not trans:
break
choices = form.cleaned_data['choice']
for i in choices:
if i=='Watch':
amount+=settings.amt_watch
elif i=='Dress':
amount+=settings.amt_dress
msg = GetMessage().message(oid, amount, id, email_id, fname, mnumber)
Transaction.objects.create(owner=usr_details, order_id=oid, email=usr_details.email, amount_initiated=amount, status='PENDING', registered_for=choices, log=str([msg]), txn_date=timezone.localtime(timezone.now()))
return render(request, 'paymentProcess.html', {'msg': msg, 'url': settings.BILL_URL})
else:
#print('not found')
error = "Given Email ID doesn't exist."
return render(request, 'paymentspage.html', {'error': error, 'form': form})
form = checkoutForm()
return render(request, 'paymentspage.html', {'form': form})
#function to display payment info to client
@csrf_exempt
def handleResponse():
if request.method=='POST':
response = request.POST
values = ResponseMessage.respMsg(response)
if not values False and values['MID']==settings.MID:
transac = Transaction.objects.filter(order_id=values['OrderID'])[0]
tstat,amnt,txnid,dnt,mode = values['TStat'],values['AMNT'], values['TaxnNo'],values['DnT'],values['TMode']
if tstat == '0300' and transac.amount_initiated==float(amnt):
id = transac.owner.id
reg_for = eval(transac.registered_for)
usr_details = Spectator.objects.filter(id=id)[0]
typ = 'success'
msgs = ['Success','Payment Succesful', reg_for]
elif tstat == '0300' and transac.amount_initiated!=amnt:
reg_for = eval(transac.registered_for)
#transac.status = 'AMOUNT Tampered'
#transac.was_success = False
msgs = ['Failed', 'Payment declined! Looked liked someone tried tampering your payment',reg_for]
typ='danger'
elif tstat == '0002':
reg_for = eval(transac.registered_for)
msgs = ['Failed', 'Billdesk is waiting for the trasaction status from your bank. Will update you as soon as we have any response',reg_for]
typ = 'info'
elif tstat != '0300':
if tstat == '0399':
detail = 'Invalid Authentication at Bank'
elif tstat == 'NA':
detail = 'Invalid Input in the Request Message'
elif tstat =='0001':
detail = 'error at billdesk'
else:
detail = 'Payment Failed'
#transac.status = "FAILED"
reg_for = eval(transac.registered_for)
msgs = ['Failed', detail, reg_for]
typ = 'danger'
transac.log += str([response])
transac.ru_date = timezone.localtime(timezone.now())
transac.save()
return render(request, 'afterPayment.html', {'error': msgs, 'typ':typ, 'txnid':txnid, 'date':dnt, 'amnt': amnt, 'mode':mode})
else:
return HttpResponse('Bad Request')
else:
msgs = ['Failed','Payment declined! Looked liked someone tried tampering your payment']
return render(request, 'afterPayment.html', {'error': msgs, 'typ': 'danger'})
else:
return HttpResponse('Bad Request')
#function to recieve data and update database
@csrf_exempt
def server_to_server():
if request.method=='POST':
response = request.POST
values = ResponseMessage.respMsg(response)
if not values False and values['MID']==settings.MID:
transac = Transaction.objects.filter(order_id=values['OrderID'])[0]
tstat,amnt,txnid,dnt,mode = values['TStat'],values['AMNT'], values['TaxnNo'],values['DnT'],values['TMode']
transac.txn_id = txnid
if tstat == '0300' and transac.amount_initiated==float(amnt):
transac.status = 'SUCCESS'
id = transac.owner.id
reg_for = eval(transac.registered_for)
usr_details = Spectator.objects.filter(id=id)[0]
transac.was_success = True
elif tstat == '0300' and transac.amount_initiated!=amnt:
transac.status = 'AMOUNT Tampered'
transac.was_success = False
elif tstat != '0300' and tstat == '0002':
transac.status = "WAITING"
elif tstat != '0300' and tstat != '0002':
transac.status = "FAILED"
transac.log += str([response])
transac.s2s_date = timezone.localtime(timezone.now())
transac.save()
'paymentspage.html'
<form action="" method="post">
{% csrf_token %}
{{form}}
<input type="submit" class="btn btn-primary" value="Pay" id="submit">
</form>
'paymentProcess.html'
<body>
<form action="{{url}}" method="post" name="billdesk">
<input name="msg" type="hidden" value="{{msg}}">
</form>
</body>
<script>
document.billdesk.submit();
</script>
'afterPayment.html'
{% ifequal typ "success" %}
<div class="alert alert-success mt-5" style="text-align: center;">
<div class="">☺</div>
{{error.0}}
<ul style="list-style-type: none;">
<li><strong>{{error.1}}</strong></li>
<li><strong>Your transaction mode is {{ mode }}</strong></li>
<li><strong>Your transaction reference number is {{ txnid }}</strong></li>
<li><strong>Your order list contains {% for i in error.2 %} {{i}}, {% endfor %}</strong></li>
<li><strong>Amount: {{amnt}}</strong></li>
<li><strong>Date and time of transaction: {{date}}</strong></li>
</ul>
</div>
{% endifequal %}