Projet

Général

Profil

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

Emmanuel Cazenave, 19 décembre 2019 19:12

Télécharger (28,5 ko)

Voir les différences:

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

 combo/apps/lingo/static/js/wait.payment.js    |  29 +++
 .../lingo/combo/item-wait-payment.html        |  14 ++
 combo/apps/lingo/urls.py                      |  11 +-
 combo/apps/lingo/views.py                     | 183 ++++++++++++--
 tests/test_lingo_payment.py                   | 238 +++++++++++++++++-
 5 files changed, 445 insertions(+), 30 deletions(-)
 create mode 100644 combo/apps/lingo/static/js/wait.payment.js
 create mode 100644 combo/apps/lingo/templates/lingo/combo/item-wait-payment.html
combo/apps/lingo/static/js/wait.payment.js
1
function display_error(message) {
2
    $('#transaction-status').text(message);
3
    $("#transaction-status").attr('class', 'errornotice');
4
}
5

  
6
function wait_payment(source_url, transaction_id) {
7
    if (transaction_id === "") {
8
        display_error($('#transaction-status').data('error'));
9
    }
10
    else {
11
        $.ajax({
12
            url: `/api/lingo/transaction-status/${transaction_id}/`,
13
            success: function(data, status) {
14
                if (data.redirect) {
15
                    window.location.replace(source_url);
16
                } else if (data.transaction_error) {
17
                    display_error(data.msg)
18
                } else {
19
                    $('#transaction-status').text(data.msg);
20
                    setTimeout(wait_payment, 3000, source_url, transaction_id);
21
                }
22
            },
23
            oerror: function(error) {
24
                display_error($('#transaction-status').data('error'));
25
                window.console && console.log(':(', error);
26
            }
27
        });
28
    }
29
};
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
<script src="{% static "js/wait.payment.js" %}"></script>
6
<script>$(function() { wait_payment('{{source_url}}', '{{transaction_id}}'); });</script>
7

  
8

  
9
 <div>
10
   <div id="transaction-status" class="infonotice"  data-error="{% trans 'An error occured' %}"></div>
11
   <p><a href="{{source_url}}">{% trans "Go back to your form" %}</a></p>
12
   </p>
13
 </div>
14
{% 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
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, regie, [item], [], next_url, email, firstname, lastname, basket=False
494
        )
451 495

  
452 496

  
453 497
class PaymentException(Exception):
......
597 641

  
598 642
    def handle_return(self, request, backend_response, **kwargs):
599 643
        transaction = None
644
        basket_item_id = request.GET.get('item-id')
600 645
        try:
601 646
            transaction = self.handle_response(request, backend_response, **kwargs)
647
            messages.error(request, _('We are sorry but the payment service '
648
                                      'failed to provide a correct answer.'))
649

  
602 650
        except UnsignedPaymentException as e:
603 651
            # some payment backends do not sign return URLs, don't mark this as
604 652
            # an error, they will provide a notification to the callback
......
607 655
        except PaymentException as e:
608 656
            messages.error(request, _('We are sorry but the payment service '
609 657
                                      'failed to provide a correct answer.'))
610
            return HttpResponseRedirect(get_basket_url())
658
            url = get_basket_url()
659
            if basket_item_id:
660
                url = get_single_item_payment_status_view(basket_item_id)
661
            return HttpResponseRedirect(url)
611 662

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

  
817

  
818
class BasketItemPaymentStatusView(View):
819

  
820
    http_method_names = ['get']
821

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

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

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

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

  
853
        if transaction and transaction.is_paid():
854
            return HttpResponseRedirect(item.source_url)
855

  
856
        extra_context_data = getattr(request, 'extra_context_data', {})
857
        extra_context_data['transaction_id'] = ''
858
        if transaction:
859
            extra_context_data['transaction_id'] = \
860
                aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))
861
        extra_context_data['source_url'] = item.source_url
862
        request.extra_context_data = extra_context_data
863
        return publish_page(request, page, template_name=template_name)
864

  
865

  
866
class TransactionStatusApiView(View):
867

  
868
    http_method_names = ['get']
869

  
870
    def get(self, request, *args, **kwargs):
871
        signature = kwargs.get('signature')
872
        try:
873
            transaction_id = aes_hex_decrypt(settings.SECRET_KEY, signature)
874
        except DecryptionError:
875
            return HttpResponseForbidden(_('Invalid transaction.'))
876

  
877
        try:
878
            transaction = Transaction.objects.get(pk=transaction_id)
879
        except Transaction.DoesNotExist:
880
            return HttpResponseForbidden(_('Invalid transaction.'))
881

  
882
        user = request.user if request.user.is_authenticated() else None
883
        error_msg = 'Transaction does not belong to the requesting user'
884
        if user and transaction.user and user != transaction.user:
885
            return HttpResponseForbidden(error_msg)
886
        if not user and transaction.user:
887
            return HttpResponseForbidden(error_msg)
888

  
889
        msg = _('Wait a moment, we are waiting for bank notification')
890
        transaction_error = False
891
        redirect = False
892

  
893
        if transaction.is_paid():
894
            redirect = True
895
            msg = _('Paiment received')
896

  
897
        elif transaction.status in (
898
                eopayment.CANCELLED, eopayment.ERROR, eopayment.DENIED, EXPIRED
899
        ):
900
            transaction_error = True
901
            msg = _('Transaction error, you can go back to your form and make another payment')
902

  
903
        return JsonResponse(
904
            {'transaction_error': transaction_error, 'redirect': redirect, 'msg': msg}
905
        )
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 resp.location == source_url
1116

  
1117

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

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

  
1133
    wait_response = {
1134
        'transaction_error': False,
1135
        'redirect': False,
1136
        'msg': 'Wait a moment, we are waiting for bank notification'
1137
    }
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
    # anonymous user on other user transaction transaction: NOTOK
1170
    transaction = Transaction.objects.create(
1171
        amount=Decimal('10.0'), regie=regie, status=0, user=user)
1172
    url = reverse(
1173
        'api-transaction-status',
1174
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1175
    )
1176
    resp = app.get(url, status=403)
1177
    assert 'Transaction does not belong to the requesting user' in resp.text
1178

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

  
1193
    # transaction error
1194
    transaction = Transaction.objects.create(
1195
        amount=Decimal('10.0'), regie=regie, status=eopayment.ERROR
1196
    )
1197
    url = reverse(
1198
        'api-transaction-status',
1199
        kwargs={'signature': aes_hex_encrypt(settings.SECRET_KEY, str(transaction.pk))}
1200
    )
1201
    resp = app.get(url)
1202
    assert resp.json == {
1203
        'transaction_error': True,
1204
        'redirect': False,
1205
        'msg': 'Transaction error, you can go back to your form and make another payment'
1206
    }
1207

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