Projet

Général

Profil

0003-lingo-make-payment-related-views-compliant-with-Paym.patch

Emmanuel Cazenave, 13 mai 2019 17:45

Télécharger (18,7 ko)

Voir les différences:

Subject: [PATCH 3/4] lingo: make payment related views compliant with
 PaymentBackend (#32441)

 combo/apps/lingo/urls.py         |  4 ++
 combo/apps/lingo/views.py        | 34 +++++++++-----
 tests/test_lingo_payment.py      | 80 ++++++++++++++++++++++----------
 tests/test_lingo_remote_regie.py | 16 +++++--
 4 files changed, 94 insertions(+), 40 deletions(-)
combo/apps/lingo/urls.py
49 49
    url(r'^lingo/pay$', PayView.as_view(), name='lingo-pay'),
50 50
    url(r'^lingo/cancel/(?P<pk>\w+)/$', CancelItemView.as_view(), name='lingo-cancel-item'),
51 51
    url(r'^lingo/callback/(?P<regie_pk>\w+)/$', CallbackView.as_view(), name='lingo-callback'),
52
    url(r'^lingo/callback-payment-backend/(?P<payment_backend_pk>\w+)/$',
53
        CallbackView.as_view(), name='lingo-callback-payment-backend'),
52 54
    url(r'^lingo/return/(?P<regie_pk>\w+)/$', ReturnView.as_view(), name='lingo-return'),
55
    url(r'^lingo/return-payment-backend/(?P<payment_backend_pk>\w+)/$',
56
        ReturnView.as_view(), name='lingo-return-payment-backend'),
53 57
    url(r'^manage/lingo/', decorated_includes(manager_required,
54 58
        include(lingo_manager_urls))),
55 59
    url(r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_crypto_id>[\w,-]+)/pdf$',
combo/apps/lingo/views.py
43 43
from combo.profile.utils import get_user_from_name_id
44 44

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

  
48
def get_eopayment_object(request, regie):
49
    options = regie.service_options
48
def get_eopayment_object(request, regie_or_payment_backend):
49
    payment_backend = regie_or_payment_backend
50
    if isinstance(regie_or_payment_backend, Regie):
51
        payment_backend = regie_or_payment_backend.payment_backend
52
    options = payment_backend.service_options
50 53
    options.update({
51 54
        'automatic_return_url': request.build_absolute_uri(
52
                reverse('lingo-callback', kwargs={'regie_pk': regie.id})),
55
                reverse('lingo-callback-payment-backend',
56
                        kwargs={'payment_backend_pk': payment_backend.id})),
53 57
        'normal_return_url': request.build_absolute_uri(
54
                reverse('lingo-return', kwargs={'regie_pk': regie.id})),
58
                reverse('lingo-return-payment-backend',
59
                        kwargs={'payment_backend_pk': payment_backend.id})),
55 60
    })
56
    return eopayment.Payment(regie.service, options)
61
    return eopayment.Payment(payment_backend.service, options)
57 62

  
58 63

  
59 64
def get_basket_url():
......
434 439

  
435 440
class PaymentView(View):
436 441
    def handle_response(self, request, backend_response, **kwargs):
437
        regie = Regie.objects.get(id=kwargs.get('regie_pk'))
438
        payment = get_eopayment_object(request, regie)
442
        if 'regie_pk' in kwargs:
443
            payment_backend = Regie.objects.get(id=kwargs['regie_pk']).payment_backend
444
        elif 'payment_backend_pk' in kwargs:
445
            payment_backend = PaymentBackend.objects.get(id=kwargs['payment_backend_pk'])
446
        else:
447
            raise Exception("A payment backend or regie primary key must be specified")
448

  
449
        payment = get_eopayment_object(request, payment_backend)
439 450
        logger = logging.getLogger(__name__)
440 451
        logger.info(u'received payment response: %r', backend_response)
441 452
        try:
......
484 495
                        transaction.status, payment_response.result))
485 496

  
486 497
        # check if transaction belongs to right regie
487
        if not transaction.regie == regie:
488
            logger.warning(u'received payment for inappropriate regie '
489
                            '(expecteds: %s, received: %s)' % (transaction.regie, regie))
498
        if not transaction.regie.payment_backend == payment_backend:
499
            logger.warning(u'received payment for inappropriate payment backend '
500
                            '(expecteds: %s, received: %s)' % (
501
                                transaction.regie.payment_backend, payment_backend))
