basket bug fix
This commit is contained in:
@@ -347,6 +347,8 @@ cart-total = Total
|
|||||||
cart-checkout = Proceed to checkout
|
cart-checkout = Proceed to checkout
|
||||||
cart-remove = Remove
|
cart-remove = Remove
|
||||||
cart-remove-confirm = Remove this item from the cart?
|
cart-remove-confirm = Remove this item from the cart?
|
||||||
|
cart-out-of-stock = Out of stock
|
||||||
|
cart-remove-unavailable = Some items in your cart are out of stock. Remove them to continue to checkout.
|
||||||
cart-update = Update
|
cart-update = Update
|
||||||
cart-continue = Continue shopping
|
cart-continue = Continue shopping
|
||||||
checkout-title = Checkout
|
checkout-title = Checkout
|
||||||
|
|||||||
@@ -347,6 +347,8 @@ cart-total = Spolu
|
|||||||
cart-checkout = Pokračovať k pokladni
|
cart-checkout = Pokračovať k pokladni
|
||||||
cart-remove = Odstrániť
|
cart-remove = Odstrániť
|
||||||
cart-remove-confirm = Odstrániť túto položku z košíka?
|
cart-remove-confirm = Odstrániť túto položku z košíka?
|
||||||
|
cart-out-of-stock = Vypredané
|
||||||
|
cart-remove-unavailable = Niektoré položky v košíku sú vypredané. Pred pokračovaním na pokladňu ich odstráňte.
|
||||||
cart-update = Aktualizovať
|
cart-update = Aktualizovať
|
||||||
cart-continue = Pokračovať v nákupe
|
cart-continue = Pokračovať v nákupe
|
||||||
checkout-title = Pokladňa
|
checkout-title = Pokladňa
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -20,6 +20,7 @@
|
|||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<a href="/shop/{{ item.slug }}" class="font-medium text-on-surface-strong hover:text-primary dark:text-on-surface-dark-strong dark:hover:text-primary-dark">{{ item.name }}</a>
|
<a href="/shop/{{ item.slug }}" class="font-medium text-on-surface-strong hover:text-primary dark:text-on-surface-dark-strong dark:hover:text-primary-dark">{{ item.name }}</a>
|
||||||
{% if item.variant_label %}<span class="block text-xs text-on-surface/60 dark:text-on-surface-dark/60">{{ item.variant_label }}</span>{% endif %}
|
{% if item.variant_label %}<span class="block text-xs text-on-surface/60 dark:text-on-surface-dark/60">{{ item.variant_label }}</span>{% endif %}
|
||||||
|
{% if item.out_of_stock %}<span class="mt-1 inline-flex items-center rounded-radius bg-danger/10 px-2 py-0.5 text-xs font-medium text-danger">{{ t(key="cart-out-of-stock", lang=lang | default(value='sk')) }}</span>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 tabular-nums">
|
<td class="px-4 py-3 tabular-nums">
|
||||||
{% if item.on_sale %}
|
{% if item.on_sale %}
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
class="w-20 rounded-radius border border-outline bg-surface-alt px-2 py-1 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark">
|
class="w-20 rounded-radius border border-outline bg-surface-alt px-2 py-1 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark">
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-right font-medium tabular-nums">{{ item.line_total }} {{ currency_symbol }}</td>
|
<td class="px-4 py-3 text-right font-medium tabular-nums">{% if item.out_of_stock %}<span class="text-on-surface/40 dark:text-on-surface-dark/40">—</span>{% else %}{{ item.line_total }} {{ currency_symbol }}{% endif %}</td>
|
||||||
<td class="px-4 py-3 text-right">
|
<td class="px-4 py-3 text-right">
|
||||||
<form method="post" action="/cart/remove"
|
<form method="post" action="/cart/remove"
|
||||||
hx-post="/cart/remove" hx-target="#cart-body" hx-swap="innerHTML">
|
hx-post="/cart/remove" hx-target="#cart-body" hx-swap="innerHTML">
|
||||||
@@ -70,9 +71,17 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if has_unavailable %}
|
||||||
|
<p role="alert" class="mt-4 rounded-radius border border-danger/40 bg-danger/10 p-3 text-sm text-on-surface dark:text-on-surface-dark">{{ t(key="cart-remove-unavailable", lang=lang | default(value='sk')) }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="mt-6 flex flex-wrap justify-between gap-3">
|
<div class="mt-6 flex flex-wrap justify-between gap-3">
|
||||||
{{ ui::button(variant="outline-secondary", label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop") }}
|
{{ ui::button(variant="outline-secondary", label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop") }}
|
||||||
|
{% if has_unavailable %}
|
||||||
|
{{ ui::button(label=t(key="cart-checkout", lang=lang | default(value='sk')), size="px-5 py-2 text-sm", attrs='disabled aria-disabled="true"', extra="opacity-50 cursor-not-allowed pointer-events-none") }}
|
||||||
|
{% else %}
|
||||||
{{ ui::button(label=t(key="cart-checkout", lang=lang | default(value='sk')), href="/checkout", size="px-5 py-2 text-sm", attrs='hx-boost="false"') }}
|
{{ ui::button(label=t(key="cart-checkout", lang=lang | default(value='sk')), href="/checkout", size="px-5 py-2 text-sm", attrs='hx-boost="false"') }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="rounded-radius border border-outline px-6 py-16 text-center dark:border-outline-dark">
|
<div class="rounded-radius border border-outline px-6 py-16 text-center dark:border-outline-dark">
|
||||||
|
|||||||
@@ -9,9 +9,10 @@
|
|||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<a href="/shop/{{ item.slug }}" class="block truncate text-sm font-medium text-on-surface-strong hover:text-primary dark:text-on-surface-dark-strong dark:hover:text-primary-dark">{{ item.name }}</a>
|
<a href="/shop/{{ item.slug }}" class="block truncate text-sm font-medium text-on-surface-strong hover:text-primary dark:text-on-surface-dark-strong dark:hover:text-primary-dark">{{ item.name }}</a>
|
||||||
{% if item.variant_label %}<span class="block truncate text-xs text-on-surface/60 dark:text-on-surface-dark/60">{{ item.variant_label }}</span>{% endif %}
|
{% if item.variant_label %}<span class="block truncate text-xs text-on-surface/60 dark:text-on-surface-dark/60">{{ item.variant_label }}</span>{% endif %}
|
||||||
<p class="mt-0.5 text-xs tabular-nums text-on-surface dark:text-on-surface-dark">{{ item.quantity }} × {{ item.price }} {{ currency_symbol }}</p>
|
{% if item.out_of_stock %}<span class="mt-0.5 inline-flex items-center rounded-radius bg-danger/10 px-1.5 py-0.5 text-xs font-medium text-danger">{{ t(key="cart-out-of-stock", lang=lang | default(value='sk')) }}</span>
|
||||||
|
{% else %}<p class="mt-0.5 text-xs tabular-nums text-on-surface dark:text-on-surface-dark">{{ item.quantity }} × {{ item.price }} {{ currency_symbol }}</p>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<span class="shrink-0 text-sm font-semibold tabular-nums text-on-surface-strong dark:text-on-surface-dark-strong">{{ item.line_total }} {{ currency_symbol }}</span>
|
<span class="shrink-0 text-sm font-semibold tabular-nums text-on-surface-strong dark:text-on-surface-dark-strong">{% if item.out_of_stock %}<span class="text-on-surface/40 dark:text-on-surface-dark/40">—</span>{% else %}{{ item.line_total }} {{ currency_symbol }}{% endif %}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -22,7 +23,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
{{ ui::button(href="/cart", variant="outline-primary", label=t(key="cart-title", lang=lang | default(value='sk')), extra="flex-1", attrs='hx-boost="false"') }}
|
{{ ui::button(href="/cart", variant="outline-primary", label=t(key="cart-title", lang=lang | default(value='sk')), extra="flex-1", attrs='hx-boost="false"') }}
|
||||||
|
{% if has_unavailable %}
|
||||||
|
{{ ui::button(variant="primary", label=t(key="cart-checkout", lang=lang | default(value='sk')), extra="flex-1 opacity-50 cursor-not-allowed pointer-events-none", attrs='disabled aria-disabled="true"') }}
|
||||||
|
{% else %}
|
||||||
{{ ui::button(href="/checkout", variant="primary", label=t(key="cart-checkout", lang=lang | default(value='sk')), extra="flex-1", attrs='hx-boost="false"') }}
|
{{ ui::button(href="/checkout", variant="primary", label=t(key="cart-checkout", lang=lang | default(value='sk')), extra="flex-1", attrs='hx-boost="false"') }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -173,12 +173,19 @@ async fn add(
|
|||||||
let user = guard::current_user(&ctx, &jar).await;
|
let user = guard::current_user(&ctx, &jar).await;
|
||||||
let mut items = stored_cart(&ctx, user.as_ref(), &jar).await?;
|
let mut items = stored_cart(&ctx, user.as_ref(), &jar).await?;
|
||||||
let add_qty = form.quantity.unwrap_or(1).max(1);
|
let add_qty = form.quantity.unwrap_or(1).max(1);
|
||||||
|
// Clamp to on-hand stock when tracked and in stock; an out-of-stock (stock 0)
|
||||||
|
// item is still added at the requested quantity so it isn't silently dropped —
|
||||||
|
// it shows in the cart as unavailable until restocked or removed.
|
||||||
|
let new_qty = |desired: i32| match variant.stock {
|
||||||
|
Some(0) => desired.max(1),
|
||||||
|
Some(s) => desired.clamp(1, s),
|
||||||
|
None => desired.max(1),
|
||||||
|
};
|
||||||
if let Some(entry) = items.iter_mut().find(|(id, _)| *id == variant.id) {
|
if let Some(entry) = items.iter_mut().find(|(id, _)| *id == variant.id) {
|
||||||
entry.1 = variant.cap(entry.1 + add_qty);
|
entry.1 = new_qty(entry.1 + add_qty);
|
||||||
} else {
|
} else {
|
||||||
items.push((variant.id, variant.cap(add_qty)));
|
items.push((variant.id, new_qty(add_qty)));
|
||||||
}
|
}
|
||||||
items.retain(|(_, qty)| *qty > 0);
|
|
||||||
|
|
||||||
let jar = persist_cart(&ctx, jar, user.as_ref(), &items).await?;
|
let jar = persist_cart(&ctx, jar, user.as_ref(), &items).await?;
|
||||||
|
|
||||||
@@ -253,17 +260,18 @@ async fn cart_response(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cur = currency::resolve(ctx, &jar).await;
|
let cur = currency::resolve(ctx, &jar).await;
|
||||||
let (lines, valid, total) = resolve_cart(ctx, &jar, &cur).await?;
|
let cart = resolve_cart(ctx, &jar, &cur).await?;
|
||||||
// Persist the re-validated cookie (drops now-invalid lines).
|
// Persist the re-validated cookie (keeps out-of-stock lines, drops gone ones).
|
||||||
let user = guard::current_user(ctx, &jar).await;
|
let user = guard::current_user(ctx, &jar).await;
|
||||||
let jar = persist_cart(ctx, jar, user.as_ref(), &valid).await?;
|
let jar = persist_cart(ctx, jar, user.as_ref(), &cart.stored).await?;
|
||||||
let response = format::view(
|
let response = format::view(
|
||||||
v,
|
v,
|
||||||
"shop/_cart_body.html",
|
"shop/_cart_body.html",
|
||||||
json!({
|
json!({
|
||||||
"items": lines,
|
"items": cart.lines,
|
||||||
"total": cur.format(total),
|
"total": cur.format(cart.total),
|
||||||
"currency_symbol": cur.symbol,
|
"currency_symbol": cur.symbol,
|
||||||
|
"has_unavailable": cart.has_unavailable,
|
||||||
"lang": current_lang(&jar),
|
"lang": current_lang(&jar),
|
||||||
}),
|
}),
|
||||||
)?;
|
)?;
|
||||||
@@ -273,12 +281,25 @@ async fn cart_response(
|
|||||||
/// Resolve the active cart into priced line items, dropping anything that is no
|
/// Resolve the active cart into priced line items, dropping anything that is no
|
||||||
/// longer purchasable and clamping quantities to current stock. Guests resolve
|
/// longer purchasable and clamping quantities to current stock. Guests resolve
|
||||||
/// from the cookie; authenticated users resolve from their account cart.
|
/// from the cookie; authenticated users resolve from their account cart.
|
||||||
|
/// The outcome of resolving the active cart. `lines` is for display and includes
|
||||||
|
/// out-of-stock items (flagged `available: false`); `purchasable` is the in-stock
|
||||||
|
/// subset used for the `total` and for checkout; `stored` is the full set written
|
||||||
|
/// back to the cart (so out-of-stock items are *kept*, not silently dropped);
|
||||||
|
/// `has_unavailable` is set when any line is currently out of stock.
|
||||||
|
pub(crate) struct ResolvedCart {
|
||||||
|
pub lines: Vec<serde_json::Value>,
|
||||||
|
pub purchasable: Vec<(i32, i32)>,
|
||||||
|
pub stored: Vec<(i32, i32)>,
|
||||||
|
pub total: i64,
|
||||||
|
pub has_unavailable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn resolve_cart(
|
pub(crate) async fn resolve_cart(
|
||||||
ctx: &AppContext,
|
ctx: &AppContext,
|
||||||
jar: &CookieJar,
|
jar: &CookieJar,
|
||||||
cur: &Currency,
|
cur: &Currency,
|
||||||
) -> Result<(Vec<serde_json::Value>, Vec<(i32, i32)>, i64)> {
|
) -> Result<ResolvedCart> {
|
||||||
// Resolve the cart entries to in-stock products first, then price them all
|
// Resolve the cart entries to their (published) products, then price them all
|
||||||
// for the current viewer in one batch (the price depends on who's logged in).
|
// for the current viewer in one batch (the price depends on who's logged in).
|
||||||
let user = guard::current_user(ctx, jar).await;
|
let user = guard::current_user(ctx, jar).await;
|
||||||
let mut items: Vec<(product_variants::Model, products::Model, i32)> = Vec::new();
|
let mut items: Vec<(product_variants::Model, products::Model, i32)> = Vec::new();
|
||||||
@@ -286,8 +307,7 @@ pub(crate) async fn resolve_cart(
|
|||||||
let Some((variant, product)) = published_variant(ctx, id).await? else {
|
let Some((variant, product)) = published_variant(ctx, id).await? else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let qty = variant.cap(qty);
|
if qty <= 0 {
|
||||||
if qty == 0 {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
items.push((variant, product, qty));
|
items.push((variant, product, qty));
|
||||||
@@ -297,13 +317,29 @@ pub(crate) async fn resolve_cart(
|
|||||||
let priced = pricing::price_variants(ctx, &variants_only, user.as_ref()).await?;
|
let priced = pricing::price_variants(ctx, &variants_only, user.as_ref()).await?;
|
||||||
|
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
let mut valid = Vec::new();
|
let mut purchasable = Vec::new();
|
||||||
|
let mut stored = Vec::new();
|
||||||
let mut total: i64 = 0;
|
let mut total: i64 = 0;
|
||||||
|
let mut has_unavailable = false;
|
||||||
for ((variant, product, qty), priced) in items.iter().zip(priced.iter()) {
|
for ((variant, product, qty), priced) in items.iter().zip(priced.iter()) {
|
||||||
|
// Out-of-stock (tracked stock of 0) items are kept in the cart, shown as
|
||||||
|
// unavailable and excluded from the total/checkout, instead of being
|
||||||
|
// silently removed. In-stock quantities are clamped to what's on hand.
|
||||||
|
let out_of_stock = matches!(variant.stock, Some(0));
|
||||||
|
let store_qty = if out_of_stock { *qty } else { variant.cap(*qty) };
|
||||||
|
if store_qty <= 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
stored.push((variant.id, store_qty));
|
||||||
|
|
||||||
let unit_price = priced.price_cents;
|
let unit_price = priced.price_cents;
|
||||||
let line_total = unit_price * i64::from(*qty);
|
let line_total = unit_price * i64::from(store_qty);
|
||||||
total += line_total;
|
if out_of_stock {
|
||||||
valid.push((variant.id, *qty));
|
has_unavailable = true;
|
||||||
|
} else {
|
||||||
|
total += line_total;
|
||||||
|
purchasable.push((variant.id, store_qty));
|
||||||
|
}
|
||||||
lines.push(json!({
|
lines.push(json!({
|
||||||
"id": variant.id,
|
"id": variant.id,
|
||||||
"name": product.name,
|
"name": product.name,
|
||||||
@@ -312,17 +348,25 @@ pub(crate) async fn resolve_cart(
|
|||||||
"price": cur.format(unit_price),
|
"price": cur.format(unit_price),
|
||||||
"regular_price": cur.format(priced.regular_cents),
|
"regular_price": cur.format(priced.regular_cents),
|
||||||
"on_sale": priced.is_reduced(),
|
"on_sale": priced.is_reduced(),
|
||||||
"quantity": qty,
|
"quantity": store_qty,
|
||||||
"stock": variant.stock,
|
"stock": variant.stock,
|
||||||
|
"available": !out_of_stock,
|
||||||
|
"out_of_stock": out_of_stock,
|
||||||
"line_total": cur.format(line_total),
|
"line_total": cur.format(line_total),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(user) = user.as_ref() {
|
if let Some(user) = user.as_ref() {
|
||||||
account_cart_items::Model::replace_for_user(&ctx.db, user.id, &valid).await?;
|
account_cart_items::Model::replace_for_user(&ctx.db, user.id, &stored).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((lines, valid, total))
|
Ok(ResolvedCart {
|
||||||
|
lines,
|
||||||
|
purchasable,
|
||||||
|
stored,
|
||||||
|
total,
|
||||||
|
has_unavailable,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
@@ -332,16 +376,17 @@ async fn show(
|
|||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let cur = currency::resolve(&ctx, &jar).await;
|
let cur = currency::resolve(&ctx, &jar).await;
|
||||||
let (lines, valid, total) = resolve_cart(&ctx, &jar, &cur).await?;
|
let cart = resolve_cart(&ctx, &jar, &cur).await?;
|
||||||
|
|
||||||
let c = guard::chrome(&ctx, &jar).await;
|
let c = guard::chrome(&ctx, &jar).await;
|
||||||
let response = format::view(
|
let response = format::view(
|
||||||
&v,
|
&v,
|
||||||
"shop/cart.html",
|
"shop/cart.html",
|
||||||
json!({
|
json!({
|
||||||
"items": lines,
|
"items": cart.lines,
|
||||||
"total": cur.format(total),
|
"total": cur.format(cart.total),
|
||||||
"currency_symbol": cur.symbol,
|
"currency_symbol": cur.symbol,
|
||||||
|
"has_unavailable": cart.has_unavailable,
|
||||||
"logged_in_admin": c.logged_in_admin,
|
"logged_in_admin": c.logged_in_admin,
|
||||||
"logged_in_customer": c.logged_in_customer,
|
"logged_in_customer": c.logged_in_customer,
|
||||||
"customer_name": c.customer_name,
|
"customer_name": c.customer_name,
|
||||||
@@ -352,7 +397,7 @@ async fn show(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let user = guard::current_user(&ctx, &jar).await;
|
let user = guard::current_user(&ctx, &jar).await;
|
||||||
let jar = persist_cart(&ctx, jar, user.as_ref(), &valid).await?;
|
let jar = persist_cart(&ctx, jar, user.as_ref(), &cart.stored).await?;
|
||||||
Ok((jar, response).into_response())
|
Ok((jar, response).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,19 +410,20 @@ async fn preview(
|
|||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let cur = currency::resolve(&ctx, &jar).await;
|
let cur = currency::resolve(&ctx, &jar).await;
|
||||||
let (lines, valid, total) = resolve_cart(&ctx, &jar, &cur).await?;
|
let cart = resolve_cart(&ctx, &jar, &cur).await?;
|
||||||
let response = format::view(
|
let response = format::view(
|
||||||
&v,
|
&v,
|
||||||
"shop/_cart_preview.html",
|
"shop/_cart_preview.html",
|
||||||
json!({
|
json!({
|
||||||
"items": lines,
|
"items": cart.lines,
|
||||||
"total": cur.format(total),
|
"total": cur.format(cart.total),
|
||||||
"currency_symbol": cur.symbol,
|
"currency_symbol": cur.symbol,
|
||||||
|
"has_unavailable": cart.has_unavailable,
|
||||||
"lang": current_lang(&jar),
|
"lang": current_lang(&jar),
|
||||||
}),
|
}),
|
||||||
)?;
|
)?;
|
||||||
let user = guard::current_user(&ctx, &jar).await;
|
let user = guard::current_user(&ctx, &jar).await;
|
||||||
let jar = persist_cart(&ctx, jar, user.as_ref(), &valid).await?;
|
let jar = persist_cart(&ctx, jar, user.as_ref(), &cart.stored).await?;
|
||||||
Ok((jar, response).into_response())
|
Ok((jar, response).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,10 +171,13 @@ async fn info_page(
|
|||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
// Checkout and everything past it (orders, confirmation) stay in the EUR
|
// Checkout and everything past it (orders, confirmation) stay in the EUR
|
||||||
// base — the settlement currency — even when the buyer browsed in another.
|
// base — the settlement currency — even when the buyer browsed in another.
|
||||||
let (lines, _valid, subtotal) = resolve_cart(&ctx, &jar, &Currency::eur()).await?;
|
let cart = resolve_cart(&ctx, &jar, &Currency::eur()).await?;
|
||||||
if lines.is_empty() {
|
// No purchasable lines, or some line is out of stock: back to the basket to
|
||||||
|
// resolve it before checking out.
|
||||||
|
if cart.purchasable.is_empty() || cart.has_unavailable {
|
||||||
return format::redirect("/cart");
|
return format::redirect("/cart");
|
||||||
}
|
}
|
||||||
|
let (lines, subtotal) = (cart.lines, cart.total);
|
||||||
|
|
||||||
// Prefill the form for a logged-in customer: contact name/email come from
|
// Prefill the form for a logged-in customer: contact name/email come from
|
||||||
// the user account, the address/phone from their saved profile (if any).
|
// the user account, the address/phone from their saved profile (if any).
|
||||||
@@ -248,8 +251,8 @@ async fn submit_info(
|
|||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
Form(form): Form<InfoForm>,
|
Form(form): Form<InfoForm>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let (_lines, valid, _total) = resolve_cart(&ctx, &jar, &Currency::eur()).await?;
|
let cart = resolve_cart(&ctx, &jar, &Currency::eur()).await?;
|
||||||
if valid.is_empty() {
|
if cart.purchasable.is_empty() || cart.has_unavailable {
|
||||||
return format::redirect("/cart");
|
return format::redirect("/cart");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,10 +346,11 @@ async fn payment_page(
|
|||||||
Query(params): Query<std::collections::HashMap<String, String>>,
|
Query(params): Query<std::collections::HashMap<String, String>>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let (lines, _valid, subtotal) = resolve_cart(&ctx, &jar, &Currency::eur()).await?;
|
let cart = resolve_cart(&ctx, &jar, &Currency::eur()).await?;
|
||||||
if lines.is_empty() {
|
if cart.purchasable.is_empty() || cart.has_unavailable {
|
||||||
return format::redirect("/cart");
|
return format::redirect("/cart");
|
||||||
}
|
}
|
||||||
|
let (lines, subtotal) = (cart.lines, cart.total);
|
||||||
if decode_state::<CheckoutInfo>(&jar, INFO_COOKIE).is_none() {
|
if decode_state::<CheckoutInfo>(&jar, INFO_COOKIE).is_none() {
|
||||||
return format::redirect("/checkout/info");
|
return format::redirect("/checkout/info");
|
||||||
}
|
}
|
||||||
@@ -446,10 +450,11 @@ async fn place_order(
|
|||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
Form(form): Form<PaymentForm>,
|
Form(form): Form<PaymentForm>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let (_lines, valid, subtotal) = resolve_cart(&ctx, &jar, &Currency::eur()).await?;
|
let cart = resolve_cart(&ctx, &jar, &Currency::eur()).await?;
|
||||||
if valid.is_empty() {
|
if cart.purchasable.is_empty() || cart.has_unavailable {
|
||||||
return format::redirect("/cart");
|
return format::redirect("/cart");
|
||||||
}
|
}
|
||||||
|
let (valid, subtotal) = (cart.purchasable, cart.total);
|
||||||
let Some(info) = decode_state::<CheckoutInfo>(&jar, INFO_COOKIE) else {
|
let Some(info) = decode_state::<CheckoutInfo>(&jar, INFO_COOKIE) else {
|
||||||
return format::redirect("/checkout/info");
|
return format::redirect("/checkout/info");
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user