Files

106 lines
2.3 KiB
TypeScript

export const CARD_BRANDS = [
"visa",
"mastercard",
"unionpay",
"amex",
"diners",
"discover",
"jcb",
] as const
export type CardBrand = (typeof CARD_BRANDS)[number]
export const CARD_BRAND_LABELS: Record<CardBrand, string> = {
visa: "Visa",
mastercard: "Mastercard",
unionpay: "UnionPay",
amex: "American Express",
diners: "Diners Club",
discover: "Discover",
jcb: "JCB",
}
export const CARD_BRAND_LOGOS: Record<CardBrand, string> = {
visa: "/payment-logos/visa.svg",
mastercard: "/payment-logos/mastercard.svg",
unionpay: "/payment-logos/unionpay.svg",
amex: "/payment-logos/amex.svg",
diners: "/payment-logos/diners.svg",
discover: "/payment-logos/discover.svg",
jcb: "/payment-logos/jcb.svg",
}
const CARD_BRAND_ALIASES: Record<string, CardBrand> = {
visa: "visa",
mastercard: "mastercard",
unionpay: "unionpay",
amex: "amex",
americanexpress: "amex",
diners: "diners",
dinersclub: "diners",
discover: "discover",
jcb: "jcb",
}
export function normalizeCardBrand(
input: string | null | undefined
): CardBrand | null {
if (typeof input !== "string") {
return null
}
const key = input
.trim()
.toLowerCase()
.replace(/[\s_-]+/g, "")
return CARD_BRAND_ALIASES[key] ?? null
}
export function getCardBrandLogoUrl(
input: string | null | undefined
): string | null {
const brand = normalizeCardBrand(input)
return brand ? CARD_BRAND_LOGOS[brand] : null
}
type CardNumberResult = {
card: { type?: string } | null
isPotentiallyValid: boolean
}
type CardValidatorApi = {
number?: (value: string) => CardNumberResult
}
type CardValidatorModule = CardValidatorApi & {
default?: CardValidatorApi
}
export async function detectCardBrand(
cardNumber: string
): Promise<CardBrand | null> {
const digits = cardNumber.replace(/\D/g, "")
if (!digits) {
return null
}
try {
const loadedValidator =
(await import("card-validator")) as unknown as CardValidatorModule
const validator = loadedValidator.default ?? loadedValidator
if (typeof validator.number !== "function") {
return null
}
const result = validator.number(digits)
if (!result.card || !result.isPotentiallyValid) {
return null
}
return result.card.type ? normalizeCardBrand(result.card.type) : null
} catch {
return null
}
}