490 502
            raise PaymentException('Invalid payment regie')
491 503

  
492 504
        transaction.status = payment_response.result
tests/test_lingo_payment.py
18 18
from webtest import TestApp
19 19

  
20 20
from combo.data.models import Page
21
from combo.apps.lingo.models import (Regie, BasketItem, Transaction,
22
        TransactionOperation, RemoteItem, EXPIRED, LingoBasketCell)
21
from combo.apps.lingo.models import (
22
    Regie, BasketItem, Transaction, TransactionOperation, RemoteItem, EXPIRED, LingoBasketCell,
23
    PaymentBackend)
23 24
from combo.utils import sign_url
24 25

  
25 26
from .test_manager import login
......
27 28
pytestmark = pytest.mark.django_db
28 29

  
29 30

  
31
def get_url(with_payment_backend, view_name, regie):
32
    if with_payment_backend:
33
        return reverse(
34
            view_name + '-payment-backend',
35
            kwargs={'payment_backend_pk': regie.payment_backend.id})
36
    return reverse(view_name, kwargs={'regie_pk': regie.id})
37

  
38

  
30 39
@contextmanager
31 40
def check_log(caplog, message):
32 41
    idx = len(caplog.records)
......
37 46

  
38 47
@pytest.fixture
39 48
def regie():
49
    try:
50
        payment_backend = PaymentBackend.objects.get(slug='test1')
51
    except PaymentBackend.DoesNotExist:
52
        payment_backend = PaymentBackend.objects.create(
53
            label='test1', slug='test1', service='dummy', service_options={'siret': '1234'})
40 54
    try:
41 55
        regie = Regie.objects.get(slug='test')
42 56
    except Regie.DoesNotExist:
......
45 59
        regie.slug = 'test'
46 60
        regie.description = 'test'
47 61
        regie.payment_min_amount = Decimal(4.5)
48
        regie.service = 'dummy'
49
        regie.service_options = {'siret': '1234'}
62
        regie.payment_backend = payment_backend
50 63
        regie.save()
51 64
    return regie
52 65

  
......
85 98
        return settings.LINGO_API_SIGN_KEY
86 99

  
87 100
def test_default_regie():
101
    payment_backend = PaymentBackend.objects.create(label='foo', slug='foo')
88 102
    Regie.objects.all().delete()
89
    regie1 = Regie(label='foo', slug='foo')
103
    regie1 = Regie(label='foo', slug='foo', payment_backend=payment_backend)
90 104
    regie1.save()
91 105
    assert bool(regie1.is_default) is True
92
    regie2 = Regie(label='bar', slug='bar')
106
    regie2 = Regie(label='bar', slug='bar', payment_backend=payment_backend)
93 107
    regie2.save()
94 108
    assert bool(regie2.is_default) is False
95 109

  
......
121 135
    resp = resp.form.submit()
122 136
    assert resp.status_code == 302
123 137

  
124
def test_successfull_items_payment(app, basket_page, regie, user):
138

  
139
@pytest.mark.parametrize('with_payment_backend', [False, True])
140
def test_successfull_items_payment(app, basket_page, regie, user, with_payment_backend):
125 141
    items = {'item1': {'amount': '10.5', 'source_url': 'http://example.org/item/1'},
126 142
             'item2': {'amount': '42', 'source_url': 'http://example.org/item/2'},
127 143
             'item3': {'amount': '100', 'source_url': 'http://example.org/item/3'},
......
145 161
            'ok': True, 'reason': 'Paid'}
146 162
    # make sure return url is the user return URL
147 163
    assert urlparse.urlparse(qs['return_url'][0]).path.startswith(
148
            reverse('lingo-return', kwargs={'regie_pk': regie.id}))
164
            reverse('lingo-return-payment-backend',
165
                    kwargs={'payment_backend_pk': regie.payment_backend.id}))
149 166
    # simulate successful call to callback URL
150 167
    with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
151
        resp = app.get(reverse('lingo-callback', kwargs={'regie_pk': regie.id}), params=args)
168
        resp = app.get(get_url(with_payment_backend, 'lingo-callback', regie), params=args)
152 169
    assert resp.status_code == 200
153 170
    # simulate successful return URL
154 171
    resp = app.get(qs['return_url'][0], params=args)
......
158 175
    assert 'Your payment has been succesfully registered.' in resp.text
