Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 81520a5

Browse files
committedMar 20, 2024
Update ActiveMerchant::Billing::CreditCardMethods to v1.135.0 from upstream
1 parent 4dfe595 commit 81520a5

File tree

2 files changed

+639
-98
lines changed

2 files changed

+639
-98
lines changed
 

‎lib/active_merchant/billing/credit_card_methods.rb‎

Lines changed: 334 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,58 @@
1+
require 'set'
2+
3+
# Updated to v1.135.0
14
module ActiveMerchant #:nodoc:
25
module Billing #:nodoc:
36
# Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object.
47
module CreditCardMethods
5-
CARD_COMPANIES = {
6-
'visa' => /^4\d{12}(\d{3})?(\d{3})?$/,
7-
'master' => /^(5[1-5]\d{4}|677189|222[1-9]\d{2}|22[3-9]\d{3}|2[3-6]\d{4}|27[01]\d{3}|2720\d{2})\d{10}$/,
8-
'discover' => /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/,
9-
'american_express' => /^3[47]\d{13}$/,
10-
'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
11-
'jcb' => /^35(28|29|[3-8]\d)\d{12}$/,
12-
'switch' => /^6759\d{12}(\d{2,3})?$/,
13-
'solo' => /^6767\d{12}(\d{2,3})?$/,
14-
'dankort' => /^5019\d{12}$/,
15-
'maestro' => /^(5[06-8]|6\d)\d{10,17}$/,
16-
'forbrugsforeningen' => /^600722\d{10}$/,
17-
'laser' => /^(6304|6706|6709|6771(?!89))\d{8}(\d{4}|\d{6,7})?$/,
18-
'sodexo' => /^(606071|603389|606070|606069|606068|600818)\d{8}$/,
19-
'vr' => /^(627416|637036)\d{8}$/
8+
CARD_COMPANY_DETECTORS = {
9+
'visa' => ->(num) { num =~ /^4\d{12}(\d{3})?(\d{3})?$/ },
10+
'master' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), MASTERCARD_RANGES) },
11+
'elo' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), ELO_RANGES) },
12+
'cabal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 8), CABAL_RANGES) },
13+
'alelo' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), ALELO_RANGES) },
14+
'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12,15}$/ },
15+
'american_express' => ->(num) { num =~ /^3[47]\d{13}$/ },
16+
'naranja' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), NARANJA_RANGES) },
17+
'diners_club' => ->(num) { num =~ /^3(0[0-5]|[68]\d)\d{11,16}$/ },
18+
'jcb' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 4), JCB_RANGES) },
19+
'dankort' => ->(num) { num =~ /^5019\d{12}$/ },
20+
'maestro' => lambda { |num|
21+
(12..19).cover?(num&.size) && (
22+
in_bin_range?(num.slice(0, 6), MAESTRO_RANGES) ||
23+
MAESTRO_BINS.any? { |bin| num.slice(0, bin.size) == bin }
24+
)
25+
},
26+
'maestro_no_luhn' => ->(num) { num =~ /^(501080|501081|501082)\d{6,13}$/ },
27+
'forbrugsforeningen' => ->(num) { num =~ /^600722\d{10}$/ },
28+
'sodexo' => ->(num) { num =~ /^(606071|603389|606070|606069|606068|600818|505864|505865)\d{10}$/ },
29+
'alia' => ->(num) { num =~ /^(504997|505878|601030|601073|505874)\d{10}$/ },
30+
'vr' => ->(num) { num =~ /^(627416|637036)\d{10}$/ },
31+
'unionpay' => ->(num) { (16..19).cover?(num&.size) && in_bin_range?(num.slice(0, 8), UNIONPAY_RANGES) },
32+
'carnet' => lambda { |num|
33+
num&.size == 16 && (
34+
in_bin_range?(num.slice(0, 6), CARNET_RANGES) ||
35+
CARNET_BINS.any? { |bin| num.slice(0, bin.size) == bin }
36+
)
37+
},
38+
'cartes_bancaires' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), CARTES_BANCAIRES_RANGES) },
39+
'olimpica' => ->(num) { num =~ /^636853\d{10}$/ },
40+
'creditel' => ->(num) { num =~ /^601933\d{10}$/ },
41+
'confiable' => ->(num) { num =~ /^560718\d{10}$/ },
42+
'synchrony' => ->(num) { num =~ /^700600\d{10}$/ },
43+
'routex' => ->(num) { num =~ /^(700676|700678)\d{13}$/ },
44+
'mada' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), MADA_RANGES) },
45+
'bp_plus' => ->(num) { num =~ /^(7050\d\s\d{9}\s\d{3}$|705\d\s\d{8}\s\d{5}$)/ },
46+
'passcard' => ->(num) { num =~ /^628026\d{10}$/ },
47+
'edenred' => ->(num) { num =~ /^637483\d{10}$/ },
48+
'anda' => ->(num) { num =~ /^603199\d{10}$/ },
49+
'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ },
50+
'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) },
51+
'panal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), PANAL_RANGES) }
2052
}
2153

54+
SODEXO_NO_LUHN = ->(num) { num =~ /^(505864|505865)\d{10}$/ }
55+
2256
# http://www.barclaycard.co.uk/business/files/bin_rules.pdf
2357
ELECTRON_RANGES = [
2458
[400115],
@@ -39,20 +73,206 @@ module CreditCardMethods
3973
(491730..491759),
4074
]
4175

