import json import logging from collections import OrderedDict import stripe from django import forms from django.contrib import messages from django.template.loader import get_template from django.utils.translation import ugettext_lazy as _ from pretix.base.models import Quota from pretix.base.payment import BasePaymentProvider from pretix.base.services.orders import mark_order_paid from pretix.helpers.urls import build_absolute_uri logger = logging.getLogger('pretix.plugins.stripe') class Stripe(BasePaymentProvider): identifier = 'stripe' verbose_name = _('Credit Card via Stripe') @property def settings_form_fields(self): return OrderedDict( list(super().settings_form_fields.items()) + [ ('secret_key', forms.CharField( label=_('Secret key'), )), ('publishable_key', forms.CharField( label=_('Publishable key'), )) ] ) def settings_content_render(self, request): return "
%s
%s
" % ( _('Please configure a Stripe Webhook to ' 'the following endpoint in order to automatically cancel orders when a charges are refunded externally.'), build_absolute_uri('plugins:stripe:webhook') ) def payment_is_valid_session(self, request): return request.session.get('payment_stripe_token') != '' def retry_prepare(self, request, order): return self.checkout_prepare(request, None) def checkout_prepare(self, request, cart): token = request.POST.get('stripe_token', '') request.session['payment_stripe_token'] = token request.session['payment_stripe_brand'] = request.POST.get('stripe_card_brand', '') request.session['payment_stripe_last4'] = request.POST.get('stripe_card_last4', '') if token == '': messages.error(request, _('You may need to enable JavaScript for Stripe payments.')) return False return True def payment_form_render(self, request) -> str: template = get_template('pretixplugins/stripe/checkout_payment_form.html') ctx = {'request': request, 'event': self.event, 'settings': self.settings} return template.render(ctx) def _init_api(self): stripe.api_version = '2015-04-07' stripe.api_key = self.settings.get('secret_key') def checkout_confirm_render(self, request) -> str: template = get_template('pretixplugins/stripe/checkout_payment_confirm.html') ctx = {'request': request, 'event': self.event, 'settings': self.settings} return template.render(ctx) def order_can_retry(self, order): return True def payment_perform(self, request, order) -> str: self._init_api() try: charge = stripe.Charge.create( amount=int(order.total * 100), currency=request.event.currency.lower(), source=request.session['payment_stripe_token'], metadata={ 'order': order.identity, 'event': self.event.identity, 'code': order.code }, # TODO: Is this sufficient? idempotency_key=self.event.identity + order.code + request.session['payment_stripe_token'] ) except stripe.error.CardError as e: err = e.json_body['error'] messages.error(request, _('Stripe reported an error with your card: %s' % err['message'])) logger.info('Stripe card error: %s' % str(err)) order = order.clone() order.payment_info = json.dumps({ 'error': True, 'message': err['message'], }) order.save() except stripe.error.InvalidRequestError or stripe.error.AuthenticationError or stripe.error.APIConnectionError \ or stripe.error.StripeError as e: err = e.json_body['error'] 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 = order.clone() order.payment_info = json.dumps({ 'error': True, 'message': err['message'], }) order.save() else: if charge.status == 'succeeded' and charge.paid: try: mark_order_paid(order, 'stripe', str(charge)) except Quota.QuotaExceededException as e: messages.error(request, str(e)) else: messages.warning(request, _('Stripe reported an error: %s' % charge.failure_message)) logger.info('Charge failed: %s' % str(charge)) order = order.clone() order.payment_info = str(charge) order.save() del request.session['payment_stripe_token'] def order_pending_render(self, request, order) -> str: if order.payment_info: payment_info = json.loads(order.payment_info) else: payment_info = None template = get_template('pretixplugins/stripe/pending.html') ctx = {'request': request, 'event': self.event, 'settings': self.settings, 'order': order, 'payment_info': payment_info} return template.render(ctx) def order_control_render(self, request, order) -> str: if order.payment_info: payment_info = json.loads(order.payment_info) if 'amount' in payment_info: payment_info['amount'] /= 100 else: payment_info = None template = get_template('pretixplugins/stripe/control.html') ctx = {'request': request, 'event': self.event, 'settings': self.settings, 'payment_info': payment_info, 'order': order} return template.render(ctx) def order_control_refund_render(self, order) -> str: return '
%s
' % _('The money will be automatically refunded.') def order_control_refund_perform(self, request, order) -> "bool|str": self._init_api() if order.payment_info: payment_info = json.loads(order.payment_info) else: payment_info = None if not payment_info: order.mark_refunded() messages.warning(request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.')) return try: ch = stripe.Charge.retrieve(payment_info['id']) ch.refunds.create() ch.refresh() except stripe.error.InvalidRequestError or stripe.error.AuthenticationError or stripe.error.APIConnectionError \ as e: err = e.json_body['error'] 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)) except stripe.error.StripeError: order.mark_refunded() messages.warning(request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.')) else: order = order.mark_refunded() order.payment_info = str(ch) order.save()