Send payment confirmation mail after payment, and send weekly reminders for people that have not paid

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-02-23 18:02:24 +01:00
parent 6a928ee35b
commit cae1c6fdb8
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
11 changed files with 92 additions and 106 deletions

View File

@ -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")

View File

@ -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()

View File

@ -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()

View File

@ -4,11 +4,12 @@
from datetime import date, datetime from datetime import date, datetime
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.mail import send_mail
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.template import loader from django.template import loader
from django.urls import reverse_lazy, reverse 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.crypto import get_random_string
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode from django.utils.http import urlsafe_base64_encode
@ -634,6 +635,37 @@ class Payment(models.Model):
return checkout_intent 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): def get_absolute_url(self):
return reverse_lazy("registration:update_payment", args=(self.pk,)) return reverse_lazy("registration:update_payment", args=(self.pk,))

View File

@ -9,7 +9,7 @@
<body> <body>
<p> <p>
{% trans "Hi" %} {{ user.registration }}, {% trans "Hi" %} {{ registration }},
</p> </p>
<p> <p>
@ -28,7 +28,7 @@
<ul> <ul>
<li>{% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }}</li> <li>{% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }}</li>
<li>{% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }}</li> <li>{% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }}</li>
<li>{% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.start|date }} {% trans "to" %} {{ payment.tournament.end|date }}</li> <li>{% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.date_start|date }} {% trans "to" %} {{ payment.tournament.date_end|date }}</li>
</ul> </ul>
</p> </p>

View File

@ -1,6 +1,5 @@
{% load i18n %} {% load i18n %}
{% trans "Hi" %} {{ registration|safe }},
{% trans "Hi" %} {{ user.registration }},
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament.name %} {% 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 }}! 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 "As a reminder, here are the following important dates:" %}
* {% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }} * {% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }}
* {% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|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." %} {% trans "Please note that these dates may be subject to change. If your local organizers gave you different dates, trust them." %}

View File

@ -9,7 +9,7 @@
<body> <body>
<p> <p>
{% trans "Hi" %} {{ user.registration }}, {% trans "Hi" %} {{ registration }},
</p> </p>
<p> <p>
@ -19,19 +19,19 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<p>
{% if payment.grouped %} {% if payment.grouped %}
<p>
{% trans "This price includes the registrations of all members of your team." %} {% trans "This price includes the registrations of all members of your team." %}
{% endif %}
</p> </p>
{% endif %}
<p> <p>
{% trans "You can pay by credit card or by bank transfer. You can read full instructions on the payment page:" %} {% trans "You can pay by credit card or by bank transfer. You can read full instructions on the payment page:" %}
</p> </p>
<p> <p>
<a href="https://{{ domain }}{% url "registration.update_payment" pk=payment.pk %}"> <a href="https://{{ domain }}{% url "registration:update_payment" pk=payment.pk %}">
https://{{ domain }}{% url "registration.update_payment" pk=payment.pk %} https://{{ domain }}{% url "registration:update_payment" pk=payment.pk %}
</a> </a>
</p> </p>

View File

@ -1,19 +1,16 @@
{% load i18n %} {% load i18n %}
{% trans "Hi" %} {{ registration|safe }},
{% trans "Hi" %} {{ user.registration }},
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament %} {% 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. 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 }} €. To end your inscription, you must pay the amount of {{ amount }} €.
{% endblocktrans %} {% endblocktrans %}
{% if payment.grouped %} {% if payment.grouped %}
{% trans "This price includes the registrations of all members of your team." %} {% 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:" %} {% 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." %} {% 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." %}

View File

@ -623,6 +623,7 @@ class PaymentHelloAssoReturnView(DetailView):
payment.save() payment.save()
messages.success(request, _("The payment has been successfully validated! " messages.success(request, _("The payment has been successfully validated! "
"Your registration is now complete.")) "Your registration is now complete."))
payment.send_helloasso_payment_confirmation_mail()
else: else:
payment.type = "helloasso" payment.type = "helloasso"
payment.valid = None payment.valid = None

View File

@ -9,6 +9,8 @@
# Check payments from Hello Asso # Check payments from Hello Asso
*/6 * * * * cd /code && python manage.py check_hello_asso &> /dev/null */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 # Clean temporary files
30 * * * * rm -rf /tmp/* 30 * * * * rm -rf /tmp/*

View File

@ -46,7 +46,7 @@ def get_hello_asso_access_token():
return _access_token return _access_token
if response.status_code == 400: if response.status_code == 400:
raise ValueError(str(response.json()['errors'])) raise ValueError(str(response.json()))
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()