lingo: add management command to retry payment notifications (#6638)

This commit is contained in:
Frédéric Péters 2017-08-17 08:57:47 +02:00
parent 2d394d0221
commit 4961ac6bd1
4 changed files with 97 additions and 3 deletions

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
#
# lingo - basket and payment system
# Copyright (C) 2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import logging
from django.core.management.base import BaseCommand
from django.utils import timezone
from combo.apps.lingo.models import BasketItem
class Command(BaseCommand):
def handle(self, *args, **kwargs):
logger = logging.getLogger(__name__)
now = timezone.now()
for item in BasketItem.objects.filter(
notification_date__isnull=True,
cancellation_date__isnull=True,
payment_date__lt=now-datetime.timedelta(minutes=5)):
try:
item.notify_payment()
except:
logger.exception('error in async notification for basket item %s', item.id)

View File

@ -436,9 +436,9 @@ class CallbackView(View):
item.save()
try:
item.notify_payment()
except RuntimeError:
# ignore errors, it should be retried later on if it fails
pass
except:
# ignore errors, it will be retried later on if it fails
logger.exception('error in sync notification for basket item %s', item.id)
regie.compute_extra_fees(user=transaction.user)
if transaction.remote_items:
transaction.first_notify_remote_items_of_payments()

1
debian/cron.hourly vendored
View File

@ -3,3 +3,4 @@ su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command clearsessions --all
su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_transactions --all-tenants"
su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_momo_manifest --all-tenants -v0"
su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_index --remove --all-tenants -v0"
su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command notify_payments --all-tenants -v0"

View File

@ -21,6 +21,7 @@ from combo.data.models import Page
from combo.apps.lingo.models import (Regie, BasketItem, Transaction,
TransactionOperation, RemoteItem, EXPIRED, LingoBasketCell)
from combo.apps.lingo.management.commands.update_transactions import Command as UpdateTransactionsCommand
from combo.apps.lingo.management.commands.notify_payments import Command as NotifyPaymentsCommand
from combo.utils import sign_url
pytestmark = pytest.mark.django_db
@ -573,3 +574,55 @@ def test_extra_fees(key, regie, user):
resp = client.get(callback_url, data)
assert resp.status_code == 200
assert Transaction.objects.get(order_id=transaction_id).status == 3
def test_payment_callback_error(regie, user):
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
login()
resp = client.post(reverse('lingo-pay'), {'regie': regie.pk})
assert resp.status_code == 302
location = resp.get('location')
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '10.50'
# call callback with GET
callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
with mock.patch('combo.utils.RequestsSession.request') as request:
mock_response = mock.Mock()
def kaboom():
raise Exception('kaboom')
mock_response.status_code = 500
mock_response.raise_for_status = kaboom
request.return_value = mock_response
get_resp = client.get(callback_url, data)
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
assert get_resp.status_code == 200
assert Transaction.objects.get(order_id=transaction_id).status == 3
assert BasketItem.objects.get(id=item.id).payment_date
assert not BasketItem.objects.get(id=item.id).notification_date
# too soon
NotifyPaymentsCommand().handle()
assert BasketItem.objects.get(id=item.id).payment_date
assert not BasketItem.objects.get(id=item.id).notification_date
# fake delay
basket_item = BasketItem.objects.get(id=item.id)
basket_item.payment_date = timezone.now() - timedelta(hours=1)
basket_item.save()
with mock.patch('combo.utils.RequestsSession.request') as request:
mock_response = mock.Mock()
mock_response.status_code = 200
request.return_value = mock_response
NotifyPaymentsCommand().handle()
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
assert BasketItem.objects.get(id=item.id).payment_date
assert BasketItem.objects.get(id=item.id).notification_date