diff --git a/apps/addons/tests/test_views.py b/apps/addons/tests/test_views.py index 323cd860cb0..678d89509f6 100644 --- a/apps/addons/tests/test_views.py +++ b/apps/addons/tests/test_views.py @@ -18,6 +18,8 @@ from users.models import UserProfile from translations.query import order_by_translation +import re + class TestHomepage(amo.test_utils.ExtraSetup, test_utils.TestCase): fixtures = ['base/fixtures', 'base/global-stats', 'base/featured'] @@ -116,6 +118,105 @@ def test_title(self): eq_(title[:37], 'Thank you for installing Gmail S/MIME') +class TestContribute(test_utils.TestCase): + fixtures = ['base/fixtures'] + + def test_invalid_is_404(self): + """we get a 404 in case of invalid addon id""" + response = self.client.get(reverse('addons.contribute', args=[1])) + eq_(response.status_code, 404) + + def test_redirect_params_no_type(self): + """Test that we have the required ppal params when no type is given""" + response = self.client.get(reverse('addons.contribute', + args=[592]), follow=True) + redirect_url = response.redirect_chain[0][0] + required_params = ['bn', 'business', 'charset', 'cmd', 'item_name', + 'no_shipping', 'notify_url', + 'return', 'item_number'] + for param in required_params: + assert(redirect_url.find(param + '=') > -1), \ + "param [%s] not found" % param + + def test_redirect_params_common(self): + """Test for the common values that do not change based on type, + Check that they have expected values""" + response = self.client.get(reverse('addons.contribute', + args=[592]), follow=True) + redirect_url = response.redirect_chain[0][0] + assert(re.search('business=([^&]+)', redirect_url)) + common_params = {'bn': r'-AddonID592', + 'business': r'gmailsmime%40seantek.com', + 'charset': r'utf-8', + 'cmd': r'_donations', + 'item_name': r'Contribution\+for\+Gmail\+S%2FMIME', + 'no_shipping': r'1', + 'notify_url': r'%2Fservices%2Fpaypal', + 'return': r'x', + 'item_number': r'[a-f\d]{32}'} + + message = 'param [%s] unexpected value: given [%s], ' \ + + 'expected pattern [%s]' + for param, value_pattern in common_params.items(): + match = re.search(r'%s=([^&]+)' % param, redirect_url) + assert(match and re.search(value_pattern, match.group(1))), \ + message % (param, match.group(1), value_pattern) + + def test_redirect_params_type_suggested(self): + """Test that we have the required ppal param when type + suggested is given""" + request_params = '?type=suggested' + response = self.client.get(reverse('addons.contribute', + args=[592]) + request_params, + follow=True) + redirect_url = response.redirect_chain[0][0] + required_params = ['amount', 'bn', 'business', 'charset', + 'cmd', 'item_name', 'no_shipping', 'notify_url', + 'return', 'item_number'] + for param in required_params: + assert(redirect_url.find(param + '=') > -1), \ + "param [%s] not found" % param + + def test_redirect_params_type_onetime(self): + """Test that we have the required ppal param when + type onetime is given""" + request_params = '?type=onetime&onetime-amount=42' + response = self.client.get(reverse('addons.contribute', + args=[592]) + request_params, + follow=True) + redirect_url = response.redirect_chain[0][0] + required_params = ['amount', 'bn', 'business', 'charset', 'cmd', + 'item_name', 'no_shipping', 'notify_url', + 'return', 'item_number'] + for param in required_params: + assert(redirect_url.find(param + '=') > -1), \ + "param [%s] not found" % param + + assert(redirect_url.find('amount=42') > -1) + + def test_redirect_params_type_monthly(self): + """Test that we have the required ppal param when + type monthly is given""" + request_params = '?type=monthly&monthly-amount=42' + response = self.client.get(reverse('addons.contribute', + args=[592]) + request_params, + follow=True) + redirect_url = response.redirect_chain[0][0] + required_params = ['no_note', 'a3', 't3', 'p3', 'bn', 'business', + 'charset', 'cmd', 'item_name', 'no_shipping', + 'notify_url', 'return', 'item_number'] + for param in required_params: + assert(redirect_url.find(param + '=') > -1), \ + "param [%s] not found" % param + + assert(redirect_url.find('cmd=_xclick-subscriptions') > -1), \ + 'param a3 was not 42' + assert(redirect_url.find('p3=12') > -1), 'param p3 was not 12' + assert(redirect_url.find('t3=M') > -1), 'param t3 was not M' + assert(redirect_url.find('a3=42') > -1), 'param a3 was not 42' + assert(redirect_url.find('no_note=1') > -1), 'param no_note was not 1' + + class TestDeveloperPages(test_utils.TestCase): fixtures = ['base/fixtures', 'addons/eula+contrib-addon'] diff --git a/apps/addons/urls.py b/apps/addons/urls.py index da23e3cc287..770fc7d6957 100644 --- a/apps/addons/urls.py +++ b/apps/addons/urls.py @@ -15,9 +15,10 @@ {'page': 'roadblock'}, name='addons.roadblock'), url('^contribute/installed/', views.developers, {'page': 'installed'}, name='addons.installed'), - ('^contribute/thanks$', lambda r, addon_id: redirect('addons.detail', - addon_id)), - + url('^contribute/thanks', lambda r, + addon_id: redirect('addons.detail', addon_id), + name='addons.thanks'), + url('^contribute/', views.contribute, name='addons.contribute'), ('^about$', lambda r, addon_id: redirect('addons.installed', addon_id, permanent=True)), diff --git a/apps/addons/views.py b/apps/addons/views.py index 86a0d843720..8525aba76bd 100644 --- a/apps/addons/views.py +++ b/apps/addons/views.py @@ -13,11 +13,15 @@ from amo import urlresolvers from amo.urlresolvers import reverse from bandwagon.models import Collection, CollectionFeature, CollectionPromo -from stats.models import GlobalStat +from stats.models import GlobalStat, Contribution from tags.models import Tag from translations.query import order_by_translation from .models import Addon +import hashlib +import urllib +import uuid + def author_addon_clicked(f): """Decorator redirecting clicks on "Other add-ons by author".""" @@ -397,3 +401,82 @@ def developers(request, addon_id, page): return jingo.render(request, 'addons/developers.html', {'addon': addon, 'author_addons': author_addons, 'page': page}) + + +def contribute(request, addon_id): + + addon = get_object_or_404(Addon.objects.valid(), id=addon_id) + + contrib_type = request.GET.get('type', '') + is_suggested = contrib_type == 'suggested' + source = request.GET.get('source', '') + comment = request.GET.get('comment', '') + + amount = { + 'suggested': addon.suggested_amount, + 'onetime': request.GET.get('onetime-amount', ''), + 'monthly': request.GET.get('monthly-amount', '')}.get(contrib_type, '') + + contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() + + contrib = Contribution(addon_id=addon.id, + amount=amount, + source=source, + source_locale=request.LANG, + annoying=addon.annoying, + uuid=str(contribution_uuid), + is_suggested=is_suggested, + suggested_amount=addon.suggested_amount, + comment=comment) + contrib.save() + + return_url = "%s?%s" % (reverse('addons.thanks', args=[addon.id]), + urllib.urlencode({'uuid': contribution_uuid})) + + redirect_url_params = contribute_url_params( + addon.paypal_id, + addon.id, + 'Contribution for %s' % addon.name, + return_url, + amount, + contribution_uuid, + contrib_type == 'monthly', + comment) + + return http.HttpResponseRedirect(settings.PAYPAL_CGI_URL + + '?' + + urllib.urlencode(redirect_url_params)) + + +def contribute_url_params(business, addon_id, item_name, return_url, + amount='', item_number='', + monthly=False, comment=''): + + """Get all the data elements that will be URL params + on the Paypal redirect URL""" + + data = {'business': business, + 'item_name': item_name, + 'item_number': item_number, + 'bn': settings.PAYPAL_BN + '-AddonID' + str(addon_id), + 'no_shipping': '1', + 'return': return_url, + 'charset': 'utf-8', + 'notify_url': reverse('amo.paypal')} + + if (not monthly): + data['cmd'] = '_donations' + if (amount): + data['amount'] = amount + else: + data.update({ + 'cmd': '_xclick-subscriptions', + 'p3': '12', # duration: for 12 months + 't3': 'M', # time unit, 'M' for month + 'a3': amount, # recurring contribution amount + 'no_note': '1'}) # required: no "note" text field for user + + if (comment): + data['custom'] = comment + + return data diff --git a/apps/amo/tests/test_redirects.py b/apps/amo/tests/test_redirects.py index 0c3ebc3ee2f..c497e582a1f 100644 --- a/apps/amo/tests/test_redirects.py +++ b/apps/amo/tests/test_redirects.py @@ -25,9 +25,18 @@ def test_persona(self): assert r.redirect_chain[-1][0].endswith('/en-US/firefox/addon/4/') def test_contribute_installed(self): - """`/addon/\d+/about` should go to `/addon/\d+/contribute/installed`.""" + """`/addon/\d+/about` should go to + `/addon/\d+/contribute/installed`.""" r = self.client.get(u'addon/5326/about', follow=True) - assert r.redirect_chain[-1][0].endswith('/en-US/firefox/addon/5326/contribute/installed/') + redirect = r.redirect_chain[-1][0] + assert redirect.endswith( + '/en-US/firefox/addon/5326/contribute/installed/') + + def test_contribute(self): + """`/addons/contribute/$id` should go to `/addon/$id/contribute`.""" + response = self.client.get(u'addon/5326/contribute', follow=True) + redirect = response.redirect_chain[-1][0] + assert redirect.endswith('/en-US/firefox/addon/5326/contribute/') def test_utf8(self): """Without proper unicode handling this will fail.""" diff --git a/settings.py b/settings.py index 7fa50a5f0dc..b2bc4e8722c 100644 --- a/settings.py +++ b/settings.py @@ -448,6 +448,7 @@ def JINJA_CONFIG(): # PayPal Settings PAYPAL_CGI_URL = 'https://www.paypal.com/cgi-bin/webscr' +PAYPAL_BN = '' # Email settings DEFAULT_FROM_EMAIL = "Mozilla Add-ons " diff --git a/urls.py b/urls.py index 92b6335f625..35fd4170475 100644 --- a/urls.py +++ b/urls.py @@ -77,6 +77,9 @@ ('^search-engines.*$', lambda r: redirect('browse.search-tools', permanent=True)), + ('^addons/contribute/(\d+)/?$', + lambda r, id: redirect('addons.contribute', id, permanent=True)), + # Firefox Cup page, /firefoxcup ('^firefoxcup/', include('firefoxcup.urls')) )