diff --git a/app/api/checkout/route.ts b/app/api/checkout/route.ts index 01e6f54..0d8d478 100644 --- a/app/api/checkout/route.ts +++ b/app/api/checkout/route.ts @@ -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, diff --git a/app/components/AddToCartButton.tsx b/app/components/AddToCartButton.tsx index a8ce6bf..3f322fc 100644 --- a/app/components/AddToCartButton.tsx +++ b/app/components/AddToCartButton.tsx @@ -81,7 +81,7 @@ export function AddToCartButton({ const displayLabel = currentlyOutOfStock ? "Ausverkauft" : label; return ( -