mirror of
https://github.com/ae-utbm/sith.git
synced 2025-04-16 02:50:22 +00:00
Use htmx to fill up billing info
This commit is contained in:
parent
ed52a4f828
commit
5c2f324e13
@ -43,6 +43,7 @@ class BillingInfoForm(forms.ModelForm):
|
|||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
"phone_number": RegionalPhoneNumberWidget,
|
"phone_number": RegionalPhoneNumberWidget,
|
||||||
|
"country": AutoCompleteSelect,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,31 +1,13 @@
|
|||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from ninja_extra import ControllerBase, api_controller, route
|
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 ninja_extra.permissions import IsAuthenticated
|
||||||
from pydantic import NonNegativeInt
|
|
||||||
|
|
||||||
from core.models import User
|
from counter.models import BillingInfo
|
||||||
from counter.models import BillingInfo, Customer
|
|
||||||
from eboutic.models import Basket
|
from eboutic.models import Basket
|
||||||
from eboutic.schemas import BillingInfoSchema
|
|
||||||
|
|
||||||
|
|
||||||
@api_controller("/etransaction", permissions=[IsAuthenticated])
|
@api_controller("/etransaction", permissions=[IsAuthenticated])
|
||||||
class EtransactionInfoController(ControllerBase):
|
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")
|
@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.
|
||||||
@ -35,4 +17,7 @@ class EtransactionInfoController(ControllerBase):
|
|||||||
basket = Basket.from_session(self.context.request.session)
|
basket = Basket.from_session(self.context.request.session)
|
||||||
if basket is None:
|
if basket is None:
|
||||||
raise NotFound
|
raise NotFound
|
||||||
|
try:
|
||||||
return dict(basket.get_e_transaction_data())
|
return dict(basket.get_e_transaction_data())
|
||||||
|
except BillingInfo.DoesNotExist as e:
|
||||||
|
raise NotFound from e
|
||||||
|
@ -16,6 +16,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import hmac
|
import hmac
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
from typing import Any, Self
|
from typing import Any, Self
|
||||||
|
|
||||||
from dict2xml import dict2xml
|
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)]
|
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):
|
class Basket(models.Model):
|
||||||
"""Basket is built when the user connects to an eboutic page."""
|
"""Basket is built when the user connects to an eboutic page."""
|
||||||
|
|
||||||
@ -127,7 +150,11 @@ class Basket(models.Model):
|
|||||||
if not hasattr(user, "customer"):
|
if not hasattr(user, "customer"):
|
||||||
raise Customer.DoesNotExist
|
raise Customer.DoesNotExist
|
||||||
customer = user.customer
|
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
|
raise BillingInfo.DoesNotExist
|
||||||
cart = {
|
cart = {
|
||||||
"shoppingcart": {"total": {"totalQuantity": min(self.items.count(), 99)}}
|
"shoppingcart": {"total": {"totalQuantity": min(self.items.count(), 99)}}
|
||||||
|
@ -1,91 +1,17 @@
|
|||||||
import { exportToHtml } from "#core:utils/globals";
|
import { etransactioninfoFetchEtransactionData } from "#openapi";
|
||||||
import {
|
|
||||||
type BillingInfoSchema,
|
|
||||||
etransactioninfoFetchEtransactionData,
|
|
||||||
etransactioninfoPutUserBillingInfo,
|
|
||||||
} from "#openapi";
|
|
||||||
|
|
||||||
enum BillingInfoReqState {
|
|
||||||
Success = "0",
|
|
||||||
Failure = "1",
|
|
||||||
Sending = "2",
|
|
||||||
}
|
|
||||||
|
|
||||||
exportToHtml("BillingInfoReqState", BillingInfoReqState);
|
|
||||||
|
|
||||||
document.addEventListener("alpine:init", () => {
|
document.addEventListener("alpine:init", () => {
|
||||||
Alpine.data("etransactionData", (initialData) => ({
|
Alpine.data("etransaction", (initialData) => ({
|
||||||
data: initialData,
|
data: initialData,
|
||||||
|
isCbAvailable: Object.keys(initialData).length > 0,
|
||||||
|
|
||||||
async fill() {
|
async fill() {
|
||||||
const button = document.getElementById("bank-submit-button") as HTMLButtonElement;
|
this.isCbAvailable = false;
|
||||||
button.disabled = true;
|
|
||||||
const res = await etransactioninfoFetchEtransactionData();
|
const res = await etransactioninfoFetchEtransactionData();
|
||||||
if (res.response.ok) {
|
if (res.response.ok) {
|
||||||
this.data = res.data;
|
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 "";
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
50
eboutic/templates/eboutic/eboutic_billing_info.jinja
Normal file
50
eboutic/templates/eboutic/eboutic_billing_info.jinja
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<span>
|
||||||
|
<div
|
||||||
|
class="collapse"
|
||||||
|
:class="{'shadow': collapsed}"
|
||||||
|
x-data="{collapsed: !{{ "true" if billing_infos_state == BillingInfoState.VALID and not form.errors else "false" }}}"
|
||||||
|
>
|
||||||
|
<div class="collapse-header clickable" @click="collapsed = !collapsed">
|
||||||
|
<span class="collapse-header-text">
|
||||||
|
{% trans %}Billing information{% endtrans %}
|
||||||
|
</span>
|
||||||
|
<span class="collapse-header-icon" :class="{'reverse': collapsed}">
|
||||||
|
<i class="fa fa-caret-down"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
class="collapse-body"
|
||||||
|
hx-trigger="submit"
|
||||||
|
hx-post="{{ action }}"
|
||||||
|
hx-swap="outerHTML settle:100"
|
||||||
|
hx-target="closest span"
|
||||||
|
x-show="collapsed"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p() }}
|
||||||
|
<input
|
||||||
|
type="submit" class="btn btn-blue clickable"
|
||||||
|
value="{% trans %}Validate{% endtrans %}"
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
{% if billing_infos_state == BillingInfoState.EMPTY %}
|
||||||
|
<div class="alert alert-yellow">
|
||||||
|
{% trans trimmed %}
|
||||||
|
You must fill your billing infos if you want to pay with your credit card
|
||||||
|
{% endtrans %}
|
||||||
|
</div>
|
||||||
|
{% elif billing_infos_state == BillingInfoState.MISSING_PHONE_NUMBER %}
|
||||||
|
<div class="alert alert-yellow">
|
||||||
|
{% trans trimmed %}
|
||||||
|
The Crédit Agricole changed its policy related to the billing
|
||||||
|
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
|
||||||
|
to the data you already provided.
|
||||||
|
{% endtrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
@ -15,7 +15,11 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h3>{% trans %}Eboutic{% endtrans %}</h3>
|
<h3>{% trans %}Eboutic{% endtrans %}</h3>
|
||||||
|
|
||||||
<div>
|
<script type="text/javascript">
|
||||||
|
let billingInfos = {{ billing_infos|tojson }};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div x-data="etransaction(billingInfos)">
|
||||||
<p>{% trans %}Basket: {% endtrans %}</p>
|
<p>{% trans %}Basket: {% endtrans %}</p>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -53,80 +57,21 @@
|
|||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
|
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
|
||||||
<div
|
<div @htmx:after-request="fill">
|
||||||
class="collapse"
|
{{ billing_infos_form }}
|
||||||
:class="{'shadow': collapsed}"
|
|
||||||
x-data="{collapsed: !{{ "true" if billing_infos else "false" }}}"
|
|
||||||
x-cloak
|
|
||||||
>
|
|
||||||
<div class="collapse-header clickable" @click="collapsed = !collapsed">
|
|
||||||
<span class="collapse-header-text">
|
|
||||||
{% trans %}Billing information{% endtrans %}
|
|
||||||
</span>
|
|
||||||
<span class="collapse-header-icon" :class="{'reverse': collapsed}">
|
|
||||||
<i class="fa fa-caret-down"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<form
|
|
||||||
class="collapse-body"
|
|
||||||
id="billing_info_form"
|
|
||||||
x-data="billing_infos({{ user.id }})"
|
|
||||||
x-show="collapsed"
|
|
||||||
x-transition.scale.origin.top
|
|
||||||
@submit.prevent="await sendForm()"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ billing_form }}
|
|
||||||
<br />
|
|
||||||
<div
|
|
||||||
x-show="[BillingInfoReqState.Success, BillingInfoReqState.Failure].includes(reqState)"
|
|
||||||
class="alert"
|
|
||||||
:class="'alert-' + getAlertColor()"
|
|
||||||
x-transition
|
|
||||||
>
|
|
||||||
<div class="alert-main" x-text="getAlertMessage()"></div>
|
|
||||||
<div class="clickable" @click="reqState = null">
|
|
||||||
<i class="fa fa-close"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="submit" class="btn btn-blue clickable"
|
|
||||||
value="{% trans %}Validate{% endtrans %}"
|
|
||||||
:disabled="reqState === BillingInfoReqState.Sending"
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
{% if billing_infos_state == BillingInfoState.EMPTY %}
|
|
||||||
<div class="alert alert-yellow">
|
|
||||||
{% trans trimmed %}
|
|
||||||
You must fill your billing infos if you want to pay with your credit card
|
|
||||||
{% endtrans %}
|
|
||||||
</div>
|
|
||||||
{% elif billing_infos_state == BillingInfoState.MISSING_PHONE_NUMBER %}
|
|
||||||
<div class="alert alert-yellow">
|
|
||||||
{% trans trimmed %}
|
|
||||||
The Crédit Agricole changed its policy related to the billing
|
|
||||||
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
|
|
||||||
to the data you already provided.
|
|
||||||
{% endtrans %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<form
|
<form
|
||||||
method="post"
|
method="post"
|
||||||
action="{{ settings.SITH_EBOUTIC_ET_URL }}"
|
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">
|
<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
|
||||||
|
x-cloak
|
||||||
type="submit"
|
type="submit"
|
||||||
id="bank-submit-button"
|
id="bank-submit-button"
|
||||||
{% if billing_infos_state != BillingInfoState.VALID %}disabled="disabled"{% endif %}
|
:disabled="!isCbAvailable"
|
||||||
value="{% trans %}Pay with credit card{% endtrans %}"
|
value="{% trans %}Pay with credit card{% endtrans %}"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
@ -144,15 +89,3 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block script %}
|
|
||||||
<script>
|
|
||||||
{% if billing_infos -%}
|
|
||||||
const initialEtData = {{ billing_infos|safe }}
|
|
||||||
{%- else -%}
|
|
||||||
const initialEtData = {}
|
|
||||||
{%- endif %}
|
|
||||||
</script>
|
|
||||||
{{ super() }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# details.
|
# details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU General Public License along with
|
||||||
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
|
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
@ -26,9 +26,9 @@ from django.urls import path, register_converter
|
|||||||
|
|
||||||
from eboutic.converters import PaymentResultConverter
|
from eboutic.converters import PaymentResultConverter
|
||||||
from eboutic.views import (
|
from eboutic.views import (
|
||||||
|
BillingInfoFormFragment,
|
||||||
EbouticCommand,
|
EbouticCommand,
|
||||||
EtransactionAutoAnswer,
|
EtransactionAutoAnswer,
|
||||||
e_transaction_data,
|
|
||||||
eboutic_main,
|
eboutic_main,
|
||||||
pay_with_sith,
|
pay_with_sith,
|
||||||
payment_result,
|
payment_result,
|
||||||
@ -40,9 +40,9 @@ urlpatterns = [
|
|||||||
# Subscription views
|
# Subscription views
|
||||||
path("", eboutic_main, name="main"),
|
path("", eboutic_main, name="main"),
|
||||||
path("command/", EbouticCommand.as_view(), name="command"),
|
path("command/", EbouticCommand.as_view(), name="command"),
|
||||||
|
path("billing-infos/", BillingInfoFormFragment.as_view(), name="billing_infos"),
|
||||||
path("pay/sith/", pay_with_sith, name="pay_with_sith"),
|
path("pay/sith/", pay_with_sith, name="pay_with_sith"),
|
||||||
path("pay/<res:result>/", payment_result, name="payment_result"),
|
path("pay/<res:result>/", payment_result, name="payment_result"),
|
||||||
path("et_data/", e_transaction_data, name="et_data"),
|
|
||||||
path(
|
path(
|
||||||
"et_autoanswer",
|
"et_autoanswer",
|
||||||
EtransactionAutoAnswer.as_view(),
|
EtransactionAutoAnswer.as_view(),
|
||||||
|
@ -13,10 +13,11 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import contextlib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
@ -33,16 +34,19 @@ 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
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.http import require_GET, require_POST
|
from django.views.decorators.http import require_GET, require_POST
|
||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, UpdateView, View
|
||||||
|
|
||||||
|
from core.views.mixins import FragmentMixin, UseFragmentsMixin
|
||||||
from counter.forms import BillingInfoForm
|
from counter.forms import BillingInfoForm
|
||||||
from counter.models import Counter, Customer, Product
|
from counter.models import BillingInfo, Counter, Customer, Product
|
||||||
from eboutic.forms import BasketForm
|
from eboutic.forms import BasketForm
|
||||||
from eboutic.models import (
|
from eboutic.models import (
|
||||||
Basket,
|
Basket,
|
||||||
BasketItem,
|
BasketItem,
|
||||||
|
BillingInfoState,
|
||||||
Invoice,
|
Invoice,
|
||||||
InvoiceItem,
|
InvoiceItem,
|
||||||
get_eboutic_products,
|
get_eboutic_products,
|
||||||
@ -88,15 +92,38 @@ def payment_result(request, result: str) -> HttpResponse:
|
|||||||
return render(request, "eboutic/eboutic_payment_result.jinja", context)
|
return render(request, "eboutic/eboutic_payment_result.jinja", context)
|
||||||
|
|
||||||
|
|
||||||
class BillingInfoState(Enum):
|
class BillingInfoFormFragment(LoginRequiredMixin, FragmentMixin, UpdateView):
|
||||||
VALID = 1
|
"""Update billing info"""
|
||||||
EMPTY = 2
|
|
||||||
MISSING_PHONE_NUMBER = 3
|
model = BillingInfo
|
||||||
|
form_class = BillingInfoForm
|
||||||
|
template_name = "eboutic/eboutic_billing_info.jinja"
|
||||||
|
|
||||||
|
def get_object(self, *args, **kwargs):
|
||||||
|
customer, _ = Customer.get_or_create(self.request.user)
|
||||||
|
if not hasattr(customer, "billing_infos"):
|
||||||
|
customer.billing_infos = BillingInfo()
|
||||||
|
return customer.billing_infos
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
if not hasattr(self, "object"):
|
||||||
|
self.object = self.get_object()
|
||||||
|
kwargs = super().get_context_data(**kwargs)
|
||||||
|
kwargs["action"] = reverse("eboutic:billing_infos")
|
||||||
|
kwargs["BillingInfoState"] = BillingInfoState
|
||||||
|
kwargs["billing_infos_state"] = BillingInfoState.from_model(self.object)
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_success_url(self, **kwargs):
|
||||||
|
return self.request.path
|
||||||
|
|
||||||
|
|
||||||
class EbouticCommand(LoginRequiredMixin, TemplateView):
|
class EbouticCommand(LoginRequiredMixin, UseFragmentsMixin, TemplateView):
|
||||||
template_name = "eboutic/eboutic_makecommand.jinja"
|
template_name = "eboutic/eboutic_makecommand.jinja"
|
||||||
basket: Basket
|
basket: Basket
|
||||||
|
fragments = {
|
||||||
|
"billing_infos_form": BillingInfoFormFragment,
|
||||||
|
}
|
||||||
|
|
||||||
@method_decorator(login_required)
|
@method_decorator(login_required)
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
@ -134,6 +161,7 @@ class EbouticCommand(LoginRequiredMixin, TemplateView):
|
|||||||
return super().get(request)
|
return super().get(request)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
kwargs = super().get_context_data(**kwargs)
|
||||||
default_billing_info = None
|
default_billing_info = None
|
||||||
if hasattr(self.request.user, "customer"):
|
if hasattr(self.request.user, "customer"):
|
||||||
customer = self.request.user.customer
|
customer = self.request.user.customer
|
||||||
@ -142,34 +170,14 @@ class EbouticCommand(LoginRequiredMixin, TemplateView):
|
|||||||
default_billing_info = customer.billing_infos
|
default_billing_info = customer.billing_infos
|
||||||
else:
|
else:
|
||||||
kwargs["customer_amount"] = None
|
kwargs["customer_amount"] = None
|
||||||
# make the enum available in the template
|
|
||||||
kwargs["BillingInfoState"] = BillingInfoState
|
|
||||||
if default_billing_info is None:
|
|
||||||
kwargs["billing_infos_state"] = BillingInfoState.EMPTY
|
|
||||||
elif default_billing_info.phone_number is None:
|
|
||||||
kwargs["billing_infos_state"] = BillingInfoState.MISSING_PHONE_NUMBER
|
|
||||||
else:
|
|
||||||
kwargs["billing_infos_state"] = BillingInfoState.VALID
|
|
||||||
if kwargs["billing_infos_state"] == BillingInfoState.VALID:
|
|
||||||
# the user has already filled all of its billing_infos, thus we can
|
|
||||||
# get it without expecting an error
|
|
||||||
kwargs["billing_infos"] = dict(self.basket.get_e_transaction_data())
|
|
||||||
kwargs["basket"] = self.basket
|
kwargs["basket"] = self.basket
|
||||||
kwargs["billing_form"] = BillingInfoForm(instance=default_billing_info)
|
kwargs["billing_form"] = BillingInfoForm(instance=default_billing_info)
|
||||||
|
kwargs["billing_infos"] = {}
|
||||||
|
with contextlib.suppress(BillingInfo.DoesNotExist):
|
||||||
|
kwargs["billing_infos"] = dict(self.basket.get_e_transaction_data())
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@require_GET
|
|
||||||
def e_transaction_data(request):
|
|
||||||
basket = Basket.from_session(request.session)
|
|
||||||
if basket is None:
|
|
||||||
return HttpResponse(status=404, content=json.dumps({"data": []}))
|
|
||||||
data = basket.get_e_transaction_data()
|
|
||||||
data = {"data": [{"key": key, "value": val} for key, val in data]}
|
|
||||||
return HttpResponse(status=200, content=json.dumps(data))
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_POST
|
@require_POST
|
||||||
def pay_with_sith(request):
|
def pay_with_sith(request):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user