Skip to content

Commit

Permalink
Fix some paypal troubles
Browse files Browse the repository at this point in the history
  • Loading branch information
clouserw committed May 3, 2010
1 parent 960b835 commit 0f8b684
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 13 deletions.
31 changes: 29 additions & 2 deletions apps/amo/tests/test_views.py
@@ -1,9 +1,12 @@
import urllib

from django import http, test
from django.conf import settings
from django.core.cache import cache, parse_backend_uri
from amo.urlresolvers import reverse

from mock import patch, Mock
from nose import SkipTest
from nose.tools import eq_
import test_utils

Expand Down Expand Up @@ -73,7 +76,6 @@ def cookie_box(xeno, locale='en-US'):

response = self.client.get("/%s/firefox/?%slang=%s&next=/" % (
locale, xeno, locale), follow=True)
doc = PyQuery(response.content)
box = PyQuery(response.content)('#locale-only').attr('checked')
cookie = self.client.cookies.get("locale-only")

Expand Down Expand Up @@ -109,6 +111,7 @@ class TestPaypal(test_utils.TestCase):

def setUp(self):
self.url = reverse('amo.paypal')
self.item = 1234567890

def urlopener(self, status):
m = Mock()
Expand Down Expand Up @@ -136,4 +139,28 @@ def test_subscription_event(self, urlopen):

def test_get_not_allowed(self):
response = self.client.get(self.url)
eq_(response.status_code, 405)
assert isinstance(response, http.HttpResponseNotAllowed)

@patch('amo.views.urllib2.urlopen')
def test_mysterious_contribution(self, urlopen):
scheme, servers, _ = parse_backend_uri(settings.CACHE_BACKEND)
if 'dummy' in scheme:
raise SkipTest()
urlopen.return_value = self.urlopener('VERIFIED')

key = "%s%s:%s" % (settings.CACHE_PREFIX, 'contrib', self.item)

data = {'txn_id': 100,
'payer_email': 'jbalogh@wherever.com',
'receiver_email': 'clouserw@gmail.com',
'mc_gross': '99.99',
'item_number': self.item,
'payment_status': 'Completed'}
response = self.client.post(self.url, data)
assert isinstance(response, http.HttpResponseServerError)
eq_(cache.get(key), 1)

cache.set(key, 10, 1209600)
response = self.client.post(self.url, data)
assert isinstance(response, http.HttpResponse)
eq_(cache.get(key), None)
41 changes: 30 additions & 11 deletions apps/amo/views.py
Expand Up @@ -3,7 +3,7 @@

from django import http
from django.conf import settings
from django.core.cache import parse_backend_uri
from django.core.cache import cache, parse_backend_uri
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt

Expand Down Expand Up @@ -68,38 +68,57 @@ def paypal(request):
IPN will retry periodically until it gets success (status=200). Any
db errors or replication lag will result in an exception and http
status of 500, which is good so PayPal will try again later.
PayPal IPN variables available at:
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content
&content_ID=developer/e_howto_html_IPNandPDTVariables
"""
if request.method != 'POST':
return http.HttpResponseNotAllowed(['POST'])

# Check that the request is valid and coming from PayPal.
data = request.POST.copy()
data['cmd'] = '_notify-validate'
if urllib2.urlopen(settings.PAYPAL_CGI_URL,
data.urlencode(), 20).readline() != 'VERIFIED':
pr = urllib2.urlopen(settings.PAYPAL_CGI_URL,
data.urlencode(), 20).readline()
if pr != 'VERIFIED':
log.error("Expecting 'VERIFIED' from PayPal, got '%s'. Failing." % pr)
return http.HttpResponseForbidden('Invalid confirmation')

if request.POST.get('txn_type', '').startswith('subscr_'):
SubscriptionEvent.objects.create(post_data=php.serialize(request.POST))
return http.HttpResponse()
return http.HttpResponse('Success!')

# We only care about completed transactions.
if request.POST.get('payment_status') != 'Completed':
return http.HttpResponse('Payment not completed')

# Make sure transaction has not yet been processed.
if len(Contribution.objects.filter(transaction_id=request.POST['txn_id'])) > 0:
if (Contribution.objects
.filter(transaction_id=request.POST['txn_id']).count()) > 0:
return http.HttpResponse('Transaction already processed')

# Fetch and update the contribution - item_number is the uuid we created.
try:
c = Contribution.objects.get(uuid=request.POST['item_number'])
c = Contribution.objects.no_cache().get(
uuid=request.POST['item_number'])
except Contribution.DoesNotExist:
# If these warnings frequently occur on the first IPN attempt,
# perhaps consider logging only if POST['resend'] is set.
ipn_type = 'repeated' if 'resend' in request.POST else 'initial'
log.warning('Contribution (uuid=%s) not found for %s IPN request.'
% (request.POST['item_number'], ipn_type))
key = "%s%s:%s" % (settings.CACHE_PREFIX, 'contrib',
request.POST['item_number'])
count = cache.get(key, 0) + 1

log.warning('Contribution (uuid=%s) not found for IPN request #%s.'
% (request.POST['item_number'], count))
if count > 10:
logme = (request.POST['txn_id'], request.POST['payer_email'],
request.POST['receiver_email'], request.POST['mc_gross'])
log.error("Paypal sent a transaction that we don't know about and "
"we're giving up on it! (TxnID={d[txn_id]}; "
"From={d[payer_email]}; To={d[receiver_email]}; "
"Amount={d[mc_gross]})".format(d=request.POST))
cache.delete(key)
return http.HttpResponse('Transaction not found; skipping.')
cache.set(key, count, 1209600) # This is 2 weeks.
return http.HttpResponseServerError('Contribution not found')

c.transaction_id = request.POST['txn_id']
Expand Down

0 comments on commit 0f8b684

Please sign in to comment.