Projet

Général

Profil

0001-lingo-support-anonymous-and-no-basket-payment-36876.patch

Emmanuel Cazenave, 23 décembre 2019 13:27

Télécharger (28,5 ko)

Voir les différences:

Subject: [PATCH] lingo: support anonymous and no basket payment (#36876)

 .../lingo/combo/item-wait-payment.html        |  51 ++++
 combo/apps/lingo/urls.py                      |  11 +-
 combo/apps/lingo/views.py                     | 186 ++++++++++++--
 tests/test_lingo_payment.py                   | 239 +++++++++++++++++-
 4 files changed, 456 insertions(+), 31 deletions(-)
 create mode 100644 combo/apps/lingo/templates/lingo/combo/item-wait-payment.html
combo/apps/lingo/templates/lingo/combo/item-wait-payment.html
1
{% extends "combo/page_template.html" %}
2
{% load staticfiles i18n %}
3

  
4
{% block combo-content %}
5
{% block wait-js %}
6
<script>
7
function display_error(message) {
8
    $('#transaction-error').text(message);
9
    $('#transaction-error').show();
10
    $("#wait-msg").hide();
11
}
12

  
13
$(function() {
14
    var source_url = '{{source_url}}';
15
    var transaction_id = '{{transaction_id}}';
16
    if (transaction_id === "") {
17
        display_error($('#transaction-status').data('error'));
18
    }
19
    else {
20
        $.ajax({
21
            url: `/api/lingo/transaction-status/${transaction_id}/`,
22
            success: function(data, status) {
23
                if (!data.wait) {
24
                    window.location.replace(source_url);
25
                } else if (data.error) {
26
                    display_error(data.error_msg)
27
                } else {
28
                    setTimeout(wait_payment, 3000, source_url, transaction_id);
29
                }
30
            },
31
            error: function(error) {
32
                display_error($('#transaction-status').data('error'));
33
                window.console && console.log(':(', error);
34
            }
35
        });
36
    }
37
});
38
</script>
39
{% endblock %}
40

  
41
{% block wait-content%}
42
<div>
43
  {% block wait-message %}
44
  <h2 id="wait-msg">{% trans "Please wait while your request is being processed..." %}</h2>
45
  {% endblock %}
46
   <div id="transaction-error" class="errornotice" data-error="{% trans 'An error occured' %}" style="display: none;"></div>
47
   <p><a href="{{source_url}}">{% trans "Continue" %}</a></p>
48
   </p>
49
</div>
50
{% endblock %}
51
{% endblock %}
combo/apps/lingo/urls.py
21 21
from .views import (RegiesApiView, AddBasketItemApiView, PayView, CallbackView,
22 22
                    ReturnView, ItemDownloadView, ItemView, CancelItemView,
23 23
                    RemoveBasketItemApiView, ValidateTransactionApiView,
24
                    CancelTransactionApiView, SelfInvoiceView, BasketItemPayView)
24
                    CancelTransactionApiView, SelfInvoiceView, BasketItemPayView,
25
                    TransactionStatusApiView, BasketItemPaymentStatusView)
25 26
from .manager_views import (RegieListView, RegieCreateView, RegieUpdateView,
26 27
                            RegieDeleteView, TransactionListView, BasketItemErrorListView,
27 28
                            download_transactions_csv, PaymentBackendListView,
......
60 61
        name='api-validate-transaction'),
61 62
    url('^api/lingo/cancel-transaction$', CancelTransactionApiView.as_view(),
62 63
        name='api-cancel-transaction'),
64
    url(
65
        '^api/lingo/transaction-status/(?P<signature>\w+)/$', TransactionStatusApiView.as_view(),
66
        name='api-transaction-status'
67
    ),
63 68
    url(r'^lingo/pay$', PayView.as_view(), name='lingo-pay'),
64 69
    url(r'^lingo/cancel/(?P<pk>\w+)/$', CancelItemView.as_view(), name='lingo-cancel-item'),
65 70
    url(r'^lingo/callback/(?P<regie_pk>\w+)/$', CallbackView.as_view(), name='lingo-callback'),
......
74 79
        ItemDownloadView.as_view(), name='download-item-pdf'),
