diff --git a/registration/models.py b/registration/models.py
index 407e5aa..cc0ef80 100644
--- a/registration/models.py
+++ b/registration/models.py
@@ -1,13 +1,13 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
-from datetime import date
+from datetime import date, datetime
from django.contrib.sites.models import Site
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.template import loader
-from django.urls import reverse_lazy
+from django.urls import reverse_lazy, reverse
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.encoding import force_bytes
@@ -16,6 +16,8 @@ from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from polymorphic.models import PolymorphicModel
+
+from tfjm import helloasso
from tfjm.tokens import email_validation_token
@@ -564,6 +566,46 @@ class Payment(models.Model):
default=False,
)
+ def get_checkout_intent(self):
+ if self.checkout_intent_id is None:
+ return None
+ return helloasso.get_checkout_intent(self.checkout_intent_id)
+
+ def create_checkout_intent(self):
+ checkout_intent = self.get_checkout_intent()
+ if checkout_intent is not None:
+ return checkout_intent
+
+ from participation.models import Tournament
+ tournament = self.registrations.first().team.participation.tournament \
+ if not self.final else Tournament.final_tournament()
+ year = datetime.now().year
+ base_site = "https://" + Site.objects.first().domain
+ checkout_intent = helloasso.create_checkout_intent(
+ amount=100 * self.amount,
+ name=f"Participation au TFJM² {year} - {tournament.name}",
+ back_url=base_site + reverse('registration:update_payment', args=(self.id,)),
+ error_url=base_site + reverse('registration:update_payment', args=(self.id,)),
+ return_url=base_site + reverse('registration:update_payment', args=(self.id,)),
+ contains_donation=False,
+ metadata=dict(
+ users=[
+ dict(user_id=registration.user.id,
+ first_name=registration.user.first_name,
+ last_name=registration.user.last_name,
+ email=registration.user.email,)
+ for registration in self.registrations.all()
+ ],
+ payment_id=self.id,
+ final=self.final,
+ tournament_id=tournament.id,
+ )
+ )
+ self.checkout_intent_id = checkout_intent["id"]
+ self.save()
+
+ return checkout_intent
+
def get_absolute_url(self):
return reverse_lazy("registration:update_payment", args=(self.pk,))
diff --git a/registration/templates/registration/payment_form.html b/registration/templates/registration/payment_form.html
index 6272c2a..327bb2f 100644
--- a/registration/templates/registration/payment_form.html
+++ b/registration/templates/registration/payment_form.html
@@ -86,7 +86,7 @@
payer pour vous.
diff --git a/registration/urls.py b/registration/urls.py
index 68ceeb4..463d2c5 100644
--- a/registration/urls.py
+++ b/registration/urls.py
@@ -4,8 +4,8 @@
from django.urls import path
from .views import AddOrganizerView, AdultPhotoAuthorizationTemplateView, ChildPhotoAuthorizationTemplateView, \
- InstructionsTemplateView, MyAccountDetailView, ParentalAuthorizationTemplateView, PaymentUpdateView, \
- PaymentUpdateGroupView, \
+ InstructionsTemplateView, MyAccountDetailView, ParentalAuthorizationTemplateView, \
+ PaymentUpdateGroupView, PaymentUpdateView, PaymenRedirectHelloAssoView, \
ResetAdminView, SignupView, UserDetailView, UserImpersonateView, UserListView, UserResendValidationEmailView, \
UserUpdateView, UserUploadHealthSheetView, UserUploadParentalAuthorizationView, UserUploadPhotoAuthorizationView, \
UserUploadVaccineSheetView, UserValidateView, UserValidationEmailSentView
@@ -40,6 +40,7 @@ urlpatterns = [
path("update-payment//", PaymentUpdateView.as_view(), name="update_payment"),
path("update-payment//toggle-group-mode/", PaymentUpdateGroupView.as_view(),
name="update_payment_group_mode"),
+ path("update-payment//hello-asso/", PaymenRedirectHelloAssoView.as_view(), name="payment_hello_asso"),
path("user//impersonate/", UserImpersonateView.as_view(), name="user_impersonate"),
path("user/list/", UserListView.as_view(), name="user_list"),
path("reset-admin/", ResetAdminView.as_view(), name="reset_admin"),
diff --git a/registration/views.py b/registration/views.py
index 5ea96df..304de01 100644
--- a/registration/views.py
+++ b/registration/views.py
@@ -506,6 +506,7 @@ class PaymentUpdateGroupView(LoginRequiredMixin, DetailView):
payment.grouped = False
tournament = first_reg.team.participation.tournament if not payment.final else Tournament.final_tournament()
payment.amount = tournament.price
+ payment.checkout_intent_id = None
payment.save()
for registration in registrations[1:]:
p = Payment.objects.create(type=payment.type,
@@ -525,11 +526,30 @@ class PaymentUpdateGroupView(LoginRequiredMixin, DetailView):
payment.registrations.add(student)
payment.amount = tournament.price * reg.team.students.count()
payment.grouped = True
+ payment.checkout_intent_id = None
payment.save()
return redirect(reverse_lazy("registration:update_payment", args=(payment.pk,)))
+class PaymenRedirectHelloAssoView(LoginRequiredMixin, DetailView):
+ model = Payment
+
+ def dispatch(self, request, *args, **kwargs):
+ if not self.request.user.is_authenticated or \
+ not self.request.user.registration.is_admin \
+ 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 super().dispatch(request, *args, **kwargs)
+
+ def get(self, request, *args, **kwargs):
+ payment = self.get_object()
+ checkout_intent = payment.create_checkout_intent()
+
+ return redirect(checkout_intent["redirectUrl"])
+
+
class PhotoAuthorizationView(LoginRequiredMixin, View):
"""
Display the sent photo authorization.
diff --git a/tfjm/helloasso.py b/tfjm/helloasso.py
new file mode 100644
index 0000000..768fe88
--- /dev/null
+++ b/tfjm/helloasso.py
@@ -0,0 +1,86 @@
+# Copyright (C) 2024 by Animath
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from datetime import datetime, timedelta
+
+from django.conf import settings
+import requests
+
+
+_access_token = None
+_refresh_token = None
+_expires_at = None
+
+
+def get_hello_asso_access_token():
+ global _access_token, _refresh_token, _expires_at
+
+ now = datetime.now()
+ if _access_token is None:
+ response = requests.post(
+ "https://api.helloasso.com/oauth2/token",
+ data={
+ "grant_type": "client_credentials",
+ "client_id": settings.HELLOASSO_CLIENT_ID,
+ "client_secret": settings.HELLOASSO_CLIENT_SECRET,
+ },
+ )
+ response.raise_for_status()
+ data = response.json()
+ _access_token = data["access_token"]
+ _refresh_token = data["refresh_token"]
+ _expires_at = now + timedelta(seconds=data["expires_in"])
+ elif now >= _expires_at:
+ response = requests.post(
+ "https://api.helloasso.com/oauth2/token",
+ data={
+ "grant_type": "refresh_token",
+ "refresh_token": _refresh_token,
+ },
+ )
+ response.raise_for_status()
+ data = response.json()
+ _access_token = data["access_token"]
+ _refresh_token = data["refresh_token"]
+ _expires_at = now + timedelta(seconds=data["expires_in"])
+
+ return _access_token
+
+
+def get_checkout_intent(checkout_id):
+ token = get_hello_asso_access_token()
+ response = requests.get(
+ f"https://api.helloasso.com/v5/organizations/animath/checkout-intents/{checkout_id}",
+ headers={"Authorization": f"Bearer {token}"},
+ )
+ if response.status_code == 404:
+ return None
+ response.raise_for_status()
+
+ checkout_intent = response.json()
+ if requests.head(checkout_intent["redirectUrl"]).status_code == 404:
+ return None
+
+ return checkout_intent
+
+
+def create_checkout_intent(amount, name, back_url, error_url, return_url, contains_donation=False, metadata=None):
+ token = get_hello_asso_access_token()
+ metadata = metadata or {}
+ response = requests.post(
+ "https://api.helloasso.com/v5/organizations/animath/checkout-intents/",
+ headers={"Authorization": f"Bearer {token}"},
+ json={
+ "totalAmount": amount,
+ "initialAmount": amount,
+ "itemName": name,
+ "backUrl": back_url,
+ "errorUrl": error_url,
+ "returnUrl": return_url,
+ "containsDonation": contains_donation,
+ "metadata": metadata,
+ },
+ )
+ print(response.text)
+ response.raise_for_status()
+ return response.json()
diff --git a/tfjm/settings.py b/tfjm/settings.py
index a817b37..130f551 100644
--- a/tfjm/settings.py
+++ b/tfjm/settings.py
@@ -238,7 +238,9 @@ else:
PHONENUMBER_DB_FORMAT = 'NATIONAL'
PHONENUMBER_DEFAULT_REGION = 'FR'
-GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
+# Hello Asso API creds
+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')
# Custom parameters
PROBLEMS = [