refactor eboutic command page

This commit is contained in:
Thomas Girod 2025-04-06 16:25:55 +02:00
parent e35c1d1928
commit d03c425a17
8 changed files with 90 additions and 92 deletions

View File

@ -9,7 +9,7 @@ repos:
# Run the formatter. # Run the formatter.
- id: ruff-format - id: ruff-format
- repo: https://github.com/biomejs/pre-commit - repo: https://github.com/biomejs/pre-commit
rev: "v0.1.0" # Use the sha / tag you want to point at rev: v0.6.1
hooks: hooks:
- id: biome-check - id: biome-check
additional_dependencies: ["@biomejs/biome@1.9.4"] additional_dependencies: ["@biomejs/biome@1.9.4"]

View File

@ -26,7 +26,7 @@ class EtransactionInfoController(ControllerBase):
customer=customer, defaults=info.model_dump(exclude_none=True) customer=customer, defaults=info.model_dump(exclude_none=True)
) )
@route.get("/data", url_name="etransaction_data", include_in_schema=False) @route.get("/data", url_name="etransaction_data")
def fetch_etransaction_data(self): def fetch_etransaction_data(self):
"""Generate the data to pay an eboutic command with paybox. """Generate the data to pay an eboutic command with paybox.

View File

@ -1,56 +1,61 @@
/** import { exportToHtml } from "#core:utils/globals";
* @readonly import {
* @enum {number} type BillingInfoSchema,
*/ etransactioninfoFetchEtransactionData,
const BillingInfoReqState = { etransactioninfoPutUserBillingInfo,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum } from "#openapi";
SUCCESS: 1,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum enum BillingInfoReqState {
FAILURE: 2, Success = "0",
// biome-ignore lint/style/useNamingConvention: this feels more like an enum Failure = "1",
SENDING: 3, Sending = "2",
}; }
exportToHtml("BillingInfoReqState", BillingInfoReqState);
document.addEventListener("alpine:init", () => { document.addEventListener("alpine:init", () => {
Alpine.store("billing_inputs", { Alpine.data("etransactionData", (initialData) => ({
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja data: initialData,
data: etData,
async fill() { async fill() {
const button = document.getElementById("bank-submit-button") as HTMLButtonElement; const button = document.getElementById("bank-submit-button") as HTMLButtonElement;
button.disabled = true; button.disabled = true;
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja const res = await etransactioninfoFetchEtransactionData();
const res = await fetch(etDataUrl); if (res.response.ok) {
if (res.ok) { this.data = res.data;
this.data = await res.json();
button.disabled = false; button.disabled = false;
} }
}, },
}); }));
Alpine.data("billing_infos", () => ({ Alpine.data("billing_infos", (userId: number) => ({
/** @type {BillingInfoReqState | null} */ /** @type {BillingInfoReqState | null} */
reqState: null, reqState: null,
async sendForm() { async sendForm() {
this.reqState = BillingInfoReqState.SENDING; this.reqState = BillingInfoReqState.Sending;
const form = document.getElementById("billing_info_form"); const form = document.getElementById("billing_info_form");
document.getElementById("bank-submit-button").disabled = true; const submitButton = document.getElementById(
"bank-submit-button",
) as HTMLButtonElement;
submitButton.disabled = true;
const payload = Object.fromEntries( const payload = Object.fromEntries(
Array.from(form.querySelectorAll("input, select")) Array.from(form.querySelectorAll("input, select"))
.filter((elem) => elem.type !== "submit" && elem.value) .filter((elem: HTMLInputElement) => elem.type !== "submit" && elem.value)
.map((elem) => [elem.name, elem.value]), .map((elem: HTMLInputElement) => [elem.name, elem.value]),
); );
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja const res = await etransactioninfoPutUserBillingInfo({
const res = await fetch(billingInfoUrl, { // biome-ignore lint/style/useNamingConvention: API is snake_case
method: "PUT", path: { user_id: userId },
body: JSON.stringify(payload), body: payload as unknown as BillingInfoSchema,
}); });
this.reqState = res.ok this.reqState = res.response.ok
? BillingInfoReqState.SUCCESS ? BillingInfoReqState.Success
: BillingInfoReqState.FAILURE; : BillingInfoReqState.Failure;
if (res.status === 422) { if (res.response.status === 422) {
const errors = (await res.json()).detail.flatMap((err) => err.loc); const errors = await res.response
.json()
.detail.flatMap((err: Record<"loc", string>) => err.loc);
for (const elem of Array.from(form.querySelectorAll("input")).filter((elem) => for (const elem of Array.from(form.querySelectorAll("input")).filter((elem) =>
errors.includes(elem.name), errors.includes(elem.name),
)) { )) {
@ -58,29 +63,27 @@ document.addEventListener("alpine:init", () => {
elem.reportValidity(); elem.reportValidity();
elem.oninput = () => elem.setCustomValidity(""); elem.oninput = () => elem.setCustomValidity("");
} }
} else if (res.ok) { } else if (res.response.ok) {
Alpine.store("billing_inputs").fill(); this.$dispatch("billing-infos-filled");
} }
}, },
getAlertColor() { getAlertColor() {
if (this.reqState === BillingInfoReqState.SUCCESS) { if (this.reqState === BillingInfoReqState.Success) {
return "green"; return "green";
} }
if (this.reqState === BillingInfoReqState.FAILURE) { if (this.reqState === BillingInfoReqState.Failure) {
return "red"; return "red";
} }
return ""; return "";
}, },
getAlertMessage() { getAlertMessage() {
if (this.reqState === BillingInfoReqState.SUCCESS) { if (this.reqState === BillingInfoReqState.Success) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja return gettext("Billing info registration success");
return billingInfoSuccessMessage;
} }
if (this.reqState === BillingInfoReqState.FAILURE) { if (this.reqState === BillingInfoReqState.Failure) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja return gettext("Billing info registration failure");
return billingInfoFailureMessage;
} }
return ""; return "";
}, },

View File

@ -158,4 +158,3 @@
flex-direction: column; flex-direction: column;
} }
} }

View File

@ -9,7 +9,7 @@
{% endblock %} {% endblock %}
{% block additional_js %} {% block additional_js %}
<script src="{{ static('bundled/eboutic/makecommand-index.ts') }}" defer></script> <script type="module" src="{{ static('bundled/eboutic/makecommand-index.ts') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -56,7 +56,7 @@
<div <div
class="collapse" class="collapse"
:class="{'shadow': collapsed}" :class="{'shadow': collapsed}"
x-data="{collapsed: !billingInfoExist}" x-data="{collapsed: !{{ "true" if billing_infos else "false" }}}"
x-cloak x-cloak
> >
<div class="collapse-header clickable" @click="collapsed = !collapsed"> <div class="collapse-header clickable" @click="collapsed = !collapsed">
@ -70,7 +70,7 @@
<form <form
class="collapse-body" class="collapse-body"
id="billing_info_form" id="billing_info_form"
x-data="billing_infos" x-data="billing_infos({{ user.id }})"
x-show="collapsed" x-show="collapsed"
x-transition.scale.origin.top x-transition.scale.origin.top
@submit.prevent="await sendForm()" @submit.prevent="await sendForm()"
@ -79,7 +79,7 @@
{{ billing_form }} {{ billing_form }}
<br /> <br />
<div <div
x-show="[BillingInfoReqState.SUCCESS, BillingInfoReqState.FAILURE].includes(reqState)" x-show="[BillingInfoReqState.Success, BillingInfoReqState.Failure].includes(reqState)"
class="alert" class="alert"
:class="'alert-' + getAlertColor()" :class="'alert-' + getAlertColor()"
x-transition x-transition
@ -92,19 +92,20 @@
<input <input
type="submit" class="btn btn-blue clickable" type="submit" class="btn btn-blue clickable"
value="{% trans %}Validate{% endtrans %}" value="{% trans %}Validate{% endtrans %}"
:disabled="reqState === BillingInfoReqState.SENDING" :disabled="reqState === BillingInfoReqState.Sending"
> >
</form> </form>
</div> </div>
<br> <br>
{% if billing_infos_state == BillingInfoState.EMPTY %} {% if billing_infos_state == BillingInfoState.EMPTY %}
<div class="alert alert-yellow"> <div class="alert alert-yellow">
{% trans %}You must fill your billing infos if you want to pay with your credit {% trans trimmed %}
card{% endtrans %} You must fill your billing infos if you want to pay with your credit card
{% endtrans %}
</div> </div>
{% elif billing_infos_state == BillingInfoState.MISSING_PHONE_NUMBER %} {% elif billing_infos_state == BillingInfoState.MISSING_PHONE_NUMBER %}
<div class="alert alert-yellow"> <div class="alert alert-yellow">
{% trans %} {% trans trimmed %}
The Crédit Agricole changed its policy related to the billing The Crédit Agricole changed its policy related to the billing
information that must be provided in order to pay with a credit card. information that must be provided in order to pay with a credit card.
If you want to pay with your credit card, you must add a phone number If you want to pay with your credit card, you must add a phone number
@ -112,8 +113,14 @@
{% endtrans %} {% endtrans %}
</div> </div>
{% endif %} {% endif %}
<form method="post" action="{{ settings.SITH_EBOUTIC_ET_URL }}" name="bank-pay-form"> <form
<template x-data x-for="[key, value] in Object.entries($store.billing_inputs.data)"> method="post"
action="{{ settings.SITH_EBOUTIC_ET_URL }}"
name="bank-pay-form"
x-data="etransactionData(initialEtData)"
@billing-infos-filled.window="await fill()"
>
<template x-for="[key, value] in Object.entries(data)" :key="key">
<input type="hidden" :name="key" :value="value"> <input type="hidden" :name="key" :value="value">
</template> </template>
<input <input
@ -140,17 +147,11 @@
{% block script %} {% block script %}
<script> <script>
const billingInfoUrl = '{{ url("api:put_billing_info", user_id=request.user.id) }}'; {% if billing_infos -%}
const etDataUrl = '{{ url("api:etransaction_data") }}'; const initialEtData = {{ billing_infos|safe }}
const billingInfoExist = {{ "true" if billing_infos else "false" }}; {%- else -%}
const billingInfoSuccessMessage = "{% trans %}Billing info registration success{% endtrans %}"; const initialEtData = {}
const billingInfoFailureMessage = "{% trans %}Billing info registration failure{% endtrans %}"; {%- endif %}
{% if billing_infos %}
const etData = {{ billing_infos|safe }}
{% else %}
const etData = {}
{% endif %}
</script> </script>
{{ super() }} {{ super() }}
{% endblock %} {% endblock %}

View File

@ -26,7 +26,9 @@ from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.primitives.serialization import load_pem_public_key
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import (
LoginRequiredMixin,
)
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.db import DatabaseError, transaction from django.db import DatabaseError, transaction
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse

View File

@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-06 16:58+0200\n" "POT-Creation-Date: 2025-04-06 15:54+0200\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -3831,25 +3831,18 @@ msgstr "Informations de facturation"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja #: eboutic/templates/eboutic/eboutic_makecommand.jinja
msgid "" msgid ""
"You must fill your billing infos if you want to pay with your credit\n" "You must fill your billing infos if you want to pay with your credit card"
" card"
msgstr "" msgstr ""
"Vous devez renseigner vos coordonnées de facturation si vous voulez payer " "Vous devez renseigner vos coordonnées de facturation si vous voulez payer "
"par carte bancaire" "par carte bancaire"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja #: eboutic/templates/eboutic/eboutic_makecommand.jinja
msgid "" msgid ""
"\n" "The Crédit Agricole changed its policy related to the billing information "
" The Crédit Agricole changed its policy related to the " "that must be provided in order to pay with a credit card. If you want to pay "
"billing\n" "with your credit card, you must add a phone number to the data you already "
" information that must be provided in order to pay with a " "provided."
"credit card.\n"
" If you want to pay with your credit card, you must add a "
"phone number\n"
" to the data you already provided.\n"
" "
msgstr "" msgstr ""
"\n"
"Le Crédit Agricole a changé sa politique relative aux informations à " "Le Crédit Agricole a changé sa politique relative aux informations à "
"fournir pour effectuer un paiement par carte bancaire. De ce fait, si vous " "fournir pour effectuer un paiement par carte bancaire. De ce fait, si vous "
"souhaitez payer par carte, vous devez rajouter un numéro de téléphone aux " "souhaitez payer par carte, vous devez rajouter un numéro de téléphone aux "
@ -3876,14 +3869,6 @@ msgstr ""
msgid "Pay with Sith account" msgid "Pay with Sith account"
msgstr "Payer avec un compte AE" msgstr "Payer avec un compte AE"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja
msgid "Billing info registration success"
msgstr "Informations de facturation enregistrées"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja
msgid "Billing info registration failure"
msgstr "Echec de l'enregistrement des informations de facturation."
#: eboutic/templates/eboutic/eboutic_payment_result.jinja #: eboutic/templates/eboutic/eboutic_payment_result.jinja
msgid "Payment successful" msgid "Payment successful"
msgstr "Le paiement a été effectué" msgstr "Le paiement a été effectué"

View File

@ -7,7 +7,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-28 13:52+0100\n" "POT-Creation-Date: 2025-04-06 15:47+0200\n"
"PO-Revision-Date: 2024-09-17 11:54+0200\n" "PO-Revision-Date: 2024-09-17 11:54+0200\n"
"Last-Translator: Sli <antoine@bartuccio.fr>\n" "Last-Translator: Sli <antoine@bartuccio.fr>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -201,10 +201,18 @@ msgstr "Types de produits réordonnés !"
msgid "Product type reorganisation failed with status code : %d" msgid "Product type reorganisation failed with status code : %d"
msgstr "La réorganisation des types de produit a échoué avec le code : %d" msgstr "La réorganisation des types de produit a échoué avec le code : %d"
#: eboutic/static/eboutic/js/makecommand.js #: eboutic/static/bundled/eboutic/makecommand-index.ts
msgid "Incorrect value" msgid "Incorrect value"
msgstr "Valeur incorrecte" msgstr "Valeur incorrecte"
#: eboutic/static/bundled/eboutic/makecommand-index.ts
msgid "Billing info registration success"
msgstr "Informations de facturation enregistrées"
#: eboutic/static/bundled/eboutic/makecommand-index.ts
msgid "Billing info registration failure"
msgstr "Echec de l'enregistrement des informations de facturation."
#: sas/static/bundled/sas/pictures-download-index.ts #: sas/static/bundled/sas/pictures-download-index.ts
msgid "pictures.%(extension)s" msgid "pictures.%(extension)s"
msgstr "photos.%(extension)s" msgstr "photos.%(extension)s"