75 80
    url(r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_crypto_id>[\w,-]+)/$',
76 81
        ItemView.as_view(), name='view-item'),
77
    url(r'^lingo/item/(?P<item_id>\d+)/pay$',
82
    url(r'^lingo/item/(?P<signature>\w+)/pay$',
78 83
        BasketItemPayView.as_view(), name='basket-item-pay-view'),
84
    url(r'^lingo/item/(?P<signature>\w+)/payment-status$',
85
        BasketItemPaymentStatusView.as_view(), name='basket-item-payment-status'),
79 86
    url(r'^lingo/self-invoice/(?P<cell_id>\w+)/$', SelfInvoiceView.as_view(),
80 87
        name='lingo-self-invoice'),
81 88
]
combo/apps/lingo/views.py
23 23
from django.contrib.auth.models import User
24 24
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
25 25
from django.core.urlresolvers import reverse
26
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
26
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseRedirect, HttpResponseBadRequest
27 27
from django.http import HttpResponseForbidden, Http404, JsonResponse
28 28
from django.template.response import TemplateResponse
29 29
from django.utils import timezone, dateparse, six
......
39 39
import eopayment
40 40

  
41 41
from combo.data.models import Page
42
from combo.utils import check_request_signature, aes_hex_decrypt, DecryptionError
42
from combo.utils import check_request_signature, aes_hex_decrypt, aes_hex_encrypt, DecryptionError
43 43
from combo.profile.utils import get_user_from_name_id
44
from combo.public.views import publish_page
44 45

  
45 46
from .models import (Regie, BasketItem, Transaction, TransactionOperation,
46
                     LingoBasketCell, SelfDeclaredInvoicePayment, PaymentBackend)
47
                     LingoBasketCell, SelfDeclaredInvoicePayment, PaymentBackend, EXPIRED)
47 48

  
48
def get_eopayment_object(request, regie_or_payment_backend):
49

  
50
def get_eopayment_object(request, regie_or_payment_backend, unique_item=None):
49 51
    payment_backend = regie_or_payment_backend
50 52
    if isinstance(regie_or_payment_backend, Regie):
51 53
        payment_backend = regie_or_payment_backend.payment_backend
52 54
    options = payment_backend.service_options
55
    normal_return_url = reverse(
56
        'lingo-return-payment-backend',
57
        kwargs={'payment_backend_pk': payment_backend.id}
58
    )
59
    if unique_item:
60
        normal_return_url = "%s?item-id=%s" % (normal_return_url, unique_item.pk)
53 61
    options.update({
54 62
        'automatic_return_url': request.build_absolute_uri(
55 63
                reverse('lingo-callback-payment-backend',
56 64
                        kwargs={'payment_backend_pk': payment_backend.id})),
57
        'normal_return_url': request.build_absolute_uri(
58
                reverse('lingo-return-payment-backend',
59
                        kwargs={'payment_backend_pk': payment_backend.id})),
65
        'normal_return_url': request.build_absolute_uri(normal_return_url)
60 66
    })
61 67
    return eopayment.Payment(payment_backend.service, options)
62 68

  
......
150 156
            elif request.GET.get('email'):
151 157
                user = User.objects.get(email=request.GET.get('email'))
152 158
            else:
153
                raise Exception('no user specified')
159
                user = None
154 160
        except User.DoesNotExist:
155 161
            raise Exception('unknown user')
156 162

  
......
192 198
                    'Bad format for capture date, it should be yyyy-mm-dd.')
193 199

  
194 200
        item.save()
195
        item.regie.compute_extra_fees(user=item.user)
201
        if user:
202
            item.regie.compute_extra_fees(user=item.user)
196 203

  
197
        payment_url = reverse('basket-item-pay-view', kwargs={'item_id': item.id})
204
        payment_url = reverse(
205
            'basket-item-pay-view',
206
            kwargs={
207
                'signature': aes_hex_encrypt(settings.SECRET_KEY, str(item.id))
208
            })
198 209
        return JsonResponse({'result': 'success', 'id': str(item.id),
199 210
                             'payment_url': request.build_absolute_uri(payment_url)})
