wechat-ipay 2.0.0

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,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 = '2.0.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,27 @@
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-ipay'
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 = ['tianlu1677']
13
+ s.email = 'tianlu1677@gmail.com'
14
+ s.files = `git ls-files -z`.split("\x0").reject do |f|
15
+ f.match(%r{^(test|spec|features)/})
16
+ end
17
+
18
+ s.required_ruby_version = '>= 2.6'
19
+ s.require_paths = ['lib']
20
+ s.homepage = 'https://github.com/tianlu1677/wechat-pay/'
21
+ s.license = 'MIT'
22
+
23
+ s.add_runtime_dependency 'rest-client', '~> 2.0', '>= 2.0.0'
24
+ s.add_development_dependency 'rake', '~> 13.0', '>= 13.0.3'
25
+ s.add_development_dependency 'rspec', '~> 3.10.0'
26
+ s.add_development_dependency 'rubocop', '~> 1.14.0'
27
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wechat-ipay
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - tianlu1677
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.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: '2.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 13.0.3
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '13.0'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 13.0.3
53
+ - !ruby/object:Gem::Dependency
54
+ name: rspec
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 3.10.0
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: 3.10.0
67
+ - !ruby/object:Gem::Dependency
68
+ name: rubocop
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: 1.14.0
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: 1.14.0
81
+ description: A simple Wechat pay ruby gem in api V3.
82
+ email: tianlu1677@gmail.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - ".github/workflows/code_quality.yml"
88
+ - ".gitignore"
89
+ - ".rspec"
90
+ - ".rubocop.yml"
91
+ - ".yardopts"
92
+ - CHANGELOG.md
93
+ - Gemfile
94
+ - README.md
95
+ - Rakefile
96
+ - lib/wechat-pay.rb
97
+ - lib/wechat-pay/direct.rb
98
+ - lib/wechat-pay/ecommerce.rb
99
+ - lib/wechat-pay/ecommerce/applyment.rb
100
+ - lib/wechat-pay/ecommerce/balance.rb
101
+ - lib/wechat-pay/ecommerce/bill.rb
102
+ - lib/wechat-pay/ecommerce/combine_order.rb
103
+ - lib/wechat-pay/ecommerce/order.rb
104
+ - lib/wechat-pay/ecommerce/profitsharing.rb
105
+ - lib/wechat-pay/ecommerce/refund.rb
106
+ - lib/wechat-pay/ecommerce/subsidies.rb
107
+ - lib/wechat-pay/ecommerce/withdraw.rb
108
+ - lib/wechat-pay/helper.rb
109
+ - lib/wechat-pay/sign.rb
110
+ - lib/wechat-pay/version.rb
111
+ - wechat-ipay.gemspec
112
+ homepage: https://github.com/tianlu1677/wechat-pay/
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '2.6'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubygems_version: 3.4.10
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Wechat Pay in api V3
135
+ test_files: []