76+
CARNET_RANGES = [
77+
(506199..506499),
78+
]
79+
80+
CARNET_BINS = Set.new(
81+
%w[
82+
286900 502275 606333 627535 636318 636379 639388
83+
639484 639559 50633601 50633606 58877274 62753500
84+
60462203 60462204 588772
85+
]
86+
)
87+
88+
CARTES_BANCAIRES_RANGES = [
89+
(507589..507590),
90+
(507593..507595),
91+
[507597],
92+
[560408],
93+
[581752],
94+
(585402..585405),
95+
(585501..585505),
96+
(585577..585582)
97+
]
98+
99+
# https://www.mastercard.us/content/dam/mccom/global/documents/mastercard-rules.pdf, page 73
100+
MASTERCARD_RANGES = [
101+
(222100..272099),
102+
(510000..559999),
103+
[605272],
104+
[606282],
105+
[637095],
106+
[637568],
107+
(637599..637600),
108+
[637609],
109+
]
110+
111+
MAESTRO_BINS = Set.new(
112+
%w[ 500057
113+
501018 501043 501045 501047 501049 501051 501072 501075 501083 501087 501089 501095
114+
501500 501623
115+
501879 502113 502120 502121 502301
116+
503175 503337 503645 503670
117+
504310 504338 504363 504533 504587 504620 504639 504656 504738 504781 504910
118+
507001 507002 507004 507082 507090
119+
560014 560565 561033
120+
572402 572610 572626
121+
576904
122+
578614
123+
581149
124+
585274 585697
125+
586509
126+
588729 588792
127+
589244 589407 589471 589605 589633 589647 589671 589916
128+
590043 590206 590263 590265 590278 590361 590362 590379 590393 590590
129+
591235 591420 591481 591620 591770 591948 591994
130+
592024 592161 592184 592186 592201 592384 592393 592528 592566 592704 592735 592879 592884
131+
593074 593264 593272 593355 593496 593556 593589 593666 593709 593825 593963 593994
132+
594184 594409 594468 594475 594581 594665 594691 594710 594874 594968
133+
595355 595364 595532 595547 595561 595568 595743 595929
134+
596245 596289 596399 596405 596590 596608 596645 596646 596791 596808 596815 596846
135+
597077 597094 597143 597370 597410 597765 597855 597862
136+
598053 598054 598395 598585 598793 598794 598815 598835 598838 598880 598889
137+
599000 599069 599089 599148 599191 599310 599741 599742 599867
138+
601070 601452 601628 601638
139+
602648
140+
603326 603450 603689
141+
604983
142+
606126
143+
608710
144+
627339 627453 627454 627973
145+
636117 636380 636422 636502 636639
146+
637046 637529 637568 637600 637756
147+
639130 639229 639350
148+
690032]
149+
)
150+
151+
# https://www.mastercard.us/content/dam/mccom/global/documents/mastercard-rules.pdf, page 79
152+
MAESTRO_RANGES = [
153+
(500032..500033),
154+
(501015..501016),
155+
(501020..501021),
156+
(501023..501029),
157+
(501038..501041),
158+
(501053..501058),
159+
(501060..501063),
160+
(501066..501067),
161+
(501091..501092),
162+
(501104..501105),
163+
(501107..501108),
164+
(501104..501105),
165+
(501107..501108),
166+
(501800..501899),
167+
(502000..502099),
168+
(503800..503899),
169+
(561200..561269),
170+
(561271..561299),
171+
(561320..561356),
172+
(581700..581751),
173+
(581753..581800),
174+
(589300..589399),
175+
(589998..591259),
176+
(591261..596770),
177+
(596772..598744),
178+
(598746..599999),
179+
(600297..600314),
180+
(600316..600335),
181+
(600337..600362),
182+
(600364..600382),
183+
(601232..601254),
184+
(601256..601276),
185+
(601640..601652),
186+
(601689..601700),
187+
(602011..602048),
188+
[602050],
189+
(630400..630499),
190+
(639000..639099),
191+
(670000..679999),
192+
]
193+
194+
# https://dev.elo.com.br/apis/tabela-de-bins, download csv from left sidebar
195+
ELO_RANGES = [
196+
506707..506708, 506715..506715, 506717..506722, 506724..506736, 506739..506743,
197+
506745..506747, 506753..506753, 506774..506778, 509000..509007, 509009..509014,
198+
509020..509030, 509035..509042, 509044..509089, 509091..509101, 509104..509807,
199+
509831..509877, 509897..509900, 509918..509964, 509971..509986, 509995..509999,
200+
627780..627780, 636297..636298, 636368..636368, 650031..650033, 650035..650051,
201+
650057..650081, 650406..650439, 650485..650504, 650506..650538, 650552..650598,
202+
650720..650727, 650901..650922, 650928..650928, 650938..650939, 650946..650978,
203+
651652..651704, 655000..655019, 655021..655057
204+
]
205+
206+
# Alelo provides BIN ranges by e-mailing them out periodically.
207+
# The BINs beginning with the digit 4 overlap with Visa's range of valid card numbers.
208+
# By placing the 'alelo' entry in CARD_COMPANY_DETECTORS below the 'visa' entry, we
209+
# identify these cards as Visa. This works because transactions with such cards will
210+
# run on Visa rails.
211+
ALELO_RANGES = [
212+
402588..402588, 404347..404347, 405876..405876, 405882..405882, 405884..405884,
213+
405886..405886, 430471..430471, 438061..438061, 438064..438064, 470063..470066,
214+
496067..496067, 506699..506704, 506706..506706, 506713..506714, 506716..506716,
215+
506749..506750, 506752..506752, 506754..506756, 506758..506767, 506770..506771,
216+
506773..506773, 509015..509019, 509880..509882, 509884..509885, 509887..509887,
217+
509987..509992
218+
]
219+
220+
CABAL_RANGES = [
221+
60420100..60440099,
222+
58965700..58965799,
223+
60352200..60352299,
224+
65027200..65027299,
225+
65008700..65008700
226+
]
227+
228+
MADA_RANGES = [
229+
504300..504300, 506968..506968, 508160..508160, 585265..585265, 588848..588848,
230+
588850..588850, 588982..588983, 589005..589005, 589206..589206, 604906..604906,
231+
605141..605141, 636120..636120, 968201..968209, 968211..968211
232+
]
233+
234+
NARANJA_RANGES = [
235+
589562..589562
236+
]
237+
238+
# https://www.discoverglobalnetwork.com/content/dam/discover/en_us/dgn/pdfs/IPP-VAR-Enabler-Compliance.pdf
239+
UNIONPAY_RANGES = [
240+
62000000..62000000, 62212600..62379699, 62400000..62699999, 62820000..62889999,
241+
81000000..81099999, 81100000..81319999, 81320000..81519999, 81520000..81639999, 81640000..81719999
242+
]
243+
244+
JCB_RANGES = [
245+
3528..3589, 3088..3094, 3096..3102, 3112..3120, 3158..3159, 3337..3349
246+
]
247+
248+
HIPERCARD_RANGES = [
249+
384100..384100, 384140..384140, 384160..384160, 606282..606282, 637095..637095,
250+
637568..637568, 637599..637599, 637609..637609, 637612..637612
251+
]
252+
253+
PANAL_RANGES = [[602049]]
254+
42255
def self.included(base)
43256
base.extend(ClassMethods)
44257
end
45258

