From 5aa7740c453b5013b10ce0d60ce8e3a9273d596d Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 1 Sep 2016 14:19:53 +0200 Subject: [PATCH] Stripe: Removed global webhook, added test for webhook --- src/pretix/plugins/stripe/urls.py | 6 - src/pretix/plugins/stripe/views.py | 18 +-- src/tests/plugins/stripe/__init__.py | 0 src/tests/plugins/stripe/test_webhook.py | 135 +++++++++++++++++++++++ 4 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 src/tests/plugins/stripe/__init__.py create mode 100644 src/tests/plugins/stripe/test_webhook.py diff --git a/src/pretix/plugins/stripe/urls.py b/src/pretix/plugins/stripe/urls.py index a37398dbd7..473dbba0e9 100644 --- a/src/pretix/plugins/stripe/urls.py +++ b/src/pretix/plugins/stripe/urls.py @@ -2,12 +2,6 @@ from django.conf.urls import include, url from .views import webhook -urlpatterns = [ - url(r'^stripe/', include([ - url(r'^webhook/$', webhook, name='webhook'), - ])), -] - event_patterns = [ url(r'^stripe/', include([ url(r'^webhook/$', webhook, name='webhook'), diff --git a/src/pretix/plugins/stripe/views.py b/src/pretix/plugins/stripe/views.py index 1aae7a7605..fc4078b72d 100644 --- a/src/pretix/plugins/stripe/views.py +++ b/src/pretix/plugins/stripe/views.py @@ -9,12 +9,14 @@ from django.views.decorators.http import require_POST from pretix.base.models import Event, Order from pretix.base.services.orders import mark_order_refunded from pretix.plugins.stripe.payment import Stripe +from pretix.presale.utils import event_view logger = logging.getLogger('pretix.plugins.stripe') @csrf_exempt @require_POST +@event_view def webhook(request, *args, **kwargs): event_json = json.loads(request.body.decode('utf-8')) @@ -28,7 +30,7 @@ def webhook(request, *args, **kwargs): elif event_json['data']['object']['object'] == "dispute": charge_id = event_json['data']['object']['charge'] else: - return HttpResponse("unhandled", status=200) + return HttpResponse("Not interested in this data type", status=200) prov = Stripe(request.event) prov._init_api() @@ -40,21 +42,19 @@ def webhook(request, *args, **kwargs): metadata = charge['metadata'] if 'event' not in metadata: - return HttpResponse('Event not given', status=200) + return HttpResponse('Event not given in charge metadata', status=200) + + if int(metadata['event']) != request.event.pk: + return HttpResponse('Not interested in this event', status=200) try: - event = Event.objects.get(id=metadata['event']) - except Event.DoesNotExist: - return HttpResponse('Event not found', status=200) - - try: - order = event.orders.objects.get(id=metadata['order']) + order = request.event.orders.get(id=metadata['order']) except Order.DoesNotExist: return HttpResponse('Order not found', status=200) order.log_action('pretix.plugins.stripe.event', data=event_json) - if order.status == Order.STATUS_PAID and (charge['refunded']['total_count'] > 0 or charge['dispute']): + if order.status == Order.STATUS_PAID and (len(charge['refunds']) > 0 or charge['dispute']): mark_order_refunded(order) return HttpResponse(status=200) diff --git a/src/tests/plugins/stripe/__init__.py b/src/tests/plugins/stripe/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tests/plugins/stripe/test_webhook.py b/src/tests/plugins/stripe/test_webhook.py new file mode 100644 index 0000000000..964ab8bdad --- /dev/null +++ b/src/tests/plugins/stripe/test_webhook.py @@ -0,0 +1,135 @@ +import json +from datetime import timedelta +from decimal import Decimal + +import pytest +from django.utils.timezone import now + +from pretix.base.models import Event, Order, Organizer + + +@pytest.fixture +def env(): + o = Organizer.objects.create(name='Dummy', slug='dummy') + event = Event.objects.create( + organizer=o, name='Dummy', slug='dummy', + date_from=now(), live=True + ) + o1 = Order.objects.create( + code='FOOBAR', event=event, email='dummy@dummy.test', + status=Order.STATUS_PAID, + datetime=now(), expires=now() + timedelta(days=10), + total=Decimal('13.37'), payment_provider='banktransfer' + ) + return event, o1 + + +def get_test_charge(order: Order): + return { + "id": "ch_18TY6GGGWE2Is8TZHanef25", + "object": "charge", + "amount": 1337, + "amount_refunded": 1000, + "application_fee": None, + "balance_transaction": "txn_18TY6GGGWE2Ias8TkwY6o51W", + "captured": True, + "created": 1467642664, + "currency": "eur", + "customer": None, + "description": None, + "destination": None, + "dispute": None, + "failure_code": None, + "failure_message": None, + "fraud_details": {}, + "invoice": None, + "livemode": False, + "metadata": { + "code": order.code, + "order": str(order.pk), + "event": str(order.event.pk), + }, + "order": None, + "paid": True, + "receipt_email": None, + "receipt_number": None, + "refunded": False, + "refunds": [], + "shipping": None, + "source": { + "id": "card_18TY5wGGWE2Ias8Td38PjyPy", + "object": "card", + "address_city": None, + "address_country": None, + "address_line1": None, + "address_line1_check": None, + "address_line2": None, + "address_state": None, + "address_zip": None, + "address_zip_check": None, + "brand": "Visa", + "country": "US", + "customer": None, + "cvc_check": "pass", + "dynamic_last4": None, + "exp_month": 12, + "exp_year": 2016, + "fingerprint": "FNbGTMaFvhRU2Y0E", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": "Carl Cardholder", + "tokenization_method": None, + }, + "source_transfer": None, + "statement_descriptor": None, + "status": "succeeded" + } + + +@pytest.mark.django_db +def test_webhook_partial_refund(env, client, monkeypatch): + charge = get_test_charge(env[1]) + charge['refunds'] = { + "object": "list", + "data": [ + { + "id": "re_18otImGGWE2Ias8TY0QvwKYQ", + "object": "refund", + "amount": "12300", + "balance_transaction": "txn_18otImGGWE2Ias8T4fLOxesC", + "charge": "ch_18TY6GGGWE2Ias8TZHanef25", + "created": 1472729052, + "currency": "eur", + "metadata": {}, + "reason": None, + "receipt_number": None, + "status": "succeeded" + } + ] + } + monkeypatch.setattr("stripe.Charge.retrieve", lambda *args: charge) + + client.post('/dummy/dummy/stripe/webhook/', json.dumps( + { + "id": "evt_18otImGGWE2Ias8TUyVRDB1G", + "object": "event", + "api_version": "2016-03-07", + "created": 1472729052, + "data": { + "object": { + "id": "ch_18TY6GGGWE2Ias8TZHanef25", + "object": "charge", + # Rest of object is ignored anway + } + }, + "livemode": True, + "pending_webhooks": 1, + "request": "req_977XOWC8zk51Z9", + "type": "charge.refunded" + } + ), content_type='application_json') + + order = env[1] + order.refresh_from_db() + assert order.status == Order.STATUS_REFUNDED