Projet

Général

Profil

0001-lingo-add-management-command-to-retry-payment-notifi.patch

Frédéric Péters, 17 août 2017 09:47

Télécharger (6,86 ko)

Voir les différences:

Subject: [PATCH] lingo: add management command to retry payment notifications
 (#6638)

 .../lingo/management/commands/notify_payments.py   | 40 ++++++++++++++++
 combo/apps/lingo/views.py                          |  6 +--
 debian/cron.hourly                                 |  1 +
 tests/test_lingo_payment.py                        | 53 ++++++++++++++++++++++
 4 files changed, 97 insertions(+), 3 deletions(-)
 create mode 100644 combo/apps/lingo/management/commands/notify_payments.py
combo/apps/lingo/management/commands/notify_payments.py
1
# -*- coding: utf-8 -*-
2
#
3
# lingo - basket and payment system
4
# Copyright (C) 2017  Entr'ouvert
5
#
6
# This program is free software: you can redistribute it and/or modify it
7
# under the terms of the GNU Affero General Public License as published
8
# by the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU Affero General Public License for more details.
15
#
16
# You should have received a copy of the GNU Affero General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18

  
19
import datetime
20
import logging
21

  
22
from django.core.management.base import BaseCommand
23
from django.utils import timezone
24

  
25
from combo.apps.lingo.models import BasketItem
26

  
27

  
28
class Command(BaseCommand):
29

  
30
    def handle(self, *args, **kwargs):
31
        logger = logging.getLogger(__name__)
32
        now = timezone.now()
33
        for item in BasketItem.objects.filter(
34
                notification_date__isnull=True,
35
                cancellation_date__isnull=True,
36
                payment_date__lt=now-datetime.timedelta(minutes=5)):
37
            try:
38
                item.notify_payment()
39
            except:
40
                logger.exception('error in async notification for basket item %s', item.id)
combo/apps/lingo/views.py
436 436
            item.save()
437 437
            try:
438 438
                item.notify_payment()
439
            except RuntimeError:
440
                # ignore errors, it should be retried later on if it fails
441
                pass
439
            except:
440
                # ignore errors, it will be retried later on if it fails
441
                logger.exception('error in sync notification for basket item %s', item.id)
442 442
        regie.compute_extra_fees(user=transaction.user)
443 443
        if transaction.remote_items:
444 444
            transaction.first_notify_remote_items_of_payments()
debian/cron.hourly
3 3
su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_transactions --all-tenants"
4 4
su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_momo_manifest --all-tenants -v0"
5 5
su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_index --remove --all-tenants -v0"
6
su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command notify_payments --all-tenants -v0"
tests/test_lingo_payment.py
21 21
from combo.apps.lingo.models import (Regie, BasketItem, Transaction,
22 22
        TransactionOperation, RemoteItem, EXPIRED, LingoBasketCell)
23 23
from combo.apps.lingo.management.commands.update_transactions import Command as UpdateTransactionsCommand
24
from combo.apps.lingo.management.commands.notify_payments import Command as NotifyPaymentsCommand
24 25
from combo.utils import sign_url
25 26

  
26 27
pytestmark = pytest.mark.django_db
......
573 574
    resp = client.get(callback_url, data)
574 575
    assert resp.status_code == 200
575 576
    assert Transaction.objects.get(order_id=transaction_id).status == 3
577

  
578
def test_payment_callback_error(regie, user):
579
    item = BasketItem.objects.create(user=user, regie=regie,
580
                        subject='test_item', amount='10.5',
581
                        source_url='http://example.org/testitem/')
582
    login()
583
    resp = client.post(reverse('lingo-pay'), {'regie': regie.pk})
584
    assert resp.status_code == 302
585
    location = resp.get('location')
586
    parsed = urlparse.urlparse(location)
587
    qs = urlparse.parse_qs(parsed.query)
588
    transaction_id = qs['transaction_id'][0]
589
    data = {'transaction_id': transaction_id, 'signed': True,
590
            'amount': qs['amount'][0], 'ok': True}
591
    assert data['amount'] == '10.50'
592

  
593
    # call callback with GET
594
    callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
595
    with mock.patch('combo.utils.RequestsSession.request') as request:
596
        mock_response = mock.Mock()
597
        def kaboom():
598
            raise Exception('kaboom')
599
        mock_response.status_code = 500
600
        mock_response.raise_for_status = kaboom
601
        request.return_value = mock_response
602
        get_resp = client.get(callback_url, data)
603
        url = request.call_args[0][1]
604
        assert url.startswith('http://example.org/testitem/jump/trigger/paid')
605
    assert get_resp.status_code == 200
606
    assert Transaction.objects.get(order_id=transaction_id).status == 3
607
    assert BasketItem.objects.get(id=item.id).payment_date
608
    assert not BasketItem.objects.get(id=item.id).notification_date
609

  
610
    # too soon
611
    NotifyPaymentsCommand().handle()
612
    assert BasketItem.objects.get(id=item.id).payment_date
613
    assert not BasketItem.objects.get(id=item.id).notification_date
614

  
615
    # fake delay
616
    basket_item = BasketItem.objects.get(id=item.id)
617
    basket_item.payment_date = timezone.now() - timedelta(hours=1)
618
    basket_item.save()
619

  
620
    with mock.patch('combo.utils.RequestsSession.request') as request:
621
        mock_response = mock.Mock()
622
        mock_response.status_code = 200
623
        request.return_value = mock_response
624
        NotifyPaymentsCommand().handle()
625
        url = request.call_args[0][1]
626
        assert url.startswith('http://example.org/testitem/jump/trigger/paid')
627
    assert BasketItem.objects.get(id=item.id).payment_date
628
    assert BasketItem.objects.get(id=item.id).notification_date
576
-