diff --git a/.drone.yml b/.drone.yml index 97a9807..ef377c8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,12 +7,14 @@ pipeline: image: python:3.5 environment: + - PYTHONUSERBASE=/drone/src/cache/packages - POSTGRES_USER=mhackspace - USE_DOCKER=yes - DJANGO_SETTINGS_MODULE=config.settings.test commands: - cp -n env.example .env - - pip install -r ./requirements/test.txt + - mkdir -p ./cache/packages ./cache/pip + - pip install --user --cache-dir ./cache/pip -r ./requirements/test.txt - python manage.py test mhackspace --verbosity 2 publish: diff --git a/.gitignore b/.gitignore index 8d4e2a6..7cf220f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules src .env staticfiles/* +cache/ diff --git a/config/settings/stage.py b/config/settings/stage.py index 0e29c12..031391a 100644 --- a/config/settings/stage.py +++ b/config/settings/stage.py @@ -200,7 +200,7 @@ LOGGING = { }, 'loggers': { 'django.request': { - 'handlers': ['mail_admins'], + 'handlers': ['mail_admins', 'logfile'], 'level': 'ERROR', 'propagate': True }, diff --git a/mhackspace/subscriptions/payments.py b/mhackspace/subscriptions/payments.py index ed1f24a..e6ded1c 100644 --- a/mhackspace/subscriptions/payments.py +++ b/mhackspace/subscriptions/payments.py @@ -118,12 +118,23 @@ class gocardless_provider: def create_subscription(self, amount, name, redirect_success, redirect_failure, interval_unit='month', interval_length='1'): return gocardless.client.new_subscription_url( - amount=float(amount), - interval_length=interval_length, + amount=float(amount), + interval_length=interval_length, interval_unit=interval_unit, name=name, redirect_uri=redirect_success) + def confirm_subscription(self, provider_response): + response = gocardless.client.confirm_resource(provider_response) + subscription = gocardless.client.subscription(provider_response.get('resource_id')) + user = subscription.user() + return { + 'amount': subscription.amount, + 'email': user.email, + 'start_date': subscription.created_at, + 'reference': subscription.id, + 'success': response.success + } class braintree_provider: form_remote = False @@ -154,6 +165,17 @@ class braintree_provider: #'name': name }) + def confirm_subscription(self, args): + if self.provider == 'gocardless': + response = gocardless.client.confirm_resource(args) + subscription = gocardless.client.subscription(args.get('resource_id')) + return { + 'amount': subscription.amount, + 'start_date': subscription.created_at, + 'reference': subscription.id + } + + def fetch_subscriptions(self): for paying_member in braintree.Subscription.search(braintree.SubscriptionSearch.status == braintree.Subscription.Status.Active): user=paying_member.user() @@ -246,7 +268,7 @@ class payment: 'amount': paying_member.amount} - def subscribe_confirm(self, args): + def confirm_subscription(self, args): if self.provider == 'gocardless': response = gocardless.client.confirm_resource(args) subscription = gocardless.client.subscription(args.get('resource_id')) diff --git a/mhackspace/subscriptions/tests/test_views.py b/mhackspace/subscriptions/tests/test_views.py new file mode 100644 index 0000000..d0d89fb --- /dev/null +++ b/mhackspace/subscriptions/tests/test_views.py @@ -0,0 +1,83 @@ +from django.contrib.messages.storage.fallback import FallbackStorage +# from django.contrib.auth.models import Group +from django.test import RequestFactory +from django.core.urlresolvers import reverse +from test_plus.test import TestCase +from mock import patch, Mock +from mhackspace.users.models import Membership +from mhackspace.users.models import Membership + +from ..views import ( + MembershipCancelView, + MembershipJoinView, + MembershipJoinSuccessView, + MembershipJoinFailureView +) + + +class BaseUserTestCase(TestCase): + fixtures = ['groups'] + + def setUp(self): + self.user = self.make_user() + self.factory = RequestFactory() + + +class TestSubscriptionSuccessRedirectView(BaseUserTestCase): + @patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True) + @patch('mhackspace.subscriptions.views.select_provider', autospec=True) + def test_success_redirect_url(self, mock_subscription, mock_provider): + mock_subscription.return_value = mock_provider + mock_provider.confirm_subscription.return_value = { + 'amount': 20.00, + 'start_date': '2017-01-01T17:07:09Z', + 'reference': 'MH0001', + 'email': 'user@test.com', + 'success': True + } + + request = self.factory.post( + reverse('join_hackspace_success', kwargs={'provider': 'gocardless'}), + {'signature': 'test_signature'} + ) + + setattr(request, 'session', 'session') + messages = FallbackStorage(request) + setattr(request, '_messages', messages) + request.user = self.user + + view = MembershipJoinSuccessView() + view.request = request + self.assertEqual( + view.get_redirect_url(provider ='gocardless'), + reverse('users:detail', kwargs={'username': self.user.username}) + ) + + members = Membership.objects.all() + self.assertEqual(members.count(), 1) + + @patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True) + def test_failure_redirect_url(self, mock_obj): + # Instantiate the view directly. Never do this outside a test! + # Generate a fake request + request = self.factory.post( + reverse('join_hackspace_failure', kwargs={'provider': 'gocardless'}), + data={'signature': 'test_signature'} + ) + + + setattr(request, 'session', 'session') + messages = FallbackStorage(request) + setattr(request, '_messages', messages) + request.user = self.user + + view = MembershipJoinFailureView() + view.request = request + + self.assertEqual( + view.get_redirect_url(provider='gocardless'), + reverse('users:detail', kwargs={'username': self.user.username}) + ) + + members = Membership.objects.all() + self.assertEqual(len(members), 0) diff --git a/mhackspace/subscriptions/views.py b/mhackspace/subscriptions/views.py index 19638f1..26c2476 100644 --- a/mhackspace/subscriptions/views.py +++ b/mhackspace/subscriptions/views.py @@ -33,6 +33,7 @@ class MembershipCancelView(LoginRequiredMixin, RedirectView): member.status = MEMBERSHIP_CANCELLED member.save() + # remove user from group on success group = Group.objects.get(name='members') self.request.user.groups.remove(group) @@ -40,7 +41,6 @@ class MembershipCancelView(LoginRequiredMixin, RedirectView): self.request, messages.SUCCESS, 'Your membership has now been cancelled') - kwargs['username'] = self.request.user.get_username() return super(MembershipCancelView, self).get_redirect_url(*args, **kwargs) @@ -79,7 +79,33 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView): pattern_name = 'users:detail' def get_redirect_url(self, *args, **kwargs): + payment_provider = 'gocardless' + provider = select_provider(payment_provider) + result = provider.confirm_subscription( + provider_response=kwargs + ) + + #if something went wrong return to profile with an error + if result.get('success') is False: + messages.add_message( + self.request, + messages.ERROR, + 'Failure something went wrong activating your membership please contact us.') + return super(MembershipJoinSuccessView, self).get_redirect_url(*args, **kwargs) + del(kwargs['provider']) + try: + member = Membership.objects.get(user=self.request.user) + except Membership.DoesNotExist: + member = Membership() + + member.user = self.request.user + member.email = result.get('email') + member.reference = result.get('reference') + member.payment = result.get('amount') + member.date = result.get('start_date') + member.status = Membership.lookup_status(name=result.get('status')) + member.save() messages.add_message( self.request, messages.SUCCESS, @@ -89,6 +115,10 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView): # add user to group on success group = Group.objects.get(name='members') self.request.user.groups.add(group) + messages.add_message( + self.request, + messages.SUCCESS, + 'Success your membership should now be active') return super(MembershipJoinSuccessView, self).get_redirect_url(*args, **kwargs) @@ -101,6 +131,6 @@ class MembershipJoinFailureView(LoginRequiredMixin, RedirectView): messages.add_message( self.request, messages.ERROR, - 'Failed to sign up something went wrong with your payment, please contact us at %s' % EMAIL_SUPPORT) + 'Failed to sign up something went wrong with your payment, please contact us at %s' % settings.EMAIL_SUPPORT) kwargs['username'] = self.request.user.get_username() return super(MembershipJoinFailureView, self).get_redirect_url(*args, **kwargs)