forked from CGM_Public/pretix_original
Payments via Stripe (#30)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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("<div class='alert alert-danger'>" + response.error.message + "</div>");
|
||||
$(".stripe-errors").slideDown();
|
||||
} else {
|
||||
var token = response.id;
|
||||
// Insert the token into the form so it gets submitted to the server
|
||||
$("#stripe_token").val(token);
|
||||
$("#stripe_card_brand").val(response.card.brand);
|
||||
$("#stripe_card_last4").val(response.card.last4);
|
||||
// and submit
|
||||
$form.get(0).submit();
|
||||
}
|
||||
}
|
||||
};
|
||||
$(function() {
|
||||
if (!$("#stripe_number").length) // Not on the checkout page
|
||||
return;
|
||||
|
||||
$("#stripe_number").change(pretixstripe.validate_number).keydown(pretixstripe.validate_number)
|
||||
.keyup(pretixstripe.validate_number);
|
||||
$(".stripe-exp input").change(pretixstripe.validate_expire).keydown(pretixstripe.validate_expire)
|
||||
.keyup(pretixstripe.validate_expire)
|
||||
$("#stripe_cvc").change(pretixstripe.validate_cvc).keydown(pretixstripe.validate_cvc)
|
||||
.keyup(pretixstripe.validate_cvc)
|
||||
$("#stripe_number").parents("form").submit(
|
||||
function () {
|
||||
if ($("#stripe_token").val() == "") {
|
||||
pretixstripe.request();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>{% blocktrans trimmed %}
|
||||
The total amount will be withdrawn from your credit card.
|
||||
{% endblocktrans %}</p>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Card type" %}</dt>
|
||||
<dd>{{ request.session.payment_stripe_brand }}</dd>
|
||||
<dt>{% trans "Card number" %}</dt>
|
||||
<dd>**** **** **** {{ request.session.payment_stripe_last4 }}</dd>
|
||||
</dl>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="form-horizontal">
|
||||
<div class="stripe-errors sr-only">
|
||||
|
||||
</div>
|
||||
<div class="form-group stripe-number">
|
||||
<label class="control-label col-sm-2">
|
||||
{% trans "Credit card number" %}
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" id="stripe_number" class="form-control" placeholder="4242 4242 4242 4242">
|
||||
<span class="fa form-control-feedback sr-only"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group stripe-exp">
|
||||
<label class="control-label col-sm-2">
|
||||
{% trans "Expiration date" %}
|
||||
</label>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="1" max="12" id="stripe_exp_month" class="form-control" placeholder=
|
||||
"{% trans "Month" %}">
|
||||
<span class="fa form-control-feedback sr-only"></span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="{% now "Y" %}" id="stripe_exp_year" class="form-control" placeholder=
|
||||
"{% trans "Year" %}">
|
||||
<span class="fa form-control-feedback sr-only"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group stripe-cvc">
|
||||
<label class="control-label col-sm-2">
|
||||
{% trans "Security code (CVC)" %}
|
||||
</label>
|
||||
<div class="col-sm-2">
|
||||
<input type="text" maxlength="3" id="stripe_cvc" class="form-control" placeholder=
|
||||
"{% trans "123" %}">
|
||||
<span class="fa form-control-feedback sr-only"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group stripe-name">
|
||||
<label class="control-label col-sm-2">
|
||||
{% trans "Cardholder name" %}
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" id="stripe_name" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<em>{% 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 %}</em>
|
||||
<input type="hidden" name="stripe_token" value="" id="stripe_token" />
|
||||
<input type="hidden" name="stripe_card_last4" value="" id="stripe_card_last4" />
|
||||
<input type="hidden" name="stripe_card_brand" value="" id="stripe_card_brand" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>{% blocktrans trimmed %}
|
||||
The credit card transaction could not be completed. Please contact us.
|
||||
{% endblocktrans %}</p>
|
||||
@@ -0,0 +1,12 @@
|
||||
{% load staticfiles %}
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static "pretixplugins/stripe/pretix-stripe.js" %}"></script>
|
||||
{% endcompress %}
|
||||
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
|
||||
<script type="text/javascript">
|
||||
Stripe.setPublishableKey('{{ settings.publishable_key }}');
|
||||
var stripe_loading_message = '{% trans "Contacting Stripe…" %}';
|
||||
</script>
|
||||
@@ -6,3 +6,64 @@ $(function () {
|
||||
$($(this).attr("data-target")).collapse('show');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Module for displaying "Waiting for..." dialog using Bootstrap
|
||||
*
|
||||
* @author Eugene Maslovich <ehpc@em42.ru>
|
||||
* MIT License
|
||||
*/
|
||||
|
||||
var waitingDialog = (function ($) {
|
||||
|
||||
// Creating modal dialog's DOM
|
||||
var $dialog = $(
|
||||
'<div class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog" aria-hidden="true" style="padding-top:15%; overflow-y:visible;">' +
|
||||
'<div class="modal-dialog modal-m">' +
|
||||
'<div class="modal-content">' +
|
||||
'<div class="modal-header"><h3 style="margin:0;"></h3></div>' +
|
||||
'<div class="modal-body">' +
|
||||
'<div class="progress progress-striped active" style="margin-bottom:0;"><div class="progress-bar" style="width: 100%"></div></div>' +
|
||||
'</div>' +
|
||||
'</div></div></div>');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Opens our dialog
|
||||
* @param message Custom message
|
||||
* @param options Custom options:
|
||||
* options.dialogSize - bootstrap postfix for dialog size, e.g. "sm", "m";
|
||||
* options.progressType - bootstrap postfix for progress bar type, e.g. "success", "warning".
|
||||
*/
|
||||
show: function (message, options) {
|
||||
// Assigning defaults
|
||||
var settings = $.extend({
|
||||
dialogSize: 'm',
|
||||
progressType: ''
|
||||
}, options);
|
||||
if (typeof message === 'undefined') {
|
||||
message = 'Loading';
|
||||
}
|
||||
if (typeof options === 'undefined') {
|
||||
options = {};
|
||||
}
|
||||
// Configuring dialog
|
||||
$dialog.find('.modal-dialog').attr('class', 'modal-dialog').addClass('modal-' + settings.dialogSize);
|
||||
$dialog.find('.progress-bar').attr('class', 'progress-bar');
|
||||
if (settings.progressType) {
|
||||
$dialog.find('.progress-bar').addClass('progress-bar-' + settings.progressType);
|
||||
}
|
||||
$dialog.find('h3').text(message);
|
||||
// Opening dialog
|
||||
$dialog.modal();
|
||||
},
|
||||
/**
|
||||
* Closes dialog
|
||||
*/
|
||||
hide: function () {
|
||||
$dialog.modal('hide');
|
||||
}
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.panel-title .radio {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.form-control + .form-control-feedback {
|
||||
/* Fix for https://github.com/FortAwesome/Font-Awesome/issues/4313 */
|
||||
.form-control-feedback;
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
{% load bootstrap3 %}
|
||||
{% bootstrap_form form layout='horizontal' %}
|
||||
<div class="form-horizontal">
|
||||
{% bootstrap_form form layout='horizontal' %}
|
||||
</div>
|
||||
Reference in New Issue
Block a user