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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8a1da7465e7436ff9a4d1f959f198a63ffc3e028
4
- data.tar.gz: 4bfc9ba3e18733fe9146679b32b295665934d2d8
2
+ SHA256:
3
+ metadata.gz: 16a6bc7196eabd9a9a07d4ae092757cbf20d32a2bd851da21ea75d5589bb035a
4
+ data.tar.gz: f4cb5aefdcc2c8536d76e6ded2c2495bf543253898c8cf81cedac4839ae1bc4f
5
5
  SHA512:
6
- metadata.gz: d79b3b01a2fbb922ea6019af273d12244cdda7e15bc250e30f648a2c9c7a94c76f8759619304edb91f911563a5c5e4e47ff62c035e70bab10bdd00abaaab2bf7
7
- data.tar.gz: 4a7aba85ce5c46fb22b8101c25b97c28330b9795db8cbeaf5acb2fc1da5fd2526ae3fbef4279bf7b520c0454ca1392ca76ff1aae4f024bfcff5d1fdc3fe36ec4
6
+ metadata.gz: fd5e2d6b94f108e7ac1ad741b61f1ac4a426dc1b535fe30d08c505260e9d6e5f8cfeb1a90088e246506ebdeae43cd4e2261eeb326cfba0302c12ae2c60d78261
7
+ data.tar.gz: 8fdf75fe1d2ec7c09b512e2243f143e8c3308aa7fb738943b8f43c327c87667eb6e6cf908e4eb40cb783c6fe59c770386b981f9e528a6206ec5130c79334d49c
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  Gemfile.lock
2
2
  pkg
3
+ .DS_Store
data/Gemfile CHANGED
@@ -1,9 +1,3 @@
1
- source 'https://ruby.taobao.org'
1
+ source 'https://rubygems.org'
2
2
  # Specify your gem's dependencies in ucpaas.gemspec
3
3
  gemspec
4
-
5
- group :development do
6
- gem 'guard'
7
- gem 'guard-rspec'
8
- gem 'rspec-its'
9
- end
data/README.md CHANGED
@@ -4,8 +4,7 @@ https://github.com/lazing/wechat-api
4
4
 
5
5
  用于微信 api 调用(非服务端推送信息)的处理。
6
6
 
7
- [![Circle CI](https://circleci.com/gh/lazing/wechat-api.svg?style=svg)](https://circleci.com/gh/lazing/wechat-api)
8
- [![Gem Version](https://badge.fury.io/rb/wechat-api.svg)](http://badge.fury.io/rb/ucpaas)
7
+ [![Gem Version](https://badge.fury.io/rb/wechat-api.svg)](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
- client = Wechat::Api::Client.new 'appid', 'appsecret'
38
+ api = Wechat::Api::Client.new 'appid', 'appsecret'
34
39
 
35
- client.get 'user/info', nextopenid: 'xxx'
40
+ api.get 'user/info', nextopenid: 'xxx'
36
41
  # 当使用 get 方式时,hash 参数将做个 query params 附在请求后
37
42
 
38
- client.post 'user/info/updateremark', openid;'xxxx', remark: '我是注释'
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
- client.create_qrcode(scene_str)
65
+ api.create_qrcode(scene_str)
51
66
 
52
67
  # 创建临时二维码
53
- client.create_qrcode_temp(scene_id)
68
+ api.create_qrcode_temp(scene_id)
54
69
 
55
70
  # 发送模板消息
56
- client.send_template(template_id, openid, url, data = {})
71
+ api.send_template(template_id, openid, url, data = {})
57
72
  ````
58
73
 
59
74
  以上接口返回微信文档定义的 json 数据转换后的ruby的 hash 对象
@@ -1,2 +1,2 @@
1
1
  require 'wechat/api'
2
- require 'wechat/pay'
2
+ require 'wechat/qy'
@@ -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
@@ -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', API_BASE)
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', API_BASE, uri)
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
- fail AccessTokenExpiredError, response
109
+ raise AccessTokenExpiredError, response
101
110
  else
102
- fail ResponseError, response
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
@@ -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
- res = get 'ticket/getticket', type: :jsapi
33
- res['ticket']
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
@@ -2,6 +2,6 @@
2
2
  module Wechat
3
3
  #
4
4
  module Api
5
- VERSION = '0.1.3'
5
+ VERSION = '0.4.1'.freeze
6
6
  end
7
7
  end
@@ -0,0 +1,10 @@
1
+ module Wechat
2
+ #
3
+ module Qy
4
+ end
5
+ end
6
+
7
+ require 'wechat/qy/client'
8
+ require 'wechat/qy/message'
9
+
10
+ ::Wechat::Qy::Client.send :include, ::Wechat::Qy::Message
@@ -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
@@ -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 "faraday", ">= 0.9"
21
- spec.add_runtime_dependency "rest-client", ">= 1.7.0"
22
- spec.add_runtime_dependency "multi_json", ">= 1.2"
23
- spec.add_runtime_dependency 'gyoku', '>= 1.0.0'
24
- spec.add_runtime_dependency 'nori'
25
- spec.add_development_dependency "bundler"
26
- spec.add_development_dependency "rake"
27
- spec.add_development_dependency "pry"
28
- spec.add_development_dependency "rspec", "~> 3.0"
29
- spec.add_development_dependency "webmock", "~> 1.18"
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.3
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: 2015-10-20 00:00:00.000000000 Z
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.7.0
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.7.0
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.2'
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.2'
40
+ version: '1.0'
55
41
  - !ruby/object:Gem::Dependency
56
- name: gyoku
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.0
62
- type: :runtime
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.0
54
+ version: '1.0'
69
55
  - !ruby/object:Gem::Dependency
70
- name: nori
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: :runtime
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: bundler
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: pry
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: '3.0'
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: '3.0'
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: '1.18'
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: '1.18'
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/pay.rb
175
- - lib/wechat/pay/client.rb
176
- - lib/wechat/pay/redpack.rb
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/pay/client_spec.rb
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
- rubyforge_project:
201
- rubygems_version: 2.2.2
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/pay/client_spec.rb
209
- has_rdoc:
194
+ - spec/wechat/api/js_ticket_spec.rb
195
+ - spec/wechat/api/util_spec.rb
@@ -1,5 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - ruby-head
4
- - 2.1.0
5
- - 1.9.3
@@ -1,7 +0,0 @@
1
- require 'wechat/pay/client'
2
-
3
- module Wechat
4
- #
5
- module Pay
6
- end
7
- end
@@ -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
@@ -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