wechat-pay 1.0.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.
@@ -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: []