From f91655ce49a4a987712e84dc2c1eb6e412cbf118 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Fri, 12 Apr 2019 19:15:55 +0200 Subject: [PATCH 3/3] lingo: add an intermediate page on external redirect (#32239) --- .../lingo/templates/lingo/payment-error.html | 10 ++++ combo/apps/lingo/views.py | 47 ++++++++++++++++++- tests/test_lingo_payment.py | 15 +++++- 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 combo/apps/lingo/templates/lingo/payment-error.html diff --git a/combo/apps/lingo/templates/lingo/payment-error.html b/combo/apps/lingo/templates/lingo/payment-error.html new file mode 100644 index 0000000..120e088 --- /dev/null +++ b/combo/apps/lingo/templates/lingo/payment-error.html @@ -0,0 +1,10 @@ +{% extends "combo/page_template.html" %} +{% load i18n %} + +{% block combo-content %} +
+

+ {% trans "Continue" %} +

+
+{% endblock %} diff --git a/combo/apps/lingo/views.py b/combo/apps/lingo/views.py index 124edfc..a9bbde4 100644 --- a/combo/apps/lingo/views.py +++ b/combo/apps/lingo/views.py @@ -35,12 +35,15 @@ from django.contrib import messages from django.utils.translation import ugettext_lazy as _ from django.db.transaction import atomic from django.utils.encoding import smart_text +from django.shortcuts import render import eopayment +from combo.public.views import publish_page from combo.data.models import Page from combo.utils import check_request_signature, aes_hex_decrypt, DecryptionError from combo.profile.utils import get_user_from_name_id +from combo.utils.urls import same_origin from .models import (Regie, BasketItem, Transaction, TransactionOperation, LingoBasketCell, SelfDeclaredInvoicePayment) @@ -67,6 +70,46 @@ def lingo_check_request_signature(request): return check_request_signature(request, keys=keys) +def payment_error(request, location, *args, **kwargs): + try: + page = Page.objects.get(slug='payment-error') + template_name = None + except Page.DoesNotExist: + page = Page() + page.template_name = 'standard' + template_name = 'lingo/payment-error.html' + request.extra_context_data = {'location': location} + return publish_page(request, page, template_name=template_name) + + +class ShowMessageOnExternalRedirectMixin(object): + def dispatch(self, request, *args, **kwargs): + response = super(ShowMessageOnExternalRedirectMixin, self).dispatch(request, *args, **kwargs) + if self.is_external_redirect(request, response) and self.has_messages(request): + return payment_error(request, response['Location'], *args, **kwargs) + return response + + def has_messages(self, request): + storage = messages.get_messages(request) + if not storage: + return False + return bool(len(storage)) + + def is_external_redirect(self, request, response): + if not isinstance(response, HttpResponseRedirect): + return False + return not self.is_same_origin(request, response) + + def is_same_origin(self, request, response): + local_url = request.build_absolute_uri() + redirect_url = response['Location'] + if (not redirect_url + or (redirect_url[0] == '/' + and (len(redirect_url) == 1 or redirect_url[1] != '/'))): + return True + return same_origin(local_url, redirect_url) + + class RegiesApiView(ListView): model = Regie @@ -403,7 +446,7 @@ class PayView(PayMixin, View): return self.handle_payment(request, regie, items, remote_items, next_url, email) -class BasketItemPayView(PayMixin, View): +class BasketItemPayView(ShowMessageOnExternalRedirectMixin, PayMixin, View): def get(self, request, *args, **kwargs): next_url = request.GET.get('next_url') or '/' if not (request.user and request.user.is_authenticated()): @@ -545,7 +588,7 @@ class CallbackView(PaymentView): return super(CallbackView, self).dispatch(*args, **kwargs) -class ReturnView(PaymentView): +class ReturnView(ShowMessageOnExternalRedirectMixin, PaymentView): @csrf_exempt def dispatch(self, *args, **kwargs): return super(ReturnView, self).dispatch(*args, **kwargs) diff --git a/tests/test_lingo_payment.py b/tests/test_lingo_payment.py index ac1d4f5..c892a0b 100644 --- a/tests/test_lingo_payment.py +++ b/tests/test_lingo_payment.py @@ -317,6 +317,7 @@ def test_pay_single_basket_item(app, key, regie, user, john_doe): assert 'No item payment allowed for anonymous users.' in resp.text login(app, username='john.doe', password='john.doe') + resp = app.get(payment_url, status=403) assert 'Wrong item: payment not allowed.' in resp.text @@ -331,8 +332,17 @@ def test_pay_single_basket_item(app, key, regie, user, john_doe): regie.extra_fees_ws_url = '' regie.save() + regie.payment_min_amount = 100 + regie.save() + resp = app.get(payment_url, params={'next_url': 'http://example.net/form/id/'}) + assert 'Continue' in resp.text + assert 'http://example.net/form/id/' in resp.text + regie.payment_min_amount = 0 + regie.save() + + resp = app.get(payment_url, params={'next_url': 'http://example.net/form/id/'}) # make sure the redirection is done to the payment backend assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/') qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query) @@ -347,7 +357,10 @@ def test_pay_single_basket_item(app, key, regie, user, john_doe): # check that item is paid assert BasketItem.objects.filter(regie=regie, amount=amount, payment_date__isnull=False).exists() # check that user is redirected to the next_url passed previously - assert resp.location == 'http://example.net/form/id/' + assert 'Continue' in resp.text + assert 'Your payment has been succesfully registered' in resp.text + assert 'http://example.net/form/id/' in resp.text + def test_pay_multiple_regies(app, key, regie, user): test_add_amount_to_basket(app, key, regie, user) -- 2.20.1