200 211

  
......
321 332

  
322 333
class PayMixin(object):
323 334
    @atomic
324
    def handle_payment(self, request, regie, items, remote_items, next_url='/', email=''):
335
    def handle_payment(
336
            self, request, regie, items, remote_items, next_url='/', email='', firstname='',
337
            lastname='', basket=True):
338

  
339
        single_item = None
340
        if not basket:
341
            single_item = items[0]
342

  
325 343
        if remote_items:
326 344
            total_amount = sum([x.amount for x in remote_items])
327 345
        else:
......
329 347

  
330 348
        if total_amount < regie.payment_min_amount:
331 349
            messages.warning(request, _(u'Minimal payment amount is %s €.') % regie.payment_min_amount)
332
            return HttpResponseRedirect(next_url)
350
            if basket:
351
                return HttpResponseRedirect(next_url)
352
            return HttpResponseRedirect(get_single_item_payment_status_view(single_item.pk))
333 353

  
334 354
        for item in items:
335 355
            if item.regie != regie:
......
344 364
            lastname = user.last_name
345 365
        else:
346 366
            transaction.user = None
347
            firstname = ''
348
            lastname = ''
349 367

  
350 368
        transaction.save()
351 369
        transaction.regie = regie
......
354 372
        transaction.status = 0
355 373
        transaction.amount = total_amount
356 374

  
357
        payment = get_eopayment_object(request, regie)
375
        payment = get_eopayment_object(request, regie, single_item)
358 376
        kwargs = {
359 377
            'email': email, 'first_name': firstname, 'last_name': lastname
360 378
        }
......
373 391

  
374 392
        # store the next url in session in order to be able to redirect to
375 393
        # it if payment is canceled
394
        lingo_next_url = request.build_absolute_uri(next_url)
395
        if not basket:
396
            lingo_next_url = get_single_item_payment_status_view(single_item.pk, transaction.pk)
397

  
376 398
        request.session.setdefault('lingo_next_url',
377
                                   {})[order_id] = request.build_absolute_uri(next_url)
399
                                   {})[order_id] = lingo_next_url
378 400
        request.session.modified = True
379 401

  
380 402
        if kind == eopayment.URL:
......
433 455
        return self.handle_payment(request, regie, items, remote_items, next_url, email)
434 456

  
435 457

  
458
def get_single_item_payment_status_view(item_id, transaction_id=None):
459
    url = reverse(
460
        'basket-item-payment-status',
461
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(item_id))}
462
    )
463
    if transaction_id:
464
        url = "%s?transaction-id=%s" % (
465
            url, aes_hex_encrypt(settings.SECRET_KEY, str(transaction_id))
466
        )
467
    return url
468

  
469

  
436 470
class BasketItemPayView(PayMixin, View):
471

  
437 472
    def get(self, request, *args, **kwargs):
438 473
        next_url = request.GET.get('next_url') or '/'
439
        if not (request.user and request.user.is_authenticated):
440
            return HttpResponseForbidden(_('No item payment allowed for anonymous users.'))
474
        email = request.GET.get('email', '')
475
        firstname = request.GET.get('firstname', '')
476
        lastname = request.GET.get('lastname', '')
441 477

  
442
        item = BasketItem.objects.get(pk=kwargs['item_id'])
478
        signature = kwargs.get('signature')
479
        try:
480
            item_id = aes_hex_decrypt(settings.SECRET_KEY, signature)
481
        except DecryptionError:
482
            return HttpResponseForbidden(_('Invalid payment request.'))
483

  
484
        item = BasketItem.objects.get(pk=item_id)
443 485
        regie = item.regie
444 486
        if regie.extra_fees_ws_url:
445 487
            return HttpResponseForbidden(_('No item payment allowed as extra fees set.'))
446 488

  
447
        if item.user != request.user:
489
        if item.user and item.user != request.user:
448 490
            return HttpResponseForbidden(_('Wrong item: payment not allowed.'))
449 491

  
450
        return self.handle_payment(request, regie, [item], [], next_url)
492
        return self.handle_payment(
493
            request=request, regie=regie, items=[item], remote_items=[], next_url=next_url, email=email,
494
            firstname=firstname, lastname=lastname, basket=False
495
        )
