wechat-pay-next 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
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
+ 'Accept-Encoding' => '*'
21
+ }.merge(extra_headers)
22
+
23
+ RestClient::Request.execute(
24
+ url: "#{GATEWAY_URL}#{path}",
25
+ method: method.downcase,
26
+ payload: payload,
27
+ headers: headers.compact # Remove empty items
28
+ )
29
+ rescue ::RestClient::ExceptionWithResponse => e
30
+ e.response
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,245 @@
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 in app
17
+ #
18
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_7.shtml
19
+ #
20
+ # Take app for example
21
+ #
22
+ # ``` ruby
23
+ # appid = 'appid for mobile'
24
+ #
25
+ # params = {
26
+ # sp_appid: 'Your appid',
27
+ # sp_mchid: 'Your mchid',
28
+ # description: 'pay',
29
+ # out_trade_no: 'Order Number',
30
+ # amount: {
31
+ # total: 10
32
+ # },
33
+ # sub_mchid: 'Your sub mchid',
34
+ # notify_url: 'the url'
35
+ # }
36
+ # result = WechatPay::Ecommerce.invoke_transactions_in_app(params).body
37
+ # # => { prepay_id => 'wx201410272009395522657a690389285100' }
38
+ # prepay_id = result['prepay_id']
39
+ # WechatPay::Sign.generate_app_payment_params_from_prepay_id_and_appid(appid, prepay_id)
40
+ # # => params for invoking the wechat pay in app
41
+ # ```
42
+
43
+ def generate_app_payment_params_from_prepay_id_and_appid(appid, prepay_id)
44
+ timestamp = Time.now.to_i.to_s
45
+ noncestr = SecureRandom.hex
46
+ string = build_app_paysign_string(appid, timestamp, noncestr, prepay_id)
47
+
48
+ {
49
+ appId: appid,
50
+ partnerId: WechatPay.mch_id,
51
+ timeStamp: timestamp,
52
+ nonceStr: noncestr,
53
+ prepayId: prepay_id,
54
+ packageValue: 'Sign=WXPay',
55
+ sign: sign_string(string)
56
+ }.stringify_keys
57
+ end
58
+
59
+ # Generate payment params with appid and prepay_id for invoking the wechat pay in miniprogram
60
+ #
61
+ #
62
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_8.shtml
63
+ #
64
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_9.shtml
65
+ #
66
+ # Take app for example
67
+ #
68
+ # ``` ruby
69
+ # appid = 'appid for mobile'
70
+ #
71
+ # params = {
72
+ # sp_appid: 'Your appid',
73
+ # sp_mchid: 'Your mchid',
74
+ # description: 'pay',
75
+ # out_trade_no: 'Order Number',
76
+ # payer: {
77
+ # sp_openid: 'wechat open id'
78
+ # },
79
+ # amount: {
80
+ # total: 10
81
+ # },
82
+ # sub_mchid: 'Your sub mchid',
83
+ # notify_url: 'the url'
84
+ # }
85
+ # result = WechatPay::Ecommerce.invoke_transactions_in_miniprogram(params).body
86
+ # # => { prepay_id => 'wx201410272009395522657a690389285100' }
87
+ # prepay_id = result['prepay_id']
88
+ # WechatPay::Sign.generate_payment_params_from_prepay_id_and_appid(appid, prepay_id)
89
+ # # => params for invoking the wechat pay in miniprogram
90
+ # ```
91
+ def generate_payment_params_from_prepay_id_and_appid(appid, prepay_id)
92
+ timestamp = Time.now.to_i.to_s
93
+ noncestr = SecureRandom.hex
94
+ string = build_paysign_string(appid, timestamp, noncestr, prepay_id)
95
+
96
+ {
97
+ timeStamp: timestamp,
98
+ nonceStr: noncestr,
99
+ package: "prepay_id=#{prepay_id}",
100
+ paySign: sign_string(string),
101
+ signType: 'RSA'
102
+ }.stringify_keys
103
+ end
104
+
105
+ # For checkingi if the requests from wechat platform
106
+ #
107
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_11.shtml
108
+ #
109
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml
110
+ #
111
+ # Usage:
112
+ #
113
+ # ``` ruby
114
+ # def pay_action
115
+ # timestamp = request.headers['Wechatpay-Timestamp']
116
+ # noncestr = request.headers['Wechatpay-Nonce']
117
+ # signature = request.headers['Wechatpay-Signature']
118
+ # body = JSON.parse(request.body.read)
119
+ # raise Exceptions::InvalidAction, '非法请求,请求并非来自微信' unless WechatV3.notification_from_wechat?(timestamp, noncestr, body.to_json, signature)
120
+ # # ....
121
+ # end
122
+ # ```
123
+ def notification_from_wechat?(timestamp, noncestr, json_body, signature)
124
+ string = build_callback_string(timestamp, noncestr, json_body)
125
+ decoded_signature = Base64.strict_decode64(signature)
126
+ WechatPay.platform_cert.public_key.verify('SHA256', decoded_signature, string)
127
+ end
128
+
129
+ # For signing the sensitive information
130
+ #
131
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_3.shtml
132
+ #
133
+ # Usage:
134
+ #
135
+ # ``` ruby
136
+ # string = 'Ruby'
137
+ # WechatPay::Sign.sign_important_info(string)
138
+ # ```
139
+ #
140
+ # ``` ruby
141
+ # # result
142
+ # "K0MK7g3laREAQ4HIlpIndVmFdz4IyxxiVp42hXFx2CzWRB1fn85ANBxnQXESq91vJ1P9mCt94cHZDoshlEOJRkE1KvcxpBCnG3ghIqiSsLKdLZ3ytO94GBDzCt8nsq+vJKXJbK2XuL9p5h0KYGKZyjt2ydU9Ig6daWTpZH8lAKIsLzPTsaUtScuw/v3M/7t8/4py8N0MOLKbDBDnR5Q+MRHbEWI9nCA3HTAWsSerIIgE7igWnzybxsUzhkV8m49P/Shr2zh6yJAlEnyPLFmQG7GuUaYwDTSLKOWzzPYwxMcucWQha2krC9OlwnZJe6ZWUAI3s4ej4kFRfheOYywRoQ=="
143
+ # ```
144
+ def sign_important_info(string)
145
+ platform_public_key = WechatPay.platform_cert.public_key
146
+ Base64.strict_encode64(platform_public_key.public_encrypt(string, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING))
147
+ end
148
+
149
+ # For Decrypting the encrypt params from wechat platform
150
+ #
151
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_2.shtml
152
+ #
153
+ # Usage:
154
+ #
155
+ # ``` ruby
156
+ # def pay_action
157
+ # # check if the request from wechat
158
+ # associated_data = body['resource']['associated_data']
159
+ # nonce = body['resource']['nonce']
160
+ # ciphertext = body['resource']['ciphertext']
161
+ # res = WechatPay::Sign.decrypt_the_encrypt_params(
162
+ # associated_data: associated_data,
163
+ # nonce: nonce,
164
+ # ciphertext: ciphertext
165
+ # )
166
+ # result = JSON.parse(res) # Get the real params
167
+ # end
168
+ # ```
169
+ #
170
+ def decrypt_the_encrypt_params(associated_data:, nonce:, ciphertext:)
171
+ # https://contest-server.cs.uchicago.edu/ref/ruby_2_3_1_stdlib/libdoc/openssl/rdoc/OpenSSL/Cipher.html
172
+ tag_length = 16
173
+ decipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
174
+ decipher.key = WechatPay.mch_key
175
+ decipher.iv = nonce
176
+ signature = Base64.strict_decode64(ciphertext)
177
+ length = signature.length
178
+ real_signature = signature.slice(0, length - tag_length)
179
+ tag = signature.slice(length - tag_length, length)
180
+ decipher.auth_tag = tag
181
+ decipher.auth_data = associated_data
182
+ decipher.update(real_signature)
183
+ end
184
+
185
+ # Build authorization header for request
186
+ #
187
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_0.shtml
188
+ #
189
+ # Usage:
190
+ #
191
+ # ``` ruby
192
+ # method = 'GET'
193
+ # url = '/v3/certificates'
194
+ # json_body = ''
195
+ # WechatPay::sign.build_authorization_header(method, url, json_body)
196
+ # ```
197
+ #
198
+ # ``` ruby
199
+ # # Result
200
+ # "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\""
201
+ # ```
202
+ def build_authorization_header(method, url, json_body)
203
+ timestamp = Time.now.to_i
204
+ nonce_str = SecureRandom.hex
205
+ string = build_string(method, url, timestamp, nonce_str, json_body)
206
+ signature = sign_string(string)
207
+
208
+ params = {
209
+ mchid: WechatPay.mch_id,
210
+ nonce_str: nonce_str,
211
+ serial_no: WechatPay.apiclient_serial_no,
212
+ signature: signature,
213
+ timestamp: timestamp
214
+ }
215
+
216
+ params_string = params.stringify_keys.map { |key, value| "#{key}=\"#{value}\"" }.join(',')
217
+
218
+ "WECHATPAY2-SHA256-RSA2048 #{params_string}"
219
+ end
220
+
221
+ def sign_string(string)
222
+ result = WechatPay.apiclient_key.sign('SHA256', string) # 商户私钥的SHA256-RSA2048签名
223
+ Base64.strict_encode64(result) # Base64处理
224
+ end
225
+
226
+ private
227
+
228
+ def build_string(method, url, timestamp, noncestr, body)
229
+ "#{method}\n#{url}\n#{timestamp}\n#{noncestr}\n#{body}\n"
230
+ end
231
+
232
+ def build_callback_string(timestamp, noncestr, body)
233
+ "#{timestamp}\n#{noncestr}\n#{body}\n"
234
+ end
235
+
236
+ def build_paysign_string(appid, timestamp, noncestr, prepayid)
237
+ "#{appid}\n#{timestamp}\n#{noncestr}\nprepay_id=#{prepayid}\n"
238
+ end
239
+
240
+ def build_app_paysign_string(appid, timestamp, noncestr, prepayid)
241
+ "#{appid}\n#{timestamp}\n#{noncestr}\n#{prepayid}\n"
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WechatPay
4
+ VERSION = '1.1.0'
5
+ end
data/lib/wechat-pay.rb ADDED
@@ -0,0 +1,44 @@
1
+ # rubocop:disable Naming/FileName
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'restclient'
6
+ require 'wechat-pay/sign'
7
+ require 'wechat-pay/direct' # 直连模式
8
+ require 'wechat-pay/ecommerce' # 电商平台
9
+
10
+ # # 微信支付
11
+ #
12
+ # 设置关键信息
13
+ module WechatPay
14
+ class << self
15
+ attr_accessor :app_id, :mch_id, :mch_key
16
+ attr_reader :apiclient_key, :apiclient_cert, :platform_cert
17
+
18
+ # 设置商户私钥,从微信商户平台下载
19
+ def apiclient_key=(key)
20
+ @apiclient_key = OpenSSL::PKey::RSA.new(key)
21
+ end
22
+
23
+ # 设置平台证书,通过接口获取 https://github.com/lanzhiheng/wechat-pay/blob/master/lib/wechat-pay/ecommerce/applyment.rb#L116
24
+ def platform_cert=(cert)
25
+ @platform_cert = OpenSSL::X509::Certificate.new(cert)
26
+ end
27
+
28
+ # 设置商户证书,从微信商户平台下载
29
+ def apiclient_cert=(cert)
30
+ @apiclient_cert = OpenSSL::X509::Certificate.new(cert)
31
+ end
32
+
33
+ # 平台证书序列号
34
+ def platform_serial_no
35
+ @platform_serial_no ||= platform_cert.serial.to_s(16)
36
+ end
37
+
38
+ # 商户证书序列号
39
+ def apiclient_serial_no
40
+ @apiclient_serial_no ||= apiclient_cert.serial.to_s(16)
41
+ end
42
+ end
43
+ end
44
+ # rubocop:enable Naming/FileName
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+
5
+ require 'wechat-pay/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'wechat-pay-next'
9
+ s.version = WechatPay::VERSION
10
+ s.summary = 'Wechat Pay in api V3'
11
+ s.description = 'A simple Wechat pay ruby gem in api V3.'
12
+ s.authors = ['rocky']
13
+ s.email = 'xurenlu@gmail.com'
14
+ s.files = `git ls-files -z`.split("\x0").reject do |f|
15
+ f.match(%r{^(test|spec|features)/})
16
+ end
17
+ s.required_ruby_version = '>= 2.6'
18
+ s.require_paths = ['lib']
19
+ s.homepage = 'https://github.com/xurenlu/wechat-pay/'
20
+ s.license = 'MIT'
21
+
22
+ s.add_development_dependency 'rspec', '~> 3.10.0'
23
+ s.add_development_dependency 'rubocop', '~> 1.14.0'
24
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wechat-pay-next
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - rocky
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.10.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.10.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.14.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.14.0
41
+ description: A simple Wechat pay ruby gem in api V3.
42
+ email: xurenlu@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - ".github/workflows/code_quality.yml"
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - ".rubocop.yml"
51
+ - ".yardopts"
52
+ - CHANGELOG.md
53
+ - Gemfile
54
+ - README.md
55
+ - Rakefile
56
+ - lib/wechat-pay.rb
57
+ - lib/wechat-pay/direct.rb
58
+ - lib/wechat-pay/ecommerce.rb
59
+ - lib/wechat-pay/ecommerce/applyment.rb
60
+ - lib/wechat-pay/ecommerce/balance.rb
61
+ - lib/wechat-pay/ecommerce/bill.rb
62
+ - lib/wechat-pay/ecommerce/combine_order.rb
63
+ - lib/wechat-pay/ecommerce/order.rb
64
+ - lib/wechat-pay/ecommerce/profitsharing.rb
65
+ - lib/wechat-pay/ecommerce/refund.rb
66
+ - lib/wechat-pay/ecommerce/subsidies.rb
67
+ - lib/wechat-pay/ecommerce/withdraw.rb
68
+ - lib/wechat-pay/helper.rb
69
+ - lib/wechat-pay/sign.rb
70
+ - lib/wechat-pay/version.rb
71
+ - wechat-pay.gemspec
72
+ homepage: https://github.com/xurenlu/wechat-pay/
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '2.6'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.5.5
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Wechat Pay in api V3
95
+ test_files: []