diff --git a/README.rst b/README.rst index 980a407509f9611440bf784f8c8a5afb27762781..f5598bb695ae306143d31639f53d742ff18ddd22 100644 --- a/README.rst +++ b/README.rst @@ -17,6 +17,7 @@ Licence :: Copyright © 2022 Dominik George <dominik.george@teckids.org> + Copyright © 2022 Tom Teichler <tom.teichler@teckids.org> Licenced under the EUPL, version 1.2 or later diff --git a/aleksis/apps/tezor/apps.py b/aleksis/apps/tezor/apps.py index 91be7bb899eae1e9d1a56bd40651423b41fe37e8..e3973c6b47d191e5ef33081dcab67b2aa65311ad 100644 --- a/aleksis/apps/tezor/apps.py +++ b/aleksis/apps/tezor/apps.py @@ -1,4 +1,7 @@ +from django.apps import apps + from aleksis.core.util.apps import AppConfig +from aleksis.core.util.core_helpers import get_site_preferences class DefaultConfig(AppConfig): @@ -10,4 +13,51 @@ class DefaultConfig(AppConfig): "Repository": "https://edugit.org/AlekSIS/onboarding//AlekSIS-App-Tezor", } licence = "EUPL-1.2+" - copyright_info = (([2022], "Dominik George", "dominik.george@teckids.org"),) + copyright_info = (([2022], "Dominik George", "dominik.george@teckids.org"), ([2022], "Tom Teichler", "tom.teichler@teckids.org"),) + + def ready(self): + from django.conf import settings # noqa + settings.PAYMENT_MODEL = "tezor.Invoice" + settings.PAYMENT_VARIANTS = { + "dummy": ("payments.dummy.DummyProvider", {}) + } + + for app_config in apps.app_configs.values(): + if hasattr(app_config, "get_payment_variants"): + variants = app_config.get_payment_variants() + for name, config in variants.items(): + if name not in settings.PAYMENT_VARIANTS: + settings.PAYMENT_VARIANTS[name] = config + + def get_payment_variants(self): + prefs = get_site_preferences() + variants = {} + + if prefs["payments__sofort_api_id"]: + variants["sofort"] = ("payments.sofort.SofortProvider", { + "id": prefs["payments__sofort_api_id"], + "key": prefs["payments__sofort_api_key"], + "project_id": prefs["payments__sofort_project_id"], + "endpoint": "https://api.sofort.com/api/xml", + }) + + if prefs["payments__paypal_client_id"]: + variants["paypal"] = ("payments.paypal.PaypalProvider", { + "client_id": prefs["payments__paypal_client_id"], + "secret": prefs["payments__paypal_secret"], + "capture": not prefs["payments__paypal_capture"], + "endpoint": "https://api.paypal.com", + }) + + if prefs["payments__pledge_enabled"]: + variants["pledge"] = ("djp_sepa.providers.PaymentPledgeProvider", { + }) + + + if prefs["payments__sdd_creditor_identifier"]: + variants["sdd"] = ("djp_sepa.providers.DirectDebitProvider", { + "creditor": prefs["payments__sdd_creditor"], + "creditor_identifier": prefs["payments__sdd_creditor_identifier"], + }) + + return variants diff --git a/aleksis/apps/tezor/migrations/0001_initial.py b/aleksis/apps/tezor/migrations/0001_initial.py index 16716174e4829ed019de800d4d83e803f95bee6d..98802cabe1a043e935049e80df04c07b1ce497e7 100644 --- a/aleksis/apps/tezor/migrations/0001_initial.py +++ b/aleksis/apps/tezor/migrations/0001_initial.py @@ -85,7 +85,7 @@ class Migration(migrations.Migration): ), migrations.AddConstraint( model_name='invoice', - constraint=models.UniqueConstraint(fields=('transaction_id', 'group'), name='number_uniq_per_group'), + constraint=models.UniqueConstraint(fields=('number', 'group'), name='number_uniq_per_group'), ), migrations.AddConstraint( model_name='client', diff --git a/aleksis/apps/tezor/models/invoice.py b/aleksis/apps/tezor/models/invoice.py index fd6be73de84a1e3113009a4d38351adc9d7db4c2..d5d4ab4b39f315c02aa4e68e506cbcb2c3127605 100644 --- a/aleksis/apps/tezor/models/invoice.py +++ b/aleksis/apps/tezor/models/invoice.py @@ -1,6 +1,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models +from django.shortcuts import reverse from django.utils.translation import gettext_lazy as _ from djmoney.models.fields import CurrencyField, MoneyField @@ -32,6 +33,13 @@ class InvoiceGroup(ExtensibleModel): class Invoice(BasePayment, PureDjangoModel): + VARIANT_DISPLAY = { + "paypal": (_("PayPal"), "logos:paypal"), + "sofort": (_("Klarna / Sofort"), "simple-icons:klarna"), + "pledge": (_("Payment pledge / manual payment"), "mdi:hand-coin"), + "sdd": (_("SEPA Direct Debit"), "mdi:bank-transfer"), + } + group = models.ForeignKey( InvoiceGroup, verbose_name=_("Invoice group"), related_name="invoices", on_delete=models.SET_NULL, null=True ) @@ -42,12 +50,26 @@ class Invoice(BasePayment, PureDjangoModel): for_object_id = models.PositiveIntegerField() for_object = GenericForeignKey("for_content_type", "for_object_id") + def get_variant_name(self): + return self.__class__.VARIANT_DISPLAY[self.variant][0] + + def get_variant_icon(self): + return self.__class__.VARIANT_DISPLAY[self.variant][1] + def get_purchased_items(self): return self.for_object.get_purchased_items() + def get_person(self): + if hasattr(self.for_object, "person"): + return self.for_object.person + elif hasattr(self.for_object, "get_person"): + return self.for_object.get_person() + + return None + class Meta: constraints = [ - models.UniqueConstraint(fields=["transaction_id", "group"], name="number_uniq_per_group") + models.UniqueConstraint(fields=["number", "group"], name="number_uniq_per_group") ] @property @@ -77,3 +99,9 @@ class Invoice(BasePayment, PureDjangoModel): }) return TotalsTable(values) + + def get_success_url(self): + return reverse("invoice_by_token", kwargs={"slug": self.token}) + + def get_failure_url(self): + return reverse("invoice_by_token", kwargs={"slug": self.token}) diff --git a/aleksis/apps/tezor/predicates.py b/aleksis/apps/tezor/predicates.py new file mode 100644 index 0000000000000000000000000000000000000000..e9fb75deeb3b67e7063152b13105afa77cd7d81d --- /dev/null +++ b/aleksis/apps/tezor/predicates.py @@ -0,0 +1,26 @@ +from django.contrib.auth import get_user_model + +from rules import predicate + +from .models.invoice import Invoice + +User = get_user_model() + +@predicate +def is_own_invoice(user: User, obj: Invoice): + """Predicate which checks if the invoice is linked to the current user.""" + return obj.get_person() == user.person + +@predicate +def has_no_payment_variant(user: User, obj: Invoice): + """Predicate which checks that the invoice has no payment variant.""" + return not obj.variant + +def is_in_payment_status(status: str): + """Predicate which checks whether the invoice is in a specific state.""" + + @predicate + def _predicate(user: User, obj: Invoice): + return obj.status == status + + return _predicate diff --git a/aleksis/apps/tezor/preferences.py b/aleksis/apps/tezor/preferences.py new file mode 100644 index 0000000000000000000000000000000000000000..8a25f34519a9b34394ec9b126aa6d67651a12049 --- /dev/null +++ b/aleksis/apps/tezor/preferences.py @@ -0,0 +1,119 @@ +from django.utils.translation import gettext_lazy as _ + +from dynamic_preferences.preferences import Section +from dynamic_preferences.types import BooleanPreference, StringPreference + +from aleksis.core.registries import site_preferences_registry + +payments = Section("payments", verbose_name=_("Payments")) + + +@site_preferences_registry.register +class EnablePledge(BooleanPreference): + """Allow payments to be made by anyone, not only invoice recipient.""" + + section = payments + name = "public_payments" + verbose_name = _("Public payments") + help_text = _("Allow anyone (including guests) to make payments. Basic invoice information will be visible to anyone who knows the invoice token.") + default = True + required = False + + +@site_preferences_registry.register +class SofortAPIID(StringPreference): + """Sofort payment backend - API ID.""" + + section = payments + name = "sofort_api_id" + verbose_name = _("Sofort / Klarna - API ID") + default = "" + required = False + + +@site_preferences_registry.register +class SofortAPIKey(StringPreference): + """Sofort payment backend - API key.""" + + section = payments + name = "sofort_api_key" + verbose_name = _("Sofort / Klarna - API Key") + default = "" + required = False + + +@site_preferences_registry.register +class SofortProjectID(StringPreference): + """Sofort payment backend - project ID.""" + + section = payments + name = "sofort_project_id" + verbose_name = _("Sofort / Klarna - Project ID") + default = "" + required = False + + +@site_preferences_registry.register +class PaypalClientID(StringPreference): + """PayPal payment backend - client ID.""" + + section = payments + name = "paypal_client_id" + verbose_name = _("PayPal - Client ID") + default = "" + required = False + + +@site_preferences_registry.register +class PaypalSecret(StringPreference): + """PayPal payment backend - secret.""" + + section = payments + name = "paypal_secret" + verbose_name = _("PayPal - Secret") + default = "" + required = False + + +@site_preferences_registry.register +class PaypalCapture(BooleanPreference): + """PayPal payment backend - use Authorize & Capture.""" + + section = payments + name = "paypal_capture" + verbose_name = _("PayPal - Use Authorize & Capture") + default = False + required = False + + +@site_preferences_registry.register +class EnablePledge(BooleanPreference): + """Payment pledge payment backend - enable or not.""" + + section = payments + name = "pledge_enabled" + verbose_name = _("Enabledp ledged payments") + default = False + required = False + + +@site_preferences_registry.register +class SDDCreditor(StringPreference): + """SEPA direct debit backend - creditor name.""" + + section = payments + name = "sdd_creditor" + verbose_name = _("SEPA Direct Debit - Creditor name") + default = "" + required = False + + +@site_preferences_registry.register +class SDDCreditorIdentifier(StringPreference): + """SEPA direct debit backend - creditor identifier.""" + + section = payments + name = "sdd_creditor_identifier" + verbose_name = _("SEPA Direct Debit - Creditor identifier") + default = "" + required = False diff --git a/aleksis/apps/tezor/rules.py b/aleksis/apps/tezor/rules.py index 98aa9de4d49b81646950b1704128336f68271a57..12a27e3b55a38bbff4d7af450d0b9b757e0a01b6 100644 --- a/aleksis/apps/tezor/rules.py +++ b/aleksis/apps/tezor/rules.py @@ -1,9 +1,11 @@ import rules +from payments import PaymentStatus + +from aleksis.core.util.predicates import has_person, has_global_perm, has_any_object, has_object_perm, is_site_preference_set from .models.base import Client from .models.invoice import Invoice, InvoiceGroup - -from aleksis.core.util.predicates import has_person, has_global_perm, has_any_object, has_object_perm +from .predicates import has_no_payment_variant, is_own_invoice, is_in_payment_status # View clients view_clients_predicate = has_person & ( @@ -65,8 +67,25 @@ delete_invoice_groups_predicate = has_person & ( ) rules.add_perm("tezor.delete_invoice_groups_rule", delete_invoice_groups_predicate) +# Display invoice billing information +display_billing_predicate = has_person & (is_own_invoice | has_global_perm("tezor.display_billing") | has_object_perm("tezor.display_billing")) +rules.add_perm("tezor.display_billing_rule", display_billing_predicate) + +# Display invoice purchased items +display_purchased_items_predicate = has_person & (is_own_invoice | has_global_perm("tezor.display_purchased_items") | has_object_perm("tezor.display_purchased_items")) +rules.add_perm("tezor.display_purchased_items_rule", display_purchased_items_predicate) + +# Change payment variant +change_payment_variant_predicate = has_person & is_in_payment_status(PaymentStatus.WAITING) & ((is_own_invoice & has_no_payment_variant) | has_global_perm("tezor.change_payment_variant") | has_object_perm("tezor.change_payment_variant")) +rules.add_perm("tezor.change_payment_variant", change_payment_variant_predicate) + +# Start payment +do_payment_predicate = has_person & (is_in_payment_status(PaymentStatus.WAITING) | is_in_payment_status(PaymentStatus.INPUT) | is_in_payment_status(PaymentStatus.ERROR) | is_in_payment_status(PaymentStatus.REJECTED)) & ((is_own_invoice | is_site_preference_set("payments", "public_payments")) | has_global_perm("tezor.do_payment") | has_object_perm("tezor.do_payment")) +rules.add_perm("tezor.do_payment", do_payment_predicate) + # View invoice -view_invoice_predicate = has_person & ( - has_global_perm("tezor.view_invoice") | has_object_perm("tezor.view_invoice") -) +view_invoice_predicate = has_person & is_own_invoice | is_site_preference_set("payments", "public_payments") | has_global_perm("tezor.view_invoice") | has_object_perm("tezor.view_invoice") rules.add_perm("tezor.view_invoice_rule", view_invoice_predicate) + +print_invoice_predicate = (view_invoice_predicate & display_billing_predicate & display_purchased_items_predicate) +rules.add_perm("tezor.print_invoice_rule", print_invoice_predicate) diff --git a/aleksis/apps/tezor/settings.py b/aleksis/apps/tezor/settings.py index dabd92baa378f72fb1d665e416b87d09f9d6c18d..3cab1e66223d9ec51e3ae9170ecfae19d16385f0 100644 --- a/aleksis/apps/tezor/settings.py +++ b/aleksis/apps/tezor/settings.py @@ -1 +1 @@ -INSTALLED_APPS = ["payments"] +INSTALLED_APPS = ["payments", "djp_sepa"] diff --git a/aleksis/apps/tezor/tables.py b/aleksis/apps/tezor/tables.py index d9001895cd8d4232f3ff24b25ea847b2747077db..16ed8a0ed491b7eb064c432cfd06950239ba620d 100644 --- a/aleksis/apps/tezor/tables.py +++ b/aleksis/apps/tezor/tables.py @@ -85,21 +85,21 @@ class InvoiceGroupsTable(tables.Table): class InvoicesTable(tables.Table): - transaction_id = tables.Column() + number = tables.Column() status = tables.Column() created = tables.DateColumn() billing_first_name = tables.Column() billing_last_name = tables.Column() total = tables.Column() view = tables.LinkColumn( - "invoice_by_pk", - args=[A("id")], + "invoice_by_token", + args=[A("token")], verbose_name=_("View"), text=_("View"), ) print = tables.LinkColumn( - "get_invoice_by_pk", - args=[A("id")], + "print_invoice", + args=[A("token")], verbose_name=_("Print"), text=_("Print"), ) diff --git a/aleksis/apps/tezor/templates/tezor/invoice/full.html b/aleksis/apps/tezor/templates/tezor/invoice/full.html index add38a90fa88f33578f98b3282ae1f315d05a031..7043c5c3bc59224d5d5ec0cdc726cfadced02344 100644 --- a/aleksis/apps/tezor/templates/tezor/invoice/full.html +++ b/aleksis/apps/tezor/templates/tezor/invoice/full.html @@ -1,16 +1,110 @@ {% extends "core/base.html" %} -{% load material_form i18n %} +{% load material_form i18n rules %} {% load render_table from django_tables2 %} -{% block browser_title %}{{ object.transaction_id }}{% endblock %} +{% block browser_title %}{{ object.number }}{% endblock %} {% block content %} - <h1>{% trans "Invoice" %} {{ object.transaction_id }} — {{ object.created.date }}</h1> - <a class="btn colour-primary waves-effect waves-light" href="{% url 'invoice_group_by_pk' object.group.pk %}">{% trans "Back" %}</a> + {% has_perm 'tezor.do_payment' user object as can_do_payment %} + {% has_perm 'tezor.view_invoice_group_rule' user object.group as can_view_invoice_group %} + {% has_perm 'tezor.display_purchased_items_rule' user object as can_view_purchased_items %} + {% has_perm 'tezor.display_billing_rule' user object as can_view_billing_information %} + {% has_perm 'tezor.print_invoice_rule' user object as can_print_invoice %} - {% render_table object.purchased_items_table %} - {% render_table object.totals_table %} + <h1>{% trans "Invoice" %} {{ object.number }} — {{ object.created.date }}</h1> + + {% if can_view_invoice_group %} + <a class="btn colour-primary waves-effect waves-light" href="{% url 'invoice_group_by_pk' object.group.pk %}">{% trans "Back" %}</a> + {% endif %} + {% if can_print_invoice %} + <a class="btn colour-primary waves-effect waves-light" href="{% url 'print_invoice' object.token %}">{% trans "Print" %}</a> + {% endif %} + + <div class="row"> + {% if can_view_billing_information %} + <div class="col s12 m6"> + <div class="card"> + <div class="card-content"> + <span class="card-title">{% trans "Billing information" %}</span> + <table class="highlight"> + <tr> + <td> + <i class="material-icons small iconify" data-icon="mdi:account-outline"></i> + </td> + <td>{{ object.billing_first_name }} {{object.billing_last_name }}</td> + </tr> + <tr> + <td rowspan="2"> + <i class="material-icons small iconify" data-icon="mdi:map-marker-outline"></i> + </td> + <td>{{ object.billing_address_1 }} {{ object.billing_address_2 }}</td> + </tr> + <tr> + <td>{{ object.billing_postcode }} {{ object.billing_city}}</td> + </tr> + <tr> + <td> + <i class="material-icons small iconify" data-icon="mdi:email-outline"></i> + </td> + <td> + <a href="mailto:{{ object.billing_email }}">{{ object.billing_email }}</a> + </td> + </tr> + </table> + </div> + </div> + </div> + {% endif %} + <div class="col s12 m6"> + <div class="card"> + <div class="card-content"> + <span class="card-title">{% trans "Payment" %}</span> + <table class="highlight"> + <tr> + <td> + <i class="material-icons iconify" data-icon="{{ object.get_variant_icon }}"></i> + </td> + <td> + {{ object.get_variant_name }} + </td> + </tr> + <tr> + <td> + {% if object.status == "waiting" or object.status == "input" %} + <i class="material-icons iconify" data-icon="mdi:cash-lock-open"></i> + {% elif object.status == "rejected" or object.status == "error" %} + <i class="material-icons iconify" data-icon="mdi:cash-remove"></i> + {% elif object.status == "preauth" %} + <i class="material-icons iconfiy" data-icon="mdi:cash-lock"></i> + {% elif object.status == "confirmed" %} + <i class="material-icons iconify" data-icon="mdi:cash-check"></i> + {% elif object.status == "refunded" %} + <i class="material-icons iconify" data-icon="mdi:cash-refund"></i> + {% endif %} + </td> + <td> + {{ object.get_status_display }} + </td> + </tr> + </table> + </div> + {% if object.status == "waiting" or object.status == "rejected" or object.status == "input" and can_do_payment %} + <div class="card-action"> + <a class="btn waves-effect waves-light green" href="{% url 'do_payment' object.token %}"> + <i class="material-icons left iconify" data-icon="mdi:cash-fast"></i> + {% trans "Pay now" %} + </a> + </div> + {% endif %} + </div> + </div> + </div> + + {% if can_view_purchased_items %} + {% render_table object.purchased_items_table %} + {% render_table object.totals_table %} + {% endif %} {% endblock %} diff --git a/aleksis/apps/tezor/templates/tezor/invoice/payment.html b/aleksis/apps/tezor/templates/tezor/invoice/payment.html new file mode 100644 index 0000000000000000000000000000000000000000..838fa26a54bda50be6865880c7fd21d8c7cabab3 --- /dev/null +++ b/aleksis/apps/tezor/templates/tezor/invoice/payment.html @@ -0,0 +1,16 @@ +{% extends 'core/base.html' %} + +{% load i18n %} +{% load material_form %} + +{% block page_title %}{% blocktrans %}Make payment for{% endblocktrans %} {{ payment.number }}{% endblock %} +{% block browser_title %}{% blocktrans %}Make payment for{% endblocktrans %} {{ payment.number }}{% endblock %} + +{% block content %} +<form action="{{ form.action }}" method="{{ form.method }}"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% trans "Confirm payment" as caption %} + {% include "core/partials/save_button.html" with caption=caption icon="shopping_cart_checkout" %} +</form> +{% endblock %} diff --git a/aleksis/apps/tezor/urls.py b/aleksis/apps/tezor/urls.py index 627d64f756e6bd336acfe3be02a244a15ae069de..57b92ac76fc5e533d3f2e4ca8dd7a160a9e3a41f 100644 --- a/aleksis/apps/tezor/urls.py +++ b/aleksis/apps/tezor/urls.py @@ -4,55 +4,56 @@ from . import views urlpatterns = [ path("payments/", include("payments.urls")), - path("invoice/<int:pk>/print", views.GetInvoicePDF.as_view(), name="get_invoice_by_pk"), + path("invoice/<str:token>/print/", views.GetInvoicePDF.as_view(), name="print_invoice"), + path("invoice/<str:token>/pay", views.DoPaymentView.as_view(), name="do_payment"), path( - "clients/list", + "clients/", views.ClientListView.as_view(), name="clients", ), path( - "clients/create", + "client/create/", views.ClientCreateView.as_view(), name="create_client", ), path( - "clients/<int:pk>/edit", + "client/<int:pk>/edit/", views.ClientEditView.as_view(), name="edit_client_by_pk", ), path( - "clients/<int:pk>/delete", + "client/<int:pk>/delete/", views.ClientDeleteView.as_view(), name="delete_client_by_pk", ), path( - "clients/<int:pk>/", + "client/<int:pk>/", views.ClientDetailView.as_view(), name="client_by_pk", ), path( - "client/<int:pk>/invoice_groups/create", + "client/<int:pk>/invoice_groups/create/", views.InvoiceGroupCreateView.as_view(), name="create_invoice_group", ), path( - "invoice_groups/<int:pk>/edit", + "invoice_group/<int:pk>/edit/", views.InvoiceGroupEditView.as_view(), name="edit_invoice_group_by_pk", ), path( - "invoice_groups/<int:pk>/", + "invoice_group/<int:pk>/", views.InvoiceGroupDetailView.as_view(), name="invoice_group_by_pk", ), path( - "invoice_groups/<int:pk>/delete", + "invoice_group/<int:pk>/delete/", views.InvoiceGroupDeleteView.as_view(), name="delete_invoice_group_by_pk", ), path( - "invoice/<int:pk>/", + "invoice/<str:slug>/", views.InvoiceDetailView.as_view(), - name="invoice_by_pk", + name="invoice_by_token", ), ] diff --git a/aleksis/apps/tezor/views.py b/aleksis/apps/tezor/views.py index 04a87cd641643b58cba1e1ff6a5015ec7c89846a..06a4df8cd04be868ffc0cba7a601bc2c90ef22d3 100644 --- a/aleksis/apps/tezor/views.py +++ b/aleksis/apps/tezor/views.py @@ -1,12 +1,12 @@ -from django.views.generic import View -from django.shortcuts import render +from django.shortcuts import redirect, render, get_object_or_404 from django.views.decorators.cache import never_cache +from django.views.generic import FormView, TemplateView, View +from django.views.generic.detail import DetailView +from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator -from django.views.generic import FormView, TemplateView from django.utils.translation import ugettext as _ -from django.urls import reverse, reverse_lazy -from django.views.generic.detail import DetailView +from payments import get_payment_model, PaymentStatus, RedirectNeeded from rules.contrib.views import PermissionRequiredMixin from django_tables2.views import SingleTableView, RequestConfig @@ -21,17 +21,43 @@ from .models.invoice import Invoice, InvoiceGroup class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView): - permission_required = "tezor.can_print_invoice" + permission_required = "tezor.print_invoice_rule" def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) - invoice = Invoice.objects.get(id=self.kwargs["pk"]) + invoice = Invoice.objects.get(token=self.kwargs["token"]) self.template_name = invoice.group.template_name context["invoice"] = invoice print(invoice.group.__dict__) return context + +class DoPaymentView(PermissionRequiredMixin, View): + + model = Invoice + permission_required = "tezor.do_payment_rule" + template_name = "tezor/invoice/payment.html" + + def dispatch(self, request, token): + self.object = get_object_or_404(self.model, token=token) + + if self.object.status not in [PaymentStatus.WAITING, PaymentStatus.INPUT, PaymentStatus.REJECTED]: + return redirect(self.object.get_success_url()) + + try: + form = self.object.get_form(data=request.POST or None) + except RedirectNeeded as redirect_to: + return redirect(str(redirect_to)) + + context = { + "form": form, + "payment": self.object, + } + + return render(request, self.template_name, context) + + class ClientListView(PermissionRequiredMixin, SingleTableView): """Table of all clients.""" @@ -151,5 +177,6 @@ class InvoiceGroupDeleteView(PermissionRequiredMixin, AdvancedDeleteView): class InvoiceDetailView(PermissionRequiredMixin, DetailView): model = Invoice + slug_field = "token" permission_required = "tezor.view_invoice_rule" template_name = "tezor/invoice/full.html" diff --git a/pyproject.toml b/pyproject.toml index f3f3a24fe11000781e67d9ac34a4398c92c20b9b..155e9e96a0c2bffd77b41429360e194f04f27770 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ include = [ ] description = "AlekSIS (School Information System) — App Tezor (account and payment system)" -authors = ["Dominik George <dominik.george@teckids.org>"] +authors = ["Dominik George <dominik.george@teckids.org>", "Tom Teichler <tom.teichler@teckids.org>"] license = "EUPL-1.2-or-later" homepage = "https://aleksis.org" repository = "https://edugit.org/AlekSIS/onboarding//AlekSIS-App-Tezor" @@ -31,7 +31,9 @@ secondary = true [tool.poetry.dependencies] python = "^3.9" aleksis-core = "^2.7" -django-payments = "^0.15.0" +django-payments = "^0.15.1" +pycountry = "22.1.10" +django-payments-sepa = "^1.0.dev0" [tool.poetry.dev-dependencies] aleksis-builddeps = "*"