Added a payment provider for free products

This commit is contained in:
Raphael Michel
2015-06-23 10:02:47 +02:00
parent 7308405da5
commit bef9e05e0b
7 changed files with 108 additions and 11 deletions

View File

@@ -66,6 +66,8 @@ The provider class
.. automethod:: checkout_form
.. automethod:: is_allowed
.. autoattribute:: checkout_form_fields
.. automethod:: checkout_prepare

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \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"
"Language-Team: Raphael Michel <michel@rami.io>\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:8
msgid "Refund order"
msgstr "Bestellung erstatteten"
msgstr "Bestellung erstatten"
#: pretix/control/templates/pretixcontrol/order/index.html:42
#: pretix/presale/templates/pretixpresale/event/order.html:65

View File

@@ -2,15 +2,19 @@ from collections import OrderedDict
from decimal import Decimal
from django import forms
from django.contrib import messages
from django.db.models import Sum, Q
from django.dispatch import receiver
from django.forms import Form
from django.http import HttpRequest
from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
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.signals import register_payment_providers
class BasePaymentProvider:
@@ -154,6 +158,16 @@ class BasePaymentProvider:
form.fields = self.checkout_form_fields
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:
"""
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
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
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,
@@ -345,3 +359,67 @@ class BasePaymentProvider:
order.mark_refunded()
messages.success(request, _('The order has been marked as refunded. Please transfer the money '
'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

View File

@@ -29,6 +29,7 @@ class EventPluginSignal(django.dispatch.Signal):
# Find the Django application this belongs to
searchpath = receiver.__module__
app = None
mod = None
while "." in searchpath:
try:
if apps.is_installed(searchpath):
@@ -37,8 +38,8 @@ class EventPluginSignal(django.dispatch.Signal):
pass
searchpath, mod = searchpath.rsplit(".", 1)
# Only fire receivers from active plugins
if app.name in sender.get_plugins():
# Only fire receivers from active plugins and core modules
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:
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))

View File

@@ -136,7 +136,9 @@ class PaymentSettings(EventPermissionRequiredMixin, TemplateView, SingleObjectMi
)
provider.settings_content = provider.settings_content_render(self.request)
provider.form.prepare_fields()
providers.append(provider)
if provider.settings_content or provider.form.fields:
# Exclude providers which do not provide any settings
providers.append(provider)
return providers
def get_context_data(self, *args, **kwargs) -> dict:

View File

@@ -35,16 +35,17 @@
</div>
</div>
</div>
{# TODO: Question answers #}
<div class="row-fluid">
<div class="panel panel-primary">
<div class="panel-heading">
{% if payment_provider.identifier != "free" %}
<div class="pull-right">
<a href="{% url "presale:event.checkout.payment" organizer=request.event.organizer.slug event=request.event.slug %}">
<span class="fa fa-edit"></span>
{% trans "Modify" %}
</a>
</div>
{% endif %}
<h3 class="panel-title">
{% trans "Payment" %}
</h3>
@@ -57,7 +58,7 @@
<div class="row checkout-button-row">
<div class="col-md-4">
<a class="btn btn-block btn-default btn-lg"
href="{{ view.get_payment_url }}">
href="{{ view.get_previous_url }}">
{% trans "Go back" %}
</a>
</div>

View File

@@ -153,7 +153,7 @@ class PaymentDetails(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin,
responses = register_payment_providers.send(self.request.event)
for receiver, response in responses:
provider = response(self.request.event)
if not provider.is_enabled:
if not provider.is_enabled or not provider.is_allowed(self.request):
continue
fee = provider.calculate_fee(self._total_order_value)
providers.append({
@@ -178,6 +178,12 @@ class PaymentDetails(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin,
messages.error(self.request, _("Please select a payment method."))
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):
ctx = super().get_context_data(**kwargs)
ctx['providers'] = self.provider_forms
@@ -208,6 +214,7 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
ctx = super().get_context_data(**kwargs)
ctx['cart'] = self.get_cart(answers=True)
ctx['payment'] = self.payment_provider.checkout_confirm_render(self.request)
ctx['payment_provider'] = self.payment_provider
return ctx
@cached_property
@@ -225,7 +232,9 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
if 'payment' not in request.session or not self.payment_provider:
messages.error(request, _('The payment information you entered was incomplete.'))
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.'))
return redirect(self.get_payment_url())
for cp in self.positions:
@@ -348,3 +357,7 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
)
OrderPosition.transform_cart_positions(cartpos, order)
return order
def get_previous_url(self):
if self.payment_provider != "free":
return self.get_payment_url()