magnet 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/magnet.rb +5 -0
- data/lib/magnet/card.rb +144 -0
- data/lib/magnet/parser.rb +34 -0
- data/lib/magnet/version.rb +3 -0
- data/test/magnet/card_test.rb +55 -0
- data/test/magnet/parser_test.rb +42 -0
- metadata +86 -0
data/lib/magnet.rb
ADDED
data/lib/magnet/card.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
module Magnet
|
2
|
+
class Card
|
3
|
+
ALLOWED_SERVICES = {
|
4
|
+
0..1 => :no_restrictions,
|
5
|
+
2 => :goods_and_services_only,
|
6
|
+
3 => :atm_only,
|
7
|
+
4 => :cash_only,
|
8
|
+
5 => :goods_and_services_only,
|
9
|
+
6 => :no_restrictions,
|
10
|
+
7 => :goods_and_services_only
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
AUTHORIZATION_PROCESSING = {
|
14
|
+
0 => :normal,
|
15
|
+
2 => :by_issuer,
|
16
|
+
4 => :by_issuer_unless_explicit_agreement
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
FORMAT = {
|
20
|
+
"A" => :reserved,
|
21
|
+
"B" => :bank,
|
22
|
+
"C".."M" => :reserved,
|
23
|
+
"N".."Z" => :available
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
INTERCHANGE = {
|
27
|
+
1..2 => :international,
|
28
|
+
5..6 => :national,
|
29
|
+
7 => :private,
|
30
|
+
9 => :test
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
PIN_REQUIREMENTS = {
|
34
|
+
0 => :pin_required,
|
35
|
+
3 => :pin_required,
|
36
|
+
5 => :pin_required,
|
37
|
+
6..7 => :prompt_for_pin_if_ped_present
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
TECHNOLOGY = {
|
41
|
+
2 => :integrated_circuit_card,
|
42
|
+
6 => :integrated_circuit_card
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
attr_accessor :allowed_services, :authorization_processing, :discretionary_data, :expiration_year, :expiration_month, :first_name, :format, :initial, :interchange, :last_name, :number, :pin_requirements, :technology, :title
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def parse(track_data, parser = Parser.new(:auto))
|
49
|
+
attributes = parser.parse(track_data)
|
50
|
+
position1, position2, position3 = (attributes[:service_code] || "").scan(/\d/).map(&:to_i)
|
51
|
+
year, month = (attributes[:expiration] || "").scan(/\d\d/).map(&:to_i)
|
52
|
+
title, first_name, initial, last_name = parse_name(attributes[:name].rstrip)
|
53
|
+
|
54
|
+
card = new
|
55
|
+
card.allowed_services = hash_lookup(ALLOWED_SERVICES, position3)
|
56
|
+
card.authorization_processing = hash_lookup(AUTHORIZATION_PROCESSING, position2)
|
57
|
+
card.discretionary_data = attributes[:discretionary_data]
|
58
|
+
card.expiration_month = month
|
59
|
+
card.expiration_year = year
|
60
|
+
card.first_name = first_name
|
61
|
+
card.format = hash_lookup(FORMAT, attributes[:format])
|
62
|
+
card.initial = initial
|
63
|
+
card.interchange = hash_lookup(INTERCHANGE, position1)
|
64
|
+
card.last_name = last_name
|
65
|
+
card.number = attributes[:pan]
|
66
|
+
card.pin_requirements = hash_lookup(PIN_REQUIREMENTS, position3)
|
67
|
+
card.technology = hash_lookup(TECHNOLOGY, position1)
|
68
|
+
card.title = title
|
69
|
+
card
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def hash_lookup(hash, key)
|
74
|
+
hash.each do |k, v|
|
75
|
+
return v if k === key
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_name(name)
|
81
|
+
last, first = name.split("/", 2)
|
82
|
+
first, initial = first.split(" ", 2) if first
|
83
|
+
initial, title = initial.split(".", 2) if initial
|
84
|
+
[title, first, initial, last]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def atm_only?
|
89
|
+
allowed_services == :atm_only
|
90
|
+
end
|
91
|
+
|
92
|
+
def cash_only?
|
93
|
+
allowed_services == :cash_only
|
94
|
+
end
|
95
|
+
|
96
|
+
def goods_and_services_only?
|
97
|
+
allowed_services == :goods_and_services_only
|
98
|
+
end
|
99
|
+
|
100
|
+
def integrated_circuit_card?
|
101
|
+
technology == :integrated_circuit_card
|
102
|
+
end
|
103
|
+
|
104
|
+
def international?
|
105
|
+
interchange == :international
|
106
|
+
end
|
107
|
+
|
108
|
+
def national?
|
109
|
+
interchange == :national
|
110
|
+
end
|
111
|
+
|
112
|
+
def no_service_restrictions?
|
113
|
+
allowed_services = :no_restrictions
|
114
|
+
end
|
115
|
+
|
116
|
+
def pin_required?
|
117
|
+
pin_requirements == :pin_required
|
118
|
+
end
|
119
|
+
|
120
|
+
def private?
|
121
|
+
interchange == :private
|
122
|
+
end
|
123
|
+
|
124
|
+
def process_by_issuer?
|
125
|
+
authorization_processing == :by_issuer
|
126
|
+
end
|
127
|
+
|
128
|
+
def process_by_issuer_unless_explicit_agreement?
|
129
|
+
authorization_processing == :by_issuer_unless_explicit_agreement
|
130
|
+
end
|
131
|
+
|
132
|
+
def process_normally?
|
133
|
+
authorization_processing == :normal
|
134
|
+
end
|
135
|
+
|
136
|
+
def prompt_for_pin_if_ped_present?
|
137
|
+
pin_requirements == :prompt_for_pin_if_ped_present
|
138
|
+
end
|
139
|
+
|
140
|
+
def test?
|
141
|
+
interchange == :test
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Magnet
|
2
|
+
class Parser
|
3
|
+
TRACKS = {
|
4
|
+
1 => /\A%(?<format>[A-Z])(?<pan>[0-9]{1,19})\^(?<name>[A-Za-z.\/ ]{2,26})\^(?<expiration>\d{4}|\^)(?<service_code>\d{3}|\^)(?<discretionary_data>[^\?]+)\?\Z/,
|
5
|
+
2 => /\A;(?<format>[A-Z])(?<pan>[0-9]{1,19})=(?<expiration>\d{4}|=)(?<service_code>\d{3}|=)(?<discretionary_data>[^\?]+)\?\Z/
|
6
|
+
}.freeze
|
7
|
+
|
8
|
+
def initialize(track = :auto)
|
9
|
+
@track = :auto
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(track_data)
|
13
|
+
tracks = @track == :auto ? TRACKS.values : [TRACKS[@track]]
|
14
|
+
|
15
|
+
tracks.each do |track|
|
16
|
+
if m = track.match(track_data)
|
17
|
+
attributes = {}
|
18
|
+
attributes[:format] = m[:format]
|
19
|
+
attributes[:pan] = m[:pan]
|
20
|
+
attributes[:name] = m[:name]
|
21
|
+
attributes[:expiration] = m[:expiration] == "^" ? nil : m[:expiration]
|
22
|
+
attributes[:service_code] = m[:service_code] == "^" ? nil : m[:service_code]
|
23
|
+
attributes[:discretionary_data] = m[:discretionary_data]
|
24
|
+
return attributes
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
raise InvalidDataError, "track data is not valid"
|
29
|
+
end
|
30
|
+
|
31
|
+
class InvalidDataError < StandardError
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Magnet::Card do
|
4
|
+
describe "Parse" do
|
5
|
+
before do
|
6
|
+
@attributes = { :format => "B", :pan => "5452300551227189", :name => "HOGAN/PAUL ", :expiration => "0804", :service_code => "321", :discretionary_data => "0000000725000000" }
|
7
|
+
@parser = stub()
|
8
|
+
@track_data = stub()
|
9
|
+
@parser.stubs(:parse).with(@track_data).returns(@attributes)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should parse basic attributes" do
|
13
|
+
card = Magnet::Card.parse(@track_data, @parser)
|
14
|
+
|
15
|
+
assert_equal :no_restrictions, card.allowed_services
|
16
|
+
assert_equal :by_issuer, card.authorization_processing
|
17
|
+
assert_equal "0000000725000000", card.discretionary_data
|
18
|
+
assert_equal 8, card.expiration_year
|
19
|
+
assert_equal 4, card.expiration_month
|
20
|
+
assert_equal "PAUL", card.first_name
|
21
|
+
assert_equal :bank, card.format
|
22
|
+
assert_equal nil, card.initial
|
23
|
+
assert_equal nil, card.interchange
|
24
|
+
assert_equal "HOGAN", card.last_name
|
25
|
+
assert_equal "5452300551227189", card.number
|
26
|
+
assert_equal nil, card.pin_requirements
|
27
|
+
assert_equal nil, card.technology
|
28
|
+
assert_equal nil, card.title
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should parse an initial" do
|
32
|
+
@attributes = { :format => "B", :pan => "5452300551227189", :name => "HOGAN/PAUL A ", :expiration => "0804", :service_code => "321", :discretionary_data => "0000000725000000" }
|
33
|
+
@parser.stubs(:parse).with(@track_data).returns(@attributes)
|
34
|
+
|
35
|
+
card = Magnet::Card.parse(@track_data, @parser)
|
36
|
+
|
37
|
+
assert_equal "PAUL", card.first_name
|
38
|
+
assert_equal "A", card.initial
|
39
|
+
assert_equal "HOGAN", card.last_name
|
40
|
+
assert_equal nil, card.title
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should parse a title" do
|
44
|
+
@attributes = { :format => "B", :pan => "5452300551227189", :name => "HOGAN/PAUL A.DR ", :expiration => "0804", :service_code => "321", :discretionary_data => "0000000725000000" }
|
45
|
+
@parser.stubs(:parse).with(@track_data).returns(@attributes)
|
46
|
+
|
47
|
+
card = Magnet::Card.parse(@track_data, @parser)
|
48
|
+
|
49
|
+
assert_equal "PAUL", card.first_name
|
50
|
+
assert_equal "A", card.initial
|
51
|
+
assert_equal "HOGAN", card.last_name
|
52
|
+
assert_equal "DR", card.title
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Magnet::Parser do
|
4
|
+
describe "Track 1" do
|
5
|
+
before do
|
6
|
+
@parser = Magnet::Parser.new(1)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should parse sample #1" do
|
10
|
+
attributes = @parser.parse("%B6011898748579348^DOE/ JOHN ^37829821000123456789?")
|
11
|
+
|
12
|
+
assert_equal "B", attributes[:format]
|
13
|
+
assert_equal "6011898748579348", attributes[:pan]
|
14
|
+
assert_equal "DOE/ JOHN ", attributes[:name]
|
15
|
+
assert_equal "3782", attributes[:expiration]
|
16
|
+
assert_equal "982", attributes[:service_code]
|
17
|
+
assert_equal "1000123456789", attributes[:discretionary_data]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should parse sample #2" do
|
21
|
+
attributes = @parser.parse("%B6011785948493759^DOE/JOHN L ^^^0000000 00998000000?")
|
22
|
+
|
23
|
+
assert_equal "B", attributes[:format]
|
24
|
+
assert_equal "6011785948493759", attributes[:pan]
|
25
|
+
assert_equal "DOE/JOHN L ", attributes[:name]
|
26
|
+
assert_equal nil, attributes[:expiration]
|
27
|
+
assert_equal nil, attributes[:service_code]
|
28
|
+
assert_equal "0000000 00998000000", attributes[:discretionary_data]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should parse sample #3" do
|
32
|
+
attributes = @parser.parse("%B5452300551227189^HOGAN/PAUL ^08043210000000725000000?")
|
33
|
+
|
34
|
+
assert_equal "B", attributes[:format]
|
35
|
+
assert_equal "5452300551227189", attributes[:pan]
|
36
|
+
assert_equal "HOGAN/PAUL ", attributes[:name]
|
37
|
+
assert_equal "0804", attributes[:expiration]
|
38
|
+
assert_equal "321", attributes[:service_code]
|
39
|
+
assert_equal "0000000725000000", attributes[:discretionary_data]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: magnet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Samuel Kadolph
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mocha
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.13.2
|
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.13.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 10.0.3
|
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: 10.0.3
|
46
|
+
description: magnet lets you parse track data from magnetic stripe cards. Currently
|
47
|
+
supports tracks 1 & 2 from bank cards.
|
48
|
+
email:
|
49
|
+
- samuel@kadolph.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- lib/magnet/card.rb
|
55
|
+
- lib/magnet/parser.rb
|
56
|
+
- lib/magnet/version.rb
|
57
|
+
- lib/magnet.rb
|
58
|
+
- test/magnet/card_test.rb
|
59
|
+
- test/magnet/parser_test.rb
|
60
|
+
homepage: http://samuelkadolph.github.com/magnet/
|
61
|
+
licenses: []
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 1.9.2
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.8.25
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: magnet is a library for decoding the track data on magnetic stripe cards.
|
84
|
+
test_files:
|
85
|
+
- test/magnet/card_test.rb
|
86
|
+
- test/magnet/parser_test.rb
|