Mercurial > public > sg101
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, |