forked from CGM_Public/pretix_original
Added a payment provider for free products
This commit is contained in:
@@ -66,6 +66,8 @@ The provider class
|
|||||||
|
|
||||||
.. automethod:: checkout_form
|
.. automethod:: checkout_form
|
||||||
|
|
||||||
|
.. automethod:: is_allowed
|
||||||
|
|
||||||
.. autoattribute:: checkout_form_fields
|
.. autoattribute:: checkout_form_fields
|
||||||
|
|
||||||
.. automethod:: checkout_prepare
|
.. automethod:: checkout_prepare
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2015-06-21 19:18+0000\n"
|
"POT-Creation-Date: 2015-06-21 19:18+0000\n"
|
||||||
"PO-Revision-Date: 2015-06-21 21:19+0100\n"
|
"PO-Revision-Date: 2015-06-23 09:51+0100\n"
|
||||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: Raphael Michel <michel@rami.io>\n"
|
"Language-Team: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
@@ -1483,7 +1483,7 @@ msgstr "Als nicht bezahlt markieren"
|
|||||||
#: pretix/control/templates/pretixcontrol/order/refund.html:4
|
#: pretix/control/templates/pretixcontrol/order/refund.html:4
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund.html:8
|
#: pretix/control/templates/pretixcontrol/order/refund.html:8
|
||||||
msgid "Refund order"
|
msgid "Refund order"
|
||||||
msgstr "Bestellung erstatteten"
|
msgstr "Bestellung erstatten"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/index.html:42
|
#: pretix/control/templates/pretixcontrol/order/index.html:42
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:65
|
#: pretix/presale/templates/pretixpresale/event/order.html:65
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ from collections import OrderedDict
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.db.models import Sum, Q
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from django.forms import Form
|
from django.forms import Form
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
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 pretix.base.forms import SettingsForm
|
from pretix.base.forms import SettingsForm
|
||||||
from pretix.base.models import Order
|
from pretix.base.models import Order, CartPosition
|
||||||
|
from pretix.base.services.orders import mark_order_paid
|
||||||
|
|
||||||
from pretix.base.settings import SettingsSandbox
|
from pretix.base.settings import SettingsSandbox
|
||||||
|
from pretix.base.signals import register_payment_providers
|
||||||
|
|
||||||
|
|
||||||
class BasePaymentProvider:
|
class BasePaymentProvider:
|
||||||
@@ -154,6 +158,16 @@ class BasePaymentProvider:
|
|||||||
form.fields = self.checkout_form_fields
|
form.fields = self.checkout_form_fields
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
def is_allowed(self, request: HttpRequest) -> bool:
|
||||||
|
"""
|
||||||
|
You can use this method to disable this payment provider for certain groups
|
||||||
|
of users, products or other criteria. If this method returns ``False``, the
|
||||||
|
user will not be able to select this payment method.
|
||||||
|
|
||||||
|
The default implementation always returns ``True``.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
def checkout_form_render(self, request: HttpRequest) -> str:
|
def checkout_form_render(self, request: HttpRequest) -> str:
|
||||||
"""
|
"""
|
||||||
When the user selects this provider as his prefered payment method,
|
When the user selects this provider as his prefered payment method,
|
||||||
@@ -243,7 +257,7 @@ class BasePaymentProvider:
|
|||||||
containing an URL the user will be redirected to. If you are done with your process
|
containing an URL the user will be redirected to. If you are done with your process
|
||||||
you should return the user to the order's detail page.
|
you should return the user to the order's detail page.
|
||||||
|
|
||||||
If the payment is completed, you should call ``pretix.bsae.services.orders.mark_order_paid(order, provider, info)``
|
If the payment is completed, you should call ``pretix.base.services.orders.mark_order_paid(order, provider, info)``
|
||||||
with ``provider`` being your :py:attr:`identifier` and ``info`` being any string
|
with ``provider`` being your :py:attr:`identifier` and ``info`` being any string
|
||||||
you might want to store for later usage. Please note, that if you want to store
|
you might want to store for later usage. Please note, that if you want to store
|
||||||
something inside ``order.payment_info``, please do it after the ``mark_order_paid`` call,
|
something inside ``order.payment_info``, please do it after the ``mark_order_paid`` call,
|
||||||
@@ -345,3 +359,67 @@ class BasePaymentProvider:
|
|||||||
order.mark_refunded()
|
order.mark_refunded()
|
||||||
messages.success(request, _('The order has been marked as refunded. Please transfer the money '
|
messages.success(request, _('The order has been marked as refunded. Please transfer the money '
|
||||||
'back to the buyer manually.'))
|
'back to the buyer manually.'))
|
||||||
|
|
||||||
|
|
||||||
|
class FreeOrderProvider(BasePaymentProvider):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_enabled(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
return "free"
|
||||||
|
|
||||||
|
def checkout_confirm_render(self, request) -> str:
|
||||||
|
return _("No payment is required as this order only includes products which are free of charge.")
|
||||||
|
|
||||||
|
def order_pending_render(self, request: HttpRequest, order: Order) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def checkout_is_valid_session(self, request: HttpRequest) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def verbose_name(self) -> str:
|
||||||
|
return _("Free of charge")
|
||||||
|
|
||||||
|
def checkout_perform(self, request: HttpRequest, order: Order):
|
||||||
|
mark_order_paid(order, 'free')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def settings_form_fields(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def order_control_refund_render(self, order: Order) -> str:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def order_control_refund_perform(self, request: HttpRequest, order: Order) -> "bool|str":
|
||||||
|
"""
|
||||||
|
Will be called if the event administrator confirms the refund.
|
||||||
|
|
||||||
|
This should transfer the money back (if possible). You can return an URL the
|
||||||
|
user should be redirected to if you need special behaviour or None to continue
|
||||||
|
with default behaviour.
|
||||||
|
|
||||||
|
On failure, you should use Django's message framework to display an error message
|
||||||
|
to the user.
|
||||||
|
|
||||||
|
The default implementation sets the Orders state to refunded and shows a success
|
||||||
|
message.
|
||||||
|
|
||||||
|
:param request: The HTTP request
|
||||||
|
:param order: The order object
|
||||||
|
"""
|
||||||
|
order.mark_refunded()
|
||||||
|
messages.success(request, _('The order has been marked as refunded.'))
|
||||||
|
|
||||||
|
def is_allowed(self, request: HttpRequest) -> bool:
|
||||||
|
return CartPosition.objects.current.filter(
|
||||||
|
Q(user=request.user) & Q(event=request.event)
|
||||||
|
).aggregate(sum=Sum('price'))['sum'] == 0
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(register_payment_providers)
|
||||||
|
def register_payment_provider(sender, **kwargs):
|
||||||
|
return FreeOrderProvider
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class EventPluginSignal(django.dispatch.Signal):
|
|||||||
# Find the Django application this belongs to
|
# Find the Django application this belongs to
|
||||||
searchpath = receiver.__module__
|
searchpath = receiver.__module__
|
||||||
app = None
|
app = None
|
||||||
|
mod = None
|
||||||
while "." in searchpath:
|
while "." in searchpath:
|
||||||
try:
|
try:
|
||||||
if apps.is_installed(searchpath):
|
if apps.is_installed(searchpath):
|
||||||
@@ -37,8 +38,8 @@ class EventPluginSignal(django.dispatch.Signal):
|
|||||||
pass
|
pass
|
||||||
searchpath, mod = searchpath.rsplit(".", 1)
|
searchpath, mod = searchpath.rsplit(".", 1)
|
||||||
|
|
||||||
# Only fire receivers from active plugins
|
# Only fire receivers from active plugins and core modules
|
||||||
if app.name in sender.get_plugins():
|
if (searchpath, mod) == ("pretix", "base") or (app and app.name in sender.get_plugins()):
|
||||||
if not hasattr(app, 'compatibility_errors') or not app.compatibility_errors:
|
if not hasattr(app, 'compatibility_errors') or not app.compatibility_errors:
|
||||||
response = receiver(signal=self, sender=sender, **named)
|
response = receiver(signal=self, sender=sender, **named)
|
||||||
responses.append((receiver, response))
|
responses.append((receiver, response))
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ class PaymentSettings(EventPermissionRequiredMixin, TemplateView, SingleObjectMi
|
|||||||
)
|
)
|
||||||
provider.settings_content = provider.settings_content_render(self.request)
|
provider.settings_content = provider.settings_content_render(self.request)
|
||||||
provider.form.prepare_fields()
|
provider.form.prepare_fields()
|
||||||
|
if provider.settings_content or provider.form.fields:
|
||||||
|
# Exclude providers which do not provide any settings
|
||||||
providers.append(provider)
|
providers.append(provider)
|
||||||
return providers
|
return providers
|
||||||
|
|
||||||
|
|||||||
@@ -35,16 +35,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{# TODO: Question answers #}
|
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="panel panel-primary">
|
<div class="panel panel-primary">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
|
{% if payment_provider.identifier != "free" %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url "presale:event.checkout.payment" organizer=request.event.organizer.slug event=request.event.slug %}">
|
<a href="{% url "presale:event.checkout.payment" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
{% trans "Modify" %}
|
{% trans "Modify" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
{% trans "Payment" %}
|
{% trans "Payment" %}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
<div class="row checkout-button-row">
|
<div class="row checkout-button-row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<a class="btn btn-block btn-default btn-lg"
|
<a class="btn btn-block btn-default btn-lg"
|
||||||
href="{{ view.get_payment_url }}">
|
href="{{ view.get_previous_url }}">
|
||||||
{% trans "Go back" %}
|
{% trans "Go back" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class PaymentDetails(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin,
|
|||||||
responses = register_payment_providers.send(self.request.event)
|
responses = register_payment_providers.send(self.request.event)
|
||||||
for receiver, response in responses:
|
for receiver, response in responses:
|
||||||
provider = response(self.request.event)
|
provider = response(self.request.event)
|
||||||
if not provider.is_enabled:
|
if not provider.is_enabled or not provider.is_allowed(self.request):
|
||||||
continue
|
continue
|
||||||
fee = provider.calculate_fee(self._total_order_value)
|
fee = provider.calculate_fee(self._total_order_value)
|
||||||
providers.append({
|
providers.append({
|
||||||
@@ -178,6 +178,12 @@ class PaymentDetails(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin,
|
|||||||
messages.error(self.request, _("Please select a payment method."))
|
messages.error(self.request, _("Please select a payment method."))
|
||||||
return self.get(request, *args, **kwargs)
|
return self.get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if self._total_order_value == 0:
|
||||||
|
request.session['payment'] = 'free'
|
||||||
|
return redirect(self.get_confirm_url())
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['providers'] = self.provider_forms
|
ctx['providers'] = self.provider_forms
|
||||||
@@ -208,6 +214,7 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
|
|||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['cart'] = self.get_cart(answers=True)
|
ctx['cart'] = self.get_cart(answers=True)
|
||||||
ctx['payment'] = self.payment_provider.checkout_confirm_render(self.request)
|
ctx['payment'] = self.payment_provider.checkout_confirm_render(self.request)
|
||||||
|
ctx['payment_provider'] = self.payment_provider
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@@ -225,7 +232,9 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
|
|||||||
if 'payment' not in request.session or not self.payment_provider:
|
if 'payment' not in request.session or not self.payment_provider:
|
||||||
messages.error(request, _('The payment information you entered was incomplete.'))
|
messages.error(request, _('The payment information you entered was incomplete.'))
|
||||||
return redirect(self.get_payment_url())
|
return redirect(self.get_payment_url())
|
||||||
if not self.payment_provider.checkout_is_valid_session(request):
|
if not self.payment_provider.checkout_is_valid_session(request) or \
|
||||||
|
not self.payment_provider.is_enabled or \
|
||||||
|
not self.payment_provider.is_allowed(request):
|
||||||
messages.error(request, _('The payment information you entered was incomplete.'))
|
messages.error(request, _('The payment information you entered was incomplete.'))
|
||||||
return redirect(self.get_payment_url())
|
return redirect(self.get_payment_url())
|
||||||
for cp in self.positions:
|
for cp in self.positions:
|
||||||
@@ -348,3 +357,7 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
|
|||||||
)
|
)
|
||||||
OrderPosition.transform_cart_positions(cartpos, order)
|
OrderPosition.transform_cart_positions(cartpos, order)
|
||||||
return order
|
return order
|
||||||
|
|
||||||
|
def get_previous_url(self):
|
||||||
|
if self.payment_provider != "free":
|
||||||
|
return self.get_payment_url()
|
||||||
|
|||||||
Reference in New Issue
Block a user