diff --git a/src/pretix/plugins/stripe/payment.py b/src/pretix/plugins/stripe/payment.py index 95cbd65652..ff6a4438e0 100644 --- a/src/pretix/plugins/stripe/payment.py +++ b/src/pretix/plugins/stripe/payment.py @@ -1,20 +1,84 @@ from collections import OrderedDict +import logging +from django.contrib import messages +from django.template import Context +from django.template.loader import get_template from django.utils.translation import ugettext_lazy as _ from django import forms +import stripe from pretix.base.payment import BasePaymentProvider +logger = logging.getLogger('pretix.plugins.stripe') + class Stripe(BasePaymentProvider): identifier = 'stripe' verbose_name = _('Credit Card via Stripe') - checkout_form_fields = OrderedDict([ - ('cc_number', - forms.CharField( - label=_('Credit card number'), - required=False - )) - ]) + + @property + def settings_form_fields(self): + return OrderedDict( + list(super().settings_form_fields.items()) + [ + ('secret_key', + forms.CharField( + label=_('Secret key'), + required=False + )), + ('publishable_key', + forms.CharField( + label=_('Publishable key'), + required=False + )) + ] + ) def checkout_is_valid_session(self, request): - return False + return request.session.get('payment_stripe_token') != '' + + 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 checkout_form_render(self, request) -> str: + template = get_template('pretixplugins/stripe/checkout_payment_form.html') + ctx = Context({'request': request, 'event': self.event, 'settings': self.settings}) + return template.render(ctx) + + def _init_api(self): + 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 = Context({'request': request, 'event': self.event, 'settings': self.settings}) + return template.render(ctx) + + def checkout_perform(self, request, order) -> str: + self._init_api() + charge = stripe.Charge.create( + amount=int(order.total * 100), + currency=request.event.currency.lower(), + source=request.session['payment_stripe_token'], + idempotency_key=self.event.identity + order.code # TODO: Use something better + ) + logging.info(charge) + if charge.status == 'succeeded' and charge.paid: + order.mark_paid('stripe', str(charge)) + messages.success(request, _('We successfully received your payment. Thank you!')) + else: + messages.warning(request, _('Stripe reported an error: %s' % charge.failure_message)) + order = order.clone() + order.payment_info = str(charge) + order.save() + + def order_pending_render(self, request, order) -> str: + template = get_template('pretixplugins/stripe/pending.html') + ctx = Context({'request': request, 'event': self.event, 'settings': self.settings, + 'order': order}) + return template.render(ctx) diff --git a/src/pretix/plugins/stripe/signals.py b/src/pretix/plugins/stripe/signals.py index 0f754b7b35..0fcbd20556 100644 --- a/src/pretix/plugins/stripe/signals.py +++ b/src/pretix/plugins/stripe/signals.py @@ -1,10 +1,26 @@ +from django.core.urlresolvers import resolve from django.dispatch import receiver +from django.template import Context +from django.template.loader import get_template from pretix.base.signals import register_payment_providers from .payment import Stripe +from pretix.presale.signals import html_head @receiver(register_payment_providers) def register_payment_provider(sender, **kwargs): return Stripe + + +@receiver(html_head) +def html_head_presale(sender, request=None, **kwargs): + provider = Stripe(sender) + url = resolve(request.path_info) + if provider.is_enabled and "checkout.payment" in url.url_name: + template = get_template('pretixplugins/stripe/presale_head.html') + ctx = Context({'event': sender, 'settings': provider.settings}) + return template.render(ctx) + else: + return "" diff --git a/src/pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js b/src/pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js new file mode 100644 index 0000000000..e33c86335e --- /dev/null +++ b/src/pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js @@ -0,0 +1,94 @@ +'use strict'; + +var pretixstripe = { + 'validate_number': function () { + var numb = $("#stripe_number").val(); + $(".stripe-number").addClass("has-feedback"); + if (Stripe.card.validateCardNumber(numb)) { + $(".stripe-number").addClass("has-success").removeClass("has-error"); + $(".stripe-number .form-control-feedback").addClass("fa-check") + .removeClass("fa-remove").removeClass("sr-only"); + } else { + $(".stripe-number").removeClass("has-success").addClass("has-error"); + $(".stripe-number .form-control-feedback").addClass("fa-remove") + .removeClass("fa-ok").removeClass("sr-only"); + } + }, + 'validate_expire': function () { + var month = $("#stripe_exp_month").val(); + var year = $("#stripe_exp_year").val(); + $(".stripe-exp").addClass("has-feedback"); + if (Stripe.card.validateExpiry(month, year)) { + $(".stripe-exp").addClass("has-success").removeClass("has-error"); + $(".stripe-exp .form-control-feedback").addClass("fa-check") + .removeClass("fa-remove").removeClass("sr-only"); + } else { + $(".stripe-exp").removeClass("has-success").addClass("has-error"); + $(".stripe-exp .form-control-feedback").addClass("fa-remove") + .removeClass("fa-ok").removeClass("sr-only"); + } + }, + 'validate_cvc': function () { + var cvc = $("#stripe_cvc").val(); + $(".stripe-cvc").addClass("has-feedback"); + if (Stripe.card.validateCVC(cvc)) { + $(".stripe-cvc").addClass("has-success").removeClass("has-error"); + $(".stripe-cvc .form-control-feedback").addClass("fa-check") + .removeClass("fa-remove").removeClass("sr-only"); + } else { + $(".stripe-cvc").removeClass("has-success").addClass("has-error"); + $(".stripe-cvc .form-control-feedback").addClass("fa-remove") + .removeClass("fa-ok").removeClass("sr-only"); + } + }, + 'request': function () { + waitingDialog.show(stripe_loading_message); + $(".stripe-errors").hide(); + Stripe.card.createToken( + { + number: $('#stripe_number').val(), + cvc: $('#stripe_cvc').val(), + exp_month: $('#stripe_exp_month').val(), + exp_year: $('#stripe_exp_year').val(), + name: $('#stripe_name').val(), + }, + pretixstripe.response + ); + }, + 'response': function (status, response) { + var $form = $("#stripe_number").parents("form"); + waitingDialog.hide(); + if (response.error) { + $(".stripe-errors").stop().hide(); + $(".stripe-errors").html("
{% blocktrans trimmed %} + The total amount will be withdrawn from your credit card. +{% endblocktrans %}
++ {% blocktrans trimmed %} + Your payment will be processed by Stripe, Inc. Your credit card data will be transmitted directly to + Stripe and never touches our servers. + {% endblocktrans %} + + + +
+{% blocktrans trimmed %} + The credit card transaction could not be completed. Please contact us. +{% endblocktrans %}
diff --git a/src/pretix/plugins/stripe/templates/pretixplugins/stripe/presale_head.html b/src/pretix/plugins/stripe/templates/pretixplugins/stripe/presale_head.html new file mode 100644 index 0000000000..02033d1e2e --- /dev/null +++ b/src/pretix/plugins/stripe/templates/pretixplugins/stripe/presale_head.html @@ -0,0 +1,12 @@ +{% load staticfiles %} +{% load compress %} +{% load i18n %} + +{% compress js %} + +{% endcompress %} + + diff --git a/src/pretix/presale/static/pretixpresale/js/ui/main.js b/src/pretix/presale/static/pretixpresale/js/ui/main.js index 877c199b21..80a496acca 100644 --- a/src/pretix/presale/static/pretixpresale/js/ui/main.js +++ b/src/pretix/presale/static/pretixpresale/js/ui/main.js @@ -6,3 +6,64 @@ $(function () { $($(this).attr("data-target")).collapse('show'); }); }); + + +/** + * Module for displaying "Waiting for..." dialog using Bootstrap + * + * @author Eugene Maslovich