iso8583-mkb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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: []
|