wechat_payment 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +215 -13
  3. data/Rakefile +5 -0
  4. data/app/assets/config/wechat_payment_manifest.js +1 -0
  5. data/app/assets/stylesheets/wechat_payment/application.css +15 -0
  6. data/app/controllers/wechat_payment/application_controller.rb +5 -0
  7. data/app/controllers/wechat_payment/callback_controller.rb +30 -0
  8. data/app/helpers/wechat_payment/application_helper.rb +4 -0
  9. data/app/jobs/wechat_payment/application_job.rb +4 -0
  10. data/app/mailers/wechat_payment/application_mailer.rb +6 -0
  11. data/app/models/wechat_payment/application_record.rb +5 -0
  12. data/app/models/wechat_payment/payment_order.rb +228 -0
  13. data/app/models/wechat_payment/refund_order.rb +127 -0
  14. data/app/services/wechat_payment/service.rb +74 -0
  15. data/app/views/layouts/wechat_payment/application.html.erb +15 -0
  16. data/config/routes.rb +4 -0
  17. data/db/migrate/20210706075217_create_wechat_payment_payment_orders.rb +22 -0
  18. data/db/migrate/20210706095205_create_wechat_payment_refund_orders.rb +20 -0
  19. data/lib/generators/wechat_payment/goods/USAGE +8 -0
  20. data/lib/generators/wechat_payment/goods/goods_generator.rb +5 -0
  21. data/lib/generators/wechat_payment/install/USAGE +8 -0
  22. data/lib/generators/wechat_payment/install/install_generator.rb +86 -0
  23. data/lib/generators/wechat_payment/install/templates/initializer.rb +19 -0
  24. data/lib/generators/wechat_payment/routes/USAGE +8 -0
  25. data/lib/generators/wechat_payment/routes/routes_generator.rb +7 -0
  26. data/lib/tasks/wechat_payment_tasks.rake +10 -0
  27. data/lib/wechat_payment.rb +40 -2
  28. data/lib/wechat_payment/client.rb +272 -0
  29. data/lib/wechat_payment/concern/goods.rb +80 -0
  30. data/lib/wechat_payment/concern/user.rb +22 -0
  31. data/lib/wechat_payment/concern/user_goods.rb +14 -0
  32. data/lib/wechat_payment/engine.rb +7 -0
  33. data/lib/wechat_payment/invoke_result.rb +35 -0
  34. data/lib/wechat_payment/missing_key_error.rb +4 -0
  35. data/lib/wechat_payment/r_logger.rb +36 -0
  36. data/lib/wechat_payment/service_result.rb +49 -0
  37. data/lib/wechat_payment/sign.rb +41 -0
  38. data/lib/wechat_payment/version.rb +1 -1
  39. metadata +68 -9
  40. data/lib/wechat_payment/railtie.rb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7c25024b1f10c62e7ddd1d03489d9d6464f51f1e0540a981574685099ed0c9e
4
- data.tar.gz: ba71d84612d41cdde00a24994237b2d9339d47689e61005695bbb0a15c5f4dda
3
+ metadata.gz: 2a2e445d0e1ce0d2cc83eef761a18bbd90ade6264e2798e2ce0a1bc1756b9139
4
+ data.tar.gz: bcfbaf1b877b6a7656cb378a894293c593e96862b018c9415eac13ed00063961
5
5
  SHA512:
6
- metadata.gz: '090abcf0b7ffacad8b5dd22bf5cd0d82938b68a6b4d156ffda335e3672f2c3136e8bf63fa1961d465d74561c951bf86876836154c11ef69d1577e18f46d439f7'
7
- data.tar.gz: 5c391978e493f2ca85f61595c758bb209d14f2700205150d5156e65e88b230fadd1ebfd4899f071a53ed415b314cc02d4ec0826a09183c036aac53150acc0aca
6
+ metadata.gz: e3157ecac086e7f9d491d1359f4ce3de8208068f277a041c9a57d9b20abd6406d1648fce5d11c25f6cbc9d8f6aea64b24e2477383c73a775212b2ac11d42bf2c
7
+ data.tar.gz: 3e41d27212801e96e9c359cb60a6a51f6c7e63cd4dbcbcd3deaba2c0ee72c13a55423e538422b1ff952602ef05717cb5df3c6d0a75dda131c6f3befff9aa6dcd
data/README.md CHANGED
@@ -1,28 +1,230 @@
1
1
  # WechatPayment
