mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-06-21 11:18:27 +02:00
Setup payment interface
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
@ -219,7 +219,7 @@ class VolunteerRegistrationForm(forms.ModelForm):
|
||||
fields = ('professional_activity', 'admin', 'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
||||
class PaymentForm(forms.ModelForm):
|
||||
class PaymentAdminForm(forms.ModelForm):
|
||||
"""
|
||||
Indicate payment information
|
||||
"""
|
||||
@ -228,7 +228,6 @@ class PaymentForm(forms.ModelForm):
|
||||
self.fields["valid"].widget.choices[0] = ('unknown', _("Pending"))
|
||||
|
||||
def clean_receipt(self):
|
||||
print(self.files)
|
||||
if "receipt" in self.files:
|
||||
file = self.files["receipt"]
|
||||
if file.size > 2e6:
|
||||
@ -241,7 +240,7 @@ class PaymentForm(forms.ModelForm):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
if "type" in cleaned_data and cleaned_data['type'] in ["scholarship", "bank_transfer"] \
|
||||
and "receipt" not in self.files and not self.instance.scholarship_file:
|
||||
and "receipt" not in self.files and not self.instance.receipt:
|
||||
self.add_error("receipt", _("You must upload your receipt."))
|
||||
|
||||
return cleaned_data
|
||||
@ -249,3 +248,37 @@ class PaymentForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Payment
|
||||
fields = ('type', 'receipt', 'additional_information', 'valid',)
|
||||
|
||||
|
||||
class PaymentForm(forms.ModelForm):
|
||||
"""
|
||||
Indicate payment information
|
||||
"""
|
||||
def __init__(self, payment_type, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['type'].widget = forms.HiddenInput(attrs={'value': payment_type})
|
||||
self.fields['receipt'].required = payment_type in ["scholarship", "bank_transfer"]
|
||||
self.fields['additional_information'].required = payment_type in ["other"]
|
||||
|
||||
def clean_receipt(self):
|
||||
if "receipt" in self.files:
|
||||
file = self.files["receipt"]
|
||||
if file.size > 2e6:
|
||||
raise ValidationError(_("The uploaded file size must be under 2 Mo."))
|
||||
if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
|
||||
raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
|
||||
return self.cleaned_data["receipt"]
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
if "type" in cleaned_data and cleaned_data['type'] in ["scholarship", "bank_transfer"] \
|
||||
and "receipt" not in self.files and not self.instance.receipt:
|
||||
self.add_error("receipt", _("You must upload your receipt."))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
class Meta:
|
||||
model = Payment
|
||||
fields = ('type', 'receipt', 'additional_information',)
|
||||
|
||||
|
@ -113,7 +113,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('type', models.CharField(blank=True, choices=[('', 'No payment'), ('helloasso', 'Hello Asso'), ('scholarship', 'Scholarship'), ('bank_transfer', 'Bank transfer'), ('other', 'Other (please indicate)'), ('free', 'The tournament is free')], default='', max_length=16, verbose_name='type')),
|
||||
('scholarship_file', models.FileField(blank=True, default='', help_text='only if you have a scholarship.', upload_to=registration.models.get_scholarship_filename, verbose_name='scholarship file')),
|
||||
('scholarship_file', models.FileField(blank=True, default='', help_text='only if you have a scholarship.', upload_to=registration.models.get_receipt_filename, verbose_name='scholarship file')),
|
||||
('additional_information', models.TextField(blank=True, default='', help_text='To help us to find your payment.', verbose_name='additional information')),
|
||||
('valid', models.BooleanField(default=False, null=True, verbose_name='valid')),
|
||||
('registration', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment', to='registration.participantregistration', verbose_name='registration')),
|
||||
|
@ -60,7 +60,7 @@ class Migration(migrations.Migration):
|
||||
blank=True,
|
||||
default="",
|
||||
help_text="only if you have a scholarship or if you chose a bank transfer.",
|
||||
upload_to=registration.models.get_scholarship_filename,
|
||||
upload_to=registration.models.get_receipt_filename,
|
||||
verbose_name="receipt",
|
||||
),
|
||||
),
|
||||
|
@ -492,8 +492,8 @@ class VolunteerRegistration(Registration):
|
||||
verbose_name_plural = _("volunteer registrations")
|
||||
|
||||
|
||||
def get_scholarship_filename(instance, filename):
|
||||
return f"authorization/scholarship/scholarship_{instance.registration.pk}"
|
||||
def get_receipt_filename(instance, filename):
|
||||
return f"authorization/receipt/receipt_{instance.id}"
|
||||
|
||||
|
||||
class Payment(models.Model):
|
||||
@ -526,7 +526,7 @@ class Payment(models.Model):
|
||||
max_length=16,
|
||||
choices=[
|
||||
('', _("No payment")),
|
||||
('helloasso', "Hello Asso"),
|
||||
('helloasso', _("Credit card")),
|
||||
('scholarship', _("Scholarship")),
|
||||
('bank_transfer', _("Bank transfer")),
|
||||
('other', _("Other (please indicate)")),
|
||||
@ -546,7 +546,7 @@ class Payment(models.Model):
|
||||
receipt = models.FileField(
|
||||
verbose_name=_("receipt"),
|
||||
help_text=_("only if you have a scholarship or if you chose a bank transfer."),
|
||||
upload_to=get_scholarship_filename,
|
||||
upload_to=get_receipt_filename,
|
||||
blank=True,
|
||||
default="",
|
||||
)
|
||||
|
@ -3,8 +3,140 @@
|
||||
{% load crispy_forms_filters i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="alert alert-warning">
|
||||
Le formulaire de paiement est temporairement désactivé. Il sera accessible d'ici quelques jours.
|
||||
</div>
|
||||
{% if payment.valid is False %}
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
{% blocktrans trimmed with amount=payment.amount %}
|
||||
You must pay {{ amount }} € for your registration.
|
||||
{% endblocktrans %}
|
||||
{% if payment.grouped %}
|
||||
{% blocktrans trimmed %}
|
||||
This price includes the registrations of all members of your team.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
This price includes only your own registration.
|
||||
You are exempt from payment if you have a scholarship,
|
||||
but you must then send us a proof of your scholarship.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% if payment.grouped %}
|
||||
{% blocktrans trimmed %}
|
||||
You want finally that each member pays its own registration? Then click on the button:
|
||||
{% endblocktrans %}
|
||||
<div class="text-center">
|
||||
<button class="btn btn-warning">
|
||||
<i class="fas fa-user"></i> {% trans "Back to single payments" %}
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
You want to pay for the registrations of all members of your team,
|
||||
or your school will pay for all registrations? Then click on the button:
|
||||
{% endblocktrans %}
|
||||
<div class="text-center">
|
||||
<button class="btn btn-warning">
|
||||
<i class="fas fa-users"></i> {% trans "Group the payments of my team" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<nav>
|
||||
<div class="nav nav-tabs card-header-tabs" id="payment-method-tab" role="tablist">
|
||||
<button class="nav-link active" id="credit-card-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#credit-card" type="button" role="tab"
|
||||
aria-controls="credit-card" aria-selected="true">
|
||||
<i class="fas fa-credit-card"></i> {% trans "Credit card" %}
|
||||
</button>
|
||||
<button class="nav-link" id="bank-transfer-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#bank-transfer" type="button" role="tab"
|
||||
aria-controls="bank-transfer" aria-selected="true">
|
||||
<i class="fas fa-money-check"></i> {% trans "Bank transfer" %}
|
||||
</button>
|
||||
{% if not payment.grouped %}
|
||||
<button class="nav-link" id="scholarship-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#scholarship" type="button" role="tab"
|
||||
aria-controls="scholarship" aria-selected="true">
|
||||
<i class="fas fa-file-invoice"></i> {% trans "I have a scholarship" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
<button class="nav-link" id="other-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#other" type="button" role="tab"
|
||||
aria-controls="other" aria-selected="true">
|
||||
<i class="fas fa-question"></i> {% trans "Other" %}
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<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.
|
||||
|
||||
<div class="text-center">
|
||||
<a href="#" class="btn btn-primary">
|
||||
<i class="fas fa-credit-card"></i> Aller sur la page Hello Asso
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="bank-transfer" role="tabpanel" aria-labelledby="bank-transfer-tab">
|
||||
<form id="bank-transfer-form" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ bank_transfer_form|crispy }}
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="scholarship" role="tabpanel" aria-labelledby="scholarship-tab">
|
||||
<form id="scholarship-form" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ scholarship_form|crispy }}
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="other" role="tabpanel" aria-labelledby="other-tab">
|
||||
<form id="other-form" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ other_form|crispy }}
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.location.hash) {
|
||||
// Open the tab of the tournament that is present in the hash
|
||||
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(elem => {
|
||||
if ('#' + elem.getAttribute('aria-controls') === document.location.hash.toLowerCase()) {
|
||||
elem.click()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// When a tab is opened, add the tournament name in the hash
|
||||
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(
|
||||
elem => elem.addEventListener(
|
||||
'click', () => document.location.hash = '#' + elem.getAttribute('aria-controls')))
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -29,7 +29,7 @@ from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
|
||||
|
||||
from .forms import AddOrganizerForm, CoachRegistrationForm, HealthSheetForm, \
|
||||
ParentalAuthorizationForm, PaymentForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm, \
|
||||
VaccineSheetForm, VolunteerRegistrationForm
|
||||
VaccineSheetForm, VolunteerRegistrationForm, PaymentAdminForm
|
||||
from .models import ParticipantRegistration, Payment, Registration, StudentRegistration
|
||||
from .tables import RegistrationTable
|
||||
|
||||
@ -443,7 +443,7 @@ class InstructionsTemplateView(AuthorizationTemplateView):
|
||||
|
||||
class PaymentUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Payment
|
||||
form_class = PaymentForm
|
||||
form_class = PaymentAdminForm
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.request.user.is_authenticated or \
|
||||
@ -453,22 +453,33 @@ class PaymentUpdateView(LoginRequiredMixin, UpdateView):
|
||||
return self.handle_no_permission()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
if not self.request.user.registration.is_admin:
|
||||
form.fields["type"].widget.choices = list(form.fields["type"].widget.choices)[:-1]
|
||||
del form.fields["valid"]
|
||||
return form
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data()
|
||||
context['title'] = _("Update payment")
|
||||
context['bank_transfer_form'] = PaymentForm(payment_type='bank_transfer',
|
||||
data=self.request.POST or None,
|
||||
instance=self.object)
|
||||
|
||||
context['scholarship_form'] = PaymentForm(payment_type='scholarship',
|
||||
data=self.request.POST or None,
|
||||
instance=self.object)
|
||||
|
||||
context['other_form'] = PaymentForm(payment_type='other',
|
||||
data=self.request.POST or None,
|
||||
instance=self.object)
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
if not self.request.user.registration.is_admin:
|
||||
form.instance.valid = None
|
||||
form.instance.valid = None
|
||||
old_instance = Payment.objects.get(pk=self.object.pk)
|
||||
if old_instance.receipt:
|
||||
old_instance.receipt.delete()
|
||||
old_instance.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("registration:user_detail", args=(self.object.registrations.first().user.pk,))
|
||||
|
||||
|
||||
class PhotoAuthorizationView(LoginRequiredMixin, View):
|
||||
"""
|
||||
@ -562,9 +573,9 @@ class ParentalAuthorizationView(LoginRequiredMixin, View):
|
||||
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
||||
|
||||
|
||||
class ScholarshipView(LoginRequiredMixin, View):
|
||||
class ReceiptView(LoginRequiredMixin, View):
|
||||
"""
|
||||
Display the sent scholarship paper.
|
||||
Display the sent payment receipt or scholarship notification.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
filename = kwargs["filename"]
|
||||
@ -573,14 +584,14 @@ class ScholarshipView(LoginRequiredMixin, View):
|
||||
raise Http404
|
||||
payment = Payment.objects.get(receipt__endswith=filename)
|
||||
user = request.user
|
||||
if not (user.registration in payment.registrations or user.registration.is_admin):
|
||||
if not (user.registration in payment.registrations.all() or user.registration.is_admin):
|
||||
raise PermissionDenied
|
||||
# Guess mime type of the file
|
||||
mime = Magic(mime=True)
|
||||
mime_type = mime.from_file(path)
|
||||
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
|
||||
# Replace file name
|
||||
true_file_name = _("Scholarship attestation of {user}.{ext}").format(user=str(user.registration), ext=ext)
|
||||
true_file_name = _("Payment receipt of {user}.{ext}").format(user=str(user.registration), ext=ext)
|
||||
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user