Projet

Général

Profil

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

Emmanuel Cazenave, 09 mai 2019 17:52

Télécharger (19,2 ko)

Voir les différences:

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

 combo/apps/lingo/urls.py         |  2 +
 combo/apps/lingo/views.py        | 37 +++++++++-------
 tests/test_lingo_payment.py      | 76 ++++++++++++++++++++++----------
 tests/test_lingo_remote_regie.py | 14 +++---
 4 files changed, 85 insertions(+), 44 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-pb/(?P<pb_pk>\w+)/$', CallbackView.as_view(), name='lingo-callback-pb'),
52 53
    url(r'^lingo/return/(?P<regie_pk>\w+)/$', ReturnView.as_view(), name='lingo-return'),
54
    url(r'^lingo/return-pb/(?P<pb_pk>\w+)/$', ReturnView.as_view(), name='lingo-return-pb'),
53 55
    url(r'^manage/lingo/', decorated_includes(manager_required,
54 56
        include(lingo_manager_urls))),
55 57
    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, payment_backend):
49
    options = payment_backend.service_options
50 50
    options.update({
51 51
        'automatic_return_url': request.build_absolute_uri(
52
                reverse('lingo-callback', kwargs={'regie_pk': regie.id})),
52
                reverse('lingo-callback-pb', kwargs={'pb_pk': payment_backend.id})),
53 53
        'normal_return_url': request.build_absolute_uri(
54
                reverse('lingo-return', kwargs={'regie_pk': regie.id})),
54
                reverse('lingo-return-pb', kwargs={'pb_pk': payment_backend.id})),
55 55
    })
56
    return eopayment.Payment(regie.service, options)
56
    return eopayment.Payment(payment_backend.service, options)
57 57

  
58 58

  
59 59
def get_basket_url():
......
228 228
                           request.GET['transaction_id'])
229 229
            raise Http404
230 230

  
231
        payment = get_eopayment_object(request, transaction.regie)
231
        payment = get_eopayment_object(request, transaction.regie.payment_backend)
232 232
        amount = request.GET['amount']
233 233

  
234 234
        logger.info(u'validating amount %s for transaction %s', amount, smart_text(transaction.id))
......
269 269
                           request.GET['transaction_id'])
270 270
            raise Http404
271 271

  
272
        payment = get_eopayment_object(request, transaction.regie)
272
        payment = get_eopayment_object(request, transaction.regie.payment_backend)
273 273
        amount = request.GET['amount']
274 274

  
275 275
        logger.info(u'cancelling amount %s for transaction %s', amount, smart_text(transaction.id))
......
326 326
        transaction.status = 0
327 327
        transaction.amount = total_amount
328 328

  
329
        payment = get_eopayment_object(request, regie)
329
        payment = get_eopayment_object(request, regie.payment_backend)
330 330
        kwargs = {
331 331
            'email': email, 'first_name': firstname, 'last_name': lastname
332 332
        }
......
434 434

  
435 435
class PaymentView(View):
436 436
    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)
437
        if 'regie_pk' in kwargs:
438
            payment_backend = Regie.objects.get(id=kwargs['regie_pk']).payment_backend
439
        elif 'pb_pk' in kwargs:
440
            payment_backend = PaymentBackend.objects.get(id=kwargs['pb_pk'])
441
        else:
442
            raise Exception("A payment backend or regie primary key must be specified")
443

  
444
        payment = get_eopayment_object(request, payment_backend)
439 445
        logger = logging.getLogger(__name__)
440 446
        logger.info(u'received payment response: %r', backend_response)
441 447
        try:
......
484 490
                        transaction.status, payment_response.result))
485 491

  
486 492
        # 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))
493
        if not transaction.regie.payment_backend == payment_backend:
494
            logger.warning(u'received payment for inappropriate payment backend '
495
                            '(expecteds: %s, received: %s)' % (
496
                                transaction.regie.payment_backend, payment_backend))
490 497
            raise PaymentException('Invalid payment regie')
491 498

  
492 499
        transaction.status = payment_response.result
......
517 524
            except:
518 525
                # ignore errors, it will be retried later on if it fails
519 526
                logger.exception('error in sync notification for basket item %s', item.id)
520
        regie.compute_extra_fees(user=transaction.user)
527

  
521 528
        if transaction.remote_items:
522 529
            transaction.first_notify_remote_items_of_payments()
523 530

  
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_pb, view_name, regie):
32
    if with_pb:
33
        return reverse(view_name + '-pb', kwargs={'pb_pk': regie.payment_backend.id})
34
    return reverse(view_name, kwargs={'regie_pk': regie.id})
35

  
36

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

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

  
......
85 96
        return settings.LINGO_API_SIGN_KEY
86 97

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

  
......
121 133
    resp = resp.form.submit()
122 134
    assert resp.status_code == 302
