cordawyn-iso8583 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1,2 @@
1
+ Tim Becker (tim@kuriositaet.de)
2
+ Slava Kravchenko (cordawyn@gmail.com)
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ 0.1.2
2
+ ==========
3
+ branched from original 0.1.1;
4
+ cleaned-up files;
5
+ put all stuff into ISO8583 namespace (module)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 Tim Becker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,27 @@
1
+
2
+ = ISO 8583 Financial Messaging for Ruby
3
+
4
+ This package currently contains code for coding an decoding ISO 8583
5
+ Financial Message.
6
+
7
+ == Using
8
+
9
+ The best place to get started using the library is the documentation of the
10
+ Message class. Once a stable state is reached, this should be the only
11
+ class you need to use ...
12
+
13
+ == Installing
14
+
15
+ You can install the +iso8583+ package by executing:
16
+
17
+ gem install iso8583
18
+
19
+ == Source
20
+
21
+ The source is most readily available on github[http://github.com/cordawyn/8583].
22
+
23
+ == Mailing List
24
+
25
+ In case you discover bugs, spelling errors, offer suggestions for
26
+ improvements or would like to help out with the project, you can contact
27
+ me directly (tim@kuriositaet.de).
data/Rakefile ADDED
@@ -0,0 +1,127 @@
1
+ require "rake/rdoctask"
2
+ require "rake/gempackagetask"
3
+ require "rake/testtask"
4
+ require "rake/clean"
5
+ require "rubygems"
6
+
7
+ # Some definitions that you'll need to edit in case you reuse this
8
+ # Rakefile for your own project.
9
+
10
+ SHORTNAME = "iso8583" # this should be the rubyforge project name
11
+ DESC = "Ruby implementation of ISO 8583 financial messaging"
12
+ PKG_VERSION = "0.1.2"
13
+ LONG_DESC = <<END_DESC
14
+ Ruby implementation of ISO 8583 financial messaging
15
+ END_DESC
16
+ RUBYFORGE_USER = "cordawyn"
17
+
18
+ # Specifies the default task to execute. This is often the "test" task
19
+ # and we'll change things around as soon as we have some tests.
20
+
21
+ task :default => [:rdoc]
22
+
23
+ # The directory to generate +rdoc+ in.
24
+ RDOC_DIR = "doc/html"
25
+
26
+ # This global variable contains files that will be erased by the `clean` task.
27
+ # The `clean` task itself is automatically generated by requiring `rake/clean`.
28
+
29
+ CLEAN << RDOC_DIR << "pkg"
30
+
31
+
32
+ # This is the task that generates the +rdoc+ documentation from the
33
+ # source files. Instantiating Rake::RDocTask automatically generates a
34
+ # task called `rdoc`.
35
+
36
+ Rake::RDocTask.new do |rd|
37
+ # Options for documenation generation are specified inside of
38
+ # this block. For example the following line specifies that the
39
+ # content of the README file should be the main page of the
40
+ # documenation.
41
+ rd.main = "README"
42
+
43
+ # The following line specifies all the files to extract
44
+ # documenation from.
45
+ rd.rdoc_files.include("README", "AUTHORS", "LICENSE", "TODO",
46
+ "CHANGELOG", "bin/**/*", "lib/**/*.rb",
47
+ "examples/**/*rb", "doc/*.rdoc")
48
+ # This one specifies the output directory ...
49
+ rd.rdoc_dir = "doc/html"
50
+
51
+ # Or the HTML title of the generated documentation set.
52
+ rd.title = "#{SHORTNAME}: #{DESC}"
53
+
54
+ # These are options specifiying how source code inlined in the
55
+ # documentation should be formatted.
56
+
57
+ rd.options = ["--line-numbers", "--inline-source"]
58
+
59
+ # Check:
60
+ # `rdoc --help` for more rdoc options
61
+ # the {rdoc documenation home}[http://www.ruby-doc.org/stdlib/libdoc/rdoc/rdoc/index.html]
62
+ # or the documentation for the +Rake::RDocTask+ task[http://rake.rubyforge.org/classes/Rake/RDocTask.html]
63
+ end
64
+
65
+ # The GemPackageTask facilitates getting all your files collected
66
+ # together into gem archives. You can also use it to generate tarball
67
+ # and zip archives.
68
+
69
+ # First you'll need to assemble a gemspec
70
+
71
+ PKG_FILES = FileList["lib/**/*.rb", "bin/**/*", "examples/**/*", "[A-Z]*", "test/**/*"].to_a
72
+
73
+ spec = Gem::Specification.new do |s|
74
+ s.platform = Gem::Platform::RUBY
75
+ s.summary = "#{SHORTNAME}: #{DESC}"
76
+ s.name = "#{RUBYFORGE_USER}-#{SHORTNAME}"
77
+ s.rubyforge_project = SHORTNAME
78
+ s.version = PKG_VERSION
79
+ s.files = PKG_FILES
80
+ s.requirements << "none"
81
+ s.require_path = "lib"
82
+ s.description = LONG_DESC
83
+ s.has_rdoc = true
84
+ s.authors = ["Slava Kravchenko", "Tim Becker"]
85
+ s.email = ["cordawyn@gmail.com", "tim.becker@kuriositaet.de"]
86
+ s.homepage = "http://github.com/cordawyn/8583/"
87
+ end
88
+
89
+ # Adding a new GemPackageTask adds a task named `package`, which generates
90
+ # packages as gems, tarball and zip archives.
91
+ Rake::GemPackageTask.new(spec) do |pkg|
92
+ pkg.need_zip = true
93
+ pkg.need_tar_gz = true
94
+ end
95
+
96
+
97
+ # This task is used to demonstrate how to upload files to Rubyforge.
98
+ # Calling `upload_page` creates a current version of the +rdoc+
99
+ # documentation and uploads it to the Rubyforge homepage of the project,
100
+ # assuming it's hosted there and naming conventions haven't changed.
101
+ #
102
+ # This task uses `sh` to call the `scp` binary, which is plattform
103
+ # dependant and may not be installed on your computer if you're using
104
+ # Windows. I'm currently not aware of any pure ruby way to do scp
105
+ # transfers.
106
+
107
+ RubyForgeProject = SHORTNAME
108
+
109
+ desc "Upload the web pages to the web."
110
+ task :upload_pages => ["rdoc"] do
111
+ if RubyForgeProject then
112
+ path = "/var/www/gforge-projects/#{RubyForgeProject}"
113
+ sh "scp -r doc/html/* #{RUBYFORGE_USER}@rubyforge.org:#{path}"
114
+ sh "scp doc/images/*.png #{RUBYFORGE_USER}@rubyforge.org:#{path}/images"
115
+ end
116
+ end
117
+
118
+ # This task will run the unit tests provided in files called
119
+ # `test/test*.rb`. The task itself can be run with a call to `rake test`
120
+
121
+ Rake::TestTask.new do |t|
122
+ t.libs << "test"
123
+ t.libs << "lib"
124
+ t.ruby_opts = ["-rubygems"]
125
+ t.test_files = FileList["test/*.rb"]
126
+ t.verbose = true
127
+ end
data/TODO ADDED
@@ -0,0 +1,10 @@
1
+
2
+ * mti_required : or something of the sort to indicate which fields are
3
+ required/mirrored/conditional for each message type
4
+
5
+ * decode_check : currently the codecs are very liberal about what to accept
6
+ or parse. E.g. if an invalid date is received, it doesn't complain. Consider
7
+ collecting the errors and presenting them through in interface so
8
+ it may be decided after parsing the message whether to reject it or no
9
+
10
+ * general cleanup/license/docs/readme
data/lib/berlin.rb ADDED
@@ -0,0 +1,82 @@
1
+ # Copyright 2009 by Tim Becker (tim.becker@kuriostaet.de)
2
+ # MIT License, for details, see the LICENSE file accompaning
3
+ # this distribution
4
+
5
+ require 'lib/iso8583'
6
+
7
+
8
+ # Example of a protocol specification based on:
9
+ # http://www.berlin-group.org/documents/BG_Authorisation_3.0.pdf
10
+ # The Berlin Groups Authorisation Interface specification.
11
+ # No gurantees are made that this is an accurate implemenation.
12
+ # It currently serves as an example only.
13
+
14
+ module ISO8583
15
+
16
+ class BerlinMessage < Message
17
+ mti_format N, :length => 4
18
+ mti 1100, "Authorization Request Acquirer Gateway"
19
+ mti 1110, "Authorization Request Response Issuer Gateway"
20
+ mti 1420, "Reversal Advice Acquirer Gateway"
21
+ mti 1421, "Reversal Advice Repeat Acquirer Gateway"
22
+ mti 1430, "Reversal Advice Response Issuer Gateway"
23
+ mti 1804, "Network Management Request Acquirer Gateway or Issuer Gateway"
24
+ mti 1814, "Network Management Request Response Issuer Gateway or Acquirer Gateway"
25
+
26
+ bmp 2, "Primary Account Number (PAN)", LLVAR_N, :max => 19
27
+ bmp 3, "Processing Code", N, :length => 6
28
+ bmp 4, "Amount (Transaction)", N, :length => 12
29
+ bmp 6, "Amount, Cardholder Billing" , N, :length => 12
30
+ bmp 7, "Date and Time, Transmission" , MMDDhhmmss
31
+ bmp 10, "Conversion Rate, Cardholder Billing", N, :length => 8
32
+ bmp 11, "System Trace Audit Number (STAN)", N, :length => 6
33
+ bmp 12, "Date and Time, Local Transaction", YYMMDDhhmmss
34
+ bmp 14, "Date, Expiration", YYMM
35
+ bmp 22, "POS Data Code", AN, :length => 12
36
+ bmp 23, "Card Sequence Number", N, :length => 3
37
+ bmp 24, "Function Code", N, :length => 3
38
+ bmp 25, "Message Reason Code", N, :length => 4
39
+ bmp 26, "Card Acceptor Business Code", N, :length => 4
40
+ bmp 30, "Amounts, Original", N, :length => 24
41
+ bmp 32, "Acquiring Institution Identification Code", LLVAR_N, :max => 11
42
+ bmp 35, "Track 2 Data", LLVAR_Z, :max => 37
43
+ bmp 37, "Retrieval Reference Number", ANP, :length => 12
44
+ bmp 38, "Approval Code", ANP, :length => 6
45
+ bmp 39, "Action Code", N, :length => 3
46
+ bmp 41, "Card Acceptor Terminal Identification", ANS, :length => 8
47
+ bmp 42, "Card Acceptor Identification Code", ANS, :length => 15
48
+ bmp 43, "Card Acceptor Name/Location", LLVAR_ANS, :max => 56
49
+ bmp 49, "Currency Code, Transaction", N, :length => 3
50
+ bmp 51, "Currency Code, Cardholder Billing", N, :length => 3
51
+ bmp 52, "Personal Identification Number (PIN) Data", B, :length => 8
52
+ bmp 53, "Security Related Control Information", LLVAR_B, :max => 48
53
+ bmp 54, "Amounts, Additional", LLLVAR_ANS,:max => 40
54
+
55
+ bmp 55, "Integrated Circuit Card (ICC) System Related Data", LLLVAR_B, :max => 255
56
+ bmp 56, "Original Data Elements", LLVAR_N, :max => 35
57
+ bmp 58, "Authorizing Agent Institution Identification Code", LLVAR_N, :max => 11
58
+ bmp 59, "Additional Data - Private", LLLVAR_ANS, :max => 67
59
+ bmp 64, "Message Authentication Code (MAC) Field", B, :length => 8
60
+
61
+ bmp_alias 2, :pan
62
+ bmp_alias 3, :proc_code
63
+ bmp_alias 4, :amount
64
+ bmp_alias 12, :exp_date
65
+ end
66
+
67
+ end
68
+
69
+ if __FILE__==$0
70
+ mes = ISO8583::BerlinMessage.new
71
+ mes.mti = 1110
72
+ mes[2] = 474747474747
73
+ mes["Processing Code"] = "123456"
74
+
75
+ pan = mes["Primary Account Number (PAN)"]
76
+ #mes.pan = 47474747474747
77
+
78
+ #puts mes.pan
79
+ puts mes.to_b
80
+ puts mes.to_s
81
+ #mes2 = BerlinMessage.parse input
82
+ end
data/lib/bitmap.rb ADDED
@@ -0,0 +1,117 @@
1
+ # Copyright 2009 by Tim Becker (tim.becker@kuriostaet.de)
2
+ # MIT License, for details, see the LICENSE file accompaning
3
+ # this distribution
4
+
5
+ module ISO8583
6
+
7
+ # This class constructs an object for handling bitmaps
8
+ # with which ISO8583 messages typically begin.
9
+ # Bitmaps are either 8 or 16 bytes long, an extended length
10
+ # bitmap is indicated by the first bit being set.
11
+ # In all likelyhood, you won't be using this class much, it's used
12
+ # transparently by the Message class.
13
+ class Bitmap
14
+
15
+ # create a new Bitmap object. In case an iso message
16
+ # is passed in, that messages bitmap will be parsed. If
17
+ # not, this initializes and empty bitmap.
18
+ def initialize(message = nil)
19
+ @bmp = Array.new(128, false)
20
+ if !message
21
+
22
+ else
23
+ initialize_from_message message
24
+ end
25
+ end
26
+
27
+ # yield once with the number of each set field.
28
+ def each #:yields: each bit set in the bitmap.
29
+ @bmp.each_with_index {|set, i| yield i+1 if set}
30
+ end
31
+
32
+ # Returns whether the bit is set or not.
33
+ def [](i)
34
+ @bmp[i-1]
35
+ end
36
+
37
+ # Set the bit to the indicated value. Only `true` sets the
38
+ # bit, any other value unsets it.
39
+ def []=(i, value)
40
+ if i > 128
41
+ raise ISO8583Exception.new("Bits > 128 are not permitted.")
42
+ elsif i < 2
43
+ raise ISO8583Exception.new("Bits < 2 are not permitted (continutation bit is set automatically)")
44
+ end
45
+ @bmp[i-1] = (value == true)
46
+ end
47
+
48
+ # Sets bit #i
49
+ def set(i)
50
+ self[i] = true
51
+ end
52
+
53
+ # Unsets bit #i
54
+ def unset(i)
55
+ self[i] = false
56
+ end
57
+
58
+ # Generate the bytes representing this bitmap.
59
+ def to_bytes
60
+ arr = [self.to_s]
61
+ # tricky and ugly, setting bit[1] only when generating to_s...
62
+ count = self[1] ? 128 : 64
63
+ arr.pack("B#{count}")
64
+ end
65
+ alias_method :to_b, :to_bytes
66
+
67
+ # Generate a String representation of this bitmap in the form:
68
+ # 01001100110000011010110110010100100110011000001101011011001010
69
+ def to_s
70
+ #check whether any `high` bits are set
71
+ @bmp[0] = false
72
+ 65.upto(128) {|i|
73
+ if self[i]
74
+ # if so, set continuation bit
75
+ @bmp[0] = true
76
+ break
77
+ end
78
+ }
79
+ str = ""
80
+ 1.upto(self[1] ? 128 : 64) {|i|
81
+ str << (self[i] ? "1" : "0")
82
+ }
83
+ str
84
+ end
85
+
86
+
87
+ private
88
+
89
+ def initialize_from_message(message)
90
+ bmp = message.unpack("B64")[0]
91
+ if bmp[0,1] == "1"
92
+ bmp = message.unpack("B128")[0]
93
+ end
94
+
95
+ 0.upto(bmp.length-1) do |i|
96
+ @bmp[i] = (bmp[i,1] == "1")
97
+ end
98
+ end
99
+
100
+ class << self
101
+ # Parse the bytes in string and return the Bitmap and bytes remaining in `str`
102
+ # after the bitmap is taken away.
103
+ def parse(str)
104
+ bmp = Bitmap.new(str)
105
+ rest = bmp[1] ? str[16, str.length] : str[8, str.length]
106
+ [ bmp, rest ]
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+
113
+ if __FILE__==$0
114
+ mp = ISO8583::Bitmap.new
115
+ 20.step(128,7) {|i| mp.set(i)}
116
+ print mp.to_bytes
117
+ end
data/lib/codec.rb ADDED
@@ -0,0 +1,189 @@
1
+ # Copyright 2009 by Tim Becker (tim.becker@kuriostaet.de)
2
+ # MIT License, for details, see the LICENSE file accompaning
3
+ # this distribution
4
+
5
+ require 'date'
6
+
7
+ module ISO8583
8
+
9
+ # Codec provides functionality to encode and decode values, codecs are
10
+ # used internally by Field instances in order to do character conversions
11
+ # and checking for proper values.
12
+ # Although they are used internally, you will probably need to write
13
+ # your own Codec sooner or later. The codecs used by Field instances are
14
+ # typically instances of Codec, it may or may not be usefull to subclass
15
+ # Codec.
16
+ #
17
+ # Say, for example, a text field needs to be encoded in EBCDIC in the
18
+ # message, this is how a corresponding codec would be constructed:
19
+ #
20
+ # EBCDIC_Codec = Codec.new
21
+ # EBCDIC_Codec.encoder = lambda {|ascii_str|
22
+ # raise ISO8583Exception.new("String (#{ascii_str})not valid!") unless =~ /someregexp/
23
+ # ascii2ebcdic ascii_str # implementing ascii_str is left as an excercise
24
+ # }
25
+ # EBCDIC_Codec.decode = lambda {|ebcdic_str|
26
+ # # you may or may not want to raise exceptions at this point ....
27
+ # # strip removes any padding...
28
+ # ebcdic2ascii(ebcdic_str).strip
29
+ # }
30
+ #
31
+ # This instance of Codec would then be used be the corresponding Field
32
+ # encoder/decoder, which may look similar to this:
33
+ #
34
+ # EBCDIC = Field.new
35
+ # EBCDIC.codec = EBCDIC_Codec
36
+ # EBCDIC.padding = PADDING_LEFT_JUSTIFIED_SPACES
37
+ #
38
+ # Notice there is a bit of inconsistancy: the padding is added by the
39
+ # field, but removed by the codec. I would like to find a better
40
+ # solution to this...
41
+ #
42
+ # See also: Field, link:files/lib/fields_rb.html
43
+ #
44
+ # The following codecs are already implemented:
45
+ # [+ASCII_Number+] encodes either a Number or String representation of
46
+ # a number to the ASCII represenation of the number,
47
+ # decodes ASCII numerals to a number
48
+ # [+AN_Codec+] passes through ASCII string checking they conform to [A-Za-z0-9]
49
+ # during encoding, no validity check during decoding.
50
+ # [+ANP_Codec+] passes through ASCII string checking they conform to [A-Za-z0-9 ]
51
+ # during encoding, no validity check during decoding.
52
+ # [+ANS_Codec+] passes through ASCII string checking they conform to [\x20-\x7E]
53
+ # during encoding, no validity check during decoding.
54
+ # [+Null_Codec+] passes anything along untouched.
55
+ # [<tt>Track2</tt>] rudimentary check that string conforms to Track2
56
+ # [+MMDDhhmmssCodec+] encodes Time, Datetime or String to the described date format, checking
57
+ # that it is a valid date. Decodes to a DateTime instance, decoding and
58
+ # encoding perform validity checks!
59
+ # [+YYMMDDhhmmssCodec+] encodes Time, Datetime or String to the described date format, checking
60
+ # that it is a valid date. Decodes to a DateTime instance, decoding and
61
+ # encoding perform validity checks!
62
+ # [+YYMMCodec+] encodes Time, Datetime or String to the described date format (exp date),
63
+ # checking that it is a valid date. Decodes to a DateTime instance, decoding
64
+ # and encoding perform validity checks!
65
+ #
66
+ class Codec
67
+ attr_accessor :encoder
68
+ attr_accessor :decoder
69
+
70
+ def decode(raw)
71
+ decoder.call(raw)
72
+ end
73
+
74
+ # length is either a fixnum or a lenth encoder.
75
+ def encode(value)
76
+ encoder.call(value)
77
+ end
78
+ end
79
+
80
+ # ASCII_Number
81
+ ASCII_Number = Codec.new
82
+ ASCII_Number.encoder= lambda{|num|
83
+ enc = num.to_s
84
+ raise ISO8583Exception.new("Invalid value: #{enc} must be numeric!") unless enc =~ /^[0-9]*$/
85
+ enc
86
+ }
87
+
88
+ ASCII_Number.decoder = lambda{|raw|
89
+ raw.to_i
90
+ }
91
+
92
+ PASS_THROUGH_DECODER = lambda{|str|
93
+ str.strip # remove padding
94
+ }
95
+
96
+ # Takes a number or str representation of a number and BCD encodes it, e.g.
97
+ # "1234" => "\x12\x34"
98
+ # 3456 => "\x34\x56"
99
+ #
100
+ # right justified with null ... (correct to do this? almost certainly not...)
101
+ Packed_Number = Codec.new
102
+ Packed_Number.encoder = lambda { |val|
103
+ val = val.to_s
104
+ val = val.length % 2 == 0 ? val : "0"+val
105
+ raise ISO8583Exception.new("Invalid value: #{val} must be numeric!") unless val =~ /^[0-9]*$/
106
+ [val].pack("H*")
107
+ }
108
+ Packed_Number.decoder = lambda{|encoded|
109
+ d = encoded.unpack("H*")[0].to_i
110
+ }
111
+
112
+ AN_Codec = Codec.new
113
+ AN_Codec.encoder = lambda{|str|
114
+ raise ISO8583Exception.new("Invalid value: #{str} must be [A-Za-y0-9]") unless str =~ /^[A-Za-z0-9]*$/
115
+ str
116
+ }
117
+ AN_Codec.decoder = PASS_THROUGH_DECODER
118
+
119
+ ANP_Codec = Codec.new
120
+ ANP_Codec.encoder = lambda{|str|
121
+ raise ISO8583Exception.new("Invalid value: #{str} must be [A-Za-y0-9 ]") unless str =~ /^[A-Za-z0-9 ]*$/
122
+ str
123
+ }
124
+ ANP_Codec.decoder = PASS_THROUGH_DECODER
125
+
126
+ ANS_Codec = Codec.new
127
+ ANS_Codec.encoder = lambda{|str|
128
+ raise ISO8583Exception.new("Invalid value: #{str} must be [\x20-\x7E]") unless str =~ /^[\x20-\x7E]*$/
129
+ str
130
+ }
131
+ ANS_Codec.decoder = PASS_THROUGH_DECODER
132
+
133
+ Null_Codec = Codec.new
134
+ Null_Codec.encoder = lambda {|str|
135
+ str
136
+ }
137
+ Null_Codec.decoder = lambda {|str|
138
+ str.gsub(/\000*$/, '')
139
+ }
140
+
141
+ Track2 = Codec.new
142
+ Track2.encoder = lambda{|track2|
143
+ #SS | PAN | FS | Expiration Date | Service Code | Discretionary Data | ES | LRC
144
+ # SS = ;
145
+ # PAN = up to 19 digits (at least 9?)
146
+ # FS = '='
147
+ # Exp Date = YYMM
148
+ # SC: 3 digits or =
149
+ # ES = ?
150
+ # lrc : 1byte
151
+ raise ISO8583Exception.new("Invalid Track2 data: #{track2}") unless track2 =~ /^;*(\d{9,19})=(.*)\?.$/
152
+ track2
153
+ }
154
+ Track2.decoder = PASS_THROUGH_DECODER
155
+
156
+ def self._date_codec(fmt)
157
+ c = Codec.new
158
+ c.encoder = lambda {|date|
159
+ enc = case date
160
+ when Date, Time
161
+ date.strftime(fmt)
162
+ when String
163
+ begin
164
+ dt = DateTime.strptime(date, fmt)
165
+ dt.strftime(fmt)
166
+ rescue
167
+ raise ISO8583Exception.new("Invalid format encoding: #{date}, must be #{fmt}.")
168
+ end
169
+ else
170
+ raise ISO8583Exception.new("Don't know how to encode: #{date.class} to a time.")
171
+ end
172
+ return enc
173
+ }
174
+ c.decoder = lambda {|str|
175
+ begin
176
+ DateTime.strptime(str, fmt)
177
+ rescue
178
+ raise ISO8583Exception.new("Invalid format decoding: #{str}, must be #{fmt}.")
179
+ end
180
+ }
181
+
182
+ c
183
+ end
184
+
185
+ MMDDhhmmssCodec = _date_codec("%m%d%H%M%S")
186
+ YYMMDDhhmmssCodec = _date_codec("%y%m%d%H%M%S")
187
+ YYMMCodec = _date_codec("%y%m")
188
+
189
+ end