lingo: move email extraction to payment views (#47513)

There is too much non-linear logic around emails in handle_payment(),
it's better to locate this logic in the calling views where situations
are more constrained.
This commit is contained in:
Benjamin Dauvergne 2020-10-12 15:28:30 +02:00
parent 4b88540880
commit 902a52278e
2 changed files with 223 additions and 8 deletions

View File

@ -382,7 +382,6 @@ class PayMixin(object):
transaction = Transaction()
if user:
transaction.user = user
email = user.email
firstname = user.first_name
lastname = user.last_name
else:
@ -407,8 +406,7 @@ class PayMixin(object):
'amount': item.amount,
'reference_id': item.reference_id,
})
if not email and item.email:
kwargs['email'] = item.email
if items:
capture_date = items[0].capture_date
if capture_date:
@ -504,10 +502,15 @@ class PayView(PayMixin, View):
_(u'Invalid grouping for basket items: different capture dates.'))
return HttpResponseRedirect(next_url)
if not user and not request.POST.get('email'):
messages.warning(request, _(u'You must give an email address.'))
return HttpResponseRedirect(request.POST.get('item_url'))
email = request.POST.get('email')
if user:
email = user.email
else:
# user is not authenticated, it comes from ItemCell where an email
# can be given in the payment form.
if not request.POST.get('email'):
messages.warning(request, _(u'You must give an email address.'))
return HttpResponseRedirect(request.POST.get('item_url'))
email = request.POST.get('email')
# XXX: mark basket items as being processed (?)
return self.handle_payment(request, regie, items, remote_items, next_url, email)
@ -527,7 +530,6 @@ class BasketItemPayView(PayMixin, View):
def get(self, request, *args, **kwargs):
next_url = request.GET.get('next_url')
email = request.GET.get('email', '')
firstname = request.GET.get('firstname', '')
lastname = request.GET.get('lastname', '')
@ -542,6 +544,16 @@ class BasketItemPayView(PayMixin, View):
if regie.extra_fees_ws_url:
return HttpResponseForbidden(_('No item payment allowed as extra fees set.'))
# Get an email from request or the item
if request.user.is_authenticated:
email = request.user.email
elif request.GET.get('email'):
email = request.GET.get('email', '')
elif item.user and item.user.email:
email = item.user.email
else:
email = item.email
if not next_url:
next_url = item.source_url

View File

