npn47_iso8583 0.1.5.a
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.
- checksums.yaml +7 -0
- data/lib/8583.rb +5 -0
- data/lib/iso8583/berlin.rb +82 -0
- data/lib/iso8583/bitmap.rb +117 -0
- data/lib/iso8583/codec.rb +196 -0
- data/lib/iso8583/exception.rb +4 -0
- data/lib/iso8583/field.rb +90 -0
- data/lib/iso8583/fields.rb +75 -0
- data/lib/iso8583/message.rb +419 -0
- data/lib/iso8583/util.rb +94 -0
- data/lib/iso8583/version.rb +4 -0
- data/lib/iso8583.rb +13 -0
- data/test/BitmapTests.rb +80 -0
- data/test/message_test.rb +163 -0
- data/test/test_codec.rb +124 -0
- data/test/test_fields.rb +246 -0
- data/test/test_seperate_msg.rb +22 -0
- data/test/test_util.rb +32 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4490ebe6f888fd296deba7834184d8ec4301ad49
|
4
|
+
data.tar.gz: 90f4dba0b5e7f3272dbac6e82472d3067223661c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5c0880e8ae9b03c7664fca0907199e206182e07b3e64d83a1cb1333a17f7d7b306d05a769095f209aa7ffb8809bcd3b3a371ec4a941e92be5108f4b8d8e9a492
|
7
|
+
data.tar.gz: 4a17f409d7eeb87e82b2c8f6a5393b7dec64a108c3139278e56612838168ffd71eb3f86fdb50d50631de8094f2007ca2002929d34aa14e76bcdafc272477931d
|
data/lib/8583.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
|
@@ -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
|
@@ -0,0 +1,196 @@
|
|
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
|
+
# [+A_Codec+] passes through ASCII string checking they conform to [A-Za-z]
|
49
|
+
# during encoding, no validity check during decoding.
|
50
|
+
# [+AN_Codec+] passes through ASCII string checking they conform to [A-Za-z0-9]
|
51
|
+
# during encoding, no validity check during decoding.
|
52
|
+
# [+ANP_Codec+] passes through ASCII string checking they conform to [A-Za-z0-9 ]
|
53
|
+
# during encoding, no validity check during decoding.
|
54
|
+
# [+ANS_Codec+] passes through ASCII string checking they conform to [\x20-\x7E]
|
55
|
+
# during encoding, no validity check during decoding.
|
56
|
+
# [+Null_Codec+] passes anything along untouched.
|
57
|
+
# [<tt>Track2</tt>] rudimentary check that string conforms to Track2
|
58
|
+
# [+MMDDhhmmssCodec+] encodes Time, Datetime or String to the described date format, checking
|
59
|
+
# that it is a valid date. Decodes to a DateTime instance, decoding and
|
60
|
+
# encoding perform validity checks!
|
61
|
+
# [+YYMMDDhhmmssCodec+] encodes Time, Datetime or String to the described date format, checking
|
62
|
+
# that it is a valid date. Decodes to a DateTime instance, decoding and
|
63
|
+
# encoding perform validity checks!
|
64
|
+
# [+YYMMCodec+] encodes Time, Datetime or String to the described date format (exp date),
|
65
|
+
# checking that it is a valid date. Decodes to a DateTime instance, decoding
|
66
|
+
# and encoding perform validity checks!
|
67
|
+
#
|
68
|
+
class Codec
|
69
|
+
attr_accessor :encoder
|
70
|
+
attr_accessor :decoder
|
71
|
+
|
72
|
+
def decode(raw)
|
73
|
+
decoder.call(raw)
|
74
|
+
end
|
75
|
+
|
76
|
+
# length is either a fixnum or a lenth encoder.
|
77
|
+
def encode(value)
|
78
|
+
encoder.call(value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# ASCII_Number
|
83
|
+
ASCII_Number = Codec.new
|
84
|
+
ASCII_Number.encoder= lambda{|num|
|
85
|
+
enc = num.to_s
|
86
|
+
raise ISO8583Exception.new("Invalid value: #{enc} must be numeric!") unless enc =~ /^[0-9]*$/
|
87
|
+
enc
|
88
|
+
}
|
89
|
+
|
90
|
+
ASCII_Number.decoder = lambda{|raw|
|
91
|
+
raw.to_i
|
92
|
+
}
|
93
|
+
|
94
|
+
PASS_THROUGH_DECODER = lambda{|str|
|
95
|
+
str.strip # remove padding
|
96
|
+
}
|
97
|
+
|
98
|
+
# Takes a number or str representation of a number and BCD encodes it, e.g.
|
99
|
+
# "1234" => "\x12\x34"
|
100
|
+
# 3456 => "\x34\x56"
|
101
|
+
#
|
102
|
+
# right justified with null ... (correct to do this? almost certainly not...)
|
103
|
+
Packed_Number = Codec.new
|
104
|
+
Packed_Number.encoder = lambda { |val|
|
105
|
+
val = val.to_s
|
106
|
+
val = val.length % 2 == 0 ? val : "0"+val
|
107
|
+
raise ISO8583Exception.new("Invalid value: #{val} must be numeric!") unless val =~ /^[0-9]*$/
|
108
|
+
[val].pack("H*")
|
109
|
+
}
|
110
|
+
Packed_Number.decoder = lambda{|encoded|
|
111
|
+
d = encoded.unpack("H*")[0].to_i
|
112
|
+
}
|
113
|
+
|
114
|
+
A_Codec = Codec.new
|
115
|
+
A_Codec.encoder = lambda{|str|
|
116
|
+
raise ISO8583Exception.new("Invalid value: #{str} must be [A-Za-z]") unless str =~ /^[A-Za-z]*$/
|
117
|
+
str
|
118
|
+
}
|
119
|
+
A_Codec.decoder = PASS_THROUGH_DECODER
|
120
|
+
|
121
|
+
AN_Codec = Codec.new
|
122
|
+
AN_Codec.encoder = lambda{|str|
|
123
|
+
raise ISO8583Exception.new("Invalid value: #{str} must be [A-Za-y0-9]") unless str =~ /^[A-Za-z0-9]*$/
|
124
|
+
str
|
125
|
+
}
|
126
|
+
AN_Codec.decoder = PASS_THROUGH_DECODER
|
127
|
+
|
128
|
+
ANP_Codec = Codec.new
|
129
|
+
ANP_Codec.encoder = lambda{|str|
|
130
|
+
raise ISO8583Exception.new("Invalid value: #{str} must be [A-Za-y0-9 ]") unless str =~ /^[A-Za-z0-9 ]*$/
|
131
|
+
str
|
132
|
+
}
|
133
|
+
ANP_Codec.decoder = PASS_THROUGH_DECODER
|
134
|
+
|
135
|
+
ANS_Codec = Codec.new
|
136
|
+
ANS_Codec.encoder = lambda{|str|
|
137
|
+
raise ISO8583Exception.new("Invalid value: #{str} must be [\x20-\x7E]") unless str =~ /^[\x20-\x7E]*$/
|
138
|
+
str
|
139
|
+
}
|
140
|
+
ANS_Codec.decoder = PASS_THROUGH_DECODER
|
141
|
+
|
142
|
+
Null_Codec = Codec.new
|
143
|
+
Null_Codec.encoder = lambda {|str|
|
144
|
+
str
|
145
|
+
}
|
146
|
+
Null_Codec.decoder = lambda {|str|
|
147
|
+
str.gsub(/\000*$/, '')
|
148
|
+
}
|
149
|
+
|
150
|
+
Track2 = Codec.new
|
151
|
+
Track2.encoder = lambda{|track2|
|
152
|
+
#SS | PAN | FS | Expiration Date | Service Code | Discretionary Data | ES | LRC
|
153
|
+
# SS = ;
|
154
|
+
# PAN = up to 19 digits (at least 9?)
|
155
|
+
# FS = '='
|
156
|
+
# Exp Date = YYMM
|
157
|
+
# SC: 3 digits or =
|
158
|
+
# ES = ?
|
159
|
+
# lrc : 1byte
|
160
|
+
raise ISO8583Exception.new("Invalid Track2 data: #{track2}") unless track2 =~ /^;*(\d{9,19})=(.*)\?.$/
|
161
|
+
track2
|
162
|
+
}
|
163
|
+
Track2.decoder = PASS_THROUGH_DECODER
|
164
|
+
|
165
|
+
def self._date_codec(fmt)
|
166
|
+
c = Codec.new
|
167
|
+
c.encoder = lambda {|date|
|
168
|
+
enc = case date
|
169
|
+
when DateTime, Date, Time
|
170
|
+
date.strftime(fmt)
|
171
|
+
when String
|
172
|
+
begin
|
173
|
+
dt = DateTime.strptime(date, fmt)
|
174
|
+
dt.strftime(fmt)
|
175
|
+
rescue
|
176
|
+
raise ISO8583Exception.new("Invalid format encoding: #{date}, must be #{fmt}.")
|
177
|
+
end
|
178
|
+
else
|
179
|
+
raise ISO8583Exception.new("Don't know how to encode: #{date.class} to a time.")
|
180
|
+
end
|
181
|
+
return enc
|
182
|
+
}
|
183
|
+
c.decoder = lambda {|str|
|
184
|
+
begin
|
185
|
+
DateTime.strptime(str, fmt)
|
186
|
+
rescue
|
187
|
+
raise ISO8583Exception.new("Invalid format decoding: #{str}, must be #{fmt}.")
|
188
|
+
end
|
189
|
+
}
|
190
|
+
|
191
|
+
c
|
192
|
+
end
|
193
|
+
|
194
|
+
YYYYMMDDHHcodec = _date_codec("%Y%m%d%H")
|
195
|
+
YYYYMMDDHH24MISScodec = _date_codec("YYYYMMDDHH24MISScodec")
|
196
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ISO8583
|
2
|
+
|
3
|
+
class Field
|
4
|
+
# may either be some other Field in which the length is encoded or a Fixnum for
|
5
|
+
# fixed length fields. Length should always be the length of the *encoded* value.
|
6
|
+
# A 6 digit BCD field will require a length 3, as will a 5 digit BCD field.
|
7
|
+
# The subclass BCDField handles this to keep things consistant.
|
8
|
+
attr_accessor :length
|
9
|
+
attr_accessor :codec
|
10
|
+
attr_accessor :padding
|
11
|
+
attr_accessor :max
|
12
|
+
|
13
|
+
attr_writer :name
|
14
|
+
attr_accessor :bmp
|
15
|
+
|
16
|
+
def name
|
17
|
+
"BMP #{bmp}: #{@name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse(raw)
|
21
|
+
len, raw = case length
|
22
|
+
when Fixnum
|
23
|
+
[length, raw]
|
24
|
+
when Field
|
25
|
+
length.parse(raw)
|
26
|
+
else
|
27
|
+
raise ISO8583Exception.new("Cannot determine the length of '#{name}' field")
|
28
|
+
end
|
29
|
+
|
30
|
+
raw_value = raw[0,len]
|
31
|
+
|
32
|
+
# make sure we have enough data ...
|
33
|
+
if raw_value.length != len
|
34
|
+
mes = "Field has incorrect length! field: #{raw_value} len/expected: #{raw_value.length}/#{len}"
|
35
|
+
raise ISO8583ParseException.new(mes)
|
36
|
+
end
|
37
|
+
|
38
|
+
rest = raw[len, raw.length]
|
39
|
+
begin
|
40
|
+
real_value = codec.decode(raw_value)
|
41
|
+
rescue
|
42
|
+
raise ISO8583ParseException.new($!.message+" (#{name})")
|
43
|
+
end
|
44
|
+
|
45
|
+
[ real_value, rest ]
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Encoding needs to consider length representation, the actual encoding (such as charset or BCD)
|
50
|
+
# and padding.
|
51
|
+
# The order may be important! This impl calls codec.encode and then pads, in case you need the other
|
52
|
+
# special treatment, you may need to override this method alltogether.
|
53
|
+
# In other cases, the padding has to be implemented by the codec, such as BCD with an odd number of nibbles.
|
54
|
+
def encode(value)
|
55
|
+
encoded_value = codec.encode(value)
|
56
|
+
|
57
|
+
if padding
|
58
|
+
if padding.arity == 1
|
59
|
+
encoded_value = padding.call(encoded_value)
|
60
|
+
elsif padding.arity == 2
|
61
|
+
encoded_value = padding.call(encoded_value, length)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
len_str = case length
|
66
|
+
when Fixnum
|
67
|
+
raise ISO8583Exception.new("Too long: #{value} (#{name})! length=#{length}") if encoded_value.length > length
|
68
|
+
raise ISO8583Exception.new("Too short: #{value} (#{name})! length=#{length}") if encoded_value.length < length
|
69
|
+
""
|
70
|
+
when Field
|
71
|
+
raise ISO8583Exception.new("Max lenth exceeded: #{value}, max: #{max}") if max && encoded_value.length > max
|
72
|
+
length.encode(encoded_value.length)
|
73
|
+
else
|
74
|
+
raise ISO8583Exception.new("Invalid length (#{length}) for '#{name}' field")
|
75
|
+
end
|
76
|
+
|
77
|
+
len_str + encoded_value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class BCDField < Field
|
82
|
+
# This corrects the length for BCD fields, as their encoded length is half (+ parity) of the
|
83
|
+
# content length. E.g. 123 (length = 3) encodes to "\x01\x23" (length 2)
|
84
|
+
def length
|
85
|
+
_length = super
|
86
|
+
(_length % 2) != 0 ? (_length / 2) + 1 : _length / 2
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ISO8583
|
2
|
+
|
3
|
+
# This file contains a number of preinstantiated Field definitions. You
|
4
|
+
# will probably need to create own fields in your implementation, please
|
5
|
+
# see Field and Codec for further discussion on how to do this.
|
6
|
+
# The fields currently available are those necessary to implement the
|
7
|
+
# BankAway spec.
|
8
|
+
#
|
9
|
+
# The following field types are available:
|
10
|
+
#
|
11
|
+
# [LLVAR_N] two byte variable length ASCII numeral, payload ASCII numerals
|
12
|
+
# [LLVAR_ANS] two byte variable length ASCII numeral, payload ASCII
|
13
|
+
# [ANS] fixed length letters, represented in ASCII
|
14
|
+
# [N] fixed lengh numerals, repesented in ASCII, padding right justified using zeros
|
15
|
+
# [B] binary data, padding left using nulls (0x00)
|
16
|
+
# [YYYYMMDDHH] Date, formatted as described in ASCII numerals
|
17
|
+
# [YYYYMMDDHH24MISS] Date, formatted as named in ASCII numerals
|
18
|
+
|
19
|
+
# Special form to de/encode variable length indicators, two bytes ASCII numerals
|
20
|
+
LL = Field.new
|
21
|
+
LL.name = "LL"
|
22
|
+
LL.length = 2
|
23
|
+
LL.codec = ASCII_Number
|
24
|
+
LL.padding = lambda {|value|
|
25
|
+
sprintf("%02d", value)
|
26
|
+
}
|
27
|
+
|
28
|
+
# Two byte variable length ASCII numeral, payload ASCII numerals
|
29
|
+
LLVAR_N = Field.new
|
30
|
+
LLVAR_N.length = LL
|
31
|
+
LLVAR_N.codec = ASCII_Number
|
32
|
+
|
33
|
+
# Two byte variable length alpha-numeric with special characters.
|
34
|
+
LLVAR_ANS = Field.new
|
35
|
+
LLVAR_ANS.length = LL
|
36
|
+
LLVAR_ANS.codec = ANS_Codec
|
37
|
+
LLVAR_ANS.padding = PADDING_LEFT_JUSTIFIED_SPACES
|
38
|
+
|
39
|
+
# Two byte variable length ASCII letters
|
40
|
+
# Fixed lengh numerals, repesented in ASCII, padding right justified using zeros
|
41
|
+
N = Field.new
|
42
|
+
N.codec = ASCII_Number
|
43
|
+
N.padding = lambda {|val, len|
|
44
|
+
sprintf("%0#{len}d", val)
|
45
|
+
}
|
46
|
+
|
47
|
+
PADDING_LEFT_JUSTIFIED_SPACES = lambda {|val, len|
|
48
|
+
sprintf "%-#{len}s", val
|
49
|
+
}
|
50
|
+
|
51
|
+
# Fixed length ASCII letters [A-Za-z]
|
52
|
+
ANS = Field.new
|
53
|
+
ANS.codec = ANS_Codec
|
54
|
+
|
55
|
+
# Binary data, padding left using nulls (0x00)
|
56
|
+
B = Field.new
|
57
|
+
B.codec = Null_Codec
|
58
|
+
B.padding = lambda {|val, len|
|
59
|
+
while val.length < len
|
60
|
+
val = val + "\000"
|
61
|
+
end
|
62
|
+
val
|
63
|
+
}
|
64
|
+
|
65
|
+
# Date, formatted as described in Bankaway
|
66
|
+
YYYYMMDDHH = Field.new
|
67
|
+
YYYYMMDDHH.codec = YYYYMMDDHHcodec
|
68
|
+
YYYYMMDDHH.length = 8
|
69
|
+
|
70
|
+
#Date, formatted as described in Bankaway
|
71
|
+
YYYYMMDDHH24MISS = Field.new
|
72
|
+
YYYYMMDDHH24MISS.codec = YYYYMMDDHH24MISScodec
|
73
|
+
YYYYMMDDHH24MISS.length = 14
|
74
|
+
|
75
|
+
end
|