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.
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