/** * Checkout Schema * * Validation schema for billing address data during checkout. * All fields are required at checkout (even though optional in users table). */ import { z } from 'zod' /** * Salutation validation * Maps to: male → HERR, female → FRAU, other → K_ANGABE (X-API format) */ export const salutationEnum = z.enum(['male', 'female', 'other'], { errorMap: () => ({ message: 'Bitte wähle eine Anrede' }), }) /** * German postal code validation (5 digits) * Format: XXXXX (e.g., 74072, 10115) */ const germanPostCodeRegex = /^\d{5}$/ /** * Date validation in YYYY-MM-DD format * Must be a valid date in the past (birth date) */ const dateRegex = /^\d{4}-\d{2}-\d{2}$/ /** * Complete checkout schema * * Validates all billing address fields required for annual pass orders. * These fields are also used for saving to user profile if "save address" checkbox is checked. */ export const checkoutSchema = z.object({ // Personal information salutation: salutationEnum, firstName: z .string() .min(2, 'Vorname muss mindestens 2 Zeichen lang sein') .max(100, 'Vorname darf maximal 100 Zeichen lang sein') .trim(), lastName: z .string() .min(2, 'Nachname muss mindestens 2 Zeichen lang sein') .max(100, 'Nachname darf maximal 100 Zeichen lang sein') .trim(), dateOfBirth: z .string() .regex(dateRegex, 'Geburtsdatum muss im Format YYYY-MM-DD sein') .refine( (date) => { const parsed = new Date(date) // Check if date is valid and in the past const today = new Date() today.setHours(0, 0, 0, 0) return !isNaN(parsed.getTime()) && parsed < today }, { message: 'Geburtsdatum muss ein gültiges Datum in der Vergangenheit sein' } ), // Address information street: z .string() .min(3, 'Straße und Hausnummer müssen mindestens 3 Zeichen lang sein') .max(200, 'Straße darf maximal 200 Zeichen lang sein') .trim(), postCode: z .string() .regex(germanPostCodeRegex, 'Postleitzahl muss aus 5 Ziffern bestehen (z.B. 74072)') .trim(), city: z .string() .min(2, 'Stadt muss mindestens 2 Zeichen lang sein') .max(100, 'Stadt darf maximal 100 Zeichen lang sein') .trim(), countryCode: z .string() .length(2, 'Ländercode muss aus 2 Zeichen bestehen (ISO 3166-1 alpha-2)') .toUpperCase() .default('DE'), // Default to Germany // Optional: flag to save address to user profile saveAddress: z.boolean().optional().default(false), }) /** * TypeScript type inferred from Zod schema */ export type CheckoutData = z.infer /** * Billing address type (subset of CheckoutData, without saveAddress flag) * Used for storing in order.billingAddress JSONB field */ export type BillingAddress = Omit