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
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'>
|
|
|