Skip to content

Instantly share code, notes, and snippets.

@FerociousCentaur
Last active June 24, 2024 09:26
Show Gist options
  • Save FerociousCentaur/8f1b7a4de9f0122e5766766554a9aeb1 to your computer and use it in GitHub Desktop.
Save FerociousCentaur/8f1b7a4de9f0122e5766766554a9aeb1 to your computer and use it in GitHub Desktop.
Billdesk integration in django

Billdesk Payment gateway integration in django

From Scratch

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.

Editing Models

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)

  1. owner = This is foreignkey field which will signify the (already resistered on your site) person who made this transaction.
  2. 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.
  3. 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.
  4. email = Conatins email of the user
  5. amount_initiated = Contains amount for which transaction request has been made.
  6. was_success = A boolean field which will say whether the transaction was successful or not.
  7. 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.
  8. log = Conatains log of the transaction no matter the transaction was successful or not.
  9. registered_for = Contains data about the items for which the payment was made.For ex- Your checkout list names at a online shopping site.
  10. txn_date = Conatins date and time when the request of payment was made.
  11. ru_date = Conatins date and time when the response was recieved at the return Url.
  12. 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.

Editing Urls File

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.

Editing Forms

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)

Editing Settings

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

Editing Views


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()

Editing Templates

Taking Transaction Info Template

'paymentspage.html'

<form action="" method="post">
{% csrf_token %}
{{form}}
<input type="submit"  class="btn btn-primary" value="Pay" id="submit">
</form>

Submitting Form From Template

'paymentProcess.html'

<body>
<form action="{{url}}" method="post" name="billdesk">
    <input name="msg" type="hidden" value="{{msg}}">
</form>
</body>
<script>

  document.billdesk.submit();
</script>

Making A Thank You Page

'afterPayment.html'

{% ifequal typ "success" %}
<div class="alert alert-success mt-5" style="text-align: center;">
            <div class="">&#9786;</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 %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment