client_for_poslynx 0.9.0 → 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,50 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ def self.CallbackMap(*args)
8
+ if args.length == 1 && EMC::CallbackMap === args.first
9
+ return args.first
10
+ else
11
+ EMC::CallbackMap.new( *args )
12
+ end
13
+ end
14
+
15
+ class CallbackMap
16
+ def initialize(callable_map={})
17
+ @callable_map = callable_map
18
+ end
19
+
20
+ def ==(other)
21
+ callable_map == other.callable_map
22
+ end
23
+
24
+ def [](callback_key)
25
+ callable_map[callback_key]
26
+ end
27
+
28
+ def to_hash
29
+ callable_map.dup
30
+ end
31
+
32
+ def merge(other)
33
+ self.class.new(
34
+ callable_map.merge( other.to_hash )
35
+ )
36
+ end
37
+
38
+ def call(callback_key, *args)
39
+ callback = callable_map[callback_key]
40
+ callback.call *args if callback
41
+ end
42
+
43
+ protected
44
+
45
+ attr_reader :callable_map
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ class ConnectionHandler < EM::Connection
8
+ include EM::Protocols::POSLynx
9
+ include EMC::HandlesConnection
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ class State < Struct.new( :encryption, :connection, :connection_status, :status_of_request )
8
+
9
+ def connection_initial? ; connection_status == :initial ; end
10
+ def connecting? ; connection_status == :connecting ; end
11
+ def connected? ; connection_status == :connected ; end
12
+ def disconnecting? ; connection_status == :disconnecting ; end
13
+ def disconnecting? ; connection_status == :disconnected ; end
14
+
15
+ def request_initial? ; status_of_request == :initial ; end
16
+ def request_pending? ; status_of_request == :pending ; end
17
+ def got_response? ; status_of_request == :got_response ; end
18
+ def request_failed? ; status_of_request == :failed ; end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,66 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ class EventDispatcher
8
+ NULL_LISTENER = ->(*) { }
9
+
10
+ class << self
11
+ private :new
12
+
13
+ def null(connection)
14
+ new( connection )
15
+ end
16
+
17
+ def for_connect(connection, opts)
18
+ original_dispatcher = opts[:original_dispatcher]
19
+ callback_map = EMC.CallbackMap(
20
+ connection_completed: opts[:on_success],
21
+ unbind: opts[:on_failure],
22
+ )
23
+ new( connection, original_dispatcher, callback_map )
24
+ end
25
+
26
+ def for_disconnect(connection, opts)
27
+ callback_map = EMC.CallbackMap(
28
+ unbind: opts[:on_completed]
29
+ )
30
+ new( connection, nil, callback_map )
31
+ end
32
+
33
+ def for_send_request(connection, opts)
34
+ callback_map = EMC.CallbackMap(
35
+ receive_response: opts[:on_response],
36
+ unbind: opts[:on_failure],
37
+ )
38
+ new( connection, nil, callback_map )
39
+ end
40
+ end
41
+
42
+ def initialize(connection, original_dispatcher = nil, callback_map = EMC.CallbackMap())
43
+ @connection = connection
44
+ @original_dispatcher = original_dispatcher
45
+ @callback_map = callback_map
46
+ end
47
+
48
+ def []=(event_type, callback)
49
+ callback_map[event_type] = callback
50
+ end
51
+
52
+ def event_occurred(event_type, *args)
53
+ connection.reset_event_dispatcher
54
+ original_dispatcher.event_occurred( event_type, *args ) if original_dispatcher
55
+ callback_map.call event_type, *args
56
+ end
57
+
58
+ private
59
+
60
+ attr_reader :connection, :original_dispatcher, :callback_map
61
+
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,57 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ module HandlesConnection
8
+ def initialize(connector_state)
9
+ @connector_state = connector_state
10
+ connector_state.connection = self
11
+ connector_state.connection_status = :connecting
12
+ end
13
+
14
+ attr_writer :event_dispatcher
15
+
16
+ def event_dispatcher
17
+ @event_dispatcher ||= EMC::EventDispatcher.null( self )
18
+ end
19
+
20
+ def reset_event_dispatcher
21
+ self.event_dispatcher = nil
22
+ end
23
+
24
+ def connection_completed
25
+ if connector_state.encryption == :use_ssl
26
+ start_tls
27
+ else
28
+ connector_state.connection_status = :connected
29
+ event_dispatcher.event_occurred :connection_completed
30
+ end
31
+ end
32
+
33
+ def ssl_handshake_completed
34
+ connector_state.connection_status = :connected
35
+ event_dispatcher.event_occurred :connection_completed
36
+ end
37
+
38
+ def unbind
39
+ connector_state.connection_status = :disconnected
40
+ connector_state.status_of_request = :failed if connector_state.request_pending?
41
+ event_dispatcher.event_occurred :unbind
42
+ end
43
+
44
+ def receive_response(response_data)
45
+ connector_state.status_of_request = :got_response if connector_state.request_pending?
46
+ event_dispatcher.event_occurred :receive_response, response_data
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :connector_state
52
+
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ def self.RequestCall(*args)
8
+ if args.length == 1 && self::RequestCall === args.first
9
+ args.first
10
+ elsif args.first.nil?
11
+ RequestCall.new(nil, {})
12
+ else
13
+ RequestCall.new( *args )
14
+ end
15
+ end
16
+
17
+ class RequestCall < Struct.new(:request_data, :result_callbacks)
18
+ def initialize(request_data, result_callbacks={})
19
+ result_callbacks = EMC.CallbackMap( result_callbacks )
20
+ super request_data, result_callbacks
21
+ freeze
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'eventmachine'
4
4
 
