blockchyp 2.2.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.
- checksums.yaml +7 -0
- data/Makefile +71 -0
- data/README.md +997 -0
- data/Rakefile +39 -0
- data/lib/blockchyp.rb +190 -0
- data/lib/blockchyp/version.rb +5 -0
- data/lib/blockchyp_client.rb +346 -0
- data/lib/crypto_utils.rb +25 -0
- data/test/boolean_prompt_test.rb +43 -0
- data/test/capture_signature_test.rb +40 -0
- data/test/gateway_timeout_test.rb +40 -0
- data/test/heartbeat_test.rb +27 -0
- data/test/new_transaction_display_test.rb +78 -0
- data/test/pan_charge_test.rb +53 -0
- data/test/pan_enroll_test.rb +51 -0
- data/test/pan_preauth_test.rb +52 -0
- data/test/simple_batch_close_test.rb +48 -0
- data/test/simple_capture_test.rb +47 -0
- data/test/simple_gift_activate_test.rb +42 -0
- data/test/simple_message_test.rb +40 -0
- data/test/simple_ping_test.rb +39 -0
- data/test/simple_refund_test.rb +48 -0
- data/test/simple_reversal_test.rb +48 -0
- data/test/simple_void_test.rb +48 -0
- data/test/terminal_charge_test.rb +51 -0
- data/test/terminal_clear_test.rb +39 -0
- data/test/terminal_ebt_balance_test.rb +41 -0
- data/test/terminal_ebt_charge_test.rb +53 -0
- data/test/terminal_enroll_test.rb +50 -0
- data/test/terminal_gift_card_balance_test.rb +40 -0
- data/test/terminal_keyed_charge_test.rb +52 -0
- data/test/terminal_manual_ebt_charge_test.rb +54 -0
- data/test/terminal_preauth_test.rb +51 -0
- data/test/terminal_status_test.rb +39 -0
- data/test/terminal_timeout_test.rb +39 -0
- data/test/terms_and_conditions_test.rb +44 -0
- data/test/test_helper.rb +65 -0
- data/test/text_prompt_test.rb +41 -0
- data/test/update_transaction_display_test.rb +78 -0
- metadata +81 -0
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
warn e.message
|
9
|
+
warn 'Run `bundle install` to install missing gems'
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'rake'
|
14
|
+
require 'rake/testtask'
|
15
|
+
|
16
|
+
task(default: %i[test lint])
|
17
|
+
|
18
|
+
Rake::TestTask.new(:test) do |t|
|
19
|
+
t.libs << '.' << 'lib' << 'test'
|
20
|
+
t.test_files = FileList['test/*_test.rb']
|
21
|
+
t.verbose = false
|
22
|
+
end
|
23
|
+
|
24
|
+
task :lint do
|
25
|
+
if RUBY_ENGINE == 'ruby'
|
26
|
+
require 'rubocop/rake_task'
|
27
|
+
RuboCop::RakeTask.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
task(gem: :build)
|
32
|
+
task :build do
|
33
|
+
system 'gem build blockchyp.gemspec'
|
34
|
+
end
|
35
|
+
|
36
|
+
task publish: :build do
|
37
|
+
system "gem push blockchyp-#{BlockChyp::VERSION}.gem"
|
38
|
+
system "rm blockchyp-#{BlockChyp::VERSION}.gem"
|
39
|
+
end
|
data/lib/blockchyp.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019 BlockChyp, Inc. All rights reserved. Use of this code is
|
4
|
+
# governed by a license that can be found in the LICENSE file.
|
5
|
+
#
|
6
|
+
# This file was generated automatically. Changes to this file will be lost
|
7
|
+
# every time the code is regenerated.
|
8
|
+
|
9
|
+
require_relative 'blockchyp_client'
|
10
|
+
|
11
|
+
module CardType
|
12
|
+
CREDIT = 0
|
13
|
+
DEBIT = 1
|
14
|
+
EBT = 2
|
15
|
+
BLOCKCHAIN_GIFT = 3
|
16
|
+
end
|
17
|
+
|
18
|
+
module SignatureFormat
|
19
|
+
NONE = ''
|
20
|
+
PNG = 'png'
|
21
|
+
JPG = 'jpg'
|
22
|
+
GIF = 'gif'
|
23
|
+
end
|
24
|
+
|
25
|
+
module PromptType
|
26
|
+
AMOUNT = 'amount'
|
27
|
+
EMAIL = 'email'
|
28
|
+
PHONE_NUMBER = 'phone'
|
29
|
+
CUSTOMER_NUMBER = 'customer-number'
|
30
|
+
REWARDS_NUMBER = 'rewards-number'
|
31
|
+
FIRST_NAME = 'first-name'
|
32
|
+
LAST_NAME = 'last-name'
|
33
|
+
end
|
34
|
+
|
35
|
+
module AVSResponse
|
36
|
+
NOT_APPLICABLE = ''
|
37
|
+
NOT_SUPPORTED = 'not_supported'
|
38
|
+
RETRY = 'retry'
|
39
|
+
NO_MATCH = 'no_match'
|
40
|
+
ADDRESS_MATCH = 'address_match'
|
41
|
+
POSTAL_CODE_MATCH = 'zip_match'
|
42
|
+
ADDRESS_AND_POSTAL_CODE_MATCH = 'match'
|
43
|
+
end
|
44
|
+
|
45
|
+
module BlockChyp
|
46
|
+
# the main autogenerated blockchyp client
|
47
|
+
class BlockChyp < BlockChypClient
|
48
|
+
|
49
|
+
def heartbeat(test)
|
50
|
+
gateway_request('GET', '/api/heartbeat', {'test' => test})
|
51
|
+
end
|
52
|
+
|
53
|
+
# Executes a standard direct preauth and capture.
|
54
|
+
def charge(request)
|
55
|
+
route_terminal_request('POST', '/api/charge', '/api/charge', request)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Executes a preauthorization intended to be captured later.
|
59
|
+
def preauth(request)
|
60
|
+
route_terminal_request('POST', '/api/preauth', '/api/preauth', request)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Tests connectivity with a payment terminal.
|
64
|
+
def ping(request)
|
65
|
+
route_terminal_request('POST', '/api/test', '/api/terminal-test', request)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Checks the remaining balance on a payment method.
|
69
|
+
def balance(request)
|
70
|
+
route_terminal_request('POST', '/api/balance', '/api/balance', request)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Clears the line item display and any in progress transaction.
|
74
|
+
def clear(request)
|
75
|
+
route_terminal_request('POST', '/api/clear', '/api/terminal-clear', request)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Prompts the user to accept terms and conditions.
|
79
|
+
def terms_and_conditions(request)
|
80
|
+
route_terminal_request('POST', '/api/tc', '/api/terminal-tc', request)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Appends items to an existing transaction display. Subtotal, Tax, and
|
84
|
+
# Total are overwritten by the request. Items with the same description
|
85
|
+
# are combined into groups.
|
86
|
+
def update_transaction_display(request)
|
87
|
+
route_terminal_request('PUT', '/api/txdisplay', '/api/terminal-txdisplay', request)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Displays a new transaction on the terminal.
|
91
|
+
def new_transaction_display(request)
|
92
|
+
route_terminal_request('POST', '/api/txdisplay', '/api/terminal-txdisplay', request)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Asks the consumer a text based question.
|
96
|
+
def text_prompt(request)
|
97
|
+
route_terminal_request('POST', '/api/text-prompt', '/api/text-prompt', request)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Asks the consumer a yes/no question.
|
101
|
+
def boolean_prompt(request)
|
102
|
+
route_terminal_request('POST', '/api/boolean-prompt', '/api/boolean-prompt', request)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Displays a short message on the terminal.
|
106
|
+
def message(request)
|
107
|
+
route_terminal_request('POST', '/api/message', '/api/message', request)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Executes a refund.
|
111
|
+
def refund(request)
|
112
|
+
route_terminal_request('POST', '/api/refund', '/api/refund', request)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Adds a new payment method to the token vault.
|
116
|
+
def enroll(request)
|
117
|
+
route_terminal_request('POST', '/api/enroll', '/api/enroll', request)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Activates or recharges a gift card.
|
121
|
+
def gift_activate(request)
|
122
|
+
route_terminal_request('POST', '/api/gift-activate', '/api/gift-activate', request)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the current status of a terminal.
|
126
|
+
def terminal_status(request)
|
127
|
+
route_terminal_request('POST', '/api/terminal-status', '/api/terminal-status', request)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Captures and returns a signature.
|
131
|
+
def capture_signature(request)
|
132
|
+
route_terminal_request('POST', '/api/capture-signature', '/api/capture-signature', request)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Executes a manual time out reversal.
|
136
|
+
#
|
137
|
+
# We love time out reversals. Don't be afraid to use them whenever a
|
138
|
+
# request to a BlockChyp terminal times out. You have up to two minutes to
|
139
|
+
# reverse any transaction. The only caveat is that you must assign
|
140
|
+
# transactionRef values when you build the original request. Otherwise, we
|
141
|
+
# have no real way of knowing which transaction you're trying to reverse
|
142
|
+
# because we may not have assigned it an id yet. And if we did assign it
|
143
|
+
# an id, you wouldn't know what it is because your request to the terminal
|
144
|
+
# timed out before you got a response.
|
145
|
+
def reverse(request)
|
146
|
+
gateway_request('POST', '/api/reverse', request)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Captures a preauthorization.
|
150
|
+
def capture(request)
|
151
|
+
gateway_request('POST', '/api/capture', request)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Closes the current credit card batch.
|
155
|
+
def close_batch(request)
|
156
|
+
gateway_request('POST', '/api/close-batch', request)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Discards a previous preauth transaction.
|
160
|
+
def void(request)
|
161
|
+
gateway_request('POST', '/api/void', request)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Updates or creates a customer record.
|
165
|
+
def update_customer(request)
|
166
|
+
gateway_request('POST', '/api/update-customer', request)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Retrieves a customer by id.
|
170
|
+
def customer(request)
|
171
|
+
gateway_request('POST', '/api/customer', request)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Searches the customer database.
|
175
|
+
def customer_search(request)
|
176
|
+
gateway_request('POST', '/api/customer-search', request)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Retrieves the current status of a transaction.
|
180
|
+
def transaction_status(request)
|
181
|
+
gateway_request('POST', '/api/tx-status', request)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Creates and send a payment link to a customer.
|
185
|
+
def send_payment_link(request)
|
186
|
+
gateway_request('POST', '/api/send-payment-link', request)
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,346 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'cgi'
|
5
|
+
require 'digest'
|
6
|
+
require 'json'
|
7
|
+
require 'net/http'
|
8
|
+
require 'openssl'
|
9
|
+
require 'time'
|
10
|
+
require 'tmpdir'
|
11
|
+
require 'uri'
|
12
|
+
|
13
|
+
require_relative 'crypto_utils'
|
14
|
+
|
15
|
+
module BlockChyp
|
16
|
+
# base class for the blockchyp generated blockchyp client
|
17
|
+
class BlockChypClient
|
18
|
+
def initialize(api_key, bearer_token, signing_key)
|
19
|
+
@api_key = api_key
|
20
|
+
@bearer_token = bearer_token
|
21
|
+
@signing_key = signing_key
|
22
|
+
@gateway_host = 'https://api.blockchyp.com'
|
23
|
+
@test_gateway_host = 'https://test.blockchyp.com'
|
24
|
+
@https = false
|
25
|
+
@route_cache_location = File.join(Dir.tmpdir, '.blockchyp_route')
|
26
|
+
@route_cache_ttl = 60
|
27
|
+
@gateway_timeout = 20
|
28
|
+
@terminal_timeout = 120
|
29
|
+
@terminal_connect_timeout = 5
|
30
|
+
@offline_cache_enabled = true
|
31
|
+
@route_cache = {}
|
32
|
+
@offline_fixed_key = 'cb22789c9d5c344a10e0474f134db39e25eb3bbf5a1b1a5e89b507f15ea9519c'
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :api_key
|
36
|
+
attr_reader :bearer_token
|
37
|
+
attr_reader :signing_key
|
38
|
+
attr_reader :offline_fixed_key
|
39
|
+
attr_accessor :gateway_host
|
40
|
+
attr_accessor :test_gateway_host
|
41
|
+
attr_accessor :https
|
42
|
+
attr_accessor :route_cache_ttl
|
43
|
+
attr_accessor :gateway_timeout
|
44
|
+
attr_accessor :terminal_timeout
|
45
|
+
attr_accessor :offline_cache_enabled
|
46
|
+
attr_accessor :terminal_connect_timeout
|
47
|
+
attr_accessor :route_cache_location
|
48
|
+
|
49
|
+
def generate_gateway_headers
|
50
|
+
nonce = CryptoUtils.generate_nonce
|
51
|
+
tsp = CryptoUtils.timestamp
|
52
|
+
|
53
|
+
sig = compute_hmac(tsp, nonce)
|
54
|
+
|
55
|
+
{
|
56
|
+
'Nonce' => nonce,
|
57
|
+
'Timestamp' => tsp,
|
58
|
+
'Authorization' => 'Dual ' + bearer_token + ':' + api_key + ':' + sig
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def compute_hmac(tsp, nonce)
|
63
|
+
canonical_string = api_key + bearer_token + tsp + nonce
|
64
|
+
|
65
|
+
OpenSSL::HMAC.hexdigest('SHA256', CryptoUtils.hex2bin(signing_key), canonical_string)
|
66
|
+
end
|
67
|
+
|
68
|
+
def resolve_gateway_uri(path, request)
|
69
|
+
url = if request.nil? || !request['test']
|
70
|
+
gateway_host
|
71
|
+
else
|
72
|
+
test_gateway_host
|
73
|
+
end
|
74
|
+
|
75
|
+
URI.parse(url + path)
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_error_response(msg)
|
79
|
+
[
|
80
|
+
'success' => false,
|
81
|
+
'error' => msg,
|
82
|
+
'responseDescription' => msg
|
83
|
+
]
|
84
|
+
end
|
85
|
+
|
86
|
+
def route_terminal_request(method, terminal_path, gateway_path, request)
|
87
|
+
if request['terminalName'].nil?
|
88
|
+
return gateway_request(method, gateway_path, request)
|
89
|
+
end
|
90
|
+
|
91
|
+
route = resolve_terminal_route(request['terminalName'])
|
92
|
+
if !route
|
93
|
+
return generate_error_response('Unkown Terminal')
|
94
|
+
elsif route['cloudRelayEnabled']
|
95
|
+
return gateway_request(method, gateway_path, request, relay: true)
|
96
|
+
end
|
97
|
+
|
98
|
+
terminal_request(method, route, terminal_path, request, true)
|
99
|
+
end
|
100
|
+
|
101
|
+
def terminal_request(method, route, path, request, open_retry)
|
102
|
+
uri = resolve_terminal_uri(route, path)
|
103
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
104
|
+
http.use_ssl = uri.instance_of?(URI::HTTPS)
|
105
|
+
timeout = get_timeout(request, terminal_timeout)
|
106
|
+
http.open_timeout = timeout
|
107
|
+
http.read_timeout = timeout
|
108
|
+
|
109
|
+
tx_creds = route['transientCredentials']
|
110
|
+
|
111
|
+
wrapped_request = {
|
112
|
+
'apiKey' => tx_creds['apiKey'],
|
113
|
+
'bearerToken' => tx_creds['bearerToken'],
|
114
|
+
'signingKey' => tx_creds['signingKey'],
|
115
|
+
'request' => request
|
116
|
+
}
|
117
|
+
|
118
|
+
req = get_http_request(method, uri)
|
119
|
+
|
120
|
+
req['User-Agent'] = user_agent
|
121
|
+
json = wrapped_request.to_json
|
122
|
+
req['Content-Type'] = 'application/json'
|
123
|
+
req['Content-Length'] = json.length
|
124
|
+
req.body = json
|
125
|
+
|
126
|
+
begin
|
127
|
+
response = http.request(req)
|
128
|
+
rescue Net::OpenTimeout, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ENETUNREACH
|
129
|
+
if open_retry
|
130
|
+
evict(route['terminalName'])
|
131
|
+
route = resolve_terminal_route(route['terminalName'])
|
132
|
+
return terminal_request(method, route, path, request, false)
|
133
|
+
end
|
134
|
+
raise
|
135
|
+
end
|
136
|
+
if response.is_a?(Net::HTTPSuccess)
|
137
|
+
JSON.parse(response.body)
|
138
|
+
else
|
139
|
+
raise response.message
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_http_request(method, uri)
|
144
|
+
case method
|
145
|
+
when 'GET'
|
146
|
+
Net::HTTP::Get.new(uri.request_uri)
|
147
|
+
when 'PUT'
|
148
|
+
Net::HTTP::Put.new(uri.request_uri)
|
149
|
+
when 'POST'
|
150
|
+
Net::HTTP::Post.new(uri.request_uri)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def resolve_terminal_uri(route, path)
|
155
|
+
url = if https
|
156
|
+
'https://'
|
157
|
+
else
|
158
|
+
'http://'
|
159
|
+
end
|
160
|
+
url += route['ipAddress']
|
161
|
+
port = if https
|
162
|
+
':8443'
|
163
|
+
else
|
164
|
+
':8080'
|
165
|
+
end
|
166
|
+
URI.parse(url + port + path)
|
167
|
+
end
|
168
|
+
|
169
|
+
def gateway_request(method, path, request = nil, relay = false)
|
170
|
+
uri = resolve_gateway_uri(path, request)
|
171
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
172
|
+
http.use_ssl = uri.instance_of?(URI::HTTPS)
|
173
|
+
timeout = get_timeout(request, relay ? terminal_timeout : gateway_timeout)
|
174
|
+
http.open_timeout = timeout
|
175
|
+
http.read_timeout = timeout
|
176
|
+
|
177
|
+
req = get_http_request(method, uri)
|
178
|
+
|
179
|
+
req['User-Agent'] = user_agent
|
180
|
+
unless request.nil?
|
181
|
+
json = request.to_json
|
182
|
+
req['Content-Type'] = 'application/json'
|
183
|
+
req['Content-Length'] = json.length
|
184
|
+
req.body = json
|
185
|
+
end
|
186
|
+
|
187
|
+
headers = generate_gateway_headers
|
188
|
+
headers.each do |key, value|
|
189
|
+
req[key] = value
|
190
|
+
end
|
191
|
+
|
192
|
+
response = http.request(req)
|
193
|
+
|
194
|
+
if response.is_a?(Net::HTTPSuccess)
|
195
|
+
JSON.parse(response.body)
|
196
|
+
else
|
197
|
+
raise response.message
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
attr_accessor :routeCache
|
202
|
+
|
203
|
+
def resolve_terminal_route(terminal_name)
|
204
|
+
route = route_cache_get(terminal_name, false)
|
205
|
+
|
206
|
+
if route.nil?
|
207
|
+
route = request_route_from_gateway(terminal_name)
|
208
|
+
if route.nil?
|
209
|
+
return route
|
210
|
+
end
|
211
|
+
|
212
|
+
ttl = Time.now.utc + (route_cache_ttl * 60)
|
213
|
+
route_cache_entry = {}
|
214
|
+
route_cache_entry['route'] = route
|
215
|
+
route_cache_entry['ttl'] = ttl
|
216
|
+
@route_cache[api_key + terminal_name] = route_cache_entry
|
217
|
+
update_offline_cache(route_cache_entry)
|
218
|
+
end
|
219
|
+
route
|
220
|
+
end
|
221
|
+
|
222
|
+
def update_offline_cache(route_cache_entry)
|
223
|
+
if offline_cache_enabled
|
224
|
+
offline_cache = read_offline_cache
|
225
|
+
offline_entry = route_cache_entry.clone
|
226
|
+
route = route_cache_entry['route'].clone
|
227
|
+
tx_creds = route['transientCredentials'].clone
|
228
|
+
tx_creds['apiKey'] = encrypt(tx_creds['apiKey'])
|
229
|
+
tx_creds['bearerToken'] = encrypt(tx_creds['bearerToken'])
|
230
|
+
tx_creds['signingKey'] = encrypt(tx_creds['signingKey'])
|
231
|
+
route['transientCredentials'] = tx_creds
|
232
|
+
offline_entry['route'] = route
|
233
|
+
offline_cache[api_key + route['terminalName']] = offline_entry
|
234
|
+
File.write(route_cache_location, offline_cache.to_json)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def encrypt(plain_text)
|
239
|
+
return plain_text if plain_text.nil? || plain_text.empty?
|
240
|
+
|
241
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
242
|
+
cipher.encrypt
|
243
|
+
cipher.key = derive_offline_key
|
244
|
+
iv = cipher.random_iv
|
245
|
+
|
246
|
+
Base64.encode64(iv) + ':' + Base64.encode64(cipher.update(plain_text) + cipher.final)
|
247
|
+
end
|
248
|
+
|
249
|
+
def decrypt(cipher_text)
|
250
|
+
return cipher_text if cipher_text.nil? || cipher_text.empty?
|
251
|
+
|
252
|
+
tokens = cipher_text.split(':')
|
253
|
+
|
254
|
+
return cipher_text if tokens[0].nil? || tokens[1].nil?
|
255
|
+
|
256
|
+
iv = Base64.decode64(tokens[0])
|
257
|
+
cp = Base64.decode64(tokens[1])
|
258
|
+
|
259
|
+
decipher = OpenSSL::Cipher::AES256.new(:CBC)
|
260
|
+
decipher.decrypt
|
261
|
+
decipher.key = derive_offline_key
|
262
|
+
decipher.iv = iv
|
263
|
+
|
264
|
+
decipher.update(cp) + decipher.final
|
265
|
+
end
|
266
|
+
|
267
|
+
def derive_offline_key
|
268
|
+
Digest::SHA256.digest offline_fixed_key + signing_key
|
269
|
+
end
|
270
|
+
|
271
|
+
def request_route_from_gateway(terminal_name)
|
272
|
+
route = gateway_request('GET', '/api/terminal-route?terminal=' + CGI.escape(terminal_name))
|
273
|
+
if !route.nil? && !route['ipAddress'].empty?
|
274
|
+
route['exists'] = true
|
275
|
+
end
|
276
|
+
route
|
277
|
+
end
|
278
|
+
|
279
|
+
def evict(terminal_name)
|
280
|
+
route_cache.delete(api_key + terminal_name)
|
281
|
+
|
282
|
+
offline_cache = read_offline_cache
|
283
|
+
offline_cache.delete(api_key + terminal_name)
|
284
|
+
File.write(route_cache_location, route_cache.to_json)
|
285
|
+
end
|
286
|
+
|
287
|
+
def route_cache_get(terminal_name, stale)
|
288
|
+
route_cache_entry = @route_cache[api_key + terminal_name]
|
289
|
+
|
290
|
+
if route_cache_entry.nil? && offline_cache_enabled
|
291
|
+
offline_cache = read_offline_cache
|
292
|
+
route_cache_entry = offline_cache[@api_key + terminal_name]
|
293
|
+
end
|
294
|
+
|
295
|
+
if route_cache_entry
|
296
|
+
route = route_cache_entry['route']
|
297
|
+
tx_creds = route['transientCredentials']
|
298
|
+
tx_creds['apiKey'] = decrypt(tx_creds['apiKey'])
|
299
|
+
tx_creds['bearerToken'] = decrypt(tx_creds['bearerToken'])
|
300
|
+
tx_creds['signingKey'] = decrypt(tx_creds['signingKey'])
|
301
|
+
route['transientCredentials'] = tx_creds
|
302
|
+
route_cache_entry['route'] = route
|
303
|
+
end
|
304
|
+
|
305
|
+
if route_cache_entry
|
306
|
+
now = Time.new
|
307
|
+
raw_ttl = route_cache_entry['ttl']
|
308
|
+
if raw_ttl.instance_of?(Time)
|
309
|
+
ttl = raw_ttl
|
310
|
+
else
|
311
|
+
ttl = Time.parse(route_cache_entry['ttl'])
|
312
|
+
end
|
313
|
+
if stale || now < ttl
|
314
|
+
route_cache_entry['route']
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def read_offline_cache
|
320
|
+
if File.file?(route_cache_location)
|
321
|
+
|
322
|
+
config_file = File.open(route_cache_location)
|
323
|
+
content = config_file.read
|
324
|
+
|
325
|
+
return JSON.parse(content)
|
326
|
+
end
|
327
|
+
{}
|
328
|
+
end
|
329
|
+
|
330
|
+
def user_agent
|
331
|
+
if defined? VERSION
|
332
|
+
"BlockChyp-Ruby/#{VERSION}"
|
333
|
+
else
|
334
|
+
"BlockChyp-Ruby"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def get_timeout(request, default)
|
339
|
+
if request.nil? || request['timeout'].nil? || request['timeout'].zero?
|
340
|
+
return default
|
341
|
+
end
|
342
|
+
|
343
|
+
request['timeout']
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|