Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Commit

Permalink
YC-1149 paiement prolongation (#935)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Alexandre Junod <[email protected]>
  • Loading branch information
3 people authored Mar 7, 2024
1 parent 1b2b529 commit 99f5bde
Show file tree
Hide file tree
Showing 14 changed files with 464 additions and 73 deletions.
60 changes: 52 additions & 8 deletions geocity/apps/submissions/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -366,26 +367,69 @@ 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
}
elif prices.count() == 1:
# 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"]
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
),
),
]
12 changes: 12 additions & 0 deletions geocity/apps/submissions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions geocity/apps/submissions/payments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",)
Expand Down
71 changes: 52 additions & 19 deletions geocity/apps/submissions/payments/postfinance/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 = (
Expand All @@ -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 = (
Expand All @@ -101,20 +127,14 @@ 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

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,
Expand Down Expand Up @@ -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):
Expand Down
Loading

0 comments on commit 99f5bde

Please sign in to comment.