451 496

  
452 497

  
453 498
class PaymentException(Exception):
......
597 642

  
598 643
    def handle_return(self, request, backend_response, **kwargs):
599 644
        transaction = None
645
        basket_item_id = request.GET.get('item-id')
600 646
        try:
601 647
            transaction = self.handle_response(request, backend_response, **kwargs)
602 648
        except UnsignedPaymentException as e:
......
607 653
        except PaymentException as e:
608 654
            messages.error(request, _('We are sorry but the payment service '
609 655
                                      'failed to provide a correct answer.'))
610
            return HttpResponseRedirect(get_basket_url())
656
            url = get_basket_url()
657
            if basket_item_id:
658
                url = get_single_item_payment_status_view(basket_item_id)
659
            return HttpResponseRedirect(url)
611 660

  
612 661
        if transaction and transaction.status in (eopayment.PAID, eopayment.ACCEPTED):
613 662
            messages.info(request, transaction.regie.get_text_on_success())
......
762 811
            return HttpResponseRedirect(url)
763 812
        messages.warning(request, msg)
764 813
        return HttpResponseRedirect(request.GET.get('page_path') or '/')
814

  
815

  
816
class BasketItemPaymentStatusView(View):
817

  
818
    http_method_names = ['get']
819

  
820
    def get(self, request, *args, **kwargs):
821
        page = Page()
822
        page.template_name = 'standard'
823
        template_name = 'lingo/combo/item-wait-payment.html'
824
        signature = kwargs.get('signature')
825

  
826
        try:
827
            item_id = aes_hex_decrypt(settings.SECRET_KEY, signature)
828
        except DecryptionError:
829
            return HttpResponseForbidden(_('Invalid item signature.'))
830

  
831
        try:
832
            item = BasketItem.objects.get(
833
                pk=item_id, cancellation_date__isnull=True
834
            )
835
        except BasketItem.DoesNotExist:
836
            return HttpResponseForbidden(_('Invalid basket item'))
837

  
838
        transaction = None
839
        if 'transaction-id' in request.GET:
840
            try:
841
                transaction_id = aes_hex_decrypt(
842
                    settings.SECRET_KEY, request.GET.get('transaction-id')
843
                )
844
            except DecryptionError:
845
                return HttpResponseForbidden(_('Invalid transaction signature.'))
846
            try:
847
                transaction = Transaction.objects.get(pk=transaction_id)
848
            except Transaction.DoesNotExist:
849
                return HttpResponseForbidden(_('Invalid transaction.'))
850

  
851
        extra_context_data = getattr(request, 'extra_context_data', {})
852
        extra_context_data['transaction_id'] = ''
853
        if transaction:
854
            extra_context_data['transaction_id'] = \
855
                aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))
856
        extra_context_data['source_url'] = item.source_url
857
        request.extra_context_data = extra_context_data
858
        return publish_page(request, page, template_name=template_name)
859

  
860

  
861
class TransactionStatusApiView(View):
862

  
863
    http_method_names = ['get']
864

  
865
    def get(self, request, *args, **kwargs):
866
        signature = kwargs.get('signature')
867
        try:
868
            transaction_id = aes_hex_decrypt(settings.SECRET_KEY, signature)
869
        except DecryptionError:
870
            return HttpResponseBadRequest(_('Invalid transaction.'))
871
        try:
872
            transaction = Transaction.objects.get(pk=transaction_id)
873
        except Transaction.DoesNotExist:
874
            return HttpResponseNotFound(_('Unknown transaction.'))
875

  
876
        user = request.user if request.user.is_authenticated() else None
877
        error_msg = _('Transaction does not belong to the requesting user')
878
        if user and transaction.user and user != transaction.user:
879
            return HttpResponseForbidden(error_msg)
880
        if not user and transaction.user:
881
            return HttpResponseForbidden(error_msg)
882

  
883
        if transaction.is_paid():
884
            data = {
885
                'wait': False,
886
                'error': False,
887
                'error_msg': ''
888
            }
889
            return JsonResponse(data=data)
