How to debug Django PayPal IPN? - django

I'm using this django app to implement PayPal IPN. I'm testing it using PayPal's IPN simulator, but it's telling me
IPN delivery failed. HTTP error code 500: Internal Server Error
So how can I debug this and see what's really going on? I've dug into code:
#require_POST
def ipn(request, item_check_callable=None):
"""
PayPal IPN endpoint (notify_url).
Used by both PayPal Payments Pro and Payments Standard to confirm transactions.
http://tinyurl.com/d9vu9d
PayPal IPN Simulator:
https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session
"""
flag = None
ipn_obj = None
form = PayPalIPNForm(request.POST)
if form.is_valid():
try:
ipn_obj = form.save(commit=False)
except Exception, e:
flag = "Exception while processing. (%s)" % e
else:
flag = "Invalid form. (%s)" % form.errors
if ipn_obj is None:
ipn_obj = PayPalIPN()
ipn_obj.initialize(request)
if flag is not None:
ipn_obj.set_flag(flag)
else:
# Secrets should only be used over SSL.
if request.is_secure() and 'secret' in request.GET:
ipn_obj.verify_secret(form, request.GET['secret'])
else:
ipn_obj.verify(item_check_callable)
ipn_obj.save()
return HttpResponse("OKAY")
All looks fine and dandy there, but since it's not sending a response to my browser, it's kinda tricky to debug. What should I do? I'm trying to look at my apache logs, but it really isn't telling me much.
216.113.191.33 - - [06/Mar/2010:14:10:30 -0600] "POST /paypal/ipn HTTP/1.0" 500 16282 "-" "-"
I tried to send emails and log messages when this view was called, but neither wanted to work. It's possible that I entered the wrong URL into the IPN simulator :) I disabled the "post required" decorator and went to the page directly to see what was going on. My system started to logging "invalid transactions" as expected (since there was no post-data) and then I took a random stab in the dark and figured that Django's CSRF protection was kicking in and preventing PayPal from sending me the data. Adding the #csrf_exempt decorator seems to have fixed it. Yay for guessing errors.

In your django settings.py file, set DEBUG = False
Then for any HTTP 500s (incl. for those being returned to PayPal), you'll be sent a debugging email with all the python stack information.
You'll need to have Django email already set up for this to work, see http://docs.djangoproject.com/en/dev/howto/error-reporting/ for more info on that

You can install a Poster Add-on and make a POST to IPN notify_url from the browser. You will get a Response with all errors. Pretty helpful for debugging.

I just ran into the same problem and this was what I did wrong. Just in case anyone else is as silly as me...
Do not change the method signature from the wiki's
def show_me_the_money(sender, **kwargs):
to something like
def show_me_the_money(ipn, **kwargs):
Reason: in paypal.standard.ipn.models.PayPalIPN.send_signals the listeners are being called with a named argument: payment_was_successful.send(sender=self)
Therefore the method has to have an argument called sender.

I recall having hit (something like) this when using django-paypal too. I can't remember for sure what my cause was, but have you created/migrated the appropriate IPN tables in your database after including the ipn app in your setttings.py?

Related

Django message history

I wonder if you have any ideas on this
I present messages as below - it works great, when there is an error it shows the error.
But, how do I retain these messages - so the user can see all of the errors they had since their session started?
I.e. if I working for a while, I might want to review all my form validation failures at the end.
Thanks
messages.error(request, ('ERROR: Your milestone was not created. Milestone points must be numeric. Please try again.'))
Maybe Django's Sessions.
https://docs.djangoproject.com/en/3.2/topics/http/sessions/#using-sessions-in-views
def message_error(request, msg):
errors = request.session.get('errors', []):
errors.append(msg)
request.session['errors'] = errors
messages.error(request, msg)
def get_message_error_list(request):
return request.session.get('errors', [])

How to employ an error alert system in a deployed Django Site

I am deploying my django site soon!! In debug=True mode, there is an error page that comes up when there is some bug in the code.
When in debug=False mode, after I deploy, I want to set up something so that I am alerted whenever anyone on the prod site reaches this page. Is there any way to do this? Thanks!
This setup would send an email after someone make a request. Here is the documention on sending emails through Django. https://docs.djangoproject.com/en/3.0/topics/email/
def home_page(request):
send_mail(
'Site Visitor',
'someone visted the site.',
'from#example.com',
['to#example.com'],
fail_silently=False,
)
return render(request, 'template')
For an "error alert system," you can look into Sentry. https://sentry.io/for/django/

Process Paypal IPN in Django view

