diff --git a/src/pretix/plugins/stripe/payment.py b/src/pretix/plugins/stripe/payment.py index c6a5e005a7..1a9968d367 100644 --- a/src/pretix/plugins/stripe/payment.py +++ b/src/pretix/plugins/stripe/payment.py @@ -93,7 +93,7 @@ class Stripe(BasePaymentProvider): try: charge = stripe.Charge.create( amount=int(order.total * 100), - currency=request.event.currency.lower(), + currency=self.event.currency.lower(), source=request.session['payment_stripe_token'], metadata={ 'order': str(order.id), @@ -104,7 +104,12 @@ class Stripe(BasePaymentProvider): idempotency_key=str(self.event.id) + order.code + request.session['payment_stripe_token'] ) except stripe.error.CardError as e: - err = e.json_body['error'] + if e.json_body: + err = e.json_body['error'] + logger.exception('Stripe error: %s' % str(err)) + else: + err = {'message': str(e)} + logger.exception('Stripe error: %s' % str(e)) messages.error(request, _('Stripe reported an error with your card: %s' % err['message'])) logger.info('Stripe card error: %s' % str(err)) order.payment_info = json.dumps({ @@ -112,12 +117,15 @@ class Stripe(BasePaymentProvider): 'message': err['message'], }) order.save() - except (stripe.error.InvalidRequestError, stripe.error.AuthenticationError, stripe.error.APIConnectionError, - stripe.error.StripeError) as e: - err = e.json_body['error'] + except stripe.error.StripeError as e: + if e.json_body: + err = e.json_body['error'] + logger.exception('Stripe error: %s' % str(err)) + else: + err = {'message': str(e)} + logger.exception('Stripe error: %s' % str(e)) messages.error(request, _('We had trouble communicating with Stripe. Please try again and get in touch ' 'with us if this problem persists.')) - logger.error('Stripe error: %s' % str(err)) order.payment_info = json.dumps({ 'error': True, 'message': err['message'], @@ -184,7 +192,12 @@ class Stripe(BasePaymentProvider): ch.refresh() except (stripe.error.InvalidRequestError, stripe.error.AuthenticationError, stripe.error.APIConnectionError) \ as e: - err = e.json_body['error'] + if e.json_body: + err = e.json_body['error'] + logger.exception('Stripe error: %s' % str(err)) + else: + err = {'message': str(e)} + logger.exception('Stripe error: %s' % str(e)) messages.error(request, _('We had trouble communicating with Stripe. Please try again and contact ' 'support if the problem persists.')) logger.error('Stripe error: %s' % str(err)) diff --git a/src/tests/plugins/stripe/test_provider.py b/src/tests/plugins/stripe/test_provider.py new file mode 100644 index 0000000000..e5ab461ae2 --- /dev/null +++ b/src/tests/plugins/stripe/test_provider.py @@ -0,0 +1,199 @@ +import json +from datetime import timedelta +from decimal import Decimal + +import pytest +from django.test import RequestFactory +from django.utils.timezone import now +from stripe import APIConnectionError, CardError, StripeError + +from pretix.base.models import Event, Order, Organizer +from pretix.plugins.stripe.payment import Stripe + + +@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_PENDING, + datetime=now(), expires=now() + timedelta(days=10), + total=Decimal('13.37'), payment_provider='banktransfer' + ) + return event, o1 + + +@pytest.fixture(autouse=True) +def no_messages(monkeypatch): + # Patch out template rendering for performance improvements + monkeypatch.setattr("django.contrib.messages.api.add_message", lambda *args, **kwargs: None) + + +@pytest.fixture +def factory(): + return RequestFactory() + + +class MockedCharge(): + def __init__(self): + self.status = '' + self.paid = False + + def refresh(self): + pass + + +@pytest.mark.django_db +def test_perform_success(env, factory, monkeypatch): + event, order = env + + def charge_create(**kwargs): + assert kwargs['amount'] == 1337 + assert kwargs['currency'] == 'eur' + assert kwargs['source'] == 'tok_189fTT2eZvKYlo2CvJKzEzeu' + c = MockedCharge() + c.status = 'succeeded' + c.paid = True + return c + + monkeypatch.setattr("stripe.Charge.create", charge_create) + prov = Stripe(event) + req = factory.post('/', { + 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', + 'stripe_last4': '4242', + 'stripe_brand': 'Visa' + }) + req.session = {} + prov.checkout_prepare(req, {}) + assert 'payment_stripe_token' in req.session + prov.payment_perform(req, order) + order.refresh_from_db() + assert order.status == Order.STATUS_PAID + + +@pytest.mark.django_db +def test_perform_card_error(env, factory, monkeypatch): + event, order = env + + def charge_create(**kwargs): + raise CardError(message='Foo', param='foo', code=100) + + monkeypatch.setattr("stripe.Charge.create", charge_create) + prov = Stripe(event) + req = factory.post('/', { + 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', + 'stripe_last4': '4242', + 'stripe_brand': 'Visa' + }) + req.session = {} + prov.checkout_prepare(req, {}) + assert 'payment_stripe_token' in req.session + prov.payment_perform(req, order) + order.refresh_from_db() + assert order.status == Order.STATUS_PENDING + + +@pytest.mark.django_db +def test_perform_stripe_error(env, factory, monkeypatch): + event, order = env + + def charge_create(**kwargs): + raise StripeError(message='Foo') + + monkeypatch.setattr("stripe.Charge.create", charge_create) + prov = Stripe(event) + req = factory.post('/', { + 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', + 'stripe_last4': '4242', + 'stripe_brand': 'Visa' + }) + req.session = {} + prov.checkout_prepare(req, {}) + assert 'payment_stripe_token' in req.session + prov.payment_perform(req, order) + order.refresh_from_db() + assert order.status == Order.STATUS_PENDING + + +@pytest.mark.django_db +def test_perform_failed(env, factory, monkeypatch): + event, order = env + + def charge_create(**kwargs): + c = MockedCharge() + c.status = 'failed' + c.paid = True + c.failure_message = 'Foo' + return c + + monkeypatch.setattr("stripe.Charge.create", charge_create) + prov = Stripe(event) + req = factory.post('/', { + 'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu', + 'stripe_last4': '4242', + 'stripe_brand': 'Visa' + }) + req.session = {} + prov.checkout_prepare(req, {}) + assert 'payment_stripe_token' in req.session + prov.payment_perform(req, order) + order.refresh_from_db() + assert order.status == Order.STATUS_PENDING + + +@pytest.mark.django_db +def test_refund_success(env, factory, monkeypatch): + event, order = env + + def charge_retr(*args, **kwargs): + def refund_create(): + pass + + c = MockedCharge() + c.refunds = MockedCharge() + c.refunds.create = refund_create + return c + + monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) + order.status = Order.STATUS_PAID + order.payment_info = json.dumps({ + 'id': 'ch_123345345' + }) + order.save() + prov = Stripe(event) + req = factory.get('/') + req.user = None + prov.order_control_refund_perform(req, order) + order.refresh_from_db() + assert order.status == Order.STATUS_REFUNDED + + +@pytest.mark.django_db +def test_refund_unavailable(env, factory, monkeypatch): + event, order = env + + def charge_retr(*args, **kwargs): + def refund_create(): + raise APIConnectionError(message='Foo') + + c = MockedCharge() + c.refunds = object() + c.refunds.create = refund_create() + return c + + monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) + order.status = Order.STATUS_PAID + order.payment_info = json.dumps({ + 'id': 'ch_123345345' + }) + order.save() + prov = Stripe(event) + req = factory.get('/') + req.user = None + prov.order_control_refund_perform(req, order) + order.refresh_from_db() + assert order.status == Order.STATUS_PAID