credy 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +38 -0
- data/Rakefile +5 -0
- data/bin/credy +79 -0
- data/credy.gemspec +22 -0
- data/data/americanexpress.yml +8 -0
- data/data/bankcard.yml +3 -0
- data/data/china-unionpay.yml +3 -0
- data/data/diners-club-carte-blanche.yml +3 -0
- data/data/diners-club-enroute.yml +3 -0
- data/data/diners-club-international.yml +3 -0
- data/data/diners-club-us-ca.yml +3 -0
- data/data/instapayment.yml +3 -0
- data/data/jcb.yml +3 -0
- data/data/laser.yml +3 -0
- data/data/maestro.yml +3 -0
- data/data/mastercard.yml +7 -0
- data/data/solo.yml +3 -0
- data/data/switch.yml +3 -0
- data/data/visa-electron.yml +3 -0
- data/data/visa.yml +6 -0
- data/lib/credy.rb +84 -0
- data/lib/credy/check.rb +19 -0
- data/lib/credy/rules.rb +78 -0
- data/lib/credy/string.rb +13 -0
- data/lib/credy/version.rb +3 -0
- data/readme.md +92 -0
- data/spec/bin/credy_spec.rb +131 -0
- data/spec/lib/credy/check_spec.rb +19 -0
- data/spec/lib/credy/rules_spec.rb +167 -0
- data/spec/lib/credy/version_spec.rb +7 -0
- data/spec/lib/credy_spec.rb +104 -0
- data/spec/spec_helper.rb +24 -0
- data/travis.yml +10 -0
- metadata +168 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
credy (0.1.1)
|
5
|
+
thor
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
columnize (0.3.6)
|
11
|
+
debugger (1.3.0)
|
12
|
+
columnize (>= 0.3.1)
|
13
|
+
debugger-linecache (~> 1.1.1)
|
14
|
+
debugger-ruby_core_source (~> 1.1.7)
|
15
|
+
debugger-linecache (1.1.2)
|
16
|
+
debugger-ruby_core_source (>= 1.1.1)
|
17
|
+
debugger-ruby_core_source (1.1.7)
|
18
|
+
diff-lcs (1.1.3)
|
19
|
+
rake (10.0.3)
|
20
|
+
rspec (2.12.0)
|
21
|
+
rspec-core (~> 2.12.0)
|
22
|
+
rspec-expectations (~> 2.12.0)
|
23
|
+
rspec-mocks (~> 2.12.0)
|
24
|
+
rspec-core (2.12.2)
|
25
|
+
rspec-expectations (2.12.1)
|
26
|
+
diff-lcs (~> 1.1.3)
|
27
|
+
rspec-mocks (2.12.2)
|
28
|
+
thor (0.17.0)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
credy!
|
35
|
+
debugger
|
36
|
+
rake
|
37
|
+
rspec
|
38
|
+
rspec-mocks
|
data/Rakefile
ADDED
data/bin/credy
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'credy'
|
5
|
+
|
6
|
+
module Credy
|
7
|
+
|
8
|
+
class CLI < Thor
|
9
|
+
|
10
|
+
desc "generate", "Generate a credit card number"
|
11
|
+
method_option :details, aliases: '-d', desc: "Show card details"
|
12
|
+
method_option :country, aliases: '-c', type: :string, default: 'all', desc: "Card's issuing country (au, fr, us, ca, ...)"
|
13
|
+
method_option :type, aliases: '-t', type: :string, default: 'all', desc: "Card's type (visa, mastercard, americanexpress)"
|
14
|
+
method_option :number, aliases: '-n', type: :numeric, default: 1, desc: "Number of cards"
|
15
|
+
def generate
|
16
|
+
details = options[:details]
|
17
|
+
|
18
|
+
args = {}
|
19
|
+
args[:country] = options[:country] unless options[:country] == 'all'
|
20
|
+
args[:type] = options[:type] unless options[:type] == 'all'
|
21
|
+
|
22
|
+
options[:number].times do
|
23
|
+
number = CreditCard.generate args
|
24
|
+
|
25
|
+
unless number
|
26
|
+
puts 'No rule found for those criteria.'
|
27
|
+
break
|
28
|
+
end
|
29
|
+
|
30
|
+
if details
|
31
|
+
d = [number[:type], number[:country]].compact
|
32
|
+
puts "#{number[:number]} (#{d.join(', ')})"
|
33
|
+
else
|
34
|
+
puts number[:number]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
map 'g' => :generate
|
39
|
+
|
40
|
+
desc 'infos NUMBER', 'Show information about a credit card number'
|
41
|
+
def infos(number)
|
42
|
+
infos = CreditCard.infos number
|
43
|
+
if infos
|
44
|
+
puts "Type: #{infos[:type]}"
|
45
|
+
puts "Country: #{infos[:country]}" if infos[:country]
|
46
|
+
if CreditCard.validate(number)[:valid]
|
47
|
+
puts "Valid".green
|
48
|
+
else
|
49
|
+
puts "Not valid".red
|
50
|
+
end
|
51
|
+
else
|
52
|
+
puts 'No information available for this number.'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
map 'i' => :infos
|
56
|
+
|
57
|
+
desc 'validate NUMBER', 'Check if a credit card number is valid or not'
|
58
|
+
def validate(number)
|
59
|
+
validity = CreditCard.validate number
|
60
|
+
|
61
|
+
if validity[:valid]
|
62
|
+
puts 'This number is valid.'.green
|
63
|
+
else
|
64
|
+
puts 'This number is not valid.'.red
|
65
|
+
end
|
66
|
+
|
67
|
+
details = []
|
68
|
+
validity[:details].each do |criteria, value|
|
69
|
+
value = value ? 'v'.green : 'x'.red
|
70
|
+
details << "#{criteria}: #{value}"
|
71
|
+
end
|
72
|
+
puts "(#{details.join(', ')})"
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
Credy::CLI.start
|
data/credy.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path '../lib/credy/version', __FILE__
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.authors = ['Tim Petricola']
|
7
|
+
s.email = ['hi@timpetricola.com']
|
8
|
+
s.summary = %q{A simple (but powerful) credit card number generator}
|
9
|
+
s.homepage = ''
|
10
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
11
|
+
s.files = `git ls-files`.split "\n"
|
12
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split "\n"
|
13
|
+
s.name = 'credy'
|
14
|
+
s.require_paths = ['lib']
|
15
|
+
s.version = Credy::VERSION
|
16
|
+
|
17
|
+
s.add_development_dependency 'rspec'
|
18
|
+
s.add_development_dependency 'rspec-mocks'
|
19
|
+
s.add_development_dependency 'rake'
|
20
|
+
s.add_development_dependency 'debugger'
|
21
|
+
s.add_runtime_dependency 'thor'
|
22
|
+
end
|
data/data/bankcard.yml
ADDED
data/data/jcb.yml
ADDED
@@ -0,0 +1,3 @@
|
|
1
|
+
jcb:
|
2
|
+
length: 16
|
3
|
+
prefix: ['3528', '3529', '3530', '3531', '3532', '3533', '3534', '3535', '3536', '3537', '3538', '3539', '3540', '3541', '3542', '3543', '3544', '3545', '3546', '3547', '3548', '3549', '3550', '3551', '3552', '3553', '3554', '3555', '3556', '3557', '3558', '3559', '3560', '3561', '3562', '3563', '3564', '3565', '3566', '3567', '3568', '3569', '3570', '3571', '3572', '3573', '3574', '3575', '3576', '3577', '3578', '3579', '3580', '3581', '3582', '3583', '3584', '3585', '3586', '3587', '3588', '3589']
|
data/data/laser.yml
ADDED
data/data/maestro.yml
ADDED
data/data/mastercard.yml
ADDED
data/data/solo.yml
ADDED
data/data/switch.yml
ADDED
data/data/visa.yml
ADDED
data/lib/credy.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'credy/version'
|
2
|
+
require 'credy/string'
|
3
|
+
require 'credy/rules'
|
4
|
+
require 'credy/check'
|
5
|
+
|
6
|
+
module Credy
|
7
|
+
|
8
|
+
def self.root
|
9
|
+
File.expand_path '../..', __FILE__
|
10
|
+
end
|
11
|
+
|
12
|
+
class CreditCard
|
13
|
+
|
14
|
+
# Generate a credit card number
|
15
|
+
def self.generate(options = {})
|
16
|
+
|
17
|
+
# Include global rules (not based on country)
|
18
|
+
include_global_rules = options[:country].nil?
|
19
|
+
rule = Rules.filter(options, include_global_rules).sample
|
20
|
+
|
21
|
+
return nil unless rule
|
22
|
+
|
23
|
+
length = rule[:length].is_a?(Array) ? rule[:length].sample : rule[:length]
|
24
|
+
number = rule[:prefix]
|
25
|
+
|
26
|
+
# Generates n-1 digits
|
27
|
+
(length - number.length - 1).times do
|
28
|
+
number = number + rand(10).to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
# Generates the last digit according to luhn algorithm
|
32
|
+
l = nil
|
33
|
+
digits = (0..9).to_a.map(&:to_s)
|
34
|
+
begin
|
35
|
+
l = digits.delete digits.sample
|
36
|
+
end while !Check.luhn number+l
|
37
|
+
|
38
|
+
number = number+l
|
39
|
+
|
40
|
+
{
|
41
|
+
number: number,
|
42
|
+
type: rule[:type],
|
43
|
+
country: rule[:country]
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns information about a number
|
48
|
+
def self.infos(number)
|
49
|
+
rules = Rules.flatten(true).select do |rule|
|
50
|
+
valid = true
|
51
|
+
|
52
|
+
# Check number of digits
|
53
|
+
lengths = rule[:length].is_a?(Array) ? rule[:length] : [rule[:length]]
|
54
|
+
valid = false unless lengths.include? number.length
|
55
|
+
|
56
|
+
# Check prefix
|
57
|
+
valid = false unless !(number =~ Regexp.new("^#{rule[:prefix]}")).nil?
|
58
|
+
valid
|
59
|
+
end
|
60
|
+
|
61
|
+
if rules
|
62
|
+
rules[0]
|
63
|
+
else
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Validates a number
|
69
|
+
def self.validate(number)
|
70
|
+
criterii = {}
|
71
|
+
criterii[:luhn] = Check.luhn number
|
72
|
+
criterii[:type] = !!self.infos(number)
|
73
|
+
|
74
|
+
valid = criterii.all? { |_, v| v == true }
|
75
|
+
|
76
|
+
{
|
77
|
+
valid: valid,
|
78
|
+
details: criterii
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
data/lib/credy/check.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Credy
|
2
|
+
|
3
|
+
class Check
|
4
|
+
|
5
|
+
def self.luhn(code)
|
6
|
+
s1 = s2 = 0
|
7
|
+
code.to_s.reverse.chars.each_slice(2) do |odd, even|
|
8
|
+
s1 += odd.to_i
|
9
|
+
|
10
|
+
double = even.to_i * 2
|
11
|
+
double -= 9 if double >= 10
|
12
|
+
s2 += double
|
13
|
+
end
|
14
|
+
(s1 + s2) % 10 == 0
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/credy/rules.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Credy
|
4
|
+
|
5
|
+
class Rules
|
6
|
+
|
7
|
+
# Return all the rules from yml files
|
8
|
+
def self.all
|
9
|
+
@rules ||= begin
|
10
|
+
rules = {}
|
11
|
+
Dir.glob "#{Credy.root}/data/*.yml" do |filename|
|
12
|
+
r = YAML::load IO.read(filename)
|
13
|
+
rules.merge! r
|
14
|
+
end
|
15
|
+
rules
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Change hash format to process rules
|
20
|
+
def self.flatten(global_rules = false)
|
21
|
+
rules = []
|
22
|
+
|
23
|
+
all.each do |type, details|
|
24
|
+
|
25
|
+
if global_rules
|
26
|
+
# Add general rules
|
27
|
+
global_prefixes = details['prefix']
|
28
|
+
if global_prefixes
|
29
|
+
global_prefixes = [global_prefixes] unless global_prefixes.is_a? Array
|
30
|
+
|
31
|
+
global_prefixes.each do |prefix|
|
32
|
+
rules.push({
|
33
|
+
prefix: prefix.to_s,
|
34
|
+
length: details['length'],
|
35
|
+
type: type
|
36
|
+
})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Process each country
|
42
|
+
if details['countries']
|
43
|
+
details['countries'].each do |country, prefixes|
|
44
|
+
prefixes = [prefixes] unless prefixes.is_a? Array
|
45
|
+
|
46
|
+
# Add a rule for each prefix
|
47
|
+
prefixes.each do |prefix|
|
48
|
+
rules.push({
|
49
|
+
prefix: prefix.to_s,
|
50
|
+
length: details['length'],
|
51
|
+
type: type,
|
52
|
+
country: country,
|
53
|
+
})
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Sort rules by prefix length
|
61
|
+
rules.sort! { |x, y| y[:prefix].length <=> x[:prefix].length }
|
62
|
+
|
63
|
+
rules
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns rules according to given filters
|
67
|
+
def self.filter(filters = {}, global_rules = false)
|
68
|
+
flatten(global_rules).select do |rule|
|
69
|
+
[:country, :type].each do |condition|
|
70
|
+
break false if filters[condition] && filters[condition] != rule[condition]
|
71
|
+
true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/lib/credy/string.rb
ADDED
data/readme.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Credy
|
2
|
+
|
3
|
+
A simple credit card generator.
|
4
|
+
|
5
|
+
## Functionnalities
|
6
|
+
* Generates a valid number (per country/type)
|
7
|
+
* Get information for a number
|
8
|
+
* Check validity of a number
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
`gem install credy`
|
13
|
+
|
14
|
+
## CLI usage
|
15
|
+
|
16
|
+
### Generate
|
17
|
+
```
|
18
|
+
credy generate --country au --type visa
|
19
|
+
```
|
20
|
+
|
21
|
+
### Get informations
|
22
|
+
```
|
23
|
+
credy infos 5108756163954799
|
24
|
+
```
|
25
|
+
|
26
|
+
### Validate
|
27
|
+
```
|
28
|
+
credy validate 5108756163954799
|
29
|
+
```
|
30
|
+
|
31
|
+
## Ruby usage
|
32
|
+
|
33
|
+
### Generate
|
34
|
+
``` ruby
|
35
|
+
options {
|
36
|
+
:country => 'au',
|
37
|
+
:type => 'mastercard'
|
38
|
+
}
|
39
|
+
Credy::CreditCard.generate options
|
40
|
+
```
|
41
|
+
|
42
|
+
### Get informations
|
43
|
+
``` ruby
|
44
|
+
Credy::CreditCard.infos '5108756163954799'
|
45
|
+
```
|
46
|
+
|
47
|
+
### Validate
|
48
|
+
``` ruby
|
49
|
+
Credy::CreditCard.validate '5108756163954799'
|
50
|
+
```
|
51
|
+
|
52
|
+
## Supported cards
|
53
|
+
|
54
|
+
At the moment, only a few types and countries are (partially) supported.
|
55
|
+
|
56
|
+
### Types
|
57
|
+
* americanexpress (American Express)
|
58
|
+
* bankcard (Bankcard)
|
59
|
+
* china-unionpay (China UnionPay)
|
60
|
+
* diners-club-carte-blanche (Diners Club Carte Blanche)
|
61
|
+
* diners-club-enroute (Diners Club enRoute)
|
62
|
+
* diners-club-international (Diners Club International)
|
63
|
+
* diners-club-us-ca (Diners Club United States & Canada)
|
64
|
+
* instapayment (InstaPayment)
|
65
|
+
* jcb (JCB)
|
66
|
+
* laser (Laser)
|
67
|
+
* maestro (Maestro)
|
68
|
+
* mastercard (Mastercard)
|
69
|
+
* solo (Solo)
|
70
|
+
* switch (Switch)
|
71
|
+
* visa (Visa)
|
72
|
+
* visa-electron (Visa Electron)
|
73
|
+
|
74
|
+
### Countries
|
75
|
+
* au (Australia)
|
76
|
+
* ca (Canada)
|
77
|
+
* fr (France)
|
78
|
+
* pl (Poland)
|
79
|
+
* es (Spain)
|
80
|
+
* ch (Switzerland)
|
81
|
+
* uk (United Kingdom)
|
82
|
+
* us (United States)
|
83
|
+
|
84
|
+
## Todo
|
85
|
+
* Add more data (see the Data source section)
|
86
|
+
* Remove luhn validation for *China UnionPay* and *Diners Club enRoute*
|
87
|
+
|
88
|
+
## Data source
|
89
|
+
All data is coming from the [Bank card number](http://en.wikipedia.org/wiki/Bank_card_number) page and the [List of Issuer Identification Numbers](http://en.wikipedia.org/wiki/List_of_Issuer_Identification_Numbers) on [Wikipedia](http://wikipedia.org). I do not assume the responsibility for wrong data.
|
90
|
+
|
91
|
+
## License
|
92
|
+
Credy is released under the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
silent { load 'bin/credy' }
|
4
|
+
|
5
|
+
describe Credy::CLI do
|
6
|
+
|
7
|
+
before :all do
|
8
|
+
@stdout = $stdout
|
9
|
+
$stdout = StringIO.new
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'generate' do
|
13
|
+
|
14
|
+
it 'works without options' do
|
15
|
+
Credy::CreditCard.should_receive(:generate).with({})
|
16
|
+
r = Credy::CLI.start ['generate']
|
17
|
+
end
|
18
|
+
|
19
|
+
it '--country' do
|
20
|
+
Credy::CreditCard.should_receive(:generate).with(country: 'au').twice
|
21
|
+
Credy::CLI.start ['generate', '--country', 'au']
|
22
|
+
Credy::CLI.start ['generate', '-c', 'au']
|
23
|
+
end
|
24
|
+
|
25
|
+
it '--type' do
|
26
|
+
Credy::CreditCard.should_receive(:generate).with(type: 'visa').twice
|
27
|
+
Credy::CLI.start ['generate', '--type', 'visa']
|
28
|
+
Credy::CLI.start ['generate', '-t', 'visa']
|
29
|
+
end
|
30
|
+
|
31
|
+
it '--number' do
|
32
|
+
Credy::CreditCard.should_receive(:generate).exactly(20).and_return({number: '50076645747856835'})
|
33
|
+
Credy::CLI.start ['generate', '--number', 10]
|
34
|
+
Credy::CLI.start ['generate', '-n', 10]
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'result' do
|
38
|
+
|
39
|
+
before :all do
|
40
|
+
$stdout = @stdout
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'stops if no number is found' do
|
44
|
+
Credy::CreditCard.should_receive(:generate).once.and_return(nil)
|
45
|
+
STDOUT.should_receive(:puts).with('No rule found for those criteria.').once
|
46
|
+
Credy::CLI.start ['generate', '-n', 10]
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '--details' do
|
50
|
+
before do
|
51
|
+
number = {number: '50076645747856835', type: 'visa', country: 'au'}
|
52
|
+
Credy::CreditCard.should_receive(:generate).any_number_of_times.and_return number
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'only returns the number' do
|
56
|
+
STDOUT.should_receive(:puts).with('50076645747856835').once
|
57
|
+
Credy::CLI.start ['generate']
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'accepts to return more details' do
|
61
|
+
STDOUT.should_receive(:puts).with('50076645747856835 (visa, au)').twice
|
62
|
+
Credy::CLI.start ['generate', '--details']
|
63
|
+
Credy::CLI.start ['generate', '-d']
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'infos' do
|
72
|
+
|
73
|
+
it 'calls the infos function' do
|
74
|
+
Credy::CreditCard.should_receive(:infos).with '5108756163954799'
|
75
|
+
Credy::CLI.start ['infos', '5108756163954799']
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'result' do
|
79
|
+
before :all do
|
80
|
+
$stdout = @stdout
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'shows error if nothing found' do
|
84
|
+
Credy::CreditCard.should_receive(:infos).and_return nil
|
85
|
+
STDOUT.should_receive(:puts).with 'No information available for this number.'
|
86
|
+
Credy::CLI.start ['infos', '5108756163954799']
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'shows the card informations' do
|
90
|
+
number = {number: '50076645747856835', type: 'visa', country: 'au'}
|
91
|
+
Credy::CreditCard.should_receive(:infos).at_least(1).times.and_return number
|
92
|
+
STDOUT.should_receive(:puts).with 'Type: visa'
|
93
|
+
STDOUT.should_receive(:puts).with 'Country: au'
|
94
|
+
STDOUT.should_receive(:puts).with 'Valid'
|
95
|
+
Credy::CLI.start ['infos', '50076645747856835']
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'validate' do
|
102
|
+
it 'calls the validate function' do
|
103
|
+
Credy::CreditCard.should_receive(:validate).with('5108756163954799').and_return({valid: false, details: {}})
|
104
|
+
Credy::CLI.start ['validate', '5108756163954799']
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'result' do
|
108
|
+
before :all do
|
109
|
+
$stdout = @stdout
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'shows card validity and details' do
|
113
|
+
validity = { valid: true, details: { luhn: true, type: true} }
|
114
|
+
Credy::CreditCard.should_receive(:validate).and_return validity
|
115
|
+
STDOUT.should_receive(:puts).with 'This number is valid.'
|
116
|
+
STDOUT.should_receive(:puts).with '(luhn: v, type: v)'
|
117
|
+
Credy::CLI.start ['validate', '50076645747856835']
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'shows card validity and details for invalid number' do
|
121
|
+
validity = { valid: false, details: { luhn: false, type: false} }
|
122
|
+
Credy::CreditCard.should_receive(:validate).and_return validity
|
123
|
+
STDOUT.should_receive(:puts).with 'This number is not valid.'
|
124
|
+
STDOUT.should_receive(:puts).with '(luhn: x, type: x)'
|
125
|
+
Credy::CLI.start ['validate', '5108756163954799']
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Credy::Check do
|
4
|
+
|
5
|
+
describe '.luhn' do
|
6
|
+
|
7
|
+
it 'returns true for correct data' do
|
8
|
+
Credy::Check.luhn(49927398716).should be_true
|
9
|
+
Credy::Check.luhn(1234567812345670).should be_true
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns false for non correct data' do
|
13
|
+
Credy::Check.luhn(49927398717).should be_false
|
14
|
+
Credy::Check.luhn(1234567812345678).should be_false
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Credy::Rules do
|
4
|
+
|
5
|
+
subject { Credy::Rules }
|
6
|
+
|
7
|
+
describe '.all' do
|
8
|
+
|
9
|
+
it { should respond_to :all }
|
10
|
+
its(:all) { should be_a Hash }
|
11
|
+
|
12
|
+
it 'should be tested more'
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.flatten' do
|
17
|
+
|
18
|
+
it { should respond_to :flatten }
|
19
|
+
its(:flatten) { should be_a Array }
|
20
|
+
|
21
|
+
it 'flattens a rules hash' do
|
22
|
+
subject.stub(:all).and_return({
|
23
|
+
'visa' => {
|
24
|
+
'length' => [13, 16],
|
25
|
+
'countries' => {
|
26
|
+
'ch' => '404159',
|
27
|
+
'au' => '401795'
|
28
|
+
}
|
29
|
+
},
|
30
|
+
'mastercard' => {
|
31
|
+
'length' => 16,
|
32
|
+
'countries' => {
|
33
|
+
'us' => '504837'
|
34
|
+
}
|
35
|
+
},
|
36
|
+
})
|
37
|
+
|
38
|
+
subject.flatten.should == [
|
39
|
+
{:prefix=>"404159", :length=>[13, 16], :type=>"visa", :country=>"ch"},
|
40
|
+
{:prefix=>"401795", :length=>[13, 16], :type=>"visa", :country=>"au"},
|
41
|
+
{:prefix=>"504837", :length=>16, :type=>"mastercard", :country=>"us"}
|
42
|
+
]
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'works with string prefixes' do
|
46
|
+
subject.stub(:all).and_return({
|
47
|
+
'visa' => {
|
48
|
+
'length' => [13, 16],
|
49
|
+
'countries' => {
|
50
|
+
'au' => '401795'
|
51
|
+
}
|
52
|
+
}
|
53
|
+
})
|
54
|
+
|
55
|
+
subject.flatten.should == [
|
56
|
+
{:prefix=>"401795", :length=>[13, 16], :type=>"visa", :country=>"au"}
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'works with integer prefixes' do
|
61
|
+
subject.stub(:all).and_return({
|
62
|
+
'visa' => {
|
63
|
+
'length' => [13, 16],
|
64
|
+
'countries' => {
|
65
|
+
'au' => 401795
|
66
|
+
}
|
67
|
+
}
|
68
|
+
})
|
69
|
+
|
70
|
+
subject.flatten.should == [
|
71
|
+
{:prefix=>"401795", :length=>[13, 16], :type=>"visa", :country=>"au"}
|
72
|
+
]
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'works with an array of prefixes' do
|
76
|
+
subject.stub(:all).and_return({
|
77
|
+
'visa' => {
|
78
|
+
'length' => [13, 16],
|
79
|
+
'countries' => {
|
80
|
+
'au' => ['401795', '404137']
|
81
|
+
}
|
82
|
+
}
|
83
|
+
})
|
84
|
+
|
85
|
+
subject.flatten.should == [
|
86
|
+
{:prefix=>"401795", :length=>[13, 16], :type=>"visa", :country=>"au"},
|
87
|
+
{:prefix=>"404137", :length=>[13, 16], :type=>"visa", :country=>"au"}
|
88
|
+
]
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'includes global rules' do
|
92
|
+
subject.stub(:all).and_return({
|
93
|
+
'visa' => {
|
94
|
+
'length' => [13, 16],
|
95
|
+
'prefix' => '4',
|
96
|
+
'countries' => {
|
97
|
+
'ch' => '404159',
|
98
|
+
'au' => '401'
|
99
|
+
}
|
100
|
+
},
|
101
|
+
'mastercard' => {
|
102
|
+
'length' => 16,
|
103
|
+
'prefix' => '51'
|
104
|
+
},
|
105
|
+
})
|
106
|
+
|
107
|
+
subject.flatten(true).should == [
|
108
|
+
{:prefix=>"404159", :length=>[13, 16], :type=>"visa", :country=>"ch"},
|
109
|
+
{:prefix=>"401", :length=>[13, 16], :type=>"visa", :country=>"au"},
|
110
|
+
{:prefix=>"51", :length=>16, :type=>"mastercard"},
|
111
|
+
{:prefix=>"4", :length=>[13, 16], :type=>"visa"}
|
112
|
+
]
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '.filter' do
|
118
|
+
|
119
|
+
before do
|
120
|
+
subject.stub(:all).and_return({
|
121
|
+
'visa' => {
|
122
|
+
'length' => [13, 16],
|
123
|
+
'countries' => {
|
124
|
+
'ch' => '404159',
|
125
|
+
'au' => '401795'
|
126
|
+
}
|
127
|
+
},
|
128
|
+
'mastercard' => {
|
129
|
+
'length' => 16,
|
130
|
+
'countries' => {
|
131
|
+
'au' => '401795'
|
132
|
+
}
|
133
|
+
},
|
134
|
+
})
|
135
|
+
end
|
136
|
+
|
137
|
+
it { should respond_to :filter }
|
138
|
+
|
139
|
+
it 'returns everything if no filter is provided' do
|
140
|
+
subject.filter.should be_a Array
|
141
|
+
subject.filter.should == subject.flatten
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'filters by type' do
|
145
|
+
subject.filter(type: 'visa').length.should == 2
|
146
|
+
subject.filter(type: 'mastercard').length.should == 1
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'accepts the :country option' do
|
150
|
+
subject.filter(country: 'ch').length.should == 1
|
151
|
+
subject.filter(country: 'au').length.should == 2
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'accepts several options at the same time' do
|
155
|
+
rules = subject.filter type: 'visa', country: 'au'
|
156
|
+
rules.length.should == 1
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'returns an empty array if nothing is found' do
|
160
|
+
rules = subject.filter type: 'foo', country: 'bar'
|
161
|
+
rules.should be_a Array
|
162
|
+
rules.should be_empty
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Credy::CreditCard do
|
4
|
+
|
5
|
+
subject { Credy::CreditCard }
|
6
|
+
|
7
|
+
let(:rules) do
|
8
|
+
{
|
9
|
+
'visa' => {
|
10
|
+
'length' => [13, 16],
|
11
|
+
'countries' => {
|
12
|
+
'ch' => '404159',
|
13
|
+
'au' => ['401795', '404137']
|
14
|
+
}
|
15
|
+
},
|
16
|
+
'mastercard' => {
|
17
|
+
'length' => 16,
|
18
|
+
'countries' => {
|
19
|
+
'au' => '401795',
|
20
|
+
'us' => ['504837', '510875']
|
21
|
+
}
|
22
|
+
},
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
before(:each) do
|
27
|
+
Credy::Rules.stub(:all).and_return rules
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.generate' do
|
31
|
+
|
32
|
+
it 'returns a number, type and country' do
|
33
|
+
subject.generate[:number].should_not be_nil
|
34
|
+
subject.generate[:type].should_not be_nil
|
35
|
+
subject.generate[:country].should_not be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'accepts the :type option' do
|
39
|
+
number = subject.generate type: 'visa'
|
40
|
+
number[:type].should == 'visa'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'accepts the :country option' do
|
44
|
+
number = subject.generate country: 'ch'
|
45
|
+
number[:country].should == 'ch'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'accepts several options at the same time' do
|
49
|
+
number = subject.generate type: 'mastercard', country: 'au'
|
50
|
+
number[:type].should == 'mastercard'
|
51
|
+
number[:country].should == 'au'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'generates the right number of digits' do
|
55
|
+
number = subject.generate type: 'mastercard'
|
56
|
+
number[:number].length.should == 16
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'generate a number with the right prefix' do
|
60
|
+
number = subject.generate type: 'mastercard', country: 'au'
|
61
|
+
number[:number].should =~ /^401795/
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns nil if nothing is found' do
|
65
|
+
number = subject.generate type: 'foo', country: 'bar'
|
66
|
+
number.should be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '.infos' do
|
72
|
+
|
73
|
+
it 'returns the correct information according to the card number' do
|
74
|
+
infos = subject.infos '5108756163954792'
|
75
|
+
infos[:type].should == 'mastercard'
|
76
|
+
infos[:country].should == 'us'
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '.validate' do
|
82
|
+
|
83
|
+
it 'returns a hash' do
|
84
|
+
r = subject.validate '5108756163954792'
|
85
|
+
r[:valid].should be_true
|
86
|
+
r[:details][:luhn].should be_true
|
87
|
+
r[:details][:type].should be_true
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'checks against luhn algorithm' do
|
91
|
+
r = subject.validate '5108756163954791'
|
92
|
+
r[:valid].should be_false
|
93
|
+
r[:details][:luhn].should be_false
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'checks against card type' do
|
97
|
+
r = subject.validate '99999999999999999999992'
|
98
|
+
r[:valid].should be_false
|
99
|
+
r[:details][:type].should be_false
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'credy'
|
5
|
+
|
6
|
+
def silent
|
7
|
+
_stdout = $stdout
|
8
|
+
$stdout = StringIO.new
|
9
|
+
begin
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
$stdout = _stdout
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Don't colorize strings in test environment
|
17
|
+
class String
|
18
|
+
def colorize(color_code)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
end
|
data/travis.yml
ADDED
metadata
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: credy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tim Petricola
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec-mocks
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: debugger
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: thor
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description:
|
95
|
+
email:
|
96
|
+
- hi@timpetricola.com
|
97
|
+
executables:
|
98
|
+
- credy
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- .gitignore
|
103
|
+
- Gemfile
|
104
|
+
- Gemfile.lock
|
105
|
+
- Rakefile
|
106
|
+
- bin/credy
|
107
|
+
- credy.gemspec
|
108
|
+
- data/americanexpress.yml
|
109
|
+
- data/bankcard.yml
|
110
|
+
- data/china-unionpay.yml
|
111
|
+
- data/diners-club-carte-blanche.yml
|
112
|
+
- data/diners-club-enroute.yml
|
113
|
+
- data/diners-club-international.yml
|
114
|
+
- data/diners-club-us-ca.yml
|
115
|
+
- data/instapayment.yml
|
116
|
+
- data/jcb.yml
|
117
|
+
- data/laser.yml
|
118
|
+
- data/maestro.yml
|
119
|
+
- data/mastercard.yml
|
120
|
+
- data/solo.yml
|
121
|
+
- data/switch.yml
|
122
|
+
- data/visa-electron.yml
|
123
|
+
- data/visa.yml
|
124
|
+
- lib/credy.rb
|
125
|
+
- lib/credy/check.rb
|
126
|
+
- lib/credy/rules.rb
|
127
|
+
- lib/credy/string.rb
|
128
|
+
- lib/credy/version.rb
|
129
|
+
- readme.md
|
130
|
+
- spec/bin/credy_spec.rb
|
131
|
+
- spec/lib/credy/check_spec.rb
|
132
|
+
- spec/lib/credy/rules_spec.rb
|
133
|
+
- spec/lib/credy/version_spec.rb
|
134
|
+
- spec/lib/credy_spec.rb
|
135
|
+
- spec/spec_helper.rb
|
136
|
+
- travis.yml
|
137
|
+
homepage: ''
|
138
|
+
licenses: []
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ! '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ! '>='
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 1.8.24
|
158
|
+
signing_key:
|
159
|
+
specification_version: 3
|
160
|
+
summary: A simple (but powerful) credit card number generator
|
161
|
+
test_files:
|
162
|
+
- spec/bin/credy_spec.rb
|
163
|
+
- spec/lib/credy/check_spec.rb
|
164
|
+
- spec/lib/credy/rules_spec.rb
|
165
|
+
- spec/lib/credy/version_spec.rb
|
166
|
+
- spec/lib/credy_spec.rb
|
167
|
+
- spec/spec_helper.rb
|
168
|
+
has_rdoc:
|