From 99f5bde21bf1042f492d69af09e8b5cce97dd6ea Mon Sep 17 00:00:00 2001 From: Danick Fort <88370151+danickfort@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:30:59 +0100 Subject: [PATCH] YC-1149 paiement prolongation (#935) * YC-1149 prolongation with payment * YC-1149 - Add clearer test * merge develop * lint * lint * fix migration conflict * update html * indent html code * Fix html --------- Co-authored-by: monodo Co-authored-by: Alexandre Junod --- geocity/apps/submissions/forms.py | 60 ++++++- ...cetransaction_transaction_type_and_more.py | 42 +++++ geocity/apps/submissions/models.py | 12 ++ geocity/apps/submissions/payments/models.py | 12 ++ .../payments/postfinance/processor.py | 71 ++++++-- .../apps/submissions/payments/processors.py | 70 +++++++- geocity/apps/submissions/services.py | 2 +- geocity/apps/submissions/tables.py | 4 + .../submissions/_submission_actions.html | 30 ++-- .../submissions/_submission_prices.html | 2 +- .../submissions/submission_prolongation.html | 9 +- geocity/apps/submissions/urls.py | 10 ++ geocity/apps/submissions/views.py | 168 +++++++++++++++--- .../submissions/test_a_permit_request.py | 45 +++++ 14 files changed, 464 insertions(+), 73 deletions(-) create mode 100644 geocity/apps/submissions/migrations/0027_historicalpostfinancetransaction_transaction_type_and_more.py diff --git a/geocity/apps/submissions/forms.py b/geocity/apps/submissions/forms.py index ad6f77412..50443242d 100644 --- a/geocity/apps/submissions/forms.py +++ b/geocity/apps/submissions/forms.py @@ -355,7 +355,8 @@ def save(self): return self.instance -class FormsPriceSelectForm(forms.Form): +class AbstractFormsPriceSelectForm(forms.Form): + selected_price = forms.ChoiceField( label=False, widget=SingleFormRadioSelectWidget(), required=True ) @@ -366,7 +367,10 @@ def __init__(self, instance, *args, **kwargs): prices = form_for_payment.prices.order_by("formprice") initial = {} - if self.instance.submission_price is not None: + if ( + self.instance.submission_price is not None + and self.instance.submission_price.original_price is not None + ): initial = { "selected_price": self.instance.submission_price.original_price.pk } @@ -374,18 +378,58 @@ def __init__(self, instance, *args, **kwargs): # Select the only available price initial = {"selected_price": prices.first().pk} - super(FormsPriceSelectForm, self).__init__( - *args, **{**kwargs, "initial": initial} - ) - - if self.instance.status != self.instance.STATUS_DRAFT: - self.fields["selected_price"].widget.attrs["disabled"] = "disabled" + super().__init__(*args, **{**kwargs, "initial": initial}) choices = [] for price in prices: choices.append((price.pk, price.str_for_choice())) self.fields["selected_price"].choices = choices + +class FormsPriceSelectForm(AbstractFormsPriceSelectForm): + + selected_price = forms.ChoiceField( + label=False, widget=SingleFormRadioSelectWidget(), required=True + ) + + def __init__(self, instance, *args, **kwargs): + super().__init__(instance, *args, **kwargs) + if self.instance.status != self.instance.STATUS_DRAFT: + self.fields["selected_price"].widget.attrs["disabled"] = "disabled" + + @transaction.atomic + def save(self): + selected_price_id = self.cleaned_data["selected_price"] + selected_price = Price.objects.get(pk=selected_price_id) + price_data = { + "amount": selected_price.amount, + "currency": selected_price.currency, + "text": selected_price.text, + } + current_submission_price = self.instance.get_submission_price() + if current_submission_price is None: + SubmissionPrice.objects.create( + **{ + **price_data, + "original_price": selected_price, + "submission": self.instance, + } + ) + else: + if self.instance.status != self.instance.STATUS_DRAFT: + raise forms.ValidationError( + _("Le prix ne peut pas être modifié pour cette demande.") + ) + current_submission_price.amount = price_data["amount"] + current_submission_price.text = price_data["text"] + current_submission_price.currency = price_data["currency"] + current_submission_price.original_price = selected_price + current_submission_price.save() + + return self.instance + + +class ProlongationFormsPriceSelectForm(AbstractFormsPriceSelectForm): @transaction.atomic def save(self): selected_price_id = self.cleaned_data["selected_price"] diff --git a/geocity/apps/submissions/migrations/0027_historicalpostfinancetransaction_transaction_type_and_more.py b/geocity/apps/submissions/migrations/0027_historicalpostfinancetransaction_transaction_type_and_more.py new file mode 100644 index 000000000..086ec4a39 --- /dev/null +++ b/geocity/apps/submissions/migrations/0027_historicalpostfinancetransaction_transaction_type_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.9 on 2024-02-08 10:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "submissions", + "0026_service_fee", + ), + ] + + operations = [ + migrations.AddField( + model_name="historicalpostfinancetransaction", + name="transaction_type", + field=models.CharField( + choices=[ + ("submission", "Soumission"), + ("prolongation", "Prolongation"), + ], + default="submission", + max_length=20, + verbose_name="Type de transaction", + ), + ), + migrations.AddField( + model_name="postfinancetransaction", + name="transaction_type", + field=models.CharField( + choices=[ + ("submission", "Soumission"), + ("prolongation", "Prolongation"), + ], + default="submission", + max_length=20, + verbose_name="Type de transaction", + ), + ), + ] diff --git a/geocity/apps/submissions/models.py b/geocity/apps/submissions/models.py index 357785de3..b0c437cd4 100644 --- a/geocity/apps/submissions/models.py +++ b/geocity/apps/submissions/models.py @@ -1271,6 +1271,18 @@ def get_transactions(self): return None return self.submission_price.get_transactions() + def get_last_prolongation_transaction(self): + from .payments.models import Transaction + + if self.get_transactions() is None: + return None + return ( + self.get_transactions() + .filter(transaction_type=Transaction.TYPE_PROLONGATION) + .order_by("-updated_date") + .first() + ) + # ServiceFees for submission def get_service_fees(self): return ServiceFee.objects.filter(submission=self) diff --git a/geocity/apps/submissions/payments/models.py b/geocity/apps/submissions/payments/models.py index 0afad3724..dc49d1765 100644 --- a/geocity/apps/submissions/payments/models.py +++ b/geocity/apps/submissions/payments/models.py @@ -35,6 +35,8 @@ class Transaction(models.Model): STATUS_TO_REFUND = "to_refund" STATUS_REFUNDED = "refunded" STATUS_FAILED = "failed" + TYPE_SUBMISSION = "submission" + TYPE_PROLONGATION = "prolongation" submission_price = models.ForeignKey( "SubmissionPrice", on_delete=models.CASCADE, @@ -62,6 +64,16 @@ class Transaction(models.Model): ) payment_url = models.CharField(_("URL de paiement"), max_length=255, null=True) + transaction_type = models.CharField( + _("Type de transaction"), + choices=[ + (TYPE_SUBMISSION, _("Soumission")), + (TYPE_PROLONGATION, _("Prolongation")), + ], + max_length=20, + default=TYPE_SUBMISSION, + ) + class Meta: abstract = True ordering = ("-creation_date",) diff --git a/geocity/apps/submissions/payments/postfinance/processor.py b/geocity/apps/submissions/payments/postfinance/processor.py index c587ae578..b32094f90 100644 --- a/geocity/apps/submissions/payments/postfinance/processor.py +++ b/geocity/apps/submissions/payments/postfinance/processor.py @@ -52,7 +52,9 @@ def _get_transaction_payment_page_service_api(self): ) return self.transaction_payment_page_service - def create_merchant_transaction(self, request, submission, transaction): + def create_merchant_transaction( + self, request, submission, transaction, extra_kwargs=None + ): """ Creates a transaction on PostFinance Checkout. The transaction contains 1 line item with: - Name: The submission's price's text (description of the price) @@ -73,7 +75,10 @@ def create_merchant_transaction(self, request, submission, transaction): ) # If the form is None interrupt payment flow - if not submission.get_form_for_payment() or not submission.requires_payment: + if ( + not submission.get_form_for_payment() + or not submission.requires_online_payment() + ): raise SuspiciousOperation internal_account = ( @@ -82,13 +87,34 @@ def create_merchant_transaction(self, request, submission, transaction): attribute = LineItemAttribute(label="Compte interne", value=internal_account) + if extra_kwargs is None: + amount = submission.price.amount + currency = submission.price.currency + text = submission.price.text + price_pk = submission.price.original_price.pk + success_url = request.build_absolute_uri( + reverse( + "submissions:confirm_transaction", kwargs={"pk": transaction.pk} + ) + ) + failed_url = request.build_absolute_uri( + reverse("submissions:fail_transaction", kwargs={"pk": transaction.pk}) + ) + else: + amount = extra_kwargs["amount"] + currency = extra_kwargs["currency"] + text = extra_kwargs["text"] + price_pk = extra_kwargs["pk"] + success_url = extra_kwargs["success_url"] + failed_url = extra_kwargs["failed_url"] + line_item = LineItem( - name=submission.price.text, + name=text, unique_id=str(submission.pk), - sku=str(submission.price.original_price.pk), + sku=str(price_pk), quantity=1, attributes={"internal_account": attribute}, - amount_including_tax=float(submission.price.amount), + amount_including_tax=float(amount), type=LineItemType.PRODUCT, ) environment_selection_strategy = ( @@ -101,12 +127,6 @@ def create_merchant_transaction(self, request, submission, transaction): if settings.PAYMENT_PROCESSING_TEST_ENVIRONMENT else Environment.LIVE ) - success_url = request.build_absolute_uri( - reverse("submissions:confirm_transaction", kwargs={"pk": transaction.pk}) - ) - failed_url = request.build_absolute_uri( - reverse("submissions:fail_transaction", kwargs={"pk": transaction.pk}) - ) merchant_reference = f"GEOCITY-{submission.id}" customer_id = f"GEOCITY-{request.user.id}" customer_email_address = request.user.email @@ -114,7 +134,7 @@ def create_merchant_transaction(self, request, submission, transaction): merchant_transaction = TransactionCreate( line_items=[line_item], auto_confirmation_enabled=True, - currency=submission.price.currency, + currency=currency, environment=environment, environment_selection_strategy=environment_selection_strategy, success_url=success_url, @@ -153,24 +173,37 @@ def is_transaction_authorized(self, transaction): TransactionState.AUTHORIZED, ) - def _create_internal_transaction(self, submission): + def _create_internal_transaction( + self, submission, transaction_type=None, override_price=None + ): # If there is a related existing transaction, which: # 1. Is still within the PostFinance authorization time window # 2. Has the same amount and currency # (if it is different, it means that the user has chosen a different price) # 3. Is unpaid # Then we can reuse it, instead of re-generating another one + if override_price is None: + amount = submission.price.amount + currency = submission.price.currency + else: + amount = override_price.amount + currency = override_price.currency + filter_kwargs = { + "submission_price": submission.submission_price, + "amount": amount, + "currency": currency, + "status": self.transaction_class.STATUS_UNPAID, + "authorization_timeout_on__gt": timezone.now(), + } + if transaction_type is not None: + filter_kwargs["transaction_type"] = transaction_type existing_transaction = self.transaction_class.objects.filter( - submission_price=submission.submission_price, - amount=submission.submission_price.amount, - currency=submission.submission_price.currency, - status=self.transaction_class.STATUS_UNPAID, - authorization_timeout_on__gt=timezone.now(), + **filter_kwargs ).first() if existing_transaction: return existing_transaction, False return super(PostFinanceCheckoutProcessor, self)._create_internal_transaction( - submission + submission, transaction_type, override_price ) def _save_merchant_data(self, transaction, merchant_transaction_data): diff --git a/geocity/apps/submissions/payments/processors.py b/geocity/apps/submissions/payments/processors.py index fd5477d53..5fce258ab 100644 --- a/geocity/apps/submissions/payments/processors.py +++ b/geocity/apps/submissions/payments/processors.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.urls import reverse class MissingPaymentProcessorSettingError(Exception): @@ -24,7 +25,9 @@ def _check_required_settings(self): def __init__(self): self._check_required_settings() - def create_merchant_transaction(self, request, submission, transaction): + def create_merchant_transaction( + self, request, submission, transaction, extra_kwargs=None + ): """ Returns a dict with the merchant transaction data. Example: { @@ -34,15 +37,26 @@ def create_merchant_transaction(self, request, submission, transaction): """ raise NotImplementedError - def _create_internal_transaction(self, submission): + def _create_internal_transaction( + self, submission, transaction_type=None, override_price=None + ): price = submission.get_submission_price() + if override_price is None: + currency = price.currency + amount = price.amount + else: + currency = override_price.currency + amount = override_price.amount + create_kwargs = { + "submission_price": price, + "transaction_id": 0, + "amount": amount, + "currency": currency, + } + if transaction_type is not None: + create_kwargs["transaction_type"] = transaction_type return ( - self.transaction_class.objects.create( - submission_price=price, - transaction_id=0, # TODO: field null=True instead of this? - amount=price.amount, - currency=price.currency, - ), + self.transaction_class.objects.create(**create_kwargs), True, ) @@ -68,6 +82,46 @@ def create_transaction_and_return_payment_page_url(self, submission, request): else: return transaction.payment_url + def create_prolongation_transaction_and_return_payment_page_url( + self, submission, prolongation_price, prolongation_date, request + ): + transaction, is_new_transaction = self._create_internal_transaction( + submission, + transaction_type=self.transaction_class.TYPE_PROLONGATION, + override_price=prolongation_price, + ) + if is_new_transaction: + merchant_transaction_data = self.create_merchant_transaction( + request, + submission, + transaction, + extra_kwargs={ + "amount": prolongation_price.amount, + "currency": prolongation_price.currency, + "text": prolongation_price.text, + "pk": prolongation_price.pk, + "success_url": request.build_absolute_uri( + reverse( + "submissions:confirm_prolongation_transaction", + kwargs={ + "pk": transaction.pk, + "prolongation_date": prolongation_date, + }, + ) + ), + "failed_url": request.build_absolute_uri( + reverse( + "submissions:fail_prolongation_transaction", + kwargs={"pk": transaction.pk}, + ) + ), + }, + ) + self._save_merchant_data(transaction, merchant_transaction_data) + return merchant_transaction_data["payment_page_url"] + else: + return transaction.payment_url + def _get_transaction_status(self, transaction): raise NotImplementedError diff --git a/geocity/apps/submissions/services.py b/geocity/apps/submissions/services.py index 3fa9ed713..11fb96bd7 100644 --- a/geocity/apps/submissions/services.py +++ b/geocity/apps/submissions/services.py @@ -72,7 +72,7 @@ def submit_submission(submission, request): # Check if submission requires payment attachments = [] - if submission.requires_payment: + if submission.requires_online_payment(): attachments = submission.get_submission_payment_attachments("confirmation") send_email_notification(data, attachments=attachments) diff --git a/geocity/apps/submissions/tables.py b/geocity/apps/submissions/tables.py index 77d8fb4b0..e6034738f 100644 --- a/geocity/apps/submissions/tables.py +++ b/geocity/apps/submissions/tables.py @@ -353,6 +353,9 @@ class TransactionsTable(tables.Table): updated_date = tables.Column( verbose_name=_("Date de modification"), orderable=False ) + transaction_type = tables.Column( + verbose_name=_("Type de transaction"), orderable=False + ) transaction_id = tables.Column(verbose_name=_("ID transaction"), orderable=False) amount = tables.Column(verbose_name=_("Montant"), orderable=False) currency = tables.Column(verbose_name=_("Devise"), orderable=False) @@ -367,6 +370,7 @@ class Meta: model = Transaction fields = ( "transaction_id", + "transaction_type", "creation_date", "updated_date", "amount", diff --git a/geocity/apps/submissions/templates/submissions/_submission_actions.html b/geocity/apps/submissions/templates/submissions/_submission_actions.html index 20bf1a139..37fa1b910 100644 --- a/geocity/apps/submissions/templates/submissions/_submission_actions.html +++ b/geocity/apps/submissions/templates/submissions/_submission_actions.html @@ -170,10 +170,10 @@

{{ object_type }}

+ {% if forms.amend.disabled %}disabled{% endif %}>{% translate "Enregistrer" %}
@@ -189,7 +189,7 @@

{% translate "Mise en consultation publique" %}

@@ -315,10 +315,7 @@

{% translate "Classement de la demande" %}

{% endif %} {% endif %} {% if forms.prolong and prolongation_enabled %} -
+

{% translate "Prolongation de la demande" %}

{% csrf_token %} @@ -327,15 +324,18 @@

{% translate "Prolongation de la demande" %}

{% translate "Aucune prolongation n'a été demandée par l'auteur-e." %}
{% endif %} {% bootstrap_form forms.prolong layout='horizontal' %} + {% if submission.get_last_prolongation_transaction %} +
+ +
+ {{ submission.get_last_prolongation_transaction.updated_date }}: {{ submission.submission_price.text }} - {{ submission.get_last_prolongation_transaction.amount }} {{ submission.get_last_prolongation_transaction.currency }} - {{ submission.get_last_prolongation_transaction.get_status_display }} (ID {{ submission.get_last_prolongation_transaction.transaction_id }}) +
+
+ {% endif %}
- - + +
diff --git a/geocity/apps/submissions/templates/submissions/_submission_prices.html b/geocity/apps/submissions/templates/submissions/_submission_prices.html index b28c8ce7a..755d993c8 100644 --- a/geocity/apps/submissions/templates/submissions/_submission_prices.html +++ b/geocity/apps/submissions/templates/submissions/_submission_prices.html @@ -1,4 +1,4 @@ -{% if submission.requires_online_payment and submission.get_form_for_payment.requires_online_payment %} +{% if submission.requires_online_payment %}
{{ submission.get_form_for_payment.payment_settings.prices_label }}
diff --git a/geocity/apps/submissions/templates/submissions/submission_prolongation.html b/geocity/apps/submissions/templates/submissions/submission_prolongation.html index 796a67747..ae3736fcf 100644 --- a/geocity/apps/submissions/templates/submissions/submission_prolongation.html +++ b/geocity/apps/submissions/templates/submissions/submission_prolongation.html @@ -13,10 +13,17 @@

{% translate "Demande de prolongation de permis" %}

{{ submission_prolongation_form.media }} {% bootstrap_form submission_prolongation_form %} + {% include "submissions/_submission_prices.html" %} {% buttons %}
{% translate "Retour à la liste des demandes" %} - +
{% endbuttons %} diff --git a/geocity/apps/submissions/urls.py b/geocity/apps/submissions/urls.py index 998194f23..aa07b1df6 100644 --- a/geocity/apps/submissions/urls.py +++ b/geocity/apps/submissions/urls.py @@ -131,6 +131,16 @@ views.ConfirmTransactionCallback.as_view(), name="confirm_transaction", ), + path( + "transactions/confirm_prolongation//", + views.ConfirmProlongationTransactionView.as_view(), + name="confirm_prolongation_transaction", + ), + path( + "transactions/fail_prolongation/", + views.FailProlongationTransactionCallback.as_view(), + name="fail_prolongation_transaction", + ), path( "transactions/fail/", views.FailTransactionCallback.as_view(), diff --git a/geocity/apps/submissions/views.py b/geocity/apps/submissions/views.py index 345d76c63..e4666c74e 100644 --- a/geocity/apps/submissions/views.py +++ b/geocity/apps/submissions/views.py @@ -49,7 +49,7 @@ ) from geocity.apps.accounts.models import AdministrativeEntity, PermitDepartment from geocity.apps.accounts.users import get_departments, has_profile -from geocity.apps.forms.models import Field, Form +from geocity.apps.forms.models import Field, Form, Price from . import filters, forms, models, permissions, services, shortcuts, tables from .exceptions import BadSubmissionStatus, NonProlongableSubmission @@ -1444,6 +1444,43 @@ def submission_fields(request, submission_id): ) +def _set_prolongation_requested_and_notify(submission, request): + submission.prolongation_status = submission.PROLONGATION_STATUS_PENDING + submission.save() + + attachments = [] + if ( + submission.requires_online_payment() + and submission.author.userprofile.notify_per_email + ): + attachments = submission.get_submission_payment_attachments("confirmation") + data = { + "subject": "{} ({})".format( + _("Votre demande de prolongation"), submission.get_forms_names_list() + ), + "users_to_notify": [submission.author.email], + "template": "submission_acknowledgment.txt", + "submission": submission, + "absolute_uri_func": request.build_absolute_uri, + } + services.send_email_notification(data, attachments=attachments) + + # Send the email to the services + messages.success(request, _("Votre demande de prolongation a été envoyée")) + + data = { + "subject": "{} ({})".format( + _("Une demande de prolongation vient d'être soumise"), + submission.get_forms_names_list(), + ), + "users_to_notify": submission.get_secretary_email(), + "template": "submission_prolongation_for_services.txt", + "submission": submission, + "absolute_uri_func": request.build_absolute_uri, + } + services.send_email_notification(data, attachments=attachments) + + @redirect_bad_status_to_detail @login_required @permanent_user_required @@ -1463,31 +1500,55 @@ def submission_prolongation(request, submission_id): messages.error(request, _("La demande de permis ne peut pas être prolongée.")) return redirect("submissions:submissions_list") + prices_form = None + requires_online_payment = False + if submission.requires_online_payment(): + form_payment = submission.get_form_for_payment() + if form_payment is not None: + requires_online_payment = form_payment.requires_online_payment + if request.method == "POST": form = forms.SubmissionProlongationForm(instance=submission, data=request.POST) del form.fields["prolongation_status"] del form.fields["prolongation_comment"] - if form.is_valid(): - obj = form.save(commit=False) - obj.prolongation_status = submission.PROLONGATION_STATUS_PENDING - obj.save() - - # Send the email to the services - messages.success(request, _("Votre demande de prolongation a été envoyée")) - - data = { - "subject": "{} ({})".format( - _("Une demande de prolongation vient d'être soumise"), - submission.get_forms_names_list(), - ), - "users_to_notify": submission.get_secretary_email(), - "template": "submission_prolongation_for_services.txt", - "submission": form.instance, - "absolute_uri_func": request.build_absolute_uri, - } - services.send_email_notification(data) + prices_form_valid = True + if requires_online_payment: + prices_form = forms.ProlongationFormsPriceSelectForm( + instance=submission, data=request.POST + ) + if prices_form.is_valid(): + prices_form.save() + else: + prices_form_valid = False + if form.is_valid() and prices_form_valid: + if requires_online_payment: + if not prices_form.cleaned_data.get("selected_price"): + messages.error(request, _("Veuillez sélectionner un tarif")) + return render( + request, + "submissions/submission_prolongation.html", + { + "submission": submission, + "submission_prolongation_form": form, + "prices_form": prices_form, + }, + ) + else: + price = Price.objects.get( + pk=int(prices_form.cleaned_data.get("selected_price")) + ) + processor = get_payment_processor(submission.get_form_for_payment()) + payment_url = processor.create_prolongation_transaction_and_return_payment_page_url( + submission, + price, + int(form.cleaned_data["prolongation_date"].timestamp()), + request, + ) + return redirect(payment_url) + obj = form.save() + _set_prolongation_requested_and_notify(obj, request) return redirect("submissions:submissions_list") else: if submission.author != request.user: @@ -1515,6 +1576,8 @@ def submission_prolongation(request, submission_id): return redirect("submissions:submissions_list") form = forms.SubmissionProlongationForm(instance=submission) + if requires_online_payment: + prices_form = forms.ProlongationFormsPriceSelectForm(instance=submission) del form.fields["prolongation_status"] del form.fields["prolongation_comment"] @@ -1524,6 +1587,7 @@ def submission_prolongation(request, submission_id): { "submission": submission, "submission_prolongation_form": form, + "prices_form": prices_form, }, ) @@ -2649,3 +2713,67 @@ def submission_validations_edit(request, submission_id): "formset": formset, }, ) + + +@method_decorator(login_required, name="dispatch") +class ConfirmProlongationTransactionView(View): + def get(self, request, pk, prolongation_date, *args, **kwargs): + transaction = get_transaction_from_id(pk) + submission = transaction.submission_price.submission + + submission.generate_and_save_pdf("confirmation", transaction) + + if ( + not request.user == submission.author + or not transaction.status == transaction.STATUS_UNPAID + ): + raise PermissionDenied + + processor = get_payment_processor(submission.get_form_for_payment()) + if processor.is_transaction_authorized(transaction): + transaction.set_paid() + submission.prolongation_date = datetime.fromtimestamp(prolongation_date) + _set_prolongation_requested_and_notify(submission, request) + return render( + request, + "submissions/submission_payment_callback_confirm.html", + { + "submission": submission, + }, + ) + + transaction.set_failed() + return render( + request, + "submissions/submission_payment_callback_fail.html", + { + "submission": submission, + "submission_url": reverse( + "submissions:submission_prolongation", + kwargs={"submission_id": submission.pk}, + ), + }, + ) + + +@method_decorator(login_required, name="dispatch") +class FailProlongationTransactionCallback(View): + def get(self, request, pk, *args, **kwargs): + transaction = get_transaction_from_id(pk) + submission = transaction.submission_price.submission + if not request.user == submission.author: + raise PermissionDenied + + transaction.set_failed() + + return render( + request, + "submissions/submission_payment_callback_fail.html", + { + "submission": submission, + "submission_url": reverse( + "submissions:submission_prolongation", + kwargs={"submission_id": submission.pk}, + ), + }, + ) diff --git a/geocity/tests/submissions/test_a_permit_request.py b/geocity/tests/submissions/test_a_permit_request.py index 84a8cd2a0..84b284720 100644 --- a/geocity/tests/submissions/test_a_permit_request.py +++ b/geocity/tests/submissions/test_a_permit_request.py @@ -2870,6 +2870,51 @@ def test_price_is_required_to_be_selected_in_submit_page(self): assert "Vous devez choisir un tarif" in response.content.decode() + def test_price_selection_in_prolongation_page(self): + submission = factories.SubmissionFactory( + author=self.user, status=submissions_models.Submission.STATUS_APPROVED + ) + + self.parent_type.form.needs_date = True + self.parent_type.form.permit_duration = 120 + self.parent_type.form.expiration_reminder = True + self.parent_type.form.days_before_reminder = 10 + self.parent_type.form.save() + + submission.forms.set([self.parent_type.form]) + + response = self.client.get( + reverse( + "submissions:submission_prolongation", + kwargs={"submission_id": submission.pk}, + ) + ) + content = response.content.decode() + self.assertInHTML('', content) + + def test_single_price_is_preselected_in_prolongation_page(self): + submission = factories.SubmissionFactory( + author=self.user, status=submissions_models.Submission.STATUS_APPROVED + ) + + price = factories.PriceFactory() + self.parent_type.form.prices.set([price]) + self.parent_type.form.needs_date = True + self.parent_type.form.permit_duration = 120 + self.parent_type.form.expiration_reminder = True + self.parent_type.form.days_before_reminder = 10 + self.parent_type.form.save() + + submission.forms.set([self.parent_type.form]) + + response = self.client.get( + reverse( + "submissions:submission_prolongation", + kwargs={"submission_id": submission.pk}, + ) + ) + assert response.context["prices_form"].initial["selected_price"] == price.pk + class AdministrativeEntitySecretaryEmailTestcase(TestCase): def setUp(self):