magnet 1.0.0
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/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
|