forked from CGM_Public/pretix_original
Stripe: Refund webhook implemented (#32)
This commit is contained in:
@@ -61,6 +61,8 @@ The provider class
|
|||||||
|
|
||||||
.. autoattribute:: settings_form_fields
|
.. autoattribute:: settings_form_fields
|
||||||
|
|
||||||
|
.. automethod:: settings_content_render
|
||||||
|
|
||||||
.. automethod:: checkout_form_render
|
.. automethod:: checkout_form_render
|
||||||
|
|
||||||
.. automethod:: checkout_form
|
.. automethod:: checkout_form
|
||||||
|
|||||||
@@ -117,6 +117,13 @@ class BasePaymentProvider:
|
|||||||
)),
|
)),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def settings_content_render(self, request: HttpRequest) -> str:
|
||||||
|
"""
|
||||||
|
When the event's administrator administrator visits the event configuration
|
||||||
|
page, this method is called. It may return HTML containing additional information
|
||||||
|
that is displayed below the form fields configured in ``settings_form_fields``.
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def checkout_form_fields(self) -> dict:
|
def checkout_form_fields(self) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% bootstrap_form provider.form layout='horizontal' %}
|
{% bootstrap_form provider.form layout='horizontal' %}
|
||||||
|
{% with c=provider.settings_content %}
|
||||||
|
{% if c %}{{ c|safe }}{% endif %}
|
||||||
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ class PaymentSettings(EventPermissionRequiredMixin, TemplateView, SingleObjectMi
|
|||||||
for k, v in provider.settings_form_fields.items()
|
for k, v in provider.settings_form_fields.items()
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
provider.settings_content = provider.settings_content_render(self.request)
|
||||||
provider.form.prepare_fields()
|
provider.form.prepare_fields()
|
||||||
providers.append(provider)
|
providers.append(provider)
|
||||||
return providers
|
return providers
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from collections import OrderedDict
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django import forms
|
from django import forms
|
||||||
@@ -32,6 +33,13 @@ class Stripe(BasePaymentProvider):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def settings_content_render(self, request):
|
||||||
|
return "<div class='alert alert-info'>%s<br /><code>%s</code></div>" % (
|
||||||
|
_('Please configure a <a href="https://dashboard.stripe.com/account/webhooks">Stripe Webhook</a> to '
|
||||||
|
'the following endpoint in order to automatically cancel orders when a charges are refunded externally.'),
|
||||||
|
request.build_absolute_uri(reverse('plugins:stripe:webhook'))
|
||||||
|
)
|
||||||
|
|
||||||
def checkout_is_valid_session(self, request):
|
def checkout_is_valid_session(self, request):
|
||||||
return request.session.get('payment_stripe_token') != ''
|
return request.session.get('payment_stripe_token') != ''
|
||||||
|
|
||||||
@@ -51,6 +59,7 @@ class Stripe(BasePaymentProvider):
|
|||||||
return template.render(ctx)
|
return template.render(ctx)
|
||||||
|
|
||||||
def _init_api(self):
|
def _init_api(self):
|
||||||
|
stripe.api_version = '2015-04-07'
|
||||||
stripe.api_key = self.settings.get('secret_key')
|
stripe.api_key = self.settings.get('secret_key')
|
||||||
|
|
||||||
def checkout_confirm_render(self, request) -> str:
|
def checkout_confirm_render(self, request) -> str:
|
||||||
@@ -65,6 +74,11 @@ class Stripe(BasePaymentProvider):
|
|||||||
amount=int(order.total * 100),
|
amount=int(order.total * 100),
|
||||||
currency=request.event.currency.lower(),
|
currency=request.event.currency.lower(),
|
||||||
source=request.session['payment_stripe_token'],
|
source=request.session['payment_stripe_token'],
|
||||||
|
metadata={
|
||||||
|
'order': order.identity,
|
||||||
|
'event': self.event.identity,
|
||||||
|
'code': order.code
|
||||||
|
},
|
||||||
idempotency_key=self.event.identity + order.code # TODO: Use something better
|
idempotency_key=self.event.identity + order.code # TODO: Use something better
|
||||||
)
|
)
|
||||||
except stripe.error.CardError as e:
|
except stripe.error.CardError as e:
|
||||||
@@ -83,16 +97,15 @@ class Stripe(BasePaymentProvider):
|
|||||||
'in touch with us if this problem persists.'))
|
'in touch with us if this problem persists.'))
|
||||||
logger.error('Stripe error: %s' % str(err))
|
logger.error('Stripe error: %s' % str(err))
|
||||||
else:
|
else:
|
||||||
logger.info(charge)
|
|
||||||
if charge.status == 'succeeded' and charge.paid:
|
if charge.status == 'succeeded' and charge.paid:
|
||||||
try:
|
try:
|
||||||
order.mark_paid('paypal', str(charge))
|
order.mark_paid('paypal', str(charge))
|
||||||
messages.success(request, _('We successfully received your payment. Thank you!'))
|
messages.success(request, _('We successfully received your payment. Thank you!'))
|
||||||
except Quota.QuotaExceededException as e:
|
except Quota.QuotaExceededException as e:
|
||||||
messages.error(request, str(e))
|
messages.error(request, str(e))
|
||||||
messages.success(request, _('We successfully received your payment. Thank you!'))
|
|
||||||
else:
|
else:
|
||||||
messages.warning(request, _('Stripe reported an error: %s' % charge.failure_message))
|
messages.warning(request, _('Stripe reported an error: %s' % charge.failure_message))
|
||||||
|
logger.info('Charge failed: %s' % str(charge))
|
||||||
order = order.clone()
|
order = order.clone()
|
||||||
order.payment_info = str(charge)
|
order.payment_info = str(charge)
|
||||||
order.save()
|
order.save()
|
||||||
|
|||||||
10
src/pretix/plugins/stripe/urls.py
Normal file
10
src/pretix/plugins/stripe/urls.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
|
from .views import webhook
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^stripe/', include([
|
||||||
|
url(r'^webhook/$', webhook, name='webhook'),
|
||||||
|
])),
|
||||||
|
]
|
||||||
53
src/pretix/plugins/stripe/views.py
Normal file
53
src/pretix/plugins/stripe/views.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
from pretix.base.models import Order, Event
|
||||||
|
from pretix.plugins.stripe.payment import Stripe
|
||||||
|
import stripe
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('pretix.plugins.stripe')
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@require_POST
|
||||||
|
def webhook(request):
|
||||||
|
event_json = json.loads(request.body.decode('utf-8'))
|
||||||
|
event_type = event_json['type']
|
||||||
|
if event_type != 'charge.refunded':
|
||||||
|
# Not interested
|
||||||
|
return HttpResponse('Event is not a refund', status=200)
|
||||||
|
|
||||||
|
charge = event_json['data']['object']
|
||||||
|
if charge['object'] != 'charge':
|
||||||
|
return HttpResponse('Object is not a charge', status=200)
|
||||||
|
|
||||||
|
metadata = charge['metadata']
|
||||||
|
if 'event' not in metadata:
|
||||||
|
return HttpResponse('Event not given', status=200)
|
||||||
|
|
||||||
|
try:
|
||||||
|
event = Event.objects.current.get(identity=metadata['event'])
|
||||||
|
except Event.DoesNotExist:
|
||||||
|
return HttpResponse('Event not found', status=200)
|
||||||
|
|
||||||
|
try:
|
||||||
|
order = Order.objects.current.get(identity=metadata['order'])
|
||||||
|
except Order.DoesNotExist:
|
||||||
|
return HttpResponse('Order not found', status=200)
|
||||||
|
|
||||||
|
prov = Stripe(event)
|
||||||
|
prov._init_api()
|
||||||
|
|
||||||
|
try:
|
||||||
|
charge = stripe.Charge.retrieve(charge['id'])
|
||||||
|
except stripe.error.StripeError as err:
|
||||||
|
logger.error('Stripe error on webhook: %s Event data: %s' % (str(err), str(event_json)))
|
||||||
|
return HttpResponse('StripeError', status=500)
|
||||||
|
|
||||||
|
if charge['refunds']['total_count'] > 0 and order.status == Order.STATUS_PAID:
|
||||||
|
order.mark_refunded()
|
||||||
|
|
||||||
|
return HttpResponse(status=200)
|
||||||
Reference in New Issue
Block a user