From cae1c6fdb87f74e08103b986cc5d6aeb0431b0f2 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Fri, 23 Feb 2024 18:02:24 +0100 Subject: [PATCH] Send payment confirmation mail after payment, and send weekly reminders for people that have not paid Signed-off-by: Emmy D'Anello --- .../management/commands/check_hello_asso.py | 87 ------------------- .../management/commands/check_hello_asso.py | 25 ++++++ .../management/commands/remind_payments.py | 17 ++++ registration/models.py | 34 +++++++- .../mails/payment_confirmation.html | 4 +- .../mails/payment_confirmation.txt | 5 +- .../registration/mails/payment_reminder.html | 14 +-- .../registration/mails/payment_reminder.txt | 7 +- registration/views.py | 1 + tfjm.cron | 2 + tfjm/helloasso.py | 2 +- 11 files changed, 92 insertions(+), 106 deletions(-) delete mode 100644 participation/management/commands/check_hello_asso.py create mode 100644 registration/management/commands/check_hello_asso.py create mode 100644 registration/management/commands/remind_payments.py diff --git a/participation/management/commands/check_hello_asso.py b/participation/management/commands/check_hello_asso.py deleted file mode 100644 index a3d1b48..0000000 --- a/participation/management/commands/check_hello_asso.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (C) 2020 by Animath -# SPDX-License-Identifier: GPL-3.0-or-later - -import os - -from django.contrib.auth.models import User -from django.core.management import BaseCommand -from django.db.models import Q -import requests - - -class Command(BaseCommand): - def handle(self, *args, **options): # noqa: C901 - # Get access token - response = requests.post('https://api.helloasso.com/oauth2/token', headers={ - 'Content-Type': 'application/x-www-form-urlencoded', - }, data={ - 'client_id': os.getenv('HELLOASSO_CLIENT_ID', ''), - 'client_secret': os.getenv('HELLOASSO_CLIENT_SECRET', ''), - 'grant_type': 'client_credentials', - }).json() - - token = response['access_token'] - - organization = "animath" - form_slug = "tfjm-2023-tournois-regionaux" - from_date = "2000-01-01" - url = f"https://api.helloasso.com/v5/organizations/{organization}/forms/Event/{form_slug}/payments" \ - f"?from={from_date}&pageIndex=1&pageSize=100&retrieveOfflineDonations=false" - headers = { - "Accept": "application/json", - "Authorization": f"Bearer {token}", - } - http_response = requests.get(url, headers=headers) - response = http_response.json() - - if http_response.status_code != 200: - message = response["message"] - self.stderr.write(f"Error while querying Hello Asso: {message}") - return - - for payment in response["data"]: - if payment["state"] != "Authorized": - continue - - payer = payment["payer"] - email = payer["email"] - last_name = payer["lastName"] - first_name = payer["firstName"] - base_filter = Q( - registration__participantregistration__isnull=False, - registration__participantregistration__team__isnull=False, - registration__participantregistration__team__participation__valid=True, - ) - qs = User.objects.filter( - base_filter, - email=email, - ) - if not qs.exists(): - qs = User.objects.filter( - base_filter, - last_name__icontains=last_name, - ) - if qs.count() >= 2: - qs = qs.filter(first_name__icontains=first_name) - if not qs.exists(): - self.stderr.write(f"Warning: a payment was found by {first_name} {last_name} ({email}), " - "but this user is unknown.") - continue - if qs.count() > 1: - self.stderr.write(f"Warning: a payment was found by {first_name} {last_name} ({email}), " - f"but there are {qs.count()} matching users.") - continue - user = qs.get() - if not user.registration.participates: - self.stderr.write(f"Warning: a payment was found by the email address {email}, " - "but this user is not a participant.") - continue - payment_obj = user.registration.payment - payment_obj.valid = True - payment_obj.type = "helloasso" - payment_obj.additional_information = f"Identifiant de transation : {payment['id']}\n" \ - f"Date : {payment['date']}\n" \ - f"Reçu : {payment['paymentReceiptUrl']}\n" \ - f"Montant : {payment['amount'] / 100:.2f} €" - payment_obj.save() - self.stdout.write(f"{payment_obj} is validated") diff --git a/registration/management/commands/check_hello_asso.py b/registration/management/commands/check_hello_asso.py new file mode 100644 index 0000000..90b3e89 --- /dev/null +++ b/registration/management/commands/check_hello_asso.py @@ -0,0 +1,25 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later + +import json + +from django.core.management import BaseCommand + +from registration.models import Payment + + +class Command(BaseCommand): + """ + This command checks if the initiated Hello Asso payments are validated or not. + """ + help = "Vérifie si les paiements Hello Asso initiés sont validés ou non. Si oui, valide les inscriptions." + + def handle(self, *args, **options): + for payment in Payment.objects.exclude(valid=True).filter(checkout_intent_id__isnull=False).all(): + checkout_intent = payment.get_checkout_intent() + if checkout_intent is not None and 'order' in checkout_intent: + payment.type = 'helloasso' + payment.valid = True + payment.additional_information = json.dumps(checkout_intent['order']) + payment.save() + payment.send_helloasso_payment_confirmation_mail() diff --git a/registration/management/commands/remind_payments.py b/registration/management/commands/remind_payments.py new file mode 100644 index 0000000..ec85a9c --- /dev/null +++ b/registration/management/commands/remind_payments.py @@ -0,0 +1,17 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.core.management import BaseCommand + +from registration.models import Payment + + +class Command(BaseCommand): + """ + This command sends a mail to each participant who has not yet paid. + """ + help = "Envoie un mail de rappel à toustes les participant⋅es qui n'ont pas encore payé ou déclaré de paiement." + + def handle(self, *args, **options): + for payment in Payment.objects.filter(valid=False).filter(registrations__team__participation__valid=True).all(): + payment.send_remind_mail() diff --git a/registration/models.py b/registration/models.py index bd869e3..4ebdea4 100644 --- a/registration/models.py +++ b/registration/models.py @@ -4,11 +4,12 @@ from datetime import date, datetime from django.contrib.sites.models import Site +from django.core.mail import send_mail from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.template import loader from django.urls import reverse_lazy, reverse -from django.utils import timezone +from django.utils import timezone, translation from django.utils.crypto import get_random_string from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode @@ -634,6 +635,37 @@ class Payment(models.Model): return checkout_intent + def send_remind_mail(self): + translation.activate('fr') + subject = "[TFJM²] " + str(_("Reminder for your payment")) + site = Site.objects.first() + for registration in self.registrations.all(): + message = loader.render_to_string('registration/mails/payment_reminder.txt', + dict(registration=registration, payment=self, domain=site.domain)) + html = loader.render_to_string('registration/mails/payment_reminder.html', + dict(registration=registration, payment=self, domain=site.domain)) + registration.user.email_user(subject, message, html_message=html) + + def send_helloasso_payment_confirmation_mail(self): + translation.activate('fr') + subject = "[TFJM²] " + str(_("Payment confirmation")) + site = Site.objects.first() + for registration in self.registrations.all(): + message = loader.render_to_string('registration/mails/payment_confirmation.txt', + dict(registration=registration, payment=self, domain=site.domain)) + html = loader.render_to_string('registration/mails/payment_confirmation.html', + dict(registration=registration, payment=self, domain=site.domain)) + registration.user.email_user(subject, message, html_message=html) + + payer = self.get_checkout_intent()['order']['payer'] + payer_name = f"{payer['firstName']} {payer['lastName']}" + if not self.registrations.filter(user__email=payer['email']).exists(): + message = loader.render_to_string('registration/mails/payment_confirmation.txt', + dict(registration=payer_name, payment=self, domain=site.domain)) + html = loader.render_to_string('registration/mails/payment_confirmation.html', + dict(registration=payer_name, payment=self, domain=site.domain)) + send_mail(subject, message, None, [payer['email']], html_message=html) + def get_absolute_url(self): return reverse_lazy("registration:update_payment", args=(self.pk,)) diff --git a/registration/templates/registration/mails/payment_confirmation.html b/registration/templates/registration/mails/payment_confirmation.html index 8d0a182..6547bcc 100644 --- a/registration/templates/registration/mails/payment_confirmation.html +++ b/registration/templates/registration/mails/payment_confirmation.html @@ -9,7 +9,7 @@

