lingo: allow invoices anonymous payment (#12637)

This commit is contained in:
Jean-Baptiste Jaillet 2016-07-22 18:06:45 +02:00
parent e6aded4460
commit 9779837cb4
5 changed files with 134 additions and 12 deletions

View File

@ -172,7 +172,10 @@ class Regie(models.Model):
item = requests.get(self.signed_url(request, url,
NameID=mellon['name_id_content'])).json()
return build_remote_item(item.get('data'), self)
return {}
else:
url = self.webservice_url + '/invoice/%s/' % item
item = requests.get(self.signed_url(request, url)).json()
return build_remote_item(item.get('data'), self)
def pay_item(self, request, item_id, transaction_id, transaction_date):
url = self.webservice_url + '/invoice/%s/pay/' % item_id

View File

@ -0,0 +1,21 @@
{% load i18n %}<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% blocktrans %}Invoice number {{ number }}{% endblocktrans %}</title>
</head>
<body class="invoice_fullpage">
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<div class="invoice">
{% include 'lingo/combo/item.html' %}
</div>
</body>
</html>

View File

@ -34,8 +34,18 @@
{% endif %}
{% if item.online_payment and item.amount >= regie.payment_min_amount %}
{% csrf_token %}
<input type="hidden" name="regie" value="{{ regie.pk }}" />
<input type="hidden" name="item" value="{{ item.id }}" />
{% if not user.is_authenticated %}
<div class="email">
<label for="email">{% trans 'Email:' %}</label>
<input type="email" id="email" name="email" required/>
</div>
{% endif %}
<input type="hidden" name="regie" value="{{ regie.pk }}"/>
<input type="hidden" name="item" value="{{ item.id }}"/>
{% if item_url %}
<input type="hidden" name="item_url" value="{{ item_url }}"/>
{% endif %}
<div class="buttons">
<button>{% trans "Pay" %}</button>
</div>

View File

@ -308,8 +308,18 @@ class PayView(View):
transaction = Transaction()
if request.user.is_authenticated():
transaction.user = request.user
email = request.user.email
firstname = request.user.first_name
lastname = request.user.last_name
else:
transaction.user = None
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')
firstname = ''
lastname = ''
transaction.save()
transaction.regie = regie
transaction.items = items
@ -328,9 +338,10 @@ class PayView(View):
return HttpResponseRedirect(next_url)
payment = get_eopayment_object(request, regie)
(order_id, kind, data) = payment.request(total_amount, email=request.user.email,
first_name=request.user.first_name,
last_name=request.user.last_name)
(order_id, kind, data) = payment.request(total_amount, email=email,
first_name=firstname,
last_name=lastname)
logger = logging.getLogger(__name__)
logger.info(u'emitted payment request with id %s', smart_text(order_id), extra={
'eopayment_order_id': smart_text(order_id), 'eopayment_data': repr(data)})
@ -489,6 +500,8 @@ class ItemDownloadView(View):
data = regie.download_item(request, item_id)
except PermissionDenied:
return HttpResponseForbidden()
except DecryptionError as e:
return Http404(str(e))
if data.status_code != 200:
logging.error('failed to retrieve invoice (%r)', data.status_code)
@ -504,21 +517,30 @@ class ItemDownloadView(View):
class ItemView(TemplateView):
http_method_names = [u'get']
template_name = 'lingo/combo/item.html'
def get_context_data(self, **kwargs):
ret = {'item_url': self.request.get_full_path()}
try:
regie = Regie.objects.get(pk=kwargs['regie_id'])
except Regie.DoesNotExist:
raise Http404()
try:
item_id = aes_hex_decrypt(settings.SECRET_KEY, kwargs['item_crypto_id'])
except DecryptionError:
raise Http404()
item = regie.get_item(self.request, item_id)
if not item:
raise Http404(_('No item was found.'))
return {'item': item, 'regie': regie}
ret.update({'item': item, 'regie': regie})
return ret
def get_template_names(self):
if self.request.is_ajax:
return ['lingo/combo/item.html']
return ['lingo/combo/invoice_fullpage.html']
class CancelItemView(DetailView):

View File

@ -6,13 +6,13 @@ from decimal import Decimal
from django.contrib.auth.models import User
from django.test.client import RequestFactory
from django.template import Context
from django.core.urlresolvers import reverse
from django.conf import settings
from django.utils import timezone
from combo.utils import check_query
from combo.utils import check_query, aes_hex_encrypt
from combo.data.models import Page
from combo.apps.lingo.models import Regie, BasketItem, Transaction
from combo.apps.lingo.models import ActiveItems
from combo.apps.lingo.models import Regie, ActiveItems
pytestmark = pytest.mark.django_db
@ -100,3 +100,69 @@ def test_remote_regie_cell(mock_get, remote_regie, user):
mock_json.json.return_value = {'err': 0, 'data': []}
content = cell.render(context)
assert 'No items yet' in content
@mock.patch('combo.apps.lingo.models.Regie.pay_item')
@mock.patch('combo.apps.lingo.models.requests.get')
def test_anonymous_successful_item_payment(mock_get, mock_pay_item, app, remote_regie):
assert remote_regie.is_remote() == True
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, 'F201601')
mock_json = mock.Mock()
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
mock_get.return_value = mock_json
mock_pay_item.return_value = mock.Mock(status_code=200)
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id))
form = resp.form
assert 'email' in form.fields
assert form['email'].value == ''
assert 'item_url' in form.fields
assert form['item_url'].value == '/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id)
assert 'item' in form.fields
assert form['item'].value == 'F201601'
assert 'regie' in form.fields
assert form['regie'].value == unicode(remote_regie.pk)
form['email'] = 'ghost@buster.com'
resp = form.submit()
assert resp.status_code == 302
location = resp.location
assert 'dummy-payment' in location
parsed = urlparse.urlparse(location)
# get return_url and transaction id from location
qs = urlparse.parse_qs(parsed.query)
args = {'transaction_id': qs['transaction_id'][0], 'signed': True,
'ok': True, 'reason': 'Paid'}
# make sure return url is the user return URL
assert urlparse.urlparse(qs['return_url'][0]).path.startswith(
reverse('lingo-return', kwargs={'regie_pk': remote_regie.id}))
# simulate successful return URL
resp = app.get(qs['return_url'][0], args)
assert resp.status_code == 302
assert urlparse.urlparse(resp.url).path == '/'
# simulate successful call to callback URL
resp = app.get(reverse('lingo-callback', kwargs={'regie_pk': remote_regie.id}), args)
assert resp.status_code == 200
@mock.patch('combo.apps.lingo.models.requests.get')
def test_anonymous_item_payment_email_error(mock_get, app, remote_regie):
assert remote_regie.is_remote() == True
encrypt_id = aes_hex_encrypt(settings.SECRET_KEY, 'F201601')
mock_json = mock.Mock()
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
mock_get.return_value = mock_json
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id))
form = resp.form
resp = form.submit()
assert resp.status_code == 302
path = urlparse.urlparse(resp.location).path
assert path == '/lingo/item/%s/%s/' % (remote_regie.id, encrypt_id)
@mock.patch('combo.apps.lingo.models.requests.get')
def test_wrong_crypted_item(mock_get, remote_regie, app):
assert remote_regie.is_remote() == True
mock_json = mock.Mock()
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
mock_get.return_value = mock_json
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, 'zrzer854sfaear45e6rzerzerzef'), status=404)