5

I am working on a django webapp. I connected the paytm payment gateway with the django app. I did everything according to the docs, and everything works. almost.

I am having a problem when calling the callback URL once the payment is over.

Here is the code

views.py

def donate(request):
    if request.method == "POST":
        form = DonateForm(request.POST)

        name = request.POST.get('firstName')
        phone = request.POST.get('phone')
        email = request.POST.get('email')
        amount = float("{0:.2f}".format(int(request.POST.get('amount'))))
        ord_id = OrdID()
        cust_id = CustID()

        paytm_params = {
            "MID" : MERCHANTID,
            "WEBSITE" : "WEBSTAGING",
            "INDUSTRY_TYPE_ID" : "Retail",
            "CHANNEL_ID" : "WEB",
            "ORDER_ID" : ord_id,
            "CUST_ID" : cust_id,
            "MOBILE_NO" : phone,
            "EMAIL" : email,
            "TXN_AMOUNT" : str(amount),
            "CALLBACK_URL" : "http://127.0.0.1:8000/payment/status",

            }

        paytm_params['CHECKSUMHASH'] = Checksum.generate_checksum(paytm_params, MERCHANTKEY)

        return render(request, 'paytm.html', {'paytm_params': paytm_params})

    else:
        form = DonateForm()
        context = {'Donate': form}
        return render(request, 'donate.html', context=context)

@csrf_exempt
def handlerequest(request):
    if request.method == 'POST':
        form = request.POST
        response_dict = {}

        for i in form.keys():
            response_dict[i] = form[i]

            if i == 'CHECKSUMHASH':
                checksum = form[i]
                print(checksum)

        verify = Checksum.verify_checksum(response_dict, MERCHANTKEY, checksum)

        if verify:
            if response_dict['RESPCODE'] == '01':
                print('order successful')
            else:
                print('error: ' + response_dict['RESPMSG'])

        return render(request, 'paymentstatus.html', {'response': response_dict})

urls.py

path('donate', views.donate, name='donate'),
path('payment/status', views.handlerequest, name='handlerequest'),

donate.html

<form class="test_paytm" action="{% url 'donate' %}" method="post">
    {% csrf_token %}
    <div class="row">
        <div class="col">
            {{ Donate.firstName|as_crispy_field }}
        </div>
        <div class="col">
            {{ Donate.lastName|as_crispy_field }}
        </div>
    </div>
    <div class="row">
        <div class="col">
            {{ Donate.email|as_crispy_field }}
        </div>
        <div class="col">
            {{ Donate.phone|as_crispy_field }}
        </div>
    </div>
    <div class="row">
        <div class="col">
            {{ Donate.amount|as_crispy_field }}
        </div>
    </div>
    <button type="submit" name="button" class="btn btn-lg mb-5 contact_submit">Donate</button>
</form>

paytm.html

<html>

<head>
  <title>Merchant Check Out Page</title>
</head>

<body>
  <center>
    <h1>Please do not refresh this page...</h1>
  </center>
  <form method="post" action="https://securegw.paytm.in/order/process" name="paytm">
    {% for key, value in paytm_params.items %}
    <input type="hidden" name="{{key}}" value="{{value}}">
    {% endfor %}
  </form>
</body>
<script type="text/javascript">
  document.paytm.submit()

</script>

</html>

paymentstatus.html

<div class="container">
  {% if response_dict.RESPCODE == 01 %}
  <center>
    <h2>Thank you for your donation</h2>
    <p>
      We are thrilled to have your support. Through your donation we will be able to accomplish our goal. You truly make the difference for us, and we are
      extremely grateful!
    </p>
  </center>

  <h3>Order ID: {{response_dict.ORDERID}}</h3>
  <h3>Order Date: {{response_dict.TXNDATE}}</h3>
  <h3>Amount: {{response_dict.TXNAMOUNT}}</h3>
  <h3>Payment Mode: {{response_dict.PAYMENTMODE}}</h3>

  {% else %}
  <center>
    <p>
      There seems to be a problem. We will try to fix this from our end.
    </p>
  </center>
  {% endif %}
</div>

But once the payment is over, The website is not calling handlerequest from views.py correctly. That is why I had added the @csrf_exempt so that an outside page can call the url without any issues. But I am still getting the 403 error. I am not sure what I am doing wrong

EDIT1

