Line data Source code
1 : // ignore_for_file: always_specify_types
2 :
3 : ///
4 : ///
5 : /// https://github.com/braintree/credit-card-type/blob/main/src/lib/card-types.ts
6 : ///
7 : enum CreditCardType {
8 : /// Visa
9 : visa(
10 : brand: 'Visa',
11 : mask: '#### #### #### #### ###',
12 : lengths: [16, 18, 19],
13 : code: CreditCardCode('CVV', [3]),
14 : patterns: {
15 : Range(4),
16 : },
17 : ),
18 :
19 : /// Mastercard
20 : mastercard(
21 : brand: 'Master',
22 : mask: '#### #### #### ####',
23 : lengths: [16],
24 : code: CreditCardCode('CVC', [3]),
25 : patterns: {
26 : Range(51, 55),
27 : Range(2221, 2229),
28 : Range(223, 229),
29 : Range(23, 26),
30 : Range(270, 271),
31 : Range(2720),
32 : },
33 : extraBrands: ['mastercard'],
34 : ),
35 :
36 : /// American Express
37 : amex(
38 : brand: 'Amex',
39 : mask: '#### ###### #####',
40 : lengths: [15],
41 : code: CreditCardCode('CID', [4]),
42 : patterns: {
43 : Range(34),
44 : Range(37),
45 : },
46 : extraBrands: ['americanexpress'],
47 : ),
48 :
49 : /// Diners Club
50 : dinersclub(
51 : brand: 'Diners',
52 : mask: '#### ###### #########',
53 : lengths: [14, 16, 19],
54 : code: CreditCardCode('CVV', [3]),
55 : patterns: {
56 : Range(300, 305),
57 : Range(36),
58 : Range(38),
59 : Range(39),
60 : },
61 : extraBrands: ['dinersclub'],
62 : ),
63 :
64 : /// Discover
65 : discover(
66 : brand: 'Discover',
67 : mask: '#### #### #### #### ###',
68 : lengths: [16, 19],
69 : code: CreditCardCode('CID', [3]),
70 : patterns: {
71 : Range(6011),
72 : Range(644, 649),
73 : Range(65),
74 : },
75 : ),
76 :
77 : /// JCB
78 : jcb(
79 : brand: 'JCB',
80 : mask: '#### #### #### #### ###',
81 : lengths: [16, 17, 18, 19],
82 : code: CreditCardCode('CVV', [3]),
83 : patterns: {
84 : Range(2131),
85 : Range(1800),
86 : Range(3528, 3589),
87 : },
88 : ),
89 :
90 : /// UnionPay
91 : unionpay(
92 : brand: 'UnionPay',
93 : mask: '#### #### #### #### ###',
94 : lengths: [14, 15, 16, 17, 18, 19],
95 : code: CreditCardCode('CVN', [3]),
96 : patterns: {
97 : Range(620),
98 : Range(62100, 62182),
99 : Range(62184, 62187),
100 : Range(62185, 62197),
101 : Range(62200, 62205),
102 : Range(622010, 622999),
103 : Range(622018),
104 : Range(62207, 62209),
105 : Range(623, 626),
106 : Range(6270),
107 : Range(6272),
108 : Range(6276),
109 : Range(627700, 627779),
110 : Range(627781, 627799),
111 : Range(6282, 6289),
112 : Range(6291),
113 : Range(6292),
114 : Range(810),
115 : Range(8110, 8131),
116 : Range(8132, 8151),
117 : Range(8152, 8163),
118 : Range(8164, 8171),
119 : },
120 : checkLuhn: false,
121 : ),
122 :
123 : /// Maestro
124 : maestro(
125 : brand: 'Maestro',
126 : mask: '#### #### #### #### ###',
127 : lengths: [12, 13, 14, 15, 16, 17, 18, 19],
128 : code: CreditCardCode('CVC', [3]),
129 : patterns: {
130 : Range(493698),
131 : Range(500000, 504174),
132 : Range(504176, 506698),
133 : Range(506779, 508999),
134 : Range(56, 59),
135 : Range(63),
136 : Range(67),
137 : Range(6),
138 : },
139 : ),
140 :
141 : /// Elo
142 : elo(
143 : brand: 'Elo',
144 : mask: '#### #### #### ####',
145 : lengths: [16],
146 : code: CreditCardCode('CVE', [3]),
147 : patterns: {
148 : Range(401178),
149 : Range(401179),
150 : Range(438935),
151 : Range(457631),
152 : Range(457632),
153 : Range(431274),
154 : Range(451416),
155 : Range(457393),
156 : Range(504175),
157 : Range(506699, 506778),
158 : Range(509000, 509999),
159 : Range(627780),
160 : Range(636297),
161 : Range(636368),
162 : Range(650031, 650033),
163 : Range(650035, 650051),
164 : Range(650405, 650439),
165 : Range(650485, 650538),
166 : Range(650541, 650598),
167 : Range(650700, 650718),
168 : Range(650720, 650727),
169 : Range(650901, 650978),
170 : Range(651652, 651679),
171 : Range(655000, 655019),
172 : Range(655021, 655058),
173 : },
174 : ),
175 :
176 : /// Mir
177 : mir(
178 : brand: 'Mir',
179 : mask: '#### #### #### #### ###',
180 : lengths: [16, 17, 18, 19],
181 : code: CreditCardCode('CVP2', [3]),
182 : patterns: {
183 : Range(2200, 2204),
184 : },
185 : ),
186 :
187 : /// Hiper
188 : hiper(
189 : brand: 'Hiper',
190 : mask: '#### #### #### ####',
191 : lengths: [16],
192 : code: CreditCardCode('CVC', [3]),
193 : patterns: {
194 : Range(637095),
195 : Range(63737423),
196 : Range(63743358),
197 : Range(637568),
198 : Range(637599),
199 : Range(637609),
200 : Range(637612),
201 : },
202 : ),
203 :
204 : /// Hipercard
205 : hipercard(
206 : brand: 'Hipercard',
207 : mask: '#### #### #### ####',
208 : lengths: [16],
209 : code: CreditCardCode('CVC', [3]),
210 : patterns: {
211 : Range(606282),
212 : },
213 : ),
214 :
215 : /// Unknown
216 : unknown(
217 : brand: 'Unknown',
218 : mask: '#### #### #### #### ###',
219 : lengths: [12, 13, 14, 15, 16, 17, 18, 19],
220 : code: CreditCardCode('CVV', [3, 4]),
221 : patterns: {},
222 : );
223 :
224 : ///
225 : ///
226 : ///
227 : final String brand;
228 : final String mask;
229 : final List<int> lengths;
230 : final CreditCardCode code;
231 : final Set<Range> patterns;
232 : final bool checkLuhn;
233 : final List<String> extraBrands;
234 :
235 : ///
236 : ///
237 : ///
238 : const CreditCardType({
239 : required this.brand,
240 : required this.mask,
241 : required this.lengths,
242 : required this.code,
243 : required this.patterns,
244 : this.checkLuhn = true,
245 : this.extraBrands = const <String>[],
246 : });
247 :
248 : ///
249 : ///
250 : ///
251 0 : bool validLength(String ccNum) => lengths.contains(clearNum(ccNum).length);
252 :
253 : ///
254 : ///
255 : ///
256 1 : bool validNumber(String ccNum) {
257 1 : if (!checkLuhn) {
258 : return true;
259 : }
260 :
261 2 : if (ccNum.length < 2) {
262 : return false;
263 : }
264 :
265 2 : ccNum = ccNum.replaceAll(RegExp(r'\D'), '');
266 :
267 2 : int mod = ccNum.length % 2;
268 : int sum = 0;
269 :
270 : try {
271 4 : for (int pos = ccNum.length - 1; pos >= 0; pos--) {
272 2 : int digit = int.parse(ccNum[pos]);
273 :
274 2 : if (pos % 2 == mod) {
275 1 : digit *= 2;
276 1 : if (digit > 9) {
277 1 : digit -= 9;
278 : }
279 : }
280 :
281 1 : sum += digit;
282 : }
283 :
284 2 : return sum % 10 == 0;
285 0 : } on Exception {
286 : return false;
287 : }
288 : }
289 :
290 : ///
291 : ///
292 : ///
293 1 : bool cvvCheck(String cvv) =>
294 4 : code.size.contains(cvv.length) &&
295 5 : code.size.contains(clearNum(cvv).length);
296 :
297 : ///
298 : ///
299 : ///
300 3 : static String clearNum(String ccNum) => ccNum.replaceAll(RegExp(r'\D'), '');
301 :
302 : ///
303 : ///
304 : ///
305 1 : static CreditCardType detectType(String ccNum) {
306 1 : ccNum = clearNum(ccNum);
307 :
308 2 : for (final CreditCardType type in CreditCardType.values) {
309 2 : for (final Range range in type.patterns) {
310 1 : if (range.isValid(ccNum)) {
311 : return type;
312 : }
313 : }
314 : }
315 :
316 : return CreditCardType.unknown;
317 : }
318 :
319 : ///
320 : ///
321 : ///
322 1 : static CreditCardType parse(String? value) =>
323 : // ignore: prefer-enums-by-name
324 1 : CreditCardType.values.firstWhere(
325 1 : (CreditCardType type) {
326 3 : String? newValue = value?.replaceAll(RegExp(r'\s'), '').toLowerCase();
327 :
328 3 : return type.brand.toLowerCase() == newValue ||
329 2 : type.extraBrands.fold<bool>(
330 : false,
331 1 : (bool previous, String element) =>
332 2 : previous || element.toLowerCase() == newValue,
333 : );
334 : },
335 1 : orElse: () => CreditCardType.unknown,
336 : );
337 : }
338 :
339 : ///
340 : ///
341 : ///
342 : class CreditCardCode {
343 : final String name;
344 : final List<int> size;
345 :
346 : ///
347 : ///
348 : ///
349 2 : const CreditCardCode(this.name, this.size);
350 : }
351 :
352 : ///
353 : ///
354 : ///
355 : class Range {
356 : final int initialValue;
357 : final int? finalValue;
358 :
359 2 : const Range(this.initialValue, [this.finalValue]);
360 :
361 : ///
362 : ///
363 : ///
364 1 : bool isValid(String ccNum) {
365 3 : int qtd = initialValue.toString().length;
366 :
367 2 : if (ccNum.length < qtd) {
368 : return false;
369 : }
370 :
371 2 : int? ccInt = int.tryParse(ccNum.substring(0, qtd));
372 :
373 : if (ccInt == null) {
374 : return false;
375 : }
376 :
377 1 : return finalValue == null
378 2 : ? ccInt == initialValue
379 4 : : ccInt >= initialValue && ccInt <= finalValue!;
380 : }
381 : }
|