Mercurial > public > sg101
comparison donations/views.py @ 581:ee87ea74d46b
For Django 1.4, rearranged project structure for new manage.py.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sat, 05 May 2012 17:10:48 -0500 |
parents | gpp/donations/views.py@767cedc7d12a |
children | e1c03da72818 |
comparison
equal
deleted
inserted
replaced
580:c525f3e0b5d0 | 581:ee87ea74d46b |
---|---|
1 """ | |
2 Views for the donations application. | |
3 """ | |
4 import urllib2 | |
5 import decimal | |
6 import datetime | |
7 import logging | |
8 | |
9 from django.shortcuts import render_to_response | |
10 from django.template import RequestContext | |
11 from django.conf import settings | |
12 from django.contrib.sites.models import Site | |
13 from django.http import HttpResponse | |
14 from django.http import HttpResponseServerError | |
15 from django.contrib.auth.models import User | |
16 from django.views.decorators.csrf import csrf_exempt | |
17 | |
18 | |
19 from donations.models import Donation | |
20 | |
21 PP_DATE_FMT = '%H:%M:%S %b %d, %Y' | |
22 | |
23 def paypal_params(): | |
24 """ | |
25 This function returns a tuple where the 1st element is the Paypal | |
26 URL and the 2nd element is the Paypal business email. This information | |
27 depends on the setting DONATIONS_DEBUG. | |
28 """ | |
29 if settings.DONATIONS_DEBUG: | |
30 form_action = 'https://www.sandbox.paypal.com/cgi-bin/webscr' | |
31 business = settings.DONATIONS_BUSINESS_DEBUG | |
32 else: | |
33 form_action = 'https://www.paypal.com/cgi-bin/webscr' | |
34 business = settings.DONATIONS_BUSINESS | |
35 | |
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() | |
57 | |
58 | |
59 def index(request): | |
60 gross, net, donations = Donation.objects.monthly_stats() | |
61 current_site = Site.objects.get_current() | |
62 form_action, business = paypal_params() | |
63 | |
64 return render_to_response('donations/index.html', { | |
65 'goal': settings.DONATIONS_GOAL, | |
66 'gross': gross, | |
67 'net': net, | |
68 'left': settings.DONATIONS_GOAL - net, | |
69 'donations': donations, | |
70 'form_action': form_action, | |
71 'business': business, | |
72 'anonymous': settings.DONATIONS_ANON_NAME, | |
73 'item_name': settings.DONATIONS_ITEM_NAME, | |
74 'item_number': settings.DONATIONS_ITEM_NUM, | |
75 'item_anon_number': settings.DONATIONS_ITEM_ANON_NUM, | |
76 'domain': current_site.domain, | |
77 }, | |
78 context_instance = RequestContext(request)) | |
79 | |
80 | |
81 @csrf_exempt | |
82 def ipn(request): | |
83 """ | |
84 This function is the IPN listener and handles the IPN POST from Paypal. | |
85 The algorithm here roughly follows the outline described in chapter 2 | |
86 of Paypal's IPNGuide.pdf "Implementing an IPN Listener". | |
87 | |
88 """ | |
89 # Log some info about this IPN event | |
90 ip = request.META.get('REMOTE_ADDR', '?') | |
91 parameters = request.POST.copy() | |
92 logging.info('IPN from %s; post data: %s', ip, parameters.urlencode()) | |
93 | |
94 # Now we follow the instructions in chapter 2 of the Paypal IPNGuide.pdf. | |
95 # Create a request that contains exactly the same IPN variables and values in | |
96 # the same order, preceded with cmd=_notify-validate | |
97 parameters['cmd']='_notify-validate' | |
98 | |
99 # Post the request back to Paypal (either to the sandbox or the real deal), | |
100 # and read the response: | |
101 status = verify_request(parameters.urlencode()) | |
102 if status != 'VERIFIED': | |
103 logging.warning('IPN: Payapl did not verify; status was %s', status) | |
104 return HttpResponse() | |
105 | |
106 # Response was VERIFIED; act on this if it is a Completed donation, | |
107 # otherwise don't handle it (we are just a donations application. Here | |
108 # is where we could be expanded to be a more general payment processor). | |
109 | |
110 payment_status = parameters.get('payment_status') | |
111 if payment_status != 'Completed': | |
112 logging.info('IPN: payment_status is %s; we are done.', payment_status) | |
113 return HttpResponse() | |
114 | |
115 # Is this a donation to the site? | |
116 item_number = parameters.get('item_number') | |
117 if (item_number == settings.DONATIONS_ITEM_NUM or | |
118 item_number == settings.DONATIONS_ITEM_ANON_NUM): | |
119 process_donation(item_number, parameters) | |
120 else: | |
121 logging.info('IPN: not a donation; done.') | |
122 | |
123 return HttpResponse() | |
124 | |
125 | |
126 def process_donation(item_number, params): | |
127 """ | |
128 A few validity and duplicate checks are made on the donation params. | |
129 If everything is ok, construct a donation object from the parameters and | |
130 store it in the database. | |
131 | |
132 """ | |
133 # Has this transaction been processed before? | |
134 txn_id = params.get('txn_id') | |
135 if txn_id is None: | |
136 logging.error('IPN: missing txn_id') | |
137 return | |
138 | |
139 try: | |
140 donation = Donation.objects.get(txn_id__exact=txn_id) | |
141 except Donation.DoesNotExist: | |
142 pass | |
143 else: | |
144 logging.warning('IPN: duplicate txn_id') | |
145 return # no exception, this is a duplicate | |
146 | |
147 # Is the email address ours? | |
148 business = params.get('business') | |
149 if business != paypal_params()[1]: | |
150 logging.warning('IPN: invalid business: %s', business) | |
151 return | |
152 | |
153 # is this a payment received? | |
154 txn_type = params.get('txn_type') | |
155 if txn_type != 'web_accept': | |
156 logging.warning('IPN: invalid txn_type: %s', txn_type) | |
157 return | |
158 | |
159 # Looks like a donation, save it to the database. | |
160 # Determine which user this came from, if any. | |
161 # The username is stored in the custom field if the user was logged in when | |
162 # the donation was made. | |
163 user = None | |
164 if 'custom' in params and params['custom']: | |
165 try: | |
166 user = User.objects.get(username__exact=params['custom']) | |
167 except User.DoesNotExist: | |
168 pass | |
169 | |
170 is_anonymous = item_number == settings.DONATIONS_ITEM_ANON_NUM | |
171 test_ipn = params.get('test_ipn') == '1' | |
172 | |
173 first_name = params.get('first_name', '') | |
174 last_name = params.get('last_name', '') | |
175 payer_email = params.get('payer_email', '') | |
176 payer_id = params.get('payer_id', '') | |
177 memo = params.get('memo', '') | |
178 payer_status = params.get('payer_status', '') | |
179 | |
180 try: | |
181 mc_gross = decimal.Decimal(params['mc_gross']) | |
182 mc_fee = decimal.Decimal(params['mc_fee']) | |
183 except KeyError, decimal.InvalidOperation: | |
184 logging.error('IPN: invalid/missing mc_gross or mc_fee') | |
185 return | |
186 | |
187 payment_date = params.get('payment_date') | |
188 if payment_date is None: | |
189 logging.error('IPN: missing payment_date') | |
190 return | |
191 | |
192 # strip off the timezone | |
193 payment_date = payment_date[:-4] | |
194 try: | |
195 payment_date = datetime.datetime.strptime(payment_date, PP_DATE_FMT) | |
196 except ValueError: | |
197 logging.error('IPN: invalid payment_date "%s"', params['payment_date']) | |
198 return | |
199 | |
200 try: | |
201 donation = Donation( | |
202 user=user, | |
203 is_anonymous=is_anonymous, | |
204 test_ipn=test_ipn, | |
205 txn_id=txn_id, | |
206 txn_type=txn_type, | |
207 first_name=first_name, | |
208 last_name=last_name, | |
209 payer_email=payer_email, | |
210 payer_id=payer_id, | |
211 memo=memo, | |
212 payer_status=payer_status, | |
213 mc_gross=mc_gross, | |
214 mc_fee=mc_fee, | |
215 payment_date=payment_date) | |
216 except: | |
217 logging.exception('IPN: exception during donation creation') | |
218 else: | |
219 donation.save() | |
220 logging.info('IPN: donation saved') | |
221 |