wechat_payment 0.1.0 → 2.0.1

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +214 -12
  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/controllers/wechat_payment/payment_orders_controller.rb +7 -0
  9. data/app/helpers/wechat_payment/application_helper.rb +4 -0
  10. data/app/jobs/wechat_payment/application_job.rb +4 -0
  11. data/app/mailers/wechat_payment/application_mailer.rb +6 -0
  12. data/app/models/wechat_payment/application_record.rb +5 -0
  13. data/app/models/wechat_payment/payment_order.rb +232 -0
  14. data/app/models/wechat_payment/refund_order.rb +127 -0
  15. data/app/services/wechat_payment/service.rb +74 -0
  16. data/app/views/layouts/wechat_payment/application.html.erb +15 -0
  17. data/config/routes.rb +4 -0
  18. data/db/migrate/20210706075217_create_wechat_payment_payment_orders.rb +22 -0
  19. data/db/migrate/20210706095205_create_wechat_payment_refund_orders.rb +20 -0
  20. data/lib/generators/wechat_payment/goods/USAGE +8 -0
  21. data/lib/generators/wechat_payment/goods/goods_generator.rb +5 -0
  22. data/lib/generators/wechat_payment/install/USAGE +8 -0
  23. data/lib/generators/wechat_payment/install/install_generator.rb +91 -0
  24. data/lib/generators/wechat_payment/install/templates/initializer.rb +19 -0
  25. data/lib/generators/wechat_payment/routes/USAGE +8 -0
  26. data/lib/generators/wechat_payment/routes/routes_generator.rb +7 -0
  27. data/lib/tasks/wechat_payment_tasks.rake +10 -0
  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. data/lib/wechat_payment.rb +40 -2
  40. metadata +72 -12
  41. 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
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Wechat payment</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "wechat_payment/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ WechatPayment::Engine.routes.draw do
2
+ post "/callback/payment", to: "callback#payment"
3
+ post "/callback/refund", to: "callback#refund"
4
+ end
@@ -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 :openid
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, :openid
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,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate goods Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,5 @@
1
+ class WechatPayment::GoodsGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+
5
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate install Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,91 @@
1
+ class WechatPayment::InstallGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+ argument :goods, type: :string
5
+ argument :relation_model, type: :string
6
+ argument :user, type: :string, default: :User
7
+
8
+
9
+ # 生成 initializer 文件
10
+ def gen_initializer_file
11
+ copy_file "initializer.rb", "config/initializers/wechat_payment.rb"
12
+ end
13
+
14
+ # 挂载 engine 到路由上
15
+ def mount_payment_engine
16
+ route %Q(mount WechatPayment::Engine => "/wechat_payment")
17
+ end
18
+
19
+ # 安装迁移文件
20
+ def copy_migration
21
+ rake "wechat_payment:install:migrations"
22
+ end
23
+
24
+ def add_concern_to_goods
25
+ goods_model_head_one = "class #{goods_model_name} < ApplicationRecord"
26
+ inject_into_file goods_model_file, after: goods_model_head_one do <<-GOODS_CONCERN
27
+
28
+ include WechatPayment::Concern::Goods
29
+ #{def_custom_user_model}
30
+ GOODS_CONCERN
31
+ end
32
+ end
33
+
34
+ def add_concern_to_users
35
+ user_model_head_one = "class #{user_model_name} < ApplicationRecord"
36
+ inject_into_file user_model_file, after: user_model_head_one do <<-'USERS_CONCERN'
37
+
38
+ include WechatPayment::Concern::User
39
+ USERS_CONCERN
40
+ end
41
+ end
42
+
43
+ def add_concern_to_user_goods
44
+ user_goods_model_head_one = "class #{user_goods_model_name} < ApplicationRecord"
45
+ inject_into_file user_goods_model_file, after: user_goods_model_head_one do <<-'USER_GOOD_CONCERN'
46
+
47
+ include WechatPayment::Concern::UserGoods
48
+ USER_GOOD_CONCERN
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def goods_model_file
55
+ "app/models/#{goods.to_s.underscore}.rb"
56
+ end
57
+
58
+ def user_model_file
59
+ "app/models/#{user.to_s.underscore}.rb"
60
+ end
61
+
62
+ def user_goods_model_file
63
+ if relation_model
64
+ "app/models/#{relation_model.underscore}.rb"
65
+ else
66
+ "app/models/#{user.to_s.underscore}_#{goods.to_s.underscore}.rb"
67
+ end
68
+ end
69
+
70
+ def goods_model_name
71
+ goods.to_s.camelize
72
+ end
73
+
74
+ def user_model_name
75
+ user.to_s.camelize
76
+ end
77
+
78
+ def user_goods_model_name
79
+ (relation_model&.camelize) || (user_model_name + goods_model_name)
80
+ end
81
+
82
+ def def_custom_user_model
83
+ if user_model_name != 'User'
84
+ <<-DEF
85
+ self.user_model = "#{user_model_name}"
86
+ self.user_ref_field = "#{user_model_name.underscore}"
87
+ self.user_goods_model = "#{user_model_name}#{goods_model_name}"
88
+ DEF
89
+ end
90
+ end
91
+ 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
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate routes Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,7 @@
1
+ class WechatPayment::RoutesGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+ def mount_wechat_payment
5
+ route %Q(mount WechatPayment::Engine => "/wechat_payment")
6
+ end
7
+ 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