UNPKG

2.33 kBPlain TextView Raw
1import luhn10 = require("./luhn-10");
2import getCardTypes = require("credit-card-type");
3import type { Verification } from "./types";
4
5// TODO this should probably come from credit-card-type
6type CreditCardType = {
7 niceType: string;
8 type: string;
9 patterns: Array<number[] | number>;
10 gaps: number[];
11 lengths: number[];
12 code: {
13 name: string;
14 size: number;
15 };
16};
17
18export interface CardNumberVerification extends Verification {
19 card: CreditCardType | null;
20}
21
22type CardNumberOptions = {
23 maxLength?: number;
24 luhnValidateUnionPay?: boolean;
25 skipLuhnValidation?: boolean;
26};
27
28function verification(
29 card: CreditCardType | null,
30 isPotentiallyValid: boolean,
31 isValid: boolean,
32): CardNumberVerification {
33 return {
34 card,
35 isPotentiallyValid,
36 isValid,
37 };
38}
39
40export function cardNumber(
41 value: string | unknown,
42 options: CardNumberOptions = {},
43): CardNumberVerification {
44 let isPotentiallyValid, isValid, maxLength;
45
46 if (typeof value !== "string" && typeof value !== "number") {
47 return verification(null, false, false);
48 }
49
50 const testCardValue = String(value).replace(/-|\s/g, "");
51
52 if (!/^\d*$/.test(testCardValue)) {
53 return verification(null, false, false);
54 }
55
56 const potentialTypes = getCardTypes(testCardValue);
57
58 if (potentialTypes.length === 0) {
59 return verification(null, false, false);
60 } else if (potentialTypes.length !== 1) {
61 return verification(null, true, false);
62 }
63
64 const cardType = potentialTypes[0];
65
66 if (options.maxLength && testCardValue.length > options.maxLength) {
67 return verification(cardType, false, false);
68 }
69
70 if (
71 options.skipLuhnValidation === true ||
72 (cardType.type === getCardTypes.types.UNIONPAY &&
73 options.luhnValidateUnionPay !== true)
74 ) {
75 isValid = true;
76 } else {
77 isValid = luhn10(testCardValue);
78 }
79
80 maxLength = Math.max.apply(null, cardType.lengths);
81 if (options.maxLength) {
82 maxLength = Math.min(options.maxLength, maxLength);
83 }
84
85 for (let i = 0; i < cardType.lengths.length; i++) {
86 if (cardType.lengths[i] === testCardValue.length) {
87 isPotentiallyValid = testCardValue.length < maxLength || isValid;
88
89 return verification(cardType, isPotentiallyValid, isValid);
90 }
91 }
92
93 return verification(cardType, testCardValue.length < maxLength, false);
94}