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:
parent
6a928ee35b
commit
cae1c6fdb8
|
@ -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")
|
|
|
@ -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()
|
|
@ -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()
|
|
@ -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,))
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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." %}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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." %}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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/*
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue