diff --git a/counter/forms.py b/counter/forms.py
index 27dc74d7..10109b4d 100644
--- a/counter/forms.py
+++ b/counter/forms.py
@@ -43,6 +43,7 @@ class BillingInfoForm(forms.ModelForm):
]
widgets = {
"phone_number": RegionalPhoneNumberWidget,
+ "country": AutoCompleteSelect,
}
diff --git a/eboutic/api.py b/eboutic/api.py
index 797adf20..3c2a1dc2 100644
--- a/eboutic/api.py
+++ b/eboutic/api.py
@@ -1,31 +1,13 @@
-from django.shortcuts import get_object_or_404
from ninja_extra import ControllerBase, api_controller, route
-from ninja_extra.exceptions import NotFound, PermissionDenied
+from ninja_extra.exceptions import NotFound
from ninja_extra.permissions import IsAuthenticated
-from pydantic import NonNegativeInt
-from core.models import User
-from counter.models import BillingInfo, Customer
+from counter.models import BillingInfo
from eboutic.models import Basket
-from eboutic.schemas import BillingInfoSchema
@api_controller("/etransaction", permissions=[IsAuthenticated])
class EtransactionInfoController(ControllerBase):
- @route.put("/billing-info/{user_id}", url_name="put_billing_info")
- def put_user_billing_info(self, user_id: NonNegativeInt, info: BillingInfoSchema):
- """Update or create the billing info of this user."""
- if user_id == self.context.request.user.id:
- user = self.context.request.user
- elif self.context.request.user.is_root:
- user = get_object_or_404(User, pk=user_id)
- else:
- raise PermissionDenied
- customer, _ = Customer.get_or_create(user)
- BillingInfo.objects.update_or_create(
- customer=customer, defaults=info.model_dump(exclude_none=True)
- )
-
@route.get("/data", url_name="etransaction_data")
def fetch_etransaction_data(self):
"""Generate the data to pay an eboutic command with paybox.
@@ -35,4 +17,7 @@ class EtransactionInfoController(ControllerBase):
basket = Basket.from_session(self.context.request.session)
if basket is None:
raise NotFound
- return dict(basket.get_e_transaction_data())
+ try:
+ return dict(basket.get_e_transaction_data())
+ except BillingInfo.DoesNotExist as e:
+ raise NotFound from e
diff --git a/eboutic/models.py b/eboutic/models.py
index cd55d0ae..60dddb91 100644
--- a/eboutic/models.py
+++ b/eboutic/models.py
@@ -16,6 +16,7 @@ from __future__ import annotations
import hmac
from datetime import datetime
+from enum import Enum
from typing import Any, Self
from dict2xml import dict2xml
@@ -44,6 +45,28 @@ def get_eboutic_products(user: User) -> list[Product]:
return [p for p in products if p.can_be_sold_to(user)]
+class BillingInfoState(Enum):
+ VALID = 1
+ EMPTY = 2
+ MISSING_PHONE_NUMBER = 3
+
+ @classmethod
+ def from_model(cls, info: BillingInfo) -> BillingInfoState:
+ for attr in [
+ "first_name",
+ "last_name",
+ "address_1",
+ "zip_code",
+ "city",
+ "country",
+ ]:
+ if getattr(info, attr) == "":
+ return cls.EMPTY
+ if info.phone_number is None:
+ return cls.MISSING_PHONE_NUMBER
+ return cls.VALID
+
+
class Basket(models.Model):
"""Basket is built when the user connects to an eboutic page."""
@@ -127,7 +150,11 @@ class Basket(models.Model):
if not hasattr(user, "customer"):
raise Customer.DoesNotExist
customer = user.customer
- if not hasattr(user.customer, "billing_infos"):
+ if (
+ not hasattr(user.customer, "billing_infos")
+ or BillingInfoState.from_model(user.customer.billing_infos)
+ != BillingInfoState.VALID
+ ):
raise BillingInfo.DoesNotExist
cart = {
"shoppingcart": {"total": {"totalQuantity": min(self.items.count(), 99)}}
diff --git a/eboutic/static/bundled/eboutic/makecommand-index.ts b/eboutic/static/bundled/eboutic/makecommand-index.ts
index c1e4b52f..47e81cb6 100644
--- a/eboutic/static/bundled/eboutic/makecommand-index.ts
+++ b/eboutic/static/bundled/eboutic/makecommand-index.ts
@@ -1,91 +1,17 @@
-import { exportToHtml } from "#core:utils/globals";
-import {
- type BillingInfoSchema,
- etransactioninfoFetchEtransactionData,
- etransactioninfoPutUserBillingInfo,
-} from "#openapi";
-
-enum BillingInfoReqState {
- Success = "0",
- Failure = "1",
- Sending = "2",
-}
-
-exportToHtml("BillingInfoReqState", BillingInfoReqState);
+import { etransactioninfoFetchEtransactionData } from "#openapi";
document.addEventListener("alpine:init", () => {
- Alpine.data("etransactionData", (initialData) => ({
+ Alpine.data("etransaction", (initialData) => ({
data: initialData,
+ isCbAvailable: Object.keys(initialData).length > 0,
async fill() {
- const button = document.getElementById("bank-submit-button") as HTMLButtonElement;
- button.disabled = true;
+ this.isCbAvailable = false;
const res = await etransactioninfoFetchEtransactionData();
if (res.response.ok) {
this.data = res.data;
- button.disabled = false;
+ this.isCbAvailable = true;
}
},
}));
-
- 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 "";
- },
- }));
});
diff --git a/eboutic/templates/eboutic/eboutic_billing_info.jinja b/eboutic/templates/eboutic/eboutic_billing_info.jinja
new file mode 100644
index 00000000..b498e234
--- /dev/null
+++ b/eboutic/templates/eboutic/eboutic_billing_info.jinja
@@ -0,0 +1,50 @@
+
+
+
+ {% if billing_infos_state == BillingInfoState.EMPTY %}
+
{% trans %}Basket: {% endtrans %}