Mercurial > public > sg101
view donations/views.py @ 709:e1153e58162f
Update to Django 1.5.4; update requirements.txt.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sun, 15 Sep 2013 21:25:54 -0500 |
parents | ee87ea74d46b |
children | e1c03da72818 |
line wrap: on
line source
""" Views for the donations application. """ import urllib2 import decimal import datetime import logging from django.shortcuts import render_to_response from django.template import RequestContext from django.conf import settings from django.contrib.sites.models import Site from django.http import HttpResponse from django.http import HttpResponseServerError from django.contrib.auth.models import User from django.views.decorators.csrf import csrf_exempt from donations.models import Donation PP_DATE_FMT = '%H:%M:%S %b %d, %Y' def paypal_params(): """ This function returns a tuple where the 1st element is the Paypal URL and the 2nd element is the Paypal business email. This information depends on the setting DONATIONS_DEBUG. """ if settings.DONATIONS_DEBUG: form_action = 'https://www.sandbox.paypal.com/cgi-bin/webscr' business = settings.DONATIONS_BUSINESS_DEBUG else: form_action = 'https://www.paypal.com/cgi-bin/webscr' business = settings.DONATIONS_BUSINESS return form_action, business def verify_request(params): """ Send the parameters back to Paypal and return the response string. """ # If we are doing localhost-type unit tests, just return whatever # the test wants us to... if hasattr(settings, 'DONATIONS_DEBUG_VERIFY_RESPONSE'): return settings.DONATIONS_DEBUG_VERIFY_RESPONSE req = urllib2.Request(paypal_params()[0], params) req.add_header("Content-type", "application/x-www-form-urlencoded") try: response = urllib2.urlopen(req) except URLError, e: logging.exception('IPN: exception verifying IPN: %s', e) return None return response.read() def index(request): gross, net, donations = Donation.objects.monthly_stats() current_site = Site.objects.get_current() form_action, business = paypal_params() return render_to_response('donations/index.html', { 'goal': settings.DONATIONS_GOAL, 'gross': gross, 'net': net, 'left': settings.DONATIONS_GOAL - net, 'donations': donations, 'form_action': form_action, 'business': business, 'anonymous': settings.DONATIONS_ANON_NAME, 'item_name': settings.DONATIONS_ITEM_NAME, 'item_number': settings.DONATIONS_ITEM_NUM, 'item_anon_number': settings.DONATIONS_ITEM_ANON_NUM, 'domain': current_site.domain, }, context_instance = RequestContext(request)) @csrf_exempt def ipn(request): """ This function is the IPN listener and handles the IPN POST from Paypal. The algorithm here roughly follows the outline described in chapter 2 of Paypal's IPNGuide.pdf "Implementing an IPN Listener". """ # Log some info about this IPN event ip = request.META.get('REMOTE_ADDR', '?') parameters = request.POST.copy() logging.info('IPN from %s; post data: %s', ip, parameters.urlencode()) # Now we follow the instructions in chapter 2 of the Paypal IPNGuide.pdf. # Create a request that contains exactly the same IPN variables and values in # the same order, preceded with cmd=_notify-validate parameters['cmd']='_notify-validate' # Post the request back to Paypal (either to the sandbox or the real deal), # and read the response: status = verify_request(parameters.urlencode()) if status != 'VERIFIED': logging.warning('IPN: Payapl did not verify; status was %s', status) return HttpResponse() # Response was VERIFIED; act on this if it is a Completed donation, # otherwise don't handle it (we are just a donations application. Here # is where we could be expanded to be a more general payment processor). payment_status = parameters.get('payment_status') if payment_status != 'Completed': logging.info('IPN: payment_status is %s; we are done.', payment_status) return HttpResponse() # Is this a donation to the site? item_number = parameters.get('item_number') if (item_number == settings.DONATIONS_ITEM_NUM or item_number == settings.DONATIONS_ITEM_ANON_NUM): process_donation(item_number, parameters) else: logging.info('IPN: not a donation; done.') return HttpResponse() def process_donation(item_number, params): """ A few validity and duplicate checks are made on the donation params. If everything is ok, construct a donation object from the parameters and store it in the database. """ # Has this transaction been processed before? txn_id = params.get('txn_id') if txn_id is None: logging.error('IPN: missing txn_id') return try: donation = Donation.objects.get(txn_id__exact=txn_id) except Donation.DoesNotExist: pass else: logging.warning('IPN: duplicate txn_id') return # no exception, this is a duplicate # Is the email address ours? business = params.get('business') if business != paypal_params()[1]: logging.warning('IPN: invalid business: %s', business) return # is this a payment received? txn_type = params.get('txn_type') if txn_type != 'web_accept': logging.warning('IPN: invalid txn_type: %s', txn_type) return # Looks like a donation, save it to the database. # Determine which user this came from, if any. # The username is stored in the custom field if the user was logged in when # the donation was made. user = None if 'custom' in params and params['custom']: try: user = User.objects.get(username__exact=params['custom']) except User.DoesNotExist: pass is_anonymous = item_number == settings.DONATIONS_ITEM_ANON_NUM test_ipn = params.get('test_ipn') == '1' first_name = params.get('first_name', '') last_name = params.get('last_name', '') payer_email = params.get('payer_email', '') payer_id = params.get('payer_id', '') memo = params.get('memo', '') payer_status = params.get('payer_status', '') try: mc_gross = decimal.Decimal(params['mc_gross']) mc_fee = decimal.Decimal(params['mc_fee']) except KeyError, decimal.InvalidOperation: logging.error('IPN: invalid/missing mc_gross or mc_fee') return payment_date = params.get('payment_date') if payment_date is None: logging.error('IPN: missing payment_date') return # strip off the timezone payment_date = payment_date[:-4] try: payment_date = datetime.datetime.strptime(payment_date, PP_DATE_FMT) except ValueError: logging.error('IPN: invalid payment_date "%s"', params['payment_date']) return try: donation = Donation( user=user, is_anonymous=is_anonymous, test_ipn=test_ipn, txn_id=txn_id, txn_type=txn_type, first_name=first_name, last_name=last_name, payer_email=payer_email, payer_id=payer_id, memo=memo, payer_status=payer_status, mc_gross=mc_gross, mc_fee=mc_fee, payment_date=payment_date) except: logging.exception('IPN: exception during donation creation') else: donation.save() logging.info('IPN: donation saved')