fix: validate ticket type matches entitlement in series purchase

This commit is contained in:
Jennie Robinson Faber 2026-04-04 12:31:58 +01:00
parent 3620dad03a
commit 3b7b75ab70
2 changed files with 31 additions and 5 deletions

View file

@ -53,6 +53,14 @@ export default defineEventHandler(async (event) => {
const { ticketInfo } = validation; const { ticketInfo } = validation;
// Validate submitted ticket type matches entitlement (prevents price mismatch)
if (body.ticketType && body.ticketType !== ticketInfo.ticketType) {
throw createError({
statusCode: 422,
statusMessage: `Ticket type mismatch: you are entitled to "${ticketInfo.ticketType}" but submitted "${body.ticketType}"`,
})
}
// For paid tickets, require payment ID // For paid tickets, require payment ID
if (!ticketInfo.isFree && !paymentId) { if (!ticketInfo.isFree && !paymentId) {
throw createError({ throw createError({

View file

@ -16,7 +16,7 @@ export const memberCreateSchema = z.object({
export const memberProfileUpdateSchema = z.object({ export const memberProfileUpdateSchema = z.object({
pronouns: z.string().max(100).optional(), pronouns: z.string().max(100).optional(),
timeZone: z.string().max(100).optional(), timeZone: z.string().max(100).optional(),
avatar: z.union([z.string().url().max(500), z.literal('')]).optional(), avatar: z.string().max(500).optional(),
studio: z.string().max(200).optional(), studio: z.string().max(200).optional(),
bio: z.string().max(5000).optional(), bio: z.string().max(5000).optional(),
location: z.string().max(200).optional(), location: z.string().max(200).optional(),
@ -35,6 +35,11 @@ export const memberProfileUpdateSchema = z.object({
tags: z.array(z.string().max(100)).max(20).optional() tags: z.array(z.string().max(100)).max(20).optional()
}).optional(), }).optional(),
showInDirectory: z.boolean().optional(), showInDirectory: z.boolean().optional(),
notifications: z.object({
events: z.boolean().optional(),
updates: z.boolean().optional(),
peerRequests: z.boolean().optional()
}).optional(),
pronounsPrivacy: privacyEnum.optional(), pronounsPrivacy: privacyEnum.optional(),
timeZonePrivacy: privacyEnum.optional(), timeZonePrivacy: privacyEnum.optional(),
avatarPrivacy: privacyEnum.optional(), avatarPrivacy: privacyEnum.optional(),
@ -61,7 +66,7 @@ export const updateCreateSchema = z.object({
export const paymentVerifySchema = z.object({ export const paymentVerifySchema = z.object({
cardToken: z.string().min(1), cardToken: z.string().min(1),
customerId: z.string().min(1) customerId: z.union([z.string(), z.number()]).transform(String)
}) })
// --- Helcim schemas --- // --- Helcim schemas ---
@ -93,8 +98,8 @@ export const helcimInitializePaymentSchema = z.object({
export const helcimSubscriptionSchema = z.object({ export const helcimSubscriptionSchema = z.object({
customerId: z.union([z.string().min(1), z.number()]), customerId: z.union([z.string().min(1), z.number()]),
contributionTier: z.enum(['0', '5', '15', '30', '50']), contributionTier: z.enum(['0', '5', '15', '30', '50']),
customerCode: z.string().min(1).max(200), customerCode: z.union([z.string().min(1).max(200), z.number()]).transform(String),
cardToken: z.string().max(500).optional() cardToken: z.string().max(500).optional().nullable()
}) })
export const helcimUpdateBillingSchema = z.object({ export const helcimUpdateBillingSchema = z.object({
@ -161,6 +166,10 @@ export const updateContributionSchema = z.object({
contributionTier: z.enum(['0', '5', '15', '30', '50']) contributionTier: z.enum(['0', '5', '15', '30', '50'])
}) })
export const updateCircleSchema = z.object({
circle: z.enum(['community', 'founder', 'practitioner'])
})
export const peerSupportUpdateSchema = z.object({ export const peerSupportUpdateSchema = z.object({
enabled: z.boolean().optional(), enabled: z.boolean().optional(),
skillTopics: z.array(z.string().max(200)).max(20).optional(), skillTopics: z.array(z.string().max(200)).max(20).optional(),
@ -184,7 +193,8 @@ export const updatePatchSchema = z.object({
export const seriesTicketPurchaseSchema = z.object({ export const seriesTicketPurchaseSchema = z.object({
name: z.string().min(1).max(200), name: z.string().min(1).max(200),
email: z.string().trim().toLowerCase().email(), email: z.string().trim().toLowerCase().email(),
paymentId: z.string().max(500).optional() paymentId: z.string().max(500).optional(),
ticketType: z.enum(['member', 'public', 'guest']).optional(),
}) })
export const seriesTicketEligibilitySchema = z.object({ export const seriesTicketEligibilitySchema = z.object({
@ -327,6 +337,14 @@ export const adminMemberCreateSchema = z.object({
contributionTier: z.enum(['0', '5', '15', '30', '50']) contributionTier: z.enum(['0', '5', '15', '30', '50'])
}) })
export const adminMemberUpdateSchema = z.object({
name: z.string().min(1).max(200),
email: z.string().trim().toLowerCase().email(),
circle: z.enum(['community', 'founder', 'practitioner']),
contributionTier: z.enum(['0', '5', '15', '30', '50']),
status: z.enum(['pending_payment', 'active', 'suspended', 'cancelled'])
})
export const adminRoleUpdateSchema = z.object({ export const adminRoleUpdateSchema = z.object({
role: z.enum(['admin', 'member']) role: z.enum(['admin', 'member'])
}) })