iso8583-mkb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/iso8583-mkb.gemspec +20 -0
- data/lib/iso8583-mkb/authorization.rb +63 -0
- data/lib/iso8583-mkb/errors.rb +108 -0
- data/lib/iso8583-mkb/gateway.rb +84 -0
- data/lib/iso8583-mkb/iso8583_connection.rb +225 -0
- data/lib/iso8583-mkb/iso8583_patches.rb +39 -0
- data/lib/iso8583-mkb/iso8583_request.rb +26 -0
- data/lib/iso8583-mkb/iso8583_transaction.rb +43 -0
- data/lib/iso8583-mkb/logging.rb +29 -0
- data/lib/iso8583-mkb/mkb_message.rb +100 -0
- data/lib/iso8583-mkb/request.rb +121 -0
- data/lib/iso8583-mkb/reversal.rb +42 -0
- data/lib/iso8583-mkb/synchronous_gateway.rb +74 -0
- data/lib/iso8583-mkb/version.rb +5 -0
- data/lib/iso8583-mkb.rb +18 -0
- metadata +97 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
data/iso8583-mkb.gemspec
ADDED
@@ -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
|
data/lib/iso8583-mkb.rb
ADDED
@@ -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: []
|