95 lines
2.4 KiB
TypeScript
95 lines
2.4 KiB
TypeScript
import { z } from "zod";
|
|
|
|
export enum PaymentMethod {
|
|
Card = "card",
|
|
// INFO: for other future payment methods
|
|
}
|
|
|
|
function isValidLuhn(cardNumber: string): boolean {
|
|
const digits = cardNumber.replace(/\D/g, "");
|
|
let sum = 0;
|
|
let isEven = false;
|
|
|
|
for (let i = digits.length - 1; i >= 0; i--) {
|
|
let digit = parseInt(digits[i], 10);
|
|
|
|
if (isEven) {
|
|
digit *= 2;
|
|
if (digit > 9) {
|
|
digit -= 9;
|
|
}
|
|
}
|
|
|
|
sum += digit;
|
|
isEven = !isEven;
|
|
}
|
|
|
|
return sum % 10 === 0;
|
|
}
|
|
|
|
function isValidExpiry(expiryDate: string): boolean {
|
|
const match = expiryDate.match(/^(0[1-9]|1[0-2])\/(\d{2})$/);
|
|
if (!match) return false;
|
|
|
|
const month = parseInt(match[1], 10);
|
|
const year = parseInt(match[2], 10);
|
|
|
|
const currentDate = new Date();
|
|
const currentYear = currentDate.getFullYear() % 100; // Get last 2 digits of year
|
|
const maxYear = currentYear + 20;
|
|
|
|
// Check if year is within valid range
|
|
if (year < currentYear || year > maxYear) return false;
|
|
|
|
// If it's the current year, check if the month is valid
|
|
if (year === currentYear) {
|
|
const currentMonth = currentDate.getMonth() + 1; // getMonth() returns 0-11
|
|
if (month < currentMonth) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
export const cardInfoModel = z.object({
|
|
cardholderName: z.string().min(1).max(128),
|
|
cardNumber: z
|
|
.string()
|
|
.min(1)
|
|
.max(20)
|
|
.regex(/^\d+$/, "Card number must be numeric")
|
|
.refine((val) => isValidLuhn(val), {
|
|
message: "Invalid card number",
|
|
}),
|
|
expiry: z
|
|
.string()
|
|
.regex(/^(0[1-9]|1[0-2])\/\d{2}$/, "Expiry must be in mm/yy format")
|
|
.refine((val) => isValidExpiry(val), {
|
|
message: "Invalid expiry date",
|
|
}),
|
|
cvv: z
|
|
.string()
|
|
.min(3)
|
|
.max(4)
|
|
.regex(/^\d{3,4}$/, "CVV must be 3-4 digits"),
|
|
});
|
|
export type CardInfo = z.infer<typeof cardInfoModel>;
|
|
|
|
export const paymentInfoPayloadModel = z.object({
|
|
method: z.enum([PaymentMethod.Card]),
|
|
cardDetails: cardInfoModel,
|
|
productId: z.number().int(),
|
|
orderId: z.number().int(),
|
|
});
|
|
export type PaymentInfoPayload = z.infer<typeof paymentInfoPayloadModel>;
|
|
|
|
export const paymentInfoModel = cardInfoModel.merge(
|
|
z.object({
|
|
id: z.number().int(),
|
|
productId: z.number().int(),
|
|
orderId: z.number().int(),
|
|
createdAt: z.coerce.string().datetime(),
|
|
updatedAt: z.coerce.string().datetime(),
|
|
}),
|
|
);
|
|
export type PaymentInfo = z.infer<typeof paymentInfoModel>;
|