wechat-ipay 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 417cacf5d330314a773fe49fb898dc94194da17ae4486588e09813e1fdfcf547
4
+ data.tar.gz: '09fd147836791a7842536f0d598bcbb590fcf1d9fefdb9b94e55580e600086cc'
5
+ SHA512:
6
+ metadata.gz: 9abaf1188c2a61716948e4139194aa62b3a79cba3a9eb75b05e70a15d72a0b25b631b93a7042adb89eb4ed2b479dd9cdd5f98c42b538b86a6a95b75882e09729
7
+ data.tar.gz: 5f36f28a6c57af14678d438bd13d05d9eb580aa1b594ba72f469545a885368a378e069a83c90e99bfe87edb4e1be3d8c90c2dbdee165c8a53fcc2790a18cf554
@@ -0,0 +1,35 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ pull_request:
12
+ branches: [ master ]
13
+
14
+ jobs:
15
+ test:
16
+
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ matrix:
20
+ ruby-version: ['2.6', '2.7', '3.0']
21
+
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ - name: Set up Ruby
25
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
26
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
27
+ # uses: ruby/setup-ruby@v1
28
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
29
+ with:
30
+ ruby-version: ${{ matrix.ruby-version }}
31
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
32
+ - name: Run rubocop
33
+ run: bundle exec rubocop
34
+ - name: Run tests
35
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ doc/
4
+ .yardoc/
5
+ rdoc/
6
+ /*.pem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --force-color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,21 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+
5
+ Style/AsciiComments:
6
+ Enabled: false
7
+
8
+ Metrics/BlockLength:
9
+ Enabled: false
10
+
11
+ Metrics/AbcSize:
12
+ Enabled: false
13
+
14
+ Metrics/MethodLength:
15
+ Max: 30
16
+
17
+ Layout/LineLength:
18
+ Enabled: false
19
+
20
+ Metrics/ModuleLength:
21
+ Max: 200
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --markup markdown
2
+ --protected
3
+ --no-private
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Next Release
2
+
3
+ ## 1.1.0 (07/26/2021)
4
+
5
+ * Fix `Accept-Encoding` issue in wechat.
6
+
7
+
8
+ ## 1.0.8 (05/26/2021)
9
+
10
+ * Add api `invoke_transactions_in_native` to `WechatPay::Ecommerce`
11
+
12
+ ## 1.0.7 (05/16/2021)
13
+
14
+ * Change `WechatPay::Ecommerce.get_certificates` to `WechatPay::Ecommerce.certificates`
15
+ * Refactor code and document.
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Declare your gem's dependencies in tenpay.gemspec.
6
+ # Bundler will treat runtime dependencies like base dependencies, and
7
+ # development dependencies will be added by default to the :development group.
8
+ gemspec
9
+
10
+ # Declare any dependencies that are still in development here instead of in
11
+ # your gemspec. These might include edge Rails or gems from your path or
12
+ # Git. Remember to move these dependencies to your gemspec before releasing
13
+ # your gem to rubygems.org.
14
+
15
+ # To use debugger
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # Wechat Pay
2
+
3
+ A simple Wechat pay ruby gem, without unnecessary magic or wrapper. Just a simple wrapper for api V3. Refer to [wx_pay](https://github.com/jasl/wx_pay)
4
+
5
+ Please read official document first: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/pages/index.shtml
6
+
7
+ If you want check the present public api, you can find them in the [Document](https://www.rubydoc.info/github/lanzhiheng/wechat-pay/master/index)。
8
+
9
+ Summary:
10
+
11
+ `WechatPay::Direct` will contain the public api for direct connection merchant(直连商户)and `WechatPay::Ecommerce` will contain the public api for ecommerce(服务商,电商平台)。For more detail you can refer to the wechat document.
12
+
13
+ - 直连商户: https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
14
+ - 服务商: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/index.shtml
15
+ - 电商平台(电商收付通): https://pay.weixin.qq.com/wiki/doc/apiv3_partner/open/pay/chapter3_3_3.shtml
16
+
17
+ If you find any issue in this repo, don't shy to create issues https://github.com/lanzhiheng/wechat-pay/issues
18
+
19
+ For more Information,you can check my posts: https://www.lanzhiheng.com/posts/preview/ruby-gem-for-wechat-pay-v3
20
+
21
+ # Installation
22
+
23
+ Add this line to your Gemfile:
24
+
25
+ ```
26
+ gem 'wechat-pay'
27
+ ```
28
+
29
+ or development version
30
+
31
+ ```
32
+ gem 'wechat-pay', :github => 'lanzhiheng/wechat-pay'
33
+ ```
34
+
35
+ And then execute:
36
+
37
+ ```
38
+ $ bundle
39
+ ```
40
+
41
+ # Usage
42
+
43
+ ## Configuration
44
+
45
+ Create `config/initializer/wechat_pay.rb`and put following configurations into it
46
+
47
+ ``` ruby
48
+ WechatPay.apiclient_key = File.read('apiclient_key.pem')
49
+ WechatPay.platform_cert = File.read('platform_cert.pem') # You should comment this line before downloaded platform_cert.
50
+ WechatPay.apiclient_cert = File.read('apiclient_cert.pem')
51
+ WechatPay.app_id = 'Your App Id'
52
+ WechatPay.mch_id = 'Your Mch Id'
53
+ WechatPay.mch_key = 'Your Mch Key'
54
+ ```
55
+
56
+ ## Download
57
+
58
+ I will provide a simple script for you to download the platform_cert
59
+
60
+ ``` ruby
61
+ def download_certificate
62
+ download_path = 'Your Download Path'
63
+ raise '必须提供证书下载路径' if download_path.blank?
64
+
65
+ response = WechatPay::Ecommerce.certificates
66
+
67
+ raise '证书下载失败' unless response.code == 200
68
+
69
+ result = JSON.parse(response.body)
70
+ # 需要按生效日期进行排序,获取最新的证书
71
+ array = result['data'].sort_by { |item| -Time.parse(item['effective_time']).to_i }
72
+ current_data = array.first
73
+ encrypt_certificate = current_data['encrypt_certificate']
74
+ associated_data = encrypt_certificate['associated_data']
75
+ nonce = encrypt_certificate['nonce']
76
+ ciphertext = encrypt_certificate['ciphertext']
77
+
78
+ content = WechatPay::Sign.decrypt_the_encrypt_params(
79
+ associated_data: associated_data,
80
+ nonce: nonce,
81
+ ciphertext: ciphertext
82
+ )
83
+
84
+ File.open(download_path, 'w') do |f|
85
+ f.write(content)
86
+ end
87
+
88
+ puts '证书下载成功'
89
+ end
90
+ ```
91
+
92
+
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
9
+ rescue LoadError
10
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
11
+ end
12
+
13
+ require 'rdoc/task'
14
+ Rake::RDocTask.new do |rdoc|
15
+ rdoc.rdoc_dir = 'rdoc'
16
+ rdoc.title = "wechat-pay #{WechatPay::VERSION}"
17
+ rdoc.rdoc_files.include('README*')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
@@ -0,0 +1,371 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'wechat-pay/helper'
5
+
6
+ module WechatPay
7
+ # # 直连商户相关接口封装(常用的已有,待完善)
8
+ # 文档: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
9
+ module Direct
10
+ include WechatPayHelper
11
+
12
+ # @private
13
+ # @!macro [attach] define_transaction_method
14
+ # 直连$1下单
15
+ #
16
+ # Document: $3
17
+ #
18
+ # Example:
19
+ #
20
+ # ``` ruby
21
+ # params = {
22
+ # appid: 'Your Open id',
23
+ # mchid: 'Your Mch id'',
24
+ # description: '回流',
25
+ # out_trade_no: 'Checking',
26
+ # payer: {
27
+ # openid: 'oly6s5c'
28
+ # },
29
+ # amount: {
30
+ # total: 1
31
+ # },
32
+ # notify_url: ENV['NOTIFICATION_URL']
33
+ # }
34
+ #
35
+ # WechatPay::Direct.invoke_transactions_in_$1(params)
36
+ # ```
37
+ # @!method invoke_transactions_in_$1
38
+ # @!scope class
39
+ def self.define_transaction_method(key, value, _document)
40
+ const_set("INVOKE_TRANSACTIONS_IN_#{key.upcase}_FIELDS",
41
+ %i[description out_trade out_trade_no payer amount notify_url].freeze)
42
+ define_singleton_method "invoke_transactions_in_#{key}" do |params|
43
+ direct_transactions_method_by_suffix(value, params)
44
+ end
45
+ end
46
+
47
+ define_transaction_method('js', 'jsapi', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml')
48
+ define_transaction_method('miniprogram', 'jsapi', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml')
49
+ define_transaction_method('app', 'app', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml')
50
+ define_transaction_method('h5', 'h5', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_1.shtml')
51
+ define_transaction_method('native', 'native', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml')
52
+
53
+ # @private
54
+ # @!macro [attach] define_combine_transaction_method
55
+ # 直连合单$1下单
56
+ #
57
+ # Document: $3
58
+ #
59
+ # ``` ruby
60
+ # params = {
61
+ # combine_out_trade_no: 'combine_out_trade_no',
62
+ # combine_payer_info: {
63
+ # openid: 'client open id'
64
+ # },
65
+ # sub_orders: [
66
+ # {
67
+ # mchid: 'mchid',
68
+ # sub_mchid: 'sub mchid',
69
+ # attach: 'attach',
70
+ # amount: {
71
+ # total_amount: 100,
72
+ # currency: 'CNY'
73
+ # },
74
+ # out_trade_no: 'out_trade_no',
75
+ # description: 'description'
76
+ # }
77
+ # ],
78
+ # notify_url: 'the_url'
79
+ # }
80
+ #
81
+ # WechatPay::Direct.invoke_combine_transactions_in_$1(params)
82
+ # ```
83
+ # @!method invoke_combine_transactions_in_$1
84
+ # @!scope class
85
+ def self.define_combine_transaction_method(key, _value, _document)
86
+ const_set("INVOKE_COMBINE_TRANSACTIONS_IN_#{key.upcase}_FIELDS",
87
+ %i[combine_out_trade_no scene_info sub_orders notify_url].freeze)
88
+ define_singleton_method("invoke_combine_transactions_in_#{key}") do |params|
89
+ WechatPay::Ecommerce.send("invoke_combine_transactions_in_#{key}", params)
90
+ end
91
+ end
92
+
93
+ define_combine_transaction_method('app', 'app', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_1.shtml')
94
+ define_combine_transaction_method('js', 'jsapi', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_3.shtml')
95
+ define_combine_transaction_method('h5', 'h5', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_2.shtml')
96
+ define_combine_transaction_method('miniprogram', 'jsapi', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_4.shtml')
97
+ define_combine_transaction_method('native', 'native', 'https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_5.shtml')
98
+
99
+ QUERY_COMBINE_ORDER_FIELDS = %i[combine_out_trade_no].freeze # :nodoc:
100
+ #
101
+ # 合单查询
102
+ #
103
+ # TODO: 与电商平台相同,稍后重构
104
+ #
105
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_11.shtml
106
+ #
107
+ # ``` ruby
108
+ # WechatPay::Direct.query_order(combine_out_trade_no: 'C202104302474')
109
+ # ```
110
+ #
111
+ def self.query_combine_order(params)
112
+ combine_out_trade_no = params.delete(:combine_out_trade_no)
113
+
114
+ url = "/v3/combine-transactions/out-trade-no/#{combine_out_trade_no}"
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
+ CLOSE_COMBINE_ORDER_FIELDS = %i[combine_out_trade_no sub_orders].freeze # :nodoc:
128
+ #
129
+ # 关闭合单
130
+ #
131
+ # TODO: 与电商平台相同,稍后重构
132
+ #
133
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_11.shtml
134
+ #
135
+ # ``` ruby
136
+ # WechatPay::Direct.close_combine_order(combine_out_trade_no: 'C202104302474')
137
+ # ```
138
+ def self.close_combine_order(params)
139
+ combine_out_trade_no = params.delete(:combine_out_trade_no)
140
+
141
+ url = "/v3/combine-transactions/out-trade-no/#{combine_out_trade_no}/close"
142
+
143
+ payload = {
144
+ combine_appid: WechatPay.app_id
145
+ }.merge(params)
146
+
147
+ payload_json = payload.to_json
148
+
149
+ method = 'POST'
150
+
151
+ make_request(
152
+ method: method,
153
+ for_sign: payload_json,
154
+ payload: payload_json,
155
+ path: url
156
+ )
157
+ end
158
+
159
+ QUERY_ORDER_FIELDS = %i[out_trade_no transaction_id].freeze # :nodoc:
160
+ #
161
+ # 直连订单查询
162
+ #
163
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_2.shtml
164
+ #
165
+ # Example:
166
+ #
167
+ # ``` ruby
168
+ # WechatPay::Direct.query_order(transaction_id: '4323400972202104305133344444') # by transaction_id
169
+ # WechatPay::Direct.query_order(out_trade_no: 'N202104302474') # by out_trade_no
170
+ # ```
171
+ #
172
+ def self.query_order(params)
173
+ if params[:transaction_id]
174
+ params.delete(:out_trade_no)
175
+ transaction_id = params.delete(:transaction_id)
176
+ path = "/v3/pay/transactions/id/#{transaction_id}"
177
+ else
178
+ params.delete(:transaction_id)
179
+ out_trade_no = params.delete(:out_trade_no)
180
+ path = "/v3/pay/transactions/out-trade-no/#{out_trade_no}"
181
+ end
182
+
183
+ params = params.merge({
184
+ mchid: WechatPay.mch_id
185
+ })
186
+
187
+ method = 'GET'
188
+ query = build_query(params)
189
+ url = "#{path}?#{query}"
190
+
191
+ make_request(
192
+ method: method,
193
+ path: url,
194
+ extra_headers: {
195
+ 'Content-Type' => 'application/x-www-form-urlencoded'
196
+ }
197
+ )
198
+ end
199
+
200
+ CLOSE_ORDER_FIELDS = %i[out_trade_no].freeze # :nodoc:
201
+ #
202
+ # 关闭订单
203
+ #
204
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_3.shtml
205
+ #
206
+ # Example:
207
+ #
208
+ # ``` ruby
209
+ # WechatPay::Direct.close_order(out_trade_no: 'N3344445')
210
+ # ```
211
+ #
212
+ def self.close_order(params)
213
+ out_trade_no = params.delete(:out_trade_no)
214
+ url = "/v3/pay/transactions/out-trade-no/#{out_trade_no}/close"
215
+ params = params.merge({
216
+ mchid: WechatPay.mch_id
217
+ })
218
+
219
+ method = 'POST'
220
+
221
+ make_request(
222
+ method: method,
223
+ path: url,
224
+ for_sign: params.to_json,
225
+ payload: params.to_json
226
+ )
227
+ end
228
+
229
+ INVOKE_REFUND_FIELDS = %i[transaction_id out_trade_no out_refund_no amount].freeze # :nodoc:
230
+ #
231
+ # 退款申请
232
+ #
233
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_9.shtml
234
+ #
235
+ # Example:
236
+ #
237
+ # ``` ruby
238
+ # WechatPay::Direct.invoke_refund(transaction_id: '4323400972202104305131070170', total: 1, refund: 1, out_refund_no: 'R10000')
239
+ # WechatPay::Direct.invoke_refund(out_trade_no: 'N2021', total: 1, refund: 1, out_refund_no: 'R10000').body
240
+ # ```
241
+ def self.invoke_refund(params)
242
+ url = '/v3/refund/domestic/refunds'
243
+ method = 'POST'
244
+ amount = {
245
+ refund: params.delete(:refund),
246
+ total: params.delete(:total),
247
+ currency: 'CNY'
248
+ }
249
+
250
+ params = params.merge({
251
+ amount: amount
252
+ })
253
+
254
+ make_request(
255
+ path: url,
256
+ method: method,
257
+ for_sign: params.to_json,
258
+ payload: params.to_json
259
+ )
260
+ end
261
+
262
+ QUERY_REFUND_FIELDS = %i[sub_mchid refund_id out_refund_no].freeze # :nodoc:
263
+ #
264
+ # 直连退款查询
265
+ #
266
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_10.shtml
267
+ #
268
+ # Example:
269
+ #
270
+ # ``` ruby
271
+ # WechatPay::Direct.query_refund(out_refund_no: 'R10000')
272
+ # ```
273
+ #
274
+ def self.query_refund(params)
275
+ out_refund_no = params.delete(:out_refund_no)
276
+ url = "/v3/refund/domestic/refunds/#{out_refund_no}"
277
+
278
+ method = 'GET'
279
+
280
+ make_request(
281
+ method: method,
282
+ path: url,
283
+ extra_headers: {
284
+ 'Content-Type' => 'application/x-www-form-urlencoded'
285
+ }
286
+ )
287
+ end
288
+
289
+ TRADEBILL_FIELDS = [:bill_date].freeze # :nodoc:
290
+ #
291
+ # 直连申请交易账单
292
+ #
293
+ # Todo: 跟商户平台接口相同
294
+ #
295
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_6.shtml
296
+ #
297
+ # Example:
298
+ #
299
+ # ``` ruby
300
+ # WechatPay::direct.tradebill(bill_date: '2021-04-30')
301
+ # ```
302
+ def self.tradebill(params)
303
+ path = '/v3/bill/tradebill'
304
+ method = 'GET'
305
+
306
+ query = build_query(params)
307
+ url = "#{path}?#{query}"
308
+
309
+ make_request(
310
+ path: url,
311
+ method: method,
312
+ extra_headers: {
313
+ 'Content-Type' => 'application/x-www-form-urlencoded'
314
+ }
315
+ )
316
+ end
317
+
318
+ FUNDFLOWBILL_FIELDS = [:bill_date].freeze # :nodoc:
319
+ #
320
+ # 申请资金账单
321
+ #
322
+ # Todo: 跟商户平台接口相同
323
+ #
324
+ # Document: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_7.shtml
325
+ #
326
+ # Example:
327
+ #
328
+ # ``` ruby
329
+ # WechatPay::Direct.fundflowbill(bill_date: '2021-04-30')
330
+ # ```
331
+ #
332
+ def self.fundflowbill(params)
333
+ path = '/v3/bill/fundflowbill'
334
+ method = 'GET'
335
+
336
+ query = build_query(params)
337
+ url = "#{path}?#{query}"
338
+
339
+ make_request(
340
+ path: url,
341
+ method: method,
342
+ extra_headers: {
343
+ 'Content-Type' => 'application/x-www-form-urlencoded'
344
+ }
345
+ )
346
+ end
347
+
348
+ class << self
349
+ private
350
+
351
+ def direct_transactions_method_by_suffix(suffix, params)
352
+ url = "/v3/pay/transactions/#{suffix}"
353
+ method = 'POST'
354
+
355
+ params = {
356
+ mchid: WechatPay.mch_id,
357
+ appid: WechatPay.app_id
358
+ }.merge(params)
359
+
360
+ payload_json = params.to_json
361
+
362
+ make_request(
363
+ method: method,
364
+ path: url,
365
+ for_sign: payload_json,
366
+ payload: payload_json
367
+ )
368
+ end
369
+ end
370
+ end
371
+ end