259+
def self.in_bin_range?(number, ranges)
260+
bin = number.to_i
261+
ranges.any? do |range|
262+
range.include?(bin)
263+
end
264+
end
265+
46266
def valid_month?(month)
47-
(1..12).include?(month.to_i)
267+
(1..12).cover?(month.to_i)
48268
end
49269

50270
def credit_card?
51271
true
52272
end
53273

54274
def valid_expiry_year?(year)
55-
(Time.now.year..Time.now.year + 20).include?(year.to_i)
275+
(Time.now.year..Time.now.year + 20).cover?(year.to_i)
56276
end
57277

58278
def valid_start_year?(year)
@@ -77,7 +297,14 @@ def valid_card_verification_value?(cvv, brand)
77297
end
78298

79299
def card_verification_value_length(brand)
80-
brand == 'american_express' ? 4 : 3
300+
case brand
301+
when 'american_express'
302+
4
303+
when 'maestro'
304+
0
305+
else
306+
3
307+
end
81308
end
82309

83310
def valid_issue_number?(number)
@@ -99,46 +326,27 @@ module ClassMethods
99326
def valid_number?(number)
100327
valid_test_mode_card_number?(number) ||
101328
valid_card_number_length?(number) &&
102-
valid_card_number_characters?(number) &&
103-
valid_checksum?(number)
329+
valid_card_number_characters?(brand?(number), number) &&
330+
valid_by_algorithm?(brand?(number), number)
104331
end
105332

106-
# Regular expressions for the known card companies.
107-
#
108-
# References:
109-
# - http://en.wikipedia.org/wiki/Credit_card_number
110-
# - http://www.barclaycardbusiness.co.uk/information_zone/processing/bin_rules.html
111333
def card_companies
112-
CARD_COMPANIES
334+
CARD_COMPANY_DETECTORS.keys
113335
end
114336

115337
# Returns a string containing the brand of card from the list of known information below.
116-
# Need to check the cards in a particular order, as there is some overlap of the allowable ranges
117-
#--
118-
# TODO Refactor this method. We basically need to tighten up the Maestro Regexp.
119-
#
120-
# Right now the Maestro regexp overlaps with the MasterCard regexp (IIRC). If we can tighten
121-
# things up, we can boil this whole thing down to something like...
122-
#
123-
# def brand?(number)
124-
# return 'visa' if valid_test_mode_card_number?(number)
125-
# card_companies.find([nil]) { |brand, regexp| number =~ regexp }.first.dup
126-
# end
127-
#
128338
def brand?(number)
129339
return 'bogus' if valid_test_mode_card_number?(number)
130340

131-
card_companies.reject { |c,p| c == 'maestro' }.each do |company, pattern|
132-
return company.dup if number =~ pattern
341+
CARD_COMPANY_DETECTORS.each do |company, func|
342+
return company.dup if func.call(number)
133343
end
134344

135-
return 'maestro' if number =~ card_companies['maestro']
136-
137345
return nil
138346
end
139347

140348
def electron?(number)
141-
return false unless [16, 19].include?(number.length)
349+
return false unless [16, 19].include?(number&.length)
142350

143351
# don't recalculate for each range
144352
bank_identification_number = first_digits(number).to_i
@@ -149,16 +357,18 @@ def electron?(number)
149357
end
150358

151359
def type?(number)
152-
ActiveMerchant.deprecated "CreditCard#type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand? instead."
360+
ActiveMerchant.deprecated 'CreditCard#type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand? instead.'
153361
brand?(number)
154362
end
155363

156364
def first_digits(number)
157-
number.to_s.slice(0,6)
365+
number&.slice(0, 6) || ''
158366
end
159367

160368
def last_digits(number)
161-
number.to_s.length <= 4 ? number : number.to_s.slice(-4..-1)
369+
return '' if number.nil?
370+
371+
number.length <= 4 ? number : number.slice(-4..-1)
162372
end
163373

164374
def mask(number)
@@ -171,26 +381,52 @@ def matching_brand?(number, brand)
171381
end
172382

173383
def matching_type?(number, brand)
174-
ActiveMerchant.deprecated "CreditCard#matching_type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#matching_brand? instead."
384+
ActiveMerchant.deprecated 'CreditCard#matching_type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#matching_brand? instead.'
175385
matching_brand?(number, brand)
176386
end
177387

178388
private
179389

180390
def valid_card_number_length?(number) #:nodoc:
181-
number.to_s.length >= 12
391+
return false if number.nil?
392+
393+
number.length >= 12
182394
end
183395

