Skip to content
This repository has been archived by the owner on Mar 15, 2018. It is now read-only.

Commit

Permalink
add in payments page (bug 707366)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy McKay committed Dec 15, 2011
1 parent 7a8b3c9 commit 41583b5
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 3 deletions.
6 changes: 6 additions & 0 deletions apps/amo/context_processors.py
Expand Up @@ -7,6 +7,7 @@
import waffle

import amo
from amo.helpers import loc
from amo.urlresolvers import reverse
from access import acl
from cake.urlresolvers import remora_url
Expand Down Expand Up @@ -62,6 +63,11 @@ def global_settings(request):
if waffle.switch_is_active('marketplace'):
account_links.append({'text': _('My Purchases'),
'href': reverse('users.purchases')})

if waffle.flag_is_active(request, 'allow-pre-auth'):
account_links.append({'text': loc('Payment Profile'),
'href': reverse('users.payments')})

account_links.append({
'text': _('Log out'),
'href': remora_url('/users/logout?to=' + urlquote(request.path)),
Expand Down
14 changes: 13 additions & 1 deletion apps/market/tests/test_models.py
Expand Up @@ -9,7 +9,7 @@
import amo
import amo.tests
from amo.urlresolvers import reverse
from market.models import AddonPremium, Price
from market.models import AddonPremium, PreApprovalUser, Price
from stats.models import Contribution
from users.models import UserProfile

Expand Down Expand Up @@ -190,3 +190,15 @@ def test_user_refunded(self):
self.addon.addonpurchase_set.create(user=self.user,
type=amo.CONTRIB_REFUND)
eq_(list(self.user.purchase_ids()), [])


class TestUserPreApproval(amo.tests.TestCase):
fixtures = ['base/users']

def setUp(self):
self.user = UserProfile.objects.get(pk=999)

def test_get_preapproval(self):
eq_(self.user.get_preapproval(), None)
pre = PreApprovalUser.objects.create(user=self.user)
eq_(self.user.get_preapproval(), pre)
29 changes: 29 additions & 0 deletions apps/paypal/__init__.py
Expand Up @@ -234,6 +234,35 @@ def get_permissions_token(request_token, verification_code):
return r['token']


def get_preapproval_key(data):
"""
Get a preapproval key from PayPal. If this passes, you get a key that
you can use in a redirect to PayPal.
"""
paypal_data = {
'currencyCode': 'USD',
'startingDate': data['startDate'].strftime('%Y-%m-%d'),
'endingDate': data['endDate'].strftime('%Y-%m-%d'),
'maxTotalAmountOfAllPayments': str(data.get('maxAmount', '2000')),
'returnUrl': absolutify(reverse(data['pattern'], args=['complete'])),
'cancelUrl': absolutify(reverse(data['pattern'], args=['cancel'])),
}
with statsd.timer('paypal.preapproval.token'):
response = _call(settings.PAYPAL_PAY_URL + 'Preapproval', paypal_data,
ip=data.get('ip'))

return response


def get_preapproval_url(key):
"""
Returns the URL that you need to bounce user to in order to set up
pre-approval.
"""
return urlparams(settings.PAYPAL_CGI_URL, cmd='_ap-preapproval',
preapprovalkey=key)


def _call(url, paypal_data, ip=None):
request = urllib2.Request(url)

Expand Down
56 changes: 56 additions & 0 deletions apps/paypal/tests/test.py
@@ -1,4 +1,5 @@
from cStringIO import StringIO
from datetime import datetime, timedelta
from decimal import Decimal
import urlparse

Expand All @@ -9,6 +10,8 @@
import time

from addons.models import Addon
from amo.helpers import absolutify
from amo.urlresolvers import reverse
import amo.tests
import paypal

Expand Down Expand Up @@ -226,3 +229,56 @@ def test_refundFailure(self, opener):
opener.return_value = StringIO(error_refund_string)
with self.assertRaises(paypal.PaypalError):
paypal.refund('fake-paykey')


# TODO: would be nice to see if we could get some more errors out of PayPal
# but it looks like anything else just raises an error.
good_preapproval_string = {
'responseEnvelope.build': '2279004',
'responseEnvelope.ack': 'Success',
'responseEnvelope.timestamp': '2011-12-13T16:11:34.567-08:00',
'responseEnvelope.correlationId': '56aaa9b53b12f',
'preapprovalKey': 'PA-2L635945UC9045439'
}


@mock.patch('paypal._call')
class TestPreApproval(amo.tests.TestCase):

def get_data(self):
return {'startDate': datetime.today(),
'endDate': datetime.today() + timedelta(days=365),
'pattern': 'users.payments',
}

def test_preapproval_works(self, _call):
_call.return_value = good_preapproval_string
eq_(paypal.get_preapproval_key(self.get_data()),
good_preapproval_string)

def test_preapproval_no_data(self, _call):
self.assertRaises(KeyError, paypal.get_preapproval_key, {})

def test_preapproval_amount(self, _call):
_call.return_value = good_preapproval_string
data = self.get_data()
paypal.get_preapproval_key(data)
eq_(_call.call_args[0][1]['maxTotalAmountOfAllPayments'], '2000')

data['maxAmount'] = 1000
paypal.get_preapproval_key(data)
eq_(_call.call_args[0][1]['maxTotalAmountOfAllPayments'], '1000')

def test_preapproval_patterns(self, _call):
_call.return_value = good_preapproval_string
data = self.get_data()
paypal.get_preapproval_key(data)
eq_(_call.call_args[0][1]['cancelUrl'],
absolutify(reverse(data['pattern'], args=['cancel'])))
eq_(_call.call_args[0][1]['returnUrl'],
absolutify(reverse(data['pattern'], args=['complete'])))

def test_preapproval_url(self, _call):
url = paypal.get_preapproval_url('foo')
assert (url.startswith(settings.PAYPAL_CGI_URL) and
url.endswith('foo')), 'Incorrect URL returned'
11 changes: 11 additions & 0 deletions apps/users/models.py
Expand Up @@ -10,6 +10,7 @@
from django.conf import settings
from django.contrib.auth.models import User as DjangoUser
from django.core import validators
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.template import Context, loader
from django.utils.encoding import smart_str, smart_unicode
Expand Down Expand Up @@ -357,6 +358,16 @@ def needs_completion(self):
"""
return not self.username or not self.display_name

def get_preapproval(self):
"""
Returns the pre approval object for this user, or None if it does
not exist
"""
try:
return self.preapprovaluser
except ObjectDoesNotExist:
pass


@dispatch.receiver(models.signals.post_save, sender=UserProfile,
dispatch_uid='user.post_save')
Expand Down
5 changes: 5 additions & 0 deletions apps/users/templates/users/includes/navigation.html
Expand Up @@ -12,6 +12,11 @@
{% if waffle.switch('marketplace') %}
{% do links.append((_('My Purchases'), url('users.purchases'))) %}
{% endif %}

{% if waffle.flag('allow-pre-auth') %}
{% do links.append((loc('Payment Profile'), url('users.payments'))) %}
{% endif %}

<div id="secondary-nav" class="secondary">
<h2>{{ _('My Account') }}</h2>
<ul>
Expand Down
27 changes: 27 additions & 0 deletions apps/users/templates/users/payments.html
@@ -0,0 +1,27 @@
{% extends "impala/base_shared.html" %}
{% block title %}{{ page_title('Payment Profile') }}{% endblock %}

{% block content %}
{% include 'users/includes/navigation.html' %}
<section class="primary">
{{ impala_breadcrumbs([(None, loc('Payment Profile'))]) }}
<h1>{{ loc('Payment Profile') }}</h1>
{% if preapproval.paypal_key %}
<form id="preapproval" method="post" action="{{ url('users.payments', 'remove') }}">

This comment has been minimized.

Copy link
@cvan

cvan Dec 15, 2011

Contributor

where's the island?

This comment has been minimized.

Copy link
@andymckay

andymckay Dec 16, 2011

Contributor

It looked wierd and wrong to me. Perhaps I've just be looking at it for too long as a continent.

This comment has been minimized.

Copy link
@cvan

cvan Dec 16, 2011

Contributor

but we have islands on every page! the page will hopefully get a makeover sooner than later anyway. I just hope we don't drown in the marketplace!

{{ csrf() }}
<p>{{ loc('Your pre-approval is set up.') }}</p>
<p><button>{{ loc('Remove pre-approval') }}</button></p>
</form>
{% else %}
<form id="preapproval" method="post" action="{{ url('users.payments.preapproval') }}">
{{ csrf() }}
<p>{{ loc('Setting up PayPal pre-approval allows you to buy
apps quickly on this site. They also allow you to use
in-app purchases that go through this site.') }}</p>
<p>{{ loc('You can cancel pre-approval at any time by returning
to this page.') }}</p>
<p><button>{{ loc('Set up pre-approval') }}</button></p>
</form>
{% endif %}
</section>
{% endblock %}
67 changes: 66 additions & 1 deletion apps/users/tests/test_views.py
Expand Up @@ -29,7 +29,8 @@
from amo.urlresolvers import reverse
from bandwagon.models import Collection, CollectionAddon, CollectionWatcher
from devhub.models import ActivityLog
from market.models import Price
from market.models import PreApprovalUser, Price
import paypal
from reviews.models import Review
from stats.models import Contribution
from users.models import BlacklistedPassword, UserProfile, UserNotification
Expand Down Expand Up @@ -1620,3 +1621,67 @@ def test_chargeback(self):
@amo.tests.mobile_test
def test_mobile_chargeback(self):
self._test_chargeback()


# Turning on the allow-pre-auth flag.
@patch.object(waffle, 'flag_is_active', lambda x, y: True)
class TestPreapproval(amo.tests.TestCase):
fixtures = ['base/users.json']

def setUp(self):
self.user = UserProfile.objects.get(pk=999)
assert self.client.login(username=self.user.email,
password='password')

def get_url(self, status=None):
if status:
return reverse('users.payments', args=[status])
return reverse('users.payments')

def test_preapproval_denied(self):
self.client.logout()
eq_(self.client.get(self.get_url()).status_code, 302)

def test_preapproval_allowed(self):
eq_(self.client.get(self.get_url()).status_code, 200)

def test_preapproval_setup(self):
doc = pq(self.client.get(self.get_url()).content)
eq_(doc('#preapproval').attr('action'),
reverse('users.payments.preapproval'))

@patch('paypal.get_preapproval_key')
def test_fake_preapproval(self, get_preapproval_key):
get_preapproval_key.return_value = {'preapprovalKey': 'xyz'}
res = self.client.post(reverse('users.payments.preapproval'))
ssn = self.client.session['setup-preapproval']
eq_(ssn['key'], 'xyz')
# Checking it's in the future at least 353 just so this test will work
# on leap years at 11:59pm.
assert (ssn['expiry'] - datetime.today()).days > 353
eq_(res['Location'], paypal.get_preapproval_url('xyz'))

def test_preapproval_complete(self):
ssn = self.client.session
ssn['setup-preapproval'] = {'key': 'xyz'}
ssn.save()
res = self.client.post(self.get_url('complete'))
eq_(res.status_code, 200)
eq_(self.user.preapprovaluser.paypal_key, 'xyz')

def test_preapproval_cancel(self):
PreApprovalUser.objects.create(user=self.user, paypal_key='xyz')
res = self.client.post(self.get_url('cancel'))
eq_(res.status_code, 200)
eq_(self.user.preapprovaluser.paypal_key, 'xyz')
eq_(pq('#preapproval').attr('action'),
self.get_url('remove'))

def test_preapproval_remove(self):
PreApprovalUser.objects.create(user=self.user, paypal_key='xyz')
res = self.client.post(self.get_url('remove'))
eq_(res.status_code, 200)
eq_(self.user.preapprovaluser.paypal_key, '')
eq_(pq(res.content)('form').eq(1).attr('action'),
reverse('users.payments.preapproval'))

6 changes: 5 additions & 1 deletion apps/users/urls.py
Expand Up @@ -62,7 +62,11 @@
name='users.purchases.receipt'),
url(r'support/(?P<contribution_id>\d+)(?:/(?P<step>[\w-]+))?$',
views.SupportWizard.as_view(),
name='users.support')
name='users.support'),
url(r'payments(?:/(?P<status>cancel|complete|remove))?$', views.payments,
name='users.payments'),
url(r'payments/preapproval$', views.preapproval,
name='users.payments.preapproval'),
)

urlpatterns = patterns('',
Expand Down
61 changes: 61 additions & 0 deletions apps/users/views.py
Expand Up @@ -16,6 +16,7 @@
from django.contrib.auth.tokens import default_token_generator

from django_browserid.auth import BrowserIDBackend
from waffle.decorators import waffle_flag

import commonware.log
import jingo
Expand All @@ -41,6 +42,7 @@
from addons.decorators import addon_view_factory
from access import acl
from bandwagon.models import Collection
from market.models import PreApprovalUser
import paypal
from stats.models import Contribution
from translations.query import order_by_translation
Expand Down Expand Up @@ -930,3 +932,62 @@ def render(self, request, template, context):
return jingo.render(request, template, context)
context['content'] = template
return jingo.render(request, wrapper, context)


@login_required
@waffle_flag('allow-pre-auth')
def payments(request, status=None):
# Note this is not post required, because PayPal does not reply with a
# POST but a GET, that's a sad face.
if status:
pre, created = (PreApprovalUser.objects
.safer_get_or_create(user=request.amo_user))

if status == 'complete':
# The user has completed the setup at PayPal and bounced back.
messages.success(request, loc('Pre-approval setup.'))
paypal_log.info(u'Preapproval key created for user: %s'
% request.amo_user)
data = request.session.get('setup-preapproval', {})
pre.update(paypal_key=data.get('key'),
paypal_expiry=data.get('expiry'))
del request.session['setup-preapproval']

elif status == 'cancel':
# The user has chosen to cancel out of PayPal. Nothing really
# to do here, PayPal just bounces to this page.
messages.success(request, loc('Pre-approval changes cancelled.'))

elif status == 'remove':
# The user has an pre approval key set and chooses to remove it
if pre.paypal_key:
pre.update(paypal_key='')
messages.success(request, loc('Pre-approval removed.'))
paypal_log.info(u'Preapproval key removed for user: %s'
% request.amo_user)

context = {'preapproval': request.amo_user.get_preapproval()}
return jingo.render(request, 'users/payments.html', context)


@post_required
@login_required
@waffle_flag('allow-pre-auth')
def preapproval(request):
today = datetime.today()
data = {'startDate': today,
'endDate': today + timedelta(days=365),
'pattern': 'users.payments',
}
try:
result = paypal.get_preapproval_key(data)
except paypal.PaypalError, e:
paypal_log.error(u'Preapproval key: %s' % e, exc_info=True)
raise

paypal_log.info(u'Got preapproval key for user: %s' % request.amo_user.pk)
request.session['setup-preapproval'] = {
'key': result['preapprovalKey'],
'expiry': data['endDate'],
}
return redirect(paypal.get_preapproval_url(result['preapprovalKey']))

0 comments on commit 41583b5

Please sign in to comment.