Merge pull request #1064 from ae-utbm/makecommand

refactor eboutic command page
This commit is contained in:
thomas girod 2025-04-06 22:36:56 +02:00 committed by GitHub
commit 59e8272c7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 222 additions and 223 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

@ -0,0 +1,91 @@
import { exportToHtml } from "#core:utils/globals";
import {
type BillingInfoSchema,
etransactioninfoFetchEtransactionData,
etransactioninfoPutUserBillingInfo,
} from "#openapi";
enum BillingInfoReqState {
Success = "0",
Failure = "1",
Sending = "2",
}
exportToHtml("BillingInfoReqState", BillingInfoReqState);
document.addEventListener("alpine:init", () => {
Alpine.data("etransactionData", (initialData) => ({
data: initialData,
async fill() {
const button = document.getElementById("bank-submit-button") as HTMLButtonElement;
button.disabled = true;
const res = await etransactioninfoFetchEtransactionData();
if (res.response.ok) {
this.data = res.data;
button.disabled = false;
}
},
}));
Alpine.data("billing_infos", (userId: number) => ({
/** @type {BillingInfoReqState | null} */
reqState: null,
async sendForm() {
this.reqState = BillingInfoReqState.Sending;
const form = document.getElementById("billing_info_form");
const submitButton = document.getElementById(
"bank-submit-button",
) as HTMLButtonElement;
submitButton.disabled = true;
const payload = Object.fromEntries(
Array.from(form.querySelectorAll("input, select"))
.filter((elem: HTMLInputElement) => elem.type !== "submit" && elem.value)
.map((elem: HTMLInputElement) => [elem.name, elem.value]),
);
const res = await etransactioninfoPutUserBillingInfo({
// biome-ignore lint/style/useNamingConvention: API is snake_case
path: { user_id: userId },
body: payload as unknown as BillingInfoSchema,
});
this.reqState = res.response.ok
? BillingInfoReqState.Success
: BillingInfoReqState.Failure;
if (res.response.status === 422) {
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) =>
errors.includes(elem.name),
)) {
elem.setCustomValidity(gettext("Incorrect value"));
elem.reportValidity();
elem.oninput = () => elem.setCustomValidity("");
}
} else if (res.response.ok) {
this.$dispatch("billing-infos-filled");
}
},
getAlertColor() {
if (this.reqState === BillingInfoReqState.Success) {
return "green";
}
if (this.reqState === BillingInfoReqState.Failure) {
return "red";
}
return "";
},
getAlertMessage() {
if (this.reqState === BillingInfoReqState.Success) {
return gettext("Billing info registration success");
}
if (this.reqState === BillingInfoReqState.Failure) {
return gettext("Billing info registration failure");
}
return "";
},
}));
});

View File

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

View File

@ -1,87 +0,0 @@
/**
* @readonly
* @enum {number}
*/
const BillingInfoReqState = {
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
SUCCESS: 1,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
FAILURE: 2,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
SENDING: 3,
};
document.addEventListener("alpine:init", () => {
Alpine.store("billing_inputs", {
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
data: etData,
async fill() {
document.getElementById("bank-submit-button").disabled = true;
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
const res = await fetch(etDataUrl);
if (res.ok) {
this.data = await res.json();
document.getElementById("bank-submit-button").disabled = false;
}
},
});
Alpine.data("billing_infos", () => ({
/** @type {BillingInfoReqState | null} */
reqState: null,
async sendForm() {
this.reqState = BillingInfoReqState.SENDING;
const form = document.getElementById("billing_info_form");
document.getElementById("bank-submit-button").disabled = true;
const payload = Object.fromEntries(
Array.from(form.querySelectorAll("input, select"))
.filter((elem) => elem.type !== "submit" && elem.value)
.map((elem) => [elem.name, elem.value]),
);
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
const res = await fetch(billingInfoUrl, {
method: "PUT",
body: JSON.stringify(payload),
});
this.reqState = res.ok
? BillingInfoReqState.SUCCESS
: BillingInfoReqState.FAILURE;
if (res.status === 422) {
const errors = (await res.json()).detail.flatMap((err) => err.loc);
for (const elem of Array.from(form.querySelectorAll("input")).filter((elem) =>
errors.includes(elem.name),
)) {
elem.setCustomValidity(gettext("Incorrect value"));
elem.reportValidity();
elem.oninput = () => elem.setCustomValidity("");
}
} else if (res.ok) {
Alpine.store("billing_inputs").fill();
}
},
getAlertColor() {
if (this.reqState === BillingInfoReqState.SUCCESS) {
return "green";
}
if (this.reqState === BillingInfoReqState.FAILURE) {
return "red";
}
return "";
},
getAlertMessage() {
if (this.reqState === BillingInfoReqState.SUCCESS) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
return billingInfoSuccessMessage;
}
if (this.reqState === BillingInfoReqState.FAILURE) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
return billingInfoFailureMessage;
}
return "";
},
}));
});

View File

@ -9,7 +9,7 @@
{% endblock %} {% endblock %}
{% block additional_js %} {% block additional_js %}
<script src="{{ static('eboutic/js/makecommand.js') }}" defer></script> <script type="module" src="{{ static('bundled/eboutic/makecommand-index.ts') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -33,7 +33,7 @@
<td>{{ item.product_unit_price }} €</td> <td>{{ item.product_unit_price }} €</td>
</tr> </tr>
{% endfor %} {% endfor %}
<tbody> </tbody>
</table> </table>
<p> <p>
@ -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"