wechat_payment 0.1.0 → 0.2.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 +4 -4
- data/README.md +215 -13
- data/Rakefile +5 -0
- data/app/assets/config/wechat_payment_manifest.js +1 -0
- data/app/assets/stylesheets/wechat_payment/application.css +15 -0
- data/app/controllers/wechat_payment/application_controller.rb +5 -0
- data/app/controllers/wechat_payment/callback_controller.rb +30 -0
- data/app/helpers/wechat_payment/application_helper.rb +4 -0
- data/app/jobs/wechat_payment/application_job.rb +4 -0
- data/app/mailers/wechat_payment/application_mailer.rb +6 -0
- data/app/models/wechat_payment/application_record.rb +5 -0
- data/app/models/wechat_payment/payment_order.rb +228 -0
- data/app/models/wechat_payment/refund_order.rb +127 -0
- data/app/services/wechat_payment/service.rb +74 -0
- data/app/views/layouts/wechat_payment/application.html.erb +15 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20210706075217_create_wechat_payment_payment_orders.rb +22 -0
- data/db/migrate/20210706095205_create_wechat_payment_refund_orders.rb +20 -0
- data/lib/generators/wechat_payment/goods/USAGE +8 -0
- data/lib/generators/wechat_payment/goods/goods_generator.rb +5 -0
- data/lib/generators/wechat_payment/install/USAGE +8 -0
- data/lib/generators/wechat_payment/install/install_generator.rb +86 -0
- data/lib/generators/wechat_payment/install/templates/initializer.rb +19 -0
- data/lib/generators/wechat_payment/routes/USAGE +8 -0
- data/lib/generators/wechat_payment/routes/routes_generator.rb +7 -0
- data/lib/tasks/wechat_payment_tasks.rake +10 -0
- data/lib/wechat_payment.rb +40 -2
- data/lib/wechat_payment/client.rb +272 -0
- data/lib/wechat_payment/concern/goods.rb +80 -0
- data/lib/wechat_payment/concern/user.rb +22 -0
- data/lib/wechat_payment/concern/user_goods.rb +14 -0
- data/lib/wechat_payment/engine.rb +7 -0
- data/lib/wechat_payment/invoke_result.rb +35 -0
- data/lib/wechat_payment/missing_key_error.rb +4 -0
- data/lib/wechat_payment/r_logger.rb +36 -0
- data/lib/wechat_payment/service_result.rb +49 -0
- data/lib/wechat_payment/sign.rb +41 -0
- data/lib/wechat_payment/version.rb +1 -1
- metadata +68 -9
- data/lib/wechat_payment/railtie.rb +0 -4
@@ -0,0 +1,127 @@
|
|
1
|
+
module WechatPayment
|
2
|
+
class RefundOrder < ApplicationRecord
|
3
|
+
belongs_to :payment_order
|
4
|
+
|
5
|
+
before_create :gen_out_refund_no
|
6
|
+
|
7
|
+
belongs_to :goods, polymorphic: true
|
8
|
+
belongs_to :customer, polymorphic: true
|
9
|
+
|
10
|
+
enum state: {
|
11
|
+
pending: "pending",
|
12
|
+
refunded: "refunded",
|
13
|
+
failed: "failed"
|
14
|
+
}, _default: :pending
|
15
|
+
|
16
|
+
# 生成退款编号
|
17
|
+
def gen_out_refund_no
|
18
|
+
loop do
|
19
|
+
out_refund_no = "#{Time.current.to_i}#{SecureRandom.random_number(999_999_999)}"
|
20
|
+
record = WechatPayment::RefundOrder.find_by(out_refund_no: out_refund_no)
|
21
|
+
|
22
|
+
if record.blank?
|
23
|
+
self.out_refund_no = out_refund_no
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_refund_params
|
30
|
+
slice(:out_trade_no, :out_refund_no, :refund_fee, :total_fee).to_options
|
31
|
+
end
|
32
|
+
# 发起退款成功
|
33
|
+
# @param [Hash] result
|
34
|
+
#
|
35
|
+
# result example:
|
36
|
+
#
|
37
|
+
# {
|
38
|
+
# "return_code"=>"SUCCESS",
|
39
|
+
# "return_msg"=>"OK",
|
40
|
+
# "appid"=>"wxc5f2606121234cf",
|
41
|
+
# "mch_id"=>"1363241234",
|
42
|
+
# "sub_mch_id"=>"1525912341",
|
43
|
+
# "nonce_str"=>"RsXVcs0GMg2p5NRD",
|
44
|
+
# "sign"=>"F10AB3929B900DE4E189CA93B73D9D7A",
|
45
|
+
# "result_code"=>"SUCCESS",
|
46
|
+
# "transaction_id"=>"4200001199202106280049902399",
|
47
|
+
# "out_trade_no"=>"1624867410475591608",
|
48
|
+
# "out_refund_no"=>"1624867450917685776",
|
49
|
+
# "refund_id"=>"50301108952021062810183695009",
|
50
|
+
# "refund_channel"=>"",
|
51
|
+
# "refund_fee"=>"1",
|
52
|
+
# "coupon_refund_fee"=>"0",
|
53
|
+
# "total_fee"=>"1",
|
54
|
+
# "cash_fee"=>"1",
|
55
|
+
# "coupon_refund_count"=>"0",
|
56
|
+
# "cash_refund_fee"=>"1"
|
57
|
+
# }
|
58
|
+
def refund_apply_success(result)
|
59
|
+
if payment_order.goods.respond_to? :refund_apply_success
|
60
|
+
payment_order.goods.refund_apply_success(result)
|
61
|
+
end
|
62
|
+
|
63
|
+
update(
|
64
|
+
refund_id: result["refund_id"],
|
65
|
+
state: :pending
|
66
|
+
)
|
67
|
+
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
# 发起退款失败
|
72
|
+
def refund_apply_failure(result)
|
73
|
+
# TODO 没遇到过,待补充
|
74
|
+
|
75
|
+
if payment_order.goods.respond_to? :refund_apply_failure
|
76
|
+
payment_order.goods.refund_apply_failure(result)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# 退款成功(回调)
|
81
|
+
# @param [Hash] result
|
82
|
+
#
|
83
|
+
# result example:
|
84
|
+
#
|
85
|
+
# {
|
86
|
+
# "out_refund_no"=>"1624873658515277479",
|
87
|
+
# "out_trade_no"=>"1624873575281298144",
|
88
|
+
# "refund_account"=>"REFUND_SOURCE_RECHARGE_FUNDS",
|
89
|
+
# "refund_fee"=>"1",
|
90
|
+
# "refund_id"=>"50301308842021062810182580986",
|
91
|
+
# "refund_recv_accout"=>"招商银行信用卡4003",
|
92
|
+
# "refund_request_source"=>"API",
|
93
|
+
# "refund_status"=>"SUCCESS",
|
94
|
+
# "settlement_refund_fee"=>"1",
|
95
|
+
# "settlement_total_fee"=>"1",
|
96
|
+
# "success_time"=>"2021-06-28 17:47:47",
|
97
|
+
# "total_fee"=>"1",
|
98
|
+
# "transaction_id"=>"4200001202202106280268010129"
|
99
|
+
# }
|
100
|
+
def refund_exec_success(result)
|
101
|
+
update(
|
102
|
+
state: :refunded,
|
103
|
+
refunded_at: Time.current
|
104
|
+
)
|
105
|
+
|
106
|
+
if payment_order.total_fee_refunded?
|
107
|
+
payment_order.update(state: :refunded, refunded_at: Time.current)
|
108
|
+
end
|
109
|
+
|
110
|
+
if payment_order.goods.respond_to? :refund_exec_success
|
111
|
+
payment_order.goods.refund_exec_success(result)
|
112
|
+
end
|
113
|
+
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
# 退款失败(回调)
|
118
|
+
def refund_exec_failure(result)
|
119
|
+
# TODO 待补充
|
120
|
+
if payment_order.goods.respond_to? :refund_exec_failure
|
121
|
+
payment_order.goods.refund_exec_failure(result)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
module WechatPayment
|
3
|
+
class Service
|
4
|
+
attr_reader :client, :payment_order
|
5
|
+
|
6
|
+
def initialize(payment_order)
|
7
|
+
@client = WechatPayment::Client.new
|
8
|
+
@payment_order = payment_order
|
9
|
+
end
|
10
|
+
|
11
|
+
# 下单
|
12
|
+
def order
|
13
|
+
order_result = client.order(payment_order.as_order_params)
|
14
|
+
|
15
|
+
if order_result.success?
|
16
|
+
payment_order.payment_apply_success(order_result.data)
|
17
|
+
else
|
18
|
+
payment_order.payment_apply_failure(order_result.errors)
|
19
|
+
end
|
20
|
+
|
21
|
+
order_result
|
22
|
+
end
|
23
|
+
|
24
|
+
# 退款
|
25
|
+
def refund(refund_fee)
|
26
|
+
if !payment_order.balance_enough_to_refund?(refund_fee)
|
27
|
+
return WechatPayment::ServiceResult.new(message_type: :error, message: "Balance is not enough.")
|
28
|
+
end
|
29
|
+
|
30
|
+
refund_order = payment_order.create_refund_order(refund_fee)
|
31
|
+
refund_result = client.refund(refund_order.as_refund_params)
|
32
|
+
|
33
|
+
if refund_result.success?
|
34
|
+
refund_order.refund_apply_success(refund_result.data)
|
35
|
+
else
|
36
|
+
refund_order.refund_apply_failure(refund_result.errors)
|
37
|
+
end
|
38
|
+
|
39
|
+
refund_result
|
40
|
+
end
|
41
|
+
|
42
|
+
# 处理支付回调
|
43
|
+
def self.handle_payment_notify(notify_data)
|
44
|
+
result = WechatPayment::Client.handle_payment_notify(notify_data)
|
45
|
+
payment_order = WechatPayment::PaymentOrder.find_by(out_trade_no: notify_data["out_trade_no"])
|
46
|
+
|
47
|
+
if result.success? && payment_order.pending?
|
48
|
+
payment_order.with_lock do
|
49
|
+
payment_order.payment_exec_success(result.data)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
payment_order.payment_exec_failure(result.errors)
|
53
|
+
end
|
54
|
+
|
55
|
+
result
|
56
|
+
end
|
57
|
+
|
58
|
+
# 处理退款回调
|
59
|
+
def self.handle_refund_notify(notify_data)
|
60
|
+
result = WechatPayment::Client.handle_refund_notify(notify_data)
|
61
|
+
refund_order = WechatPayment::RefundOrder.find_by(out_refund_no: result.data["out_refund_no"])
|
62
|
+
|
63
|
+
if result.success? && refund_order.pending?
|
64
|
+
refund_order.with_lock do
|
65
|
+
refund_order.refund_exec_success(result.data)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
refund_order.refund_exec_failure(result.errors)
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateWechatPaymentPaymentOrders < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :wechat_payment_payment_orders do |t|
|
4
|
+
t.string :open_id
|
5
|
+
t.string :out_trade_no
|
6
|
+
t.references :goods, polymorphic: true, null: false
|
7
|
+
t.references :customer, polymorphic: true, null: false
|
8
|
+
t.string :transaction_id
|
9
|
+
t.string :body
|
10
|
+
t.integer :total_fee
|
11
|
+
t.string :trade_type
|
12
|
+
t.string :spbill_create_ip
|
13
|
+
t.string :prepay_id
|
14
|
+
t.string :state
|
15
|
+
t.datetime :paid_at
|
16
|
+
t.datetime :refunded_at
|
17
|
+
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
add_index :wechat_payment_payment_orders, :open_id
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CreateWechatPaymentRefundOrders < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :wechat_payment_refund_orders do |t|
|
4
|
+
t.integer :payment_order_id
|
5
|
+
t.references :goods, polymorphic: true, null: false
|
6
|
+
t.references :customer, polymorphic: true, null: false
|
7
|
+
t.integer :refund_fee
|
8
|
+
t.integer :total_fee
|
9
|
+
t.string :out_trade_no
|
10
|
+
t.string :out_refund_no
|
11
|
+
t.string :refund_id
|
12
|
+
t.string :state
|
13
|
+
t.datetime :refunded_at
|
14
|
+
|
15
|
+
t.timestamps
|
16
|
+
|
17
|
+
t.index :payment_order_id, name: "payment_id_on_refund_orders"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class WechatPayment::InstallGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path('templates', __dir__)
|
3
|
+
|
4
|
+
argument :goods, type: :string
|
5
|
+
argument :user, type: :string, default: :User
|
6
|
+
|
7
|
+
|
8
|
+
# 生成 initializer 文件
|
9
|
+
def gen_initializer_file
|
10
|
+
copy_file "initializer.rb", "config/initializers/wechat_payment.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
# 挂载 engine 到路由上
|
14
|
+
def mount_payment_engine
|
15
|
+
route %Q(mount WechatPayment::Engine => "/wechat_payment")
|
16
|
+
end
|
17
|
+
|
18
|
+
# 安装迁移文件
|
19
|
+
def copy_migration
|
20
|
+
rake "wechat_payment:install:migrations"
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_concern_to_goods
|
24
|
+
goods_model_head_one = "class #{goods_model_name} < ApplicationRecord"
|
25
|
+
inject_into_file goods_model_file, after: goods_model_head_one do <<-GOODS_CONCERN
|
26
|
+
|
27
|
+
include WechatPayment::Concern::Goods
|
28
|
+
#{def_custom_user_model}
|
29
|
+
GOODS_CONCERN
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_concern_to_users
|
34
|
+
user_model_head_one = "class #{user_model_name} < ApplicationRecord"
|
35
|
+
inject_into_file user_model_file, after: user_model_head_one do <<-'USERS_CONCERN'
|
36
|
+
|
37
|
+
include WechatPayment::Concern::User
|
38
|
+
USERS_CONCERN
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_concern_to_user_goods
|
43
|
+
user_goods_model_head_one = "class #{user_goods_model_name} < ApplicationRecord"
|
44
|
+
inject_into_file user_goods_model_file, after: user_goods_model_head_one do <<-'USER_GOOD_CONCERN'
|
45
|
+
|
46
|
+
include WechatPayment::Concern::UserGoods
|
47
|
+
USER_GOOD_CONCERN
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def goods_model_file
|
54
|
+
"app/models/#{goods.to_s.underscore}.rb"
|
55
|
+
end
|
56
|
+
|
57
|
+
def user_model_file
|
58
|
+
"app/models/#{user.to_s.underscore}.rb"
|
59
|
+
end
|
60
|
+
|
61
|
+
def user_goods_model_file
|
62
|
+
"app/models/#{user.to_s.underscore}_#{goods.to_s.underscore}.rb"
|
63
|
+
end
|
64
|
+
|
65
|
+
def goods_model_name
|
66
|
+
goods.to_s.camelize
|
67
|
+
end
|
68
|
+
|
69
|
+
def user_model_name
|
70
|
+
user.to_s.camelize
|
71
|
+
end
|
72
|
+
|
73
|
+
def user_goods_model_name
|
74
|
+
user_model_name + goods_model_name
|
75
|
+
end
|
76
|
+
|
77
|
+
def def_custom_user_model
|
78
|
+
if user_model_name != 'User'
|
79
|
+
<<-DEF
|
80
|
+
self.user_model = "#{user_model_name}"
|
81
|
+
self.user_ref_field = "#{user_model_name.underscore}"
|
82
|
+
self.user_goods_model = "#{user_model_name}#{goods_model_name}"
|
83
|
+
DEF
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
WechatPayment.setup do |config|
|
3
|
+
# 接收回调的域名
|
4
|
+
config.host = "https://xxx.com"
|
5
|
+
|
6
|
+
# 下面所有参数都需要改成你自己的小程序配置
|
7
|
+
config.appid = "wxc5c26065c6123456"
|
8
|
+
config.app_secret = "123456784aa91bb538867d3d2790b308"
|
9
|
+
config.mch_id = "112241802"
|
10
|
+
config.key = "123456723erivPO09irNNbh78u8udwFer"
|
11
|
+
|
12
|
+
# 证书可以在微信支付后台获取到,路径是相对于项目根路径,如果需要退款的话,则必须要证书
|
13
|
+
# config.cert_path = "config/apiclient_cert.p12"
|
14
|
+
config.cert_path = nil
|
15
|
+
|
16
|
+
config.sub_appid = "wx8f9f912623456789"
|
17
|
+
config.sub_mch_id = "1234911291"
|
18
|
+
config.sub_app_secret = "88888231e2f3a21152d163f61b99999"
|
19
|
+
end
|
@@ -2,3 +2,13 @@
|
|
2
2
|
# task :wechat_payment do
|
3
3
|
# # Task goes here
|
4
4
|
# end
|
5
|
+
|
6
|
+
desc "Install Wechat Payment Engine"
|
7
|
+
namespace :wechat_payment do
|
8
|
+
task :install, [:goods, :user] => :environment do |task, args|
|
9
|
+
Rake::Task["wechat_payment:install:migrations"].invoke
|
10
|
+
sh "rails g wechat_payment:initializer wechat_payment"
|
11
|
+
sh "rails g wechat_payment:routes wechat_payment"
|
12
|
+
sh "rails g wechat_payment:goods #{args.goods} user:#{args.user.presence || 'user'}"
|
13
|
+
end
|
14
|
+
end
|
data/lib/wechat_payment.rb
CHANGED
@@ -1,6 +1,44 @@
|
|
1
1
|
require "wechat_payment/version"
|
2
|
-
require "wechat_payment/
|
2
|
+
require "wechat_payment/engine"
|
3
3
|
|
4
4
|
module WechatPayment
|
5
|
-
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_reader :apiclient_cert, :apiclient_key
|
8
|
+
attr_accessor :appid, :app_secret, :mch_id, :sub_appid, :sub_app_secret, :sub_mch_id, :key, :cert_path, :host
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.setup
|
12
|
+
yield self if block_given?
|
13
|
+
|
14
|
+
if cert_path
|
15
|
+
set_apiclient_by_pkcs12(File.binread(cert_path), mch_id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.set_apiclient_by_pkcs12(str, pass)
|
20
|
+
pkcs12 = OpenSSL::PKCS12.new(str, pass)
|
21
|
+
@apiclient_cert = pkcs12.certificate
|
22
|
+
@apiclient_key = pkcs12.key
|
23
|
+
|
24
|
+
pkcs12
|
25
|
+
end
|
26
|
+
|
27
|
+
def apiclient_cert=(cert)
|
28
|
+
@apiclient_cert = OpenSSL::X509::Certificate.new(cert)
|
29
|
+
end
|
30
|
+
|
31
|
+
def apiclient_key=(key)
|
32
|
+
@apiclient_key = OpenSSL::PKey::RSA.new(key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.as_payment_params
|
36
|
+
{
|
37
|
+
appid: appid,
|
38
|
+
mch_id: mch_id,
|
39
|
+
sub_appid: sub_appid,
|
40
|
+
sub_mch_id: sub_mch_id
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
6
44
|
end
|