mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-06-21 01:58:23 +02:00
Allow anonymous users to perform a payment using a special auth token
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
@ -0,0 +1,41 @@
|
||||
# Generated by Django 5.0.1 on 2024-02-20 22:48
|
||||
|
||||
import registration.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("registration", "0011_remove_payment_registration_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="token",
|
||||
field=models.CharField(
|
||||
default=registration.models.get_random_token,
|
||||
help_text="A token to authorize external users to make this payment.",
|
||||
max_length=32,
|
||||
verbose_name="token",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="payment",
|
||||
name="type",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("", "No payment"),
|
||||
("helloasso", "Credit card"),
|
||||
("scholarship", "Scholarship"),
|
||||
("bank_transfer", "Bank transfer"),
|
||||
("other", "Other (please indicate)"),
|
||||
("free", "The tournament is free"),
|
||||
],
|
||||
default="",
|
||||
max_length=16,
|
||||
verbose_name="type",
|
||||
),
|
||||
),
|
||||
]
|
@ -506,6 +506,10 @@ def get_receipt_filename(instance, filename):
|
||||
return f"authorization/receipt/receipt_{instance.id}"
|
||||
|
||||
|
||||
def get_random_token():
|
||||
return get_random_string(32)
|
||||
|
||||
|
||||
class Payment(models.Model):
|
||||
registrations = models.ManyToManyField(
|
||||
ParticipantRegistration,
|
||||
@ -526,6 +530,13 @@ class Payment(models.Model):
|
||||
default=0,
|
||||
)
|
||||
|
||||
token = models.CharField(
|
||||
verbose_name=_("token"),
|
||||
max_length=32,
|
||||
default=get_random_token,
|
||||
help_text=_("A token to authorize external users to make this payment."),
|
||||
)
|
||||
|
||||
final = models.BooleanField(
|
||||
verbose_name=_("for final tournament"),
|
||||
default=False,
|
||||
@ -585,13 +596,13 @@ class Payment(models.Model):
|
||||
return Tournament.final_tournament()
|
||||
return self.registrations.first().team.participation.tournament
|
||||
|
||||
def get_checkout_intent(self):
|
||||
def get_checkout_intent(self, none_if_link_disabled=False):
|
||||
if self.checkout_intent_id is None:
|
||||
return None
|
||||
return helloasso.get_checkout_intent(self.checkout_intent_id)
|
||||
return helloasso.get_checkout_intent(self.checkout_intent_id, none_if_link_disabled=none_if_link_disabled)
|
||||
|
||||
def create_checkout_intent(self):
|
||||
checkout_intent = self.get_checkout_intent()
|
||||
checkout_intent = self.get_checkout_intent(none_if_link_disabled=True)
|
||||
if checkout_intent is not None:
|
||||
return checkout_intent
|
||||
|
||||
|
@ -79,17 +79,39 @@
|
||||
<div class="card-body">
|
||||
<div class="tab-content" id="payment-form">
|
||||
<div class="tab-pane fade show active" id="credit-card" role="tabpanel" aria-labelledby="credit-card-tab">
|
||||
Le paiement par carte bancaire s'effectue via Hello Asso. Pour cela, vous pouvez cliquer sur
|
||||
le bouton ci-dessous, qui vous redirigera vers la page de paiement sécurisée de Hello Asso.
|
||||
La validation du paiement sera ensuite faite automatiquement, sous quelques minutes.
|
||||
Si un tiers doit payer pour vous (parents, lycée,…), vous pouvez lui transmettre le lien pour
|
||||
payer pour vous.
|
||||
<p>
|
||||
Le paiement par carte bancaire s'effectue via Hello Asso. Pour cela, vous pouvez cliquer sur
|
||||
le bouton ci-dessous, qui vous redirigera vers la page de paiement sécurisée de Hello Asso.
|
||||
La validation du paiement sera ensuite faite automatiquement, sous quelques minutes.
|
||||
</p>
|
||||
|
||||
<div class="text-center">
|
||||
<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>
|
||||
|
||||
<p>
|
||||
Si un tiers doit payer pour vous (parents, lycée,…), vous pouvez lui transmettre le lien pour
|
||||
payer pour vous :
|
||||
</p>
|
||||
|
||||
<div class="text-center border border-1 my-3 p-2 border-danger bg-body-tertiary shadow-lg rounded">
|
||||
{% url "registration:payment_hello_asso" pk=payment.pk as payment_url %}
|
||||
{{ request.scheme }}://{{ request.site.domain }}{{ payment_url }}?token={{ payment.token }}
|
||||
<a id="copyIcon" href="#"
|
||||
data-bs-title="Copié !"
|
||||
onclick="event.preventDefault();copyToClipboard('{{ request.scheme }}://{{ request.site.domain }}{{ payment_url }}?token={{ payment.token }}')">
|
||||
<i class="fas fa-copy"></i> Copier
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Si tel est le cas et si une facture est nécessaire, merci de contacter les organisateur⋅ices
|
||||
du tournoi en transmettant le nom de l'équipe, le nombre de participant⋅es, le nom de
|
||||
l'établissement payeur, l'adresse mail de l'établissement et/ou l'adresse mail du ou de la
|
||||
gestionnaire de l'établissement.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="bank-transfer" role="tabpanel" aria-labelledby="bank-transfer-tab">
|
||||
@ -138,5 +160,28 @@
|
||||
elem => elem.addEventListener(
|
||||
'click', () => document.location.hash = '#' + elem.getAttribute('aria-controls')))
|
||||
})
|
||||
|
||||
function copyToClipboard(text) {
|
||||
const copyIcon = document.getElementById('copyIcon')
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
const tooltip = bootstrap.Tooltip.getOrCreateInstance(copyIcon)
|
||||
tooltip.setContent('Copied!')
|
||||
tooltip.show()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
const input = document.createElement('input')
|
||||
input.value = text
|
||||
document.body.appendChild(input)
|
||||
input.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(input)
|
||||
const tooltip = bootstrap.Tooltip.getOrCreateInstance(copyIcon)
|
||||
tooltip.enable()
|
||||
tooltip.show()
|
||||
setTimeout(() => {tooltip.disable(); tooltip.hide()}, 2000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -7,7 +7,7 @@ from tempfile import mkdtemp
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
@ -535,21 +535,41 @@ class PaymentUpdateGroupView(LoginRequiredMixin, DetailView):
|
||||
return redirect(reverse_lazy("registration:update_payment", args=(payment.pk,)))
|
||||
|
||||
|
||||
class PaymenRedirectHelloAssoView(LoginRequiredMixin, DetailView):
|
||||
class PaymenRedirectHelloAssoView(AccessMixin, 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):
|
||||
payment = self.get_object()
|
||||
|
||||
# An external user has the link for the payment
|
||||
token = request.GET.get('token', "")
|
||||
if token and token == payment.token:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not request.user.registration.is_admin:
|
||||
if request.user.registration.is_volunteer \
|
||||
and payment.tournament not in request.user.registration.organized_tournaments.all():
|
||||
return self.handle_no_permission()
|
||||
|
||||
if request.user.registration.is_student \
|
||||
and request.user.registration not in payment.registrations.all():
|
||||
return self.handle_no_permission()
|
||||
|
||||
if request.user.registration.is_coach \
|
||||
and request.user.registration.team != payment.team:
|
||||
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()
|
||||
if payment.valid is not False:
|
||||
raise PermissionDenied(_("The payment is already valid or pending validation."))
|
||||
|
||||
checkout_intent = payment.create_checkout_intent()
|
||||
return redirect(checkout_intent["redirectUrl"])
|
||||
|
||||
|
||||
@ -558,9 +578,10 @@ class PaymentHelloAssoReturnView(DetailView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
checkout_id = request.GET.get("checkoutIntentId")
|
||||
payment = Payment.objects.get(checkout_intent_id=checkout_id).exclude(valid=True)
|
||||
if payment != self.get_object():
|
||||
messages.error(request, _("The payment is not found or is already validated."))
|
||||
payment = self.get_object()
|
||||
payment_qs = Payment.objects.exclude(valid=True).filter(checkout_intent_id=checkout_id).filter(pk=payment.pk)
|
||||
if not payment_qs.exists():
|
||||
messages.error(request, _("The payment is not found or is already validated."), "danger")
|
||||
return redirect("index")
|
||||
|
||||
team = payment.team
|
||||
@ -580,18 +601,18 @@ class PaymentHelloAssoReturnView(DetailView):
|
||||
return_type = request.GET.get("type")
|
||||
if return_type == "error":
|
||||
messages.error(request, format_lazy(_("An error occurred during the payment: {error}"),
|
||||
error=request.GET.get("error")))
|
||||
error=request.GET.get("error")), "danger")
|
||||
return error_response
|
||||
elif return_type == "return":
|
||||
code = request.GET.get("code")
|
||||
if code == "refused":
|
||||
messages.error(request, _("The payment has been refused."))
|
||||
messages.error(request, _("The payment has been refused."), "danger")
|
||||
return error_response
|
||||
elif code != "success":
|
||||
messages.error(request, format_lazy(_("The return code is unknown: {code}"), code=code))
|
||||
elif code != "succeeded":
|
||||
messages.error(request, format_lazy(_("The return code is unknown: {code}"), code=code), "danger")
|
||||
return error_response
|
||||
else:
|
||||
messages.error(request, format_lazy(_("The return type is unknown: {type}"), type=return_type))
|
||||
messages.error(request, format_lazy(_("The return type is unknown: {type}"), type=return_type), "danger")
|
||||
return error_response
|
||||
|
||||
checkout_intent = payment.get_checkout_intent()
|
||||
@ -608,7 +629,7 @@ class PaymentHelloAssoReturnView(DetailView):
|
||||
"and will be automatically done. "
|
||||
"If it is not the case, please contact us."))
|
||||
|
||||
if request.user.registration in payment.registrations.all():
|
||||
if not request.user.is_anonymous and request.user.registration in payment.registrations.all():
|
||||
success_response = redirect("registration:user_detail", args=(request.user.pk,))
|
||||
elif right_to_see:
|
||||
success_response = redirect("participation:team_detail", args=(team.pk,))
|
||||
|
Reference in New Issue
Block a user