cope with different ipn, purchase order (bug 704971)
This commit is contained in:
Родитель
64faf56744
Коммит
e8412b4d5d
|
@ -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;
|
Загрузка…
Ссылка в новой задаче