wechat-pay 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WechatPay
4
+ module Ecommerce
5
+ class<<self
6
+ WITHDRAW_FIELDS = %i[sub_mchid out_request_no amount].freeze # :nodoc:
7
+ #
8
+ # 二级商户提现
9
+ #
10
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_2.shtml
11
+ #
12
+ # Example:
13
+ #
14
+ # ``` ruby
15
+ # WechatPay::Ecommerce.withdraw(sub_mchid: '160000', out_request_no: 'P10000', amount: 1)
16
+ # ```
17
+ #
18
+ def withdraw(params)
19
+ url = '/v3/ecommerce/fund/withdraw'
20
+ method = 'POST'
21
+
22
+ make_request(
23
+ method: method,
24
+ path: url,
25
+ for_sign: params.to_json,
26
+ payload: params.to_json
27
+ )
28
+ end
29
+
30
+ QUERY_WITHDRAW_FIELDS = %i[withdraw_id out_request_no sub_mchid].freeze # :nodoc:
31
+ #
32
+ # 二级商户提现查询
33
+ #
34
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_3.shtml
35
+ #
36
+ # Example:
37
+ #
38
+ # ``` ruby
39
+ # WechatPay::Ecommerce.query_withdraw(withdraw_id: '335556', sub_mchid: '160000')
40
+ # WechatPay::Ecommerce.query_withdraw(out_request_no: 'P1000', sub_mchid: '160000')
41
+ # ```
42
+ #
43
+ def query_withdraw(params)
44
+ if params[:withdraw_id]
45
+ params.delete(:out_request_no)
46
+ withdraw_id = params.delete(:withdraw_id)
47
+ path = "/v3/ecommerce/fund/withdraw/#{withdraw_id}"
48
+ else
49
+ params.delete(:withdraw_id)
50
+ out_request_no = params.delete(:out_request_no)
51
+ path = "/v3/ecommerce/fund/withdraw/out-request-no/#{out_request_no}"
52
+ end
53
+
54
+ method = 'GET'
55
+ query = build_query(params)
56
+ url = "#{path}?#{query}"
57
+
58
+ make_request(
59
+ method: method,
60
+ path: url,
61
+ extra_headers: {
62
+ 'Content-Type' => 'application/x-www-form-urlencoded'
63
+ }
64
+ )
65
+ end
66
+
67
+ PLATFORM_WITHDRAW_FIELDS = %i[out_request_no amount account_type].freeze # :nodoc:
68
+ #
69
+ # 电商平台提现
70
+ #
71
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_5.shtml
72
+ #
73
+ # Example:
74
+ #
75
+ # ``` ruby
76
+ # WechatPay::Ecommerce.platform_withdraw(out_request_no: 'P10000', amount: 1, account_type: 'BASIC')
77
+ # WechatPay::Ecommerce.platform_withdraw(out_request_no: 'P10000', amount: 1, account_type: 'FEES')
78
+ # ```
79
+ #
80
+ def platform_withdraw(params)
81
+ url = '/v3/merchant/fund/withdraw'
82
+ method = 'POST'
83
+
84
+ make_request(
85
+ method: method,
86
+ path: url,
87
+ for_sign: params.to_json,
88
+ payload: params.to_json
89
+ )
90
+ end
91
+
92
+ QUERY_PLATFORM_WITHDRAW_FIELDS = %i[withdraw_id out_request_no].freeze # :nodoc:
93
+ #
94
+ # 商户平台提现查询
95
+ #
96
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_6.shtml
97
+ #
98
+ # Example:
99
+ #
100
+ # ``` ruby
101
+ # WechatPay::Ecommerce.query_platform_withdraw(out_request_no: 'P1000')
102
+ # WechatPay::Ecommerce.query_platform_withdraw(withdraw_id: '12313153')
103
+ # ```
104
+ #
105
+ def query_platform_withdraw(params)
106
+ if params[:withdraw_id]
107
+ params.delete(:out_request_no)
108
+ withdraw_id = params.delete(:withdraw_id)
109
+ url = "/v3/merchant/fund/withdraw/withdraw-id/#{withdraw_id}"
110
+ else
111
+ params.delete(:withdraw_id)
112
+ out_request_no = params.delete(:out_request_no)
113
+ url = "/v3/merchant/fund/withdraw/out-request-no/#{out_request_no}"
114
+ end
115
+
116
+ method = 'GET'
117
+
118
+ make_request(
119
+ method: method,
120
+ path: url,
121
+ extra_headers: {
122
+ 'Content-Type' => 'application/x-www-form-urlencoded'
123
+ }
124
+ )
125
+ end
126
+
127
+ DOWNLOAD_EXCEPTION_WITHDRAW_FILE = %i[bill_type bill_date].freeze # :nodoc:
128
+ #
129
+ # 按日下载提现异常文件
130
+ #
131
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_4.shtml
132
+ #
133
+ # ``` ruby
134
+ # WechatPay::Ecommerce.download_exception_withdraw_file(bill_type: 'NO_SUCC', bill_date: '2021-05-10')
135
+ # ```
136
+ def download_exception_withdraw_file(params)
137
+ bill_type = params.delete(:bill_type)
138
+ path = "/v3/merchant/fund/withdraw/bill-type/#{bill_type}"
139
+
140
+ method = 'GET'
141
+
142
+ query = build_query(params)
143
+ url = "#{path}?#{query}"
144
+
145
+ make_request(
146
+ method: method,
147
+ path: url,
148
+ extra_headers: {
149
+ 'Content-Type' => 'application/x-www-form-urlencoded'
150
+ }
151
+ )
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module WechatPayHelper # :nodoc:
6
+ GATEWAY_URL = 'https://api.mch.weixin.qq.com'
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ class_methods do
11
+ def build_query(params)
12
+ params.sort.map { |key, value| "#{key}=#{value}" }.join('&')
13
+ end
14
+
15
+ def make_request(method:, path:, for_sign: '', payload: {}, extra_headers: {})
16
+ authorization = WechatPay::Sign.build_authorization_header(method, path, for_sign)
17
+ headers = {
18
+ 'Authorization' => authorization,
19
+ 'Content-Type' => 'application/json'
20
+ }.merge(extra_headers)
21
+
22
+ RestClient::Request.execute(
23
+ url: "#{GATEWAY_URL}#{path}",
24
+ method: method.downcase,
25
+ payload: payload,
26
+ headers: headers.compact # Remove empty items
27
+ )
28
+ rescue ::RestClient::ExceptionWithResponse => e
29
+ e.response
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+ require 'securerandom'
6
+ require 'active_support/core_ext/hash'
7
+
8
+ module WechatPay
9
+ # # 微信签名相关的封装
10
+ # 文档: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
11
+ #
12
+ # PS: 提供了常用的帮助方法,方便您的开发
13
+ #
14
+ module Sign
15
+ class<<self
16
+ # Generate payment params with appid and prepay_id for invoking the wechat pay.
17
+ #
18
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_7.shtml
19
+ #
20
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_8.shtml
21
+ #
22
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_9.shtml
23
+ #
24
+ # Take app for example
25
+ #
26
+ # ``` ruby
27
+ # appid = 'appid for mobile'
28
+ #
29
+ # params = {
30
+ # sp_appid: 'Your appid',
31
+ # sp_mchid: 'Your mchid',
32
+ # description: 'pay',
33
+ # out_trade_no: 'Order Number',
34
+ # payer: {
35
+ # sp_openid: 'wechat open id'
36
+ # },
37
+ # amount: {
38
+ # total: 10
39
+ # },
40
+ # sub_mchid: 'Your sub mchid',
41
+ # notify_url: 'the url'
42
+ # }
43
+ # result = WechatPay::Ecommerce.invoke_transactions_in_app(params).body
44
+ # # => { prepay_id => 'wx201410272009395522657a690389285100' }
45
+ # prepay_id = result['prepay_id']
46
+ # WechatPay::Sign.generate_payment_params_from_prepay_id_and_appid(appid, prepay_id)
47
+ # # => params for invoking the wechat pay in miniprogram
48
+ # ```
49
+ #
50
+ # Miniprogram and js process are similar, just get the prepay_id by method
51
+ #
52
+ # ``` ruby
53
+ # result = WechatPay::Ecommerce.invoke_transactions_in_js(params_for_miniprogram)
54
+ # # => { prepay_id => 'wx201410272009395522657a690389285100' }
55
+ # prepay_id = result['prepay_id']
56
+ # WechatPay::Sign.generate_payment_params_from_prepay_id_and_appid(appid, prepay_id)
57
+ # # => params for invoking the wechat pay in miniprogram or js
58
+ # ```
59
+ def generate_payment_params_from_prepay_id_and_appid(appid, prepay_id)
60
+ timestamp = Time.now.to_i.to_s
61
+ noncestr = SecureRandom.hex
62
+ string = build_paysign_string(appid, timestamp, noncestr, prepay_id)
63
+
64
+ {
65
+ timeStamp: timestamp,
66
+ nonceStr: noncestr,
67
+ package: "prepay_id=#{prepay_id}",
68
+ paySign: sign_string(string),
69
+ signType: 'RSA'
70
+ }.stringify_keys
71
+ end
72
+
73
+ # For checkingi if the requests from wechat platform
74
+ #
75
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_11.shtml
76
+ #
77
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml
78
+ #
79
+ # Usage:
80
+ #
81
+ # ``` ruby
82
+ # def pay_action
83
+ # timestamp = request.headers['Wechatpay-Timestamp']
84
+ # noncestr = request.headers['Wechatpay-Nonce']
85
+ # signature = request.headers['Wechatpay-Signature']
86
+ # body = JSON.parse(request.body.read)
87
+ # raise Exceptions::InvalidAction, '非法请求,请求并非来自微信' unless WechatV3.notification_from_wechat?(timestamp, noncestr, body.to_json, signature)
88
+ # # ....
89
+ # end
90
+ # ```
91
+ def notification_from_wechat?(timestamp, noncestr, json_body, signature)
92
+ string = build_callback_string(timestamp, noncestr, json_body)
93
+ decoded_signature = Base64.strict_decode64(signature)
94
+ WechatPay.platform_cert.public_key.verify('SHA256', decoded_signature, string)
95
+ end
96
+
97
+ # For signing the sensitive information
98
+ #
99
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_3.shtml
100
+ #
101
+ # Usage:
102
+ #
103
+ # ``` ruby
104
+ # string = 'Ruby'
105
+ # WechatPay::Sign.sign_important_info(string)
106
+ # ```
107
+ #
108
+ # ``` ruby
109
+ # # result
110
+ # "K0MK7g3laREAQ4HIlpIndVmFdz4IyxxiVp42hXFx2CzWRB1fn85ANBxnQXESq91vJ1P9mCt94cHZDoshlEOJRkE1KvcxpBCnG3ghIqiSsLKdLZ3ytO94GBDzCt8nsq+vJKXJbK2XuL9p5h0KYGKZyjt2ydU9Ig6daWTpZH8lAKIsLzPTsaUtScuw/v3M/7t8/4py8N0MOLKbDBDnR5Q+MRHbEWI9nCA3HTAWsSerIIgE7igWnzybxsUzhkV8m49P/Shr2zh6yJAlEnyPLFmQG7GuUaYwDTSLKOWzzPYwxMcucWQha2krC9OlwnZJe6ZWUAI3s4ej4kFRfheOYywRoQ=="
111
+ # ```
112
+ def sign_important_info(string)
113
+ platform_public_key = WechatPay.platform_cert.public_key
114
+ Base64.strict_encode64(platform_public_key.public_encrypt(string, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING))
115
+ end
116
+
117
+ # For Decrypting the encrypt params from wechat platform
118
+ #
119
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_2.shtml
120
+ #
121
+ # Usage:
122
+ #
123
+ # ``` ruby
124
+ # def pay_action
125
+ # # check if the request from wechat
126
+ # associated_data = body['resource']['associated_data']
127
+ # nonce = body['resource']['nonce']
128
+ # ciphertext = body['resource']['ciphertext']
129
+ # res = WechatPay::Sign.decrypt_the_encrypt_params(
130
+ # associated_data: associated_data,
131
+ # nonce: nonce,
132
+ # ciphertext: ciphertext
133
+ # )
134
+ # result = JSON.parse(res) # Get the real params
135
+ # end
136
+ # ```
137
+ #
138
+ def decrypt_the_encrypt_params(associated_data:, nonce:, ciphertext:)
139
+ # https://contest-server.cs.uchicago.edu/ref/ruby_2_3_1_stdlib/libdoc/openssl/rdoc/OpenSSL/Cipher.html
140
+ tag_length = 16
141
+ decipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
142
+ decipher.key = WechatPay.mch_key
143
+ decipher.iv = nonce
144
+ signature = Base64.strict_decode64(ciphertext)
145
+ length = signature.length
146
+ real_signature = signature.slice(0, length - tag_length)
147
+ tag = signature.slice(length - tag_length, length)
148
+ decipher.auth_tag = tag
149
+ decipher.auth_data = associated_data
150
+ decipher.update(real_signature)
151
+ end
152
+
153
+ # Build authorization header for request
154
+ #
155
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_0.shtml
156
+ #
157
+ # Usage:
158
+ #
159
+ # ``` ruby
160
+ # method = 'GET'
161
+ # url = '/v3/certificates'
162
+ # json_body = ''
163
+ # WechatPay::sign.build_authorization_header(method, url, json_body)
164
+ # ```
165
+ #
166
+ # ``` ruby
167
+ # # Result
168
+ # "WECHATPAY2-SHA256-RSA2048 mchid=\"16000000\",nonce_str=\"42ac357637f9331794e0c6fb3b3de048\",serial_no=\"0254A801C0\",signature=\"WBJaWlVFur5OGQ/E0ZKIlSDhR8WTNrkW2oCECF3Udrh8BVlnfYf5N5ROeOt9PBxdwD0+ufFQANZKugmXDNat+sFRY2DrIzdP3qYvFIzaYjp6QEtB0UPzvTgcLDULGbwCSTNDxvKRDi07OXPFSmVfmA5SbpbfumgjYOfzt1wcl9Eh+/op/gAB3N010Iu1w4OggR78hxQvPb9GIscuKHjaUWqqwf6v+p3/b0tiSO/SekJa3bMKPhJ2wJj8utBHQtbGO+iUQj1n90naL25MNJUM2XYocv4MasxZZgZnV3v1dtRvFkVo0ApqFyDoiRndr1Q/jPh+wmsb80LuhZ1S4eNfew==\",timestamp=\"1620571488\""
169
+ # ```
170
+ def build_authorization_header(method, url, json_body)
171
+ timestamp = Time.now.to_i
172
+ nonce_str = SecureRandom.hex
173
+ string = build_string(method, url, timestamp, nonce_str, json_body)
174
+ signature = sign_string(string)
175
+
176
+ params = {
177
+ mchid: WechatPay.mch_id,
178
+ nonce_str: nonce_str,
179
+ serial_no: WechatPay.apiclient_serial_no,
180
+ signature: signature,
181
+ timestamp: timestamp
182
+ }
183
+
184
+ params_string = params.stringify_keys.map { |key, value| "#{key}=\"#{value}\"" }.join(',')
185
+
186
+ "WECHATPAY2-SHA256-RSA2048 #{params_string}"
187
+ end
188
+
189
+ def sign_string(string)
190
+ result = WechatPay.apiclient_key.sign('SHA256', string) # 商户私钥的SHA256-RSA2048签名
191
+ Base64.strict_encode64(result) # Base64处理
192
+ end
193
+
194
+ private
195
+
196
+ def build_string(method, url, timestamp, noncestr, body)
197
+ "#{method}\n#{url}\n#{timestamp}\n#{noncestr}\n#{body}\n"
198
+ end
199
+
200
+ def build_callback_string(timestamp, noncestr, body)
201
+ "#{timestamp}\n#{noncestr}\n#{body}\n"
202
+ end
203
+
204
+ def build_paysign_string(appid, timestamp, noncestr, prepayid)
205
+ "#{appid}\n#{timestamp}\n#{noncestr}\nprepay_id=#{prepayid}\n"
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WechatPay
4
+ VERSION = '1.0.1'
5
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wechat-pay
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - lanzhiheng
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-05-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: rest-client
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 2.0.0
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '2.0'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 2.0.0
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '2.0'
53
+ - !ruby/object:Gem::Dependency
54
+ name: minitest
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 5.14.4
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: 5.14.4
67
+ - !ruby/object:Gem::Dependency
68
+ name: rake
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '13.0'
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 13.0.3
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '13.0'
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 13.0.3
87
+ description: A simple Wechat pay ruby gem in api V3.
88
+ email: lanzhihengrj@gmail.com
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - lib/set_base_envrionment.rb
94
+ - lib/wechat-pay.rb
95
+ - lib/wechat-pay/direct.rb
96
+ - lib/wechat-pay/ecommerce.rb
97
+ - lib/wechat-pay/ecommerce/applyment.rb
98
+ - lib/wechat-pay/ecommerce/balance.rb
99
+ - lib/wechat-pay/ecommerce/bill.rb
100
+ - lib/wechat-pay/ecommerce/combine_order.rb
101
+ - lib/wechat-pay/ecommerce/order.rb
102
+ - lib/wechat-pay/ecommerce/profitsharing.rb
103
+ - lib/wechat-pay/ecommerce/refund.rb
104
+ - lib/wechat-pay/ecommerce/subsidies.rb
105
+ - lib/wechat-pay/ecommerce/withdraw.rb
106
+ - lib/wechat-pay/helper.rb
107
+ - lib/wechat-pay/sign.rb
108
+ - lib/wechat-pay/version.rb
109
+ homepage: https://rubygems.org/gems/wechat-pay
110
+ licenses:
111
+ - MIT
112
+ metadata:
113
+ source_code_uri: https://github.com/lanzhiheng/wechat-pay
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '2.5'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.0.3
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Wechat Pay in api V3
133
+ test_files: []