According to the PayPal IPN documentation, you have to respond to an IPN by first sending an empty HTTP 200 response to https://www.sandbox.paypal.com/cgi-bin/webscr and then send an HTTP POST request. The only way I could figure out to send an HTTP 200 response was by returning HttpResponse('') in the view, which works since the IPN simulator says the "IPN was sent and the handshake was verified." But then how would I send the HTTP POST request when the view has already returned? My plan was to use urllib2 to generate the POST request.
I also would like to send an HTTP 200 response without using HttpResponse('') since I don't know where the original request is coming from, and I want to be sure that I'm sending the response to https://www.sandbox.paypal.com/cgi-bin/webscr. I've looked at urllib2, requests, and PyCurl, and I've been unable to find a way to create an empty response to a particular URL.
You want to use requests or urllib2 to make a POST request to verify the IPN before you return the HttpResponse(''); the docs imply you have to do it in the opposite order, but you don't actually have to, and this way is a lot simpler in most web frameworks. So your view should look vaguely like
def ipn(request):
verification = requests.POST(PAYPAL_URL, data=request.body)
if verification.text == 'VERIFIED':
# process IPN
return HttpResponse('')
else:
return HttpResponseForbidden()
Note that if this errors somewhere, you will return a 500 to PayPal, and they will retry. If you do this too much, they will get annoyed at you and eventually deactivate your account, so make sure you are getting alerted of the error in some fashion. (You could also wrap things in a try/except block, but then you risk returning a 200 without actually doing your processing if there's an error, which may be worse.)
just respond to the request made by paypal. for the other requests no response (403 http status)
from django.http import HttpResponseForbidden
def answer_to_pp_ipn():
# ...
if 'paypal' in request.META['HTTP_HOST']:
return HttpResponse('OK 200')
return HttpResponseForbidden()
and as you mentioned, requests is the perfect choice to make a post request.

Using django-socialregistration and getting 'Facebook' object has no attribute 'graph'

I've installed djangosocialregistration and it seemed like it was working fine for a while, but now I'm getting an error and I can't figure out where it's coming from. Inside my view I'm doing this to start looking at the API...
me = request.facebook.graph.get_object("me")
and I'm getting this...
'Facebook' object has no attribute 'graph'
After it quit working I rolled back a couple small changes I'd made, reset everything, deleted cookies and it's still not working. I'm running django 1.1.1 and it's slightly difficult for me to upgrade, not impossible though. I've been reloading a bunch trying to get it working, is there any possibility facebook throttles login connections on their end?
The Facebook class in the middleware of socialregistration looks like this:
class Facebook(object):
def __init__(self, user=None):
if user is None:
self.uid = None
else:
self.uid = user['uid']
self.user = user
self.graph = facebook.GraphAPI(user['access_token'])
If no user is set on __inii__ it will simply not set graph. In the Middleware this should be set via:
fb_user = facebook.get_user_from_cookie(request.COOKIES, getattr(settings, 'FACEBOOK_APP_ID', settings.FACEBOOK_API_KEY), settings.FACEBOOK_SECRET_KEY)
request.facebook = Facebook(fb_user)
So my guess that the cookie from Facebook is not set for your site. Maybe you add some debug logging to determine if there is a cookie from facebook or not.
Another guess would be that request.facebook is overwritten somewhere. Maybe you check this as well.

Django Piston - Is there a login_required decorator? If not, how do we raise errors?

I can't figure out for the life of me how to ensure a user is authenticated in Piston. Here's what I've tried.
Login_required decorator in Piston. This doesn't seem to work, so I looked and found authentication in Piston.
HTTPBasicAuthentication seems to log a user in, rather than ensures a user is_authenticated. I just want to make sure they're authenticated before posting data.
Wrote code manually to check if user.is_authenticated. But then when a user is not authenticated, how do I raise an error that is consistent with Piston's error response?
After this, I was stuck. Thanks for any help.
UPDATE: ok, I figured out the error part. At the very least, I can do this manually. In case anyone wants to know, it's this.
from piston.utils import rc
resp = rc.BAD_REQUEST
resp.write("Need to be logged in yo")
return resp
Create a separate handler for your anonymous clients in your handlers.py extending piston.handler.AnonymousBaseHandler, like described here.
Setup the Resource in your urls.py using the authentication parameter, like in this question.
edit:
Actually by loggin a user in piston.authentication.HttpBasicAuthentication does ensure the user is_authenticated. Try this
def read(self, request):
return {'user': request.user.is_authenticated()}
in your handler and test it with curl -u user:password <url>
You'll get
{
"user": true
}
in your response body.
Your error part works but you're returning a 400 status code by doing that which is a Bad Request, would be more "RESTful" to return a 401 status code which is Unauthorized.
resp = HttpResponse("Authorization Required")
resp.status_code = 401
Here's a listing of all the status codes: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html