2
- Short description and motivation.
2
+ 微信支付的 engine
3
3
 
4
- ## Usage
5
- How to use my plugin.
6
4
 
7
- ## Installation
8
- Add this line to your application's Gemfile:
5
+ ## Convention
6
+ 在使用该 Engine 之前,需要先建立`用户模型`,`商品模型`,`用户商品关联模型`,用户表需要有 open_id 字段,
7
+ 商品表需要有 price 和 name 字段。假设`商品模型`为`Product`,则用户和商品之间的关联模型为`UserProduct`,
8
+ 关联模型属于`user`和`product`。
9
+
10
+ ## Getting Started
11
+ 1. 在`Gemfile`中添加
12
+ ```ruby
13
+ gem 'wechat_payment'
14
+ ```
15
+
16
+ 2. 执行 `bundle install`
17
+
18
+ 3. 初始化
19
+ ```bash
20
+ # rails g wechat_payment:install [GoodsModel] [UserModel]
21
+ $ rails g wechat_payment:install Product User
22
+ $ rails db:migrate
23
+ ```
24
+
25
+ ## 配置
26
+ 打开 `config/initializers/wechat_payment.rb`,將里边的配置改成你自己的小程序配置
27
+
28
+ ## 支付
29
+ 用户模型具有`buy`方法,而商品模型有`sell`方法,用户购买商品或商品售出都会产生一张订单,
30
+ 调用订单的`pay`方法则会向微信发起下单请求,并将处理后的结果返回,此时返回的是一个`ServiceResult`对象,
31
+ 执行该对象的`as_json`方法,得到的就是小程序需要拉起支付的参数
32
+
33
+ ```ruby
34
+ user = User.first
35
+
36
+ product = Product.first
37
+
38
+ # 微信要传一个用户调起支付的 ip: spbill_create_ip ,文档上写必传,但是传空、固定ip、不传都不会报错,
39
+ # 但是还是给他加上了,保存到用户模型的 last_spbill_create_ip 属性上,实际使用中可以
40
+ # 在控制器里边通过 request.remote_ip 获取请求的 ip
41
+ #
42
+ # user.spbill_create_ip = request.remote_ip
43
+ client_ip = "45.12.112.31"
44
+ user.spbill_create_ip = client_ip
45
+
46
+ order = user.buy(product)
47
+
48
+ result = order.pay
49
+
50
+ # 返回给前端调起支付的数据
51
+ result.as_json
52
+ ```
53
+
54
+ ## 退款
55
+ 调用订单的`refund`,参数是退款金额,默认全额退款,执行成功后会创建一张退款订单,此时退款订单状态为 pending,
56
+ 并返回退款下单的结果(`SerivceResult`对象),如果成功,触发用户商品关联模型的`refund_apply_success`,
57
+ 失败则触发`refund_apply_failure`
9
58
 
10
59
  ```ruby
11
- gem 'wechat_payment'
60
+ payment_order = User.first.payment_orders.last
61
+ result = payment_order.refund
62
+
63
+ if result.success?
64
+ # do something on success
65
+ else
66
+ # do something on failure
67
+ end
12
68
  ```
13
69
 
