Fix money rounding drift, low-stock hint spacing, spotlight CTA height mismatch

- Round subtotal/discountAmount/total to 2 decimals before persisting an
  order — float arithmetic on money was drifting into values like
  84.30000000000001, invisible wherever a display already ran it through
  toFixed(2), but stored as-is and visible raw in the Payload admin's
  plain number field
- Low-stock hint now uses gap-1 consistently (was gap-2) in both
  AddToCartButton/AddToCartInlineButton, for smaller/consistent spacing
  above it regardless of context
- ProductSpotlight's CTA row now uses items-start at sm: — without it,
  default cross-axis stretch made "Mehr erfahren" grow to match
  AddToCartButton's height whenever the low-stock hint made that one
  taller, so the link visibly looked "fatter" than the actual button

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-22 23:10:43 +00:00
parent 43944d8cc8
commit dc6b61324f
4 changed files with 26 additions and 7 deletions
+17 -4
View File
@@ -9,6 +9,18 @@ import { describeBundleContents } from "../../lib/bundleContents";
import { sendCriticalAlert } from "../../lib/alertAdmin";
import { sendOrderConfirmationEmail } from "../../lib/orderEmail";
// Plain float arithmetic on money (quantity × unitPrice summed across
// lines, a percent discount, subtracting/adding those together) drifts
// into results like 84.30000000000001 — cosmetically invisible wherever
// formatPrice()'s toFixed(2) already rounds for display, but stored as-is
// on the order otherwise, which is where it actually showed up (Payload's
// admin list/edit view for a plain number field has no such formatting).
// Rounded once here, right before persisting, rather than chasing it down
// at every downstream display site.
function roundMoney(amount: number): number {
return Math.round(amount * 100) / 100;
}
type CheckoutBody = {
cart: CartItem[];
shippingMethodId: number;
@@ -160,7 +172,7 @@ export async function POST(request: Request) {
variantName: variant?.name ?? null,
});
}
const subtotal = items.reduce((sum, i) => sum + i.quantity * i.unitPrice, 0);
const subtotal = roundMoney(items.reduce((sum, i) => sum + i.quantity * i.unitPrice, 0));
const shippingMethods = await getShippingMethods();
const shippingMethod = shippingMethods.find((m) => m.id === body.shippingMethodId);
@@ -178,10 +190,11 @@ export async function POST(request: Request) {
if (!validation.valid) return NextResponse.json({ ok: false, reason: validation.reason }, { status: 400 });
const redeemed = await redeemDiscountCode(validation.doc);
if (!redeemed) return NextResponse.json({ ok: false, reason: "Rabattcode konnte nicht eingelöst werden." }, { status: 400 });
discountAmount =
validation.doc.type === "percent" ? (subtotal * validation.doc.value) / 100 : Math.min(validation.doc.value, subtotal);
discountAmount = roundMoney(
validation.doc.type === "percent" ? (subtotal * validation.doc.value) / 100 : Math.min(validation.doc.value, subtotal),
);
}
const total = Math.max(0, subtotal - discountAmount) + shippingCost;
const total = roundMoney(Math.max(0, subtotal - discountAmount) + shippingCost);
const order = await createOrder({
customerId: customer.id,