890

  
891
        if transaction.status in (
892
                eopayment.CANCELLED, eopayment.ERROR, eopayment.DENIED, EXPIRED
893
        ):
894
            data = {
895
                'wait': True,
896
                'error': True,
897
                'error_msg': _('Payment error, you can continue and make another payment')
898
            }
899
            return JsonResponse(data=data)
900

  
901
        data = {
902
            'wait': True,
903
            'error': False,
904
            'error_msg': ''
905
        }
906
        return JsonResponse(data=data)
tests/test_lingo_payment.py
6 6
from decimal import Decimal
7 7
import json
8 8
import mock
9
import os.path
9 10

  
10 11
from django.apps import apps
11 12
from django.contrib.auth.models import User
......
22 23
from combo.apps.lingo.models import (
23 24
    Regie, BasketItem, Transaction, TransactionOperation, RemoteItem, EXPIRED, LingoBasketCell,
24 25
    PaymentBackend)
25
from combo.utils import sign_url
26
from combo.utils import aes_hex_decrypt, aes_hex_encrypt, sign_url
26 27

  
27 28
from .test_manager import login
28 29

  
......
120 121
    else:
121 122
        return settings.LINGO_API_SIGN_KEY
122 123

  
124

  
125
def assert_payment_status(url, item_id, transaction_id=None):
126
    if hasattr(url, 'path'):
127
        url = url.path
128

  
129
    if transaction_id:
130
        url, part = url.split('?')
131
        assert 'transaction-id' in part
132
        signature = part.replace('transaction-id=', '')
133
        assert aes_hex_decrypt(settings.SECRET_KEY, signature) == str(transaction_id)
134

  
135
    url, part = os.path.split(url)
136
    assert part == 'payment-status'
137
    url, part = os.path.split(url)
138
    assert aes_hex_decrypt(settings.SECRET_KEY, part) == str(item_id)
139
    url, part = os.path.split(url)
140
    assert part == 'item'
141
    url, part = os.path.split(url)
142
    assert part == 'lingo'
143

  
144

  
123 145
def test_default_regie():
124 146
    payment_backend = PaymentBackend.objects.create(label='foo', slug='foo')
125 147
    Regie.objects.all().delete()
......
297 319
    assert resp.status_code == 200
298 320
    response = json.loads(resp.text)
299 321
    assert response['result'] == 'success'
300
    assert response['payment_url'].endswith('/lingo/item/%s/pay' % item.id)
322
    payment_url = urlparse.urlparse(response['payment_url'])
323
    assert payment_url.path.startswith('/lingo/item/')
324
    assert payment_url.path.endswith('/pay')
301 325
    assert BasketItem.objects.filter(amount=Decimal('22.23')).exists()
302 326
    assert BasketItem.objects.filter(amount=Decimal('22.23'))[0].regie_id == other_regie.id
303 327

  
......
417 441
    assert BasketItem.objects.filter(regie=regie, amount=amount, payment_date__isnull=True).exists()
418 442
    payment_url = resp.json['payment_url']
419 443
    resp = app.get(payment_url, status=403)
420
    assert 'No item payment allowed for anonymous users.' in resp.text
444
    assert 'Wrong item: payment not allowed.' in resp.text
421 445

  
422 446
    login(app, username='john.doe', password='john.doe')
423 447
    resp = app.get(payment_url, status=403)
......
440 464
    assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
441 465
    qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
442 466
    assert qs['amount'] == ['12.00']
443

  
444 467
    # simulate successful payment response from dummy backend
445 468
    data = {'transaction_id': qs['transaction_id'][0], 'ok': True,
446 469
            'amount': qs['amount'][0], 'signed': True}
......
448 471
    # dummy module put that URL in return_url query string parameter).
449 472
    resp = app.get(qs['return_url'][0], params=data)
450 473
    # check that item is paid
451
    assert BasketItem.objects.filter(regie=regie, amount=amount, payment_date__isnull=False).exists()
452
    # check that user is redirected to the next_url passed previously
453
    assert resp.location == 'http://example.net/form/id/'
474
    item  = BasketItem.objects.filter(regie=regie, amount=amount, payment_date__isnull=False).first()
475
    # check that user is redirected to the item payment status view
