-
Notifications
You must be signed in to change notification settings - Fork 1
/
merchant.rb
157 lines (126 loc) · 4.17 KB
/
merchant.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
require 'roda'
require 'cgi'
require 'json'
require 'uri'
require 'net/http'
require 'openssl'
require 'pedicel'
def new_ssl_http_worker(uri, cert, key)
worker = Net::HTTP.new(uri.host, uri.port)
worker.use_ssl = true
worker.ssl_timeout = 3
worker.read_timeout = 3
worker.open_timeout = 3
worker.cert = cert
worker.key = key
worker.ssl_version = :TLSv1_2
worker.verify_mode = OpenSSL::SSL::VERIFY_PEER
worker
end
# Implementation of
# https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session
def do_validation(uri, cert, key, cfg)
req = Net::HTTP::Post.new(uri)
data = JSON.generate(
"merchantIdentifier": cfg['merchantIdentifier'],
"initiative": 'web',
"initiativeContext": cfg['initiativeContext'],
"displayName": cfg['displayName']
)
worker = new_ssl_http_worker(uri, cert, key)
worker.request(req, data)
end
def load_merchant_identity_certificates
certfile = File.read('merchant_identity.pem')
cert = OpenSSL::X509::Certificate.new(certfile)
keyfh = File.open('merchant_identity.key')
key = OpenSSL::PKey.read(keyfh)
[cert, key]
end
def load_payment_processing_key
File.read('payment_processing.key')
end
def read_configuration(p)
begin
contents = File.read(p)
rescue
raise "Configuration file #{p} must exist in project root"
end
begin
cfg = JSON.parse(contents)
rescue
raise 'Config must be parse as JSON'
end
required_keys = %w[merchantIdentifier initiativeContext displayName]
unless required_keys.map { |x| cfg.key?(x) }.all?
raise 'Configuration missing keys'
end
cfg
end
def find_symmetric_key(token, cert)
pedicel = Pedicel::EC.new(token)
pp_key = load_payment_processing_key
pedicel.symmetric_key(private_key: pp_key, certificate: cert.to_pem).unpack('H*').first
end
# ApplePayMerchant is the handler for requests to applepaymerchant.clrhs.dk
# Started by puma.
class ApplePayMerchant < Roda
plugin :halt
plugin :json_parser
# Avoid caching of served files.
# Headers borrowed from https://stackoverflow.com/a/2068407
plugin :public, headers: {
'Cache-Control' => 'no-cache, no-store, must-revalidate',
'Pragma' => 'no-cache',
'Expires' => '0'
}
route do |r|
@cert, @key = load_merchant_identity_certificates
r.root do
r.redirect '/index.html'
end
# Print the token to standard error.
r.post 'completesession' do
body = r.body.read
json_body = JSON.parse(body)
payment_data = json_body['token']['paymentData']
symmetric_key = find_symmetric_key(payment_data, @cert)
puts('Payment Token:', JSON.unparse(payment_data))
puts('Symmetric key:', symmetric_key)
r.halt(200)
end
# Implement
# https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session
r.post 'validatemerchant' do
@config = read_configuration('config.json')
# Allowed validationURL's, reference Listing 1 in
# https://developer.apple.com/documentation/apple_pay_on_the_web/setting_up_your_server
@url_pattern = /\A(cn-)?apple-pay-gateway[-a-z0-9]*\.apple\.com\z/
validation_url = r.POST['validationURL']
validation_url = CGI.unescape(validation_url) if validation_url
v_uri = URI.parse(validation_url)
if @url_pattern.match(v_uri.host).nil?
r.halt(400, "Invalid validation_url #{validation_url}")
end
begin
applepaysession = do_validation(v_uri, @cert, @key, @config)
unless applepaysession.is_a?(Net::HTTPSuccess)
r.halt(applepaysession.code.to_i, 'Apple request failure')
end
response.status = 200
response['Content-Type'] = 'application/json'
response.write(applepaysession.body)
rescue OpenSSL::SSL::SSLError => e
puts("Error connecting to validation url '#{validation_url}':", e.message)
r.halt(504, 'Error connecting to validation_url')
rescue => e
puts("Validation error: #{e.message}")
r.halt(500, 'Validation Error')
end
end
# Serve 'public/' folder as static files
r.on do
r.public
end
end
end