184-
def valid_card_number_characters?(number) #:nodoc:
185-
!number.to_s.match(/\D/)
396+
def valid_card_number_characters?(brand, number) #:nodoc:
397+
return false if number.nil?
398+
return number =~ /\A[0-9 ]+\Z/ if brand == 'bp_plus'
399+
400+
!number.match(/\D/)
186401
end
187402

188403
def valid_test_mode_card_number?(number) #:nodoc:
189404
ActiveMerchant::Billing::Base.test? &&
190-
%w[1 2 3 success failure error].include?(number.to_s)
405+
%w[1 2 3 success failure error].include?(number)
406+
end
407+
408+
def sodexo_no_luhn?(numbers)
409+
SODEXO_NO_LUHN.call(numbers)
410+
end
411+
412+
def valid_by_algorithm?(brand, numbers) #:nodoc:
413+
case brand
414+
when 'naranja'
415+
valid_naranja_algo?(numbers)
416+
when 'creditel'
417+
valid_creditel_algo?(numbers)
418+
when 'alia', 'confiable', 'maestro_no_luhn', 'anda', 'tarjeta-d', 'hipercard'
419+
true
420+
when 'sodexo'
421+
sodexo_no_luhn?(numbers) ? true : valid_luhn?(numbers)
422+
when 'bp_plus', 'passcard', 'edenred'
423+
valid_luhn_non_zero_check_digit?(numbers)
424+
else
425+
valid_luhn?(numbers)
426+
end
191427
end
192428

