jdpay 0.1.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/.gitignore +18 -0
- data/.travis.yml +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +361 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/jd_pay.gemspec +29 -0
- data/lib/jd_pay.rb +24 -0
- data/lib/jd_pay/des.rb +66 -0
- data/lib/jd_pay/qr_service.rb +17 -0
- data/lib/jd_pay/result.rb +28 -0
- data/lib/jd_pay/service.rb +180 -0
- data/lib/jd_pay/sign.rb +47 -0
- data/lib/jd_pay/util.rb +34 -0
- data/lib/jd_pay/version.rb +3 -0
- data/test/jd_pay/des_test.rb +16 -0
- data/test/jd_pay/qr_service_test.rb +28 -0
- data/test/jd_pay/result_test.rb +12 -0
- data/test/jd_pay/service_test.rb +131 -0
- data/test/jd_pay/sign_test.rb +21 -0
- data/test/jd_pay/util_test.rb +27 -0
- data/test/jd_pay_test.rb +11 -0
- data/test/test_helper.rb +42 -0
- metadata +178 -0
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "jd_pay"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/jd_pay.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'jd_pay/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "jdpay"
|
|
8
|
+
spec.version = JdPay::VERSION
|
|
9
|
+
spec.authors = ["Genkin He"]
|
|
10
|
+
spec.email = ["hemengzhi88@gmail.com"]
|
|
11
|
+
spec.summary = %q{An unofficial simple jdpay gem.}
|
|
12
|
+
spec.description = %q{An unofficial simple jdpay gem}
|
|
13
|
+
spec.homepage = "https://github.com/genkin-he/jd_pay"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
spec.test_files = Dir["test/**/*"]
|
|
20
|
+
|
|
21
|
+
spec.add_runtime_dependency "rest-client", '>= 2.0.0'
|
|
22
|
+
spec.add_runtime_dependency "activesupport", '>= 3.2'
|
|
23
|
+
spec.add_runtime_dependency "builder", '~> 3.2.2'
|
|
24
|
+
|
|
25
|
+
spec.add_development_dependency "bundler", '~> 1.3'
|
|
26
|
+
spec.add_development_dependency "rake", '~> 11.2'
|
|
27
|
+
spec.add_development_dependency "webmock", '~> 2.3'
|
|
28
|
+
spec.add_development_dependency "minitest", '~> 5'
|
|
29
|
+
end
|
data/lib/jd_pay.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "jd_pay/version"
|
|
2
|
+
require "jd_pay/des"
|
|
3
|
+
require "jd_pay/result"
|
|
4
|
+
require "jd_pay/service"
|
|
5
|
+
require "jd_pay/sign"
|
|
6
|
+
require "jd_pay/util"
|
|
7
|
+
require "jd_pay/qr_service"
|
|
8
|
+
|
|
9
|
+
module JdPay
|
|
10
|
+
@extra_rest_client_options = {}
|
|
11
|
+
@debug_mode = true
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_accessor :mch_id, :md5_key, :des_key, :pri_key, :pub_key, :debug_mode,
|
|
15
|
+
:extra_rest_client_options, :qr_mch_id, :qr_des_key, :qr_pri_key
|
|
16
|
+
def public_key
|
|
17
|
+
self.pub_key ? self.pub_key : JdPay::Sign::JDPAY_RSA_PUBLIC_KEY
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def debug_mode?
|
|
21
|
+
!!@debug_mode
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/jd_pay/des.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module JdPay
|
|
2
|
+
module Des
|
|
3
|
+
class << self
|
|
4
|
+
def encrypt_3des(str, options = {})
|
|
5
|
+
des = OpenSSL::Cipher::Cipher.new('des-ede3')
|
|
6
|
+
str = format_str_data(str)
|
|
7
|
+
des.encrypt
|
|
8
|
+
des.key = decode_key(options)
|
|
9
|
+
des.iv = des.random_iv
|
|
10
|
+
str = des.update(str)
|
|
11
|
+
to_hex(str.bytes)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def decrypt_3des(encrypt_str, options = {})
|
|
15
|
+
encrypt_str = to_decimal(encrypt_str)
|
|
16
|
+
des2 = OpenSSL::Cipher::Cipher.new('des-ede3')
|
|
17
|
+
des2.decrypt
|
|
18
|
+
des2.key = decode_key(options)
|
|
19
|
+
des2.iv = des2.random_iv
|
|
20
|
+
des2.padding = 0
|
|
21
|
+
result = (des2.update(encrypt_str) + des2.final).bytes
|
|
22
|
+
result.first(valid_size(result.shift(4))).map(&:chr).join.force_encoding('utf-8').encode('utf-8')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def decode_key(options = {})
|
|
28
|
+
Base64.decode64(options[:des_key] || JdPay.des_key)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 计算补位数据数组
|
|
32
|
+
def padding_array(num)
|
|
33
|
+
temp = (num + 4) % 8
|
|
34
|
+
Array.new(temp == 0 ? 0 : (8 - temp), 0x00)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# 有效数据长度数组
|
|
38
|
+
def valid_size_array(num)
|
|
39
|
+
size_array = [num >> 24 & 0xff, num >> 16 & 0xff, num >> 8 & 0xff, num & 0xff]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# 根据数组算出有效数据值
|
|
43
|
+
def valid_size(arr)
|
|
44
|
+
to_hex(arr).to_i(16)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 格式化加密元数据
|
|
48
|
+
def format_str_data(str)
|
|
49
|
+
str_bytes = str.bytes
|
|
50
|
+
str_bytes_size = str_bytes.length
|
|
51
|
+
(valid_size_array(str_bytes_size) + str_bytes + padding_array(str_bytes_size)).map(&:chr).join
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# 10进制string转16进制
|
|
55
|
+
def to_hex(arr)
|
|
56
|
+
arr.map { |num| "%02x" % num }.join
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 16进制string转10进制
|
|
60
|
+
def to_decimal(str)
|
|
61
|
+
# str.scan(/../).map{ |r| r.hex.chr }.join
|
|
62
|
+
Array(str).pack('H*')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'rest_client'
|
|
2
|
+
require 'active_support/core_ext/hash/conversions'
|
|
3
|
+
module JdPay
|
|
4
|
+
module QrService
|
|
5
|
+
USABLE_METHODS = %i(qrcode_pay refund query revoke notify_verify)
|
|
6
|
+
def self.method_missing(method, *args)
|
|
7
|
+
|
|
8
|
+
super unless USABLE_METHODS.include?(method)
|
|
9
|
+
qr_service_default_config = {
|
|
10
|
+
mch_id: JdPay.qr_mch_id, des_key: JdPay.qr_des_key, pri_key: JdPay.qr_pri_key
|
|
11
|
+
}
|
|
12
|
+
args[1] = {} if args[1].nil?
|
|
13
|
+
args[1] = qr_service_default_config.merge(args[1])
|
|
14
|
+
JdPay::Service.public_send(method, *args)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module JdPay
|
|
2
|
+
class Result < ::Hash
|
|
3
|
+
SUCCESS_FLAG = '000000'.freeze
|
|
4
|
+
|
|
5
|
+
def initialize(result, options = {})
|
|
6
|
+
super nil
|
|
7
|
+
|
|
8
|
+
self['jdpay'] = result['jdpay']
|
|
9
|
+
|
|
10
|
+
if result['jdpay'].class == Hash && (decrypt = self.decrypt_verify(options = {})).class == Hash
|
|
11
|
+
self['jdpay'] = decrypt['jdpay']
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def success?
|
|
16
|
+
self['jdpay']['result']['code'] == SUCCESS_FLAG
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def decrypt_verify(options = {})
|
|
20
|
+
if self.success?
|
|
21
|
+
content_hash = Hash.from_xml JdPay::Des.decrypt_3des(Base64.decode64(self['jdpay']['encrypt']), options)
|
|
22
|
+
JdPay::Sign.rsa_verify?(content_hash, options) ? content_hash : (raise "JdPay_verify_err:#{content_hash}")
|
|
23
|
+
else
|
|
24
|
+
raise "JdPay::Result#decrypt_verify_err:#{self}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
require 'rest_client'
|
|
2
|
+
require 'active_support/core_ext/hash/conversions'
|
|
3
|
+
|
|
4
|
+
module JdPay
|
|
5
|
+
module Service
|
|
6
|
+
UNIORDER_URL = 'https://paygate.jd.com/service/uniorder'
|
|
7
|
+
QUERY_URL = 'https://paygate.jd.com/service/query'
|
|
8
|
+
REFUND_URL = 'https://paygate.jd.com/service/refund'
|
|
9
|
+
H5_PAY_URL = 'https://h5pay.jd.com/jdpay/saveOrder'
|
|
10
|
+
PC_PAY_URL = 'https://wepay.jd.com/jdpay/saveOrder'
|
|
11
|
+
REVOKE_URL = 'https://paygate.jd.com/service/revoke'
|
|
12
|
+
QRCODE_PAY_URL = 'https://paygate.jd.com/service/fkmPay'
|
|
13
|
+
USER_RELATION_URL = 'https://paygate.jd.com/service/getUserRelation'
|
|
14
|
+
CANCEL_USER_URL = 'https://paygate.jd.com/service/cancelUserRelation'
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# the difference between pc and h5 is just request url
|
|
18
|
+
def pc_pay(params, options = {})
|
|
19
|
+
web_pay(params, PC_PAY_URL, options = {})
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def h5_pay(params, options = {})
|
|
23
|
+
web_pay(params, H5_PAY_URL, options = {})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
WEB_PAY_REQUIRED_FIELDS = [:tradeNum, :tradeName, :amount, :orderType, :notifyUrl, :callbackUrl, :userId]
|
|
27
|
+
def web_pay(params, url, options = {})
|
|
28
|
+
params = {
|
|
29
|
+
version: "V2.0",
|
|
30
|
+
merchant: options[:mch_id] || JdPay.mch_id,
|
|
31
|
+
tradeTime: Time.now.strftime("%Y%m%d%H%M%S"),
|
|
32
|
+
currency: "CNY"
|
|
33
|
+
}.merge(params)
|
|
34
|
+
|
|
35
|
+
check_required_options(params, WEB_PAY_REQUIRED_FIELDS)
|
|
36
|
+
sign = JdPay::Sign.rsa_encrypt(JdPay::Util.to_uri(params), options)
|
|
37
|
+
skip_encrypt_params = %i(version merchant)
|
|
38
|
+
params.each do |k, v|
|
|
39
|
+
params[k] = skip_encrypt_params.include?(k) ? v : JdPay::Des.encrypt_3des(v)
|
|
40
|
+
end
|
|
41
|
+
params[:sign] = sign
|
|
42
|
+
JdPay::Util.build_pay_form(url, params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
UNIORDER_REQUIRED_FIELDS = [:tradeNum, :tradeName, :amount, :orderType, :notifyUrl, :userId]
|
|
46
|
+
def uniorder(params, options = {})
|
|
47
|
+
params = {
|
|
48
|
+
version: "V2.0",
|
|
49
|
+
merchant: options[:mch_id] || JdPay.mch_id,
|
|
50
|
+
tradeTime: Time.now.strftime("%Y%m%d%H%M%S"),
|
|
51
|
+
currency: "CNY"
|
|
52
|
+
}.merge(params)
|
|
53
|
+
|
|
54
|
+
check_required_options(params, UNIORDER_REQUIRED_FIELDS)
|
|
55
|
+
params[:sign] = JdPay::Sign.rsa_encrypt(JdPay::Util.to_xml(params), options)
|
|
56
|
+
|
|
57
|
+
JdPay::Result.new(Hash.from_xml(invoke_remote(UNIORDER_URL, make_payload(params), options)), options)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
QRCODE_REQUIRED_FIELDS = [:tradeNum, :tradeName, :amount, :device, :token]
|
|
61
|
+
def qrcode_pay(params, options = {})
|
|
62
|
+
params = {
|
|
63
|
+
version: "V2.0",
|
|
64
|
+
merchant: options[:mch_id] || JdPay.mch_id,
|
|
65
|
+
tradeTime: Time.now.strftime("%Y%m%d%H%M%S"),
|
|
66
|
+
currency: "CNY"
|
|
67
|
+
}.merge(params)
|
|
68
|
+
|
|
69
|
+
check_required_options(params, QRCODE_REQUIRED_FIELDS)
|
|
70
|
+
params[:sign] = JdPay::Sign.rsa_encrypt(JdPay::Util.to_xml(params), options)
|
|
71
|
+
|
|
72
|
+
JdPay::Result.new(Hash.from_xml(invoke_remote(QRCODE_PAY_URL, make_payload(params), options)), options)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
USER_RELATION_REQUIRED_FIELDS = [:useId]
|
|
76
|
+
def user_relation(params, options = {})
|
|
77
|
+
params = {
|
|
78
|
+
version: "V2.0",
|
|
79
|
+
merchant: options[:mch_id] || JdPay.mch_id,
|
|
80
|
+
}.merge(params)
|
|
81
|
+
|
|
82
|
+
check_required_options(params, USER_RELATION_REQUIRED_FIELDS)
|
|
83
|
+
params[:sign] = JdPay::Sign.rsa_encrypt(JdPay::Util.to_xml(params), options)
|
|
84
|
+
|
|
85
|
+
JdPay::Result.new(Hash.from_xml(invoke_remote(USER_RELATION_URL, make_payload(params), options)), options)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cancel_user(params, options = {})
|
|
89
|
+
params = {
|
|
90
|
+
version: "V2.0",
|
|
91
|
+
merchant: options[:mch_id] || JdPay.mch_id,
|
|
92
|
+
}.merge(params)
|
|
93
|
+
|
|
94
|
+
check_required_options(params, USER_RELATION_REQUIRED_FIELDS)
|
|
95
|
+
params[:sign] = JdPay::Sign.rsa_encrypt(JdPay::Util.to_xml(params), options)
|
|
96
|
+
|
|
97
|
+
JdPay::Result.new(Hash.from_xml(invoke_remote(CANCEL_USER_URL, make_payload(params), options)), options)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
REFUND_REQUIRED_FIELDS = [:tradeNum, :oTradeNum, :amount, :notifyUrl]
|
|
101
|
+
def refund(params, options = {})
|
|
102
|
+
params = {
|
|
103
|
+
version: "V2.0",
|
|
104
|
+
merchant: options[:mch_id] || JdPay.mch_id,
|
|
105
|
+
tradeTime: Time.now.strftime("%Y%m%d%H%M%S"),
|
|
106
|
+
currency: "CNY"
|
|
107
|
+
}.merge(params)
|
|
108
|
+
|
|
109
|
+
check_required_options(params, REFUND_REQUIRED_FIELDS)
|
|
110
|
+
params[:sign] = JdPay::Sign.rsa_encrypt(JdPay::Util.to_xml(params), options)
|
|
111
|
+
|
|
112
|
+
JdPay::Result.new(Hash.from_xml(invoke_remote(REFUND_URL, make_payload(params), options)), options)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
QUERY_REQUIRED_FIELDS = [:tradeNum, :tradeType]
|
|
116
|
+
def query(params, options = {})
|
|
117
|
+
params = {
|
|
118
|
+
version: "V2.0",
|
|
119
|
+
merchant: options[:mch_id] || JdPay.mch_id,
|
|
120
|
+
tradeType: '0'
|
|
121
|
+
}.merge(params)
|
|
122
|
+
|
|
123
|
+
check_required_options(params, QUERY_REQUIRED_FIELDS)
|
|
124
|
+
params[:sign] = JdPay::Sign.rsa_encrypt(JdPay::Util.to_xml(params), options)
|
|
125
|
+
|
|
126
|
+
JdPay::Result.new(Hash.from_xml(invoke_remote(QUERY_URL, make_payload(params), options)), options)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
REVOKE_REQUIRED_FIELDS = [:tradeNum, :oTradeNum, :amount]
|
|
130
|
+
def revoke(params, options = {})
|
|
131
|
+
params = {
|
|
132
|
+
version: "V2.0",
|
|
133
|
+
merchant: options[:mch_id] || JdPay.mch_id,
|
|
134
|
+
tradeTime: Time.now.strftime("%Y%m%d%H%M%S"),
|
|
135
|
+
currency: "CNY"
|
|
136
|
+
}.merge(params)
|
|
137
|
+
|
|
138
|
+
check_required_options(params, REVOKE_REQUIRED_FIELDS)
|
|
139
|
+
params[:sign] = JdPay::Sign.rsa_encrypt(JdPay::Util.to_xml(params), options)
|
|
140
|
+
|
|
141
|
+
JdPay::Result.new(Hash.from_xml(invoke_remote(REVOKE_URL, make_payload(params), options)), options)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def notify_verify(xml_str, options = {})
|
|
145
|
+
JdPay::Result.new(Hash.from_xml(xml_str), options)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def check_required_options(options, names)
|
|
151
|
+
return unless JdPay.debug_mode?
|
|
152
|
+
names.each do |name|
|
|
153
|
+
warn("JdPay Warn: missing required option: #{name}") unless options.has_key?(name)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def make_payload(params, options = {})
|
|
158
|
+
request_hash = {
|
|
159
|
+
"version" => "V2.0",
|
|
160
|
+
"merchant" => options[:mch_id] || JdPay.mch_id,
|
|
161
|
+
"encrypt" => Base64.strict_encode64(JdPay::Des.encrypt_3des JdPay::Util.to_xml(params))
|
|
162
|
+
}
|
|
163
|
+
JdPay::Util.to_xml(request_hash)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def invoke_remote(url, payload, options = {})
|
|
167
|
+
options = JdPay.extra_rest_client_options.merge(options)
|
|
168
|
+
|
|
169
|
+
RestClient::Request.execute(
|
|
170
|
+
{
|
|
171
|
+
method: :post,
|
|
172
|
+
url: url,
|
|
173
|
+
payload: payload,
|
|
174
|
+
headers: { content_type: 'application/xml' }
|
|
175
|
+
}.merge(options)
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
data/lib/jd_pay/sign.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
require "base64"
|
|
3
|
+
|
|
4
|
+
module JdPay
|
|
5
|
+
module Sign
|
|
6
|
+
|
|
7
|
+
JDPAY_RSA_PUBLIC_KEY = <<EOF
|
|
8
|
+
-----BEGIN PUBLIC KEY-----
|
|
9
|
+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCKE5N2xm3NIrXON8Zj19GNtLZ8
|
|
10
|
+
xwEQ6uDIyrS3S03UhgBJMkGl4msfq4Xuxv6XUAN7oU1XhV3/xtabr9rXto4Ke3d6
|
|
11
|
+
WwNbxwXnK5LSgsQc1BhT5NcXHXpGBdt7P8NMez5qGieOKqHGvT0qvjyYnYA29a8Z
|
|
12
|
+
4wzNR7vAVHp36uD5RwIDAQAB
|
|
13
|
+
-----END PUBLIC KEY-----
|
|
14
|
+
EOF
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# params:
|
|
18
|
+
# :orderId
|
|
19
|
+
def md5_sign(order_id, options = {})
|
|
20
|
+
Digest::MD5.hexdigest(
|
|
21
|
+
"merchant=#{options[:mch_id] || JdPay.mch_id}" +
|
|
22
|
+
"&orderId=#{order_id}&key=#{options[:md5_key] || JdPay.md5_key}"
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rsa_encrypt(str, options = {})
|
|
27
|
+
private_key = OpenSSL::PKey::RSA.new(options[:pri_key] || JdPay.pri_key)
|
|
28
|
+
Base64.strict_encode64(private_key.private_encrypt Digest::SHA256.hexdigest(str))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def rsa_decrypt(sign_str, options = {})
|
|
32
|
+
public_key = OpenSSL::PKey::RSA.new(options[:pub_key] || JdPay.public_key)
|
|
33
|
+
public_key.public_decrypt(Base64.decode64(sign_str))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def rsa_verify?(params, options = {})
|
|
37
|
+
params = params['jdpay'].dup
|
|
38
|
+
sign_str = params.delete('sign')
|
|
39
|
+
xml_without_sign = JdPay::Util.to_xml(params, root: 'jdpay')
|
|
40
|
+
ori_datas = [xml_without_sign, xml_without_sign.gsub("?>", " ?>")].map do |xml|
|
|
41
|
+
Digest::SHA256.hexdigest(xml)
|
|
42
|
+
end
|
|
43
|
+
ori_datas.include? rsa_decrypt(sign_str, options)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/jd_pay/util.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module JdPay
|
|
2
|
+
module Util
|
|
3
|
+
class << self
|
|
4
|
+
def to_xml(params, options = {})
|
|
5
|
+
options[:root] = options[:root] || 'jdpay'
|
|
6
|
+
denilize(params).to_xml(options).gsub(/>[[:space:]]+/, ">")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_uri(params)
|
|
10
|
+
params.sort.map do |k, v|
|
|
11
|
+
"#{k}=#{v}"
|
|
12
|
+
end.compact.join('&')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def denilize(h)
|
|
16
|
+
h.each_with_object({}) { | (k, v), g |
|
|
17
|
+
g[k] = (Hash === v) ? denilize(v) : v ? v : '' }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_pay_form(url, form_attributes)
|
|
21
|
+
inputs = ''
|
|
22
|
+
"<html>
|
|
23
|
+
<body onload=document.getElementById('payForm').submit(); style='display: none;'>
|
|
24
|
+
<form action=#{url} method='post' id='payForm'>
|
|
25
|
+
#{form_attributes.each do |k, v|
|
|
26
|
+
inputs << "<input type='text' name=#{k} value=#{v}>"
|
|
27
|
+
end and inputs}
|
|
28
|
+
</form>
|
|
29
|
+
</body>
|
|
30
|
+
</html>".gsub(/>[[:space:]]+/, ">")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|