iso8583-mkb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .*.kate-swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in iso8583-mkb.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sergey Gridassov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Iso8583::Mkb
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'iso8583-mkb'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install iso8583-mkb
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/iso8583-mkb/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Sergey Gridasov"]
6
+ gem.email = ["grindars@gmail.com"]
7
+ gem.description = %q{ISO8583 implementation for Credit Bank of Moscow}
8
+ gem.summary = %q{ISO8583 implementation for MKB}
9
+ gem.homepage = "https://github.com/smartkiosk/iso8583-mkb"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "iso8583-mkb"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = ISO8583::MKB::VERSION
17
+
18
+ gem.add_dependency 'iso8583'
19
+ gem.add_dependency 'eventmachine'
20
+ end
@@ -0,0 +1,63 @@
1
+ module ISO8583::MKB
2
+ class Authorization < Request
3
+ MTI = "Authorization Request"
4
+ REPEAT_MTI = "Authorization Repeat"
5
+
6
+ MANDATORY = {
7
+ "pan" => "Primary Account Number (PAN)",
8
+ "processing_code" => "Processing Code",
9
+ "amount" => "Amount, Transaction",
10
+ "expiry" => "Date, Expiration",
11
+ "merchant_type" => "Merchant's Type",
12
+ "acquirer_country" => "Acquiring Country Code",
13
+ "entry_mode" => "Point of Service Entry Mode Code",
14
+ "condition_code" => "Point of Service Condition Code",
15
+ "acquirer" => "Acquiring Institution ID code",
16
+ "track2" => "Track 2 data",
17
+ "currency" => "Currency Code, Transaction",
18
+ }
19
+
20
+ OPTIONAL = {
21
+ "conversion_date" => "Date, Conversion",
22
+
23
+ "billing_amount" => "Amount, Cardholder Billing",
24
+ "billing_convrate" => "Conversion, Cardholder Billing",
25
+ "billing_currency" => "Currency Code, Cardholder Billing",
26
+
27
+ "terminal_id" => "Card Acceptor Terminal ID",
28
+ "acceptor_id" => "Card Acceptor ID Code",
29
+ "acceptor_name" => "Card Acceptor Name/Location",
30
+
31
+ "additional" => "Additional Information"
32
+ }
33
+
34
+ RESPONSE = {
35
+ "Authorization Identification Response" => "auth_id",
36
+ "Additional Response Data" => "additional_resp"
37
+ }
38
+
39
+ build_class
40
+
41
+ def reverse
42
+ reversal = Reversal.new
43
+
44
+ [
45
+ :pan, :processing_code, :amount, :expiry, :merchant_type,
46
+ :acquirer_country, :entry_mode, :condition_code, :acquirer,
47
+ :currency
48
+ ].each do |key|
49
+ reversal.send :"#{key}=", instance_variable_get(:"@#{key}")
50
+ end
51
+
52
+ reversal.auth_id = @auth_id
53
+ reversal.response_code = @status
54
+ reversal.original_data = sprintf("%04d%06d%10s%011d%011d",
55
+ @request.request.mti,
56
+ @transaction.trace,
57
+ @request.request["Transmission Date and Time"].strftime("%m%d%H%M%S"),
58
+ @acquirer, @acquirer).to_i
59
+
60
+ reversal
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,108 @@
1
+ module ISO8583::MKB
2
+ module Errors
3
+ ERRORS = {
4
+ "00" => "Approved",
5
+ "01" => "Refer to issuer",
6
+ "02" => "Refer to issuer (special)",
7
+ "03" => "Invalid merchant",
8
+ "04" => "Pick up card",
9
+ "05" => "Do not honor",
10
+ "06" => "Error",
11
+ "07" => "Pick up card (special)",
12
+ "08" => "Honor with identification",
13
+ "09" => "Request in progress",
14
+ "10" => "Approved for partial amount",
15
+ "11" => "VIP Approval",
16
+ "12" => "Invalid transaction",
17
+ "13" => "Invalid amount",
18
+ "14" => "Card number does not exist",
19
+ "15" => "No such issuer",
20
+ "16" => "Approved, update track 3",
21
+ "17" => "Customer cancellation",
22
+ "18" => "Customer dispute",
23
+ "19" => "Re-enter transaction",
24
+ "20" => "Invalid response",
25
+ "21" => "No action taken (no match)",
26
+ "22" => "Suspected malfunction",
27
+ "23" => "Unacceptable transaction fee",
28
+ "24" => "File update not supported by receiver",
29
+ "25" => "Unable to locate record",
30
+ "26" => "Duplicate file update record",
31
+ "27" => "File update field edit error",
32
+ "28" => "File temporarily unavailable",
33
+ "29" => "File update not successful",
34
+ "30" => "Format error",
35
+ "31" => "Issuer sign-off",
36
+ "32" => "Completed partially",
37
+ "33" => "Expired card",
38
+ "34" => "Suspected fraud",
39
+ "35" => "Card acceptor contact acquirer",
40
+ "36" => "Restricted card",
41
+ "37" => "Card acceptor call acquirer",
42
+ "38" => "Allowable PIN tries exceeded",
43
+ "39" => "No credit account",
44
+ "40" => "Function not supported",
45
+ "41" => "Pick up card (lost card)",
46
+ "42" => "No universal account",
47
+ "43" => "Pick up card (stolen card)",
48
+ "44" => "No investment account",
49
+ "51" => "Not sufficient funds",
50
+ "52" => "No checking account",
51
+ "53" => "No savings account",
52
+ "54" => "Expired card",
53
+ "55" => "Incorrect PIN",
54
+ "56" => "No card record",
55
+ "57" => "Transaction not permitted to card",
56
+ "58" => "Transaction not permitted to card",
57
+ "59" => "Suspected fraud",
58
+ "60" => "Card acceptor contact acquirer",
59
+ "61" => "Exceeds withdrawal limit",
60
+ "62" => "Restricted card",
61
+ "63" => "Security violation",
62
+ "64" => "Original amount incorrect",
63
+ "65" => "Activity count exceeded",
64
+ "66" => "Card acceptor call acquirer",
65
+ "67" => "Card pick up at ATM",
66
+ "68" => "Response received too late",
67
+ "75" => "Too many wrong PIN tries",
68
+ "76" => "Previous message not found",
69
+ "77" => "Data does not match original message",
70
+ "80" => "Invalid date",
71
+ "81" => "Cryptographic error in PIN",
72
+ "82" => "Incorrect CVV",
73
+ "83" => "Unable to verify PIN",
74
+ "84" => "Invalid authorization life cycle",
75
+ "85" => "No reason to decline",
76
+ "86" => "PIN validation not possible",
77
+ "88" => "Cryptographic failure",
78
+ "89" => "Authentication failure",
79
+ "90" => "Cutoff is in process",
80
+ "91" => "Issuer or switch inoperative",
81
+ "92" => "No routing path",
82
+ "93" => "Violation of law",
83
+ "94" => "Duplicate transmission",
84
+ "95" => "Reconcile error",
85
+ "96" => "System malfunction",
86
+ "XA" => "Forward to issuer",
87
+ "XD" => "Forward to issuer",
88
+
89
+ # ISO8583::MKB-generated errors
90
+ "C0" => "Request timed out",
91
+ }
92
+
93
+ def self.success?(status)
94
+ # TODO: FIXME
95
+
96
+ status == "00"
97
+ end
98
+
99
+ def self.error(status)
100
+ if ERRORS.include? status
101
+ ERRORS[status]
102
+ else
103
+ "(unknown error #{status})"
104
+ end
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,84 @@
1
+ module ISO8583::MKB
2
+ class Gateway
3
+ def initialize(config)
4
+ uri = URI(config[:dhi])
5
+
6
+ raise "expected tcp scheme for DHI" if uri.scheme != "tcp"
7
+ @dhi_host = uri.host
8
+ @dhi_port = uri.port
9
+ @dhi_open = false
10
+ @reopen_timer = nil
11
+
12
+ @deferred = []
13
+ Logging.logger.info "Gateway started"
14
+ end
15
+
16
+ def run
17
+ EventMachine.run do
18
+ reopen_dhi
19
+ end
20
+ end
21
+
22
+ def stop
23
+ if !@reopen_timer.nil?
24
+ EventMachine.cancel_timer @reopen_timer
25
+ @reopen_timer = nil
26
+ end
27
+
28
+ if !@dhi.nil?
29
+ @dhi_open = false
30
+ @dhi.closed = ->() {}
31
+
32
+ @dhi.close_connection
33
+ @dhi = nil
34
+ end
35
+
36
+ @deferred.clear
37
+ end
38
+
39
+ def new_transaction
40
+ ISO8583Transaction.new self
41
+ end
42
+
43
+ def post_request(request)
44
+
45
+ if @dhi_open ||
46
+ ((request.request.mti == 800 || request.request.mti == 810) && !@dhi.nil?)
47
+ @dhi.request request
48
+ else
49
+ @deferred << request
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def dhi_open
56
+ @dhi_open = true
57
+
58
+ @deferred.each do |request|
59
+ @dhi.request request
60
+ end
61
+
62
+ @deferred.clear
63
+ end
64
+
65
+ def dhi_closed
66
+ @dhi_open = false
67
+ @dhi = nil
68
+
69
+ @reopen_timer = EventMachine.add_timer 15, method(:reopen_dhi)
70
+ end
71
+
72
+ def reopen_dhi
73
+ @reopen_timer = nil
74
+
75
+ Logging.logger.debug "Opening connection to DHI"
76
+
77
+ @dhi = EventMachine.connect @dhi_host, @dhi_port, ISO8583Connection, self
78
+ @dhi.open = method :dhi_open
79
+ @dhi.closed = method :dhi_closed
80
+ @dhi.sign_in
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,225 @@
1
+ module ISO8583::MKB
2
+ class ISO8583Connection < EventMachine::Connection
3
+ attr_reader :gateway
4
+ attr_accessor :open, :closed
5
+
6
+ def initialize(gateway)
7
+ super
8
+
9
+ @gateway = gateway
10
+ @in_air = {}
11
+ @buffer = ""
12
+ @state = :connecting
13
+ @post_sign_on = []
14
+ @ping_timer = nil
15
+ @open = nil
16
+ @closed = nil
17
+ end
18
+
19
+ def post_init
20
+ Logging.logger.debug "Connected to DHI"
21
+ end
22
+
23
+ def sign_in
24
+
25
+ transaction = @gateway.new_transaction
26
+ request = transaction.request do |m|
27
+ m.mti = "Network Management Request"
28
+ m["Network Management Information Code"] = 1
29
+ end
30
+
31
+ @state = :ready
32
+
33
+ request.start(30) do |response|
34
+
35
+ transaction.complete
36
+
37
+ if response.nil?
38
+ Logging.logger.error "No response to Sign On"
39
+
40
+ close_connection_after_writing
41
+ else
42
+ if Errors.success? response.response_code
43
+
44
+ @state = :ready
45
+
46
+ @post_sign_on.each do |request|
47
+ self.request request
48
+ end
49
+
50
+ @post_sign_on.clear
51
+
52
+ send_ping
53
+
54
+ @open.call
55
+ else
56
+ Logging.logger.error "Sign On failed: #{Errors.error response.response_code}"
57
+
58
+ close_connection_after_writing
59
+ end
60
+ end
61
+ end
62
+
63
+ @state = :connecting
64
+ end
65
+
66
+ def receive_data(data)
67
+ @buffer << data
68
+
69
+ until @buffer.empty?
70
+ marker = @buffer.index "\xFF"
71
+
72
+ if marker.nil?
73
+ @buffer.clear
74
+ break
75
+ end
76
+
77
+ if marker != 0
78
+ @buffer.slice! 0, marker
79
+ end
80
+
81
+ break if @buffer.length < 6
82
+
83
+ data_length = @buffer[1..5].to_i
84
+
85
+ break if @buffer.length < 6 + data_length
86
+
87
+ @buffer.slice! 0, 6
88
+ msg = @buffer.slice! 0, data_length
89
+ begin
90
+ receive_message MKBMessage.parse(msg)
91
+ rescue => e
92
+ Logging.logger.error "receive_message failed: #{e}"
93
+ e.backtrace.each { |line| Logging.logger.error line }
94
+ end
95
+ end
96
+ end
97
+
98
+ def unbind
99
+ Logging.logger.debug "Disconnected from DHI"
100
+
101
+ @state = :disconnecting
102
+
103
+ @closed.call
104
+
105
+ EventMachine.cancel_timer @ping_timer unless @ping_timer.nil?
106
+
107
+ @in_air.each do |key, (request, timer)|
108
+ Logging.logger.debug "Request #{request.request.reference} dropped"
109
+
110
+ EventMachine.cancel_timer timer unless timer.nil?
111
+ request.complete nil
112
+ end
113
+
114
+ @post_sign_on.each do |request|
115
+ @gateway.post_request request
116
+ end
117
+
118
+ @in_air.clear
119
+ @post_sign_on.clear
120
+ end
121
+
122
+ def request(request)
123
+ if @state == :connecting
124
+ @post_sign_on << request
125
+ else
126
+
127
+ id = request.request.reference.to_i
128
+
129
+ Logging.logger.debug "Request #{id}: started"
130
+
131
+ raise "duplicate request #{id}" if @in_air.include? id
132
+
133
+ if request.timeout.nil?
134
+ timer = nil
135
+ else
136
+ timer = EventMachine.add_timer(request.timeout) do
137
+ @in_air[id][1] = nil
138
+ forget_request id
139
+ request.complete nil
140
+ end
141
+ end
142
+
143
+ @in_air[id] = [ request, timer ]
144
+
145
+ send_message request.request
146
+ end
147
+ end
148
+
149
+ private
150
+
151
+ def send_ping
152
+ Logging.logger.debug "Ping"
153
+
154
+ @ping_timer = nil
155
+
156
+ transaction = @gateway.new_transaction
157
+ request = transaction.request do |m|
158
+ m.mti = "Network Management Request"
159
+ m["Network Management Information Code"] = 301
160
+ end
161
+
162
+ request.start(60) do |resp|
163
+ transaction.complete
164
+
165
+ if resp.nil?
166
+ Logging.logger.error "Ping timeout"
167
+
168
+ close_connection_after_writing
169
+ elsif !Errors.success?(resp.response_code)
170
+ Logging.logger.error "Ping failed: #{Errors.error resp.response_code}"
171
+
172
+ close_connection_after_writing
173
+ else
174
+ Logging.logger.debug "Pong"
175
+
176
+ @ping_timer = EventMachine.add_timer(60, method(:send_ping))
177
+ end
178
+ end
179
+ end
180
+
181
+ def forget_request(id)
182
+ Logging.logger.debug "Request #{id}: completed"
183
+
184
+ request, timer = @in_air[id]
185
+ EventMachine.cancel_timer timer unless timer.nil?
186
+
187
+ @in_air.delete id
188
+ end
189
+
190
+ def send_message(message)
191
+ Logging.message message
192
+
193
+ msg = message.to_b
194
+ msg.prepend sprintf("\xFF%05u", msg.length)
195
+
196
+ send_data msg
197
+ end
198
+
199
+ def receive_message(message)
200
+ Logging.message message
201
+
202
+ type = (message.mti / 10) % 10
203
+ case type
204
+ when 0, 2, 4
205
+ Logging.logger.warn "cannot handle type #{type} yet"
206
+
207
+ when 1, 3
208
+ id = message.reference.to_i
209
+
210
+ if !@in_air.include?(id)
211
+ Logging.logger.warn "message #{id} not in air - already timed out?"
212
+ else
213
+ request, = @in_air[id]
214
+
215
+ forget_request id
216
+
217
+ request.complete message
218
+ end
219
+
220
+ else
221
+ Logging.logger.warn "unknown type in MTI: #{type}"
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,39 @@
1
+ module ISO8583
2
+ MMDDCodec = _date_codec("%m%d")
3
+ FhhmmssCodec = _date_codec("%H%M%S")
4
+
5
+ MMDD = Field.new
6
+ MMDD.codec = MMDDCodec
7
+ MMDD.length = 4
8
+
9
+ Fhhmmss = Field.new
10
+ Fhhmmss.codec = FhhmmssCodec
11
+ Fhhmmss.length = 6
12
+
13
+ F63Length = Field.new
14
+ F63Length.name = "F63"
15
+ F63Length.length = 6
16
+ F63Length.codec = ASCII_Number
17
+ F63Length.padding = ->(value) do
18
+ sprintf("\x20\x00\x00%03d", value)
19
+ end
20
+
21
+ F63 = Field.new
22
+ F63.length = F63Length
23
+ F63.codec = ASCII_Number
24
+
25
+ class Message
26
+ def self.parse(str)
27
+ message = self.new
28
+ message.mti, rest = _mti_format.parse(str)
29
+ bmp,rest = Bitmap.parse(rest)
30
+ bmp.each {|bit|
31
+ next if bit < 2
32
+ bmp_def = _definitions[bit]
33
+ value, rest = bmp_def.field.parse(rest)
34
+ message[bit] = value
35
+ }
36
+ message
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module ISO8583::MKB
2
+ class ISO8583Request
3
+ attr_reader :transaction, :request, :timeout
4
+
5
+ def initialize(transaction, request)
6
+ @transaction = transaction
7
+ @request = request
8
+ @timeout = nil
9
+ @completion = nil
10
+ end
11
+
12
+ def start(timeout = nil, &completion)
13
+ @completion = completion
14
+ @timeout = timeout
15
+
16
+ @transaction.gateway.post_request self
17
+ end
18
+
19
+ def complete(result)
20
+ block = @completion
21
+ @completion = nil
22
+
23
+ block.call result
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,43 @@
1
+ module ISO8583::MKB
2
+ class ISO8583Transaction
3
+ attr_reader :id, :trace, :gateway
4
+
5
+ @@trace_counter = 0
6
+
7
+ def initialize(gateway)
8
+ @gateway = gateway
9
+
10
+ now = Time.now
11
+ @id = (now.year % 100) * 1_00_0000_0000 +
12
+ (now.mday) * 1_0000_0000 +
13
+ now.hour * 3600_000 +
14
+ now.min * 60_000 +
15
+ now.sec * 1_000 +
16
+ now.usec / 1_000
17
+
18
+ @trace = @@trace_counter += 1
19
+ @started_at = now
20
+
21
+ Logging.logger.debug "Transaction #{id} started"
22
+ end
23
+
24
+ def request(&block)
25
+ message = MKBMessage.new
26
+ message["Transmission Date and Time"] = Time.now.utc
27
+ message["Systems Trace Audit Number"] = @trace
28
+ message["Time, Local Transaction"] = @started_at
29
+ message["Date, Local Transaction"] = @started_at
30
+ message.reference = @id.to_s
31
+
32
+ yield message
33
+
34
+ ISO8583Request.new self, message
35
+ end
36
+
37
+ def complete
38
+ # TODO: track transactions
39
+
40
+ Logging.logger.debug "Transaction #{@id} completed"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ module ISO8583::MKB
2
+ module Logging
3
+ class << self
4
+ def started?
5
+ !@logger.nil?
6
+ end
7
+
8
+ def start(logfile = nil)
9
+ logfile = STDERR if logfile.nil?
10
+
11
+ if logfile.kind_of?(Logger)
12
+ @logger = logfile
13
+ else
14
+ @logger = Logger.new logfile, :daily
15
+ end
16
+ end
17
+
18
+ def stop
19
+ @logger = nil
20
+ end
21
+
22
+ def message(message)
23
+ message.to_s.split("\n").each(&@logger.method(:debug))
24
+ end
25
+
26
+ attr_reader :logger
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,100 @@
1
+ module ISO8583::MKB
2
+ class MKBMessage < ISO8583::Message
3
+ include ISO8583
4
+
5
+ mti_format N, length: 4
6
+
7
+ mti 100, "Authorization Request"
8
+ mti 101, "Authorization Repeat"
9
+ mti 102, "ATM Confirmation"
10
+ mti 110, "Authorization Response"
11
+ mti 120, "Advice Authorization Request"
12
+ mti 121, "Advice Repeat"
13
+ mti 130, "Authorization Advice Response"
14
+ mti 200, "Financial Transaction Request" # to host only
15
+ mti 210, "Financial Transaction Response" # from host only
16
+ mti 220, "Financial Transaction Advice" # to host only
17
+ mti 230, "Financial Transaction Response" # from host only
18
+ mti 302, "Interface Exception Request"
19
+ mti 312, "Host Exception Response"
20
+ mti 322, "Host Exception Advice Request"
21
+ mti 332, "Interface Exception Advice Response"
22
+ mti 400, "Reversal Request"
23
+ mti 401, "Reversal Repeat"
24
+ mti 410, "Reversal Response"
25
+ mti 420, "Reversal Advice" # to host only
26
+ mti 430, "Reversal Advice" # from host only
27
+ mti 522, "Issuer Reconciliation Advice" # to host only (for future use)
28
+ mti 532, "Issuer Reconciliation Advice" # from host only
29
+ mti 800, "Network Management Request"
30
+ mti 810, "Network Management Response"
31
+
32
+ bmp 2, "Primary Account Number (PAN)", LLVAR_N, max: 19
33
+ bmp 3, "Processing Code", N, length: 6
34
+ bmp 4, "Amount, Transaction", N, length: 12
35
+ bmp 5, "Amount, Settlement", N, length: 12
36
+ bmp 6, "Amount, Cardholder Billing", N, length: 12
37
+ bmp 7, "Transmission Date and Time", MMDDhhmmss
38
+ bmp 9, "Conversion Rate, Settlement", N, length: 8
39
+ bmp 10, "Conversion, Cardholder Billing", N, length: 8
40
+ bmp 11, "Systems Trace Audit Number", N, length: 6
41
+ bmp 12, "Time, Local Transaction", Fhhmmss
42
+ bmp 13, "Date, Local Transaction", MMDD
43
+ bmp 14, "Date, Expiration", YYMM
44
+ bmp 15, "Date, Settlement", MMDD
45
+ bmp 16, "Date, Conversion", MMDD
46
+ bmp 18, "Merchant's Type", N, length: 4
47
+ bmp 19, "Acquiring Country Code", N, length: 3
48
+ bmp 22, "Point of Service Entry Mode Code", N, length: 4
49
+ bmp 24, "Network International Identifier", AN, length: 3
50
+ bmp 25, "Point of Service Condition Code", N, length: 2
51
+ bmp 28, "Amount, Fee", N, length: 9
52
+ bmp 32, "Acquiring Institution ID code", LLVAR_N, max: 11
53
+ bmp 33, "Forwarding Institution ID code", LLVAR_N, max: 11
54
+ bmp 35, "Track 2 data", LLVAR_B, max: 37
55
+ bmp 37, "Retrieval Reference Number", AN, length: 12
56
+ bmp 38, "Authorization Identification Response", ANP, length: 6
57
+ bmp 39, "Response Code", AN, length: 2
58
+ bmp 41, "Card Acceptor Terminal ID", ANS, length: 8
59
+ bmp 42, "Card Acceptor ID Code", ANS, length: 15
60
+ bmp 43, "Card Acceptor Name/Location", ANS, length: 40
61
+ bmp 44, "Additional Response Data", LLVAR_ANS, max: 25
62
+ bmp 45, "Track 1 data", LLVAR_ANS, max: 80
63
+ bmp 48, "Additional Data-Private", LLVAR_ANS, max: 99
64
+ bmp 49, "Currency Code, Transaction", N, length: 3
65
+ bmp 50, "Currency Code, Settlement", N, length: 3
66
+ bmp 51, "Currency Code, Cardholder Billing", N, length: 3
67
+ bmp 52, "Personal Identification Number Data", B, length: 8
68
+ bmp 53, "Security Related Control Information", N, length: 16
69
+ bmp 54, "Additional Amounts", LLLVAR_ANS, max: 120
70
+ bmp 55, "ICC Related Data", LLLVAR_B, max: 255
71
+ bmp 60, "Additional POS Information", LLVAR_N, max: 6
72
+ bmp 61, "Other Amounts", LLVAR_N, max: 36
73
+ bmp 63, "Message Reason Code", F63, max: 4
74
+ bmp 66, "Settlement Code", A, length: 1
75
+ bmp 70, "Network Management Information Code", N, length: 3
76
+ bmp 73, "Date, Action", N, length: 6
77
+ bmp 74, "Credits, Number", N, length: 10
78
+ bmp 75, "Credits, Reversal Number", N, length: 10
79
+ bmp 76, "Debits, Number", N, length: 10
80
+ bmp 77, "Debits, Reversal Number", N, length: 10
81
+ bmp 86, "Credits, Amount", N, length: 16
82
+ bmp 87, "Credits, Reversal Amount", N, length: 16
83
+ bmp 88, "Debits, Amount", N, length: 16
84
+ bmp 89, "Debits, Reversal Amount", N, length: 16
85
+ bmp 90, "Original Data Elements", N, length: 42
86
+ bmp 91, "File Update Code", AN, length: 1
87
+ bmp 95, "Replacement Amounts", AN, length: 42
88
+ bmp 101, "File Name", LLVAR_AN, max: 17
89
+ bmp 102, "Account From", LLVAR_AN, max: 28
90
+ bmp 103, "Account To", LLVAR_AN, max: 28
91
+ bmp 118, "Intra Country", LLVAR_AN, max: 99
92
+ bmp 120, "Additional Information", LLLVAR_ANS, max: 990
93
+ bmp 122, "Remaining Open-To-Use", LLVAR_ANS, max: 25
94
+
95
+ # TODO: fields 126, 127 (non-ISO encoding)
96
+
97
+ bmp_alias 37, :reference
98
+ bmp_alias 39, :response_code
99
+ end
100
+ end
@@ -0,0 +1,121 @@
1
+ module ISO8583::MKB
2
+ class Request
3
+ class << self
4
+ def build_class
5
+ [ const_get(:MANDATORY), const_get(:OPTIONAL) ].each do |hash|
6
+ hash.each do |key, iso8583_name|
7
+ attr_accessor key.intern
8
+ end
9
+ end
10
+
11
+ const_get(:RESPONSE).each do |iso8583_name, key|
12
+ attr_reader key.intern
13
+ end
14
+ end
15
+ end
16
+
17
+ attr_reader :request, :status
18
+
19
+ attr_accessor :retries
20
+ attr_accessor :timeout
21
+
22
+ def initialize
23
+ @retries = 4
24
+ @timeout = 60
25
+ @retry_with = nil
26
+ @complete = nil
27
+ @request = nil
28
+ end
29
+
30
+ def success?
31
+ Errors.success? @status
32
+ end
33
+
34
+ def status_description
35
+ Errors.error @status
36
+ end
37
+
38
+ def submit(gateway, &complete)
39
+ @transaction = gateway.new_transaction
40
+
41
+ translated_fields = {}
42
+
43
+ self.class.const_get(:MANDATORY).each do |key, iso8583_name|
44
+ value = instance_variable_get :"@#{key}"
45
+ if value.nil?
46
+ raise "required attribute #{key} (#{iso8583_name}) is not specified"
47
+ end
48
+
49
+ translated_fields[iso8583_name] = value
50
+ end
51
+
52
+
53
+ self.class.const_get(:OPTIONAL).each do |key, iso8583_name|
54
+ value = instance_variable_get :"@#{key}"
55
+ unless value.nil?
56
+ translated_fields[iso8583_name] = value
57
+ end
58
+ end
59
+
60
+ @request = @transaction.request do |message|
61
+ message.mti = self.class.const_get(:MTI)
62
+
63
+ translated_fields.each do |key, value|
64
+ message[key] = value
65
+ end
66
+
67
+ message.to_b # to validate encoding of the fields
68
+ end
69
+
70
+ @retry_with = [
71
+ self.class.const_get(:REPEAT_MTI),
72
+ translated_fields
73
+ ]
74
+
75
+ @complete = complete
76
+ @request.start @timeout, &method(:request_completed)
77
+ end
78
+
79
+ private
80
+
81
+ def request_completed(response)
82
+ if response.nil?
83
+ Logging.logger.warn "Request timed out"
84
+
85
+ if @retries == 0
86
+ Logging.logger.warn "Out of retries"
87
+
88
+ complete_request 'C0'
89
+ else
90
+ @retries -= 1
91
+
92
+ Logging.logger.warn "Repeating, #{@retries} more tries"
93
+
94
+ @request = @transaction.request do |message|
95
+ message.mti, fields = @retry_with
96
+
97
+ fields.each do |key, value|
98
+ message[key] = value
99
+ end
100
+
101
+ @request.start @timeout, &method(:request_completed)
102
+ end
103
+ end
104
+ else
105
+ self.class.const_get(:RESPONSE).each do |iso8583_name, key|
106
+ instance_variable_set :"@#{key}", response[iso8583_name]
107
+ end
108
+
109
+ complete_request response.response_code
110
+ end
111
+ end
112
+
113
+ def complete_request(status)
114
+ @status = status
115
+ @transaction.complete
116
+
117
+ complete, @complete = @complete, nil
118
+ complete.call self
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,42 @@
1
+ module ISO8583::MKB
2
+ class Reversal < Request
3
+ MTI = "Reversal Request"
4
+ REPEAT_MTI = "Reversal Repeat"
5
+
6
+ MANDATORY = {
7
+ "pan" => "Primary Account Number (PAN)",
8
+ "processing_code" => "Processing Code",
9
+ "amount" => "Amount, Transaction",
10
+ "expiry" => "Date, Expiration",
11
+ "merchant_type" => "Merchant's Type",
12
+ "acquirer_country" => "Acquiring Country Code",
13
+ "entry_mode" => "Point of Service Entry Mode Code",
14
+ "condition_code" => "Point of Service Condition Code",
15
+ "acquirer" => "Acquiring Institution ID code",
16
+ "currency" => "Currency Code, Transaction",
17
+ "auth_id" => "Authorization Identification Response",
18
+ "response_code" => "Response Code",
19
+ "reason" => "Message Reason Code",
20
+ "original_data" => "Original Data Elements"
21
+ }
22
+
23
+ OPTIONAL = {
24
+ "billing_amount" => "Amount, Cardholder Billing",
25
+ "billing_convrate" => "Conversion, Cardholder Billing",
26
+ "billing_currency" => "Currency Code, Cardholder Billing",
27
+
28
+ "terminal_id" => "Card Acceptor Terminal ID",
29
+ "acceptor_id" => "Card Acceptor ID Code",
30
+ "acceptor_name" => "Card Acceptor Name/Location",
31
+
32
+ "replacement_amount" => "Replacement Amounts",
33
+ "additional" => "Additional Information"
34
+ }
35
+
36
+ RESPONSE = {
37
+
38
+ }
39
+
40
+ build_class
41
+ end
42
+ end
@@ -0,0 +1,74 @@
1
+ module ISO8583::MKB
2
+ class SynchronousGateway
3
+ def initialize(config)
4
+ if EventMachine.reactor_running?
5
+ @loop_is_mine = false
6
+ @gateway = Gateway.new config
7
+ @gateway.run
8
+ else
9
+ @loop_is_mine = true
10
+ @ready = Queue.new
11
+ @thread = Thread.new(config, &method(:thread))
12
+ e = @ready.pop
13
+ @ready = nil
14
+
15
+ raise e unless e.nil?
16
+ end
17
+ end
18
+
19
+ def stop
20
+ EventMachine.schedule do
21
+ @gateway.stop
22
+ EventMachine.stop_event_loop if @loop_is_mine
23
+ end
24
+
25
+ if @loop_is_mine
26
+ @thread.join
27
+ @thread = nil
28
+ end
29
+ end
30
+
31
+ def execute(request)
32
+ queue = Queue.new
33
+
34
+ EventMachine.schedule do
35
+ request.submit(@gateway) { queue.push nil }
36
+ end
37
+
38
+ queue.pop
39
+ end
40
+
41
+ private
42
+
43
+ def thread(config)
44
+ begin
45
+ @gateway = Gateway.new config
46
+
47
+ @ready.push nil
48
+
49
+ EventMachine.epoll
50
+ EventMachine.run do
51
+ EventMachine.error_handler do
52
+ Logging.logger.error "Uncaught exception in EventMachine:"
53
+ Logging.logger.error e.to_s
54
+ e.backtrace.each do |line|
55
+ Logging.logger.error line
56
+ end
57
+ end
58
+
59
+ @gateway.run
60
+ end
61
+
62
+ rescue => e
63
+ Logging.logger.error e.to_s
64
+ e.backtrace.each do |line|
65
+ Logging.logger.error line
66
+ end
67
+
68
+ if !@ready.nil?
69
+ @ready.push e
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,5 @@
1
+ module ISO8583
2
+ module MKB
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ require "logger"
2
+
3
+ require "eventmachine"
4
+ require "iso8583"
5
+
6
+ require "iso8583-mkb/version"
7
+ require "iso8583-mkb/iso8583_patches"
8
+ require "iso8583-mkb/logging"
9
+ require "iso8583-mkb/gateway"
10
+ require "iso8583-mkb/iso8583_connection"
11
+ require "iso8583-mkb/iso8583_transaction"
12
+ require "iso8583-mkb/iso8583_request"
13
+ require "iso8583-mkb/mkb_message"
14
+ require "iso8583-mkb/errors"
15
+ require "iso8583-mkb/request"
16
+ require "iso8583-mkb/authorization"
17
+ require "iso8583-mkb/reversal"
18
+ require "iso8583-mkb/synchronous_gateway"
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iso8583-mkb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sergey Gridasov
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: iso8583
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
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: eventmachine
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
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
+ description: ISO8583 implementation for Credit Bank of Moscow
47
+ email:
48
+ - grindars@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE
56
+ - README.md
57
+ - Rakefile
58
+ - iso8583-mkb.gemspec
59
+ - lib/iso8583-mkb.rb
60
+ - lib/iso8583-mkb/authorization.rb
61
+ - lib/iso8583-mkb/errors.rb
62
+ - lib/iso8583-mkb/gateway.rb
63
+ - lib/iso8583-mkb/iso8583_connection.rb
64
+ - lib/iso8583-mkb/iso8583_patches.rb
65
+ - lib/iso8583-mkb/iso8583_request.rb
66
+ - lib/iso8583-mkb/iso8583_transaction.rb
67
+ - lib/iso8583-mkb/logging.rb
68
+ - lib/iso8583-mkb/mkb_message.rb
69
+ - lib/iso8583-mkb/request.rb
70
+ - lib/iso8583-mkb/reversal.rb
71
+ - lib/iso8583-mkb/synchronous_gateway.rb
72
+ - lib/iso8583-mkb/version.rb
73
+ homepage: https://github.com/smartkiosk/iso8583-mkb
74
+ licenses: []
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.24
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: ISO8583 implementation for MKB
97
+ test_files: []