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