@ -12,6 +12,7 @@ from mellon.models import UserSAMLIdentifier
from django.apps import apps
from django.contrib.auth.models import User
from django.http.request import QueryDict
from django.urls import reverse
from django.core.wsgi import get_wsgi_application
from django.conf import settings
@ -30,6 +31,8 @@ from combo.apps.lingo.models import (
from combo.utils import aes_hex_decrypt, sign_url
from combo.apps.lingo.views import signing_loads, signing_dumps
import httmock
from .test_manager import login
pytestmark = pytest.mark.django_db
@ -1648,3 +1651,203 @@ def test_payfip_ws_kwargs_can_pay_only_one_basket_item(payment_request, app, bas
assert resp.location == 'https://payfip/'
assert payment_request.call_args[1]['refdet'] == 'F20201030'
assert payment_request.call_args[1]['exer'] == '2020'
@pytest.fixture
def remote_invoices_httmock():
invoices = []
invoice = {}
netloc = 'remote.regie.example.com'
@httmock.urlmatch(netloc=netloc, path='^/invoice/')
def invoice_mock(url, request):
return json.dumps({'err': 0, 'data': invoice})
@httmock.urlmatch(netloc=netloc, path='^/invoices/')
def invoices_mock(url, request):
return json.dumps({'err': 0, 'data': invoices})
context_manager = httmock.HTTMock(invoices_mock, invoice_mock)
context_manager.url = 'https://%s/' % netloc
context_manager.invoices = invoices
context_manager.invoice = invoice
return context_manager
def test_email_from_basket(app, regie, remote_invoices_httmock):
def parse_qs(url):
return QueryDict(urlparse.urlparse(url).query)
user1 = User.objects.create(username='user1', email='user1@example.com')
user2 = User.objects.create(username='user2', email='user2@example.com')
item1 = BasketItem.objects.create(user=user1, regie=regie, amount=42, subject='foo item')
item12 = BasketItem.objects.create(user=user1, regie=regie, amount=42, subject='bar item')
item2 = BasketItem.objects.create(user=user2, regie=regie, amount=42, subject='foo item')
item3 = BasketItem.objects.create(regie=regie, amount=42, subject='foo item', email='no-user@example.com')
item4 = BasketItem.objects.create(regie=regie, amount=42, subject='foo item')
# **Behaviours of BasketItemPayView**
# not authenticated, email is from the basket item
response = app.get(item1.payment_url)
qs = parse_qs(response.location)
assert qs['email'] == user1.email
response = app.get(item12.payment_url)
qs = parse_qs(response.location)
assert qs['email'] == user1.email
response = app.get(item2.payment_url)
qs = parse_qs(response.location)
assert qs['email'] == user2.email
response = app.get(item3.payment_url)
qs = parse_qs(response.location)
assert qs['email'] == 'no-user@example.com'
# FIXME: if required, there should be an email requested (PayFiP, Paybox, etc..)
response = app.get(item4.payment_url)
qs = parse_qs(response.location)
assert 'email' not in qs
# not authenticated, email in the query-string is ALWAYS used
response = app.get(item1.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == 'user3@example.com'
response = app.get(item12.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == 'user3@example.com'
response = app.get(item2.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == 'user3@example.com'
response = app.get(item3.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == 'user3@example.com'
response = app.get(item4.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == 'user3@example.com'
# authenticated, the email from the authenticated user is ALWAYS used, event
# if another email is given in the URL.
app.set_user(user1)
response = app.get(item1.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == user1.email
response = app.get(item12.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == user1.email
response = app.get(item2.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == user1.email
response = app.get(item3.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == user1.email
response = app.get(item4.payment_url + '?email=user3@example.com')
qs = parse_qs(response.location)
assert qs['email'] == user1.email
# **Behaviours of PayView**
# Logout
app.set_user()
app.session.flush()
# email is mandatory for paying a remote invoice without being authenticated
response = app.get('/')
csrf_token = response.context['csrf_token']
response = app.post('/lingo/pay', params={
'regie': regie.id,
'csrfmiddlewaretoken': csrf_token,
'item': ['1'],
'item_url': '/item-url/',
})
assert response.location == '/'
assert 'Payment requires to be logged in.' in app.session['_messages']
app.set_user(user1)
response = app.get('/')
csrf_token = response.context['csrf_token']
# Paying the basket always use the logged user email
response = app.post('/lingo/pay', params={
'regie': regie.id,
'csrfmiddlewaretoken': csrf_token,
'item': ['1'],
'item_url': '/item-url/',
'email': 'no-user@example.com'
})
assert response.location.startswith('http://dummy-payment.demo.entrouvert.com/')
qs = parse_qs(response.location)
assert qs['email'] == 'user1@example.com'
# Now try with a remote regie
regie.webservice_url = remote_invoices_httmock.url
regie.save()
# Logout
app.set_user()
app.session.flush()
# Get a response to extract csrf_token
response = app.get('/')
csrf_token = response.context['csrf_token']
with remote_invoices_httmock:
remote_invoices_httmock.invoice.update({
'id': 'F201601',
'display_id': 'F-2016-One',
'label': 'invoice-one',
'regie': 'remote',
'created': '2016-02-02',
'pay_limit_date': '2999-12-31',
'total_amount': '123.45',
'amount': '123.45',
'has_pdf': True,
'online_payment': True,
'paid': False,
'payment_date': '1970-01-01',
'no_online_payment_reason': '',
})
# email is mandatory for paying a remote invoice without being authenticated
response = app.post('/lingo/pay', params={
'regie': regie.id,
'csrfmiddlewaretoken': csrf_token,
'item': ['1'],
'item_url': '/item-url/',
})
assert response.location == '/item-url/'
assert 'You must give an email address.' in app.session['_messages']
response = app.post('/lingo/pay', params={
'regie': regie.id,
'csrfmiddlewaretoken': csrf_token,
'item': ['1'],
'item_url': '/item-url/',
'email': 'no-user@example.com',
})
assert response.location.startswith('http://dummy-payment.demo.entrouvert.com/')
qs = parse_qs(response.location)
assert qs['email'] == 'no-user@example.com'
app.set_user(user1)
response = app.get('/')
csrf_token = response.context['csrf_token']
# authenticated, always use the authenticated user's email
response = app.post('/lingo/pay', params={
'regie': regie.id,
'csrfmiddlewaretoken': csrf_token,
'item': ['1'],
'item_url': '/item-url/',
'email': 'no-user@example.com',
})
assert response.location.startswith('http://dummy-payment.demo.entrouvert.com/')
qs = parse_qs(response.location)
assert qs['email'] == 'user1@example.com'