159 176

  
160 177
def test_add_amount_to_basket(app, key, regie, user):
161
    other_regie = Regie(label='test2', slug='test2', service='dummy', service_options={'siret': '1234'})
178
    payment_backend = PaymentBackend.objects.create(
179
            label='test2', slug='test2', service='dummy', service_options={'siret': '1234'})
180
    other_regie = Regie(label='test2', slug='test2', payment_backend=payment_backend)
162 181
    other_regie.save()
163 182

  
164 183
    user_email = 'foo@example.com'
......
479 498
    app.get(reverse('lingo-cancel-item', kwargs={'pk': basket_item_id}), status=404)
480 499
    app.post(reverse('lingo-cancel-item', kwargs={'pk': basket_item_id}), status=403)
481 500

  
482
def test_payment_callback(app, basket_page, regie, user):
501

  
502
@pytest.mark.parametrize('with_payment_backend', [False, True])
503
def test_payment_callback(app, basket_page, regie, user, with_payment_backend):
483 504
    page = Page(title='xxx', slug='index', template_name='standard')
484 505
    page.save()
485 506
    item = BasketItem.objects.create(user=user, regie=regie,
......
497 518
    assert data['amount'] == '10.50'
498 519

  
499 520
    # call callback with GET
500
    callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
521
    callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
501 522
    with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
502 523
        get_resp = app.get(callback_url, params=data)
503 524
        url = request.call_args[0][1]
......
526 547
    assert Transaction.objects.get(order_id=transaction_id).status == 3
527 548

  
528 549
    # call return view
529
    get_resp = app.get(reverse('lingo-return', kwargs={'regie_pk': regie.pk}), params=data)
550
    return_url = get_url(with_payment_backend, 'lingo-return', regie)
551
    get_resp = app.get(return_url, params=data)
530 552
    assert get_resp.status_code == 302
531 553
    resp = app.get(get_resp['Location'])
532 554
    assert 'Your payment has been succesfully registered.' in resp.text
533 555

  
534
def test_payment_callback_no_regie(app, basket_page, regie, user):
556
@pytest.mark.parametrize('with_payment_backend', [False, True])
557
def test_payment_callback_no_regie(app, basket_page, regie, user, with_payment_backend):
535 558
    item = BasketItem.objects.create(user=user, regie=regie,
536 559
                        subject='test_item', amount='10.5',
537 560
                        source_url='http://example.org/testitem/')
......
547 570
    assert data['amount'] == '10.50'
548 571

  
549 572
    # call callback with GET
550
    callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
573
    callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
551 574
    with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
552 575
        get_resp = app.get(callback_url, params=data)
553 576
        url = request.call_args[0][1]
......
569 592
            'amount': qs['amount'][0], 'ok': True}
570 593
    assert data['amount'] == '11.50'
571 594

  
572
def test_nonexisting_transaction(app, regie, user):
595

  
596
@pytest.mark.parametrize('with_payment_backend', [False, True])
597
def test_nonexisting_transaction(app, regie, user, with_payment_backend):
573 598
    app = login(app)
574 599
    data = {'transaction_id': 'unknown', 'signed': True,
575 600
            'amount': '23', 'ok': True}
576 601

  
577 602
    # call callback with GET
578
    callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
603
    callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
579 604
    app.get(callback_url, params=data, status=404)
580 605

  
581
def test_payment_callback_waiting(app, basket_page, regie, user):
606

  
607
@pytest.mark.parametrize('with_payment_backend', [False, True])
608
def test_payment_callback_waiting(app, basket_page, regie, user, with_payment_backend):
582 609
    item = BasketItem.objects.create(user=user, regie=regie,
583 610
                        subject='test_item', amount='10.5',
584 611
                        source_url='http://example.org/testitem/')
......
594 621
    assert data['amount'] == '10.50'
595 622

  
596 623
    # callback with WAITING state
597
    callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
624
    callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
598 625
    resp = app.get(callback_url, params=data)
599 626
    assert resp.status_code == 200
600 627
    assert Transaction.objects.get(order_id=transaction_id).status == eopayment.WAITING
......
615 642
    assert BasketItem.objects.get(id=item.id).payment_date
616 643
    assert BasketItem.get_items_to_be_paid(user).count() == 0
617 644

  
618
def test_payment_no_callback_just_return(caplog, app, basket_page, regie, user):
645

  
646
@pytest.mark.parametrize('with_payment_backend', [False, True])
647
def test_payment_no_callback_just_return(
648
        caplog, app, basket_page, regie, user, with_payment_backend):
