alipay-easysdk-ruby 1.0.0
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/README.md +213 -0
- data/Rakefile +4 -0
- data/examples/demo.rb +134 -0
- data/examples/demo_with_post.rb +239 -0
- data/lib/alipay/easysdk/kernel/alipay_constants.rb +46 -0
- data/lib/alipay/easysdk/kernel/config.rb +38 -0
- data/lib/alipay/easysdk/kernel/easy_sdk_kernel.rb +328 -0
- data/lib/alipay/easysdk/kernel/factory.rb +95 -0
- data/lib/alipay/easysdk/kernel/util/json_util.rb +30 -0
- data/lib/alipay/easysdk/kernel/util/response_checker.rb +28 -0
- data/lib/alipay/easysdk/kernel/util/sign_content_extractor.rb +50 -0
- data/lib/alipay/easysdk/kernel/util/signer.rb +125 -0
- data/lib/alipay/easysdk/payment/common/client.rb +190 -0
- data/lib/alipay/easysdk/payment/common/models/alipay_data_dataservice_bill_downloadurl_query_response.rb +13 -0
- data/lib/alipay/easysdk/payment/common/models/alipay_trade_cancel_response.rb +13 -0
- data/lib/alipay/easysdk/payment/common/models/alipay_trade_close_response.rb +13 -0
- data/lib/alipay/easysdk/payment/common/models/alipay_trade_create_response.rb +13 -0
- data/lib/alipay/easysdk/payment/common/models/alipay_trade_fastpay_refund_query_response.rb +13 -0
- data/lib/alipay/easysdk/payment/common/models/alipay_trade_query_response.rb +13 -0
- data/lib/alipay/easysdk/payment/common/models/alipay_trade_refund_response.rb +13 -0
- data/lib/alipay/easysdk/payment/common/models/base_response.rb +42 -0
- data/lib/alipay/easysdk/payment/page/client.rb +80 -0
- data/lib/alipay/easysdk/payment/page/models/alipay_trade_page_pay_response.rb +35 -0
- data/lib/alipay/easysdk/payment/wap/client.rb +88 -0
- data/lib/alipay/easysdk/payment/wap/models/alipay_trade_wap_pay_response.rb +35 -0
- data/lib/alipay/easysdk/version.rb +5 -0
- data/lib/alipay/easysdk.rb +33 -0
- data/sig/alipay/easysdk/ruby.rbs +8 -0
- metadata +130 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
require_relative 'alipay_constants'
|
|
2
|
+
require_relative 'config'
|
|
3
|
+
require_relative 'util/json_util'
|
|
4
|
+
require_relative 'util/signer'
|
|
5
|
+
require_relative 'util/sign_content_extractor'
|
|
6
|
+
require 'net/http'
|
|
7
|
+
require 'uri'
|
|
8
|
+
require 'cgi'
|
|
9
|
+
|
|
10
|
+
module Alipay
|
|
11
|
+
module EasySDK
|
|
12
|
+
module Kernel
|
|
13
|
+
class EasySDKKernel
|
|
14
|
+
attr_reader :config, :optional_text_params, :optional_biz_params, :text_params, :biz_params
|
|
15
|
+
|
|
16
|
+
def initialize(config)
|
|
17
|
+
@config = config
|
|
18
|
+
@optional_text_params = {}
|
|
19
|
+
@optional_biz_params = {}
|
|
20
|
+
@text_params = {}
|
|
21
|
+
@biz_params = {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def inject_text_param(key, value)
|
|
25
|
+
if key != nil
|
|
26
|
+
@optional_text_params[key] = value
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def inject_biz_param(key, value)
|
|
31
|
+
if key != nil
|
|
32
|
+
@optional_biz_params[key] = value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def get_timestamp
|
|
37
|
+
return Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_config(key)
|
|
41
|
+
case key
|
|
42
|
+
when 'protocol'
|
|
43
|
+
@config.protocol
|
|
44
|
+
when 'gatewayHost'
|
|
45
|
+
@config.gateway_host
|
|
46
|
+
when 'appId'
|
|
47
|
+
@config.app_id
|
|
48
|
+
when 'merchantPrivateKey'
|
|
49
|
+
@config.merchant_private_key
|
|
50
|
+
when 'alipayPublicKey'
|
|
51
|
+
@config.alipay_public_key
|
|
52
|
+
when 'signType'
|
|
53
|
+
@config.sign_type
|
|
54
|
+
when 'charset'
|
|
55
|
+
@config.charset
|
|
56
|
+
when 'format'
|
|
57
|
+
@config.format
|
|
58
|
+
when 'version'
|
|
59
|
+
@config.version
|
|
60
|
+
else
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get_sdk_version
|
|
66
|
+
AlipayConstants::SDK_VERSION
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_url_encoded_request_body(biz_params)
|
|
70
|
+
sorted_map = get_sorted_map(nil, biz_params, nil)
|
|
71
|
+
if sorted_map.nil? || sorted_map.empty?
|
|
72
|
+
return nil
|
|
73
|
+
end
|
|
74
|
+
build_query_string(sorted_map)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def read_as_json(response, method)
|
|
78
|
+
response_body = response.body
|
|
79
|
+
map = {}
|
|
80
|
+
map['body'] = response_body
|
|
81
|
+
map['method'] = method
|
|
82
|
+
return map
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def get_random_boundary
|
|
86
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S") + ''
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def generate_page(method, system_params, biz_params, text_params, sign)
|
|
90
|
+
puts "[DEBUG] generate_page - sign长度: #{sign.length}" if ENV['DEBUG']
|
|
91
|
+
if method == 'GET'
|
|
92
|
+
# 采集并排序所有参数
|
|
93
|
+
sorted_map = get_sorted_map(system_params, biz_params, text_params)
|
|
94
|
+
sorted_map[AlipayConstants::SIGN_FIELD] = sign
|
|
95
|
+
return get_gateway_server_url + '?' + build_query_string(sorted_map)
|
|
96
|
+
elsif method == 'POST'
|
|
97
|
+
# 完全按照PHP版本的逻辑:采集并排序所有参数,不覆盖已注入的参数
|
|
98
|
+
sorted_map = get_sorted_map(system_params, biz_params, text_params)
|
|
99
|
+
sorted_map[AlipayConstants::SIGN_FIELD] = sign
|
|
100
|
+
puts "[DEBUG] generate_page POST - sorted_map中sign长度: #{sorted_map[AlipayConstants::SIGN_FIELD].length}" if ENV['DEBUG']
|
|
101
|
+
return build_form(get_gateway_server_url, sorted_map)
|
|
102
|
+
else
|
|
103
|
+
raise "不支持" + method
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def get_merchant_cert_sn
|
|
108
|
+
return @config.merchant_cert_sn
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def get_alipay_cert_sn(resp_map)
|
|
112
|
+
if !@config.merchant_cert_sn.nil? && !@config.merchant_cert_sn.empty?
|
|
113
|
+
body = JSON.parse(resp_map['body'])
|
|
114
|
+
alipay_cert_sn = body['alipay_cert_sn']
|
|
115
|
+
return alipay_cert_sn
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def get_alipay_root_cert_sn
|
|
120
|
+
return @config.alipay_root_cert_sn
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def is_cert_mode
|
|
124
|
+
return @config.merchant_cert_sn
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def extract_alipay_public_key(alipay_cert_sn)
|
|
128
|
+
# Ruby 版本只存储一个版本支付宝公钥
|
|
129
|
+
return @config.alipay_public_key
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def verify(resp_map, alipay_public_key)
|
|
133
|
+
resp = JSON.parse(resp_map['body'])
|
|
134
|
+
sign = resp[AlipayConstants::SIGN_FIELD]
|
|
135
|
+
sign_content_extractor = Alipay::EasySDK::Kernel::Util::SignContentExtractor.new
|
|
136
|
+
content = sign_content_extractor.get_sign_source_data(resp_map['body'], resp_map['method'])
|
|
137
|
+
signer = Alipay::EasySDK::Kernel::Util::Signer.new
|
|
138
|
+
return signer.verify(content, sign, alipay_public_key)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def sign(system_params, biz_params, text_params, private_key)
|
|
142
|
+
sorted_map = get_sorted_map(system_params, biz_params, text_params)
|
|
143
|
+
data = get_sign_content(sorted_map)
|
|
144
|
+
if ENV['DEBUG']
|
|
145
|
+
puts "[DEBUG] sign content: #{data}"
|
|
146
|
+
end
|
|
147
|
+
puts "[DEBUG] 签名内容: #{data[0, 100]}..." if ENV['DEBUG']
|
|
148
|
+
signer = Alipay::EasySDK::Kernel::Util::Signer.new
|
|
149
|
+
signature = signer.sign(data, private_key)
|
|
150
|
+
puts "[DEBUG] 生成签名长度: #{signature.length}" if ENV['DEBUG']
|
|
151
|
+
return signature
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def generate_order_string(system_params, biz_params, text_params, sign)
|
|
155
|
+
# 采集并排序所有参数
|
|
156
|
+
sorted_map = get_sorted_map(system_params, biz_params, text_params)
|
|
157
|
+
sorted_map[AlipayConstants::SIGN_FIELD] = sign
|
|
158
|
+
URI.encode_www_form(sorted_map)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def sort_map(random_map)
|
|
162
|
+
return random_map
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def to_resp_model(resp_map)
|
|
166
|
+
body = resp_map['body']
|
|
167
|
+
method_name = resp_map['method']
|
|
168
|
+
response_node_name = method_name.gsub(".", "_") + "_response"
|
|
169
|
+
|
|
170
|
+
model = JSON.parse(body)
|
|
171
|
+
if body.include?("error_response")
|
|
172
|
+
result = model["error_response"]
|
|
173
|
+
result['body'] = body
|
|
174
|
+
else
|
|
175
|
+
result = model[response_node_name]
|
|
176
|
+
result['body'] = body
|
|
177
|
+
end
|
|
178
|
+
return result
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def verify_params(parameters, public_key)
|
|
182
|
+
signer = Alipay::EasySDK::Kernel::Util::Signer.new
|
|
183
|
+
return signer.verify_params(parameters, public_key)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def concat_str(a, b)
|
|
187
|
+
return a + b
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def build_query_string(sorted_map)
|
|
193
|
+
request_url = nil
|
|
194
|
+
sorted_map.each do |sys_param_key, sys_param_value|
|
|
195
|
+
if request_url.nil?
|
|
196
|
+
request_url = "#{sys_param_key}=" + url_encode(characet(sys_param_value.to_s, AlipayConstants::DEFAULT_CHARSET)) + "&"
|
|
197
|
+
else
|
|
198
|
+
request_url += "#{sys_param_key}=" + url_encode(characet(sys_param_value.to_s, AlipayConstants::DEFAULT_CHARSET)) + "&"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
request_url = request_url[0...-1] unless request_url.nil?
|
|
202
|
+
return request_url
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def get_sorted_map(system_params, biz_params, text_params)
|
|
206
|
+
@text_params = text_params
|
|
207
|
+
@biz_params = biz_params
|
|
208
|
+
if text_params != nil && !@optional_text_params.empty?
|
|
209
|
+
@text_params = text_params.merge(@optional_text_params)
|
|
210
|
+
elsif text_params == nil
|
|
211
|
+
@text_params = @optional_text_params
|
|
212
|
+
end
|
|
213
|
+
if biz_params != nil && !@optional_biz_params.empty?
|
|
214
|
+
@biz_params = biz_params.merge(@optional_biz_params)
|
|
215
|
+
elsif biz_params == nil
|
|
216
|
+
@biz_params = @optional_biz_params
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
json = Alipay::EasySDK::Kernel::Util::JsonUtil.new
|
|
220
|
+
if @biz_params != nil
|
|
221
|
+
biz_params = json.to_json_string(@biz_params)
|
|
222
|
+
end
|
|
223
|
+
sorted_map = system_params || {}
|
|
224
|
+
if !biz_params.nil? && !biz_params.empty?
|
|
225
|
+
# 模拟PHP json_encode($bizParams, JSON_UNESCAPED_UNICODE)
|
|
226
|
+
json_string = JSON.generate(JSON.parse(biz_params)).gsub('/', "\\/")
|
|
227
|
+
sorted_map[AlipayConstants::BIZ_CONTENT_FIELD] = json_string
|
|
228
|
+
end
|
|
229
|
+
if !@text_params.nil? && !@text_params.empty?
|
|
230
|
+
if !sorted_map.empty?
|
|
231
|
+
sorted_map = sorted_map.merge(@text_params)
|
|
232
|
+
else
|
|
233
|
+
sorted_map = @text_params
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
if get_config('notify_url') != nil
|
|
237
|
+
sorted_map['notify_url'] = get_config('notify_url')
|
|
238
|
+
end
|
|
239
|
+
return sorted_map
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def get_sign_content(params)
|
|
243
|
+
# 模拟PHP的ksort
|
|
244
|
+
sorted_params = params.sort_by { |k, _| k.to_s }.to_h
|
|
245
|
+
|
|
246
|
+
string_to_be_signed = ""
|
|
247
|
+
i = 0
|
|
248
|
+
sorted_params.each do |k, v|
|
|
249
|
+
if !check_empty(v) && v.to_s[0] != "@"
|
|
250
|
+
# 转换成目标字符集
|
|
251
|
+
v = characet(v.to_s, AlipayConstants::DEFAULT_CHARSET)
|
|
252
|
+
if i == 0
|
|
253
|
+
string_to_be_signed += "#{k}=#{v}"
|
|
254
|
+
else
|
|
255
|
+
string_to_be_signed += "&#{k}=#{v}"
|
|
256
|
+
end
|
|
257
|
+
i += 1
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
return string_to_be_signed
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def get_gateway_server_url
|
|
264
|
+
return get_config('protocol') + '://' + get_config('gatewayHost').gsub('/gateway.do', '') + '/gateway.do'
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def check_empty(value)
|
|
268
|
+
if value.nil?
|
|
269
|
+
return true
|
|
270
|
+
end
|
|
271
|
+
if value == ""
|
|
272
|
+
return true
|
|
273
|
+
end
|
|
274
|
+
if value.to_s.strip == ""
|
|
275
|
+
return true
|
|
276
|
+
end
|
|
277
|
+
return false
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def characet(data, target_charset)
|
|
281
|
+
if !data.nil? && !data.empty?
|
|
282
|
+
file_type = AlipayConstants::DEFAULT_CHARSET
|
|
283
|
+
if file_type.downcase != target_charset.downcase
|
|
284
|
+
begin
|
|
285
|
+
data = data.encode(target_charset, invalid: :replace, undef: :replace)
|
|
286
|
+
rescue
|
|
287
|
+
data = data.force_encoding(target_charset)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
return data
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def url_encode(str)
|
|
295
|
+
# 模拟PHP的urlencode
|
|
296
|
+
str.gsub(/[^a-zA-Z0-9\-_.~]/n) do |s|
|
|
297
|
+
'%' + s.unpack('H2' * s.bytesize).join('%').upcase
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def build_form(url, params)
|
|
302
|
+
puts "[DEBUG] build_form - 原始params keys: #{params.keys.join(', ')}" if ENV['DEBUG']
|
|
303
|
+
puts "[DEBUG] build_form - sign长度: #{params[AlipayConstants::SIGN_FIELD].length}" if ENV['DEBUG'] && params[AlipayConstants::SIGN_FIELD]
|
|
304
|
+
|
|
305
|
+
# 完全按照PHP版本的PageUtil::buildForm方法:过滤空值参数
|
|
306
|
+
form_fields = ""
|
|
307
|
+
params.each do |key, val|
|
|
308
|
+
if !check_empty(val) # 与PHP版本的checkEmpty逻辑完全一致
|
|
309
|
+
# 按照PHP版本:将单引号替换为'
|
|
310
|
+
escaped_val = val.to_s.gsub("'", "'")
|
|
311
|
+
form_fields += "<input type='hidden' name='#{key}' value='#{escaped_val}'/>"
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# 完全按照PHP版本:在action URL中添加charset参数
|
|
316
|
+
action_url = "#{url}?charset=#{AlipayConstants::DEFAULT_CHARSET}"
|
|
317
|
+
|
|
318
|
+
<<~HTML
|
|
319
|
+
<form id='alipaysubmit' name='alipaysubmit' action='#{action_url}' method='POST'>
|
|
320
|
+
#{form_fields}
|
|
321
|
+
<input type='submit' value='ok' style='display:none;'></form>
|
|
322
|
+
<script>document.forms['alipaysubmit'].submit();</script>
|
|
323
|
+
HTML
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require_relative 'alipay_constants'
|
|
2
|
+
require_relative 'config'
|
|
3
|
+
require_relative 'easy_sdk_kernel'
|
|
4
|
+
require_relative '../payment/wap/client'
|
|
5
|
+
require_relative '../payment/page/client'
|
|
6
|
+
require_relative '../payment/common/client'
|
|
7
|
+
|
|
8
|
+
module Alipay
|
|
9
|
+
module EasySDK
|
|
10
|
+
module Kernel
|
|
11
|
+
class Factory
|
|
12
|
+
class ConfigurationNotSetError < StandardError; end
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def set_options(options = nil)
|
|
16
|
+
config = normalize_config(options)
|
|
17
|
+
initialize_context(config)
|
|
18
|
+
@instance
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
alias setOptions set_options
|
|
22
|
+
|
|
23
|
+
def config(options = nil)
|
|
24
|
+
return set_options(options) if options
|
|
25
|
+
ensure_context_set!
|
|
26
|
+
@config
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def payment
|
|
30
|
+
ensure_context_set!
|
|
31
|
+
@payment
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def wap
|
|
35
|
+
payment.wap
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def page
|
|
39
|
+
payment.page
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def common
|
|
43
|
+
payment.common
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def kernel
|
|
47
|
+
ensure_context_set!
|
|
48
|
+
@kernel
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def get_sdk_version
|
|
52
|
+
Alipay::EasySDK::Kernel::AlipayConstants::SDK_VERSION
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def normalize_config(options)
|
|
58
|
+
raise ArgumentError, '配置参数不能为空' if options.nil?
|
|
59
|
+
return options if options.is_a?(Config)
|
|
60
|
+
Config.new(options)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def initialize_context(config)
|
|
64
|
+
@config = config
|
|
65
|
+
@kernel = EasySDKKernel.new(config)
|
|
66
|
+
@payment = Payment.new(@kernel)
|
|
67
|
+
@instance = self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def ensure_context_set!
|
|
71
|
+
raise ConfigurationNotSetError, '请先调用Factory.set_options(config)设置SDK配置' unless @config
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class Payment
|
|
76
|
+
def initialize(kernel)
|
|
77
|
+
@kernel = kernel
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def wap
|
|
81
|
+
@wap ||= Alipay::EasySDK::Payment::Wap::Client.new(@kernel)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def page
|
|
85
|
+
@page ||= Alipay::EasySDK::Payment::Page::Client.new(@kernel)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def common
|
|
89
|
+
@common ||= Alipay::EasySDK::Payment::Common::Client.new(@kernel)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module Alipay
|
|
4
|
+
module EasySDK
|
|
5
|
+
module Kernel
|
|
6
|
+
module Util
|
|
7
|
+
class JsonUtil
|
|
8
|
+
# 完全复制PHP版本的toJsonString方法
|
|
9
|
+
def to_json_string(obj)
|
|
10
|
+
case obj
|
|
11
|
+
when Hash, Array
|
|
12
|
+
JSON.generate(obj)
|
|
13
|
+
when String
|
|
14
|
+
obj
|
|
15
|
+
else
|
|
16
|
+
obj.to_s
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.from_json_string(json_string)
|
|
21
|
+
return nil if json_string.nil? || json_string.empty?
|
|
22
|
+
JSON.parse(json_string)
|
|
23
|
+
rescue JSON::ParserError
|
|
24
|
+
json_string
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Alipay
|
|
2
|
+
module EasySDK
|
|
3
|
+
module Kernel
|
|
4
|
+
module Util
|
|
5
|
+
class ResponseChecker
|
|
6
|
+
def success?(response)
|
|
7
|
+
return true if response.respond_to?(:code) && response.code.to_s == '10000'
|
|
8
|
+
|
|
9
|
+
code_blank = !response.respond_to?(:code) || blank?(response.code)
|
|
10
|
+
sub_code_blank = !response.respond_to?(:sub_code) || blank?(response.sub_code)
|
|
11
|
+
|
|
12
|
+
code_blank && sub_code_blank
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def success(response)
|
|
16
|
+
success?(response)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def blank?(value)
|
|
22
|
+
value.nil? || value.to_s.strip.empty?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require_relative '../alipay_constants'
|
|
2
|
+
|
|
3
|
+
module Alipay
|
|
4
|
+
module EasySDK
|
|
5
|
+
module Kernel
|
|
6
|
+
module Util
|
|
7
|
+
class SignContentExtractor
|
|
8
|
+
def initialize
|
|
9
|
+
@response_suffix = "_response"
|
|
10
|
+
@error_response = "error_response"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 完全复制PHP版本的getSignSourceData方法
|
|
14
|
+
def get_sign_source_data(body, method)
|
|
15
|
+
root_node_name = method.gsub(".", "_") + @response_suffix
|
|
16
|
+
root_index = body.index(root_node_name)
|
|
17
|
+
if root_index != body.rindex(root_node_name)
|
|
18
|
+
raise '检测到响应报文中有重复的' + root_node_name + ',验签失败。'
|
|
19
|
+
end
|
|
20
|
+
error_index = body.index(@error_response)
|
|
21
|
+
if root_index && root_index > 0
|
|
22
|
+
return parser_json_source(body, root_node_name, root_index)
|
|
23
|
+
elsif error_index && error_index > 0
|
|
24
|
+
return parser_json_source(body, @error_response, error_index)
|
|
25
|
+
else
|
|
26
|
+
return nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# 完全复制PHP版本的parserJSONSource方法
|
|
31
|
+
def parser_json_source(response_content, node_name, node_index)
|
|
32
|
+
sign_data_start_index = node_index + node_name.length + 2
|
|
33
|
+
if response_content.include?(AlipayConstants::ALIPAY_CERT_SN_FIELD)
|
|
34
|
+
sign_index = response_content.rindex("\"" + AlipayConstants::ALIPAY_CERT_SN_FIELD + "\"")
|
|
35
|
+
else
|
|
36
|
+
sign_index = response_content.rindex("\"" + AlipayConstants::SIGN_FIELD + "\"")
|
|
37
|
+
end
|
|
38
|
+
# 签名前-逗号
|
|
39
|
+
sign_data_end_index = sign_index - 1
|
|
40
|
+
index_len = sign_data_end_index - sign_data_start_index
|
|
41
|
+
if index_len < 0
|
|
42
|
+
return nil
|
|
43
|
+
end
|
|
44
|
+
return response_content[sign_data_start_index, index_len]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
|
|
4
|
+
module Alipay
|
|
5
|
+
module EasySDK
|
|
6
|
+
module Kernel
|
|
7
|
+
module Util
|
|
8
|
+
class Signer
|
|
9
|
+
# 完全复制PHP版本的sign方法
|
|
10
|
+
def sign(content, private_key_pem)
|
|
11
|
+
begin
|
|
12
|
+
pri_key = private_key_pem
|
|
13
|
+
|
|
14
|
+
# 完全按照PHP的逻辑:wordwrap($priKey, 64, "\n", true)
|
|
15
|
+
res = "-----BEGIN RSA PRIVATE KEY-----\n" +
|
|
16
|
+
wordwrap(pri_key, 64, "\n", true) +
|
|
17
|
+
"\n-----END RSA PRIVATE KEY-----"
|
|
18
|
+
|
|
19
|
+
if res.nil? || res.empty?
|
|
20
|
+
raise '您使用的私钥格式错误,请检查RSA私钥配置'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 使用SHA256算法签名,完全模仿PHP的openssl_sign
|
|
24
|
+
private_key = OpenSSL::PKey::RSA.new(res)
|
|
25
|
+
digest = OpenSSL::Digest::SHA256.new
|
|
26
|
+
sign = private_key.sign(digest, content)
|
|
27
|
+
|
|
28
|
+
# 使用标准Base64编码,完全模仿PHP的base64_encode
|
|
29
|
+
Base64.strict_encode64(sign)
|
|
30
|
+
rescue
|
|
31
|
+
raise '您使用的私钥格式错误,请检查RSA私钥配置'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# 完全复制PHP版本的verify方法
|
|
36
|
+
def verify(content, sign, public_key_pem)
|
|
37
|
+
begin
|
|
38
|
+
pub_key = public_key_pem
|
|
39
|
+
|
|
40
|
+
# 完全按照PHP的逻辑:wordwrap($pubKey, 64, "\n", true)
|
|
41
|
+
res = "-----BEGIN PUBLIC KEY-----\n" +
|
|
42
|
+
wordwrap(pub_key, 64, "\n", true) +
|
|
43
|
+
"\n-----END PUBLIC KEY-----"
|
|
44
|
+
|
|
45
|
+
if res.nil? || res.empty?
|
|
46
|
+
raise '支付宝RSA公钥错误。请检查公钥文件格式是否正确'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 调用openssl内置方法验签,完全模仿PHP的openssl_verify
|
|
50
|
+
public_key = OpenSSL::PKey::RSA.new(res)
|
|
51
|
+
digest = OpenSSL::Digest::SHA256.new
|
|
52
|
+
decoded_sign = Base64.decode64(sign)
|
|
53
|
+
|
|
54
|
+
result = public_key.verify(digest, decoded_sign, content)
|
|
55
|
+
return result
|
|
56
|
+
rescue
|
|
57
|
+
return false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def verify_params(parameters, public_key)
|
|
62
|
+
sign = parameters['sign']
|
|
63
|
+
content = get_sign_content(parameters)
|
|
64
|
+
return verify(content, sign, public_key)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def get_sign_content(params)
|
|
68
|
+
# 模拟PHP的ksort
|
|
69
|
+
sorted_params = params.sort_by { |k, _| k.to_s }.to_h
|
|
70
|
+
|
|
71
|
+
# 移除sign和sign_type字段
|
|
72
|
+
sorted_params.delete('sign')
|
|
73
|
+
sorted_params.delete('sign_type')
|
|
74
|
+
|
|
75
|
+
string_to_be_signed = ""
|
|
76
|
+
i = 0
|
|
77
|
+
sorted_params.each do |k, v|
|
|
78
|
+
if v.to_s[0] != "@"
|
|
79
|
+
if i == 0
|
|
80
|
+
string_to_be_signed += "#{k}=#{v}"
|
|
81
|
+
else
|
|
82
|
+
string_to_be_signed += "&#{k}=#{v}"
|
|
83
|
+
end
|
|
84
|
+
i += 1
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
return string_to_be_signed
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# 完全复制PHP的wordwrap函数
|
|
93
|
+
def wordwrap(str, width = 75, break_str = "\n", cut = false)
|
|
94
|
+
if str.nil? || str.empty?
|
|
95
|
+
return str
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if cut
|
|
99
|
+
# 如果cut为true,强制在指定宽度处断开
|
|
100
|
+
str.scan(/.{1,#{width}}/).join(break_str)
|
|
101
|
+
else
|
|
102
|
+
# 默认行为,不在单词中间断开
|
|
103
|
+
result = []
|
|
104
|
+
current_line = ""
|
|
105
|
+
|
|
106
|
+
str.split.each do |word|
|
|
107
|
+
if current_line.empty?
|
|
108
|
+
current_line = word
|
|
109
|
+
elsif current_line.length + 1 + word.length <= width
|
|
110
|
+
current_line += " " + word
|
|
111
|
+
else
|
|
112
|
+
result << current_line
|
|
113
|
+
current_line = word
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
result << current_line unless current_line.empty?
|
|
118
|
+
result.join(break_str)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|