-
Notifications
You must be signed in to change notification settings - Fork 9
YC-1188 - Update unpaid PF transactions status in cron task, refactor c… #1018
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import datetime | ||
|
||
from django.conf import settings | ||
from django.core.management import BaseCommand | ||
from django.utils import timezone | ||
from django.utils.translation import gettext | ||
|
||
from geocity.apps.submissions.payments.models import Transaction | ||
from geocity.apps.submissions.payments.postfinance.models import PostFinanceTransaction | ||
from geocity.apps.submissions.payments.services import get_payment_processor | ||
|
||
|
||
class Command(BaseCommand): | ||
help = gettext( | ||
"Update the status of transactions that are pending and not older than %s hours." | ||
% settings.PAYMENT_PENDING_TRANSACTION_MAX_AGE_MINS | ||
) | ||
|
||
def handle(self, *args, **options): | ||
self.stdout.write( | ||
"Checking status of unpaid transations that are not older than %s minutes..." | ||
% settings.PAYMENT_PENDING_TRANSACTION_MAX_AGE_MINS | ||
) | ||
|
||
nb_transactions_confirmed = 0 | ||
nb_transactions_failed = 0 | ||
# Get all unpaid transactions that are not older than the specified time | ||
transactions_to_update = PostFinanceTransaction.objects.filter( | ||
status=Transaction.STATUS_UNPAID, | ||
authorization_timeout_on__gte=timezone.now() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an older issue here, for a reason I don't get, the value of this attribute in DB is truncated to the day, the time is not stored (2024-06-12 --> 2024-06-12 00:00:00.000 +0200). So with the default value of 30 minutes, it will never work. If we use the value as provided in But it's not clean, is there a way to store the timeout value with the hours? |
||
- datetime.timedelta( | ||
minutes=settings.PAYMENT_PENDING_TRANSACTION_MAX_AGE_MINS | ||
), | ||
) | ||
|
||
for transaction in transactions_to_update: | ||
submission = transaction.submission_price.submission | ||
processor = get_payment_processor(submission.get_form_for_payment()) | ||
|
||
if processor.is_transaction_authorized(transaction): | ||
transaction.confirm_payment() | ||
nb_transactions_confirmed += 1 | ||
elif processor.is_transaction_failed(transaction): | ||
transaction.set_failed() | ||
nb_transactions_failed += 1 | ||
|
||
if nb_transactions_confirmed: | ||
self.stdout.write( | ||
"Marked %d transactions as confirmed." % nb_transactions_confirmed | ||
) | ||
if nb_transactions_failed: | ||
self.stdout.write( | ||
"Marked %d transactions as failed." % nb_transactions_failed | ||
) | ||
if not nb_transactions_confirmed and not nb_transactions_failed: | ||
self.stdout.write("No transactions to update.") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Generated by Django 4.2.11 on 2024-06-06 12:46 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("submissions", "0030_alter_servicefeetype_fix_price_editable"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="historicalpostfinancetransaction", | ||
name="extra_data", | ||
field=models.JSONField( | ||
default=dict, verbose_name="Données supplémentaires" | ||
), | ||
), | ||
migrations.AddField( | ||
model_name="postfinancetransaction", | ||
name="extra_data", | ||
field=models.JSONField( | ||
default=dict, verbose_name="Données supplémentaires" | ||
), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1308,6 +1308,39 @@ def get_last_prolongation_transaction(self): | |
.first() | ||
) | ||
|
||
def set_prolongation_requested_and_notify(self, prolongation_date): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. J'ai profité de l'occasion pour déplacer dans le modèle cette fonction qui sert à setter la date de prolongation . Aussi, j'ai enlevé l'argument |
||
from . import services | ||
|
||
self.prolongation_status = self.PROLONGATION_STATUS_PENDING | ||
self.prolongation_date = prolongation_date | ||
self.save() | ||
|
||
attachments = [] | ||
if self.requires_online_payment() and self.author.userprofile.notify_per_email: | ||
attachments = self.get_submission_payment_attachments("confirmation") | ||
data = { | ||
"subject": "{} ({})".format( | ||
_("Votre demande de prolongation"), self.get_forms_names_list() | ||
), | ||
"users_to_notify": [self.author.email], | ||
"template": "submission_acknowledgment.txt", | ||
"submission": self, | ||
"absolute_uri_func": Submission.get_absolute_url, | ||
} | ||
services.send_email_notification(data, attachments=attachments) | ||
|
||
data = { | ||
"subject": "{} ({})".format( | ||
_("Une demande de prolongation vient d'être soumise"), | ||
self.get_forms_names_list(), | ||
), | ||
"users_to_notify": self.get_secretary_email(), | ||
"template": "submission_prolongation_for_services.txt", | ||
"submission": self, | ||
"absolute_uri_func": Submission.get_absolute_url, | ||
} | ||
services.send_email_notification(data, attachments=attachments) | ||
|
||
# ServiceFees for submission | ||
def get_service_fees(self): | ||
return ServiceFee.objects.filter(submission=self) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,6 +74,8 @@ class Transaction(models.Model): | |
default=TYPE_SUBMISSION, | ||
) | ||
|
||
extra_data = models.JSONField(_("Données supplémentaires"), default=dict) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Utilisé pour stocker la date de prolongation, quand il y a un paiement requis. Elle était avant renvoyée par paramètre dans l'URL au retour de PostFinance, mais il est nécéssaire de la stocker avant de déclencher le paiement, parce qu'il est possible que le paiement soit validé par le cronjob (donc pas de retour depuis PF) |
||
|
||
class Meta: | ||
abstract = True | ||
ordering = ("-creation_date",) | ||
|
@@ -82,6 +84,14 @@ class Meta: | |
def can_have_status_changed(self): | ||
return self.amount > 0 | ||
|
||
@property | ||
def has_been_confirmed(self): | ||
return self.status in ( | ||
self.STATUS_PAID, | ||
self.STATUS_TO_REFUND, | ||
self.STATUS_REFUNDED, | ||
) | ||
|
||
def set_refunded(self): | ||
self.status = self.STATUS_REFUNDED | ||
self.save() | ||
|
@@ -136,6 +146,17 @@ def get_refund_pdf(self, read=False): | |
output = output.read() | ||
return f"refund_{self.transaction_id}.pdf", output | ||
|
||
def confirm_payment(self): | ||
if self.transaction_type == self.TYPE_PROLONGATION: | ||
prolongation_date = self.extra_data.get("prolongation_date") | ||
if prolongation_date is None: | ||
raise SuspiciousOperation | ||
self.submission_price.submission.set_prolongation_requested_and_notify( | ||
timezone.datetime.fromtimestamp(prolongation_date) | ||
) | ||
self.set_paid() | ||
self.submission_price.submission.generate_and_save_pdf("confirmation", self) | ||
|
||
|
||
class ServiceFeeType(models.Model): | ||
administrative_entity = models.ForeignKey( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.