193-
ODD_LUHN_VALUE = {
429+
BYTES_TO_DIGITS = {
194430
48 => 0,
195431
49 => 1,
196432
50 => 2,
@@ -204,7 +440,7 @@ def valid_test_mode_card_number?(number) #:nodoc:
204440
nil => 0
205441
}.freeze
206442

207-
EVEN_LUHN_VALUE = {
443+
BYTES_TO_DIGITS_DOUBLED = {
208444
48 => 0, # 0 * 2
209445
49 => 2, # 1 * 2
210446
50 => 4, # 2 * 2
@@ -214,29 +450,68 @@ def valid_test_mode_card_number?(number) #:nodoc:
214450
54 => 3, # 6 * 2 - 9
215451
55 => 5, # etc ...
216452
56 => 7,
217-
57 => 9,
453+
57 => 9
218454
}.freeze
219455

220456
# Checks the validity of a card number by use of the Luhn Algorithm.
221457
# Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details.
222458
# This implementation is from the luhn_checksum gem, https://github.com/zendesk/luhn_checksum.
223-
def valid_checksum?(numbers) #:nodoc:
459+
def valid_luhn?(numbers) #:nodoc:
224460
sum = 0
225461

226462
odd = true
227-
numbers.reverse.bytes.each do |number|
463+
numbers.reverse.bytes.each do |bytes|
228464
if odd
229465
odd = false
230-
sum += ODD_LUHN_VALUE[number]
466+
sum += BYTES_TO_DIGITS[bytes]
231467
else
232468
odd = true
233-
sum += EVEN_LUHN_VALUE[number]
469+
sum += BYTES_TO_DIGITS_DOUBLED[bytes]
234470
end
235471
end
236472

237473
sum % 10 == 0
238474
end
475+
476+
def valid_luhn_with_check_digit?(numbers, check_digit)
477+
sum = 0
478+
479+
doubler = true
480+
481+
numbers.reverse.bytes.each do |bytes|
482+
doubler ? sum += BYTES_TO_DIGITS_DOUBLED[bytes] : sum += BYTES_TO_DIGITS[bytes]
483+
doubler = !doubler
484+
end
485+
486+
(10 - (sum % 10)) % 10 == check_digit.to_i
487+
end
488+
489+
def valid_luhn_non_zero_check_digit?(numbers)
490+
return valid_luhn?(numbers.delete(' ')) if numbers[5] == ' '
491+
492+
check_digit = numbers[-1]
493+
luhn_payload = numbers.delete(' ').chop
494+
valid_luhn_with_check_digit?(luhn_payload, check_digit)
495+
end
496+
497+
# Checks the validity of a card number by use of specific algorithms
498+
def valid_naranja_algo?(numbers) #:nodoc:
499+
num_array = numbers.to_s.chars.map(&:to_i)
500+
multipliers = [4, 3, 2, 7, 6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
501+
num_sum = num_array[0..14].zip(multipliers).map { |a, b| a * b }.reduce(:+)
502+
intermediate = 11 - (num_sum % 11)
503+
final_num = intermediate > 9 ? 0 : intermediate
504+
final_num == num_array[15]
505+
end
506+
507+
def valid_creditel_algo?(numbers) #:nodoc:
508+
num_array = numbers.to_s.chars.map(&:to_i)
509+
multipliers = [5, 4, 3, 2, 1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 9]
510+
num_sum = num_array[0..14].zip(multipliers).map { |a, b| a * b }.reduce(:+)
511+
final_num = num_sum % 10
512+
final_num == num_array[15]
513+
end
239514
end
240515
end
241516
end
242-
end
517+
end

‎test/unit/credit_card_methods_test.rb‎

Lines changed: 305 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'test_helper'
22

3+
# Updated to v1.135.0
34
class CreditCardMethodsTest < Test::Unit::TestCase
45
include ActiveMerchant::Billing::CreditCardMethods
56

@@ -9,19 +10,43 @@ class CreditCard
910

1011
def maestro_card_numbers
1112
%w[
12-
5000000000000000 5099999999999999 5600000000000000
13-
5899999999999999 6000000000000000 6999999999999999
14-
6761999999999999 6763000000000000 5038999999999999
13+
5612590000000000 5817500000000000 5818000000000000
14+
6390000000000000 6390700000000000 6390990000000000
15+
6761999999999999 6763000000000000 6799999999999999
16+
5000330000000000 5811499999999999 5010410000000000
17+
5010630000000000 5892440000000000 5016230000000000
1518
]
1619
end
1720

1821
def non_maestro_card_numbers
1922
%w[
2023
4999999999999999 5100000000000000 5599999999999999
21-
5900000000000000 5999999999999999 7000000000000000
24+
5612709999999999 5817520000000000 5818019999999999
25+
5912600000000000 6000009999999999 7000000000000000
2226
]
2327
end
2428

29+
def maestro_bins
30+
%w[500032 500057 501015 501016 501018 501020 501021 501023 501024 501025 501026 501027 501028 501029
31+
501038 501039 501040 501041 501043 501045 501047 501049 501051 501053 501054 501055 501056 501057
32+
501058 501060 501061 501062 501063 501066 501067 501072 501075 501083 501087 501623
33+
501800 501089 501091 501092 501095 501104 501105 501107 501108 501500 501879
34+
502000 502113 502301 503175 503645 503800
35+
503670 504310 504338 504363 504533 504587 504620 504639 504656 504738 504781 504910
36+
507001 507002 507004 507082 507090 560014 560565 561033 572402 572610 572626 576904 578614
37+
585274 585697 586509 588729 588792 589244 589300 589407 589471 589605 589633 589647 589671
38+
590043 590206 590263 590265
39+
590278 590361 590362 590379 590393 590590 591235 591420 591481 591620 591770 591948 591994 592024
40+
592161 592184 592186 592201 592384 592393 592528 592566 592704 592735 592879 592884 593074 593264
41+
593272 593355 593496 593556 593589 593666 593709 593825 593963 593994 594184 594409 594468 594475
42+
594581 594665 594691 594710 594874 594968 595355 595364 595532 595547 595561 595568 595743 595929
43+
596245 596289 596399 596405 596590 596608 596645 596646 596791 596808 596815 596846 597077 597094
44+
597143 597370 597410 597765 597855 597862 598053 598054 598395 598585 598793 598794 598815 598835
45+
598838 598880 598889 599000 599069 599089 599148 599191 599310 599741 599742 599867
46+
601070 604983 601638 606126
47+
630400 636380 636422 636502 636639 637046 637756 639130 639229 690032]
48+
end
49+
2550
def test_should_be_able_to_identify_valid_expiry_months
2651
assert_false valid_month?(-1)
2752
assert_false valid_month?(13)
@@ -47,11 +72,11 @@ def test_should_be_able_to_identify_valid_start_years
4772
end
4873

4974
def test_valid_start_year_can_handle_strings
50-
assert valid_start_year?("2009")
75+
assert valid_start_year?('2009')
5176
end
5277

5378
def test_valid_month_can_handle_strings
54-
assert valid_month?("1")
79+
assert valid_month?('1')
5580
end
5681

5782
def test_valid_expiry_year_can_handle_strings
@@ -99,67 +124,268 @@ def test_should_detect_electron_dk_as_visa
99124

100125
def test_should_detect_diners_club
101126
assert_equal 'diners_club', CreditCard.brand?('36148010000000')
127+
assert_equal 'diners_club', CreditCard.brand?('3000000000000004')
102128
end
103129

104130
def test_should_detect_diners_club_dk
105131
assert_equal 'diners_club', CreditCard.brand?('30401000000000')
106132
end
107133

134+
def test_should_detect_jcb_cards
135+
assert_equal 'jcb', CreditCard.brand?('3528000000000000')
136+
assert_equal 'jcb', CreditCard.brand?('3580000000000000')
137+
assert_equal 'jcb', CreditCard.brand?('3088000000000017')
138+
assert_equal 'jcb', CreditCard.brand?('3094000000000017')
139+
assert_equal 'jcb', CreditCard.brand?('3096000000000000')
140+
assert_equal 'jcb', CreditCard.brand?('3102000000000017')
141+
assert_equal 'jcb', CreditCard.brand?('3112000000000000')
142+
assert_equal 'jcb', CreditCard.brand?('3120000000000017')
143+
assert_equal 'jcb', CreditCard.brand?('3158000000000000')
144+
assert_equal 'jcb', CreditCard.brand?('3159000000000017')
145+
assert_equal 'jcb', CreditCard.brand?('3337000000000000')
146+
assert_equal 'jcb', CreditCard.brand?('3349000000000017')
147+
end
148+
108149
def test_should_detect_maestro_dk_as_maestro
109150
assert_equal 'maestro', CreditCard.brand?('6769271000000000')
110151
end
111152

112153
def test_should_detect_maestro_cards
113-
assert_equal 'maestro', CreditCard.brand?('5020100000000000')
154+
assert_equal 'maestro', CreditCard.brand?('675675000000000')
114155

115156
maestro_card_numbers.each { |number| assert_equal 'maestro', CreditCard.brand?(number) }
157+
maestro_bins.each { |bin| assert_equal 'maestro', CreditCard.brand?("#{bin}0000000000") }
116158
non_maestro_card_numbers.each { |number| assert_not_equal 'maestro', CreditCard.brand?(number) }
117159
end
118160

119161
def test_should_detect_mastercard
120-
assert_equal 'master', CreditCard.brand?('6771890000000000')
162+
assert_equal 'master', CreditCard.brand?('2720890000000000')
121163
assert_equal 'master', CreditCard.brand?('5413031000000000')
164+
assert_equal 'master', CreditCard.brand?('6052721000000000')
165+
assert_equal 'master', CreditCard.brand?('6062821000000000')
166+
assert_equal 'master', CreditCard.brand?('6370951000000000')
167+
assert_equal 'master', CreditCard.brand?('6375681000000000')
168+
assert_equal 'master', CreditCard.brand?('6375991000000000')
169+
assert_equal 'master', CreditCard.brand?('6376091000000000')
122170
end
123171

124172
def test_should_detect_forbrugsforeningen
125173
assert_equal 'forbrugsforeningen', CreditCard.brand?('6007221000000000')
126174
end
127175

128-
def test_should_detect_laser_card
129-
# 16 digits
130-
assert_equal 'laser', CreditCard.brand?('6304985028090561')
176+
def test_should_detect_sodexo_card
177+
assert_equal 'sodexo', CreditCard.brand?('6060694495764400')
178+
end
131179

132-
# 18 digits
133-
assert_equal 'laser', CreditCard.brand?('630498502809056151')
180+
def test_should_detect_alia_card
181+
assert_equal 'alia', CreditCard.brand?('5049970000000000')
182+
assert_equal 'alia', CreditCard.brand?('5058780000000000')
183+
assert_equal 'alia', CreditCard.brand?('6010300000000000')
184+
assert_equal 'alia', CreditCard.brand?('6010730000000000')
185+
assert_equal 'alia', CreditCard.brand?('5058740000000000')
186+
end
134187

135-
# 19 digits
136-
assert_equal 'laser', CreditCard.brand?('6304985028090561515')
188+
def test_should_detect_mada_card
189+
assert_equal 'mada', CreditCard.brand?('5043000000000000')
190+
assert_equal 'mada', CreditCard.brand?('5852650000000000')
191+
assert_equal 'mada', CreditCard.brand?('5888500000000000')
192+
assert_equal 'mada', CreditCard.brand?('6361200000000000')
193+
assert_equal 'mada', CreditCard.brand?('9682040000000000')
194+
end
137195

138-
# 17 digits
139-
assert_not_equal 'laser', CreditCard.brand?('63049850280905615')
196+
def test_alia_number_not_validated
197+
10.times do
198+
number = rand(5058740000000001..5058749999999999).to_s
199+
assert_equal 'alia', CreditCard.brand?(number)
200+
assert CreditCard.valid_number?(number)
201+
end
202+
end
140203

141-
# 15 digits
142-
assert_not_equal 'laser', CreditCard.brand?('630498502809056')
204+
def test_should_detect_confiable_card
205+
assert_equal 'confiable', CreditCard.brand?('5607180000000000')
206+
end
143207

144-
# Alternate format
145-
assert_equal 'laser', CreditCard.brand?('6706950000000000000')
208+
def test_should_detect_bp_plus_card
209+
assert_equal 'bp_plus', CreditCard.brand?('70501 501021600 378')
210+
assert_equal 'bp_plus', CreditCard.brand?('70502 111111111 111')
211+
assert_equal 'bp_plus', CreditCard.brand?('7050 15605297 00114')
212+
assert_equal 'bp_plus', CreditCard.brand?('7050 15546992 00062')
213+
end
146214

147-
# Alternate format (16 digits)
148-
assert_equal 'laser', CreditCard.brand?('6706123456789012')
215+
def test_should_validate_bp_plus_card
216+
assert_true CreditCard.valid_number?('70501 501021600 378')
217+
assert_true CreditCard.valid_number?('7050 15605297 00114')
218+
assert_true CreditCard.valid_number?('7050 15546992 00062')
219+
assert_true CreditCard.valid_number?('7050 16150146 00110')
220+
assert_true CreditCard.valid_number?('7050 16364764 00070')
149221

150-
# New format (16 digits)
151-
assert_equal 'laser', CreditCard.brand?('6709123456789012')
222+
# numbers with invalid formats
223+
assert_false CreditCard.valid_number?('7050_15546992_00062')
224+
assert_false CreditCard.valid_number?('70501 55469920 0062')
225+
assert_false CreditCard.valid_number?('70 501554699 200062')
152226

153-
# Ulster bank (Ireland) with 12 digits
154-
assert_equal 'laser', CreditCard.brand?('677117111234')
227+
# numbers that are luhn-invalid
228+
assert_false CreditCard.valid_number?('70502 111111111 111')
229+
assert_false CreditCard.valid_number?('7050 16364764 00071')
230+
assert_false CreditCard.valid_number?('7050 16364764 00072')
155231
end
156232

157-
def test_should_detect_sodexo_card
158-
assert_equal 'sodexo', CreditCard.brand?('60606944957644')
233+
def test_confiable_number_not_validated
234+
10.times do
235+
number = rand(5607180000000001..5607189999999999).to_s
236+
assert_equal 'confiable', CreditCard.brand?(number)
237+
assert CreditCard.valid_number?(number)
238+
end
239+
end
240+
241+
def test_should_detect_maestro_no_luhn_card
242+
assert_equal 'maestro_no_luhn', CreditCard.brand?('5010800000000000')
243+
assert_equal 'maestro_no_luhn', CreditCard.brand?('5010810000000000')
244+
assert_equal 'maestro_no_luhn', CreditCard.brand?('5010820000000000')
245+
assert_equal 'maestro_no_luhn', CreditCard.brand?('501082000000')
246+
assert_equal 'maestro_no_luhn', CreditCard.brand?('5010820000000000000')
247+
end
248+
249+
def test_maestro_no_luhn_number_not_validated
250+
10.times do
251+
number = rand(5010800000000001..5010829999999999).to_s
252+
assert_equal 'maestro_no_luhn', CreditCard.brand?(number)
253+
assert CreditCard.valid_number?(number)
254+
end
255+
end
256+
257+
def test_should_detect_olimpica_card
258+
assert_equal 'olimpica', CreditCard.brand?('6368530000000000')
259+
end
260+
261+
def test_should_detect_sodexo_no_luhn_card
262+
number1 = '5058645584812145'
263+
number2 = '5058655584812145'
264+
assert_equal 'sodexo', CreditCard.brand?(number1)
265+
assert CreditCard.valid_number?(number1)
266+
assert_equal 'sodexo', CreditCard.brand?(number2)
267+
assert CreditCard.valid_number?(number2)
268+
end
269+
270+
def test_should_validate_sodexo_no_luhn_card
271+
assert_true CreditCard.valid_number?('5058645584812145')
272+
assert_false CreditCard.valid_number?('5058665584812110')
273+
end
274+
275+
def test_should_detect_passcard_card
276+
assert_equal 'passcard', CreditCard.brand?('6280260025383009')
277+
assert_equal 'passcard', CreditCard.brand?('6280260025383280')
278+
assert_equal 'passcard', CreditCard.brand?('6280260025383298')
279+
assert_equal 'passcard', CreditCard.brand?('6280260025383306')
280+
assert_equal 'passcard', CreditCard.brand?('6280260025383314')
281+
end
282+
283+
def test_should_validate_passcard_card
284+
assert_true CreditCard.valid_number?('6280260025383009')
285+
# numbers with invalid formats
286+
assert_false CreditCard.valid_number?('6280_26002538_0005')
287+
# numbers that are luhn-invalid
288+
assert_false CreditCard.valid_number?('6280260025380991')
289+
end
290+
291+
def test_should_detect_edenred_card
292+
assert_equal 'edenred', CreditCard.brand?('6374830000000823')
293+
assert_equal 'edenred', CreditCard.brand?('6374830000000799')
294+
assert_equal 'edenred', CreditCard.brand?('6374830000000807')
295+
assert_equal 'edenred', CreditCard.brand?('6374830000000815')
296+
assert_equal 'edenred', CreditCard.brand?('6374830000000823')
297+
end
298+
299+
def test_should_validate_edenred_card
300+
assert_true CreditCard.valid_number?('6374830000000369')
301+
# numbers with invalid formats
302+
assert_false CreditCard.valid_number?('6374 8300000 00369')
303+
# numbers that are luhn-invalid
304+
assert_false CreditCard.valid_number?('6374830000000111')
305+
end
306+
307+
def test_should_detect_anda_card
308+
assert_equal 'anda', CreditCard.brand?('6031998427187914')
309+
end
310+
311+
# Creditos directos a.k.a tarjeta d
312+
def test_should_detect_tarjetad_card
313+
assert_equal 'tarjeta-d', CreditCard.brand?('6018282227431033')
314+
end
315+
316+
def test_should_detect_creditel_card
317+
assert_equal 'creditel', CreditCard.brand?('6019330047539016')
159318
end
160319

161320
def test_should_detect_vr_card
162-
assert_equal 'vr', CreditCard.brand?('63703644957644')
321+
assert_equal 'vr', CreditCard.brand?('6370364495764400')
322+
assert_equal 'vr', CreditCard.brand?('6274160000000001')
323+
end
324+
325+
def test_should_detect_elo_card
326+
assert_equal 'elo', CreditCard.brand?('5090510000000000')
327+
assert_equal 'elo', CreditCard.brand?('5067530000000000')
328+
assert_equal 'elo', CreditCard.brand?('6277800000000000')
329+
assert_equal 'elo', CreditCard.brand?('6509550000000000')
330+
assert_equal 'elo', CreditCard.brand?('5090890000000000')
331+
assert_equal 'elo', CreditCard.brand?('5092570000000000')
332+
assert_equal 'elo', CreditCard.brand?('5094100000000000')
333+
end
334+
335+
def test_should_detect_alelo_card
336+
assert_equal 'alelo', CreditCard.brand?('5067490000000010')
337+
assert_equal 'alelo', CreditCard.brand?('5067700000000028')
338+
assert_equal 'alelo', CreditCard.brand?('5067600000000036')
339+
assert_equal 'alelo', CreditCard.brand?('5067600000000044')
340+
assert_equal 'alelo', CreditCard.brand?('5099920000000000')
341+
assert_equal 'alelo', CreditCard.brand?('5067630000000000')
342+
assert_equal 'alelo', CreditCard.brand?('5098870000000000')
343+
end
344+
345+
def test_should_detect_naranja_card
346+
assert_equal 'naranja', CreditCard.brand?('5895627823453005')
347+
assert_equal 'naranja', CreditCard.brand?('5895620000000002')
348+
assert_equal 'naranja', CreditCard.brand?('5895626746595650')
349+
end
350+
351+
# Alelo BINs beginning with the digit 4 overlap with Visa's range of valid card numbers.
352+
# We intentionally misidentify these cards as Visa, which works because transactions with
353+
# such cards will run on Visa rails.
354+
def test_should_detect_alelo_number_beginning_with_4_as_visa
355+
assert_equal 'visa', CreditCard.brand?('4025880000000010')
356+
assert_equal 'visa', CreditCard.brand?('4025880000000028')
357+
assert_equal 'visa', CreditCard.brand?('4025880000000036')
358+
assert_equal 'visa', CreditCard.brand?('4025880000000044')
359+
end
360+
361+
def test_should_detect_cabal_card
362+
assert_equal 'cabal', CreditCard.brand?('6044009000000000')
363+
assert_equal 'cabal', CreditCard.brand?('5896575500000000')
364+
assert_equal 'cabal', CreditCard.brand?('6035224400000000')
365+
assert_equal 'cabal', CreditCard.brand?('6502723300000000')
366+
assert_equal 'cabal', CreditCard.brand?('6500870000000000')
367+
end
368+
369+
def test_should_detect_unionpay_card
370+
assert_equal 'unionpay', CreditCard.brand?('6221260000000000')
371+
assert_equal 'unionpay', CreditCard.brand?('6250941006528599')
372+
assert_equal 'unionpay', CreditCard.brand?('6282000000000000')
373+
assert_equal 'unionpay', CreditCard.brand?('8100000000000000')
374+
assert_equal 'unionpay', CreditCard.brand?('814400000000000000')
375+
assert_equal 'unionpay', CreditCard.brand?('8171999927660000')
376+
assert_equal 'unionpay', CreditCard.brand?('8171999900000000021')
377+
assert_equal 'unionpay', CreditCard.brand?('6200000000000005')
378+
end
379+
380+
def test_should_detect_synchrony_card
381+
assert_equal 'synchrony', CreditCard.brand?('7006000000000000')
382+
end
383+
384+
def test_should_detect_routex_card
385+
number = '7006760000000000000'
386+
assert_equal 'routex', CreditCard.brand?(number)
387+
assert CreditCard.valid_number?(number)
388+
assert_equal 'routex', CreditCard.brand?('7006789224703725591')
163389
end
164390

165391
def test_should_detect_when_an_argument_brand_does_not_match_calculated_brand
@@ -168,14 +394,14 @@ def test_should_detect_when_an_argument_brand_does_not_match_calculated_brand
168394
end
169395

170396
def test_detecting_full_range_of_maestro_card_numbers
171-
maestro = '50000000000'
397+
maestro = '63900000000'
172398

173399
assert_equal 11, maestro.length
174400
assert_not_equal 'maestro', CreditCard.brand?(maestro)
175401

176402
while maestro.length < 19
177403
maestro << '0'
178-
assert_equal 'maestro', CreditCard.brand?(maestro)
404+
assert_equal 'maestro', CreditCard.brand?(maestro), "Failed for bin #{maestro}"
179405
end
180406

181407
assert_equal 19, maestro.length
@@ -187,34 +413,67 @@ def test_detecting_full_range_of_maestro_card_numbers
187413
def test_matching_discover_card
188414
assert_equal 'discover', CreditCard.brand?('6011000000000000')
189415
assert_equal 'discover', CreditCard.brand?('6500000000000000')
190-
assert_equal 'discover', CreditCard.brand?('6221260000000000')
191416
assert_equal 'discover', CreditCard.brand?('6450000000000000')
192417

193418
assert_not_equal 'discover', CreditCard.brand?('6010000000000000')
194419
assert_not_equal 'discover', CreditCard.brand?('6600000000000000')
195420
end
196421

197422
def test_matching_invalid_card
198-
assert_nil CreditCard.brand?("XXXXXXXXXXXX0000")
199-
assert_false CreditCard.valid_number?("XXXXXXXXXXXX0000")
423+
assert_nil CreditCard.brand?('XXXXXXXXXXXX0000')
424+
assert_false CreditCard.valid_number?('XXXXXXXXXXXX0000')
425+
assert_false CreditCard.valid_number?(nil)
426+
end
427+
428+
def test_matching_valid_naranja
429+
number = '5895627823453005'
430+
assert_equal 'naranja', CreditCard.brand?(number)
431+
assert CreditCard.valid_number?(number)
432+
end
433+
434+
def test_matching_valid_creditel
435+
number = '6019330047539016'
436+
assert_equal 'creditel', CreditCard.brand?(number)
437+
assert CreditCard.valid_number?(number)
200438
end
201439

202440
def test_16_digit_maestro_uk
203441
number = '6759000000000000'
204442
assert_equal 16, number.length
205-
assert_equal 'switch', CreditCard.brand?(number)
443+
assert_equal 'maestro', CreditCard.brand?(number)
206444
end
207445

208446
def test_18_digit_maestro_uk
209447
number = '675900000000000000'
210448
assert_equal 18, number.length
211-
assert_equal 'switch', CreditCard.brand?(number)
449+
assert_equal 'maestro', CreditCard.brand?(number)
212450
end
213451

214452
def test_19_digit_maestro_uk
215453
number = '6759000000000000000'
216454
assert_equal 19, number.length
217-
assert_equal 'switch', CreditCard.brand?(number)
455+
assert_equal 'maestro', CreditCard.brand?(number)
456+
end
457+
458+
def test_carnet_cards
459+
numbers = %w[
460+
5062280000000000
461+
6046220312312312
462+
6393889871239871
463+
5022751231231231
464+
6275350000000001
465+
]
466+
numbers.each do |num|
467+
assert_equal 16, num.length
468+
assert_equal 'carnet', CreditCard.brand?(num)
469+
end
470+
end
471+
472+
def test_should_detect_cartes_bancaires_cards
473+
assert_equal 'cartes_bancaires', CreditCard.brand?('5855010000000000')
474+
assert_equal 'cartes_bancaires', CreditCard.brand?('5075935000000000')
475+
assert_equal 'cartes_bancaires', CreditCard.brand?('5075901100000000')
476+
assert_equal 'cartes_bancaires', CreditCard.brand?('5075890130000000')
218477
end
219478

220479
def test_electron_cards
@@ -230,6 +489,9 @@ def test_electron_cards
230489
end
231490
end
232491

492+
# nil check
493+
assert_false electron_test.call(nil)
494+
233495
# Visa range
234496
assert_false electron_test.call('4245180000000000')
235497
assert_false electron_test.call('4918810000000000')
@@ -241,7 +503,11 @@ def test_electron_cards
241503
assert_false electron_test.call('42496200000000000')
242504
end
243505

506+
def test_should_detect_panal_card
507+
assert_equal 'panal', CreditCard.brand?('6020490000000000')
508+
end
509+
244510
def test_credit_card?
245511
assert credit_card.credit_card?
246512
end
247-
end
513+
end

0 commit comments

Comments
 (0)
Please sign in to comment.