5
- module EventMachine
5
+ module EM
6
6
  module Protocols
7
7
 
8
8
  # Sends requests to and receives responses from a Precidia
@@ -0,0 +1,250 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+
6
+ # Provides a synchronous, structured API for making
7
+ # requests to POSLynx host using Event Machine and returning
8
+ # responses when received.
9
+ #
10
+ # Eliminates the need for complicated and messy event
11
+ # callback chains in POSLynx client code.
12
+ #
13
+ # Multiple sessions may be in effect at the same time,
14
+ # interacting with the same POSLyx host by using the same
15
+ # Event Machine connector. This is important for performing
16
+ # actions such as initiating a new payment interaction when
17
+ # a previous interaction was abandoned.
18
+ class EM_Session
19
+
20
+ # Base class for errors raised by the session to code that
21
+ # is running in the context of a sesssion.
22
+ class Error < StandardError ; end
23
+
24
+ # Raised when an error condition is detected while making
25
+ # a request or while waiting for the response.
26
+ class RequestError < Error ; end
27
+
28
+ # Raised when attempting to make a request while another
29
+ # session is waiting for a response to a request of the
30
+ # same type (other than PinPadReset).
31
+ class ConflictingRequestError < Error ; end
32
+
33
+ # Executes the given block in the context of a session
34
+ # attached to the given Event Machine connector. The
35
+ # session is passed as the argument to the block.
36
+ def self.execute(connector)
37
+ new( connector ).execute { |s| yield s }
38
+ end
39
+
40
+ attr_reader :connector
41
+
42
+ attr_reader :status
43
+
44
+ attr_reader :em_system
45
+
46
+ # Builds a new EM_Session instance attached to the given
47
+ # Event Machine connector.
48
+ #
49
+ # ==== Options
50
+ # * <tt>:em_system</tt> - The event machine system that
51
+ # will be called on for operations such as adding timers
52
+ # and deferring actions.
53
+ def initialize(connector, opts={})
54
+ self.connector = connector
55
+ @em_system = opts.fetch( :em_system, ::EM )
56
+ self.status = :initialized
57
+ end
58
+
59
+ # Executes the given block in the context of the session
60
+ # The session is passed as the argument to the block.
61
+ def execute
62
+ @fiber = Fiber.new do
63
+ yield self
64
+ :done
65
+ end
66
+ self.status = :engaged
67
+ dispatch fiber.resume
68
+ end
69
+
70
+ # Called from within an executed block of code for the
71
+ # session to send request data to the POSLynx and return
72
+ # the response to the request once it is received.
73
+ # If a connection could not be established or is lost
74
+ # before a response can be received, then a
75
+ # <tt>RequestError</tt> exception will be raised.
76
+ #
77
+ # If another session attempts to supplant this request, but
78
+ # the response to this request is subsequently received,
79
+ # then the response is returned as normal, but this
80
+ # session's status is changed to detached, and any
81
+ # subsequent request attempts made in this session will
82
+ # result in <tt>RequestError</tt> being raised.
83
+ #
84
+ # If another session is already waiting for a response,
85
+ # then this will attempt to usurp or supplant the other
86
+ # request. If the new request is of the same type as the
87
+ # existing request, and the type is not PinPadReset, then
88
+ # this call will raise a <tt>ConflictingRequestError</tt>
89
+ # exception.
90
+ def request(data)
91
+ if status == :detached
92
+ raise RequestError, "Session cannot make requests because it is detached"
93
+ end
94
+ if connector.request_pending?
95
+ pending_request_data = connector.latest_request.request_data
96
+ pending_callbacks = connector.latest_request.result_callbacks
97
+ if Data::Requests::PinPadReset === data && Data::Requests::PinPadReset === pending_request_data
98
+ pending_callbacks.call :on_failure
99
+ was_successful, resp_data_or_ex = Fiber.yield( [:_get_response] )
100
+ elsif data.class == pending_request_data.class
101
+ raise ConflictingRequestError, "Attempted a request while another request of the same type is in progress"
102
+ else
103
+ was_successful, resp_data_or_ex = Fiber.yield( [:_request, data, connector.latest_request] )
104
+ end
105
+ else
106
+ was_successful, resp_data_or_ex = Fiber.yield( [:_request, data] )
107
+ end
108
+ raise resp_data_or_ex unless was_successful
109
+ resp_data_or_ex
110
+ end
111
+
112
+ # Given a block argument, returns control to EventMachine,
113
+ # executes the block in a separate thread, waits for the
114
+ # thread to complete, and then returns the value returned
115
+ # by the block.
116
+ #
117
+ # This is implemented behind the scenes using
118
+ # EventMachine::defer and has the same considerations and
119
+ # caveats.
120
+ #
121
+ # Note that methods of the session should not be called by
122
+ # code within the block since those methods should only be
123
+ # called from code running in the main event loop thread.
124
+ def exec_dissociated(&block)
125
+ was_successful, resp_data_or_ex = Fiber.yield( [:_exec_dissociated, block] )
126
+ raise resp_data_or_ex unless was_successful
127
+ resp_data_or_ex
128
+ end
129
+
130
+ # Returns control to EventMachine, and returns control to
131
+ # the session code after the given delay-time in seconds.
132
+ def sleep(delay_time)
133
+ Fiber.yield [:_sleep, delay_time]
134
+ end
135
+
136
+ private
137
+
138
+ attr_writer :connector, :status
139
+ attr_reader :fiber
140
+
141
+ def dispatch(fiber_callback)
142
+ return if fiber_callback == :done
143
+ send *fiber_callback
144
+ end
145
+
146
+ def _request(data, overlaps_request=nil)
147
+ connector.connect(
148
+ on_success: ->() {
149
+ send_request_callbacks = response_handlers( overlaps_request )
150
+ connector.send_request data, send_request_callbacks
151
+ },
152
+ on_failure: ->() {
153
+ dispatch fiber.resume( [false, RequestError.new] )
154
+ }
155
+ )
156
+ rescue => ex
157
+ dispatch fiber.resume( [false, ex] )
158
+ end
159
+
160
+ def _get_response
161
+ connector.get_response response_handlers
162
+ end
163
+
164
+ def response_handlers(overlaps_request=nil)
165
+ overlapped_request_data = overlaps_request && overlaps_request.request_data
166
+ overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
167
+ {
168
+ on_response: on_response_handler( overlaps_request ),
169
+ on_failure: on_failure_handler( overlaps_request ),
170
+ on_detached: ->(){ detach! },
171
+ }
172
+ end
173
+
174
+ def on_response_handler(overlaps_request)
175
+ overlaps_request ?
176
+ on_response_handler_with_overlap( overlaps_request ) :
177
+ simple_on_response_handler
178
+ end
179
+
180
+ def on_response_handler_with_overlap(overlaps_request)
181
+ overlapped_request_data = overlaps_request && overlaps_request.request_data
182
+ overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
183
+ ->(response_data) {
184
+ if overlapped_request_data.potential_response?( response_data )
185
+ overlapped_request_callbacks.call :on_detached
186
+ overlapped_request_callbacks.call :on_response, response_data
187
+ _get_response
188
+ else
189
+ overlapped_request_callbacks.call :on_failure
190
+ dispatch fiber.resume( [true, response_data] )
191
+ end
192
+ }
193
+ end
194
+
195
+ def simple_on_response_handler
196
+ ->(response_data) {
197
+ dispatch fiber.resume( [true, response_data] )
198
+ }
199
+ end
200
+
201
+ def on_failure_handler(overlaps_request)
202
+ overlaps_request ?
203
+ on_failure_handler_with_overlap( overlaps_request ) :
204
+ simple_on_failure_handler
205
+ end
206
+
207
+ def on_failure_handler_with_overlap(overlaps_request)
208
+ overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
209
+ ->() {
210
+ overlapped_request_callbacks.call :on_failure if overlaps_request
211
+ dispatch fiber.resume( [false, RequestError.new] )
212
+ }
213
+ end
214
+
215
+ def simple_on_failure_handler
216
+ ->() {
217
+ dispatch fiber.resume( [false, RequestError.new] )
218
+ }
219
+ end
220
+
221
+ def detach!
222
+ self.connector = nil
223
+ self.status = :detached
224
+ end
225
+
226
+ def _exec_dissociated op
227
+ wrapped_op = ->() {
228
+ begin
229
+ result = op.call
230
+ [true, result]
231
+ rescue => ex
232
+ [false, ex]
233
+ end
234
+ }
235
+ callback = ->(result) do
236
+ dispatch fiber.resume result
237
+ end
238
+ em_system.defer wrapped_op, callback
239
+ end
240
+
241
+ def _sleep(delay_time)
242
+ callback = ->() {
243
+ dispatch fiber.resume
244
+ }
245
+ em_system.add_timer delay_time, callback
246
+ end
247
+ end
248
+
249
+ end
250
+ end