cope with different ipn, purchase order (bug 704971)

This commit is contained in:
Andy McKay 2011-11-24 10:46:42 -08:00
Родитель 64faf56744
Коммит e8412b4d5d
6 изменённых файлов: 126 добавлений и 19 удалений

Просмотреть файл

@ -357,9 +357,9 @@ class TestPurchaseEmbedded(amo.tests.TestCase):
def test_check_wrong_uuid(self, check_purchase):
check_purchase.return_value = 'COMPLETED'
self.make_contribution()
self.assertRaises(Contribution.DoesNotExist,
self.client.get_ajax,
'%s?uuid=%s' % (self.get_url('complete'), 'foo'))
res = self.client.get_ajax('%s?uuid=%s' %
(self.get_url('complete'), 'foo'))
eq_(res.status_code, 404)
@patch('paypal.check_purchase')
def test_check_pending(self, check_purchase):

Просмотреть файл

@ -21,7 +21,6 @@ import commonware.log
import session_csrf
from tower import ugettext as _, ugettext_lazy as _lazy
from mobility.decorators import mobilized, mobile_template
import waffle
import amo
from amo import messages
@ -33,7 +32,6 @@ from amo import urlresolvers
from amo.urlresolvers import reverse
from abuse.models import send_abuse_report
from bandwagon.models import Collection, CollectionFeature, CollectionPromo
from devhub.decorators import dev_required
import paypal
from reviews.forms import ReviewForm
from reviews.models import Review, GroupedRating
@ -545,7 +543,14 @@ def purchase_complete(request, addon, status):
if status == 'complete':
uuid_ = request.GET.get('uuid')
log.debug('Looking up contrib for uuid: %s' % uuid_)
con = Contribution.objects.get(uuid=uuid_, type=amo.CONTRIB_PENDING)
# The IPN may, or may not have come through. Which means looking for
# a for pre or post IPN contributions. If both fail, then we've not
# got a matching contribution.
lookup = (Q(uuid=uuid_, type=amo.CONTRIB_PENDING) |
Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE))
con = get_object_or_404(Contribution, lookup)
log.debug('Check purchase paypal addon: %s, user: %s, paykey: %s'
% (addon.pk, request.amo_user.pk, con.paykey[:10]))
try:
@ -559,9 +564,8 @@ def purchase_complete(request, addon, status):
log.debug('Paypal returned: %s for paykey: %s'
% (result, con.paykey[:10]))
if result == 'COMPLETED':
con.type = amo.CONTRIB_PURCHASE
con.save()
if result == 'COMPLETED' and con.type == amo.CONTRIB_PENDING:
con.update(type=amo.CONTRIB_PURCHASE)
response = jingo.render(request, 'addons/paypal_result.html',
{'addon': addon,

Просмотреть файл

@ -0,0 +1,97 @@
from mock import Mock, patch
from nose.tools import eq_
import waffle
from addons.models import Addon
import amo
import amo.tests
from amo.helpers import urlparams
from amo.urlresolvers import reverse
from market.models import AddonPremium
from paypal.tests.test_views import sample_purchase
from stats.models import Contribution
from users.models import UserProfile
uuid = '123'
sample_ipn = sample_purchase.copy()
sample_ipn['tracking_id'] = uuid
class TestPurchaseIPNOrder(amo.tests.TestCase):
# Specific tests that cross a few boundaries of purchase and
# IPN processing to make sure that a few of the more complicated
# scenarios don't break things.
fixtures = ['base/apps', 'base/addon_592', 'base/users', 'prices']
def setUp(self):
waffle.models.Switch.objects.create(name='marketplace', active=True)
self.addon = Addon.objects.get(pk=592)
self.addon.update(premium_type=amo.ADDON_PREMIUM,
status=amo.STATUS_PUBLIC)
self.user = UserProfile.objects.get(email='regular@mozilla.com')
self.finished = urlparams(reverse('addons.purchase.finished',
args=[self.addon.slug, 'complete']),
uuid=uuid)
self.ipn = reverse('amo.paypal')
self.client.login(username='regular@mozilla.com', password='password')
AddonPremium.objects.create(addon=self.addon, price_id=1)
self.con = Contribution.objects.create(addon=self.addon, uuid=uuid,
user=self.user, paykey='sdf',
type=amo.CONTRIB_PENDING)
def get_contribution(self):
return Contribution.objects.get(pk=self.con.pk)
def is_contribution_good(self):
# Checks that the IPN has been by and its all good.
con = self.get_contribution()
return (con.uuid == None
and con.transaction_id == uuid
and con.post_data)
def urlopener(self, status):
m = Mock()
m.readline.return_value = status
return m
@patch('paypal.check_purchase')
def get_finished(self, check_purchase):
check_purchase.return_value = 'COMPLETED'
response = self.client.get(self.finished)
eq_(response.status_code, 200)
@patch('paypal.views.urllib2.urlopen')
def get_ipn(self, urlopen):
urlopen.return_value = self.urlopener('VERIFIED')
response = self.client.post(self.ipn, sample_ipn)
eq_(response.status_code, 200)
def test_result(self):
self.get_finished()
assert self.addon.has_purchased(self.user)
assert not self.is_contribution_good()
def test_result_then_ipn(self):
self.get_finished()
assert self.addon.has_purchased(self.user)
assert not self.is_contribution_good()
self.get_ipn()
assert self.addon.has_purchased(self.user)
assert self.is_contribution_good()
def test_ipn_no_result(self):
self.get_ipn()
assert self.addon.has_purchased(self.user)
assert self.is_contribution_good()
def test_ipn_then_result(self):
self.get_ipn()
assert self.addon.has_purchased(self.user)
assert self.is_contribution_good()
self.get_finished()
assert self.addon.has_purchased(self.user)
assert self.is_contribution_good()

Просмотреть файл

@ -239,16 +239,24 @@ def paypal_completed(request, post, transaction):
paypal_log.info('Completed IPN received: %s' % post['txn_id'])
data = StatsDictField().to_python(php.serialize(post))
original.update(transaction_id=post['txn_id'], uuid=None, post_data=data)
if 'mc_gross' in post:
original.update(amount=post['mc_gross'])
update = {'transaction_id': post['txn_id'],
'uuid': None, 'post_data': data}
original = Contribution.objects.get(pk=original.pk)
if original.type == amo.CONTRIB_PENDING:
# This is a purchase that has failed to hit the completed page.
# But this ok, this IPN means that it all went through.
update['type'] = amo.CONTRIB_PURCHASE
if 'mc_gross' in post:
update['amount'] = post['mc_gross']
original.update(**update)
# Send thankyou email.
try:
original.mail_thankyou(request)
except ContributionError as e:
# A failed thankyou email is not a show stopper, but is good to know.
paypal_log.error('Thankyou note email failed with error: %s' % e)
paypal_log.info('Completed successfully processed')
return http.HttpResponse('Success!')

Просмотреть файл

@ -112,7 +112,7 @@ class ContributionError(Exception):
return repr(self.value)
class Contribution(models.Model):
class Contribution(amo.models.ModelBase):
# TODO(addon): figure out what to do when we delete the add-on.
addon = models.ForeignKey('addons.Addon')
amount = DecimalCharField(max_digits=9, decimal_places=2,
@ -123,7 +123,6 @@ class Contribution(models.Model):
source = models.CharField(max_length=255, null=True)
source_locale = models.CharField(max_length=10, null=True)
created = models.DateTimeField(auto_now_add=True)
uuid = models.CharField(max_length=255, null=True)
comment = models.CharField(max_length=255)
transaction_id = models.CharField(max_length=255, null=True)
@ -298,10 +297,6 @@ class Contribution(models.Model):
self.currency or 'USD',
locale=locale)
def update(self, **kw):
"""Temp testing"""
self.__class__.objects.filter(pk=self.pk).update(**kw)
models.signals.post_save.connect(Contribution.post_save, sender=Contribution)

Просмотреть файл

@ -0,0 +1,3 @@
-- This might take a while.
-- It's allowing nulls because we don't have that information for old records.
ALTER TABLE stats_contributions ADD COLUMN modified datetime;