You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

97 lines
2.8 KiB

/**
* 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<typeof checkoutSchema>
/**
* Billing address type (subset of CheckoutData, without saveAddress flag)
* Used for storing in order.billingAddress JSONB field
*/
export type BillingAddress = Omit<CheckoutData, 'saveAddress'>