Files
pagodera/lib/validation-schemas.ts
Evandro Fonseca Junior 8b5817e8ca feat(donation): increase minimum custom donation amount to R$ 5.00
- Updated customAmountSchema min value from 0.01 to 5
- Adjusted validation logic and error messages to reflect new minimum
- Modified DONATION_PRODUCTS minAmount for custom tier to 5
- Ensures donations meet the new minimum threshold for processing
2025-10-26 23:31:06 -03:00

143 lines
4.2 KiB
TypeScript

import { z } from "zod";
import validator from "validator";
import { isValidDocument } from "./validators/document-validator";
import sanitizeHtml from "sanitize-html";
/**
* Schemas de validação usando Zod
* Garante type-safety e validação robusta de dados de entrada
*/
// Schema para email - validação RFC 5322
export const emailSchema = z
.string()
.min(1, "Email é obrigatório")
.email("Formato de email inválido")
.refine((email) => validator.isEmail(email), {
message: "Email inválido",
})
.transform((email) => email.trim().toLowerCase());
// Schema para telefone brasileiro
export const phoneSchema = z
.string()
.min(1, "Telefone é obrigatório")
.refine((phone) => {
// Remove todos os caracteres não numéricos
const cleaned = phone.replace(/[^\d]/g, "");
// Deve ter 10 ou 11 dígitos (telefone fixo ou celular com DDD)
return cleaned.length >= 10 && cleaned.length <= 11;
}, "Telefone deve ter 10 ou 11 dígitos (incluindo DDD)")
.transform((phone) => phone.replace(/[^\d]/g, ""));
// Schema para CPF/CNPJ
export const documentSchema = z
.string()
.min(1, "CPF/CNPJ é obrigatório")
.refine((doc) => isValidDocument(doc), {
message: "CPF ou CNPJ inválido",
})
.transform((doc) => doc.replace(/[^\d]/g, "")); // Normaliza removendo formatação
// Schema para nome (alfabético, sem números)
export const nameSchema = z
.string()
.min(3, "Nome deve ter no mínimo 3 caracteres")
.max(100, "Nome deve ter no máximo 100 caracteres")
.refine(
(name) => /^[A-Za-zÀ-ÿ\s'-]+$/.test(name.trim()),
"Nome deve conter apenas letras, espaços e acentos"
)
.transform((name) => name.trim());
// Schema para mensagem (sanitiza HTML e previne XSS)
export const messageSchema = z
.string()
.max(280, "Mensagem deve ter no máximo 280 caracteres")
.transform((message) => {
// Sanitiza HTML para prevenir XSS
const sanitized = sanitizeHtml(message, {
allowedTags: [], // Remove todas as tags HTML
allowedAttributes: {},
});
return sanitized.trim();
})
.optional();
// Schema para tier de doação
export const donationTierSchema = z.enum(
["coffee", "snack", "lunch", "custom"],
{
message: "Tier de doação inválido",
}
);
// Schema para valor customizado
export const customAmountSchema = z
.number()
.min(5, "Valor mínimo é R$ 5,00")
.max(100000, "Valor máximo é R$ 100.000,00");
// Schema completo para criação de doação
export const createDonationSchema = z
.discriminatedUnion("anonymous", [
// Doação anônima
z.object({
tier: donationTierSchema,
anonymous: z.literal(true),
customAmount: z.number().optional(),
}),
// Doação identificada
z.object({
tier: donationTierSchema,
anonymous: z.literal(false),
donorName: nameSchema,
donorEmail: emailSchema,
donorPhone: phoneSchema,
donorTaxId: documentSchema,
donorMessage: messageSchema,
customAmount: z.number().optional(),
}),
])
.refine(
(data) => {
// Se tier for custom, customAmount é obrigatório
if (data.tier === "custom") {
return data.customAmount !== undefined;
}
return true;
},
{
message: "customAmount é obrigatório quando tier é 'custom'",
path: ["customAmount"],
}
)
.refine(
(data) => {
// Se tier for custom, customAmount deve estar no range válido
if (data.tier === "custom" && data.customAmount) {
return data.customAmount >= 5 && data.customAmount <= 100000;
}
return true;
},
{
message: "Valor deve estar entre R$ 5,00 e R$ 100.000,00",
path: ["customAmount"],
}
);
// Schema para verificação de status
export const verifyStatusSchema = z.object({
pixId: z.string().min(1, "pixId é obrigatório"),
});
// Schema para verificação de billing
export const verifyBillingSchema = z.object({
billingId: z.string().min(1, "ID da cobrança é obrigatório"),
});
// Type exports para uso no código
export type CreateDonationInput = z.infer<typeof createDonationSchema>;
export type VerifyStatusInput = z.infer<typeof verifyStatusSchema>;
export type VerifyBillingInput = z.infer<typeof verifyBillingSchema>;