diff --git a/combo/apps/lingo/views.py b/combo/apps/lingo/views.py index 4150d79b..70ed64b8 100644 --- a/combo/apps/lingo/views.py +++ b/combo/apps/lingo/views.py @@ -26,7 +26,7 @@ from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest from django.http import HttpResponseForbidden, Http404, JsonResponse from django.template.response import TemplateResponse -from django.utils import timezone, dateparse +from django.utils import timezone, dateparse, six from django.utils.encoding import force_text from django.views.decorators.csrf import csrf_exempt from django.views.generic import View, DetailView, ListView, TemplateView @@ -72,6 +72,14 @@ def lingo_check_request_signature(request): return check_request_signature(request, keys=keys) +class LocaleDecimal(Decimal): + # accept , instead of . for French users comfort + def __new__(cls, value="0", *args, **kwargs): + if isinstance(value, six.string_types) and settings.LANGUAGE_CODE.startswith('fr-'): + value = value.replace(',', '.') + return super(LocaleDecimal, cls).__new__(cls, value, *args, **kwargs) + + class RegiesApiView(ListView): model = Regie @@ -94,9 +102,9 @@ class AddBasketItemApiView(View): def get_amount(self, amount): if isinstance(amount, list): - d = Decimal(sum([Decimal(a) for a in amount])) + d = Decimal(sum([LocaleDecimal(a) for a in amount])) else: - d = Decimal(amount) + d = LocaleDecimal(amount) return d.quantize(Decimal('0.01'), ROUND_HALF_UP) def post(self, request, *args, **kwargs): @@ -111,13 +119,22 @@ class AddBasketItemApiView(View): raise Exception('missing amount parameter') item = BasketItem(amount=0) - item.amount = self.get_amount(request.GET.getlist('amount')) + try: + item.amount = self.get_amount(request.GET.getlist('amount')) + except ArithmeticError: + return HttpResponseBadRequest('invalid value for "amount" in query string') if request_body.get('amount'): - item.amount += self.get_amount(request_body['amount']) + try: + item.amount += self.get_amount(request_body['amount']) + except ArithmeticError: + return HttpResponseBadRequest('invalid value for "amount" in payload') if extra.get('amount'): - item.amount += self.get_amount(extra['amount']) + try: + item.amount += self.get_amount(extra['amount']) + except ArithmeticError: + return HttpResponseBadRequest('invalid value for "amount" in extra payload') if 'extra' in request_body: item.request_data = request_body.get('extra') @@ -234,11 +251,11 @@ class ValidateTransactionApiView(View): raise Http404 payment = get_eopayment_object(request, transaction.regie) - amount = request.GET['amount'] + amount = LocaleDecimal(request.GET['amount']) logger.info(u'validating amount %s for transaction %s', amount, smart_text(transaction.id)) try: - result = payment.backend.validate(Decimal(amount), transaction.bank_data) + result = payment.backend.validate(amount, transaction.bank_data) except eopayment.ResponseError as e: logger.error(u'failed in validation operation: %s', e) response = HttpResponse(content_type='application/json') @@ -247,7 +264,7 @@ class ValidateTransactionApiView(View): logger.info(u'bank validation result: %r', result) operation = TransactionOperation(transaction=transaction, - kind='validation', amount=Decimal(amount), bank_result=result) + kind='validation', amount=amount, bank_result=result) operation.save() response = HttpResponse(content_type='application/json') @@ -275,11 +292,11 @@ class CancelTransactionApiView(View): raise Http404 payment = get_eopayment_object(request, transaction.regie) - amount = request.GET['amount'] + amount = LocaleDecimal(request.GET['amount']) logger.info(u'cancelling amount %s for transaction %s', amount, smart_text(transaction.id)) try: - result = payment.backend.cancel(Decimal(amount), transaction.bank_data) + result = payment.backend.cancel(amount, transaction.bank_data) except eopayment.ResponseError as e: logger.error(u'failed in cancel operation: %s', e) response = HttpResponse(content_type='application/json') @@ -288,7 +305,7 @@ class CancelTransactionApiView(View): logger.info(u'bank cancellation result: %r', result) operation = TransactionOperation(transaction=transaction, - kind='cancellation', amount=Decimal(amount), bank_result=result) + kind='cancellation', amount=amount, bank_result=result) operation.save() response = HttpResponse(content_type='application/json') @@ -710,11 +727,11 @@ class SelfInvoiceView(View): except SelfDeclaredInvoicePayment.DoesNotExist: raise Http404() invoice_id = request.GET.get('invoice-number', '') - invoice_amount = request.GET.get('invoice-amount', '').replace(',', '.') + invoice_amount = request.GET.get('invoice-amount', '') msg = None url = None try: - invoice_amount = Decimal(invoice_amount) + invoice_amount = LocaleDecimal(invoice_amount) except ArithmeticError: invoice_amount = '-' msg = _('Sorry, the provided amount is invalid.') diff --git a/tests/test_lingo_payment.py b/tests/test_lingo_payment.py index eff7703a..aaccf536 100644 --- a/tests/test_lingo_payment.py +++ b/tests/test_lingo_payment.py @@ -12,6 +12,7 @@ from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.core.wsgi import get_wsgi_application from django.conf import settings +from django.test import override_settings from django.utils import timezone from django.utils.six.moves.urllib import parse as urlparse from django.contrib.messages.storage.session import SessionStorage @@ -238,6 +239,31 @@ def test_add_amount_to_basket(app, key, regie, user): assert json.loads(resp.content)['result'] == 'success' assert BasketItem.objects.filter(amount=Decimal('81.22')).exists() + # accept french notation if settings.LANGUAGE_CODE is 'fr-*' + url = '%s?amount=10,00&email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email) + url = sign_url(url, key) + resp = app.post_json(url, params=data, status=400) + assert 'invalid value for "amount" in query string' in resp.content + data['amount'] = '1,10' + url = '%s?amount=10.00&email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email) + url = sign_url(url, key) + resp = app.post_json(url, params=data, status=400) + assert 'invalid value for "amount" in payload' in resp.content + data['amount'] = '1.10' + data['extra'] = {'amount': '0,01'} + resp = app.post_json(url, params=data, status=400) + assert 'invalid value for "amount" in extra payload' in resp.content + + data['amount'] = '1,10' + data['extra'] = {'amount': '0,01'} + url = '%s?amount=10,00&email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email) + url = sign_url(url, key) + with override_settings(LANGUAGE_CODE='fr-be'): + resp = app.post_json(url, params=data, status=200) + assert resp.status_code == 200 + assert json.loads(resp.content)['result'] == 'success' + assert BasketItem.objects.filter(amount=Decimal('11.11')).exists() + other_regie.is_default = True other_regie.save() data['amount'] = [] @@ -408,7 +434,7 @@ def test_pay_multiple_regies(app, key, regie, user): resp = resp.forms[0].submit() assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/') qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query) - assert qs['amount'] == ['223.35'] + assert qs['amount'] == ['234.46'] resp = login(app).get(page.get_online_url()) resp = resp.forms[1].submit()