- {% trans "Hi" %} {{ user.registration }}, + {% trans "Hi" %} {{ registration }},

@@ -28,7 +28,7 @@

diff --git a/registration/templates/registration/mails/payment_confirmation.txt b/registration/templates/registration/mails/payment_confirmation.txt index 5641cb6..03d62e0 100644 --- a/registration/templates/registration/mails/payment_confirmation.txt +++ b/registration/templates/registration/mails/payment_confirmation.txt @@ -1,6 +1,5 @@ {% load i18n %} - -{% trans "Hi" %} {{ user.registration }}, +{% trans "Hi" %} {{ registration|safe }}, {% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament.name %} We successfully received the payment of {{ amount }} € for the TFJM² registration in the team {{ team }} for the tournament {{ tournament }}! @@ -12,7 +11,7 @@ We successfully received the payment of {{ amount }} € for the TFJM² registra {% trans "As a reminder, here are the following important dates:" %} * {% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }} * {% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }} -* {% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.start|date }} {% trans "to" %} {{ payment.tournament.end|date }} +* {% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.date_start|date }} {% trans "to" %} {{ payment.tournament.date_end|date }} {% trans "Please note that these dates may be subject to change. If your local organizers gave you different dates, trust them." %} diff --git a/registration/templates/registration/mails/payment_reminder.html b/registration/templates/registration/mails/payment_reminder.html index e3c3973..7de8452 100644 --- a/registration/templates/registration/mails/payment_reminder.html +++ b/registration/templates/registration/mails/payment_reminder.html @@ -9,7 +9,7 @@

