wechatpay-api 0.0.2 → 0.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 +4 -4
- data/Gemfile.lock +2 -12
- data/README.md +70 -1
- data/lib/wechatpay-api.rb +1 -0
- data/lib/wechatpay/api.rb +9 -7
- data/lib/wechatpay/api/v3/cert.rb +33 -0
- data/lib/wechatpay/api/v3/client.rb +132 -0
- data/lib/wechatpay/api/v3/jsapi.rb +26 -0
- data/lib/wechatpay/api/v3/trade.rb +22 -0
- data/lib/wechatpay/api/version.rb +1 -1
- data/wechatpay-api.gemspec +2 -4
- metadata +7 -46
- data/lib/wechatpay/api/v2/client.rb +0 -118
- data/lib/wechatpay/api/v2/pay.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b97b7c1f8ed9f448edd975a6adf2bf554840d5442fbc3dc5928be733941fdcd2
|
4
|
+
data.tar.gz: 39723a0aa3661c5bff6a1ebed35c67a79c0b1b976354882aa0651bdc2189ac89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f7be18010ec031f42fc57978b9adb186c1adf876e9c29a76a574a8c137f4d70fe99ae9862a791c4bee22223e5aae13ee1d9bd279e0f8968224c128f3f33252e
|
7
|
+
data.tar.gz: 94d67a3a5483c82c26000f9064c22f46234e8c183f1d5318c6c5a77263151caa7e211780941a09c8dc051de25abad59c8c72cdca550b18205d1943859a4899f9
|
data/Gemfile.lock
CHANGED
@@ -1,23 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
wechatpay-api (0.0.
|
4
|
+
wechatpay-api (0.0.2)
|
5
5
|
faraday (~> 1.0)
|
6
|
-
gyoku (>= 1.0.0)
|
7
6
|
multi_json (~> 1.0)
|
8
|
-
nokogiri (~> 1.0)
|
9
|
-
nori (~> 2.0)
|
10
7
|
|
11
8
|
GEM
|
12
9
|
remote: https://rubygems.org/
|
13
10
|
specs:
|
14
11
|
addressable (2.7.0)
|
15
12
|
public_suffix (>= 2.0.2, < 5.0)
|
16
|
-
builder (3.2.4)
|
17
13
|
coderay (1.1.3)
|
18
14
|
crack (0.4.4)
|
19
15
|
diff-lcs (1.4.4)
|
20
|
-
faraday (1.
|
16
|
+
faraday (1.2.0)
|
21
17
|
multipart-post (>= 1.2, < 3)
|
22
18
|
ruby2_keywords
|
23
19
|
ffi (1.13.1)
|
@@ -36,21 +32,15 @@ GEM
|
|
36
32
|
guard (~> 2.1)
|
37
33
|
guard-compat (~> 1.1)
|
38
34
|
rspec (>= 2.99.0, < 4.0)
|
39
|
-
gyoku (1.3.1)
|
40
|
-
builder (>= 2.1.2)
|
41
35
|
hashdiff (1.0.1)
|
42
36
|
listen (3.3.3)
|
43
37
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
44
38
|
rb-inotify (~> 0.9, >= 0.9.10)
|
45
39
|
lumberjack (1.2.8)
|
46
40
|
method_source (1.0.0)
|
47
|
-
mini_portile2 (2.4.0)
|
48
41
|
multi_json (1.15.0)
|
49
42
|
multipart-post (2.1.1)
|
50
43
|
nenv (0.3.0)
|
51
|
-
nokogiri (1.10.10)
|
52
|
-
mini_portile2 (~> 2.4.0)
|
53
|
-
nori (2.6.0)
|
54
44
|
notiffany (0.1.3)
|
55
45
|
nenv (~> 0.1)
|
56
46
|
shellany (~> 0.0)
|
data/README.md
CHANGED
@@ -1,2 +1,71 @@
|
|
1
1
|
# wechatpay-api
|
2
|
-
Yet Another wechatpay sdk for ruby 另一个微信支付Ruby SDK
|
2
|
+
Yet Another wechatpay sdk for ruby 另一个微信支付Ruby SDK。
|
3
|
+
APIv3 接口版本V3
|
4
|
+
|
5
|
+
公众号API SDK: https://github.com/lazing/wechat-api
|
6
|
+
|
7
|
+
# Install
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'wechatpay-api', '~>0.1'
|
11
|
+
```
|
12
|
+
|
13
|
+
|
14
|
+
or
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem install wechatpay-api
|
18
|
+
```
|
19
|
+
|
20
|
+
# Usage
|
21
|
+
|
22
|
+
## Prepare
|
23
|
+
|
24
|
+
Essential configure items and datus from wechat pay 需要从微信支付获取的必要配置项目
|
25
|
+
|
26
|
+
* appid - 公众号ID
|
27
|
+
* mchid - 商户号
|
28
|
+
* key - 32 bytes secret key to encrypt and decrypt 32字节加解密key
|
29
|
+
* cert_no - APIv3 certification serial no. 从微信支付获取的APIv3证书序列号
|
30
|
+
* cert - APIv3 certification private key pem, content as string. 从微信支付获取的APIv3证书私钥,内容作为文本输入
|
31
|
+
|
32
|
+
## Configuration and direct usage
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# initialize with a file like wechatpay-api.rb inside rails config initilizers
|
36
|
+
Wechatpay::Api.client do |klass|
|
37
|
+
klass.new 'appid', 'mchid', key: 'key', cert: 'apiv3 certification...', cert_no: 'cert_no'
|
38
|
+
end
|
39
|
+
|
40
|
+
# next in anywhere needs, using get or post, will sign automatic. response body
|
41
|
+
response_body_as_hash_symbolized = Wechatpay::Api.client.get '/path', params
|
42
|
+
response_body_as_hash_symbolized = Wechatpay::Api.client.post '/path', hash_as_body, headers
|
43
|
+
|
44
|
+
# check incoming request
|
45
|
+
Wechatpay::Api.client.verify headers, body
|
46
|
+
```
|
47
|
+
|
48
|
+
## JSAPI Helper Example
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# initilized somehow Wechatpay::Api.client do |klass| ..... end
|
52
|
+
# reference the client
|
53
|
+
client = Wechatpay::Api.client
|
54
|
+
# STEP 1. after generate your own order object, start prepay
|
55
|
+
prepay_id = client.js_prepay(openid, out_trade_no, description, total_amount, **opts)
|
56
|
+
|
57
|
+
# STEP 2. get signature for WXJS SDK call
|
58
|
+
timestamp = Time.now.to_i
|
59
|
+
nonce = SecureRandom.hex
|
60
|
+
package = "prepay_id=#{prepay_id}"
|
61
|
+
signature = client.js_sign(package, timestamp, nonce)
|
62
|
+
|
63
|
+
# STEP 3. expose to view and start payment
|
64
|
+
|
65
|
+
# STEP 4. build a API endpoint to receive wechat payment notice
|
66
|
+
# request header and body
|
67
|
+
|
68
|
+
decrypted_hash = client.notice(headers, body)
|
69
|
+
|
70
|
+
```
|
71
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'wechatpay/api'
|
data/lib/wechatpay/api.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
module Wechatpay
|
2
2
|
module Api
|
3
3
|
class Error < StandardError; end
|
4
|
-
module
|
5
|
-
end
|
4
|
+
module V3; end
|
6
5
|
|
7
6
|
def self.client(appid = 'origin_id')
|
8
7
|
var = "@v#{appid}"
|
9
|
-
if
|
10
|
-
|
11
|
-
elsif block_given?
|
12
|
-
c = yield(V2::Client)
|
8
|
+
if block_given?
|
9
|
+
c = yield(V3::Client)
|
13
10
|
instance_variable_set var, c
|
11
|
+
elsif instance_variable_defined?(var)
|
12
|
+
instance_variable_get(var)
|
14
13
|
else
|
15
14
|
raise Error, :not_initialized
|
16
15
|
end
|
@@ -18,4 +17,7 @@ module Wechatpay
|
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
21
|
-
require 'wechatpay/api/
|
20
|
+
require 'wechatpay/api/v3/client'
|
21
|
+
require 'wechatpay/api/v3/trade'
|
22
|
+
require 'wechatpay/api/v3/jsapi'
|
23
|
+
require 'wechatpay/api/v3/cert'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'singleton'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Wechatpay
|
6
|
+
module Api
|
7
|
+
module V3
|
8
|
+
class Cert
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_accessor :expires_at, :serial_no, :cert, :certificate
|
12
|
+
|
13
|
+
def update(json)
|
14
|
+
Wechatpay::Api.client.logger.debug { "Cert JSON: #{json}" }
|
15
|
+
expire_time = DateTime.parse(json[:expire_time])
|
16
|
+
return unless expires_at.nil? || expire_time > expires_at
|
17
|
+
|
18
|
+
@serial_no = json[:serial_no]
|
19
|
+
@expires_at = expire_time
|
20
|
+
ec = json[:encrypt_certificate]
|
21
|
+
@cert = yield(ec[:ciphertext], ec[:nonce], ec[:associated_data])
|
22
|
+
@certificate = OpenSSL::X509::Certificate.new @cert
|
23
|
+
end
|
24
|
+
|
25
|
+
def load
|
26
|
+
return false if cert.nil? || expires_at < DateTime.now
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'faraday'
|
3
|
+
require 'multi_json'
|
4
|
+
require 'openssl'
|
5
|
+
require 'logger'
|
6
|
+
require 'securerandom'
|
7
|
+
|
8
|
+
module Wechatpay
|
9
|
+
module Api
|
10
|
+
module V3
|
11
|
+
class Client
|
12
|
+
|
13
|
+
attr_reader :rsa_key, :serial_no, :mch_id, :appid, :key
|
14
|
+
attr_accessor :logger, :site
|
15
|
+
|
16
|
+
SCHEMA = 'WECHATPAY2-SHA256-RSA2048'.freeze
|
17
|
+
|
18
|
+
def initialize(appid, mch_id, **opts)
|
19
|
+
@appid = appid
|
20
|
+
@mch_id = mch_id
|
21
|
+
@rsa_key = OpenSSL::PKey::RSA.new opts[:cert] if opts[:cert]
|
22
|
+
@serial_no = opts[:cert_no]
|
23
|
+
@key = opts[:key]
|
24
|
+
@site = opts[:site] || 'https://api.mch.weixin.qq.com'
|
25
|
+
|
26
|
+
@logger = Logger.new(STDOUT)
|
27
|
+
end
|
28
|
+
|
29
|
+
def connection
|
30
|
+
Faraday.new(url: @site) do |conn|
|
31
|
+
conn.request :retry
|
32
|
+
conn.response :logger
|
33
|
+
# conn.response :raise_error
|
34
|
+
conn.adapter :net_http
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def get(path, params = nil)
|
39
|
+
resp = connection.get(path, params) do |req|
|
40
|
+
path = req.path
|
41
|
+
path = [req.path, Faraday::Utils.build_query(req.params)].join('?') unless params.nil? || params.empty?
|
42
|
+
req.headers['Authorization'] = authorization_header('GET', path, nil)
|
43
|
+
req.headers['Accept'] = 'application/json'
|
44
|
+
end
|
45
|
+
handle resp
|
46
|
+
end
|
47
|
+
|
48
|
+
def post(path, data, **headers)
|
49
|
+
body = data.is_a?(Hash) ? MultiJson.dump(data) : data
|
50
|
+
resp = connection.post(path, body, headers) do |req|
|
51
|
+
req.headers['Authorization'] = authorization_header('POST', path, body)
|
52
|
+
req.headers['Accept'] = 'application/json'
|
53
|
+
end
|
54
|
+
handle resp
|
55
|
+
end
|
56
|
+
|
57
|
+
def verify(headers, body)
|
58
|
+
sha256 = OpenSSL::Digest::SHA256.new
|
59
|
+
key = cert.certificate.public_key
|
60
|
+
sign = Base64.strict_decode64(headers['Wechatpay-Signature'])
|
61
|
+
data = %w[Wechatpay-Timestamp Wechatpay-Nonce].map { |k| headers[k] }
|
62
|
+
key.verify sha256, sign, data.append(body).join("\n") + "\n"
|
63
|
+
end
|
64
|
+
|
65
|
+
def cert
|
66
|
+
Wechatpay::Api::V3::Cert.instance.load || update_certs
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_certs
|
70
|
+
certs = get('/v3/certificates')[:data]
|
71
|
+
certs.map do |raw|
|
72
|
+
Wechatpay::Api::V3::Cert.instance.update(raw, &method(:decrypt))
|
73
|
+
end
|
74
|
+
Wechatpay::Api::V3::Cert.instance
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle(resp)
|
78
|
+
logger.debug { "HANDLE RESPONSE: #{resp.inspect}" }
|
79
|
+
data = resp.body
|
80
|
+
raise :empty_body unless data && !data.empty?
|
81
|
+
|
82
|
+
MultiJson.load data, symbolize_keys: true
|
83
|
+
end
|
84
|
+
|
85
|
+
def sign_with(body, method, path, timestamp, rnd)
|
86
|
+
str = [method, path, timestamp, rnd, body].join("\n") + "\n"
|
87
|
+
logger.debug { "Sign Content: #{str.inspect}" }
|
88
|
+
sign_content(str)
|
89
|
+
end
|
90
|
+
|
91
|
+
def sign_content(content)
|
92
|
+
digest = OpenSSL::Digest::SHA256.new
|
93
|
+
signed = rsa_key.sign(digest, content)
|
94
|
+
Base64.strict_encode64 signed
|
95
|
+
end
|
96
|
+
|
97
|
+
def authorization_header(http_method, path, body)
|
98
|
+
[SCHEMA, authorization_params(http_method, path, body)].join ' '
|
99
|
+
end
|
100
|
+
|
101
|
+
def authorization_params(http_method, path, body)
|
102
|
+
timestamp = Time.now.to_i
|
103
|
+
rnd = SecureRandom.hex
|
104
|
+
signature = sign_with(body, http_method, path, timestamp, rnd)
|
105
|
+
[
|
106
|
+
"mchid=\"#{mch_id}\"", "serial_no=\"#{serial_no}\"", "nonce_str=\"#{rnd}\"",
|
107
|
+
"timestamp=\"#{timestamp}\"", "signature=\"#{signature}\""
|
108
|
+
].join(',')
|
109
|
+
end
|
110
|
+
|
111
|
+
def decrypt(cipher_text, nonce, auth_data)
|
112
|
+
raise :cipher_text_invalid if (cipher_text.try(:length) || 0) < 16
|
113
|
+
|
114
|
+
data = Base64.strict_decode64(cipher_text)
|
115
|
+
dec = dechipher(data, nonce, auth_data)
|
116
|
+
dec.update(data[0, data.length - 16]).tap { |s| logger.debug "DEC: #{s}" } + dec.final
|
117
|
+
end
|
118
|
+
|
119
|
+
def dechipher(data, nonce, auth_data)
|
120
|
+
chipher = OpenSSL::Cipher.new 'aes-256-gcm'
|
121
|
+
chipher.decrypt
|
122
|
+
chipher.key = key
|
123
|
+
chipher.iv = nonce
|
124
|
+
chipher.padding = 0
|
125
|
+
chipher.auth_data = auth_data
|
126
|
+
chipher.auth_tag = data[-16, 16]
|
127
|
+
chipher
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module Wechatpay
|
3
|
+
module Api
|
4
|
+
module V3
|
5
|
+
module JSAPI
|
6
|
+
def js_prepay(openid, out_trade_no, description, total_amount, **opts)
|
7
|
+
data = {
|
8
|
+
appid: appid, mchid: mch_id, description: description,
|
9
|
+
out_trade_no: out_trade_no, amount: { total: total_amount, currency: 'CNY' },
|
10
|
+
payer: { openid: openid }
|
11
|
+
}.merge(opts)
|
12
|
+
res = post '/v3/pay/transactions/jsapi', data
|
13
|
+
res[:prepay_id]
|
14
|
+
end
|
15
|
+
|
16
|
+
def js_sign(package, timestamp, nonce)
|
17
|
+
str = [appid, timestamp, nonce, package].join("\n") + "\n"
|
18
|
+
sign_content(str)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Client.include JSAPI
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Wechatpay
|
4
|
+
module Api
|
5
|
+
module V3
|
6
|
+
module Trade
|
7
|
+
|
8
|
+
def notice(headers, payload)
|
9
|
+
raise :verify_fail unless verify(headers, payload)
|
10
|
+
|
11
|
+
data = MultiJson.load(payload, symbolize_keys: true)
|
12
|
+
r = data[:resource]
|
13
|
+
text = decrypt r[:ciphertext], r[:nonce], r[:associated_data]
|
14
|
+
MultiJson.load(text, symbolize_keys: true)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Client.include Trade
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
data/wechatpay-api.gemspec
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'wechatpay/api/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |s|
|
@@ -18,10 +19,7 @@ Gem::Specification.new do |s|
|
|
18
19
|
s.require_paths = ['lib']
|
19
20
|
|
20
21
|
s.add_dependency 'faraday', '~> 1.0'
|
21
|
-
s.add_dependency 'gyoku', '>= 1.0.0'
|
22
22
|
s.add_dependency 'multi_json', '~> 1.0'
|
23
|
-
s.add_dependency 'nokogiri', '~> 1.0'
|
24
|
-
s.add_dependency 'nori', '~> 2.0'
|
25
23
|
|
26
24
|
s.add_development_dependency 'bundler', '~> 1.0'
|
27
25
|
s.add_development_dependency 'guard', '~> 2.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wechatpay-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- lazing
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: gyoku
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.0.0
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 1.0.0
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: multi_json
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,34 +38,6 @@ dependencies:
|
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '1.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: nokogiri
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: nori
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '2.0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '2.0'
|
83
41
|
- !ruby/object:Gem::Dependency
|
84
42
|
name: bundler
|
85
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -190,9 +148,12 @@ files:
|
|
190
148
|
- Guardfile
|
191
149
|
- LICENSE.md
|
192
150
|
- README.md
|
151
|
+
- lib/wechatpay-api.rb
|
193
152
|
- lib/wechatpay/api.rb
|
194
|
-
- lib/wechatpay/api/
|
195
|
-
- lib/wechatpay/api/
|
153
|
+
- lib/wechatpay/api/v3/cert.rb
|
154
|
+
- lib/wechatpay/api/v3/client.rb
|
155
|
+
- lib/wechatpay/api/v3/jsapi.rb
|
156
|
+
- lib/wechatpay/api/v3/trade.rb
|
196
157
|
- lib/wechatpay/api/version.rb
|
197
158
|
- wechatpay-api.gemspec
|
198
159
|
homepage: https://github.com/lazing/wechatpay-api
|
@@ -1,118 +0,0 @@
|
|
1
|
-
require 'faraday'
|
2
|
-
require 'multi_json'
|
3
|
-
require 'openssl'
|
4
|
-
require 'logger'
|
5
|
-
require 'nokogiri'
|
6
|
-
require 'nori'
|
7
|
-
require 'gyoku'
|
8
|
-
require 'securerandom'
|
9
|
-
|
10
|
-
require 'wechatpay/api/v2/pay'
|
11
|
-
|
12
|
-
module Wechatpay
|
13
|
-
module Api
|
14
|
-
module V2
|
15
|
-
class Client
|
16
|
-
|
17
|
-
include Pay
|
18
|
-
|
19
|
-
attr_reader :appid, :mch_id, :key
|
20
|
-
attr_accessor :logger, :site
|
21
|
-
|
22
|
-
def initialize(appid, mch_id, **opts)
|
23
|
-
@appid = appid
|
24
|
-
@mch_id = mch_id
|
25
|
-
@key = opts[:key]
|
26
|
-
@site = opts[:site] || 'https://api.mch.weixin.qq.com'
|
27
|
-
|
28
|
-
@logger = Logger.new(STDOUT)
|
29
|
-
end
|
30
|
-
|
31
|
-
def post(url, params, headers = {})
|
32
|
-
data = sign(merge(params))
|
33
|
-
response = connection.post url, xml(data), headers
|
34
|
-
logger.debug { { response: response } }
|
35
|
-
handle(response.body)
|
36
|
-
end
|
37
|
-
|
38
|
-
def verify(response)
|
39
|
-
sign = response.delete(:sign)
|
40
|
-
test = sign(response)
|
41
|
-
logger.debug { { result: test } }
|
42
|
-
sign == test[:sign]
|
43
|
-
end
|
44
|
-
|
45
|
-
def parse(body)
|
46
|
-
parser.parse(body)
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def requires(params, keys)
|
52
|
-
actual = params.keys.map(&:to_sym)
|
53
|
-
return if keys == (actual & keys)
|
54
|
-
|
55
|
-
raise Wechatpay::Api::Error, "missing params #{keys - actual}"
|
56
|
-
end
|
57
|
-
|
58
|
-
def connection
|
59
|
-
Faraday.new(url: @site) do |conn|
|
60
|
-
conn.request :retry
|
61
|
-
conn.response :logger
|
62
|
-
conn.response :raise_error
|
63
|
-
conn.adapter :net_http
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def handle(body)
|
68
|
-
response = parse(body)
|
69
|
-
check(response)
|
70
|
-
response
|
71
|
-
end
|
72
|
-
|
73
|
-
def parser
|
74
|
-
Nori.new
|
75
|
-
end
|
76
|
-
|
77
|
-
def check(res)
|
78
|
-
return if res['xml']['result_code'] == 'SUCCESS'
|
79
|
-
|
80
|
-
handle_error(res['xml']['err_code'], res)
|
81
|
-
rescue NoMethodError
|
82
|
-
logger.warn { res.inspect }
|
83
|
-
raise Error, 'Bad Response'
|
84
|
-
end
|
85
|
-
|
86
|
-
def handle_error(error_code, response)
|
87
|
-
raise ERRORS[error_code] || PayError, response.inspect
|
88
|
-
end
|
89
|
-
|
90
|
-
def xml(hash)
|
91
|
-
Gyoku.xml({ xml: hash }, key_converter: :none)
|
92
|
-
end
|
93
|
-
|
94
|
-
def sign(params)
|
95
|
-
ordered = trim_and_sort(params)
|
96
|
-
keystr = format('key=%<key>s', key: key)
|
97
|
-
origin = ordered.map { |k, v| [k, v].join('=') }.push(keystr).join('&')
|
98
|
-
sign = Digest::MD5.hexdigest(origin).upcase
|
99
|
-
logger.debug { format('origin: %s, sign: %s', origin, sign) }
|
100
|
-
params.merge(sign: sign)
|
101
|
-
end
|
102
|
-
|
103
|
-
def trim_and_sort(params)
|
104
|
-
params.delete_if { |_k, v| v.to_s.blank? }
|
105
|
-
Hash[params.sort]
|
106
|
-
end
|
107
|
-
|
108
|
-
def merge(params)
|
109
|
-
{ mch_id: @mch_id, nonce_str: nonce_str }.merge(params)
|
110
|
-
end
|
111
|
-
|
112
|
-
def nonce_str
|
113
|
-
SecureRandom.hex
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
data/lib/wechatpay/api/v2/pay.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module Wechatpay
|
2
|
-
module Api
|
3
|
-
module V2
|
4
|
-
module Pay
|
5
|
-
|
6
|
-
def unifiedorder(params)
|
7
|
-
requires(params, %i[body out_trade_no total_fee spbill_create_ip notify_url trade_type])
|
8
|
-
post('/pay/unifiedorder', params)
|
9
|
-
end
|
10
|
-
|
11
|
-
def jsapi_params(params)
|
12
|
-
requires(params, %i[prepayid])
|
13
|
-
|
14
|
-
data = {
|
15
|
-
appId: appid,
|
16
|
-
package: format('prepay_id=%<prepayid>s', params),
|
17
|
-
nonceStr: nonce_str,
|
18
|
-
timeStamp: Time.now.to_i,
|
19
|
-
signType: 'MD5'
|
20
|
-
}
|
21
|
-
sign(data)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|