123 135

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

  
137
@pytest.mark.parametrize('with_pb', [False, True])
138
def test_successfull_items_payment(app, basket_page, regie, user, with_pb):
125 139
    items = {'item1': {'amount': '10.5', 'source_url': 'http://example.org/item/1'},
126 140
             'item2': {'amount': '42', 'source_url': 'http://example.org/item/2'},
127 141
             'item3': {'amount': '100', 'source_url': 'http://example.org/item/3'},
......
145 159
            'ok': True, 'reason': 'Paid'}
146 160
    # make sure return url is the user return URL
147 161
    assert urlparse.urlparse(qs['return_url'][0]).path.startswith(
148
            reverse('lingo-return', kwargs={'regie_pk': regie.id}))
162
            reverse('lingo-return-pb', kwargs={'pb_pk': regie.payment_backend.id}))
149 163
    # simulate successful call to callback URL
150 164
    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)
165
        resp = app.get(get_url(with_pb, 'lingo-callback', regie), params=args)
152 166
    assert resp.status_code == 200
153 167
    # simulate successful return URL
154 168
    resp = app.get(qs['return_url'][0], params=args)
......
158 172
    assert 'Your payment has been succesfully registered.' in resp.text
159 173

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

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

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

  
499
@pytest.mark.parametrize('with_pb', [False, True])
500
def test_payment_callback(app, basket_page, regie, user, with_pb):
483 501
    page = Page(title='xxx', slug='index', template_name='standard')
484 502
    page.save()
485 503
    item = BasketItem.objects.create(user=user, regie=regie,
......
497 515
    assert data['amount'] == '10.50'
498 516

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

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

  
534
def test_payment_callback_no_regie(app, basket_page, regie, user):
553
@pytest.mark.parametrize('with_pb', [False, True])
554
def test_payment_callback_no_regie(app, basket_page, regie, user, with_pb):
535 555
    item = BasketItem.objects.create(user=user, regie=regie,
536 556
                        subject='test_item', amount='10.5',
537 557
                        source_url='http://example.org/testitem/')
......
547 567
    assert data['amount'] == '10.50'
548 568

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

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

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

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

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

  
604
@pytest.mark.parametrize('with_pb', [False, True])
605
def test_payment_callback_waiting(app, basket_page, regie, user, with_pb):
582 606
    item = BasketItem.objects.create(user=user, regie=regie,
583 607
                        subject='test_item', amount='10.5',
584 608
                        source_url='http://example.org/testitem/')
......
594 618
    assert data['amount'] == '10.50'
595 619

  
596 620
    # callback with WAITING state
597
    callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
621
    callback_url = get_url(with_pb, 'lingo-callback', regie)
598 622
    resp = app.get(callback_url, params=data)
599 623
    assert resp.status_code == 200
600 624
    assert Transaction.objects.get(order_id=transaction_id).status == eopayment.WAITING
......
615 639
    assert BasketItem.objects.get(id=item.id).payment_date
616 640
    assert BasketItem.get_items_to_be_paid(user).count() == 0
617 641

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

  
643
@pytest.mark.parametrize('with_pb', [False, True])
644
def test_payment_no_callback_just_return(caplog, app, basket_page, regie, user, with_pb):
619 645
    item = BasketItem.objects.create(user=user, regie=regie,
620 646
                        subject='test_item', amount='10.5',
621 647
                        source_url='http://example.org/testitem/')
......
632 658

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

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

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

  
857
@pytest.mark.parametrize('with_pb', [False, True])
858
def test_payment_callback_error(app, basket_page, regie, user, with_pb):
831 859
    item = BasketItem.objects.create(user=user, regie=regie,
832 860
                        subject='test_item', amount='10.5',
833 861
                        source_url='http://example.org/testitem/')
......
843 871
    assert data['amount'] == '10.50'
844 872

  
845 873
    # call callback with GET
846
    callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
874
    callback_url = get_url(with_pb, 'lingo-callback', regie)
847 875
    with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
848 876
        mock_response = mock.Mock()
849 877
        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
        pb = PaymentBackend.objects.get(slug='test1')
60
    except PaymentBackend.DoesNotExist:
61
        pb = 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 = pb
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-pb', kwargs={'pb_pk': remote_regie.payment_backend.id}))
208 212
    # simulate successful return URL
209 213
    resp = app.get(qs['return_url'][0], params=args)
210 214
    assert resp.status_code == 302
......
326 330
            'ok': True, 'reason': 'Paid'}
327 331
    # make sure return url is the user return URL
328 332
    assert urlparse.urlparse(qs['return_url'][0]).path.startswith(
329
        reverse('lingo-return', kwargs={'regie_pk': remote_regie.id}))
333
        reverse('lingo-return-pb', kwargs={'pb_pk': remote_regie.payment_backend.id}))
330 334
    # simulate payment failure
331 335
    mock_get.side_effect = ConnectionError('where is my hostname?')
332 336
    resp = app.get(qs['return_url'][0], params=args)
333
-