14
- And then execute:
15
- ```bash
16
- $ bundle
70
+ ## 事件
71
+ 当支付进行时,会产生一些相应的事件,事件会触发相应的钩子,如果有事先定义的话。钩子方法需定义在用户商品关联(如`UserProduct`)模型中,
72
+ 且每个钩子接收一个参数 result ,事件和钩子的对应关系如下:
73
+
74
+ | 事件 | 钩子 |
75
+ | :-------------- | :--------------- |
76
+ | 支付下单成功 | payment_apply_success |
77
+ | 支付下单失败 | payment_apply_failure |
78
+ | 支付成功(回调) | payment_exec_success |
79
+ | 支付失败(回调) | payment_exec_failure |
80
+ | 申请退款成功 | refund_apply_success |
81
+ | 申请退款失败 | refund_apply_failure |
82
+ | 退款成功(回调) | refund_exec_success |
83
+ | 退款失败(回调) | refund_exec_failure |
84
+
85
+ 一个简单的事件处理的例子
86
+ ```ruby
87
+ # app/model/user_product.rb
88
+ class UserProduct
89
+ # result 结构见下文说明
90
+ def payment_exec_success(result)
91
+ update(state: :paid)
92
+ end
93
+ end
17
94
  ```
18
95
 
19
- Or install it yourself as:
20
- ```bash
21
- $ gem install wechat_payment
96
+ 每个钩子接收的参数结构可能不一样:
97
+
98
+ ### payment_apply_success:
99
+ ```ruby
100
+ {
101
+ "return_code"=>"SUCCESS",
102
+ "return_msg"=>"OK",
103
+ "result_code"=>"SUCCESS",
104
+ "mch_id"=>"12312412312",
105
+ "appid"=>"wxc5acd06cc6a7ac12",
106
+ "sub_mch_id"=>"1526921451",
107
+ "sub_appid"=>"wxf89f912345823dcd",
108
+ "nonce_str"=>"ZUN2rEf6ATgYU8Lr",
109
+ "sign"=>"3A216DB61196CE2313CE21182D53FD1833F",
110
+ "prepay_id"=>"wx2815535651598812181c452eb3f26b90000",
111
+ "trade_type"=>"JSAPI"
112
+ }
22
113
  ```
23
114
 
115
+ ### payment_apply_failure:
116
+ ```ruby
117
+ {
118
+ "return_code"=>"SUCCESS",
119
+ "return_msg"=>"OK",
120
+ "result_code"=>"FAIL",
121
+ "err_code_des"=>"该订单已支付",
122
+ "err_code"=>"ORDERPAID",
123
+ "mch_id"=>"12312412312",
124
+ "appid"=>"wxc5acd06cc6a7ac12",
125
+ "sub_mch_id"=>"1526921451",
126
+ "sub_appid"=>"wxf89f912345823dcd",
127
+ "nonce_str"=>"1jWLkg2YZjwnOozl",
128
+ "sign"=>"3C210A1C9BD6CFDB7C37CCFCA1AAF9E274"
129
+ }
130
+ ```
131
+
132
+ ### payment_exec_success:
133
+ ```ruby
134
+ {
135
+ "appid" => "wxc5acd06cc6a7ac12",
136
+ "bank_type" => "CMB_CREDIT",
137
+ "cash_fee" => "1",
138
+ "fee_type" => "CNY",
139
+ "is_subscribe" => "N",
140
+ "mch_id" => "12312412312",
141
+ "nonce_str" => "e42ad44489a1f4e6f8a09d1299cfa59f6",
142
+ "openid" => "oef2nsaOcYcBYrNq1x9eUucKy7NQ",
143
+ "out_trade_no" => "16267654023120174189",
144
+ "result_code" => "SUCCESS",
145
+ "return_code" => "SUCCESS",
146
+ "sign" => "608972A854024030332360B1E79511B4",
147
+ "sub_appid" => "wxf89f912345823dcd",
148
+ "sub_is_subscribe" => "N",
149
+ "sub_mch_id" => "1526921451",
150
+ "sub_openid" => "oef2nsaOcYcBYrNq1x9eUucKy7NQ",
151
+ "time_end" => "20210720151228",
152
+ "total_fee" => "1",
153
+ "trade_type" => "JSAPI",
154
+ "transaction_id" => "4200001153233202137201270712453"
155
+ }
156
+ ```
157
+
158
+ ### payment_exec_failure:
159
+ TODO 待补充
160
+
161
+ ### refund_apply_success:
162
+ ```ruby
163
+ {
164
+ "return_code"=>"SUCCESS",
165
+ "return_msg"=>"OK",
166
+ "appid"=>"wxc5acd06cc6a7ac12",
167
+ "mch_id"=>"12312412312",
168
+ "sub_mch_id"=>"1526921451",
169
+ "nonce_str"=>"RsXVcs0GMg2p5NRD",
170
+ "sign"=>"F10AB3929B900DE4E189CA93B12D9D7A",
171
+ "result_code"=>"SUCCESS",
172
+ "transaction_id"=>"420000119112202101210049902399",
173
+ "out_trade_no"=>"1624867410141191608",
174
+ "out_refund_no"=>"16248674504421685776",
175
+ "refund_id"=>"50301108952025421210183695009",
176
+ "refund_channel"=>"",
177
+ "refund_fee"=>"1",
178
+ "coupon_refund_fee"=>"0",
179
+ "total_fee"=>"1",
180
+ "cash_fee"=>"1",
181
+ "coupon_refund_count"=>"0",
182
+ "cash_refund_fee"=>"1"
183
+ }
184
+ ```
185
+
186
+ ### refund_apply_failure:
187
+ ```ruby
188
+ {
189
+ "return_code"=>"SUCCESS",
190
+ "return_msg"=>"OK",
191
+ "appid"=>"wxc5acd06cc6a7ac12",
192
+ "mch_id"=>"12312412312",
193
+ "sub_mch_id"=>"1526921451",
194
+ "nonce_str"=>"gMDFilvaKanXW80W",
195
+ "sign"=>"BA24E81B18B12AAC112DF9F84CA5E21",
196
+ "result_code"=>"FAIL",
197
+ "err_code"=>"INVALID_REQUEST",
198
+ "err_code_des"=>"订单已全额退款"
199
+ }
200
+
201
+ ```
202
+
203
+ ### refund_exec_success:
204
+ ```ruby
205
+ {
206
+ "out_refund_no"=>"16421102919350974",
207
+ "out_trade_no"=>"1626761001219492162",
208
+ "refund_account"=>"REFUND_SOURCE_RECHARGE_FUNDS",
209
+ "refund_fee"=>"1",
210
+ "refund_id"=>"503014086722221012110826014924",
211
+ "refund_recv_accout"=>"招商银行信用卡4012",
212
+ "refund_request_source"=>"API",
213
+ "refund_status"=>"SUCCESS",
214
+ "settlement_refund_fee"=>"1",
215
+ "settlement_total_fee"=>"1",
216
+ "success_time"=>"2021-07-20 15:11:52",
217
+ "total_fee"=>"1",
218
+ "transaction_id"=>"42000011872021002028121998431"
219
+ }
220
+ ```
221
+
222
+ ### refund_exec_failure:
223
+ TODO 待补充
224
+
225
+
24
226
  ## Contributing
