Compare commits
No commits in common. "de22a12e85424bc0b4ea45530d142a68c934a754" and "21552756270d1ff4ad82af0b7f1dcad5c2fb739f" have entirely different histories.
de22a12e85
...
2155275627
15
.gitignore
vendored
@ -15,6 +15,16 @@ coverage
|
|||||||
*.mo
|
*.mo
|
||||||
*.pot
|
*.pot
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
# PyCharm project settings
|
# PyCharm project settings
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
@ -22,7 +32,7 @@ coverage
|
|||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
# Local data
|
# Local data
|
||||||
settings_local.py
|
secrets.py
|
||||||
*.log
|
*.log
|
||||||
media/
|
media/
|
||||||
output/
|
output/
|
||||||
@ -32,3 +42,6 @@ output/
|
|||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
|
||||||
|
# Don't git index
|
||||||
|
whoosh_index/
|
||||||
|
@ -7,7 +7,7 @@ py311:
|
|||||||
image: python:3.11-alpine
|
image: python:3.11-alpine
|
||||||
before_script:
|
before_script:
|
||||||
- apk add --no-cache libmagic
|
- apk add --no-cache libmagic
|
||||||
- apk add --no-cache gettext git # Useful for django-haystack, remove when the newer versions are in PyPI
|
- apk add --no-cache git # Useful for django-haystack, remove when the newer versions are in PyPI
|
||||||
- pip install tox --no-cache-dir
|
- pip install tox --no-cache-dir
|
||||||
script: tox -e py311
|
script: tox -e py311
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ py312:
|
|||||||
image: python:3.12-alpine
|
image: python:3.12-alpine
|
||||||
before_script:
|
before_script:
|
||||||
- apk add --no-cache libmagic
|
- apk add --no-cache libmagic
|
||||||
- apk add --no-cache gettext git # Useful for django-haystack, remove when the newer versions are in PyPI
|
- apk add --no-cache git # Useful for django-haystack, remove when the newer versions are in PyPI
|
||||||
- pip install tox --no-cache-dir
|
- pip install tox --no-cache-dir
|
||||||
script: tox -e py312
|
script: tox -e py312
|
||||||
|
|
||||||
|
BIN
docs/_static/img/payment_bank_transfer.png
vendored
Before Width: | Height: | Size: 118 KiB |
BIN
docs/_static/img/payment_grouped.png
vendored
Before Width: | Height: | Size: 115 KiB |
BIN
docs/_static/img/payment_hello_asso_confirmation.png
vendored
Before Width: | Height: | Size: 107 KiB |
BIN
docs/_static/img/payment_hello_asso_step_1.png
vendored
Before Width: | Height: | Size: 101 KiB |
BIN
docs/_static/img/payment_hello_asso_step_2.png
vendored
Before Width: | Height: | Size: 87 KiB |
BIN
docs/_static/img/payment_index.png
vendored
Before Width: | Height: | Size: 120 KiB |
BIN
docs/_static/img/payment_scholarship.png
vendored
Before Width: | Height: | Size: 94 KiB |
@ -29,7 +29,6 @@ author = "Animath"
|
|||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
"sphinx_rtd_theme",
|
"sphinx_rtd_theme",
|
||||||
"sphinx_rtd_dark_mode",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
@ -59,5 +58,3 @@ html_theme = 'sphinx_rtd_theme'
|
|||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
default_dark_mode = True
|
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
sphinx>=3.3
|
sphinx>=3.3
|
||||||
sphinx-rtd-theme>=2.0
|
sphinx-rtd-theme>=0.5
|
||||||
sphinx_rtd_dark_mode>=1.3.0
|
|
||||||
|
117
docs/user.rst
@ -179,125 +179,16 @@ Payer son inscription
|
|||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Une fois votre inscription validée, il vous faudra payer votre inscription. Les frais s'élèvent à
|
Une fois votre inscription validée, il vous faudra payer votre inscription. Les frais s'élèvent à
|
||||||
21 € par élève, sauf pour les élèves boursièr⋅es qui en sont exonéré⋅es. Les encadrant⋅es n'ont pas
|
23 € par élève, sauf pour les élèves boursièr⋅es qui en sont exonéré⋅es. Les encadrant⋅es n'ont pas
|
||||||
à payer. Pour la finale, les frais sont de 35 € par élève.
|
à payer.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Ces frais couvrent une partie des frais de restauration et d'hébergement. L'organisation reste
|
Ces frais couvrent une partie des frais de restauration et d'hébergement. L'organisation reste
|
||||||
bénévole.
|
bénévole.
|
||||||
|
|
||||||
Il est possible de payer par carte bancaire ou virement bancaire. Pour d'autres types de paiement,
|
.. TODO
|
||||||
merci de nous contacter.
|
|
||||||
|
|
||||||
Pour payer, si votre équipe est bien validée, vous pouvez vous rendre sur la page de votre compte
|
|
||||||
ou celle de votre équipe, et cliquer sur le bouton « Modifier le paiement », qui devrais désormais
|
|
||||||
apparaître. Vous pouvez également utiliser le lien présent dans le volet « Informations ».
|
|
||||||
|
|
||||||
.. image:: /_static/img/payment_index.png
|
|
||||||
:alt: Page de paiement
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
Cette section sera mise à jour plus tard.
|
||||||
Vous recevrez un mail de rappel chaque semaine. Le paiement doit être effectué avant le début du
|
|
||||||
tournoi, sans quoi votre participation pourrait être refusée. En cas de difficultés de paiement,
|
|
||||||
merci de nous contacter.
|
|
||||||
|
|
||||||
Carte bancaire
|
|
||||||
""""""""""""""
|
|
||||||
|
|
||||||
La façon la plus simple de payer son inscription est de payer par carte bancaire. Animath utilise
|
|
||||||
`Hello Asso <https://helloasso.com/>`_ en guise de solution de paiements en ligne.
|
|
||||||
|
|
||||||
Il vous suffit de cliquer sur le bouton « Aller à la page Hello Asso ». Vous serez redirigé⋅e ensuite
|
|
||||||
vers la page de paiement.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Pour procéder au paiement, si vous êtes mineur⋅e, vous devrez demander à un⋅e adulte de payer à
|
|
||||||
votre place. Il est important dans la suite de bien mettre les coordonnées du payeur ou de la payeuse,
|
|
||||||
majeur⋅e, et non celles de l'élève.
|
|
||||||
|
|
||||||
.. image:: /_static/img/payment_hello_asso_step_1.png
|
|
||||||
:alt: Formulaire de paiement Hello Asso
|
|
||||||
|
|
||||||
La personne qui paie peut rentrer ses informations demandées (nom, prénom, e-mail, date de naissance).
|
|
||||||
|
|
||||||
Notez que, par défaut, Hello Asso ajoute automatiquement une participation à ses frais de fonctionnement,
|
|
||||||
d'environ 15 à 20 % du prix payé. Ces frais ne sont pas obligatoires, ne sont pas versés à Animath et
|
|
||||||
représentent la seule source de revenus à Hello Asso. En effet : Animath ne verse aucune commission lors
|
|
||||||
de ses transactions, et seules les contributions volontaires financent leur service.
|
|
||||||
|
|
||||||
Sur la page suivante, vous pouvez indiquer vos coordonnées bancaires :
|
|
||||||
|
|
||||||
.. image:: /_static/img/payment_hello_asso_step_2.png
|
|
||||||
:alt: Formulaire de paiement Hello Asso - coordonnées bancaires
|
|
||||||
|
|
||||||
Vous devez ensuite éventuellement confirmer votre paiement auprès de votre banque.
|
|
||||||
|
|
||||||
Une fois ceci fait, vous êtes automatiquement redirigé⋅es vers la plateforme du TFJM² :
|
|
||||||
|
|
||||||
.. image:: /_static/img/payment_hello_asso_confirmation.png
|
|
||||||
:alt: Confirmation de paiement Hello Asso
|
|
||||||
|
|
||||||
Il se peut que la validation ne soit pas instantanée. Elle peut prendre au plus quelques minutes.
|
|
||||||
Si le délai est plus long, merci de nous contacter.
|
|
||||||
|
|
||||||
Vous recevrez ensuite un mail de confirmation de la plateforme, ainsi qu'un justificatif de paiement
|
|
||||||
de la part de Hello Asso.
|
|
||||||
|
|
||||||
|
|
||||||
Carte bancaire - paiement par un tiers
|
|
||||||
""""""""""""""""""""""""""""""""""""""
|
|
||||||
|
|
||||||
Il est possible, si nécessaire, de faire payer l'inscription par carte bancaire par un tiers. Pour cela,
|
|
||||||
vous pouvez lui transmettre le lien de paiement qui apparaît au centre de l'écran. Cela est notamment
|
|
||||||
utile pour faire payer l'inscription par un établissement scolaire, ou par des parents.
|
|
||||||
|
|
||||||
L'interface de paiement sera ensuite identique.
|
|
||||||
|
|
||||||
|
|
||||||
Virement bancaire
|
|
||||||
"""""""""""""""""
|
|
||||||
|
|
||||||
Il est possible de payer par virement bancaire. Pour cela, vous pouvez ouvrir l'onglet virement bancaire :
|
|
||||||
|
|
||||||
.. image:: /_static/img/payment_bank_transfer.png
|
|
||||||
:alt: Formulaire de paiement par virement bancaire
|
|
||||||
|
|
||||||
Pour effectuer le virement, merci de mettre en référence du virement « TFJMpu » suivi du nom et du prénom de l'élève.
|
|
||||||
|
|
||||||
Les coordonnées bancaires sont :
|
|
||||||
|
|
||||||
* IBAN : FR76 1027 8065 0000 0206 4290 127
|
|
||||||
* BIC : CMCIFR2A
|
|
||||||
|
|
||||||
Une fois le paiment effectué, vous pouvez envoyer une preuve de virement via le formulaire ci-dessus. Le paiement
|
|
||||||
sera ensuite validé manuellement par les organisateur⋅rices après réception.
|
|
||||||
|
|
||||||
Si vous avez besoin d'une facture, merci de nous contacter.
|
|
||||||
|
|
||||||
|
|
||||||
Exonération - boursièr⋅es
|
|
||||||
"""""""""""""""""""""""""
|
|
||||||
|
|
||||||
Si vous bénéficiez d'une bourse, vous pouvez être exonéré⋅es des frais d'inscription. Pour cela, il vous suffit
|
|
||||||
de nous envoyer une copie de votre notification de bourse, ou tout autre document justifiant de votre situation.
|
|
||||||
Vous pouvez envoyer ce document en vous rendant sur l'onglet dédié :
|
|
||||||
|
|
||||||
.. image:: /_static/img/payment_scholarship.png
|
|
||||||
:alt: Formulaire de soumission de notification de bourse
|
|
||||||
|
|
||||||
|
|
||||||
Paiements groupés
|
|
||||||
"""""""""""""""""
|
|
||||||
|
|
||||||
Il est possible de payer en une seule fois pour toute l'équipe. Cela est notamment utile si l'inscription est
|
|
||||||
payée par l'établissement. Pour cela, il suffit de cliquer sur le bouton « Regrouper les paiements de mon équipe ».
|
|
||||||
Cela a pour effet d'unifier les paiements de l'équipe, et de ne pas demander à chaque membre de payer individuellement.
|
|
||||||
Attention : cette fonction n'est possible que si aucun membre de l'équipe n'a encore payé son inscription.
|
|
||||||
|
|
||||||
.. image:: /_static/img/payment_grouped.png
|
|
||||||
:alt: Page de paiement groupé
|
|
||||||
|
|
||||||
|
|
||||||
Envoyer ses solutions
|
Envoyer ses solutions
|
||||||
|
@ -12,9 +12,8 @@ class ParticipationConfig(AppConfig):
|
|||||||
name = 'participation'
|
name = 'participation'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from participation import signals
|
from participation.signals import create_notes, create_team_participation, update_mailing_list
|
||||||
pre_save.connect(signals.update_mailing_list, "participation.Team")
|
pre_save.connect(update_mailing_list, "participation.Team")
|
||||||
post_save.connect(signals.create_team_participation, "participation.Team")
|
post_save.connect(create_team_participation, "participation.Team")
|
||||||
post_save.connect(signals.create_payments, "participation.Participation")
|
post_save.connect(create_notes, "participation.Passage")
|
||||||
post_save.connect(signals.create_notes, "participation.Passage")
|
post_save.connect(create_notes, "participation.Pool")
|
||||||
post_save.connect(signals.create_notes, "participation.Pool")
|
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from participation.models import Note, Participation, Passage, Pool, Team, Tournament
|
from participation.models import Note, Participation, Passage, Pool, Team
|
||||||
from registration.models import Payment
|
|
||||||
from tfjm.lists import get_sympa_client
|
from tfjm.lists import get_sympa_client
|
||||||
|
|
||||||
|
|
||||||
@ -37,41 +36,6 @@ def update_mailing_list(instance: Team, raw, **_):
|
|||||||
f"{coach.user.first_name} {coach.user.last_name}")
|
f"{coach.user.first_name} {coach.user.last_name}")
|
||||||
|
|
||||||
|
|
||||||
def create_payments(instance: Participation, created, raw, **_):
|
|
||||||
"""
|
|
||||||
When a participation got created, create an associated payment.
|
|
||||||
"""
|
|
||||||
if instance.valid and not raw:
|
|
||||||
for student in instance.team.students.all():
|
|
||||||
payment_qs = Payment.objects.filter(registrations=student, final=False)
|
|
||||||
if payment_qs.exists():
|
|
||||||
payment = payment_qs.get()
|
|
||||||
else:
|
|
||||||
payment = Payment.objects.create()
|
|
||||||
payment.registrations.add(student)
|
|
||||||
payment.save()
|
|
||||||
payment.amount = instance.tournament.price
|
|
||||||
if payment.amount == 0:
|
|
||||||
payment.type = "free"
|
|
||||||
payment.valid = True
|
|
||||||
payment.save()
|
|
||||||
|
|
||||||
if instance.final:
|
|
||||||
for student in instance.team.students.all():
|
|
||||||
payment_qs = Payment.objects.filter(registrations=student, final=True)
|
|
||||||
if payment_qs.exists():
|
|
||||||
payment = payment_qs.get()
|
|
||||||
else:
|
|
||||||
payment = Payment.objects.create(final=True)
|
|
||||||
payment.registrations.add(student)
|
|
||||||
payment.save()
|
|
||||||
payment.amount = Tournament.final_tournament().price
|
|
||||||
if payment.amount == 0:
|
|
||||||
payment.type = "free"
|
|
||||||
payment.valid = True
|
|
||||||
payment.save()
|
|
||||||
|
|
||||||
|
|
||||||
def create_notes(instance: Union[Passage, Pool], raw, **_):
|
def create_notes(instance: Union[Passage, Pool], raw, **_):
|
||||||
if not raw:
|
if not raw:
|
||||||
if isinstance(instance, Pool):
|
if isinstance(instance, Pool):
|
||||||
|
@ -5,42 +5,16 @@
|
|||||||
<title>Équipe validée – TFJM²</title>
|
<title>Équipe validée – TFJM²</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>
|
Bonjour,<br/>
|
||||||
Bonjour {{ registration }},
|
<br/>
|
||||||
</p>
|
Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais apte
|
||||||
<p>
|
à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.<br>
|
||||||
Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais
|
Les organisateurs vous adressent ce message :<br/>
|
||||||
apte à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
|
<br/>
|
||||||
</p>
|
{{ message }}<br />
|
||||||
|
<br/>
|
||||||
{% if payment %}
|
Cordialement,<br/>
|
||||||
<p>
|
<br/>
|
||||||
Vous devez désormais vous acquitter de vos frais d'inscription, de {{ payment.amount }} € par élève.
|
Le comité d'organisation du TFJM²
|
||||||
Vous pouvez payer par carte bancaire ou par virement bancaire. Vous trouverez les informations
|
|
||||||
sur <a href="https://{{ domain }}{% url 'registration:update_payment' pk=payment.pk %}">la page de paiement</a>.
|
|
||||||
Si vous disposez d'une bourse, l'inscription est gratuite, mais vous devez soumettre un justificatif
|
|
||||||
sur la même page.
|
|
||||||
</p>
|
|
||||||
{% elif registration.is_coach and team.participation.tournament.amount %}
|
|
||||||
<p>
|
|
||||||
Votre équipe doit désormais s'acquitter des frais d'inscription de {{ team.participation.tournament.amount }} €
|
|
||||||
par élève (les encadrant⋅es sont exonéré⋅es). Les élèves qui disposent d'une bourse sont exonéré⋅es de ces frais.
|
|
||||||
Vous pouvez suivre l'état des paiements sur
|
|
||||||
<a href="https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}">la page de votre équipe</a>.
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if message %}
|
|
||||||
<p>
|
|
||||||
Les organisateur⋅ices vous adressent ce message :
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ message }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Le comité d'organisation du TFJM²
|
|
||||||
</p>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,23 +1,12 @@
|
|||||||
Bonjour {{ registration }},
|
Bonjour,
|
||||||
|
|
||||||
Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais apte
|
Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais apte
|
||||||
à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
|
à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
|
||||||
{% if team.participation.amount %}
|
|
||||||
Vous devez désormais vous acquitter de vos frais d'inscription, de {{ team.participation.amount }} €.
|
Les organisateurs vous adressent ce message :
|
||||||
Vous pouvez payer par carte bancaire ou par virement bancaire. Vous trouverez les informations
|
|
||||||
sur la page de paiement que vous pouvez retrouver sur votre compte :
|
|
||||||
https://{{ domain }}{% url 'registration:my_account_detail' %}
|
|
||||||
Si vous disposez d'une bourse, l'inscription est gratuite, mais vous devez soumettre un justificatif
|
|
||||||
sur la même page.
|
|
||||||
{% elif registration.is_coach and team.participation.tournament.amount %}
|
|
||||||
Votre équipe doit désormais s'acquitter des frais d'inscription de {{ team.participation.tournament.amount }} €
|
|
||||||
par élève (les encadrant⋅es sont exonéré⋅es). Les élèves qui disposent d'une bourse sont exonéré⋅es de ces frais.
|
|
||||||
Vous pouvez suivre l'état des paiements sur la page de votre équipe :
|
|
||||||
https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}
|
|
||||||
{% endif %}
|
|
||||||
{% if message %}
|
|
||||||
Les organisateurices vous adressent ce message :
|
|
||||||
|
|
||||||
{{ message }}
|
{{ message }}
|
||||||
{% endif %}
|
|
||||||
|
Cordialement,
|
||||||
|
|
||||||
Le comité d'organisation du TFJM²
|
Le comité d'organisation du TFJM²
|
||||||
|
@ -4,12 +4,10 @@
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core import mail
|
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.test import LiveServerTestCase, override_settings, TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from registration.models import CoachRegistration, Payment, StudentRegistration
|
from registration.models import CoachRegistration, StudentRegistration
|
||||||
|
|
||||||
from .models import Participation, Team, Tournament
|
from .models import Participation, Team, Tournament
|
||||||
|
|
||||||
@ -517,616 +515,6 @@ class TestStudentParticipation(TestCase):
|
|||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
|
|
||||||
class TestPayment(TestCase):
|
|
||||||
"""
|
|
||||||
Tests that are relative to a payment
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.superuser = User.objects.create_superuser(
|
|
||||||
username="admin",
|
|
||||||
email="admin@example.com",
|
|
||||||
password="admin",
|
|
||||||
)
|
|
||||||
self.tournament = Tournament.objects.create(
|
|
||||||
name="France",
|
|
||||||
place="Here",
|
|
||||||
price=21,
|
|
||||||
)
|
|
||||||
self.team = Team.objects.create(
|
|
||||||
name="Super team",
|
|
||||||
trigram="AAA",
|
|
||||||
access_code="azerty",
|
|
||||||
)
|
|
||||||
self.user = User.objects.create(
|
|
||||||
first_name="Toto",
|
|
||||||
last_name="Toto",
|
|
||||||
email="toto@example.com",
|
|
||||||
password="toto",
|
|
||||||
)
|
|
||||||
StudentRegistration.objects.create(
|
|
||||||
user=self.user,
|
|
||||||
team=self.team,
|
|
||||||
student_class=12,
|
|
||||||
address="1 Rue de Rivoli",
|
|
||||||
zip_code=75001,
|
|
||||||
city="Paris",
|
|
||||||
school="Earth",
|
|
||||||
give_contact_to_animath=True,
|
|
||||||
email_confirmed=True,
|
|
||||||
)
|
|
||||||
self.second_user = User.objects.create(
|
|
||||||
first_name="Lalala",
|
|
||||||
last_name="Lalala",
|
|
||||||
email="lalala@example.com",
|
|
||||||
password="lalala",
|
|
||||||
)
|
|
||||||
StudentRegistration.objects.create(
|
|
||||||
user=self.second_user,
|
|
||||||
team=self.team,
|
|
||||||
student_class=11,
|
|
||||||
address="1 Rue de Rivoli",
|
|
||||||
zip_code=75001,
|
|
||||||
city="Paris",
|
|
||||||
school="Moon",
|
|
||||||
give_contact_to_animath=True,
|
|
||||||
email_confirmed=True,
|
|
||||||
)
|
|
||||||
self.coach = User.objects.create(
|
|
||||||
first_name="Coach",
|
|
||||||
last_name="Coach",
|
|
||||||
email="coach@example.com",
|
|
||||||
password="coach",
|
|
||||||
)
|
|
||||||
CoachRegistration.objects.create(
|
|
||||||
user=self.coach,
|
|
||||||
team=self.team,
|
|
||||||
address="1 Rue de Rivoli",
|
|
||||||
zip_code=75001,
|
|
||||||
city="Paris",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.team.participation.tournament = self.tournament
|
|
||||||
self.team.participation.valid = True
|
|
||||||
self.team.participation.save()
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
|
|
||||||
def test_check_payments_exists(self):
|
|
||||||
"""
|
|
||||||
Check that users in a validated team have an invalid payment, but not for the final,
|
|
||||||
and that coaches are not concerned.
|
|
||||||
"""
|
|
||||||
self.assertTrue(Payment.objects.filter(final=False, valid=False, type='',
|
|
||||||
registrations=self.user.registration).exists())
|
|
||||||
self.assertTrue(Payment.objects.filter(final=False, valid=False, type='',
|
|
||||||
registrations=self.second_user.registration).exists())
|
|
||||||
self.assertFalse(Payment.objects.filter(final=False, valid=False, type='',
|
|
||||||
registrations=self.coach.registration).exists())
|
|
||||||
|
|
||||||
self.assertFalse(Payment.objects.filter(final=True, valid=False, type='',
|
|
||||||
registrations=self.user.registration).exists())
|
|
||||||
self.assertFalse(Payment.objects.filter(final=True, valid=False, type='',
|
|
||||||
registrations=self.second_user.registration).exists())
|
|
||||||
self.assertFalse(Payment.objects.filter(final=True, valid=False, type='',
|
|
||||||
registrations=self.coach.registration).exists())
|
|
||||||
|
|
||||||
def test_load_payment_page(self):
|
|
||||||
"""
|
|
||||||
Ensure that the payment page loads correctly.
|
|
||||||
"""
|
|
||||||
response = self.client.get(reverse('participation:team_detail', args=(self.team.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:user_detail', args=(self.user.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('participation:tournament_payments', args=(self.tournament.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_bank_transfer_payment(self):
|
|
||||||
"""
|
|
||||||
Try to send a bank transfer.
|
|
||||||
"""
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "bank_transfer",
|
|
||||||
'additional_information': "This is a bank transfer"})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFormError(response.context['form'], 'receipt',
|
|
||||||
["This field is required.", "You must upload your receipt."])
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "")
|
|
||||||
|
|
||||||
# README is not a valid PDF file
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "bank_transfer",
|
|
||||||
'additional_information': "This is a bank transfer",
|
|
||||||
'receipt': open("README.md", "rb")})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFormError(response.context['form'], 'receipt',
|
|
||||||
["The uploaded file must be a PDF, PNG of JPEG file."])
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "")
|
|
||||||
|
|
||||||
# Don't send too large files
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "bank_transfer",
|
|
||||||
'additional_information': "This is a bank transfer",
|
|
||||||
'receipt': SimpleUploadedFile(
|
|
||||||
"file.pdf",
|
|
||||||
content=int(0).to_bytes(2000001, "big"),
|
|
||||||
content_type="application/pdf"),
|
|
||||||
})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFormError(response.context['form'], 'receipt',
|
|
||||||
["The uploaded file size must be under 2 Mo."])
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "")
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "bank_transfer",
|
|
||||||
'additional_information': "This is a bank transfer",
|
|
||||||
'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")})
|
|
||||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertIsNone(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "bank_transfer")
|
|
||||||
self.assertEqual(payment.additional_information, "This is a bank transfer")
|
|
||||||
self.assertIsNotNone(payment.receipt)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_scholarship(self):
|
|
||||||
"""
|
|
||||||
Try to don't pay because of a scholarship.
|
|
||||||
"""
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "scholarship",
|
|
||||||
'additional_information': "I don't have to pay because I have a scholarship"})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFormError(response.context['form'], 'receipt',
|
|
||||||
["This field is required.", "You must upload your receipt."])
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "")
|
|
||||||
self.assertEqual(payment.amount, self.tournament.price)
|
|
||||||
|
|
||||||
# README is not a valid PDF file
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "scholarship",
|
|
||||||
'additional_information': "I don't have to pay because I have a scholarship",
|
|
||||||
'receipt': open("README.md", "rb")})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFormError(response.context['form'], 'receipt',
|
|
||||||
["The uploaded file must be a PDF, PNG of JPEG file."])
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "")
|
|
||||||
self.assertEqual(payment.amount, self.tournament.price)
|
|
||||||
|
|
||||||
# Don't send too large files
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "scholarship",
|
|
||||||
'additional_information': "I don't have to pay because I have a scholarship",
|
|
||||||
'receipt': SimpleUploadedFile(
|
|
||||||
"file.pdf",
|
|
||||||
content=int(0).to_bytes(2000001, "big"),
|
|
||||||
content_type="application/pdf"),
|
|
||||||
})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFormError(response.context['form'], 'receipt',
|
|
||||||
["The uploaded file size must be under 2 Mo."])
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "")
|
|
||||||
self.assertEqual(payment.amount, self.tournament.price)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "scholarship",
|
|
||||||
'additional_information': "I don't have to pay because I have a scholarship",
|
|
||||||
'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")})
|
|
||||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertIsNone(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "scholarship")
|
|
||||||
self.assertEqual(payment.additional_information, "I don't have to pay because I have a scholarship")
|
|
||||||
self.assertIsNotNone(payment.receipt)
|
|
||||||
self.assertEqual(payment.amount, 0)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_other(self):
|
|
||||||
"""
|
|
||||||
Try to send a different type of payment.
|
|
||||||
"""
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "other"})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFormError(response.context['form'], 'additional_information',
|
|
||||||
["This field is required."])
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "")
|
|
||||||
self.assertEqual(payment.amount, self.tournament.price)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'type': "other",
|
|
||||||
'additional_information': "Why should I pay"})
|
|
||||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertIsNone(payment.valid)
|
|
||||||
self.assertEqual(payment.type, "other")
|
|
||||||
self.assertEqual(payment.additional_information, "Why should I pay")
|
|
||||||
self.assertIsNotNone(payment.receipt)
|
|
||||||
self.assertEqual(payment.amount, self.tournament.price)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_group(self):
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
self.assertFalse(payment.grouped)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment_group_mode', args=(payment.pk,)))
|
|
||||||
self.assertRedirects(response, reverse('registration:update_payment', args=(payment.pk,)), 302, 200)
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertTrue(payment.grouped)
|
|
||||||
self.assertEqual(Payment.objects.count(), 1)
|
|
||||||
self.assertIn(self.user.registration, payment.registrations.all())
|
|
||||||
self.assertIn(self.second_user.registration, payment.registrations.all())
|
|
||||||
self.assertEqual(payment.amount, 2 * self.tournament.price)
|
|
||||||
|
|
||||||
def test_ungroup(self):
|
|
||||||
"""
|
|
||||||
Test to ungroup payments
|
|
||||||
"""
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
self.client.get(reverse('registration:update_payment_group_mode', args=(payment.pk,)))
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertTrue(payment.grouped)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment_group_mode', args=(payment.pk,)))
|
|
||||||
self.assertRedirects(response, reverse('registration:update_payment', args=(payment.pk,)), 302, 200)
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertFalse(payment.grouped)
|
|
||||||
self.assertEqual(Payment.objects.count(), 2)
|
|
||||||
self.assertIn(self.user.registration, payment.registrations.all())
|
|
||||||
self.assertNotIn(self.second_user.registration, payment.registrations.all())
|
|
||||||
self.assertEqual(payment.amount, self.tournament.price)
|
|
||||||
|
|
||||||
def test_group_forbidden(self):
|
|
||||||
"""
|
|
||||||
Payment grouping is forbidden if at least one payment is already valid.
|
|
||||||
"""
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
payment.valid = True
|
|
||||||
payment.save()
|
|
||||||
payment2 = Payment.objects.get(registrations=self.second_user.registration, final=False)
|
|
||||||
response = self.client.get(reverse('registration:update_payment_group_mode', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment_group_mode', args=(payment2.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
def test_validate_payment(self):
|
|
||||||
"""
|
|
||||||
Try to validate a payment.
|
|
||||||
"""
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
payment.type = "other"
|
|
||||||
payment.valid = None
|
|
||||||
payment.save()
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'valid': True})
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
self.client.force_login(self.superuser)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'valid': True})
|
|
||||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertTrue(payment.valid)
|
|
||||||
|
|
||||||
def test_invalidate_payment(self):
|
|
||||||
"""
|
|
||||||
Try to invalidate a payment.
|
|
||||||
"""
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
payment.type = "other"
|
|
||||||
payment.valid = None
|
|
||||||
payment.save()
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
self.client.force_login(self.superuser)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
|
||||||
data={'valid': False})
|
|
||||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
def test_payment_reminder(self):
|
|
||||||
"""
|
|
||||||
Check that the payment reminder command works correctly.
|
|
||||||
"""
|
|
||||||
self.assertEqual(len(mail.outbox), 0)
|
|
||||||
|
|
||||||
call_command('remind_payments')
|
|
||||||
self.assertEqual(len(mail.outbox), 2)
|
|
||||||
self.assertEqual(mail.outbox[0].subject, "[TFJM²] Rappel pour votre paiement")
|
|
||||||
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
payment2 = Payment.objects.get(registrations=self.second_user.registration, final=False)
|
|
||||||
payment.type = 'other'
|
|
||||||
payment.valid = True
|
|
||||||
payment.save()
|
|
||||||
payment2.type = 'bank_transfer'
|
|
||||||
payment2.valid = None
|
|
||||||
payment2.save()
|
|
||||||
|
|
||||||
mail.outbox = []
|
|
||||||
call_command('remind_payments')
|
|
||||||
self.assertEqual(len(mail.outbox), 0)
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(HELLOASSO_TEST_ENDPOINT=True, ROOT_URLCONF="tfjm.helloasso.test_urls")
|
|
||||||
class TestHelloAssoPayment(LiveServerTestCase):
|
|
||||||
"""
|
|
||||||
Tests that are relative to a HelloAsso
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.superuser = User.objects.create_superuser(
|
|
||||||
username="admin",
|
|
||||||
email="admin@example.com",
|
|
||||||
password="admin",
|
|
||||||
)
|
|
||||||
self.tournament = Tournament.objects.create(
|
|
||||||
name="France",
|
|
||||||
place="Here",
|
|
||||||
price=21,
|
|
||||||
)
|
|
||||||
self.team = Team.objects.create(
|
|
||||||
name="Super team",
|
|
||||||
trigram="AAA",
|
|
||||||
access_code="azerty",
|
|
||||||
)
|
|
||||||
self.user = User.objects.create(
|
|
||||||
first_name="Toto",
|
|
||||||
last_name="Toto",
|
|
||||||
email="toto@example.com",
|
|
||||||
password="toto",
|
|
||||||
)
|
|
||||||
StudentRegistration.objects.create(
|
|
||||||
user=self.user,
|
|
||||||
team=self.team,
|
|
||||||
student_class=12,
|
|
||||||
address="1 Rue de Rivoli",
|
|
||||||
zip_code=75001,
|
|
||||||
city="Paris",
|
|
||||||
school="Earth",
|
|
||||||
give_contact_to_animath=True,
|
|
||||||
email_confirmed=True,
|
|
||||||
)
|
|
||||||
self.coach = User.objects.create(
|
|
||||||
first_name="Coach",
|
|
||||||
last_name="Coach",
|
|
||||||
email="coach@example.com",
|
|
||||||
password="coach",
|
|
||||||
)
|
|
||||||
CoachRegistration.objects.create(
|
|
||||||
user=self.coach,
|
|
||||||
team=self.team,
|
|
||||||
address="1 Rue de Rivoli",
|
|
||||||
zip_code=75001,
|
|
||||||
city="Paris",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.team.participation.tournament = self.tournament
|
|
||||||
self.team.participation.valid = True
|
|
||||||
self.team.participation.save()
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
|
|
||||||
Site.objects.update(domain=self.live_server_url.replace("http://", ""))
|
|
||||||
|
|
||||||
def test_create_checkout_intent(self):
|
|
||||||
with self.settings(HELLOASSO_TEST_ENDPOINT_URL=self.live_server_url):
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
checkout_intent = payment.create_checkout_intent()
|
|
||||||
|
|
||||||
self.assertIsNotNone(checkout_intent)
|
|
||||||
self.assertEqual(checkout_intent['metadata'], {
|
|
||||||
'payment_id': payment.pk,
|
|
||||||
'users': [
|
|
||||||
{
|
|
||||||
'user_id': self.user.pk,
|
|
||||||
'first_name': self.user.first_name,
|
|
||||||
'last_name': self.user.last_name,
|
|
||||||
'email': self.user.email,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'final': False,
|
|
||||||
'tournament_id': self.tournament.pk,
|
|
||||||
})
|
|
||||||
self.assertNotIn('order', checkout_intent)
|
|
||||||
|
|
||||||
checkout_intent_fetched = payment.get_checkout_intent()
|
|
||||||
self.assertEqual(checkout_intent, checkout_intent_fetched)
|
|
||||||
|
|
||||||
# Don't create a new checkout intent if one already exists
|
|
||||||
checkout_intent_new = payment.create_checkout_intent()
|
|
||||||
self.assertEqual(checkout_intent, checkout_intent_new)
|
|
||||||
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertEqual(payment.checkout_intent_id, checkout_intent['id'])
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
def test_helloasso_payment_success(self):
|
|
||||||
"""
|
|
||||||
Simulates the redirection to Hello Asso and the return for a successful payment.
|
|
||||||
"""
|
|
||||||
with self.settings(HELLOASSO_TEST_ENDPOINT_URL=self.live_server_url):
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
self.assertIsNone(payment.checkout_intent_id)
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:payment_hello_asso', args=(payment.pk,)),
|
|
||||||
follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.redirect_chain[-1],
|
|
||||||
(reverse('participation:team_detail', args=(self.team.pk,)), 302))
|
|
||||||
self.assertIn("type=return", response.redirect_chain[1][0])
|
|
||||||
self.assertIn("code=succeeded", response.redirect_chain[1][0])
|
|
||||||
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertIsNotNone(payment.checkout_intent_id)
|
|
||||||
self.assertTrue(payment.valid)
|
|
||||||
|
|
||||||
checkout_intent = payment.get_checkout_intent()
|
|
||||||
self.assertIn('order', checkout_intent)
|
|
||||||
|
|
||||||
def test_helloasso_payment_refused(self):
|
|
||||||
"""
|
|
||||||
Simulates the redirection to Hello Asso and the return for a refused payment.
|
|
||||||
"""
|
|
||||||
with self.settings(HELLOASSO_TEST_ENDPOINT_URL=self.live_server_url):
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
checkout_intent = payment.create_checkout_intent()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.get(checkout_intent['redirectUrl'] + "?refused", follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.redirect_chain[-1],
|
|
||||||
(reverse('registration:update_payment', args=(payment.pk,)), 302))
|
|
||||||
self.assertIn("type=return", response.redirect_chain[0][0])
|
|
||||||
self.assertIn("code=refused", response.redirect_chain[0][0])
|
|
||||||
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
checkout_intent = payment.get_checkout_intent()
|
|
||||||
self.assertNotIn('order', checkout_intent)
|
|
||||||
|
|
||||||
def test_helloasso_payment_error(self):
|
|
||||||
"""
|
|
||||||
Simulates the redirection to Hello Asso and the return for an errored payment.
|
|
||||||
"""
|
|
||||||
with self.settings(HELLOASSO_TEST_ENDPOINT_URL=self.live_server_url):
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
checkout_intent = payment.create_checkout_intent()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:update_payment', args=(payment.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
response = self.client.get(checkout_intent['redirectUrl'] + "?error", follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.redirect_chain[-1],
|
|
||||||
(reverse('registration:update_payment', args=(payment.pk,)), 302))
|
|
||||||
self.assertIn("type=error", response.redirect_chain[0][0])
|
|
||||||
self.assertIn("error=", response.redirect_chain[0][0])
|
|
||||||
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
checkout_intent = payment.get_checkout_intent()
|
|
||||||
self.assertNotIn('order', checkout_intent)
|
|
||||||
|
|
||||||
def test_anonymous_payment(self):
|
|
||||||
"""
|
|
||||||
Test to make a successful payment from an anonymous user, authenticated by token.
|
|
||||||
"""
|
|
||||||
self.client.logout()
|
|
||||||
|
|
||||||
with self.settings(HELLOASSO_TEST_ENDPOINT_URL=self.live_server_url):
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
self.assertIsNone(payment.checkout_intent_id)
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
response = self.client.get(reverse('registration:payment_hello_asso', args=(payment.pk,)),
|
|
||||||
follow=True)
|
|
||||||
self.assertRedirects(response,
|
|
||||||
f"{reverse('login')}?next="
|
|
||||||
f"{reverse('registration:payment_hello_asso', args=(payment.pk,))}")
|
|
||||||
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('registration:payment_hello_asso', args=(payment.pk,)) + "?token=" + payment.token,
|
|
||||||
follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.redirect_chain[-1], (reverse('index'), 302))
|
|
||||||
self.assertIn("type=return", response.redirect_chain[1][0])
|
|
||||||
self.assertIn("code=succeeded", response.redirect_chain[1][0])
|
|
||||||
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertIsNotNone(payment.checkout_intent_id)
|
|
||||||
self.assertTrue(payment.valid)
|
|
||||||
|
|
||||||
checkout_intent = payment.get_checkout_intent()
|
|
||||||
self.assertIn('order', checkout_intent)
|
|
||||||
|
|
||||||
def test_hello_asso_payment_verification(self):
|
|
||||||
"""
|
|
||||||
Check that a payment that is pending verification can be verified.
|
|
||||||
"""
|
|
||||||
with self.settings(HELLOASSO_TEST_ENDPOINT_URL=self.live_server_url):
|
|
||||||
payment = Payment.objects.get(registrations=self.user.registration, final=False)
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
call_command('check_hello_asso')
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertFalse(payment.valid)
|
|
||||||
|
|
||||||
self.client.get(reverse('registration:payment_hello_asso', args=(payment.pk,)),
|
|
||||||
follow=True)
|
|
||||||
|
|
||||||
payment.refresh_from_db()
|
|
||||||
payment.valid = None
|
|
||||||
payment.additional_information = ""
|
|
||||||
payment.save()
|
|
||||||
self.assertIsNone(payment.valid)
|
|
||||||
|
|
||||||
call_command('check_hello_asso')
|
|
||||||
payment.refresh_from_db()
|
|
||||||
self.assertTrue(payment.valid)
|
|
||||||
self.assertTrue(payment.additional_information)
|
|
||||||
|
|
||||||
|
|
||||||
class TestAdmin(TestCase):
|
class TestAdmin(TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.user = User.objects.create_superuser(
|
self.user = User.objects.create_superuser(
|
||||||
|
@ -244,18 +244,25 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||||||
if "validate" in self.request.POST:
|
if "validate" in self.request.POST:
|
||||||
self.object.participation.valid = True
|
self.object.participation.valid = True
|
||||||
self.object.participation.save()
|
self.object.participation.save()
|
||||||
|
mail_context = dict(team=self.object, message=form.cleaned_data["message"])
|
||||||
domain = Site.objects.first().domain
|
|
||||||
for registration in self.object.participants.all():
|
|
||||||
if registration.is_student and self.object.participation.tournament.amount:
|
|
||||||
payment = Payment.objects.get(registrations=registration, final=False)
|
|
||||||
else:
|
|
||||||
payment = None
|
|
||||||
mail_context = dict(domain=domain, registration=registration, team=self.object, payment=payment,
|
|
||||||
message=form.cleaned_data["message"])
|
|
||||||
mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context)
|
mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context)
|
||||||
mail_html = render_to_string("participation/mails/team_validated.html", mail_context)
|
mail_html = render_to_string("participation/mails/team_validated.html", mail_context)
|
||||||
registration.user.email_user("[TFJM²] Équipe validée", mail_plain, html_message=mail_html)
|
send_mail("[TFJM²] Équipe validée", mail_plain, None, [self.object.email], html_message=mail_html)
|
||||||
|
|
||||||
|
for student in self.object.students.all():
|
||||||
|
payment_qs = Payment.objects.filter(registrations=student)
|
||||||
|
if payment_qs.exists():
|
||||||
|
payment = payment_qs.get()
|
||||||
|
else:
|
||||||
|
payment = Payment.objects.create()
|
||||||
|
payment.registrations.add(student)
|
||||||
|
payment.save()
|
||||||
|
payment.amount = self.object.participation.tournament.price
|
||||||
|
if payment.amount == 0:
|
||||||
|
payment.type = "free"
|
||||||
|
payment.valid = True
|
||||||
|
payment.save()
|
||||||
|
|
||||||
elif "invalidate" in self.request.POST:
|
elif "invalidate" in self.request.POST:
|
||||||
self.object.participation.valid = None
|
self.object.participation.valid = None
|
||||||
self.object.participation.save()
|
self.object.participation.save()
|
||||||
|
@ -12,8 +12,8 @@ class RegistrationConfig(AppConfig):
|
|||||||
name = 'registration'
|
name = 'registration'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from registration import signals
|
from registration.signals import create_admin_registration, \
|
||||||
pre_save.connect(signals.set_username, 'auth.User')
|
set_username, send_email_link
|
||||||
pre_save.connect(signals.send_email_link, 'auth.User')
|
pre_save.connect(set_username, "auth.User")
|
||||||
pre_save.connect(signals.update_payment_amount, 'registration.Payment')
|
pre_save.connect(send_email_link, "auth.User")
|
||||||
post_save.connect(signals.create_admin_registration, 'auth.User')
|
post_save.connect(create_admin_registration, "auth.User")
|
||||||
|
@ -226,9 +226,6 @@ class PaymentAdminForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields["valid"].widget.choices[0] = ('unknown', _("Pending"))
|
self.fields["valid"].widget.choices[0] = ('unknown', _("Pending"))
|
||||||
payment_type = kwargs.get('data', {}).get('type', "")
|
|
||||||
self.fields['receipt'].required = payment_type in ["scholarship", "bank_transfer"]
|
|
||||||
self.fields['additional_information'].required = payment_type in ["other"]
|
|
||||||
|
|
||||||
def clean_receipt(self):
|
def clean_receipt(self):
|
||||||
if "receipt" in self.files:
|
if "receipt" in self.files:
|
||||||
|
@ -387,7 +387,7 @@ class StudentRegistration(ParticipantRegistration):
|
|||||||
'priority': 3,
|
'priority': 3,
|
||||||
'content': content,
|
'content': content,
|
||||||
})
|
})
|
||||||
elif payment.valid is None:
|
elif self.payment.valid is None:
|
||||||
text = _("Your payment is under approval.")
|
text = _("Your payment is under approval.")
|
||||||
content = text
|
content = text
|
||||||
informations.append({
|
informations.append({
|
||||||
|
@ -41,13 +41,3 @@ def create_admin_registration(instance, **_):
|
|||||||
"""
|
"""
|
||||||
if instance.is_superuser:
|
if instance.is_superuser:
|
||||||
VolunteerRegistration.objects.get_or_create(user=instance, admin=True)
|
VolunteerRegistration.objects.get_or_create(user=instance, admin=True)
|
||||||
|
|
||||||
|
|
||||||
def update_payment_amount(instance, **_):
|
|
||||||
"""
|
|
||||||
When a payment got created, ensure that the amount is set.
|
|
||||||
"""
|
|
||||||
if instance.type == 'free' or instance.type == 'scholarship':
|
|
||||||
instance.amount = 0
|
|
||||||
elif instance.pk:
|
|
||||||
instance.amount = instance.registrations.count() * instance.tournament.price
|
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% if can_group %}
|
|
||||||
{% if payment.grouped %}
|
{% if payment.grouped %}
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
You want finally that each member pays its own registration? Then click on the button:
|
You want finally that each member pays its own registration? Then click on the button:
|
||||||
@ -45,7 +44,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
@ -201,7 +199,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if user.registration.is_volunteer %}
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
<div id="form-content">
|
<div id="form-content">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -209,74 +206,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" type="submit">{% trans "Update" %}</button>
|
<button class="btn btn-primary" type="submit">{% trans "Update" %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
|
||||||
{% if payment.type == 'helloasso' %}
|
|
||||||
{% if payment.valid is True %}
|
|
||||||
<div class="alert alert-success">
|
|
||||||
{% with order=payment.get_checkout_intent.order %}
|
|
||||||
{% trans "Your payment by credit card via Hello Asso is successfully validated." %}
|
|
||||||
{% trans "The paid amount is" %} {% widthratio order.amount.total 100 1 %} €.
|
|
||||||
{% if grouped %}
|
|
||||||
{% trans "It includes the registrations of all members of the team." %}
|
|
||||||
{% endif %}
|
|
||||||
{% trans "The payer was " %} {{ order.payer.firstName }} {{ order.payer.lastName }}.
|
|
||||||
{% trans "The payment was done on" %} {{ order.date }}.
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
{% elif payment.valid is None %}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
{% trans "The payment by credit card via Hello Asso is pending validation." %}
|
|
||||||
{% trans "It should takes only few minutes. If it takes longer, please contact us." %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if payment.valid is True %}
|
|
||||||
<div class="alert alert-success">
|
|
||||||
{% trans "Your payment is successfully validated by the organizers." %}
|
|
||||||
<ul>
|
|
||||||
<li>{% trans "Type:" %} {{ payment.get_type_display }}</li>
|
|
||||||
<li>
|
|
||||||
{% trans "Amount:" %} {{ payment.amount }} €
|
|
||||||
{% if payment.grouped %}
|
|
||||||
({% trans "It includes the registrations of all members of the team." %})
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% if payment.receipt %}
|
|
||||||
<li>
|
|
||||||
{% trans "Receipt:" %}
|
|
||||||
<a href="{{ payment.receipt.url }}"><i class="fas fa-download"></i> {% trans "Download" %}</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if payment.additional_information %}
|
|
||||||
<li>{% trans "Additional information:" %} {{ payment.additional_information }}</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% elif payment.valid is None %}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
{% trans "Your payment is pending validation from the organizers." %}
|
|
||||||
<ul>
|
|
||||||
<li>{% trans "Type:" %} {{ payment.get_type_display }}</li>
|
|
||||||
<li>
|
|
||||||
{% trans "Amount:" %} {{ payment.amount }} €
|
|
||||||
{% if payment.grouped %}
|
|
||||||
({% trans "It includes the registrations of all members of the team." %})
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% if payment.receipt %}
|
|
||||||
<li>
|
|
||||||
{% trans "Receipt:" %}
|
|
||||||
<a href="{{ payment.receipt.url }}"><i class="fas fa-download"></i> {% trans "Download" %}</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if payment.additional_information %}
|
|
||||||
<li>{% trans "Additional information:" %} {{ payment.additional_information }}</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
@ -451,16 +451,14 @@ class PaymentUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not self.request.user.is_authenticated or \
|
if not self.request.user.is_authenticated or \
|
||||||
not self.request.user.registration.is_admin \
|
not self.request.user.registration.is_admin \
|
||||||
and self.request.user.registration not in self.get_object().registrations.all():
|
and (self.request.user.registration not in self.get_object().registrations.all()
|
||||||
|
or self.get_object().valid is not False):
|
||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data()
|
context = super().get_context_data()
|
||||||
context['title'] = _("Update payment")
|
context['title'] = _("Update payment")
|
||||||
# Grouping is only possible if there isn't any validated payment in the team
|
|
||||||
context['can_group'] = all(p.valid is False for reg in self.object.team.students.all()
|
|
||||||
for p in reg.payments.filter(valid=self.object.valid).all())
|
|
||||||
context['bank_transfer_form'] = PaymentForm(payment_type='bank_transfer',
|
context['bank_transfer_form'] = PaymentForm(payment_type='bank_transfer',
|
||||||
data=self.request.POST or None,
|
data=self.request.POST or None,
|
||||||
instance=self.object)
|
instance=self.object)
|
||||||
@ -476,12 +474,8 @@ class PaymentUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
old_instance = Payment.objects.get(pk=self.object.pk)
|
|
||||||
if self.request.user.registration.participates:
|
|
||||||
if old_instance.valid is not False:
|
|
||||||
raise PermissionDenied(_("This payment is already valid or pending validation."))
|
|
||||||
else:
|
|
||||||
form.instance.valid = None
|
form.instance.valid = None
|
||||||
|
old_instance = Payment.objects.get(pk=self.object.pk)
|
||||||
if old_instance.receipt:
|
if old_instance.receipt:
|
||||||
old_instance.receipt.delete()
|
old_instance.receipt.delete()
|
||||||
old_instance.save()
|
old_instance.save()
|
||||||
@ -495,18 +489,11 @@ class PaymentUpdateGroupView(LoginRequiredMixin, DetailView):
|
|||||||
model = Payment
|
model = Payment
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
payment = self.get_object()
|
|
||||||
|
|
||||||
if not self.request.user.is_authenticated or \
|
if not self.request.user.is_authenticated or \
|
||||||
not self.request.user.registration.is_admin \
|
not self.request.user.registration.is_admin \
|
||||||
and (self.request.user.registration not in self.get_object().registrations.all()
|
and (self.request.user.registration not in self.get_object().registrations.all()
|
||||||
or payment.valid is not False):
|
or self.get_object().valid is not False):
|
||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
|
|
||||||
if any(p.valid is not False for reg in payment.team.students.all()
|
|
||||||
for p in reg.payments.filter(valid=payment.valid).all()):
|
|
||||||
raise PermissionDenied(_("Since one payment is already validated, or pending validation, "
|
|
||||||
"grouping is not possible."))
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
@ -6,15 +6,14 @@ from datetime import datetime, timedelta
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
_access_token = None
|
_access_token = None
|
||||||
_refresh_token = None
|
_refresh_token = None
|
||||||
_expires_at = None
|
_expires_at = None
|
||||||
|
|
||||||
|
|
||||||
def _get_hello_asso_api_base_url():
|
def _get_hello_asso_api_base_url():
|
||||||
if settings.HELLOASSO_TEST_ENDPOINT:
|
if not settings.DEBUG:
|
||||||
return f"{settings.HELLOASSO_TEST_ENDPOINT_URL}/helloasso-test/api"
|
|
||||||
elif not settings.DEBUG:
|
|
||||||
return "https://api.helloasso.com"
|
return "https://api.helloasso.com"
|
||||||
else:
|
else:
|
||||||
return "https://api.helloasso-sandbox.com"
|
return "https://api.helloasso-sandbox.com"
|
@ -1,23 +0,0 @@
|
|||||||
# Copyright (C) 2024 by Animath
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from django.urls import path
|
|
||||||
import tfjm.urls
|
|
||||||
|
|
||||||
from . import test_views
|
|
||||||
|
|
||||||
urlpatterns = tfjm.urls.urlpatterns
|
|
||||||
|
|
||||||
urlpatterns += [
|
|
||||||
path('helloasso-test/api/oauth2/token', test_views.TestHelloAssoOAuth2View.as_view(),
|
|
||||||
name='helloasso-test-oauth2-token'),
|
|
||||||
path('helloasso-test/api/v5/organizations/animath/checkout-intents/',
|
|
||||||
test_views.TestHelloAssoCheckoutIntentCreateView.as_view(),
|
|
||||||
name='helloasso-test-checkout-intent-create'),
|
|
||||||
path('helloasso-test/api/v5/organizations/animath/checkout-intents/<int:checkout_intent_id>/',
|
|
||||||
test_views.TestHelloAssoCheckoutIntentDetailView.as_view(),
|
|
||||||
name='helloasso-test-checkout-intent-detail'),
|
|
||||||
path('helloasso-test/redirect-payment/<int:checkout_intent_id>/',
|
|
||||||
test_views.TestHelloAssoRedirectPaymentView.as_view(),
|
|
||||||
name='helloasso-test-redirect-payment'),
|
|
||||||
]
|
|
@ -1,149 +0,0 @@
|
|||||||
# Copyright (C) 2024 by Animath
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.http import Http404, HttpResponse, JsonResponse
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.views.generic.base import View
|
|
||||||
|
|
||||||
_CHECKOUT_INTENTS = {}
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
|
||||||
class TestHelloAssoOAuth2View(View):
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
data = {
|
|
||||||
'access_token': 'test_access_token',
|
|
||||||
'refresh_token': 'test_refresh_token',
|
|
||||||
'expires_in': 3600,
|
|
||||||
}
|
|
||||||
return JsonResponse(data)
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
|
||||||
class TestHelloAssoCheckoutIntentCreateView(View):
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
checkout_intent_id = len(_CHECKOUT_INTENTS) + 1
|
|
||||||
body = json.loads(request.body.decode())
|
|
||||||
|
|
||||||
body['backUrl'] = body['backUrl'].replace("https", "http")
|
|
||||||
body['returnUrl'] = body['returnUrl'].replace("https", "http")
|
|
||||||
body['errorUrl'] = body['errorUrl'].replace("https", "http")
|
|
||||||
|
|
||||||
output_data = {
|
|
||||||
'id': checkout_intent_id,
|
|
||||||
'redirectUrl': f"{settings.HELLOASSO_TEST_ENDPOINT_URL}"
|
|
||||||
f"{reverse('helloasso-test-redirect-payment', args=(checkout_intent_id,))}",
|
|
||||||
'metadata': body['metadata'],
|
|
||||||
}
|
|
||||||
|
|
||||||
checkout_intent = {'input': body, 'output': output_data}
|
|
||||||
_CHECKOUT_INTENTS[checkout_intent_id] = checkout_intent
|
|
||||||
|
|
||||||
return JsonResponse(output_data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestHelloAssoCheckoutIntentDetailView(View):
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
checkout_intent_id = kwargs['checkout_intent_id']
|
|
||||||
if checkout_intent_id not in _CHECKOUT_INTENTS:
|
|
||||||
raise Http404
|
|
||||||
return JsonResponse(_CHECKOUT_INTENTS[checkout_intent_id]['output'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestHelloAssoRedirectPaymentView(View):
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
checkout_intent_id = kwargs['checkout_intent_id']
|
|
||||||
if checkout_intent_id not in _CHECKOUT_INTENTS:
|
|
||||||
raise Http404
|
|
||||||
|
|
||||||
checkout_intent = _CHECKOUT_INTENTS[checkout_intent_id]
|
|
||||||
ci_input = checkout_intent['input']
|
|
||||||
ci_output = checkout_intent['output']
|
|
||||||
|
|
||||||
if 'error' in request.GET:
|
|
||||||
return redirect(ci_input['errorUrl'] + f"&checkoutIntentId={checkout_intent_id}&error=An error occurred.")
|
|
||||||
elif 'refused' in request.GET:
|
|
||||||
return redirect(ci_input['returnUrl'] + f"&checkoutIntentId={checkout_intent_id}&code=refused")
|
|
||||||
|
|
||||||
dt = timezone.now().isoformat()
|
|
||||||
|
|
||||||
ci_output['order'] = {
|
|
||||||
'payer': {
|
|
||||||
'email': 'payer@example.com',
|
|
||||||
'country': 'FRA',
|
|
||||||
'dateOfBirth': '2000-01-01T00:00:00+01:00',
|
|
||||||
'firstName': "Payer",
|
|
||||||
'lastName': "Payer",
|
|
||||||
},
|
|
||||||
'items': [
|
|
||||||
{
|
|
||||||
'payments': [
|
|
||||||
{
|
|
||||||
'id': checkout_intent_id,
|
|
||||||
'shareAmount': ci_input['totalAmount'],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'name': ci_input['itemName'],
|
|
||||||
'priceCategory': 'Fixed',
|
|
||||||
'qrCode': '',
|
|
||||||
'id': checkout_intent_id,
|
|
||||||
'amount': ci_input['totalAmount'],
|
|
||||||
'type': 'Payment',
|
|
||||||
'state': 'Processed'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'payments': [
|
|
||||||
{
|
|
||||||
'items': [
|
|
||||||
{
|
|
||||||
'id': checkout_intent_id,
|
|
||||||
'shareAmount': ci_input['totalAmount'],
|
|
||||||
'shareItemAmount': ci_input['totalAmount'],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'cashOutState': 'MoneyIn',
|
|
||||||
'paymentReceiptUrl': "https://example.com/",
|
|
||||||
'id': checkout_intent_id,
|
|
||||||
'amount': ci_input['totalAmount'],
|
|
||||||
'date': dt,
|
|
||||||
'paymentMeans': 'Card',
|
|
||||||
'installmentNumber': 1,
|
|
||||||
'state': 'Authorized',
|
|
||||||
'meta': {
|
|
||||||
'createdAt': dt,
|
|
||||||
'updatedAt': dt,
|
|
||||||
},
|
|
||||||
'refundOperations': []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'amount': {
|
|
||||||
'total': ci_input['totalAmount'],
|
|
||||||
'vat': 0,
|
|
||||||
'discount': 0
|
|
||||||
},
|
|
||||||
'id': 13339,
|
|
||||||
'date': dt,
|
|
||||||
'formSlug': 'default',
|
|
||||||
'formType': 'Checkout',
|
|
||||||
'organizationName': 'Animath',
|
|
||||||
'organizationSlug': 'animath',
|
|
||||||
'checkoutIntentId': checkout_intent_id,
|
|
||||||
'meta': {
|
|
||||||
'createdAt': dt,
|
|
||||||
'updatedAt': dt,
|
|
||||||
},
|
|
||||||
'isAnonymous': False,
|
|
||||||
'isAmountHidden': False
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect(ci_input['returnUrl'] + f"&checkoutIntentId={checkout_intent_id}&code=succeeded")
|
|
||||||
|
|
||||||
def head(self, request, *args, **kwargs):
|
|
||||||
return HttpResponse()
|
|
@ -244,7 +244,6 @@ PHONENUMBER_DEFAULT_REGION = 'FR'
|
|||||||
# Hello Asso API creds
|
# Hello Asso API creds
|
||||||
HELLOASSO_CLIENT_ID = os.getenv('HELLOASSO_CLIENT_ID', 'CHANGE_ME_IN_ENV_SETTINGS')
|
HELLOASSO_CLIENT_ID = os.getenv('HELLOASSO_CLIENT_ID', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||||
HELLOASSO_CLIENT_SECRET = os.getenv('HELLOASSO_CLIENT_SECRET', 'CHANGE_ME_IN_ENV_SETTINGS')
|
HELLOASSO_CLIENT_SECRET = os.getenv('HELLOASSO_CLIENT_SECRET', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||||
HELLOASSO_TEST_ENDPOINT = False # Enable custom test endpoint, for unit tests
|
|
||||||
|
|
||||||
# Custom parameters
|
# Custom parameters
|
||||||
PROBLEMS = [
|
PROBLEMS = [
|
||||||
@ -285,8 +284,3 @@ if TFJM_STAGE == "prod": # pragma: no cover
|
|||||||
from .settings_prod import * # noqa: F401,F403
|
from .settings_prod import * # noqa: F401,F403
|
||||||
else:
|
else:
|
||||||
from .settings_dev import * # noqa: F401,F403
|
from .settings_dev import * # noqa: F401,F403
|
||||||
|
|
||||||
try:
|
|
||||||
from .settings_local import * # noqa: F401,F403
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
21
tox.ini
@ -8,11 +8,26 @@ skipsdist = True
|
|||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
sitepackages = False
|
sitepackages = False
|
||||||
deps = coverage
|
deps =
|
||||||
-r requirements.txt
|
coverage
|
||||||
|
channels[daphne]~=4.0.0
|
||||||
|
crispy-bootstrap5~=2023.10
|
||||||
|
Django>=5.0,<6.0
|
||||||
|
django-crispy-forms~=2.1
|
||||||
|
django-filter~=23.5
|
||||||
|
git+https://github.com/django-haystack/django-haystack.git#v3.3b1
|
||||||
|
django-phonenumber-field~=7.3.0
|
||||||
|
django-polymorphic~=3.1.0
|
||||||
|
django-tables2~=2.7.0
|
||||||
|
djangorestframework~=3.14.0
|
||||||
|
django-rest-polymorphic~=0.1.10
|
||||||
|
odfpy~=1.4.1
|
||||||
|
phonenumbers~=8.13.27
|
||||||
|
pypdf~=3.17.4
|
||||||
|
python-magic~=0.4.27
|
||||||
|
requests~=2.31.0
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
python manage.py compilemessages -i .tox -i venv
|
|
||||||
coverage run --source=api,draw,logs,participation,registration,tfjm ./manage.py test api/ draw/ logs/ participation/ registration/ tfjm/
|
coverage run --source=api,draw,logs,participation,registration,tfjm ./manage.py test api/ draw/ logs/ participation/ registration/ tfjm/
|
||||||
coverage report -m
|
coverage report -m
|
||||||
|
|
||||||
|