Add optional Firma/USt-IdNr. fields to checkout and profile
B2B checkout fields, split out from the e-invoicing migration and picked back up now that it's shipped. Both fields are independently optional, format-validated (shared regex in lib/vatId.ts, mirrored server-side in api/checkout and api/account/profile), persisted in the checkout draft, and saved as a customer profile default that pre-fills future checkouts. Order/customer snapshot fields land in a companion Payload backend commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getSessionCustomer, updateCustomerProfile } from "../../../lib/customerAuth";
|
||||
import { normalizeVatId, isValidVatId } from "../../../lib/vatId";
|
||||
|
||||
export async function GET() {
|
||||
const session = await getSessionCustomer();
|
||||
@@ -12,7 +13,7 @@ export async function PATCH(request: Request) {
|
||||
if (!session) return NextResponse.json({ ok: false, reason: "Bitte zuerst einloggen." }, { status: 401 });
|
||||
|
||||
const body = await request.json().catch(() => null);
|
||||
const { firstName, lastName, deliveryMethod, street, packstationNumber, postNumber, zip, city, country } = body ?? {};
|
||||
const { firstName, lastName, deliveryMethod, street, packstationNumber, postNumber, zip, city, country, companyName, vatId } = body ?? {};
|
||||
if (
|
||||
typeof firstName !== "string" ||
|
||||
!firstName ||
|
||||
@@ -34,6 +35,12 @@ export async function PATCH(request: Request) {
|
||||
if (deliveryMethod === "packstation" && (!packstationNumber || !postNumber)) {
|
||||
return NextResponse.json({ ok: false, reason: "Bitte Packstation- und Postnummer angeben." }, { status: 400 });
|
||||
}
|
||||
// Both independently optional (see Customers.ts's own comment) — only
|
||||
// format-checked when actually provided, same as the backend field itself.
|
||||
const normalizedVatId = typeof vatId === "string" && vatId ? normalizeVatId(vatId) : undefined;
|
||||
if (normalizedVatId && !isValidVatId(normalizedVatId)) {
|
||||
return NextResponse.json({ ok: false, reason: "Ungültiges USt-IdNr.-Format (z. B. DE123456789)." }, { status: 400 });
|
||||
}
|
||||
|
||||
const result = await updateCustomerProfile(session.token, session.customer.id, {
|
||||
firstName,
|
||||
@@ -45,6 +52,8 @@ export async function PATCH(request: Request) {
|
||||
zip,
|
||||
city,
|
||||
country,
|
||||
companyName: typeof companyName === "string" && companyName ? companyName : undefined,
|
||||
vatId: normalizedVatId,
|
||||
});
|
||||
return NextResponse.json(result, { status: result.ok ? 200 : 400 });
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { fetchProductsBySlug } from "../../lib/productsServer";
|
||||
import { describeBundleContents } from "../../lib/bundleContents";
|
||||
import { sendCriticalAlert } from "../../lib/alertAdmin";
|
||||
import { sendOrderConfirmationEmail } from "../../lib/orderEmail";
|
||||
import { normalizeVatId, isValidVatId } from "../../lib/vatId";
|
||||
|
||||
// Plain float arithmetic on money (quantity × unitPrice summed across
|
||||
// lines, a percent discount, subtracting/adding those together) drifts
|
||||
@@ -30,6 +31,8 @@ type CheckoutBody = {
|
||||
lastName: string;
|
||||
email: string;
|
||||
password?: string;
|
||||
companyName?: string;
|
||||
vatId?: string;
|
||||
deliveryMethod: "address" | "packstation";
|
||||
street?: string;
|
||||
packstationNumber?: string;
|
||||
@@ -85,6 +88,15 @@ export async function POST(request: Request) {
|
||||
if (body.deliveryMethod === "packstation" && (!body.packstationNumber || !body.postNumber)) {
|
||||
return NextResponse.json({ ok: false, reason: "Bitte Packstation- und Postnummer angeben." }, { status: 400 });
|
||||
}
|
||||
// Optional — only format-checked when actually provided, same "never
|
||||
// trust the client" reasoning as every other checkout field re-validated
|
||||
// here. Normalized the same way Orders.ts's own field does (uppercase +
|
||||
// trim), so the snapshot on the order matches what would've been
|
||||
// accepted directly through the Payload admin.
|
||||
const normalizedVatId = body.vatId ? normalizeVatId(body.vatId) : undefined;
|
||||
if (normalizedVatId && !isValidVatId(normalizedVatId)) {
|
||||
return NextResponse.json({ ok: false, reason: "Ungültiges USt-IdNr.-Format (z. B. DE123456789)." }, { status: 400 });
|
||||
}
|
||||
if (body.hasDifferentShippingAddress) {
|
||||
if (!body.shippingFirstName || !body.shippingLastName || !body.shippingZip || !body.shippingCity || !body.shippingCountry) {
|
||||
return NextResponse.json({ ok: false, reason: "Bitte alle Felder der Lieferadresse ausfüllen." }, { status: 400 });
|
||||
@@ -201,6 +213,8 @@ export async function POST(request: Request) {
|
||||
customerFirstName: body.firstName,
|
||||
customerLastName: body.lastName,
|
||||
customerEmail: body.email,
|
||||
companyName: body.companyName || undefined,
|
||||
vatId: normalizedVatId,
|
||||
deliveryMethod: body.deliveryMethod,
|
||||
street: body.street,
|
||||
packstationNumber: body.packstationNumber,
|
||||
|
||||
Reference in New Issue
Block a user