476
    assert_payment_status(resp.location, item.pk, transaction_id=item.transaction_set.last().pk)
477

  
454 478

  
455 479
def test_pay_multiple_regies(app, key, regie, user):
456 480
    test_add_amount_to_basket(app, key, regie, user)
......
995 1019
        assert url.startswith('http://example.org/testitem/jump/trigger/paid')
996 1020
    assert BasketItem.objects.get(id=item.id).payment_date
997 1021
    assert BasketItem.objects.get(id=item.id).notification_date
1022

  
1023

  
1024
@pytest.mark.parametrize("authenticated", [True, False])
1025
def test_payment_no_basket(app, user, regie, authenticated):
1026
    url = reverse('api-add-basket-item')
1027
    source_url = 'http://example.org/item/1'
1028
    data = {'amount': 10, 'display_name': 'test item', 'url': source_url}
1029
    if authenticated:
1030
        data['email'] = user.email
1031
    url = sign_url(url, settings.LINGO_API_SIGN_KEY)
1032
    resp = app.post_json(url, params=data)
1033
    assert resp.status_code == 200
1034
    payment_url = resp.json['payment_url']
1035

  
1036
    item = BasketItem.objects.first()
1037
    assert item.user is None
1038
    assert item.amount == Decimal('10.00')
1039
    path = urlparse.urlparse(payment_url).path
1040
    start = '/lingo/item/'
1041
    end = '/pay'
1042
    assert path.startswith(start)
1043
    assert path.endswith(end)
1044
    signature = path.replace(start, '').replace(end, '')
1045
    assert aes_hex_decrypt(settings.SECRET_KEY, signature) == str(item.id)
1046

  
1047
    if authenticated:
1048
        app = login(app)
1049

  
1050
    # payment error due to too small amount
1051
    item.amount = Decimal('1.00')
1052
    item.save()
1053
    resp = app.get(payment_url)
1054
    assert_payment_status(resp.location, item.pk)
1055
    resp = resp.follow()
1056
    assert 'Minimal payment amount is 4.50' in resp.text
1057
    # we can go back to form
1058
    assert source_url in resp.text
1059

  
1060
    # amount ok, redirection to payment backend
1061
    item.amount = Decimal('10.00')
1062
    item.save()
1063
    resp = app.get(payment_url)
1064
    assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
1065
    qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
1066
    assert qs['amount'] == ['10.00']
1067
    if authenticated:
1068
        assert qs['email'] == ['foo@example.com']
1069
    else:
1070
        assert 'email' not in qs
1071

  
1072
    # mail get be specified here for anonymous user
1073
    resp = app.get(
1074
        payment_url,
1075
        params={
1076
            'email': 'foo@localhost',
1077
        }
1078
    )
1079
    assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
1080
    qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
1081
    assert qs['amount'] == ['10.00']
1082
    if authenticated:
1083
        assert qs['email'] == ['foo@example.com']
1084
    else:
1085
        assert qs['email'] == ['foo@localhost']
1086

  
1087
    # simulate bad responseform payment backend, no transaction id
1088
    data = {'amount': qs['amount'][0], 'signed': True}
1089
    return_url = qs['return_url'][0]
1090
    assert return_url.endswith('?item-id=%s' % item.pk)
1091
    resp = app.get(return_url, params=data)
1092
    assert_payment_status(resp.location, item.pk)
1093
    resp = resp.follow()
1094
    assert 'We are sorry but the payment service failed to provide a correct answer.' in resp.text
1095
    assert 'http://example.org/item/1' in resp.text
1096
    # check that item is not paid
1097
    item = BasketItem.objects.get(pk=item.pk)
1098
    assert not item.payment_date
1099

  
1100
    # simulate successful payment response from dummy backend
1101
    data = {
1102
        'transaction_id': qs['transaction_id'][0], 'ok': True,
1103
        'amount': qs['amount'][0], 'signed': True
1104
    }
1105
    return_url = qs['return_url'][0]
1106
    assert return_url.endswith('?item-id=%s' % item.pk)
1107
    resp = app.get(return_url, params=data)
1108
    assert_payment_status(resp.location, item.pk, transaction_id=item.transaction_set.last().pk)