619 649
    item = BasketItem.objects.create(user=user, regie=regie,
620 650
                        subject='test_item', amount='10.5',
621 651
                        source_url='http://example.org/testitem/')
......
632 662

  
633 663
    # call return with unsigned POST
634 664
    with check_log(caplog, 'received unsigned payment'):
635
        return_url = reverse('lingo-return', kwargs={'regie_pk': regie.id})
665
        return_url = get_url(with_payment_backend, 'lingo-return', regie)
636 666
        with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
637 667
            get_resp = app.post(return_url, params=data)
638 668
            assert request.call_count == 0
......
654 684

  
655 685
    # call return with signed POST
656 686
    data['signed'] = True
657
    return_url = reverse('lingo-return', kwargs={'regie_pk': regie.id})
687
    return_url = get_url(with_payment_backend, 'lingo-return', regie)
658 688
    with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
659 689
        get_resp = app.post(return_url, params=data)
660 690
        url = request.call_args[0][1]
......
827 857
    assert resp.status_code == 200
828 858
    assert Transaction.objects.get(order_id=transaction_id).status == 3
829 859

  
830
def test_payment_callback_error(app, basket_page, regie, user):
860

  
861
@pytest.mark.parametrize('with_payment_backend', [False, True])
862
def test_payment_callback_error(app, basket_page, regie, user, with_payment_backend):
831 863
    item = BasketItem.objects.create(user=user, regie=regie,
832 864
                        subject='test_item', amount='10.5',
833 865
                        source_url='http://example.org/testitem/')
......
843 875
    assert data['amount'] == '10.50'
844 876

  
845 877
    # call callback with GET
846
    callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
878
    callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
847 879
    with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
848 880
        mock_response = mock.Mock()
849 881
        def kaboom():
tests/test_lingo_remote_regie.py
20 20
from combo.utils import check_query, aes_hex_encrypt
21 21
from combo.data.models import Page
22 22
from combo.apps.lingo.models import (Regie, ActiveItems, ItemsHistory, SelfDeclaredInvoicePayment,
23
                                     Transaction, BasketItem)
23
                                     Transaction, BasketItem, PaymentBackend)
24 24

  
25 25
pytestmark = pytest.mark.django_db
26 26

  
......
55 55

  
56 56
@pytest.fixture
57 57
def remote_regie():
58
    try:
59
        payment_backend = PaymentBackend.objects.get(slug='test1')
60
    except PaymentBackend.DoesNotExist:
61
        payment_backend = PaymentBackend.objects.create(
62
            label='test1', slug='test1', service='dummy', service_options={'siret': '1234'})
58 63
    try:
59 64
        regie = Regie.objects.get(slug='remote')
60 65
    except Regie.DoesNotExist:
......
63 68
        regie.slug = 'remote'
64 69
        regie.description = 'remote'
65 70
        regie.payment_min_amount = Decimal(2.0)
66
        regie.service = 'dummy'
67
        regie.service_options = {'siret': '1234'}
71
        regie.payment_backend = payment_backend
68 72
        regie.webservice_url = 'http://example.org/regie' # is_remote
69 73
        regie.save()
70 74
    return regie
......
204 208
            'ok': True, 'reason': 'Paid'}
205 209
    # make sure return url is the user return URL
206 210
    assert urlparse.urlparse(qs['return_url'][0]).path.startswith(
207
        reverse('lingo-return', kwargs={'regie_pk': remote_regie.id}))
211
        reverse('lingo-return-payment-backend',
212
                kwargs={'payment_backend_pk': remote_regie.payment_backend.id}))
208 213
    # simulate successful return URL
209 214
    resp = app.get(qs['return_url'][0], params=args)
210 215
    assert resp.status_code == 302
......
326 331
            'ok': True, 'reason': 'Paid'}
327 332
    # make sure return url is the user return URL
328 333
    assert urlparse.urlparse(qs['return_url'][0]).path.startswith(
329
        reverse('lingo-return', kwargs={'regie_pk': remote_regie.id}))
334
        reverse('lingo-return-payment-backend',
335
                kwargs={'payment_backend_pk': remote_regie.payment_backend.id}))
330 336
    # simulate payment failure
331 337
    mock_get.side_effect = ConnectionError('where is my hostname?')
332 338
    resp = app.get(qs['return_url'][0], params=args)
333
-