wechat-api 0.1.3 → 0.4.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 +5 -5
- data/.gitignore +1 -0
- data/Gemfile +1 -7
- data/README.md +23 -8
- data/lib/wechat-api.rb +1 -1
- data/lib/wechat/api.rb +13 -0
- data/lib/wechat/api/client.rb +15 -6
- data/lib/wechat/api/js_ticket.rb +22 -0
- data/lib/wechat/api/util.rb +21 -2
- data/lib/wechat/api/version.rb +1 -1
- data/lib/wechat/qy.rb +10 -0
- data/lib/wechat/qy/client.rb +117 -0
- data/lib/wechat/qy/message.rb +16 -0
- data/spec/wechat/api/js_ticket_spec.rb +18 -0
- data/spec/wechat/api/util_spec.rb +19 -0
- data/wechat-api.gemspec +11 -11
- metadata +54 -68
- data/.travis.yml +0 -5
- data/lib/wechat/pay.rb +0 -7
- data/lib/wechat/pay/client.rb +0 -116
- data/lib/wechat/pay/redpack.rb +0 -39
- data/spec/wechat/pay/client_spec.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 16a6bc7196eabd9a9a07d4ae092757cbf20d32a2bd851da21ea75d5589bb035a
|
4
|
+
data.tar.gz: f4cb5aefdcc2c8536d76e6ded2c2495bf543253898c8cf81cedac4839ae1bc4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd5e2d6b94f108e7ac1ad741b61f1ac4a426dc1b535fe30d08c505260e9d6e5f8cfeb1a90088e246506ebdeae43cd4e2261eeb326cfba0302c12ae2c60d78261
|
7
|
+
data.tar.gz: 8fdf75fe1d2ec7c09b512e2243f143e8c3308aa7fb738943b8f43c327c87667eb6e6cf908e4eb40cb783c6fe59c770386b981f9e528a6206ec5130c79334d49c
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -4,8 +4,7 @@ https://github.com/lazing/wechat-api
|
|
4
4
|
|
5
5
|
用于微信 api 调用(非服务端推送信息)的处理。
|
6
6
|
|
7
|
-
[](http://badge.fury.io/rb/ucpaas)
|
7
|
+
[](http://badge.fury.io/rb/wechat-api)
|
9
8
|
|
10
9
|
常见的应用场景如:
|
11
10
|
* 获取关注用户
|
@@ -21,6 +20,12 @@ https://github.com/lazing/wechat-api
|
|
21
20
|
* 不够简单,特别是用于多账号管理的时候
|
22
21
|
* 微信 api 更新频繁,需要易于使用新功能
|
23
22
|
|
23
|
+
## 主要功能清单
|
24
|
+
* 支持微信服务号和订阅号API
|
25
|
+
* 支持微信js_ticket获取
|
26
|
+
* 支持微信红包API
|
27
|
+
* 支持微信企业号主动调用API
|
28
|
+
|
24
29
|
## 使用方式
|
25
30
|
|
26
31
|
````ruby
|
@@ -30,15 +35,25 @@ gem 'wechat-api'
|
|
30
35
|
````ruby
|
31
36
|
require 'wechat-api'
|
32
37
|
|
33
|
-
|
38
|
+
api = Wechat::Api::Client.new 'appid', 'appsecret'
|
34
39
|
|
35
|
-
|
40
|
+
api.get 'user/info', nextopenid: 'xxx'
|
36
41
|
# 当使用 get 方式时,hash 参数将做个 query params 附在请求后
|
37
42
|
|
38
|
-
|
43
|
+
api.post 'user/info/updateremark', openid;'xxxx', remark: '我是注释'
|
39
44
|
# 当使用 post 方法时,hash 参数将转换为 json,因此可以支持嵌套的结构
|
40
45
|
````
|
41
46
|
|
47
|
+
微信企业号主动调用API
|
48
|
+
|
49
|
+
````ruby
|
50
|
+
require 'wechat-api'
|
51
|
+
|
52
|
+
qy = Wechat::Qy::Client.new 'corpid', 'corpsecret'
|
53
|
+
qy.text_send 'agentid', 'message', touser: 'UserId1|UserId2' #提供的预定义方法
|
54
|
+
qy.post 'api/uri', { key: :value} #未提供预定义方法时调用
|
55
|
+
````
|
56
|
+
|
42
57
|
## 一些预定义方法
|
43
58
|
|
44
59
|
预定义接口方法可以方便使用。
|
@@ -47,13 +62,13 @@ client.post 'user/info/updateremark', openid;'xxxx', remark: '我是注释'
|
|
47
62
|
|
48
63
|
````ruby
|
49
64
|
# 创建固定二维码
|
50
|
-
|
65
|
+
api.create_qrcode(scene_str)
|
51
66
|
|
52
67
|
# 创建临时二维码
|
53
|
-
|
68
|
+
api.create_qrcode_temp(scene_id)
|
54
69
|
|
55
70
|
# 发送模板消息
|
56
|
-
|
71
|
+
api.send_template(template_id, openid, url, data = {})
|
57
72
|
````
|
58
73
|
|
59
74
|
以上接口返回微信文档定义的 json 数据转换后的ruby的 hash 对象
|
data/lib/wechat-api.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
require 'wechat/api'
|
2
|
-
require 'wechat/
|
2
|
+
require 'wechat/qy'
|
data/lib/wechat/api.rb
CHANGED
@@ -4,5 +4,18 @@ require 'wechat/api/client'
|
|
4
4
|
module Wechat
|
5
5
|
#
|
6
6
|
module Api
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
def self.client(appid = 'origin_id')
|
10
|
+
var = "@_client_#{appid}"
|
11
|
+
if instance_variable_defined?(var)
|
12
|
+
instance_variable_get(var)
|
13
|
+
elsif block_given?
|
14
|
+
c = yield(Client)
|
15
|
+
instance_variable_set var, c
|
16
|
+
else
|
17
|
+
raise Error, :not_initialized
|
18
|
+
end
|
19
|
+
end
|
7
20
|
end
|
8
21
|
end
|
data/lib/wechat/api/client.rb
CHANGED
@@ -2,6 +2,7 @@ require 'multi_json'
|
|
2
2
|
require 'wechat/api/message'
|
3
3
|
require 'wechat/api/user'
|
4
4
|
require 'wechat/api/util'
|
5
|
+
require 'wechat/api/js_ticket'
|
5
6
|
require 'faraday'
|
6
7
|
require 'logger'
|
7
8
|
|
@@ -18,12 +19,13 @@ module Wechat
|
|
18
19
|
|
19
20
|
API_BASE = 'https://api.weixin.qq.com/cgi-bin/'
|
20
21
|
|
21
|
-
attr_reader :app_id, :secret
|
22
|
-
attr_accessor :logger
|
22
|
+
attr_reader :app_id, :secret, :ticket
|
23
|
+
attr_accessor :logger, :site
|
23
24
|
|
24
25
|
def initialize(app_id, secret)
|
25
26
|
@app_id, @secret = app_id, secret
|
26
27
|
@logger = Logger.new(STDOUT)
|
28
|
+
@ticket = JsTicket.new self
|
27
29
|
@token_file = File.join('/tmp', "wechat-api-#{app_id}")
|
28
30
|
end
|
29
31
|
|
@@ -37,10 +39,11 @@ module Wechat
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def refresh
|
40
|
-
url = format('%stoken',
|
42
|
+
url = format('%stoken', base_url)
|
41
43
|
resp = connection.get(url, token_params)
|
42
44
|
response = MultiJson.load(resp.body)
|
43
45
|
return handle_error(response) if response['errcode']
|
46
|
+
|
44
47
|
@access_token = response['access_token']
|
45
48
|
File.open(@token_file, 'w') { |f| f.write(resp.body) } if @access_token
|
46
49
|
@access_token
|
@@ -72,9 +75,11 @@ module Wechat
|
|
72
75
|
end
|
73
76
|
|
74
77
|
def with_access_token(uri, params, tried = 2)
|
75
|
-
url = format('%s%s',
|
78
|
+
url = format('%s%s', base_url, uri)
|
76
79
|
begin
|
77
80
|
resp = yield(url, params.merge(access_token: access_token))
|
81
|
+
raise ResponseError, resp unless resp.success?
|
82
|
+
|
78
83
|
response = MultiJson.load(resp.body)
|
79
84
|
handle_error(response)
|
80
85
|
rescue AccessTokenExpiredError => e
|
@@ -86,6 +91,10 @@ module Wechat
|
|
86
91
|
|
87
92
|
private
|
88
93
|
|
94
|
+
def base_url
|
95
|
+
site || API_BASE
|
96
|
+
end
|
97
|
+
|
89
98
|
def debug_request
|
90
99
|
response = yield
|
91
100
|
logger.debug { response }
|
@@ -97,9 +106,9 @@ module Wechat
|
|
97
106
|
when 0, nil
|
98
107
|
response
|
99
108
|
when 40_001, 42_001, 40_014
|
100
|
-
|
109
|
+
raise AccessTokenExpiredError, response
|
101
110
|
else
|
102
|
-
|
111
|
+
raise ResponseError, response
|
103
112
|
end
|
104
113
|
end
|
105
114
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Wechat
|
2
|
+
module Api
|
3
|
+
|
4
|
+
class JsTicket
|
5
|
+
attr_reader :client, :ticket, :expires_at
|
6
|
+
def initialize(client)
|
7
|
+
@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
def refresh
|
11
|
+
js = client.js_ticket
|
12
|
+
@ticket = js['ticket']
|
13
|
+
@expires_at = DateTime.now + Rational(js['expires_in'].to_i, 3600 * 24)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def expired?
|
18
|
+
expires_at.nil? || DateTime.now > expires_at
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/wechat/api/util.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
1
4
|
module Wechat
|
2
5
|
module Api
|
3
6
|
#
|
@@ -29,8 +32,24 @@ module Wechat
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def js_ticket
|
32
|
-
|
33
|
-
|
35
|
+
get 'ticket/getticket', type: :jsapi
|
36
|
+
end
|
37
|
+
|
38
|
+
def sign(params)
|
39
|
+
str = params.to_a.sort.map { |p| p.join('=') }.join('&')
|
40
|
+
logger.debug { "to_sign: #{str}" }
|
41
|
+
Digest::SHA1.hexdigest str
|
42
|
+
end
|
43
|
+
|
44
|
+
def js_config(url: nil)
|
45
|
+
r = ticket.tap { |j| j.refresh if j.expired? }.ticket
|
46
|
+
nonce = SecureRandom.hex
|
47
|
+
timestamp = Time.now.to_i
|
48
|
+
signature = sign(noncestr: nonce, timestamp: timestamp, jsapi_ticket: r, url: url)
|
49
|
+
{
|
50
|
+
appId: app_id, timestamp: timestamp, nonceStr: nonce,
|
51
|
+
signature: signature
|
52
|
+
}.tap { |hash| logger.debug { "js_config: #{hash}, url: #{url}" } }
|
34
53
|
end
|
35
54
|
end
|
36
55
|
end
|
data/lib/wechat/api/version.rb
CHANGED
data/lib/wechat/qy.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'faraday'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Wechat
|
6
|
+
module Qy
|
7
|
+
#
|
8
|
+
class ResponseError < StandardError; end
|
9
|
+
class AccessTokenExpiredError < ResponseError; end
|
10
|
+
#
|
11
|
+
class Client
|
12
|
+
|
13
|
+
API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin/'
|
14
|
+
|
15
|
+
attr_reader :corp_id, :corp_secret
|
16
|
+
attr_accessor :logger
|
17
|
+
|
18
|
+
def initialize(corp_id, corp_secret)
|
19
|
+
@corp_id, @corp_secret = corp_id, corp_secret
|
20
|
+
@logger = Logger.new(STDOUT)
|
21
|
+
@token_file = File.join('/tmp', "wechat-api-#{corp_id}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def access_token
|
25
|
+
@access_token ||= begin
|
26
|
+
token = MultiJson.load(File.read(@token_file))
|
27
|
+
token['access_token']
|
28
|
+
rescue
|
29
|
+
refresh
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def refresh
|
34
|
+
url = format('%sgettoken', API_BASE)
|
35
|
+
resp = connection.get(url, token_params)
|
36
|
+
response = MultiJson.load(resp.body)
|
37
|
+
return handle_error(response) if response['errcode']
|
38
|
+
@access_token = response['access_token']
|
39
|
+
File.open(@token_file, 'w') { |f| f.write(resp.body) } if @access_token
|
40
|
+
@access_token
|
41
|
+
end
|
42
|
+
|
43
|
+
def get(uri, params = {})
|
44
|
+
with_access_token(uri, params) do |url, params_with_token|
|
45
|
+
debug_request do
|
46
|
+
connection.get do |req|
|
47
|
+
req.url url, params_with_token
|
48
|
+
req.headers[:accept] = 'application/json'
|
49
|
+
req.headers[:content_type] = 'application/json'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def post(uri, data = {})
|
56
|
+
with_access_token(uri, {}) do |url, params|
|
57
|
+
logger.debug { [:data, data] }
|
58
|
+
debug_request do
|
59
|
+
connection.post do |req|
|
60
|
+
req.url url, params
|
61
|
+
req.headers[:accept] = 'application/json'
|
62
|
+
req.headers[:content_type] = 'application/json'
|
63
|
+
req.body = MultiJson.dump(data)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def with_access_token(uri, params, tried = 2)
|
70
|
+
url = format('%s%s', API_BASE, uri)
|
71
|
+
begin
|
72
|
+
resp = yield(url, params.merge(access_token: access_token))
|
73
|
+
response = MultiJson.load(resp.body)
|
74
|
+
handle_error(response)
|
75
|
+
rescue AccessTokenExpiredError => e
|
76
|
+
refresh
|
77
|
+
retry unless (tried -= 1).zero?
|
78
|
+
raise e
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def debug_request
|
85
|
+
response = yield
|
86
|
+
logger.debug { response }
|
87
|
+
response
|
88
|
+
end
|
89
|
+
|
90
|
+
def handle_error(response)
|
91
|
+
case response['errcode']
|
92
|
+
when 0, nil
|
93
|
+
response
|
94
|
+
when 40_001, 42_001, 40_014
|
95
|
+
fail AccessTokenExpiredError, response
|
96
|
+
else
|
97
|
+
fail ResponseError, response
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def token_params
|
102
|
+
{
|
103
|
+
corpid: corp_id,
|
104
|
+
corpsecret: corp_secret
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def connection
|
109
|
+
@connection ||= begin
|
110
|
+
Faraday.new do |faraday|
|
111
|
+
faraday.adapter Faraday.default_adapter
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Wechat
|
2
|
+
module Qy
|
3
|
+
module Message
|
4
|
+
# text_send('appid', 'hello', touser: 'User1|User2')
|
5
|
+
def text_send(agent_id, content, data = {})
|
6
|
+
params = {
|
7
|
+
msgtype: :text,
|
8
|
+
agentid: agent_id,
|
9
|
+
safe: 0,
|
10
|
+
text: { content: content }
|
11
|
+
}.merge(data)
|
12
|
+
post 'message/send', params
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
RSpec.describe Wechat::Api::JsTicket do
|
4
|
+
|
5
|
+
let(:client) { double(:client) }
|
6
|
+
subject do
|
7
|
+
Wechat::Api::JsTicket.new client
|
8
|
+
end
|
9
|
+
|
10
|
+
it :refresh do
|
11
|
+
allow(client).to receive(:js_ticket).and_return(expires_in: 7200, ticket: 'bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA')
|
12
|
+
subject.refresh
|
13
|
+
expect(subject.ticket).not_to be_nil
|
14
|
+
should_not be_expired
|
15
|
+
expect(subject.expires_at).to be_a(Date)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
RSpec.describe Wechat::Api::Util do
|
4
|
+
subject { Wechat::Api::Client.new('APPID', 'APPSECRET') }
|
5
|
+
|
6
|
+
before do
|
7
|
+
subject.logger.level = Logger::DEBUG
|
8
|
+
end
|
9
|
+
|
10
|
+
it :js_config do
|
11
|
+
allow(SecureRandom).to receive(:hex).and_return('Wm3WZYTPz0wzccnW')
|
12
|
+
allow_any_instance_of(Time).to receive(:to_i).and_return(1414587457)
|
13
|
+
allow(subject).to receive(:js_ticket).and_return(expires_in: 7200, ticket: 'sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg')
|
14
|
+
js = subject.js_config(url: 'http://mp.weixin.qq.com?params=value')
|
15
|
+
expect(js).not_to be_nil
|
16
|
+
expect(js).to include(signature: '0f9de62fce790f9a083d5c99e95740ceb90c27ed')
|
17
|
+
expect(js).to have_key :appId
|
18
|
+
end
|
19
|
+
end
|
data/wechat-api.gemspec
CHANGED
@@ -13,18 +13,18 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
16
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
16
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
17
|
spec.require_paths = ["lib"]
|
19
18
|
|
20
|
-
spec.add_runtime_dependency
|
21
|
-
spec.add_runtime_dependency
|
22
|
-
|
23
|
-
spec.
|
24
|
-
spec.
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
19
|
+
spec.add_runtime_dependency 'faraday', '~> 1.0'
|
20
|
+
spec.add_runtime_dependency 'multi_json', '~> 1.0'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.0'
|
23
|
+
spec.add_development_dependency 'guard', '~> 2.0'
|
24
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.0'
|
25
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
+
spec.add_development_dependency 'rspec-its', '~> 1.0'
|
28
|
+
spec.add_development_dependency 'webmock', '~> 3.0'
|
29
|
+
|
30
30
|
end
|
metadata
CHANGED
@@ -1,156 +1,142 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wechat-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Wong
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0.9'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0.9'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rest-client
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
17
|
+
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
19
|
+
version: '1.0'
|
34
20
|
type: :runtime
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
|
-
- - "
|
24
|
+
- - "~>"
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.
|
26
|
+
version: '1.0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: multi_json
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
|
-
- - "
|
31
|
+
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
33
|
+
version: '1.0'
|
48
34
|
type: :runtime
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
|
-
- - "
|
38
|
+
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
40
|
+
version: '1.0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: bundler
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
|
-
- - "
|
45
|
+
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.0
|
62
|
-
type: :
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
|
-
- - "
|
52
|
+
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.0
|
54
|
+
version: '1.0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
56
|
+
name: guard
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
|
-
- - "
|
59
|
+
- - "~>"
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :
|
61
|
+
version: '2.0'
|
62
|
+
type: :development
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
|
-
- - "
|
66
|
+
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
68
|
+
version: '2.0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
70
|
+
name: guard-rspec
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
|
-
- - "
|
73
|
+
- - "~>"
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
75
|
+
version: '4.0'
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
|
-
- - "
|
80
|
+
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
82
|
+
version: '4.0'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: rake
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
100
86
|
requirements:
|
101
|
-
- - "
|
87
|
+
- - "~>"
|
102
88
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
89
|
+
version: '13.0'
|
104
90
|
type: :development
|
105
91
|
prerelease: false
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
107
93
|
requirements:
|
108
|
-
- - "
|
94
|
+
- - "~>"
|
109
95
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
96
|
+
version: '13.0'
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
98
|
+
name: rspec
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
114
100
|
requirements:
|
115
|
-
- - "
|
101
|
+
- - "~>"
|
116
102
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
103
|
+
version: '3.0'
|
118
104
|
type: :development
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
107
|
requirements:
|
122
|
-
- - "
|
108
|
+
- - "~>"
|
123
109
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
110
|
+
version: '3.0'
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
|
-
name: rspec
|
112
|
+
name: rspec-its
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
128
114
|
requirements:
|
129
115
|
- - "~>"
|
130
116
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
117
|
+
version: '1.0'
|
132
118
|
type: :development
|
133
119
|
prerelease: false
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
135
121
|
requirements:
|
136
122
|
- - "~>"
|
137
123
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
124
|
+
version: '1.0'
|
139
125
|
- !ruby/object:Gem::Dependency
|
140
126
|
name: webmock
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
142
128
|
requirements:
|
143
129
|
- - "~>"
|
144
130
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
131
|
+
version: '3.0'
|
146
132
|
type: :development
|
147
133
|
prerelease: false
|
148
134
|
version_requirements: !ruby/object:Gem::Requirement
|
149
135
|
requirements:
|
150
136
|
- - "~>"
|
151
137
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
153
|
-
description:
|
138
|
+
version: '3.0'
|
139
|
+
description:
|
154
140
|
email:
|
155
141
|
- lazing@gmail.com
|
156
142
|
executables: []
|
@@ -159,7 +145,6 @@ extra_rdoc_files: []
|
|
159
145
|
files:
|
160
146
|
- ".gitignore"
|
161
147
|
- ".rspec"
|
162
|
-
- ".travis.yml"
|
163
148
|
- Gemfile
|
164
149
|
- Guardfile
|
165
150
|
- README.md
|
@@ -167,22 +152,24 @@ files:
|
|
167
152
|
- lib/wechat-api.rb
|
168
153
|
- lib/wechat/api.rb
|
169
154
|
- lib/wechat/api/client.rb
|
155
|
+
- lib/wechat/api/js_ticket.rb
|
170
156
|
- lib/wechat/api/message.rb
|
171
157
|
- lib/wechat/api/user.rb
|
172
158
|
- lib/wechat/api/util.rb
|
173
159
|
- lib/wechat/api/version.rb
|
174
|
-
- lib/wechat/
|
175
|
-
- lib/wechat/
|
176
|
-
- lib/wechat/
|
160
|
+
- lib/wechat/qy.rb
|
161
|
+
- lib/wechat/qy/client.rb
|
162
|
+
- lib/wechat/qy/message.rb
|
177
163
|
- spec/spec_helper.rb
|
178
164
|
- spec/wechat/api/client_spec.rb
|
179
|
-
- spec/wechat/
|
165
|
+
- spec/wechat/api/js_ticket_spec.rb
|
166
|
+
- spec/wechat/api/util_spec.rb
|
180
167
|
- wechat-api.gemspec
|
181
168
|
homepage: https://github.com/lazing/wechat-api
|
182
169
|
licenses:
|
183
170
|
- MIT
|
184
171
|
metadata: {}
|
185
|
-
post_install_message:
|
172
|
+
post_install_message:
|
186
173
|
rdoc_options: []
|
187
174
|
require_paths:
|
188
175
|
- lib
|
@@ -197,13 +184,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
184
|
- !ruby/object:Gem::Version
|
198
185
|
version: '0'
|
199
186
|
requirements: []
|
200
|
-
|
201
|
-
|
202
|
-
signing_key:
|
187
|
+
rubygems_version: 3.0.6
|
188
|
+
signing_key:
|
203
189
|
specification_version: 4
|
204
190
|
summary: Wechat API wrapper
|
205
191
|
test_files:
|
206
192
|
- spec/spec_helper.rb
|
207
193
|
- spec/wechat/api/client_spec.rb
|
208
|
-
- spec/wechat/
|
209
|
-
|
194
|
+
- spec/wechat/api/js_ticket_spec.rb
|
195
|
+
- spec/wechat/api/util_spec.rb
|
data/.travis.yml
DELETED
data/lib/wechat/pay.rb
DELETED
data/lib/wechat/pay/client.rb
DELETED
@@ -1,116 +0,0 @@
|
|
1
|
-
require 'rest-client'
|
2
|
-
require 'logger'
|
3
|
-
require 'multi_json'
|
4
|
-
require 'openssl'
|
5
|
-
require 'nori'
|
6
|
-
require 'gyoku'
|
7
|
-
|
8
|
-
require 'wechat/pay/redpack'
|
9
|
-
|
10
|
-
module Wechat
|
11
|
-
module Pay
|
12
|
-
class PayError < StandardError; end
|
13
|
-
class NoAuthError < PayError; end
|
14
|
-
class NotEnoughError < PayError; end
|
15
|
-
class TimeLimitedError < PayError; end
|
16
|
-
class MoneyLimitedError < PayError; end
|
17
|
-
|
18
|
-
#
|
19
|
-
class Client
|
20
|
-
include Redpack
|
21
|
-
BASE_URL = 'https://api.mch.weixin.qq.com'
|
22
|
-
REQUIRED_OPTS = %w(key password cert sign_key).map(&:to_sym).freeze
|
23
|
-
ERRORS = {
|
24
|
-
'NO_AUTH' => NoAuthError,
|
25
|
-
'NOTENOUGH' => NotEnoughError,
|
26
|
-
'TIME_LIMITED' => TimeLimitedError,
|
27
|
-
'MONEY_LIMITED' => MoneyLimitedError
|
28
|
-
}
|
29
|
-
|
30
|
-
attr_accessor :logger
|
31
|
-
|
32
|
-
def initialize(mch_id, wxappid, opts = {})
|
33
|
-
@mch_id = mch_id
|
34
|
-
@wxappid = wxappid
|
35
|
-
@opts = Hash[opts.map { |k, v| [k.to_sym, v] }]
|
36
|
-
unless (REQUIRED_OPTS - @opts.keys).empty?
|
37
|
-
fail format('%s required', REQUIRED_OPTS.join(','))
|
38
|
-
end
|
39
|
-
@logger = Logger.new(STDOUT)
|
40
|
-
rsa_setup
|
41
|
-
@parser = Nori.new
|
42
|
-
end
|
43
|
-
|
44
|
-
def post(path, params)
|
45
|
-
merged_params = merge(params)
|
46
|
-
logger.debug { merged_params }
|
47
|
-
resp = resource(path).post(xml(sign(merged_params)))
|
48
|
-
handle(resp)
|
49
|
-
rescue RestClient::ExceptionWithResponse => err
|
50
|
-
raise PayError, err.response
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def handle(resp)
|
56
|
-
response = parser.parse(resp)
|
57
|
-
check(response)
|
58
|
-
response
|
59
|
-
end
|
60
|
-
|
61
|
-
def check(r)
|
62
|
-
return if r['xml']['result_code'] == 'SUCCESS'
|
63
|
-
handle_error(r['xml']['err_code'], r)
|
64
|
-
end
|
65
|
-
|
66
|
-
def handle_error(error_code, response)
|
67
|
-
fail ERRORS[error_code] || PayError, response.inspect
|
68
|
-
end
|
69
|
-
|
70
|
-
def rsa_setup
|
71
|
-
@rsa_key = OpenSSL::PKey::RSA.new @opts[:key], @opts[:password]
|
72
|
-
@rsa_cert = OpenSSL::X509::Certificate.new @opts[:cert]
|
73
|
-
rescue StandardError => e
|
74
|
-
logger.error { e.inspect }
|
75
|
-
end
|
76
|
-
|
77
|
-
def resource(path)
|
78
|
-
RestClient.log = logger
|
79
|
-
RestClient::Resource.new\
|
80
|
-
[BASE_URL, path].join,
|
81
|
-
ssl_client_key: @rsa_key,
|
82
|
-
ssl_client_cert: @rsa_cert,
|
83
|
-
verify_ssl: OpenSSL::SSL::VERIFY_NONE
|
84
|
-
end
|
85
|
-
|
86
|
-
def xml(hash)
|
87
|
-
Gyoku.xml({ xml: hash }, key_converter: :none)
|
88
|
-
end
|
89
|
-
|
90
|
-
def sign(params)
|
91
|
-
ordered = trim_and_sort(params)
|
92
|
-
keystr = format('key=%s', @opts[:sign_key])
|
93
|
-
origin =
|
94
|
-
ordered.map { |k, v| [k, v].join('=') }.push(keystr).join('&')
|
95
|
-
sign = Digest::MD5.hexdigest(origin).upcase
|
96
|
-
logger.debug { format('origin: %s, sign: %s', origin, sign) }
|
97
|
-
params.merge(sign: sign)
|
98
|
-
end
|
99
|
-
|
100
|
-
def trim_and_sort(params)
|
101
|
-
params.delete_if { |_k, v| v.blank? }
|
102
|
-
Hash[params.sort]
|
103
|
-
end
|
104
|
-
|
105
|
-
def merge(params)
|
106
|
-
params.reverse_merge\
|
107
|
-
mch_id: @mch_id,
|
108
|
-
nonce_str: nonce_str
|
109
|
-
end
|
110
|
-
|
111
|
-
def nonce_str
|
112
|
-
SecureRandom.hex
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
data/lib/wechat/pay/redpack.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
|
3
|
-
module Wechat
|
4
|
-
module Pay
|
5
|
-
#
|
6
|
-
module Redpack
|
7
|
-
def redpack(transaction_id, openid, params = {})
|
8
|
-
post\
|
9
|
-
'/mmpaymkttransfers/sendredpack',
|
10
|
-
params.merge(
|
11
|
-
mch_billno: tran_id(transaction_id),
|
12
|
-
wxappid: @wxappid, re_openid: openid
|
13
|
-
)
|
14
|
-
end
|
15
|
-
|
16
|
-
def group_redpack(transaction_id, openid, params = {})
|
17
|
-
post\
|
18
|
-
'/mmpaymkttransfers/sendgroupredpack',
|
19
|
-
params.merge(
|
20
|
-
mch_billno: tran_id(transaction_id),
|
21
|
-
wxappid: @wxappid, re_openid: openid
|
22
|
-
)
|
23
|
-
end
|
24
|
-
|
25
|
-
def redpack_info(transaction_id)
|
26
|
-
post\
|
27
|
-
'/mmpaymkttransfers/gethbinfo',
|
28
|
-
mch_billno: tran_id(transaction_id),
|
29
|
-
bill_type: 'MCHT', appid: @wxappid
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def tran_id(origin)
|
35
|
-
format('%s%s', @mch_id, origin)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
-
|
3
|
-
RSpec.describe Wechat::Pay::Client do
|
4
|
-
subject do
|
5
|
-
described_class.new\
|
6
|
-
'mch_id', 'wxappid',
|
7
|
-
key: 'key', password: 'password', cert: 'cert',
|
8
|
-
sign_key: 'sign_key'
|
9
|
-
end
|
10
|
-
|
11
|
-
it :trim_and_sort do
|
12
|
-
expect(
|
13
|
-
subject.send(:trim_and_sort, b: 2, c: 3, a: 1).values
|
14
|
-
).to start_with(1)
|
15
|
-
end
|
16
|
-
|
17
|
-
it :sign do
|
18
|
-
expect(subject.send(:sign, b: 2, a: 1)).to have_key(:sign)
|
19
|
-
end
|
20
|
-
|
21
|
-
it :xml do
|
22
|
-
expect(subject.send(:xml, b_1: 2, a: 1)).to match(/xml/)
|
23
|
-
end
|
24
|
-
end
|