diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3233b185587b003f4550625059d08bd80eb0eaba..32dbe404414d0a0f687a2c026266bf263e8996a7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,20 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog`_, and this project adheres to `Semantic Versioning`_. +`1.2`_ - 2022-03-20 +------------------- + +Added +----- + +* Update django-payments to 1.0 +* Update invoice if person changes + +Changed +------- + +* Always set person on Invoice objects + `1.1.1`_ - 2022-03-15 --------------------- @@ -72,3 +86,4 @@ Added .. _1.0.2: https://edugit.org/AlekSIS/onboarding//AlekSIS-App-Tezor/-/tags/1.0.2 .. _1.1: https://edugit.org/AlekSIS/onboarding//AlekSIS-App-Tezor/-/tags/1.1 .. _1.1.1: https://edugit.org/AlekSIS/onboarding//AlekSIS-App-Tezor/-/tags/1.1.1 +.. _1.2 https://edugit.org/AlekSIS/onboarding//AlekSIS-App-Tezor/-/tags/1.2 diff --git a/aleksis/apps/tezor/apps.py b/aleksis/apps/tezor/apps.py index 0515dd4192e5a90a82a44e53a30920c1bc3cfd85..e66d87b96dc1ededc9efdc7f98ecb805a7d3aa77 100644 --- a/aleksis/apps/tezor/apps.py +++ b/aleksis/apps/tezor/apps.py @@ -1,8 +1,4 @@ -from django.apps import apps -from django.db import OperationalError - from aleksis.core.util.apps import AppConfig -from aleksis.core.util.core_helpers import get_site_preferences class DefaultConfig(AppConfig): @@ -20,57 +16,4 @@ class DefaultConfig(AppConfig): ) def ready(self): - from django.conf import settings # noqa - - settings.PAYMENT_VARIANTS = {} - - for app_config in apps.app_configs.values(): - if hasattr(app_config, "get_payment_variants"): - try: - variants = app_config.get_payment_variants() - except OperationalError: - # Non-fatal, database is not yet ready - continue - 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 + from .signals import update_on_person_change # noqa diff --git a/aleksis/apps/tezor/filters.py b/aleksis/apps/tezor/filters.py index 397883fff0a581ef936fa06d31d268070923ebdc..2afa6361751917dfdb75ee5c723cb01669e7469a 100644 --- a/aleksis/apps/tezor/filters.py +++ b/aleksis/apps/tezor/filters.py @@ -8,14 +8,12 @@ from .models.invoice import Invoice class InvoicesFilter(FilterSet): variant = ChoiceFilter(choices=Invoice.get_variant_choices()) status = ChoiceFilter(choices=PaymentStatus.CHOICES) - - + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.form.layout = Layout( Row("variant", "status"), ) - class Meta: models = Invoice diff --git a/aleksis/apps/tezor/forms.py b/aleksis/apps/tezor/forms.py index 71b304dbd3060a47536de47a2b09763725dd3a8d..1d2799c5b747abd0dbc35be32618179db2fbf246 100644 --- a/aleksis/apps/tezor/forms.py +++ b/aleksis/apps/tezor/forms.py @@ -1,6 +1,6 @@ from django.utils.translation import gettext as _ -from material import Layout, Row +from material import Fieldset, Layout, Row from aleksis.core.forms import ActionForm from aleksis.core.mixins import ExtensibleForm @@ -27,7 +27,29 @@ class InvoicesActionForm(ActionForm): class EditClientForm(ExtensibleForm): """Form to create or edit clients.""" - layout = Layout("name", "email") + layout = Layout( + Row("name", "email"), + Fieldset( + _("Payment pledge"), + Row("pledge_enabled"), + ), + Fieldset( + _("Sofort / Klarna"), + "sofort_enabled", + Row("sofort_api_id", "sofort_api_key", "sofort_project_id"), + ), + Fieldset( + _("PayPal"), + "paypal_enabled", + Row("paypal_client_id", "paypal_secret", "paypal_capture"), + ), + Fieldset( + _("Debit"), + "sdd_enabled", + Row("sdd_creditor", "sdd_creditor_identifier"), + Row("sdd_iban", "sdd_bic"), + ), + ) class Meta: model = Client diff --git a/aleksis/apps/tezor/migrations/0007_client_payment_variants.py b/aleksis/apps/tezor/migrations/0007_client_payment_variants.py new file mode 100644 index 0000000000000000000000000000000000000000..26e258ef53333dc9644b69f289b2d54f6947e9a1 --- /dev/null +++ b/aleksis/apps/tezor/migrations/0007_client_payment_variants.py @@ -0,0 +1,134 @@ +# Generated by Django 3.2.12 on 2022-03-19 22:21 + +import warnings + +import django.core.validators +from django.db import migrations, models +import localflavor.generic.models + + +def configure_clients(apps, schema_editor): + db_alias = schema_editor.connection.alias + + Client = apps.get_model("tezor", "Client") + SitePreferenceModel = apps.get_model("core", "SitePreferenceModel") + + fields = ["sofort_api_id", "sofort_api_key", "sofort_project_id", "paypal_client_id", "paypal_secret", "paypal_capture", "pledge_enabled", "sdd_creditor", "sdd_creditor_identifier"] + values = {} + for field in fields: + try: + pref = SitePreferenceModel.objects.using(db_alias).get(section="payments", name=field) + except SitePreferenceModel.DoesNotExist: + continue + + value = pref.raw_value + if value == "True": + value = True + elif value == "False": + value = False + + values[field] = value + if not "enabled" in field: + provider = field.split("_")[0] + values[f"{provider}_enabled"] = True + + for variant in ("sdd", "pledge", "paypal", "sofort"): + if values.get(f"{variant}_enabled", False): + for field in Client._meta.fields: + if field.name.startswith(f"{variant}_") and values.get(field.name, "") == "": + values[f"{variant}_enabled"] = False + warnings.warn(f"Payment variant {variant} enabled but {field.name} not configured!") + + Client.objects.update(**values) + +class Migration(migrations.Migration): + + dependencies = [ + ('tezor', '0006_invoice_constraints'), + ] + + operations = [ + migrations.AddField( + model_name='client', + name='paypal_capture', + field=models.BooleanField(default=False, verbose_name='Use PayPal Authorize & Capture'), + ), + migrations.AddField( + model_name='client', + name='paypal_client_id', + field=models.CharField(blank=True, max_length=255, verbose_name='PayPal client ID'), + ), + migrations.AddField( + model_name='client', + name='paypal_enabled', + field=models.BooleanField(default=False, verbose_name='PayPal enabled'), + ), + migrations.AddField( + model_name='client', + name='paypal_secret', + field=models.CharField(blank=True, max_length=255, verbose_name='PayPal secret'), + ), + migrations.AddField( + model_name='client', + name='pledge_enabled', + field=models.BooleanField(default=False, verbose_name='Pledge enabled'), + ), + migrations.AddField( + model_name='client', + name='sdd_bic', + field=localflavor.generic.models.BICField(blank=True, max_length=11, verbose_name='BIC/SWIFT code of bank'), + ), + migrations.AddField( + model_name='client', + name='sdd_creditor', + field=models.CharField(blank=True, max_length=255, verbose_name='SEPA Direct Debit - Creditor name'), + ), + migrations.AddField( + model_name='client', + name='sdd_creditor_identifier', + field=models.CharField(blank=True, max_length=35, validators=[django.core.validators.RegexValidator('^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,31}$')], verbose_name='SEPA Direct Debit - Creditor identifier'), + ), + migrations.AddField( + model_name='client', + name='sdd_enabled', + field=models.BooleanField(default=False, verbose_name='Debit enabled'), + ), + migrations.AddField( + model_name='client', + name='sdd_iban', + field=localflavor.generic.models.IBANField(blank=True, include_countries=None, max_length=34, use_nordea_extensions=False, verbose_name='IBAN of bank account'), + ), + migrations.AddField( + model_name='client', + name='sofort_api_id', + field=models.CharField(blank=True, max_length=255, verbose_name='Sofort / Klarna API ID'), + ), + migrations.AddField( + model_name='client', + name='sofort_api_key', + field=models.CharField(blank=True, max_length=255, verbose_name='Sofort / Klarna API key'), + ), + migrations.AddField( + model_name='client', + name='sofort_enabled', + field=models.BooleanField(default=False, verbose_name='Sofort / Klarna enabled'), + ), + migrations.AddField( + model_name='client', + name='sofort_project_id', + field=models.CharField(blank=True, max_length=255, verbose_name='Sofort / Klarna Project ID'), + ), + migrations.AddConstraint( + model_name='client', + constraint=models.CheckConstraint(check=models.Q(models.Q(('sofort_enabled', True), models.Q(('sofort_api_id', ''), _negated=True), models.Q(('sofort_api_key', ''), _negated=True), models.Q(('sofort_project_id', ''), _negated=True)), ('sofort_enabled', False), _connector='OR'), name='sofort_enabled_configured'), + ), + migrations.AddConstraint( + model_name='client', + constraint=models.CheckConstraint(check=models.Q(models.Q(('sdd_enabled', True), models.Q(('sdd_creditor', ''), _negated=True), models.Q(('sdd_creditor_identifier', ''), _negated=True), models.Q(('sdd_iban', ''), _negated=True), models.Q(('sdd_bic', ''), _negated=True)), ('sdd_enabled', False), _connector='OR'), name='sdd_enabled_configured'), + ), + migrations.AddConstraint( + model_name='client', + constraint=models.CheckConstraint(check=models.Q(models.Q(('paypal_enabled', True), models.Q(('paypal_client_id', ''), _negated=True), models.Q(('paypal_secret', ''), _negated=True)), ('paypal_enabled', False), _connector='OR'), name='paypal_enabled_configured'), + ), + migrations.RunPython(configure_clients), + ] diff --git a/aleksis/apps/tezor/migrations/0008_always_set_person.py b/aleksis/apps/tezor/migrations/0008_always_set_person.py new file mode 100644 index 0000000000000000000000000000000000000000..b6fcebb0e43bfd354cb8b8d5f9809fc2a7fda70d --- /dev/null +++ b/aleksis/apps/tezor/migrations/0008_always_set_person.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.12 on 2022-03-20 19:29 + +from django.db import migrations + + +def set_person_on_invoices(apps, schema_editor): + + Invoice = apps.get_model("tezor", "Invoice") + + for invoice in Invoice.objects.filter(person__isnull=True, for_object_id__isnull=False): + person = invoice.for_object.get_person() + invoice.person=person + invoice.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('tezor', '0007_client_payment_variants'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='invoice', + name='object_or_person', + ), + migrations.RunPython(set_person_on_invoices), + ] diff --git a/aleksis/apps/tezor/migrations/0009_invoice_billing_phone.py b/aleksis/apps/tezor/migrations/0009_invoice_billing_phone.py new file mode 100644 index 0000000000000000000000000000000000000000..650e565495e7f88cdfea8b0a8ca254267c238d69 --- /dev/null +++ b/aleksis/apps/tezor/migrations/0009_invoice_billing_phone.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.12 on 2022-03-20 21:51 + +from django.db import migrations +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ('tezor', '0008_always_set_person'), + ] + + operations = [ + migrations.AddField( + model_name='invoice', + name='billing_phone', + field=phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None), + ), + ] diff --git a/aleksis/apps/tezor/models/base.py b/aleksis/apps/tezor/models/base.py index 85f85ccf9f3d67cc362c6704852c81602f958101..d44902ea81627a68c07197a309bd8592c6800dc0 100644 --- a/aleksis/apps/tezor/models/base.py +++ b/aleksis/apps/tezor/models/base.py @@ -1,17 +1,102 @@ +from django.core.validators import RegexValidator from django.db import models +from django.db.models import Q from django.utils.translation import gettext_lazy as _ +from localflavor.generic.models import BICField, IBANField + from aleksis.core.mixins import ExtensibleModel class Client(ExtensibleModel): + 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"), + } name = models.CharField(verbose_name=_("Name"), max_length=255) email = models.EmailField(verbose_name=_("Email")) + sofort_enabled = models.BooleanField(verbose_name=_("Sofort / Klarna enabled"), default=False) + sofort_api_id = models.CharField( + verbose_name=_("Sofort / Klarna API ID"), blank=True, max_length=255 + ) + sofort_api_key = models.CharField( + verbose_name=_("Sofort / Klarna API key"), blank=True, max_length=255 + ) + sofort_project_id = models.CharField( + verbose_name=_("Sofort / Klarna Project ID"), blank=True, max_length=255 + ) + + paypal_enabled = models.BooleanField(verbose_name=_("PayPal enabled"), default=False) + paypal_client_id = models.CharField( + verbose_name=_("PayPal client ID"), blank=True, max_length=255 + ) + paypal_secret = models.CharField(verbose_name=_("PayPal secret"), blank=True, max_length=255) + paypal_capture = models.BooleanField( + verbose_name=_("Use PayPal Authorize & Capture"), default=False + ) + + sdd_enabled = models.BooleanField(verbose_name=_("Debit enabled"), default=False) + sdd_creditor = models.CharField( + verbose_name=_("SEPA Direct Debit - Creditor name"), blank=True, max_length=255 + ) + sdd_creditor_identifier = models.CharField( + verbose_name=_("SEPA Direct Debit - Creditor identifier"), + blank=True, + max_length=35, + validators=[RegexValidator("^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,31}$")], + ) + sdd_iban = IBANField(verbose_name=_("IBAN of bank account"), blank=True) + sdd_bic = BICField(verbose_name=_("BIC/SWIFT code of bank"), blank=True) + + pledge_enabled = models.BooleanField(verbose_name=_("Pledge enabled"), default=False) + class Meta: constraints = [ - models.UniqueConstraint(fields=["name", "site"], name="uniq_client_per_site") + models.UniqueConstraint(fields=["name", "site"], name="uniq_client_per_site"), + models.CheckConstraint( + check=( + ( + Q(sofort_enabled=True) + & ~Q(sofort_api_id="") + & ~Q(sofort_api_key="") + & ~Q(sofort_project_id="") + ) + | Q(sofort_enabled=False) + ), + name="sofort_enabled_configured", + ), + models.CheckConstraint( + check=( + ( + Q(sdd_enabled=True) + & ~Q(sdd_creditor="") + & ~Q(sdd_creditor_identifier="") + & ~Q(sdd_iban="") + & ~Q(sdd_bic="") + ) + | Q(sdd_enabled=False) + ), + name="sdd_enabled_configured", + ), + models.CheckConstraint( + check=( + (Q(paypal_enabled=True) & ~Q(paypal_client_id="") & ~Q(paypal_secret="")) + | Q(paypal_enabled=False) + ), + name="paypal_enabled_configured", + ), ] def __str__(self) -> str: return self.name + + def get_variant_choices(self=None): + choices = [] + for variant in Client.VARIANT_DISPLAY.keys(): + if self and not getattr(self, f"{variant}_enabled"): + continue + choices.append((variant, Client.VARIANT_DISPLAY[variant][0])) + return choices diff --git a/aleksis/apps/tezor/models/invoice.py b/aleksis/apps/tezor/models/invoice.py index 08805fedd809dcd57ae2d0bf507e863bc7d51426..8e786ac4f4157736cb8fec25e7eb7d860c4742c3 100644 --- a/aleksis/apps/tezor/models/invoice.py +++ b/aleksis/apps/tezor/models/invoice.py @@ -1,8 +1,6 @@ -from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models -from django.db.models import Q from django.shortcuts import reverse from django.utils.translation import gettext_lazy as _ @@ -33,6 +31,12 @@ class InvoiceGroup(ExtensibleModel): def __str__(self) -> str: return self.name + def get_variant_choices(self=None): + if self and self.client: + return self.client.get_variant_choices() + else: + return Client.get_variant_choices() + class Meta: constraints = [ models.UniqueConstraint(fields=["client", "name"], name="group_uniq_per_client") @@ -40,12 +44,6 @@ 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"), - } STATUS_ICONS = { PaymentStatus.WAITING: "mdi:cash-lock-open", PaymentStatus.INPUT: "mdi:cash-lock-open", @@ -86,12 +84,13 @@ class Invoice(BasePayment, PureDjangoModel): def save(self, *args, **kwargs): if self.person: person = self.person - elif self.for_object and getattr(self.for_object, "person", None): - person = self.for_object.person + elif self.for_object and getattr(self.for_object, "get_person", None): + person = self.for_object.get_person() else: person = None if person: + self.person = person if not self.billing_last_name: self.billing_last_name = person.last_name if not self.billing_first_name: @@ -105,18 +104,17 @@ class Invoice(BasePayment, PureDjangoModel): super().save(*args, **kwargs) - @classmethod - def get_variant_choices(cls): - choices = [] - for variant in settings.PAYMENT_VARIANTS.keys(): - choices.append((variant, cls.VARIANT_DISPLAY[variant][0])) - return choices + def get_variant_choices(self=None): + if self and self.group: + return self.group.get_variant_choices() + else: + return InvoiceGroup.get_variant_choices() def get_variant_name(self): - return self.__class__.VARIANT_DISPLAY[self.variant][0] + return Client.VARIANT_DISPLAY[self.variant][0] def get_variant_icon(self): - return self.__class__.VARIANT_DISPLAY[self.variant][1] + return Client.VARIANT_DISPLAY[self.variant][1] def get_status_icon(self): return self.__class__.STATUS_ICONS[self.status] @@ -142,13 +140,6 @@ class Invoice(BasePayment, PureDjangoModel): class Meta: constraints = [ models.UniqueConstraint(fields=["number", "group"], name="number_uniq_per_group"), - models.CheckConstraint( - check=( - (Q(for_object_id__isnull=True) & Q(person__isnull=False)) - | (Q(for_object_id__isnull=False) & Q(person__isnull=True)) - ), - name="object_or_person", - ), ] permissions = (("send_invoice_email", _("Can send invoice by email")),) diff --git a/aleksis/apps/tezor/preferences.py b/aleksis/apps/tezor/preferences.py index 2535a9be0ede9596c46d69af9e942fbf7a2f4567..6a69eefda418246355c4f931ae647ed08d035ace 100644 --- a/aleksis/apps/tezor/preferences.py +++ b/aleksis/apps/tezor/preferences.py @@ -1,7 +1,7 @@ from django.utils.translation import gettext_lazy as _ from dynamic_preferences.preferences import Section -from dynamic_preferences.types import BooleanPreference, StringPreference +from dynamic_preferences.types import BooleanPreference from aleksis.core.registries import site_preferences_registry @@ -23,72 +23,6 @@ class PublicPayments(BooleanPreference): 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.""" @@ -101,22 +35,11 @@ class EnablePledge(BooleanPreference): @site_preferences_registry.register -class SDDCreditor(StringPreference): - """SEPA direct debit backend - creditor name.""" +class UpdateOnPersonChange(BooleanPreference): + """Update Invoices if person data changes.""" 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 = "" + name = "update_on_person_change" + verbose_name = _("Update Invoices if person data changes") + default = True required = False diff --git a/aleksis/apps/tezor/settings.py b/aleksis/apps/tezor/settings.py index e7277712f384d2170c04e46ce10634a01ac71785..f3580c25dc797424edfa408c87f75d0b4370830e 100644 --- a/aleksis/apps/tezor/settings.py +++ b/aleksis/apps/tezor/settings.py @@ -1,5 +1,6 @@ INSTALLED_APPS = ["payments", "djp_sepa"] PAYMENT_MODEL = "tezor.Invoice" +PAYMENT_VARIANT_FACTORY = "aleksis.apps.tezor.util.invoice.provider_factory" -overrides = ["PAYMENT_MODEL"] +overrides = ["PAYMENT_MODEL", "PAYMENT_VARIANT_FACTORY"] diff --git a/aleksis/apps/tezor/signals.py b/aleksis/apps/tezor/signals.py new file mode 100644 index 0000000000000000000000000000000000000000..a5750b7ae2a982b8b7276267c6236b9204cd4801 --- /dev/null +++ b/aleksis/apps/tezor/signals.py @@ -0,0 +1,21 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver + +from aleksis.core.models import Person +from aleksis.core.util.core_helpers import get_site_preferences + +from .models.invoice import Invoice + + +@receiver(post_save, sender=Person) +def update_on_person_change(sender, instance, **kwargs): + + if get_site_preferences()["payments__update_on_person_change"]: + Invoice.objects.filter(person=instance, status__in=("waiting", "input", "preauth")).update( + billing_email=instance.email, + billing_first_name=instance.first_name, + billing_last_name=instance.last_name, + billing_address_1=f"{instance.street} {instance.housenumber}", + billing_postcode=instance.postal_code, + billing_city=instance.place, + ) diff --git a/aleksis/apps/tezor/util/__init__.py b/aleksis/apps/tezor/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/aleksis/apps/tezor/util/invoice.py b/aleksis/apps/tezor/util/invoice.py new file mode 100644 index 0000000000000000000000000000000000000000..d8f58820406653376a3dc0609f87bfe2bf57d520 --- /dev/null +++ b/aleksis/apps/tezor/util/invoice.py @@ -0,0 +1,43 @@ +def provider_factory(variant, payment=None): + from djp_sepa.providers import DirectDebitProvider, PaymentPledgeProvider # noqa + from payments.paypal import PaypalProvider # noqa + from payments.sofort import SofortProvider # noqa + + if not payment: + raise KeyError("Could not configure payment provider without a payment.") + if not payment.group: + raise KeyError( + "Could not configure payment provider for a payment without an invoice group." + ) + if not payment.group.client: + raise KeyError( + "Could not configure payment provider for an invoice group without a client." + ) + + client = payment.group.client + + if variant == "sofort" and client.sofort_enabled: + return SofortProvider( + key=client.sofort_api_key, id=client.sofort_api_id, project_id=client.sofort_project_id + ) + + if variant == "paypal" and client.paypal_enabled: + return PaypalProvider( + client_id=client.paypal_client_id, + secret=client.paypal_secret, + capture=client.paypal_capture, + endpoint="https://api.paypal.com", + ) + + if variant == "pledge" and client.pledge_enabled: + return PaymentPledgeProvider() + + if variant == "sdd" and client.sdd_enabled: + return DirectDebitProvider( + creditor=client.sdd_creditor, + creditor_identifier=client.sdd_creditor_identifier, + iban=client.sdd_iban, + bic=client.sdd_bic, + ) + + return KeyError("Provider not found or not configured for client.") diff --git a/aleksis/apps/tezor/views.py b/aleksis/apps/tezor/views.py index 981b5dd7d6459b7b40ea250d0f28af336fadfb24..c7c52c455d16f77e68e284376411db6a4965bd96 100644 --- a/aleksis/apps/tezor/views.py +++ b/aleksis/apps/tezor/views.py @@ -1,4 +1,3 @@ -from django.conf import settings from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse, reverse_lazy @@ -48,7 +47,7 @@ class DoPaymentView(PermissionRequiredMixin, View): new_variant = request.GET.get("variant", None) if new_variant: if request.user.has_perm("tezor.change_payment_variant", self.object): - if new_variant in settings.PAYMENT_VARIANTS: + if new_variant in dict(self.object.get_variant_choices()).keys(): self.object.variant = new_variant self.object.save() else: @@ -145,7 +144,9 @@ class InvoiceGroupDetailView(PermissionRequiredMixin, DetailView): def post(self, request, *args, **kwargs): r = super().get(request, *args, **kwargs) if self.invoices_action_form.is_valid(): - action = self.invoices_action_form._get_actions_dict()[self.invoices_action_form.cleaned_data["action"]] + action = self.invoices_action_form._get_actions_dict()[ + self.invoices_action_form.cleaned_data["action"] + ] if request.user.has_perm(action.permission): self.invoices_action_form.execute() return r @@ -162,7 +163,9 @@ class InvoiceGroupDetailView(PermissionRequiredMixin, DetailView): RequestConfig(self.request).configure(invoices_table) context["invoices_table"] = invoices_table - self.invoices_action_form = InvoicesActionForm(self.request, self.request.POST or None, queryset=invoices) + self.invoices_action_form = InvoicesActionForm( + self.request, self.request.POST or None, queryset=invoices + ) context["action_form"] = self.invoices_action_form return context diff --git a/pyproject.toml b/pyproject.toml index 52fcef9c1c803d42532ece3621ae08a3e39eedd2..e888291ee7889b3902944b3e8dd57b9cbba38633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "AlekSIS-App-Tezor" -version = "1.1.1" +version = "1.2.1.dev0" packages = [ { include = "aleksis" } ] @@ -31,7 +31,7 @@ secondary = true [tool.poetry.dependencies] python = "^3.9" aleksis-core = "^2.8.1.dev0" -django-payments = { version = "^0.15.1", extras = ["sofort"] } +django-payments = { version = "^1.0.0", extras = ["sofort"] } django-payments-sepa = "^1.0.1" [tool.poetry.dev-dependencies]