25
227
  Contribution directions go here.
26
228
 
27
229
  ## License
28
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
230
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,5 +1,10 @@
1
1
  require "bundler/setup"
2
2
 
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
3
8
  require "bundler/gem_tasks"
4
9
 
5
10
  require "rake/testtask"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/wechat_payment .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ module WechatPayment
2
+ class ApplicationController < ActionController::Base
3
+ skip_forgery_protection
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+
2
+ module WechatPayment
3
+ class CallbackController < WechatPayment::ApplicationController
4
+
5
+ # 微信支付回调
6
+ def payment
7
+ result = Hash.from_xml(request.body.read)["xml"]
8
+
9
+ process_result = WechatPayment::Service.handle_payment_notify(result)
10
+ if process_result.success?
11
+ render xml: { return_code: "SUCCESS" }.to_xml(root: 'xml', dasherize: false)
12
+ else
13
+ render xml: { return_code: "FAIL", return_msg: process_result.errors.first }.to_xml(root: 'xml', dasherize: false)
14
+ end
15
+ end
16
+
17
+ def refund
18
+ callback_info = Hash.from_xml(request.body.read)["xml"]["req_info"]
19
+ refund_result = WechatPayment::Service.handle_refund_notify(callback_info)
20
+
21
+ if refund_result.success?
22
+ render xml: {return_code: "SUCCESS"}.to_xml(root: 'xml', dasherize: false)
23
+ else
24
+ render xml: {return_code: "FAIL", return_msg: refund_result.errors.first}.to_xml(root: 'xml', dasherize: false)
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module WechatPayment
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module WechatPayment
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module WechatPayment
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module WechatPayment
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,228 @@
1
+ module WechatPayment
2
+ class PaymentOrder < ApplicationRecord
3
+
4
+ has_many :refund_orders
5
+ belongs_to :customer, polymorphic: true
6
+
7
+ before_save :set_customer_info
8
+ before_create :gen_out_trade_no
9
+ belongs_to :goods, polymorphic: true
10
+
11
+ enum state: {
12
+ paid: "paid",
13
+ pending: "pending",
14
+ refunded: "refunded",
15
+ failed: "failed"
16
+ }, _default: "pending"
17
+
18
+ # 将部分用户信息保存至订单
19
+ def set_customer_info
20
+ self.open_id = customer.open_id if open_id.blank?
21
+ self.spbill_create_ip = customer.spbill_create_ip
22
+ end
23
+
24
+ # 生成交易编号
25
+ def gen_out_trade_no
26
+ loop do
27
+ out_trade_no = "#{Time.current.to_i}#{SecureRandom.random_number(999_999_999)}"
28
+ records_count = WechatPayment::PaymentOrder.where(out_trade_no: out_trade_no).count
29
+ if records_count == 0
30
+ self.out_trade_no = out_trade_no
31
+ break
32
+ end
33
+ end
34
+ end
35
+
36
+ # 创建退款订单
37
+ def create_refund_order(refund_fee)
38
+ refund_orders.create(
39
+ out_trade_no: out_trade_no,
40
+ refund_fee: refund_fee,
41
+ total_fee: total_fee,
42
+ goods: goods,
43
+ customer: customer
44
+ )
45
+ end
46
+
47
+ def as_order_params
48
+ {
49
+ out_trade_no: out_trade_no,
50
+ spbill_create_ip: spbill_create_ip,
51
+ total_fee: total_fee,
52
+ body: body,
53
+ openid: open_id
54
+ }
55
+ end
56
+
57
+ # 发起支付
58
+ #
59
+ # @return [Hash]
60
+ #
61
+ # return example
62
+ # {
63
+ # "appId": "wxf89f9547da823dcd",
64
+ # "package": "prepay_id=wx28180521320799e04f6028c55c31bf0000",
65
+ # "nonceStr": "62350ff6c414946d0dc4c49b32ad3fd3",
66
+ # "timeStamp": "1624874721",
67
+ # "signType": "MD5",
68
+ # "paySign": "1F5CBC345B86E5DD055F235A22961422",
69
+ # "orderId": 17
70
+ # }
71
+ def pay
72
+ order_result = WechatPayment::Service.new(self).order
73
+ if order_result.success?
74
+ payload = WechatPayment::Client.gen_js_pay_payload(order_result.data).merge(orderId: id).with_indifferent_access
75
+ WechatPayment::ServiceResult.new(success: true, data: payload)
76
+ else
77
+ order_result
78
+ end
79
+ end
80
+
81
+ # 重新支付订单
82
+ def repay
83
+ gen_out_trade_no
84
+ save
85
+ pay
86
+ end
87
+
88
+ # 发起退款
89
+ # @param [Integer] refund_fee 需要退款的金额,单位:分
90
+ def refund(refund_fee = total_fee)
91
+ WechatPayment::Service.new(self).refund(refund_fee)
92
+ end
93
+
94
+ # 判断余额是否足够退款
95
+ def balance_enough_to_refund?(refund_fee)
96
+ total_fee - refunded_fee >= refund_fee
97
+ end
98
+
99
+ # 已退款的金额(包括正在退款的金额)
100
+ def refunded_fee
101
+ refund_orders.where(state: [:pending, :refunded]).sum(:refund_fee)
102
+ end
103
+
104
+ # 实际已退的金额
105
+ def actual_refunded_fee
106
+ refund_orders.where(state: :refunded).sum(:refund_fee)
107
+ end
108
+
109
+ # 订单是否可以退款
110
+ def refundable?
111
+ min_refund_fee = 1
112
+ paid? && balance_enough_to_refund?(min_refund_fee)
113
+ end
114
+
115
+ # 支付下单成功
116
+ # @param [Hash] result
117
+ #
118
+ # result example:
119
+ #
120
+ # {
121
+ # "return_code"=>"SUCCESS",
122
+ # "return_msg"=>"OK",
123
+ # "result_code"=>"SUCCESS",
124
+ # "mch_id"=>"12312412312",
125
+ # "appid"=>"wxc5f26065c6471234",
126
+ # "sub_mch_id"=>"1525911234",
127
+ # "sub_appid"=>"wxf89f912345823dcd",
128
+ # "nonce_str"=>"ZUN2rEf6ATgYU8Lr",
129
+ # "sign"=>"3A216DB61196CEC63CE282D53FD1833F",
130
+ # "prepay_id"=>"wx281553565159884f81c452eb3f26b90000",
131
+ # "trade_type"=>"JSAPI"
132
+ # }
133
+ def payment_apply_success(result)
134
+ update(prepay_id: result["prepay_id"])
135
+
136
+ if goods.respond_to? :payment_apply_success
137
+ goods.payment_apply_success(result)
138
+ end
139
+
140
+ result
141
+ end
142
+
143
+ # 支付下单失败
144
+ # @param [Hash] result
145
+ #
146
+ # result example:
147
+ #
148
+ # {
149
+ # "return_code"=>"SUCCESS",
150
+ # "return_msg"=>"OK",
151
+ # "result_code"=>"FAIL",
152
+ # "err_code_des"=>"该订单已支付",
153
+ # "err_code"=>"ORDERPAID",
154
+ # "mch_id"=>"1363241802",
155
+ # "appid"=>"wxc5f26065c6471bcf",
156
+ # "sub_mch_id"=>"1525918291",
157
+ # "sub_appid"=>"wxf89f9547da823dcd",
158
+ # "nonce_str"=>"1jWLkg2YZjwnOozl",
159
+ # "sign"=>"3C80A1C9BD6CFDB7C37CCFCEAAF9E274"
160
+ # }
161
+ def payment_apply_failure(result)
162
+ update(state: :failed)
163
+
164
+ if goods.respond_to? :payment_apply_failure
165
+ goods.payment_apply_failure(result)
166
+ end
167
+
168
+ result
169
+ end
170
+
171
+ # 支付成功(回调结果)
172
+ # @param [Hash] result
173
+ #
174
+ # result example:
175
+ #
176
+ # {
177
+ # "appid"=>"wxc5e21215c6471bcf",
178
+ # "bank_type"=>"CMB_CREDIT",
179
+ # "cash_fee"=>"1",
180
+ # "fee_type"=>"CNY",
181
+ # "is_subscribe"=>"N",
182
+ # "mch_id"=>"144223114",
183
+ # "nonce_str"=>"026b7ff3433f482f98610a67bbd8e159",
184
+ # "openid"=>"omf2nv3OgYXBYrNqdx9eUucKy7NQ",
185
+ # "out_trade_no"=>"1624866836218932068",
186
+ # "result_code"=>"SUCCESS",
187
+ # "return_code"=>"SUCCESS",
188
+ # "sign"=>"442098E6B670B82B88A843CC2A2AB54D",
189
+ # "sub_appid"=>"wxf89f95121da823dcd",
190
+ # "sub_is_subscribe"=>"N",
191
+ # "sub_mch_id"=>"1525911211",
192
+ # "sub_openid"=>"ogT7J5YddGnll-ippRvJq62Nv8W0",
193
+ # "time_end"=>"20210628155426",
194
+ # "total_fee"=>"1",
195
+ # "trade_type"=>"JSAPI",
196
+ # "transaction_id"=>"4200001174202106282207291730"
197
+ # }
198
+ def payment_exec_success(result)
199
+ update(
200
+ state: :paid,
201
+ transaction_id: result["transaction_id"],
202
+ paid_at: Time.current
203
+ )
204
+
205
+ if goods.respond_to? :payment_exec_success
206
+ goods.payment_exec_success(result)
207
+ end
208
+
209
+ result
210
+ end
211
+
212
+ # 支付失败(回调结果)
213
+ # @param [Hash] result
214
+ #
215
+ # result example:
216
+ #
217
+ def payment_exec_failure(result)
218
+ if goods.respond_to? :payment_exec_failure
219
+ goods.payment_exec_failure(result)
220
+ end
221
+ end
222
+
223
+ # 判断是否已经全额退款
224
+ def total_fee_refunded?
225
+ refunded_fee >= total_fee
226
+ end
227
+ end
228
+ end