I have added the paytm.html code to the question. I personally dont feel that the problem is with this page, as all that the page does is redirect to the payment gateway page of paytm. The problem I am facing is when returning back to my url ie. paymentstatus.html. That is through the handlerequest view. The donation process is as follows

  1. user fills out form in donate.html and clicks the donate button.
  2. paytm.html gets the information and automatically routes to paytm payment gateway
  3. User makes the donation.
  4. The URL routes back from the paytm payment gateway back to my URL.
  5. The paymentstatus.html page is displayed.

As the paymentstatus.html page is being called from an external url, csrf_exempt is required, which I have provided. But for some reason that does not work

[EDIT 2]

When I spoke to the technical team at Paytm they told me that I had to accept the callback URL in POST. The person I spoke to, had little experience in django and could not help me further. I am not really sure what accepting the response in POST means. Can someone help me out?

[EDIT 3]

Edited the handlerequest view

Sashaank
  • 880
  • 2
  • 20
  • 54
  • I'm wondering if you running the app on your localhost or an actual web server over HTTP? – zkan Mar 19 '20 at 04:05
  • Tried both ways. Does not work on both – Sashaank Mar 19 '20 at 06:16
  • This may be CORS headers issue. Check browser developer console - what requests are made when submitting payment on paytm payment page - is `OPTIONS` request sent to your callback url? – Oleg Russkin Mar 19 '20 at 10:48
  • From the payment gateway, a POST request is sent to the URL: `127.0.0.1:8000/handlerequest`. And the status code for the request is `403`. I am not seeing any `OPTIONS` request in the browser console – Sashaank Mar 21 '20 at 07:10
  • what happens when you run `Checksum.verify_checksum`. I failed to find a python package for that – mahyard Jan 10 '21 at 16:53
  • Paytm suggested a sample checksumhash validation code here: https://developer.paytm.com/docs/checksum/ – mahyard Jan 10 '21 at 16:55
  • A localhost callback URL isn't going to work. – aris Jan 13 '21 at 02:32

6 Answers6

2

But once the payment is over, The website is not calling handlerequest from views.py correctly. That is why I had added the @csrf_exempt so that an outside page can call the url without any issues. But I am still getting the 403 error. I am not sure what I am doing wrong

Is this 403 error coming from your server ? Or you are just seeing 403 in the browser... ?

I don't believe Paytm can access your server when your callback url is http://127.0.0.1:8000. In order for Paytm to access your server you need to provide your public IP address in the callback url and then configure your router to open port 8000 and then forward all requests from port 8000 to your machine. But since you didn't mention you have done that, my guess is you haven't done it.

Todor
  • 15,307
  • 5
  • 55
  • 62
2

Setting callback url:

it is very simple matter, all you have to do is to add a new url in your Django app then register it with the API you are calling, I am not familiar at all with PAYTM however definitely you will find a place to register it through your dashboard or if they have CLI interface.

get to code example:

#urls.py
path('payment/status/', views.check_status, name='payment_status') # the same full url to register in callback url in their website

#views.py
@csrf_exempt # API doesn't know how to send you csrf token
def check_status(request):
    if request.method == 'POST':
        print(request.POST)# examine the data returned from the API

by the way if you are testing locally then you need to expose your website to be reachable to the outer world, check ngrok https://ngrok.com/

Handling online payments requires to be handled with SSL, HTTPS.

you can force redirection after submission like:

place the following stub inside the payment form

<input type="hidden" name="next" value="{% url 'payment_status' %}" />

And then from you submission view

# force the redirect to
return redirect(request.POST.get('next') or 'where_ever_you_send_your_user')

Ahmed Shehab
  • 1,657
  • 15
  • 24
  • Hi! I tried test hosting it in `pythonanywhere`. I tried adding `if request.method == 'POST':` to the `handlerequest` view. I still end up with the same error. I have also edited the question. Can you take a look? – Sashaank Jan 07 '21 at 03:23
  • and Paytm does not seem to have a field in their portal to register the callback URL. One has to define the URL in the `views.py` file. This is what I have done. – Sashaank Jan 07 '21 at 03:59
  • get in touch again with their support ask about the payment return url, might be unified. you shouldn't redirect to callback view manually, it should be done after you post the payment and want to collect information about payment status, and it should be optional though. examine the network tab while submitting the payment you might find some useful information. and you need ssl anyways. – Ahmed Shehab Jan 07 '21 at 06:57
  • When you say `ssl` do you mean the website has to be an `HTTPS` and not an `HTTP` website. Sorry for this question. I am kind of a rookie – Sashaank Jan 07 '21 at 13:43
  • yes, ssl I mean HTTPS connection, and you may achieve that on local development with ngrok service, it is free just download it and type `ngrok http -bind-tls=true http://127.0.0.1:8000` and don't forget to add the generated url to ALLOWED_HOSTS – Ahmed Shehab Jan 07 '21 at 14:31
  • Hi sorry for the late reply. Turns out you were right. paytm does not support `http` websites. I tried using ngrok and the callback url was called successfully. Not sure why the technical team was not able to clear this though. Thanks a lot – Sashaank Jan 14 '21 at 20:17
  • Pleasure , happy to help ;) – Ahmed Shehab Jan 14 '21 at 20:46
  • Hi! one more small issue. I have now hosted the website in pythonanywhere as a `https` website. Check this link https://stackoverflow.com/questions/65730560/privacy-error-using-pythonanywhere-ssl-certificate . But I get a `Privacy error` in the callback url. Can you help me out with that??? – Sashaank Jan 15 '21 at 06:00
  • I wish you are using custom private domain not *.pythonanywhere.com, with lets encrypt it is pretty hard to get it wrong. follow this tutorial: [https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04] – Ahmed Shehab Jan 15 '21 at 07:57
