mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-04 11:12:18 +01:00 
			
		
		
		
	Create Hello Asso checkout intents
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
		@@ -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,))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -86,7 +86,7 @@
 | 
			
		||||
                        payer pour vous.
 | 
			
		||||
 | 
			
		||||
                        <div class="text-center">
 | 
			
		||||
                            <a href="#" class="btn btn-primary">
 | 
			
		||||
                            <a href="{% url "registration:payment_hello_asso" pk=payment.pk %}" class="btn btn-primary">
 | 
			
		||||
                                <i class="fas fa-credit-card"></i> Aller sur la page Hello Asso
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -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/<int:pk>/", PaymentUpdateView.as_view(), name="update_payment"),
 | 
			
		||||
    path("update-payment/<int:pk>/toggle-group-mode/", PaymentUpdateGroupView.as_view(),
 | 
			
		||||
         name="update_payment_group_mode"),
 | 
			
		||||
    path("update-payment/<int:pk>/hello-asso/", PaymenRedirectHelloAssoView.as_view(), name="payment_hello_asso"),
 | 
			
		||||
    path("user/<int:pk>/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"),
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								tfjm/helloasso.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tfjm/helloasso.py
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
			
		||||
@@ -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 = [
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user