- {% trans "Hi" %} {{ user.registration }}, + {% trans "Hi" %} {{ registration }},

@@ -19,19 +19,19 @@ {% endblocktrans %}

-

- {% if payment.grouped %} +{% if payment.grouped %} +

{% trans "This price includes the registrations of all members of your team." %} - {% endif %} -

+

+{% endif %}

{% trans "You can pay by credit card or by bank transfer. You can read full instructions on the payment page:" %}

- - https://{{ domain }}{% url "registration.update_payment" pk=payment.pk %} + + https://{{ domain }}{% url "registration:update_payment" pk=payment.pk %}

diff --git a/registration/templates/registration/mails/payment_reminder.txt b/registration/templates/registration/mails/payment_reminder.txt index b4cad56..18e2be7 100644 --- a/registration/templates/registration/mails/payment_reminder.txt +++ b/registration/templates/registration/mails/payment_reminder.txt @@ -1,19 +1,16 @@ {% load i18n %} - -{% trans "Hi" %} {{ user.registration }}, +{% trans "Hi" %} {{ registration|safe }}, {% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament %} You are registered for the TFJM² of {{ tournament }}. Your team {{ team }} has been successfully validated. To end your inscription, you must pay the amount of {{ amount }} €. {% endblocktrans %} - {% if payment.grouped %} {% trans "This price includes the registrations of all members of your team." %} {% endif %} - {% trans "You can pay by credit card or by bank transfer. You can read full instructions on the payment page:" %} -https://{{ domain }}{% url "registration.update_payment" pk=payment.pk %} +https://{{ domain }}{% url "registration:update_payment" pk=payment.pk %} {% trans "If you have a scholarship, then the registration is free for you. You must then upload it on the payment page using the above link." %} diff --git a/registration/views.py b/registration/views.py index 787680a..5972ff3 100644 --- a/registration/views.py +++ b/registration/views.py @@ -623,6 +623,7 @@ class PaymentHelloAssoReturnView(DetailView): payment.save() messages.success(request, _("The payment has been successfully validated! " "Your registration is now complete.")) + payment.send_helloasso_payment_confirmation_mail() else: payment.type = "helloasso" payment.valid = None diff --git a/tfjm.cron b/tfjm.cron index 4d01caa..f54319a 100644 --- a/tfjm.cron +++ b/tfjm.cron @@ -9,6 +9,8 @@ # Check payments from Hello Asso */6 * * * * cd /code && python manage.py check_hello_asso &> /dev/null +# Send reminders for payments +30 6 * * 1 cd /code && python manage.py remind_payments &> /dev/null # Clean temporary files 30 * * * * rm -rf /tmp/* diff --git a/tfjm/helloasso.py b/tfjm/helloasso.py index 48968e4..1a08e50 100644 --- a/tfjm/helloasso.py +++ b/tfjm/helloasso.py @@ -46,7 +46,7 @@ def get_hello_asso_access_token(): return _access_token if response.status_code == 400: - raise ValueError(str(response.json()['errors'])) + raise ValueError(str(response.json())) response.raise_for_status() data = response.json()