comparison gpp/donations/views.py @ 316:767cedc7d12a

Fixing #144; integrate with new Django logging support. Also added unit tests for Donations app.
author Brian Neal <bgneal@gmail.com>
date Sun, 30 Jan 2011 20:02:32 +0000
parents a3b47d0f4df1
children
comparison
equal deleted inserted replaced
315:36373d995611 316:767cedc7d12a
2 Views for the donations application. 2 Views for the donations application.
3 """ 3 """
4 import urllib2 4 import urllib2
5 import decimal 5 import decimal
6 import datetime 6 import datetime
7 import logging
7 8
8 from django.shortcuts import render_to_response 9 from django.shortcuts import render_to_response
9 from django.template import RequestContext 10 from django.template import RequestContext
10 from django.conf import settings 11 from django.conf import settings
11 from django.contrib.sites.models import Site 12 from django.contrib.sites.models import Site
31 else: 32 else:
32 form_action = 'https://www.paypal.com/cgi-bin/webscr' 33 form_action = 'https://www.paypal.com/cgi-bin/webscr'
33 business = settings.DONATIONS_BUSINESS 34 business = settings.DONATIONS_BUSINESS
34 35
35 return form_action, business 36 return form_action, business
37
38
39 def verify_request(params):
40 """
41 Send the parameters back to Paypal and return the response string.
42 """
43 # If we are doing localhost-type unit tests, just return whatever
44 # the test wants us to...
45 if hasattr(settings, 'DONATIONS_DEBUG_VERIFY_RESPONSE'):
46 return settings.DONATIONS_DEBUG_VERIFY_RESPONSE
47
48 req = urllib2.Request(paypal_params()[0], params)
49 req.add_header("Content-type", "application/x-www-form-urlencoded")
50 try:
51 response = urllib2.urlopen(req)
52 except URLError, e:
53 logging.exception('IPN: exception verifying IPN: %s', e)
54 return None
55
56 return response.read()
36 57
37 58
38 def index(request): 59 def index(request):
39 gross, net, donations = Donation.objects.monthly_stats() 60 gross, net, donations = Donation.objects.monthly_stats()
40 current_site = Site.objects.get_current() 61 current_site = Site.objects.get_current()
61 def ipn(request): 82 def ipn(request):
62 """ 83 """
63 This function is the IPN listener and handles the IPN POST from Paypal. 84 This function is the IPN listener and handles the IPN POST from Paypal.
64 The algorithm here roughly follows the outline described in chapter 2 85 The algorithm here roughly follows the outline described in chapter 2
65 of Paypal's IPNGuide.pdf "Implementing an IPN Listener". 86 of Paypal's IPNGuide.pdf "Implementing an IPN Listener".
66 """ 87
67 import logging 88 """
68
69 # Log some info about this IPN event 89 # Log some info about this IPN event
70 ip = request.META.get('REMOTE_ADDR', '?') 90 ip = request.META.get('REMOTE_ADDR', '?')
71 parameters = request.POST.copy() 91 parameters = request.POST.copy()
72 logging.info('IPN from %s; post data: %s' % (ip, parameters.urlencode())) 92 logging.info('IPN from %s; post data: %s', ip, parameters.urlencode())
73 93
74 # Now we follow the instructions in chapter 2 of the Paypal IPNGuide.pdf. 94 # Now we follow the instructions in chapter 2 of the Paypal IPNGuide.pdf.
75 # Create a request that contains exactly the same IPN variables and values in 95 # Create a request that contains exactly the same IPN variables and values in
76 # the same order, preceded with cmd=_notify-validate 96 # the same order, preceded with cmd=_notify-validate
77 parameters['cmd']='_notify-validate' 97 parameters['cmd']='_notify-validate'
78 98
79 # Post the request back to Paypal (either to the sandbox or the real deal). 99 # Post the request back to Paypal (either to the sandbox or the real deal),
80 req = urllib2.Request(paypal_params()[0], parameters.urlencode()) 100 # and read the response:
81 req.add_header("Content-type", "application/x-www-form-urlencoded") 101 status = verify_request(parameters.urlencode())
82 response = urllib2.urlopen(req)
83
84 # Wait for the response from Paypal, which should be either VERIFIED or INVALID.
85 status = response.read()
86 if status != 'VERIFIED': 102 if status != 'VERIFIED':
87 logging.warning('IPN: Payapl did not verify; status was %s' % status) 103 logging.warning('IPN: Payapl did not verify; status was %s', status)
88 return HttpResponse() 104 return HttpResponse()
89 105
90 # Response was VERIFIED; act on this if it is a Completed donation, 106 # Response was VERIFIED; act on this if it is a Completed donation,
91 # otherwise don't handle it (we are just a donations application. Here 107 # otherwise don't handle it (we are just a donations application. Here
92 # is where we could be expanded to be a more general payment processor). 108 # is where we could be expanded to be a more general payment processor).
93 109
94 payment_status = parameters.get('payment_status') 110 payment_status = parameters.get('payment_status')
95 if payment_status != 'Completed': 111 if payment_status != 'Completed':
96 logging.info('IPN: payment_status is %s; we are done.' % payment_status) 112 logging.info('IPN: payment_status is %s; we are done.', payment_status)
97 return HttpResponse() 113 return HttpResponse()
98 114
99 # Is this a donation to the site? 115 # Is this a donation to the site?
100 item_number = parameters.get('item_number') 116 item_number = parameters.get('item_number')
101 if item_number == settings.DONATIONS_ITEM_NUM or \ 117 if (item_number == settings.DONATIONS_ITEM_NUM or
102 item_number == settings.DONATIONS_ITEM_ANON_NUM: 118 item_number == settings.DONATIONS_ITEM_ANON_NUM):
103 process_donation(item_number, parameters) 119 process_donation(item_number, parameters)
104 else: 120 else:
105 logging.info('IPN: not a donation; done.') 121 logging.info('IPN: not a donation; done.')
106 122
107 return HttpResponse() 123 return HttpResponse()
108 124
109 125
110 def process_donation(item_number, params): 126 def process_donation(item_number, params):
111 """ 127 """
112 A few validity and duplicate checks are made on the donation params. 128 A few validity and duplicate checks are made on the donation params.
113 If everything is ok, construct a donation object from the parameters and 129 If everything is ok, construct a donation object from the parameters and
114 store it in the database. 130 store it in the database.
115 """ 131
116 import logging 132 """
117
118 # Has this transaction been processed before? 133 # Has this transaction been processed before?
119 txn_id = params.get('txn_id') 134 txn_id = params.get('txn_id')
120 if txn_id is None: 135 if txn_id is None:
121 logging.error('IPN: missing txn_id') 136 logging.error('IPN: missing txn_id')
122 return 137 return
130 return # no exception, this is a duplicate 145 return # no exception, this is a duplicate
131 146
132 # Is the email address ours? 147 # Is the email address ours?
133 business = params.get('business') 148 business = params.get('business')
134 if business != paypal_params()[1]: 149 if business != paypal_params()[1]:
135 logging.warning('IPN: invalid business: %s' % business) 150 logging.warning('IPN: invalid business: %s', business)
136 return 151 return
137 152
138 # is this a payment received? 153 # is this a payment received?
139 txn_type = params.get('txn_type') 154 txn_type = params.get('txn_type')
140 if txn_type != 'web_accept': 155 if txn_type != 'web_accept':
141 logging.warning('IPN: invalid txn_type: %s' % txn_type) 156 logging.warning('IPN: invalid txn_type: %s', txn_type)
142 return 157 return
143 158
144 # Looks like a donation, save it to the database. 159 # Looks like a donation, save it to the database.
145 # Determine which user this came from, if any. 160 # Determine which user this came from, if any.
146 # The username is stored in the custom field if the user was logged in when 161 # The username is stored in the custom field if the user was logged in when
147 # the donation was made. 162 # the donation was made.
148 user = None 163 user = None
177 # strip off the timezone 192 # strip off the timezone
178 payment_date = payment_date[:-4] 193 payment_date = payment_date[:-4]
179 try: 194 try:
180 payment_date = datetime.datetime.strptime(payment_date, PP_DATE_FMT) 195 payment_date = datetime.datetime.strptime(payment_date, PP_DATE_FMT)
181 except ValueError: 196 except ValueError:
182 logging.error('IPN: invalid payment_date "%s"' % params['payment_date']) 197 logging.error('IPN: invalid payment_date "%s"', params['payment_date'])
183 return 198 return
184 199
185 try: 200 try:
186 donation = Donation( 201 donation = Donation(
187 user=user, 202 user=user,