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

Commit

Permalink
YC-1188 Update unpaid PF transactions status in cron task, refactor c…
Browse files Browse the repository at this point in the history
…onfirm transaction code
  • Loading branch information
danickfort committed Jun 6, 2024
1 parent d0a1527 commit d1af953
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 68 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ OAUTH2_PKCE_REQUIRED=false
# Available payment processor. Comma-separated values.
PAYMENT_CURRENCY=CHF
PAYMENT_PROCESSING_TEST_ENVIRONMENT=true
PAYMENT_PENDING_TRANSACTION_MAX_AGE_MINS=1440
# Used to convert dates to the correct UTC, in hours. Choose a value between -12 and +14 (has to be integer)
LOCAL_TIME_ZONE_UTC=+1
# Use "localhost" in local
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ services:
AUTHOR_IBAN_VISIBLE:
PAYMENT_PROCESSING_TEST_ENVIRONMENT:
PAYMENT_CURRENCY:
PAYMENT_PENDING_TRANSACTION_MAX_AGE_MINS:
LOCAL_TIME_ZONE_UTC:
SITE_DOMAIN:
USE_THUMBOR:
Expand Down
20 changes: 20 additions & 0 deletions geocity/apps/submissions/cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,23 @@ def do(self):
inquiry.submission.save()

logger.info("The submission opening Cronjob finished")


class PaymentTransactionsStatusUpdate(CronJobBase):
RUN_EVERY_MINS = 1
schedule = Schedule(run_every_mins=RUN_EVERY_MINS)

code = "submissions.payment_transactions_status_update"

def do(self):
try:
call_command("update_payment_transactions_status")
except CommandError:
logger.error(
"Error occured while trying to update the payment transactions "
"status from the Cronjob."
)
else:
logger.info(
"The payment transactions status update Cronjob finished successfully"
)
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()
- 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"
),
),
]
33 changes: 33 additions & 0 deletions geocity/apps/submissions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,39 @@ def get_last_prolongation_transaction(self):
.first()
)

def set_prolongation_requested_and_notify(self, prolongation_date):
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)
Expand Down
21 changes: 21 additions & 0 deletions geocity/apps/submissions/payments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class Transaction(models.Model):
default=TYPE_SUBMISSION,
)

extra_data = models.JSONField(_("Données supplémentaires"), default=dict)

class Meta:
abstract = True
ordering = ("-creation_date",)
Expand All @@ -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()
Expand Down Expand Up @@ -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(
Expand Down
12 changes: 10 additions & 2 deletions geocity/apps/submissions/payments/postfinance/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,16 @@ def is_transaction_authorized(self, transaction):
TransactionState.AUTHORIZED,
)

def is_transaction_failed(self, transaction):
status = self._get_transaction_status(transaction)
return status in (
TransactionState.FAILED,
TransactionState.VOIDED,
TransactionState.DECLINE,
)

def _create_internal_transaction(
self, submission, transaction_type=None, override_price=None
self, submission, transaction_type=None, override_price=None, extra_data=None
):
# If there is a related existing transaction, which:
# 1. Is still within the PostFinance authorization time window
Expand Down Expand Up @@ -203,7 +211,7 @@ def _create_internal_transaction(
if existing_transaction:
return existing_transaction, False
return super(PostFinanceCheckoutProcessor, self)._create_internal_transaction(
submission, transaction_type, override_price
submission, transaction_type, override_price, extra_data
)

def _save_merchant_data(self, transaction, merchant_transaction_data):
Expand Down
6 changes: 4 additions & 2 deletions geocity/apps/submissions/payments/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def create_merchant_transaction(
raise NotImplementedError

def _create_internal_transaction(
self, submission, transaction_type=None, override_price=None
self, submission, transaction_type=None, override_price=None, extra_data=None
):
price = submission.get_submission_price()
if override_price is None:
Expand All @@ -53,6 +53,8 @@ def _create_internal_transaction(
"amount": amount,
"currency": currency,
}
if extra_data is not None:
create_kwargs.update({"extra_data": extra_data})
if transaction_type is not None:
create_kwargs["transaction_type"] = transaction_type
return (
Expand Down Expand Up @@ -89,6 +91,7 @@ def create_prolongation_transaction_and_return_payment_page_url(
submission,
transaction_type=self.transaction_class.TYPE_PROLONGATION,
override_price=prolongation_price,
extra_data={"prolongation_date": prolongation_date},
)
if is_new_transaction:
merchant_transaction_data = self.create_merchant_transaction(
Expand All @@ -105,7 +108,6 @@ def create_prolongation_transaction_and_return_payment_page_url(
"submissions:confirm_prolongation_transaction",
kwargs={
"pk": transaction.pk,
"prolongation_date": prolongation_date,
},
)
),
Expand Down
2 changes: 1 addition & 1 deletion geocity/apps/submissions/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
name="confirm_transaction",
),
path(
"transactions/confirm_prolongation/<int:pk>/<int:prolongation_date>",
"transactions/confirm_prolongation/<int:pk>",
views.ConfirmProlongationTransactionView.as_view(),
name="confirm_prolongation_transaction",
),
Expand Down
Loading

0 comments on commit d1af953

Please sign in to comment.