├── .gitignore
├── .travis.yml
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── lib
├── wx_pay.rb
└── wx_pay
│ ├── result.rb
│ ├── service.rb
│ ├── sign.rb
│ └── version.rb
├── test
├── test_helper.rb
└── wx_pay
│ ├── result_test.rb
│ ├── service_test.rb
│ └── sign_test.rb
└── wx_pay.gemspec
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 | test/dummy/db/*.sqlite3
5 | test/dummy/db/*.sqlite3-journal
6 | test/dummy/log/*.log
7 | test/dummy/tmp/
8 | test/dummy/.sass-cache
9 | *.gem
10 | *.rbc
11 | .bundle
12 | .config
13 | .yardoc
14 | Gemfile.lock
15 | tmp
16 | test/tmp
17 | test/version_tmp
18 | .idea
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | before_install:
3 | - gem install bundler
4 | rvm:
5 | - 2.3.8
6 | - 2.4.5
7 | - 2.5.3
8 | - 2.6.1
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | # Declare your gem's dependencies in tenpay.gemspec.
4 | # Bundler will treat runtime dependencies like base dependencies, and
5 | # development dependencies will be added by default to the :development group.
6 | gemspec
7 | # Declare any dependencies that are still in development here instead of in
8 | # your gemspec. These might include edge Rails or gems from your path or
9 | # Git. Remember to move these dependencies to your gemspec before releasing
10 | # your gem to rubygems.org.
11 |
12 | # To use debugger
13 | # gem 'debugger'
14 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Jasl
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WxPay
2 |
3 | A simple Wechat pay ruby gem, without unnecessary magic or wrapper.
4 | copied from [alipay](https://github.com/chloerei/alipay) .
5 |
6 | Please read official document first: https://pay.weixin.qq.com/wiki/doc/api/index.html.
7 |
8 | [](https://travis-ci.org/jasl/wx_pay)
9 |
10 | ## Installation
11 |
12 | Add this line to your Gemfile:
13 |
14 | ```ruby
15 | gem 'wx_pay'
16 | ```
17 |
18 | or development version
19 |
20 | ```ruby
21 | gem 'wx_pay', :github => 'jasl/wx_pay'
22 | ```
23 |
24 | And then execute:
25 |
26 | ```sh
27 | $ bundle
28 | ```
29 |
30 | ## Usage
31 |
32 | ### Config
33 |
34 | Create `config/initializers/wx_pay.rb` and put following configurations into it.
35 |
36 | ```ruby
37 | # required
38 | WxPay.appid = 'YOUR_APPID'
39 | WxPay.key = 'YOUR_KEY'
40 | WxPay.mch_id = 'YOUR_MCH_ID' # required type is String, otherwise there will be cases where JS_PAY can pay but the APP cannot pay
41 | WxPay.debug_mode = true # default is `true`
42 |
43 | # cert, see https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
44 | # using PCKS12
45 | WxPay.set_apiclient_by_pkcs12(File.read(pkcs12_filepath), cert_password)
46 |
47 | # if you want to use `generate_authorize_req` and `authenticate`
48 | WxPay.appsecret = 'YOUR_SECRET'
49 |
50 | # optional - configurations for RestClient timeout, etc.
51 | WxPay.extra_rest_client_options = {timeout: 2, open_timeout: 3}
52 | ```
53 |
54 | ~~If you need to use sandbox mode.~~
55 | (Please be aware, the WeChat has aborted the sandbox env already)
56 | ```ruby
57 | WxPay.appid = 'YOUR_APPID'
58 | WxPay.mch_id = 'YOUR_MCH_ID' # required type is String, otherwise there will be cases where JS_PAY can pay but the APP cannot pay
59 | WxPay.debug_mode = true # default is `true`
60 | WxPay.sandbox_mode = true # default is `false`
61 | result = WxPay::Service.get_sandbox_signkey
62 | WxPay.key = result['sandbox_signkey']
63 |
64 | ```
65 |
66 | Note: You should create your APIKEY (Link to [微信商户平台](https://pay.weixin.qq.com/index.php/home/login)) first if you haven't, and pay attention that **the length of the APIKEY should be 32**.
67 |
68 | ### APIs
69 |
70 | **Check official document for detailed request params and return fields**
71 |
72 | #### unifiedorder
73 |
74 | WxPay supports MWEB, JSAPI, NATIVE and APP.
75 |
76 | ```ruby
77 | # required fields
78 | params = {
79 | body: '测试商品',
80 | out_trade_no: 'test003',
81 | total_fee: 1,
82 | spbill_create_ip: '127.0.0.1',
83 | notify_url: 'http://making.dev/notify',
84 | trade_type: 'JSAPI', # could be "MWEB", ""JSAPI", "NATIVE" or "APP",
85 | openid: 'OPENID' # required when trade_type is `JSAPI`
86 | }
87 | ```
88 |
89 | `WxPay::Service.invoke_unifiedorder params` will create an payment request and return a WxPay::Result instance(subclass of Hash) contains parsed result.
90 |
91 | If your trade type is "MWEB", the result would be like this.
92 |
93 | ```ruby
94 | r = WxPay::Service.invoke_unifiedorder params
95 | # => {
96 | # "return_code"=>"SUCCESS",
97 | # "return_msg"=>"OK",
98 | # "appid"=>"YOUR APPID",
99 | # "mch_id"=>"YOUR MCH_ID",
100 | # "nonce_str"=>"8RN7YfTZ3OUgWX5e",
101 | # "sign"=>"623AE90C9679729DDD7407DC7A1151B2",
102 | # "result_code"=>"SUCCESS",
103 | # "prepay_id"=>"wx2014111104255143b7605afb0314593866",
104 | # "mweb_url"=>"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx2016121516420242444321ca0631331346&package=1405458241",
105 | # "trade_type"=>"MWEB"
106 | # }
107 | ```
108 |
109 | If your trade type is "JSAPI", the result would be like this.
110 |
111 | ```ruby
112 | r = WxPay::Service.invoke_unifiedorder params
113 | # => {
114 | # "return_code"=>"SUCCESS",
115 | # "return_msg"=>"OK",
116 | # "appid"=>"YOUR APPID",
117 | # "mch_id"=>"YOUR MCH_ID",
118 | # "nonce_str"=>"8RN7YfTZ3OUgWX5e",
119 | # "sign"=>"623AE90C9679729DDD7407DC7A1151B2",
120 | # "result_code"=>"SUCCESS",
121 | # "prepay_id"=>"wx2014111104255143b7605afb0314593866",
122 | # "trade_type"=>"JSAPI"
123 | # }
124 | ```
125 |
126 | > "JSAPI" requires openid in params,
127 | in most cases I suggest you using [omniauth](https://github.com/omniauth/omniauth) with [omniauth-wechat-oauth2](https://github.com/skinnyworm/omniauth-wechat-oauth2) to resolve this,
128 | but `wx_pay` provides `generate_authorize_url` and `authenticate` to help you get Wechat authorization in simple case.
129 |
130 | If your trade type is "NATIVE", the result would be like this.
131 |
132 | ```ruby
133 | r = WxPay::Service.invoke_unifiedorder params
134 | # => {
135 | # "return_code"=>"SUCCESS",
136 | # "return_msg"=>"OK",
137 | # "appid"=>"YOUR APPID",
138 | # "mch_id"=>"YOUR MCH_ID",
139 | # "nonce_str"=>"8RN7YfTZ3OUgWX5e",
140 | # "sign"=>"623AE90C9679729DDD7407DC7A1151B2",
141 | # "result_code"=>"SUCCESS",
142 | # "prepay_id"=>"wx2014111104255143b7605afb0314593866",
143 | # "code_url"=>"weixin://"
144 | # "trade_type"=>"NATIVE"
145 | # }
146 | ```
147 |
148 | Return true if both `return_code` and `result_code` equal `SUCCESS`
149 |
150 | ```ruby
151 | r.success? # => true
152 | ```
153 |
154 | #### pay request for app
155 |
156 | ```ruby
157 | # required fields
158 | params = {
159 | prepayid: '1101000000140415649af9fc314aa427', # fetch by call invoke_unifiedorder with `trade_type` is `APP`
160 | noncestr: '1101000000140429eb40476f8896f4c9' # must same as given to invoke_unifiedorder
161 | }
162 |
163 | # call generate_app_pay_req
164 | r = WxPay::Service.generate_app_pay_req params
165 | # => {
166 | # appid: 'wxd930ea5d5a258f4f',
167 | # partnerid: '1900000109',
168 | # prepayid: '1101000000140415649af9fc314aa427',
169 | # package: 'Sign=WXPay',
170 | # noncestr: '1101000000140429eb40476f8896f4c9',
171 | # timestamp: '1398746574',
172 | # sign: '7FFECB600D7157C5AA49810D2D8F28BC2811827B'
173 | # }
174 | ```
175 |
176 | #### pay request for JSAPI
177 |
178 | ``` ruby
179 | # required fields
180 | params = {
181 | prepayid: '1101000000140415649af9fc314aa427', # fetch by call invoke_unifiedorder with `trade_type` is `JSAPI`
182 | noncestr: SecureRandom.hex(16),
183 | }
184 |
185 | # call generate_js_pay_req
186 | r = WxPay::Service.generate_js_pay_req params
187 | # {
188 | # "appId": "wx020c5c792c8537de",
189 | # "package": "prepay_id=wx20160902211806a11ccee7a20956539837",
190 | # "nonceStr": "2vS5AJUD7uyaa5h9",
191 | # "timeStamp": "1472822286",
192 | # "signType": "MD5",
193 | # "paySign": "A52433CB75CA8D58B67B2BB45A79AA01"
194 | # }
195 | ```
196 |
197 | #### Notify Process
198 |
199 | A simple example of processing notify for Rails Action Controller.
200 |
201 | ```ruby
202 | # config/routes.rb
203 | post "notify" => "orders#notify"
204 |
205 | # app/controllers/orders_controller.rb
206 |
207 | def notify
208 | result = Hash.from_xml(request.body.read)["xml"]
209 |
210 | if WxPay::Sign.verify?(result)
211 |
212 | # find your order and process the post-paid logic.
213 |
214 | render :xml => {return_code: "SUCCESS"}.to_xml(root: 'xml', dasherize: false)
215 | else
216 | render :xml => {return_code: "FAIL", return_msg: "签名失败"}.to_xml(root: 'xml', dasherize: false)
217 | end
218 | end
219 | ```
220 |
221 | A simple example of processing notify for Grape v1.2.2 .
222 |
223 | ```ruby
224 | # Gemfile
225 | gem 'multi_xml'
226 |
227 | # config/routes.rb
228 | mount WechatPay::Api => '/'
229 |
230 | # app/api/wechat_pay/api.rb
231 | module WechatPay
232 | class Api < Grape::API
233 | content_type :xml, 'text/xml'
234 | format :xml
235 | formatter :xml, lambda { |object, env| object.to_xml(root: 'xml', dasherize: false) }
236 |
237 | post "notify" do
238 | result = params["xml"]
239 | if WxPay::Sign.verify?(result)
240 | # find your order and process the post-paid logic.
241 |
242 | status 200
243 | {return_code: "SUCCESS"}
244 | else
245 | status 200
246 | {return_code: "FAIL", return_msg: "签名失败"}
247 | end
248 | end
249 | end
250 | end
251 | ```
252 |
253 |
254 | ### Integrate with QRCode(二维码)
255 |
256 | Wechat payment integrating with QRCode is a recommended process flow which will bring users comfortable experience. It is recommended to generate QRCode using `rqrcode` and `rqrcode_png`.
257 |
258 | **Example Code** (please make sure that `public/uploads/qrcode` was created):
259 |
260 | ```ruby
261 | r = WxPay::Service.invoke_unifiedorder params
262 | qrcode_png = RQRCode::QRCode.new( r["code_url"], :size => 5, :level => :h ).to_img.resize(200, 200).save("public/uploads/qrcode/#{@order.id.to_s}_#{Time.now.to_i.to_s}.png")
263 | @qrcode_url = "/uploads/qrcode/#{@order.id.to_s}_#{Time.now.to_i.to_s}.png"
264 | ```
265 |
266 | ### More
267 |
268 | No documents yet, check `lib/wx_pay/service.rb`
269 |
270 | ## Multi-account support
271 |
272 | All functions have third argument `options`,
273 | you can pass `appid`, `mch_id`, `key`, `apiclient_cert`, `apiclient_key` as a hash.
274 |
275 | For example
276 | ```ruby
277 | another_account = {appid: 'APPID', mch_id: 'MCH_ID', key: 'KEY'}.freeze
278 | WxPay::Service.generate_app_pay_req params, another_account.dup
279 | ```
280 |
281 | ## Contributing
282 |
283 | Bug report or pull request are welcome.
284 |
285 | ### Make a pull request
286 |
287 | 1. Fork it
288 | 2. Create your feature branch (`git checkout -b my-new-feature`)
289 | 3. Commit your changes (`git commit -am 'Add some feature'`)
290 | 4. Push to the branch (`git push origin my-new-feature`)
291 | 5. Create new Pull Request
292 |
293 | Please write unit test with your code if necessary.
294 |
295 | ## License
296 |
297 | This project rocks and uses MIT-LICENSE.
298 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | begin
2 | require 'bundler/setup'
3 | rescue LoadError
4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5 | end
6 |
7 | require 'rdoc/task'
8 |
9 | RDoc::Task.new(:rdoc) do |rdoc|
10 | rdoc.rdoc_dir = 'rdoc'
11 | rdoc.title = 'Tenpay'
12 | rdoc.options << '--line-numbers'
13 | rdoc.rdoc_files.include('README.rdoc')
14 | rdoc.rdoc_files.include('lib/**/*.rb')
15 | end
16 |
17 | Bundler::GemHelper.install_tasks
18 |
19 | require 'rake/testtask'
20 |
21 | Rake::TestTask.new(:test) do |t|
22 | t.libs << 'lib'
23 | t.libs << 'test'
24 | t.pattern = 'test/**/*_test.rb'
25 | t.verbose = false
26 | end
27 |
28 | task default: :test
29 |
--------------------------------------------------------------------------------
/lib/wx_pay.rb:
--------------------------------------------------------------------------------
1 | require 'wx_pay/result'
2 | require 'wx_pay/sign'
3 | require 'wx_pay/service'
4 | require 'wx_pay/version'
5 | require 'openssl'
6 |
7 | module WxPay
8 | @extra_rest_client_options = {}
9 | @debug_mode = true
10 |
11 | class << self
12 | attr_accessor :appid, :mch_id, :key, :appsecret, :extra_rest_client_options, :debug_mode
13 | attr_reader :apiclient_cert, :apiclient_key
14 |
15 | def set_apiclient_by_pkcs12(str, pass)
16 | pkcs12 = OpenSSL::PKCS12.new(str, pass)
17 | @apiclient_cert = pkcs12.certificate
18 | @apiclient_key = pkcs12.key
19 |
20 | pkcs12
21 | end
22 |
23 | def apiclient_cert=(cert)
24 | @apiclient_cert = OpenSSL::X509::Certificate.new(cert)
25 | end
26 |
27 | def apiclient_key=(key)
28 | @apiclient_key = OpenSSL::PKey::RSA.new(key)
29 | end
30 |
31 | def debug_mode?
32 | @debug_mode
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/wx_pay/result.rb:
--------------------------------------------------------------------------------
1 | module WxPay
2 | class Result < ::Hash
3 | SUCCESS_FLAG = 'SUCCESS'.freeze
4 |
5 | def initialize(result)
6 | super nil # Or it will call `super result`
7 |
8 | self[:raw] = result
9 |
10 | if result['xml'].class == Hash
11 | result['xml'].each_pair do |k, v|
12 | self[k] = v
13 | end
14 | end
15 | end
16 |
17 | def success?
18 | self['return_code'] == SUCCESS_FLAG && self['result_code'] == SUCCESS_FLAG
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/wx_pay/service.rb:
--------------------------------------------------------------------------------
1 | require 'rest_client'
2 | require 'json'
3 | require 'cgi'
4 | require 'securerandom'
5 | require 'active_support/isolated_execution_state'
6 | require 'active_support/xml_mini'
7 | require 'active_support/core_ext/hash/conversions'
8 |
9 | module WxPay
10 | module Service
11 | GATEWAY_URL = 'https://api.mch.weixin.qq.com'.freeze
12 | FRAUD_GATEWAY_URL = 'https://fraud.mch.weixin.qq.com'.freeze
13 |
14 | def self.generate_authorize_url(redirect_uri, state = nil)
15 | state ||= SecureRandom.hex 16
16 | "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{WxPay.appid}&redirect_uri=#{CGI::escape redirect_uri}&response_type=code&scope=snsapi_base&state=#{state}"
17 | end
18 |
19 | def self.authenticate(authorization_code, options = {})
20 | options = WxPay.extra_rest_client_options.merge(options)
21 | payload = {
22 | appid: options.delete(:appid) || WxPay.appid,
23 | secret: options.delete(:appsecret) || WxPay.appsecret,
24 | code: authorization_code,
25 | grant_type: 'authorization_code'
26 | }
27 | url = "https://api.weixin.qq.com/sns/oauth2/access_token"
28 |
29 | ::JSON.parse(RestClient::Request.execute(
30 | {
31 | method: :get,
32 | headers: {params: payload},
33 | url: url
34 | }.merge(options)
35 | ), quirks_mode: true)
36 | end
37 |
38 | def self.authenticate_from_weapp(js_code, options = {})
39 | options = WxPay.extra_rest_client_options.merge(options)
40 | payload = {
41 | appid: options.delete(:appid) || WxPay.appid,
42 | secret: options.delete(:appsecret) || WxPay.appsecret,
43 | js_code: js_code,
44 | grant_type: 'authorization_code'
45 | }
46 | url = "https://api.weixin.qq.com/sns/jscode2session"
47 |
48 | ::JSON.parse(RestClient::Request.execute(
49 | {
50 | method: :get,
51 | headers: {params: payload},
52 | url: url
53 | }.merge(options)
54 | ), quirks_mode: true)
55 | end
56 |
57 | INVOKE_UNIFIEDORDER_REQUIRED_FIELDS = [:body, :out_trade_no, :total_fee, :spbill_create_ip, :notify_url, :trade_type]
58 | def self.invoke_unifiedorder(params, options = {})
59 | params = {
60 | appid: options.delete(:appid) || WxPay.appid,
61 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
62 | key: options.delete(:key) || WxPay.key,
63 | nonce_str: SecureRandom.uuid.tr('-', '')
64 | }.merge(params)
65 |
66 | check_required_options(params, INVOKE_UNIFIEDORDER_REQUIRED_FIELDS)
67 |
68 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/pay/unifiedorder", make_payload(params), options)))
69 |
70 | yield r if block_given?
71 |
72 | r
73 | end
74 |
75 | INVOKE_CLOSEORDER_REQUIRED_FIELDS = [:out_trade_no]
76 | def self.invoke_closeorder(params, options = {})
77 | params = {
78 | appid: options.delete(:appid) || WxPay.appid,
79 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
80 | key: options.delete(:key) || WxPay.key,
81 | nonce_str: SecureRandom.uuid.tr('-', '')
82 | }.merge(params)
83 |
84 | check_required_options(params, INVOKE_CLOSEORDER_REQUIRED_FIELDS)
85 |
86 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/pay/closeorder", make_payload(params), options)))
87 |
88 | yield r if block_given?
89 |
90 | r
91 | end
92 |
93 | GENERATE_APP_PAY_REQ_REQUIRED_FIELDS = [:prepayid, :noncestr]
94 | def self.generate_app_pay_req(params, options = {})
95 | params = {
96 | appid: options.delete(:appid) || WxPay.appid,
97 | partnerid: options.delete(:mch_id) || WxPay.mch_id,
98 | key: options.delete(:key) || WxPay.key,
99 | package: 'Sign=WXPay',
100 | timestamp: Time.now.to_i.to_s
101 | }.merge(params)
102 |
103 | check_required_options(params, GENERATE_APP_PAY_REQ_REQUIRED_FIELDS)
104 |
105 | params[:sign] = WxPay::Sign.generate(params)
106 |
107 | params
108 | end
109 |
110 | GENERATE_JS_PAY_REQ_REQUIRED_FIELDS = [:prepayid, :noncestr]
111 | def self.generate_js_pay_req(params, options = {})
112 | check_required_options(params, GENERATE_JS_PAY_REQ_REQUIRED_FIELDS)
113 |
114 | params = {
115 | appId: options.delete(:appid) || WxPay.appid,
116 | package: "prepay_id=#{params.delete(:prepayid)}",
117 | key: options.delete(:key) || WxPay.key,
118 | nonceStr: params.delete(:noncestr),
119 | timeStamp: Time.now.to_i.to_s,
120 | signType: 'MD5'
121 | }.merge(params)
122 |
123 | params[:paySign] = WxPay::Sign.generate(params)
124 | params
125 | end
126 |
127 | INVOKE_REFUND_REQUIRED_FIELDS = [:out_refund_no, :total_fee, :refund_fee, :op_user_id]
128 | # out_trade_no 和 transaction_id 是二选一(必填)
129 | def self.invoke_refund(params, options = {})
130 | params = {
131 | appid: options.delete(:appid) || WxPay.appid,
132 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
133 | key: options.delete(:key) || WxPay.key,
134 | nonce_str: SecureRandom.uuid.tr('-', ''),
135 | }.merge(params)
136 |
137 | params[:op_user_id] ||= params[:mch_id]
138 |
139 | check_required_options(params, INVOKE_REFUND_REQUIRED_FIELDS)
140 | warn("WxPay Warn: missing required option: out_trade_no or transaction_id must have one") if ([:out_trade_no, :transaction_id] & params.keys) == []
141 |
142 | options = {
143 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
144 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
145 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
146 | }.merge(options)
147 |
148 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/secapi/pay/refund", make_payload(params), options)))
149 |
150 | yield r if block_given?
151 |
152 | r
153 | end
154 |
155 | REFUND_QUERY_REQUIRED_FIELDS = [:out_trade_no]
156 | def self.refund_query(params, options = {})
157 | params = {
158 | appid: options.delete(:appid) || WxPay.appid,
159 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
160 | key: options.delete(:key) || WxPay.key,
161 | nonce_str: SecureRandom.uuid.tr('-', '')
162 | }.merge(params)
163 |
164 | check_required_options(params, ORDER_QUERY_REQUIRED_FIELDS)
165 |
166 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/pay/refundquery", make_payload(params), options)))
167 |
168 | yield r if block_given?
169 |
170 | r
171 | end
172 |
173 | INVOKE_TRANSFER_REQUIRED_FIELDS = [:partner_trade_no, :openid, :check_name, :amount, :desc, :spbill_create_ip]
174 | def self.invoke_transfer(params, options = {})
175 | params = {
176 | mch_appid: options.delete(:appid) || WxPay.appid,
177 | mchid: options.delete(:mch_id) || WxPay.mch_id,
178 | nonce_str: SecureRandom.uuid.tr('-', ''),
179 | key: options.delete(:key) || WxPay.key
180 | }.merge(params)
181 |
182 | check_required_options(params, INVOKE_TRANSFER_REQUIRED_FIELDS)
183 |
184 | options = {
185 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
186 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
187 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
188 | }.merge(options)
189 |
190 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/mmpaymkttransfers/promotion/transfers", make_payload(params), options)))
191 |
192 | yield r if block_given?
193 |
194 | r
195 | end
196 |
197 | GETTRANSFERINFO_FIELDS = [:partner_trade_no]
198 | def self.gettransferinfo(params, options = {})
199 | params = {
200 | appid: options.delete(:appid) || WxPay.appid,
201 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
202 | nonce_str: SecureRandom.uuid.tr('-', ''),
203 | key: options.delete(:key) || WxPay.key
204 | }.merge(params)
205 |
206 | check_required_options(params, GETTRANSFERINFO_FIELDS)
207 |
208 | options = {
209 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
210 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
211 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
212 | }.merge(options)
213 |
214 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/mmpaymkttransfers/gettransferinfo", make_payload(params), options)))
215 |
216 | yield r if block_given?
217 |
218 | r
219 | end
220 |
221 | # 获取加密银行卡号和收款方用户名的RSA公钥
222 | def self.risk_get_public_key(options = {})
223 | params = {
224 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
225 | nonce_str: SecureRandom.uuid.tr('-', ''),
226 | key: options.delete(:key) || WxPay.key,
227 | sign_type: 'MD5'
228 | }
229 |
230 | options = {
231 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
232 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
233 | verify_ssl: OpenSSL::SSL::VERIFY_NONE,
234 | gateway_url: FRAUD_GATEWAY_URL
235 | }.merge(options)
236 |
237 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/risk/getpublickey", make_payload(params), options)))
238 |
239 | yield r if block_given?
240 |
241 | r
242 | end
243 |
244 | PAY_BANK_FIELDS = [:enc_bank_no, :enc_true_name, :bank_code, :amount, :desc]
245 | def self.pay_bank(params, options = {})
246 | params = {
247 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
248 | nonce_str: SecureRandom.uuid.tr('-', ''),
249 | key: options.delete(:key) || WxPay.key,
250 | }.merge(params)
251 |
252 | check_required_options(params, PAY_BANK_FIELDS)
253 |
254 | options = {
255 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
256 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
257 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
258 | }.merge(options)
259 |
260 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/mmpaysptrans/pay_bank", make_payload(params), options)))
261 |
262 | yield r if block_given?
263 |
264 | r
265 | end
266 |
267 | QUERY_BANK_FIELDS = [:partner_trade_no]
268 | def self.query_bank(params, options = {})
269 | params = {
270 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
271 | nonce_str: SecureRandom.uuid.tr('-', ''),
272 | key: options.delete(:key) || WxPay.key,
273 | }.merge(params)
274 |
275 | check_required_options(params, QUERY_BANK_FIELDS)
276 |
277 | options = {
278 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
279 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
280 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
281 | }.merge(options)
282 |
283 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/mmpaysptrans/query_bank", make_payload(params), options)))
284 |
285 | yield r if block_given?
286 |
287 | r
288 | end
289 |
290 | INVOKE_REVERSE_REQUIRED_FIELDS = [:out_trade_no]
291 | def self.invoke_reverse(params, options = {})
292 | params = {
293 | appid: options.delete(:appid) || WxPay.appid,
294 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
295 | key: options.delete(:key) || WxPay.key,
296 | nonce_str: SecureRandom.uuid.tr('-', '')
297 | }.merge(params)
298 |
299 | check_required_options(params, INVOKE_REVERSE_REQUIRED_FIELDS)
300 |
301 | options = {
302 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
303 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
304 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
305 | }.merge(options)
306 |
307 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/secapi/pay/reverse", make_payload(params), options)))
308 |
309 | yield r if block_given?
310 |
311 | r
312 | end
313 |
314 | INVOKE_MICROPAY_REQUIRED_FIELDS = [:body, :out_trade_no, :total_fee, :spbill_create_ip, :auth_code]
315 | def self.invoke_micropay(params, options = {})
316 | params = {
317 | appid: options.delete(:appid) || WxPay.appid,
318 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
319 | key: options.delete(:key) || WxPay.key,
320 | nonce_str: SecureRandom.uuid.tr('-', '')
321 | }.merge(params)
322 |
323 | check_required_options(params, INVOKE_MICROPAY_REQUIRED_FIELDS)
324 |
325 | options = {
326 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
327 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
328 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
329 | }.merge(options)
330 |
331 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/pay/micropay", make_payload(params), options)))
332 |
333 | yield r if block_given?
334 |
335 | r
336 | end
337 |
338 | ORDER_QUERY_REQUIRED_FIELDS = [:out_trade_no]
339 | def self.order_query(params, options = {})
340 | params = {
341 | appid: options.delete(:appid) || WxPay.appid,
342 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
343 | key: options.delete(:key) || WxPay.key,
344 | nonce_str: SecureRandom.uuid.tr('-', '')
345 | }.merge(params)
346 |
347 |
348 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/pay/orderquery", make_payload(params), options)))
349 | check_required_options(params, ORDER_QUERY_REQUIRED_FIELDS)
350 |
351 | yield r if block_given?
352 |
353 | r
354 | end
355 |
356 | DOWNLOAD_BILL_REQUIRED_FIELDS = [:bill_date, :bill_type]
357 | def self.download_bill(params, options = {})
358 | params = {
359 | appid: options.delete(:appid) || WxPay.appid,
360 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
361 | key: options.delete(:key) || WxPay.key,
362 | nonce_str: SecureRandom.uuid.tr('-', ''),
363 | }.merge(params)
364 |
365 | check_required_options(params, DOWNLOAD_BILL_REQUIRED_FIELDS)
366 |
367 | r = invoke_remote("/pay/downloadbill", make_payload(params), options)
368 |
369 | yield r if block_given?
370 |
371 | r
372 | end
373 |
374 | DOWNLOAD_FUND_FLOW_REQUIRED_FIELDS = [:bill_date, :account_type]
375 | def self.download_fund_flow(params, options = {})
376 | params = {
377 | appid: options.delete(:appid) || WxPay.appid,
378 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
379 | nonce_str: SecureRandom.uuid.tr('-', ''),
380 | key: options.delete(:key) || WxPay.key
381 | }.merge(params)
382 |
383 | check_required_options(params, DOWNLOAD_FUND_FLOW_REQUIRED_FIELDS)
384 |
385 | options = {
386 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
387 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
388 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
389 | }.merge(options)
390 |
391 | r = invoke_remote("/pay/downloadfundflow", make_payload(params, WxPay::Sign::SIGN_TYPE_HMAC_SHA256), options)
392 |
393 | yield r if block_given?
394 |
395 | r
396 | end
397 |
398 | def self.sendgroupredpack(params, options={})
399 | params = {
400 | wxappid: options.delete(:appid) || WxPay.appid,
401 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
402 | key: options.delete(:key) || WxPay.key,
403 | nonce_str: SecureRandom.uuid.tr('-', '')
404 | }.merge(params)
405 |
406 | #check_required_options(params, INVOKE_MICROPAY_REQUIRED_FIELDS)
407 |
408 | options = {
409 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
410 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
411 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
412 | }.merge(options)
413 |
414 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/mmpaymkttransfers/sendgroupredpack", make_payload(params), options)))
415 |
416 | yield r if block_given?
417 |
418 | r
419 | end
420 |
421 | def self.sendredpack(params, options={})
422 | params = {
423 | wxappid: options.delete(:appid) || WxPay.appid,
424 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
425 | key: options.delete(:key) || WxPay.key,
426 | nonce_str: SecureRandom.uuid.tr('-', '')
427 | }.merge(params)
428 |
429 | #check_required_options(params, INVOKE_MICROPAY_REQUIRED_FIELDS)
430 |
431 | options = {
432 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
433 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
434 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
435 | }.merge(options)
436 |
437 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/mmpaymkttransfers/sendredpack", make_payload(params), options)))
438 |
439 | yield r if block_given?
440 |
441 | r
442 | end
443 |
444 | # 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包。
445 | GETHBINFO_FIELDS = [:mch_billno, :bill_type]
446 | def self.gethbinfo(params, options = {})
447 | params = {
448 | appid: options.delete(:appid) || WxPay.appid,
449 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
450 | nonce_str: SecureRandom.uuid.tr('-', ''),
451 | key: options.delete(:key) || WxPay.key
452 | }.merge(params)
453 |
454 | check_required_options(params, GETHBINFO_FIELDS)
455 |
456 | options = {
457 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
458 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
459 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
460 | }.merge(options)
461 |
462 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/mmpaymkttransfers/gethbinfo", make_payload(params), options)))
463 |
464 | yield r if block_given?
465 |
466 | r
467 | end
468 |
469 | PROFITSHARINGADDRECEIVER = [:nonce_str, :receiver]
470 |
471 | # 添加分账接收方
472 | def self.profitsharingaddreceiver(params, options = {})
473 | params = {
474 | appid: options.delete(:appid) || WxPay.appid,
475 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
476 | nonce_str: SecureRandom.uuid.tr('-', ''),
477 | key: options.delete(:key) || WxPay.key
478 | }.merge(params)
479 |
480 | check_required_options(params, PROFITSHARINGADDRECEIVER)
481 |
482 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/pay/profitsharingaddreceiver", make_payload(params, WxPay::Sign::SIGN_TYPE_HMAC_SHA256), options)))
483 |
484 | yield r if block_given?
485 |
486 | r
487 | end
488 |
489 | PROFITSHARINGREMOVERECEIVER = [:nonce_str, :receiver]
490 | # 删除分账接收方
491 | def self.profitsharingremovereceiver(params, options = {})
492 | params = {
493 | appid: options.delete(:appid) || WxPay.appid,
494 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
495 | nonce_str: SecureRandom.uuid.tr('-', ''),
496 | key: options.delete(:key) || WxPay.key
497 | }.merge(params)
498 |
499 | check_required_options(params, PROFITSHARINGADDRECEIVER)
500 |
501 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/pay/profitsharingremovereceiver", make_payload(params, WxPay::Sign::SIGN_TYPE_HMAC_SHA256), options)))
502 |
503 | yield r if block_given?
504 |
505 | r
506 | end
507 |
508 | # 单次分账
509 |
510 | PROFITSHARING = [:nonce_str, :receivers, :transaction_id, :out_order_no]
511 |
512 | def self.profitsharing(params, options = {})
513 | params = {
514 | appid: options.delete(:appid) || WxPay.appid,
515 | mch_id: options.delete(:mch_id) || WxPay.mch_id,
516 | nonce_str: SecureRandom.uuid.tr('-', ''),
517 | key: options.delete(:key) || WxPay.key
518 | }.merge(params)
519 |
520 | check_required_options(params, PROFITSHARING)
521 |
522 | options = {
523 | ssl_client_cert: options.delete(:apiclient_cert) || WxPay.apiclient_cert,
524 | ssl_client_key: options.delete(:apiclient_key) || WxPay.apiclient_key,
525 | verify_ssl: OpenSSL::SSL::VERIFY_NONE
526 | }.merge(options)
527 |
528 | r = WxPay::Result.new(Hash.from_xml(invoke_remote("/secapi/pay/profitsharing", make_payload(params, WxPay::Sign::SIGN_TYPE_HMAC_SHA256), options)))
529 |
530 | yield r if block_given?
531 |
532 | r
533 | end
534 |
535 | class << self
536 | private
537 |
538 | def get_gateway_url
539 | GATEWAY_URL
540 | end
541 |
542 | def check_required_options(options, names)
543 | return unless WxPay.debug_mode?
544 |
545 | names.each do |name|
546 | warn("WxPay Warn: missing required option: #{name}") unless options.has_key?(name)
547 | end
548 | end
549 |
550 | def xmlify_payload(params, sign_type = WxPay::Sign::SIGN_TYPE_MD5)
551 | sign = WxPay::Sign.generate(params, sign_type)
552 | "#{params.except(:key).sort.map { |k, v| "<#{k}>#{v}#{k}>" }.join}#{sign}"
553 | end
554 |
555 | def make_payload(params, sign_type = WxPay::Sign::SIGN_TYPE_MD5)
556 | xmlify_payload(params, sign_type)
557 | end
558 |
559 | def invoke_remote(url, payload, options = {})
560 | options = WxPay.extra_rest_client_options.merge(options)
561 | gateway_url = options.delete(:gateway_url) || get_gateway_url
562 | url = "#{gateway_url}#{url}"
563 |
564 | RestClient::Request.execute(
565 | {
566 | method: :post,
567 | url: url,
568 | payload: payload,
569 | headers: { content_type: 'application/xml' }
570 | }.merge(options)
571 | )
572 | end
573 | end
574 | end
575 | end
576 |
--------------------------------------------------------------------------------
/lib/wx_pay/sign.rb:
--------------------------------------------------------------------------------
1 | require 'digest/md5'
2 |
3 | module WxPay
4 | module Sign
5 |
6 | SIGN_TYPE_MD5 = 'MD5'
7 | SIGN_TYPE_HMAC_SHA256 = 'HMAC-SHA256'
8 |
9 | def self.generate(params, sign_type = SIGN_TYPE_MD5)
10 | key = params.delete(:key)
11 |
12 | new_key = params["key"] #after
13 | key = params.delete("key") if params["key"] #after
14 |
15 | query = params.sort.map do |k, v|
16 | "#{k}=#{v}" if v.to_s != ''
17 | end.compact.join('&')
18 |
19 | string_sign_temp = "#{query}&key=#{key || new_key || WxPay.key}" #after
20 |
21 | if sign_type == SIGN_TYPE_MD5
22 | Digest::MD5.hexdigest(string_sign_temp).upcase
23 | elsif sign_type == SIGN_TYPE_HMAC_SHA256
24 | OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, string_sign_temp).upcase
25 | else
26 | warn("WxPay Warn: unknown sign_type : #{sign_type}")
27 | end
28 | end
29 |
30 | def self.verify?(params, options = {})
31 | params = params.dup
32 | params["appid"] = options[:appid] if options[:appid]
33 | params["mch_id"] = options[:mch_id] if options[:mch_id]
34 | params["key"] = options[:key] if options[:key]
35 |
36 | sign = params.delete('sign') || params.delete(:sign)
37 |
38 | generate(params, options[:sign_type] || SIGN_TYPE_MD5) == sign
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/wx_pay/version.rb:
--------------------------------------------------------------------------------
1 | module WxPay
2 | VERSION = '0.21.0'.freeze
3 | end
4 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'active_support/core_ext/hash/conversions'
2 | require 'minitest/autorun'
3 | require 'wx_pay'
4 | require 'webmock/minitest'
5 |
6 | WxPay.appid = 'wxd930ea5d5a258f4f'
7 | WxPay.key = '8934e7d15453e97507ef794cf7b0519d'
8 | WxPay.mch_id = '1900000109'
9 | WxPay.debug_mode = true
10 |
--------------------------------------------------------------------------------
/test/wx_pay/result_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'active_support/core_ext/hash/conversions'
3 |
4 | class WxPay::ResultTest < MiniTest::Test
5 | def test_success_method_with_true
6 | r = WxPay::Result.new(
7 | Hash.from_xml(
8 | <<-XML
9 |
10 | SUCCESS
11 | SUCCESS
12 |
13 | XML
14 | ))
15 |
16 | assert_equal r.success?, true
17 | end
18 |
19 | def test_success_method_with_false
20 | r = WxPay::Result.new(
21 | Hash.from_xml(
22 | <<-XML
23 |
24 |
25 | XML
26 | ))
27 |
28 | assert_equal r.success?, false
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/test/wx_pay/service_test.rb:
--------------------------------------------------------------------------------
1 |
2 | class ServiceTest < MiniTest::Test
3 |
4 | def setup
5 | @params = {
6 | transaction_id: '1217752501201407033233368018',
7 | op_user_id: '10000100',
8 | out_refund_no: '1415701182',
9 | out_trade_no: '1415757673',
10 | refund_fee: 1,
11 | total_fee: 1
12 | }
13 | end
14 |
15 | def test_invoke_refund
16 | response_body = <<-EOF
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 1
31 | 0
32 |
33 | EOF
34 |
35 | stub_request(:post, 'api.mch.weixin.qq.com').to_return(body: response_body)
36 | end
37 |
38 | def test_accept_multiple_app_id_when_invoke
39 | params = {
40 | body: '测试商品',
41 | out_trade_no: 'test003',
42 | total_fee: 1,
43 | spbill_create_ip: '127.0.0.1',
44 | notify_url: 'http://making.dev/notify',
45 | trade_type: 'JSAPI',
46 | openid: 'OPENID',
47 | app_id: 'app_id',
48 | mch_id: 'mch_id',
49 | key: 'key'
50 | }
51 | xml_str = 'app_id测试商品mch_idhttp://making.dev/notifyOPENIDtest003127.0.0.11JSAPI172A2D487A37D13FDE32B874BA823DD6'
52 | assert_equal xml_str, WxPay::Service.send(:make_payload, params)
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/test/wx_pay/sign_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class WxPay::SignTest < MiniTest::Test
4 | def setup
5 | @params = {
6 | appid: 'wxd930ea5d5a258f4f',
7 | auth_code: 123456,
8 | body: 'test',
9 | device_info: 123,
10 | mch_id: 1900000109,
11 | nonce_str: '960f228109051b9969f76c82bde183ac',
12 | out_trade_no: '1400755861',
13 | spbill_create_ip: '127.0.0.1',
14 | total_fee: 1
15 | }
16 |
17 | @sign_md5 = '729A68AC3DE268DBD9ADE442382E7B24'
18 | @sign_hmac_sha256 = 'A58C01F990B45A4D0E496F835B2739E391C6C734927B1DA740DC873E607FB42A'
19 | end
20 |
21 | def test_generate_sign
22 | assert_equal @sign_md5, WxPay::Sign.generate(@params)
23 | end
24 |
25 | def test_generate_sign_md5
26 | assert_equal @sign_md5, WxPay::Sign.generate(@params, WxPay::Sign::SIGN_TYPE_MD5)
27 | end
28 |
29 | def test_generate_sign_hmac_sha256
30 | @params.merge!(key: "key")
31 | assert_equal @sign_hmac_sha256, WxPay::Sign.generate(@params, WxPay::Sign::SIGN_TYPE_HMAC_SHA256)
32 | end
33 |
34 | def test_verify_sign
35 | assert WxPay::Sign.verify?(@params.merge(:sign => @sign_md5))
36 | end
37 |
38 | def test_verify_sign_when_fails
39 | assert !WxPay::Sign.verify?(@params.merge(:danger => 'danger', :sign => @sign_md5))
40 | end
41 |
42 | def test_accept_pars_key_to_generate_sign
43 | @params.merge!(key: "key")
44 |
45 | assert_equal "1454C32E885B8D9E4A05E976D1C45B88", WxPay::Sign.generate(@params)
46 | end
47 |
48 | def test_verify_sign_when_use_hmac_sha256
49 | opts = { key: "key", sign_type: WxPay::Sign::SIGN_TYPE_HMAC_SHA256 }
50 |
51 | assert WxPay::Sign.verify?(@params.merge(:sign => @sign_hmac_sha256), opts)
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/wx_pay.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | require "wx_pay/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "wx_pay"
7 | s.version = WxPay::VERSION
8 | s.authors = ["Jasl"]
9 | s.email = ["jasl9187@hotmail.com"]
10 | s.homepage = "https://github.com/jasl/wx_pay"
11 | s.summary = "An unofficial simple wechat pay gem"
12 | s.description = "An unofficial simple wechat pay gem"
13 | s.license = "MIT"
14 |
15 | s.require_paths = ["lib"]
16 |
17 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
18 | s.test_files = Dir["test/**/*"]
19 |
20 | s.add_runtime_dependency "rest-client", '>= 2.0.0'
21 | s.add_runtime_dependency "activesupport", '>= 3.2'
22 |
23 | s.add_development_dependency "rake", '>= 12.3.3'
24 | s.add_development_dependency "webmock", '~> 2.3'
25 | s.add_development_dependency "minitest", '~> 5'
26 | end
27 |
--------------------------------------------------------------------------------