wechat-api 0.1.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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