1109
    # check that item is paid
1110
    item = BasketItem.objects.get(pk=item.pk)
1111
    assert item.payment_date
1112
    # accept redirection to item payment status view
1113
    resp = resp.follow()
1114
    # which should it self redirect to item.source_url as it is paid
1115
    assert 'Please wait while your request is being processed' in resp.text
1116
    assert source_url in resp.text
1117

  
1118

  
1119
def test_transaction_status_api(app, regie, user):
1120
    # invalid transaction signature
1121
    url = reverse('api-transaction-status', kwargs={'signature': 'xxxx'})
1122
    resp = app.get(url, status=400)
1123
    assert 'Invalid transaction.' in resp.text
1124

  
1125
    # unkown transaction identifier
1126
    transaction_id = 1000
1127
    url = reverse(
1128
        'api-transaction-status',
1129
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction_id))}
1130
    )
1131
    resp = app.get(url, status=404)
1132
    assert 'Unknown transaction.' in resp.text
1133

  
1134
    wait_response = {
1135
        'wait': True,
1136
        'error': False,
1137
        'error_msg': ''
1138
    }
1139
    # anonymous user on anonymous transaction: OK
1140
    transaction = Transaction.objects.create(amount=Decimal('10.0'), regie=regie, status=0)
1141
    url = reverse(
1142
        'api-transaction-status',
1143
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1144
    )
1145
    resp = app.get(url)
1146
    assert resp.json == wait_response
1147

  
1148
    # authenticated user on anonymous transaction: OK
1149
    transaction = Transaction.objects.create(amount=Decimal('10.0'), regie=regie, status=0)
1150
    url = reverse(
1151
        'api-transaction-status',
1152
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1153
    )
1154
    resp = login(app).get(url)
1155
    assert resp.json == wait_response
1156
    app.reset()
1157

  
1158
    # authenticated user on his transaction: OK
1159
    transaction = Transaction.objects.create(
1160
        amount=Decimal('10.0'), regie=regie, status=0, user=user)
1161
    url = reverse(
1162
        'api-transaction-status',
1163
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1164
    )
1165
    resp = login(app).get(url)
1166
    assert resp.json == wait_response
1167
    app.reset()
1168

  
1169
    error_msg = 'Transaction does not belong to the requesting user'
1170
    # anonymous user on other user transaction transaction: NOTOK
1171
    transaction = Transaction.objects.create(
1172
        amount=Decimal('10.0'), regie=regie, status=0, user=user)
1173
    url = reverse(
1174
        'api-transaction-status',
1175
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1176
    )
1177
    resp = app.get(url, status=403)
1178
    assert error_msg in resp.text
1179

  
1180
    # authenticated user on other user transaction transaction: NOTOK
1181
    user2 = User.objects.create_user(
1182
        'user2', password='user2', email='user2@example.com'
1183
    )
1184
    transaction = Transaction.objects.create(
1185
        amount=Decimal('10.0'), regie=regie, status=0, user=user2)
1186
    url = reverse(
1187
        'api-transaction-status',
1188
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1189
    )
1190
    resp = login(app).get(url, status=403)
1191
    assert error_msg in resp.text
1192
    app.reset()
1193

  
1194
    # transaction error
1195
    transaction = Transaction.objects.create(
1196
        amount=Decimal('10.0'), regie=regie, status=eopayment.ERROR
1197
    )
1198
    url = reverse(
1199
        'api-transaction-status',
1200
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1201
    )
1202
    resp = app.get(url)
1203
    assert resp.json == {
1204
        'wait': True,
1205
        'error': True,
1206
        'error_msg': 'Payment error, you can continue and make another payment'
1207
    }
1208

  
1209
    # transaction paid
1210
    transaction = Transaction.objects.create(
1211
        amount=Decimal('10.0'), regie=regie, status=eopayment.PAID
1212
    )
1213
    url = reverse(
1214
        'api-transaction-status',
1215
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1216
    )
1217
    resp = app.get(url)
1218
    assert resp.json == {
1219
        'wait': False,
1220
        'error': False,
1221
        'error_msg': ''
1222
    }
998
-