0

I think you should be redirecting instead of rendering that request in the donate view. I don't think your handlerequest view is even being touched. This is what I'm seeing in the donate view:

return render(request, 'paytm.html', {'paytm_params': paytm_params})

In your template for paymentstatus.html, you have:

{% if response_dict.RESPCODE == 01 %}

But you passed a context of paytm_params, not response_dict. UNLESS, you never showed me what paytm.html really is.

Edit:

Thanks for adding paytm.html. There are a couple things you should try to do.

  1. Make sure your browser is not blocking cookies. Some people block cookies and that screws up your browser.
  2. Add the {% csrf_token %} tag at the top of your form. CSRF token is important because it travels everywhere, no matter the page.
  3. Try removing the csrf_exempt from your view.
  4. Change the name of your csrf token in settings.py to something else like csrftokenasdfasdfasdf
  5. There's another thing called corsheader. I believe their package is called django-cors-headers and they have a middle wear that you can use since your doing a bunch of jumping back and forth between views.
Yoomama
  • 133
  • 8
  • I have added the paytm.html to the question. Please take a look. I personally dont feel that the problem is with paytm.html. But you might know better. – Sashaank Mar 19 '20 at 06:04
  • @Sashaank I've added a couple more recommendations since you're not exactly following the video you linked to. – Yoomama Mar 19 '20 at 14:46
  • Hi. I have tried all of the above. nothing seems to work. I did exactly what the guy in the video did as far as the payment gateway is concerned. But That does not seem to work. What do I do now? – Sashaank Mar 21 '20 at 07:01
  • Hi. I tried using `django-cors-headers`. I have edited the question. Can you please take a look. – Sashaank Mar 25 '20 at 12:00
0

Let's take a look at two lines of code

if request.method == 'POST':

and

"CALLBACK_URL" : "http://127.0.0.1:8000/payment/status",

What does this indicate?

  1. A callback URL is sent to Paytm backend servers.
  2. Setup a request handler to accept POST requests on your server.
  3. Most importantly, your server address that is sent to Paytm servers is 127.0.0.1
  4. When a payment gateway redirects in the browsers it's always a GET request. (All the post requests in the browsers are Ajax requests)

With the above facts, let's see what's happening with your code.

There are two cases that might be happening

Case 1: Paytm is redirecting to your callback your on the browser, but it would be a GET request then, and your code only works for POST request. If this is true, change the method to GET.

Case 2: Paytm servers are making a POST API call to your servers, however, you have passed 127.0.0.1 as the server IP, and PayTm servers won't be able to connect to the server that is running on your local machine with loopback IP, hence you don't see any POST request on your server. If this is true, try to host your application on some cloud and use the public IP of that machine, or use ngrok to create a tunnel to your local machine, and in the callback URL use the ngrok URL.

My personal opinion is your implementation is based on case 1.

sun_jara
  • 1,736
  • 12
  • 20
0

Might be an issue with the callback URL. You can test the view by calling the URL yourself to see if it is actually connecting. Your call back URL should be calling this with the POST method:

http://127.0.0.1:8000/payment/status

If that doesn't work try add ending '/' and make the callback call 'url/ payment/status/'

path('payment/status/', views.handlerequest, name='handlerequest') http://127.0.0.1:8000/payment/status/

Gioan Nguyen
  • 37
  • 10
0

The POST method can be like this:

urls.py

urlpatterns = [
   url(r'^callback$', views.CallbackView.as_view())
]

views.py

class CallbackViews(APIView):
    @csrf_exempt
    def post(self, request):
        # do something
Ben
  • 1
  • 2