Платёжные данные представляют собой один из наиболее чувствительных типов пользовательской информации. Ошибки в их валидации приводят не только к отказам транзакций, но и к потенциальным уязвимостям, связанным с безопасностью и мошенничеством. Библиотека Joi позволяет строить строгие и выразительные схемы проверки данных, обеспечивая предсказуемость и контроль на уровне входных структур.
В контексте платёжных форм Joi применяется для проверки номеров банковских карт, сроков действия, CVV-кодов, платёжных адресов, а также комплексных объектов транзакций.
Типичная структура платёжных данных может включать:
Joi позволяет описать эту структуру декларативно, превращая набор правил в единый объект схемы.
import Joi from 'joi';
const paymentSchema = Joi.object({
cardNumber: Joi.string().required(),
expiryMonth: Joi.number().min(1).max(12).required(),
expiryYear: Joi.number().min(2024).required(),
cvv: Joi.string().length(3).required(),
holderName: Joi.string().min(3).max(100).required(),
amount: Joi.number().positive().precision(2).required(),
currency: Joi.string().length(3).required()
});
Такой подход обеспечивает централизованную проверку и исключает дублирование логики.
Номер карты требует не только проверки формата, но и структурной корректности. На практике применяется алгоритм Луна, однако Joi не реализует его встроенно, поэтому используется кастомная проверка.
const luhnCheck = (value) => {
let sum = 0;
let shouldDouble = false;
for (let i = value.length - 1; i >= 0; i--) {
let digit = parseInt(value[i], 10);
if (shouldDouble) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
shouldDouble = !shouldDouble;
}
return sum % 10 === 0;
};
const cardNumberSchema = Joi.string()
.pattern(/^\d{13,19}$/)
.custom((value, helpers) => {
if (!luhnCheck(value)) {
return helpers.error('any.invalid');
}
return value;
});
Такой уровень проверки позволяет отсечь случайные ошибки ввода и часть недействительных номеров.
Срок действия карты состоит из месяца и года, которые должны быть не только валидными по диапазону, но и не находиться в прошлом.
const expirySchema = Joi.object({
month: Joi.number().min(1).max(12).required(),
year: Joi.number().min(2024).required()
}).custom((value, helpers) => {
const now = new Date();
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
if (value.year === currentYear && value.month < currentMonth) {
return helpers.error('date.expired');
}
if (value.year < currentYear) {
return helpers.error('date.expired');
}
return value;
});
Подобная проверка предотвращает использование просроченных платёжных инструментов ещё до обращения к платёжному шлюзу.
CVV-код зависит от платёжной системы и обычно имеет длину 3 или 4 цифры.
const cvvSchema = Joi.string()
.pattern(/^\d{3,4}$/)
.required();
Дополнительно может применяться привязка к типу карты, однако это уже относится к бизнес-логике.
Имя держателя карты часто используется в антифрод-системах. Joi позволяет ограничить набор допустимых символов:
const holderNameSchema = Joi.string()
.min(3)
.max(100)
.pattern(/^[a-zA-Z\s'-]+$/)
.required();
Такое ограничение исключает числовые значения и специальные символы, не характерные для имён.
Объединение всех компонентов формирует целостную схему:
const paymentSchema = Joi.object({
cardNumber: cardNumberSchema.required(),
expiry: expirySchema.required(),
cvv: cvvSchema.required(),
holderName: holderNameSchema.required(),
amount: Joi.number().positive().precision(2).required(),
currency: Joi.string().length(3).uppercase().required()
});
Валидация выполняется единым вызовом:
const result = paymentSchema.validate(data);
if (result.error) {
console.log(result.error.details);
}
Перед проверкой часто требуется очистка данных: удаление пробелов, дефисов и других символов, вводимых пользователем.
const normalizeCardNumber = (value) =>
value.replace(/[\s-]/g, '');
const cardSchema = Joi.string()
.custom((value, helpers) => normalizeCardNumber(value))
.pattern(/^\d{13,19}$/);
Такой подход снижает количество ошибок, связанных с форматом ввода.
Платёжные системы часто поддерживают несколько методов оплаты: карта, PayPal, банковский перевод. Joi позволяет строить условные схемы.
const paymentMethodSchema = Joi.object({
method: Joi.string().valid('card', 'paypal', 'bank'),
card: Joi.when('method', {
is: 'card',
then: Joi.object({
number: cardNumberSchema.required(),
cvv: cvvSchema.required()
}).required(),
otherwise: Joi.forbidden()
})
});
Это исключает передачу лишних данных для неподходящего метода.
Финансовые данные требуют строгой типизации и ограничений.
const amountSchema = Joi.number()
.positive()
.precision(2)
.min(0.01)
.max(100000);
Валюта обычно фиксируется по стандарту ISO 4217:
const currencySchema = Joi.string()
.valid('USD', 'EUR', 'KZT', 'GBP')
.required();
Joi позволяет задавать человекочитаемые сообщения, что важно для финансовых интерфейсов:
const schema = Joi.string()
.pattern(/^\d{16}$/)
.messages({
'string.pattern.base': 'Номер карты должен содержать 16 цифр'
});
В платёжных данных критично запрещать неожиданные поля:
const strictPaymentSchema = Joi.object({
cardNumber: Joi.string().required(),
cvv: Joi.string().required()
}).unknown(false);
Это предотвращает передачу потенциально опасных или лишних данных.
В реальных системах платёжный объект включает метаданные:
const transactionSchema = Joi.object({
payment: paymentSchema.required(),
customer: Joi.object({
id: Joi.string().required(),
email: Joi.string().email().required()
}),
metadata: Joi.object().pattern(Joi.string(), Joi.any())
});
Такой подход позволяет масштабировать структуру без потери контроля над критичными полями.
Некоторые проверки требуют обращения к внешним сервисам, например, проверка карты в платёжном шлюзе.
const asyncSchema = Joi.string().external(async (value) => {
const exists = await checkCardInGateway(value);
if (!exists) {
throw new Error('Card not supported');
}
});
Это расширяет возможности Joi за пределы синхронной валидации.
При проектировании платёжных систем Joi используется как первый барьер защиты. Его роль заключается в:
Комбинация строгих схем, кастомных валидаторов и нормализации данных формирует устойчивую основу для безопасной обработки финансовых операций.