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 @@
- {% 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 }}
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()