comparison gpp/donations/views.py @ 39:5dbfb7fec629

Donations; reworked the IPN handling and added logging.
author Brian Neal <bgneal@gmail.com>
date Fri, 12 Jun 2009 01:06:05 +0000
parents 296b610ee507
children 8c9344e36813
comparison
equal deleted inserted replaced
38:c14cfd6be87a 39:5dbfb7fec629
56 56
57 57
58 def ipn(request): 58 def ipn(request):
59 """ 59 """
60 This function is the IPN listener and handles the IPN POST from Paypal. 60 This function is the IPN listener and handles the IPN POST from Paypal.
61 The algorithm here roughly follows the outline described in chapter 2
62 of Paypal's IPNGuide.pdf "Implementing an IPN Listener".
63 """
64 import logging
61 65
62 TODO: Add logging. 66 # Log some info about this IPN event
63 """ 67 ip = request.META.get('REMOTE_ADDR', '?')
64 parameters = None 68 parameters = request.POST.copy()
65 try: 69 logging.info('IPN from %s; post data: %s' % (ip, parameters.urlencode()))
66 if request['payment_status'] == 'Completed':
67 if request.POST:
68 parameters = request.POST.copy()
69 else:
70 parameters = request.GET.copy()
71 70
72 if parameters: 71 # Now we follow the instructions in chapter 2 of the Paypal IPNGuide.pdf.
73 parameters['cmd']='_notify-validate' 72 # Create a request that contains exactly the same IPN variables and values in
74 req = urllib2.Request(paypal_params()[0], parameters.urlencode()) 73 # the same order, preceded with cmd=_notify-validate
75 req.add_header("Content-type", "application/x-www-form-urlencoded") 74 parameters['cmd']='_notify-validate'
76 response = urllib2.urlopen(req)
77 status = response.read()
78 if status != "VERIFIED":
79 parameters = None
80 75
81 if parameters: 76 # Post the request back to Paypal (either to the sandbox or the real deal).
82 # is this a donation to the site? 77 req = urllib2.Request(paypal_params()[0], parameters.urlencode())
83 try: 78 req.add_header("Content-type", "application/x-www-form-urlencoded")
84 item_number = int(parameters['item_number']) 79 response = urllib2.urlopen(req)
85 except ValueError:
86 pass
87 else:
88 if item_number == settings.DONATIONS_ITEM_NUM or \
89 item_number == settings.DONATIONS_ITEM_ANON_NUM:
90 process_donation(item_number, parameters)
91 80
92 return HttpResponse("Ok") 81 # Wait for the response from Paypal, which should be either VERIFIED or INVALID.
82 status = response.read()
83 if status != 'VERIFIED':
84 logging.warning('IPN: Payapl did not verify; status was %s' % status)
85 return HttpResponse()
93 86
94 except Exception, e: 87 # Response was VERIFIED; act on this if it is a Completed donation,
95 # print "An exeption was caught: " + str(e) 88 # otherwise don't handle it (we are just a donations application. Here
96 pass 89 # is where we could be expanded to be a more general payment processor).
97 90
98 return HttpResponseServerError("Error") 91 payment_status = parameters.get('payment_status')
92 if payment_status != 'Completed':
93 logging.info('IPN: payment_status is %s; we are done.' % payment_status)
94 return HttpResponse()
95
96 # Is this a donation to the site?
97 item_number = parameters.get('item_number')
98 if item_number == settings.DONATIONS_ITEM_NUM or \
99 item_number == settings.DONATIONS_ITEM_ANON_NUM:
100 process_donation(item_number, parameters)
101 else:
102 logging.info('IPN: not a donation; done.')
103
104 return HttpResponse()
99 105
100 106
101 def process_donation(item_number, params): 107 def process_donation(item_number, params):
102 """ 108 """
103 Constructs a donation object from the parameters and stores 109 A few validity and duplicate checks are made on the donation params.
104 in the database. 110 If everything is ok, construct a donation object from the parameters and
111 store it in the database.
105 """ 112 """
113 import logging
114
106 # Has this transaction been processed before? 115 # Has this transaction been processed before?
107 if 'txn_id' not in params: 116 txn_id = params.get('txn_id')
117 if txn_id is None:
118 logging.error('IPN: missing txn_id')
108 return 119 return
109 txn_id = params['txn_id'] 120
110 try: 121 try:
111 donation = Donation.objects.get(txn_id__exact=txn_id) 122 donation = Donation.objects.get(txn_id__exact=txn_id)
112 except Donation.DoesNotExist: 123 except Donation.DoesNotExist:
113 pass 124 pass
114 else: 125 else:
126 logging.warning('IPN: duplicate txn_id')
115 return # no exception, this is a duplicate 127 return # no exception, this is a duplicate
116 128
117 # Is the email address ours? 129 # Is the email address ours?
118 business = params.get('business') 130 business = params.get('business')
119 if business != paypal_params()[1]: 131 if business != paypal_params()[1]:
132 logging.warning('IPN: invalid business: %s' % business)
120 return 133 return
121 134
122 # is this a payment received? 135 # is this a payment received?
123 txn_type = params.get('txn_type') 136 txn_type = params.get('txn_type')
124 if txn_type != 'web_accept': 137 if txn_type != 'web_accept':
138 logging.warning('IPN: invalid txn_type: %s' % txn_type)
125 return 139 return
126 140
127 # Looks like a donation, save it to the database. 141 # Looks like a donation, save it to the database.
128 # Determine which user this came from, if any. 142 # Determine which user this came from, if any.
129 # The username is stored in the custom field if the user was logged in when 143 # The username is stored in the custom field if the user was logged in when
147 161
148 try: 162 try:
149 mc_gross = decimal.Decimal(params['mc_gross']) 163 mc_gross = decimal.Decimal(params['mc_gross'])
150 mc_fee = decimal.Decimal(params['mc_fee']) 164 mc_fee = decimal.Decimal(params['mc_fee'])
151 except KeyError, decimal.InvalidOperation: 165 except KeyError, decimal.InvalidOperation:
166 logging.error('IPN: invalid/missing mc_gross or mc_fee')
152 return 167 return
153 168
154 try: 169 try:
155 payment_date = datetime.datetime.strptime(params['payment_date'], 170 payment_date = datetime.datetime.strptime(params['payment_date'],
156 PP_DATE_FMT) 171 PP_DATE_FMT)
157 except KeyError, ValueError: 172 except KeyError, ValueError:
173 logging.error('IPN: invalid/missing payment_date')
158 return 174 return
159 175
160 donation = Donation( 176 donation = Donation(
161 user=user, 177 user=user,
162 is_anonymous=is_anonymous, 178 is_anonymous=is_anonymous,
171 mc_gross=mc_gross, 187 mc_gross=mc_gross,
172 mc_fee=mc_fee, 188 mc_fee=mc_fee,
173 payment_date=payment_date) 189 payment_date=payment_date)
174 190
175 donation.save() 191 donation.save()
192 logging.info('IPN: donation saved')
176 193