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,272 @@
|
|
1
|
+
|
2
|
+
module WechatPayment
|
3
|
+
class Client
|
4
|
+
GATEWAY_URL = 'https://api.mch.weixin.qq.com'.freeze
|
5
|
+
|
6
|
+
def initialize # (merchant = WechatPayment)
|
7
|
+
# required_attrs = [:appid, :mch_id, :key, :app_secret, :cert_path]
|
8
|
+
# missing_attrs = required_attrs.reject { |attr| merchant.respond_to?(attr) }
|
9
|
+
# if missing_attrs.present?
|
10
|
+
# raise Exceptions::MerchantMissingAttr.new("Missing attributes: #{missing_attrs}, merchant target must respond to: appid, mch_id, key, appsecret, cert_path")
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# @merchant = merchant
|
14
|
+
# cert_path = Rails.root.join(merchant.cert_path)
|
15
|
+
#
|
16
|
+
# WechatPayment.appid = merchant.appid
|
17
|
+
# WechatPayment.key = merchant.key
|
18
|
+
# WechatPayment.mch_id = merchant.mch_id
|
19
|
+
# WechatPayment.appsecret = merchant.app_secret
|
20
|
+
# WechatPayment.set_apiclient_by_pkcs12(File.binread(cert_path), merchant.mch_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
ORDER_REQUIRED_FIELD = [:out_trade_no, :spbill_create_ip, :body, :total_fee, :openid]
|
24
|
+
# 下单
|
25
|
+
def order(order_params)
|
26
|
+
check_required_key!(order_params, ORDER_REQUIRED_FIELD)
|
27
|
+
|
28
|
+
order_params.merge!(**WechatPayment.as_payment_params,
|
29
|
+
trade_type: :JSAPI,
|
30
|
+
notify_url: payment_notify_url)
|
31
|
+
|
32
|
+
# 如果是服务商模式(根据参数中有没有 sub_appid 判断),就把 openid 换成 sub_openid
|
33
|
+
if order_params[:sub_appid]
|
34
|
+
order_params[:sub_openid] = order_params.delete(:openid)
|
35
|
+
end
|
36
|
+
|
37
|
+
order_result = invoke_unifiedorder(order_params)
|
38
|
+
|
39
|
+
if order_result.success?
|
40
|
+
|
41
|
+
payment_logger.info("{params: #{order_params}, result: #{order_result}}")
|
42
|
+
# WechatPayment::ServiceResult.new(success: true, data: { order_result: order_result.with_indifferent_access, js_payload: mini_program_request_params.with_indifferent_access })
|
43
|
+
WechatPayment::ServiceResult.new(success: true, data: order_result.with_indifferent_access)
|
44
|
+
else
|
45
|
+
payment_logger.error("{params: #{order_params}, result: #{order_result}}")
|
46
|
+
WechatPayment::ServiceResult.new(success: false, errors: order_result.with_indifferent_access)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# 支付回调地址
|
51
|
+
def payment_notify_url
|
52
|
+
ENV["WECHAT_PAYMENT_NOTIFY_URL"] || "#{WechatPayment.host}/wechat_payment/callback/payment"
|
53
|
+
end
|
54
|
+
|
55
|
+
REFUND_REQUIRED_PARAMS = [:total_fee, :refund_fee, :out_trade_no, :out_refund_no]
|
56
|
+
# 退款
|
57
|
+
def refund(refund_params)
|
58
|
+
check_required_key!(refund_params, REFUND_REQUIRED_PARAMS)
|
59
|
+
|
60
|
+
refund_params.merge!(**WechatPayment.as_payment_params, notify_url: refund_notify_url)
|
61
|
+
|
62
|
+
refund_result = invoke_refund(refund_params.to_options)
|
63
|
+
|
64
|
+
if refund_result.success?
|
65
|
+
refund_logger.info "{params: #{refund_params}, result: #{refund_result}"
|
66
|
+
WechatPayment::ServiceResult.new(success: true, data: refund_result)
|
67
|
+
else
|
68
|
+
refund_logger.error "{params: #{refund_params}, result: #{refund_result}"
|
69
|
+
WechatPayment::ServiceResult.new(success: false, errors: refund_result)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# 退款回调地址
|
74
|
+
def refund_notify_url
|
75
|
+
ENV["WECHAT_REFUND_NOTIFY_URL"] || "#{WechatPayment.host}/wechat_payment/callback/refund"
|
76
|
+
end
|
77
|
+
|
78
|
+
# 处理支付回调
|
79
|
+
def self.handle_payment_notify(notify_data)
|
80
|
+
if !WechatPayment::Sign.verify?(notify_data)
|
81
|
+
payment_logger.error("{msg: 签名验证失败, errors: #{notify_data}}")
|
82
|
+
WechatPayment::ServiceResult.new(errors: notify_data, message: "回调签名验证失败")
|
83
|
+
end
|
84
|
+
|
85
|
+
result = WechatPayment::InvokeResult.new(notify_data)
|
86
|
+
|
87
|
+
if result.success?
|
88
|
+
payment_logger.info("{callback: #{notify_data}}")
|
89
|
+
WechatPayment::ServiceResult.new(success: true, data: notify_data)
|
90
|
+
else
|
91
|
+
payment_logger.error("{callback: #{notify_data}}")
|
92
|
+
WechatPayment::ServiceResult.new(errors: notify_data)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# 处理退款回调
|
97
|
+
def self.handle_refund_notify(encrypted_notify_data)
|
98
|
+
notify_data = decrypt_refund_notify(encrypted_notify_data)
|
99
|
+
|
100
|
+
result = WechatPayment::InvokeResult.new(notify_data)
|
101
|
+
if result.success?
|
102
|
+
refund_logger.info "{callback: #{notify_data}}"
|
103
|
+
WechatPayment::ServiceResult.new(success: true, data: notify_data)
|
104
|
+
else
|
105
|
+
refund_logger.error "{callback: #{notify_data}}"
|
106
|
+
WechatPayment::ServiceResult.new(errors: notify_data)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# 生成下单成功后返回给前端拉起微信支付的数据结构
|
111
|
+
def self.gen_js_pay_payload(order_result, options = {})
|
112
|
+
payment_params = {
|
113
|
+
appId: WechatPayment.sub_appid || WechatPayment.appid,
|
114
|
+
package: "prepay_id=#{order_result["prepay_id"]}",
|
115
|
+
key: options.delete(:key) || WechatPayment.key,
|
116
|
+
nonceStr: SecureRandom.hex(16),
|
117
|
+
timeStamp: Time.now.to_i.to_s,
|
118
|
+
signType: 'MD5'
|
119
|
+
}
|
120
|
+
|
121
|
+
payment_params[:paySign] = WechatPayment::Sign.generate(payment_params)
|
122
|
+
|
123
|
+
payment_params
|
124
|
+
end
|
125
|
+
|
126
|
+
GENERATE_JS_PAY_REQ_REQUIRED_FIELDS = [:prepayid, :noncestr]
|
127
|
+
def self.generate_js_pay_req(params, options = {})
|
128
|
+
check_required_options(params, GENERATE_JS_PAY_REQ_REQUIRED_FIELDS)
|
129
|
+
|
130
|
+
params = {
|
131
|
+
appId: options.delete(:appid) || WechatPayment.appid,
|
132
|
+
package: "prepay_id=#{params.delete(:prepayid)}",
|
133
|
+
key: options.delete(:key) || WechatPayment.key,
|
134
|
+
nonceStr: params.delete(:noncestr),
|
135
|
+
timeStamp: Time.now.to_i.to_s,
|
136
|
+
signType: 'MD5'
|
137
|
+
}.merge(params)
|
138
|
+
|
139
|
+
params[:paySign] = WechatPayment::Sign.generate(params)
|
140
|
+
params
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
INVOKE_UNIFIEDORDER_REQUIRED_FIELDS = [:body, :out_trade_no, :total_fee, :spbill_create_ip, :notify_url, :trade_type]
|
145
|
+
def invoke_unifiedorder(params, options = {})
|
146
|
+
params = {
|
147
|
+
appid: options.delete(:appid) || WechatPayment.appid,
|
148
|
+
mch_id: options.delete(:mch_id) || WechatPayment.mch_id,
|
149
|
+
key: options.delete(:key) || WechatPayment.key,
|
150
|
+
nonce_str: SecureRandom.uuid.tr('-', '')
|
151
|
+
}.merge(params)
|
152
|
+
|
153
|
+
check_required_options(params, INVOKE_UNIFIEDORDER_REQUIRED_FIELDS)
|
154
|
+
|
155
|
+
result = WechatPayment::InvokeResult.new(
|
156
|
+
Hash.from_xml(
|
157
|
+
invoke_remote("/pay/unifiedorder", make_payload(params), options)
|
158
|
+
)
|
159
|
+
)
|
160
|
+
|
161
|
+
yield result if block_given?
|
162
|
+
|
163
|
+
result
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
INVOKE_REFUND_REQUIRED_FIELDS = [:out_refund_no, :total_fee, :refund_fee, :op_user_id]
|
168
|
+
# out_trade_no 和 transaction_id 是二选一(必填)
|
169
|
+
def invoke_refund(params, options = {})
|
170
|
+
params = {
|
171
|
+
appid: options.delete(:appid) || WechatPayment.appid,
|
172
|
+
mch_id: options.delete(:mch_id) || WechatPayment.mch_id,
|
173
|
+
key: options.delete(:key) || WechatPayment.key,
|
174
|
+
nonce_str: SecureRandom.uuid.tr('-', ''),
|
175
|
+
}.merge(params)
|
176
|
+
|
177
|
+
params[:op_user_id] ||= params[:mch_id]
|
178
|
+
|
179
|
+
check_required_options(params, INVOKE_REFUND_REQUIRED_FIELDS)
|
180
|
+
warn("WechatPayment Warn: missing required option: out_trade_no or transaction_id must have one") if ([:out_trade_no, :transaction_id] & params.keys) == []
|
181
|
+
|
182
|
+
options = {
|
183
|
+
cert: options.delete(:apiclient_cert) || WechatPayment.apiclient_cert,
|
184
|
+
key: options.delete(:apiclient_key) || WechatPayment.apiclient_key,
|
185
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
186
|
+
}.merge(options)
|
187
|
+
|
188
|
+
result = WechatPayment::InvokeResult.new(
|
189
|
+
Hash.from_xml(
|
190
|
+
invoke_remote("/secapi/pay/refund", make_payload(params), options)
|
191
|
+
)
|
192
|
+
)
|
193
|
+
|
194
|
+
yield result if block_given?
|
195
|
+
|
196
|
+
result
|
197
|
+
end
|
198
|
+
|
199
|
+
# 解密微信退款回调信息
|
200
|
+
#
|
201
|
+
# result = Hash.from_xml(request.body.read)["xml"]
|
202
|
+
#
|
203
|
+
# data = WechatPayment::Service.decrypt_refund_notify(result)
|
204
|
+
def self.decrypt_refund_notify(decrypted_data)
|
205
|
+
aes = OpenSSL::Cipher::AES.new('256-ECB')
|
206
|
+
aes.decrypt
|
207
|
+
aes.key = Digest::MD5.hexdigest(WechatPayment.key)
|
208
|
+
result = aes.update(Base64.decode64(decrypted_data)) + aes.final
|
209
|
+
Hash.from_xml(result)["root"]
|
210
|
+
end
|
211
|
+
|
212
|
+
def make_payload(params, sign_type = WechatPayment::Sign::SIGN_TYPE_MD5)
|
213
|
+
sign = WechatPayment::Sign.generate(params, sign_type)
|
214
|
+
"<xml>#{params.except(:key).sort.map { |k, v| "<#{k}>#{v}</#{k}>" }.join}<sign>#{sign}</sign></xml>"
|
215
|
+
end
|
216
|
+
|
217
|
+
def check_required_options(options, names)
|
218
|
+
names.each do |name|
|
219
|
+
warn("WechatPayment Warn: missing required option: #{name}") unless options.has_key?(name)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def invoke_remote(url, payload, options = {})
|
224
|
+
uri = URI("#{GATEWAY_URL}#{url}")
|
225
|
+
|
226
|
+
req = Net::HTTP::Post.new(uri)
|
227
|
+
req['Content-Type'] = 'application/xml'
|
228
|
+
|
229
|
+
options = {
|
230
|
+
use_ssl: true
|
231
|
+
}.merge(options)
|
232
|
+
|
233
|
+
res = Net::HTTP.start(uri.hostname, uri.port, **options) do |http|
|
234
|
+
http.use_ssl = true
|
235
|
+
http.request(req, payload)
|
236
|
+
end
|
237
|
+
|
238
|
+
res.body
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
# 判断 hash 是否缺少 key
|
245
|
+
def check_required_key!(data, required_keys)
|
246
|
+
lack_of_keys = required_keys - data.keys.map(&:to_sym)
|
247
|
+
|
248
|
+
if lack_of_keys.present?
|
249
|
+
raise WechatPayment::MissingKeyError.new("Parameter missing keys: #{lack_of_keys}")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# 支付日志
|
254
|
+
def payment_logger
|
255
|
+
WechatPayment::Client.payment_logger
|
256
|
+
end
|
257
|
+
|
258
|
+
# 退款日志
|
259
|
+
def refund_logger
|
260
|
+
WechatPayment::Client.refund_logger
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.payment_logger
|
264
|
+
@payment_logger ||= WechatPayment::RLogger.make("wx_payment")
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.refund_logger
|
268
|
+
@refund_logger ||= WechatPayment::RLogger.make("wx_refund")
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
module WechatPayment
|
3
|
+
module Concern
|
4
|
+
module Goods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
cattr_accessor :user_model, :user_ref_field, :goods_ref_field, :user_goods_model, :persist_goods_attrs
|
11
|
+
|
12
|
+
self.user_model = "User"
|
13
|
+
self.user_ref_field = "user"
|
14
|
+
self.goods_ref_field = self.name.underscore
|
15
|
+
self.persist_goods_attrs = []
|
16
|
+
|
17
|
+
# 商品和用户的中间表模型,假设商品模型是 Product,那么中间模型就是 UserProduct
|
18
|
+
self.user_goods_model = "#{self.user_model}#{self.name}"
|
19
|
+
|
20
|
+
validates_numericality_of :price
|
21
|
+
validates_presence_of :name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# 售出
|
26
|
+
# @param [User] user
|
27
|
+
# @return [WechatPaymentOrder]
|
28
|
+
def sell_to(user)
|
29
|
+
|
30
|
+
persist_goods_data = {}.tap do |h|
|
31
|
+
self.class.persist_goods_attrs.each do |attr|
|
32
|
+
h[attr] = send(attr)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
user_goods = self.class.user_goods_model.constantize.create(
|
37
|
+
self.class.goods_ref_field => self,
|
38
|
+
self.class.user_ref_field => user,
|
39
|
+
**persist_goods_data
|
40
|
+
)
|
41
|
+
|
42
|
+
user_goods.payment_orders.create(
|
43
|
+
body: name,
|
44
|
+
total_fee: price,
|
45
|
+
trade_type: :JSAPI,
|
46
|
+
customer: user
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
# 重新支付,应用场景是: 用户取消了支付后,使用最后一张订单进行支付
|
51
|
+
# @return [WechatPayment::ServiceResult]
|
52
|
+
def repay
|
53
|
+
# 如果不是待支付状态
|
54
|
+
unless pending?
|
55
|
+
WechatPayment::ServiceResult.new(message: "当前状态不可支付")
|
56
|
+
end
|
57
|
+
|
58
|
+
result = payment_orders.last.repay
|
59
|
+
|
60
|
+
if result.success?
|
61
|
+
WechatPayment::ServiceResult.new(success: true, data: result.data[:js_payload])
|
62
|
+
else
|
63
|
+
WechatPayment::ServiceResult.new(message: result.errors.first[:err_code_des])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# 退款
|
69
|
+
# @param [Integer] refund_fee
|
70
|
+
# @return [WechatPayment::ServiceResult]
|
71
|
+
def refund(refund_fee)
|
72
|
+
payment_orders.paid.last.refund(refund_fee)
|
73
|
+
end
|
74
|
+
|
75
|
+
def payment_exec_success(payment_order)
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module WechatPayment
|
3
|
+
module Concern
|
4
|
+
module User
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
attr_accessor :spbill_create_ip
|
9
|
+
|
10
|
+
has_many :payment_orders, as: :customer, class_name: "WechatPayment::PaymentOrder"
|
11
|
+
has_many :refund_orders, as: :customer, class_name: "WechatPayment::RefundOrder"
|
12
|
+
|
13
|
+
alias me itself
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def buy(goods)
|
18
|
+
goods.sell_to(me)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
module WechatPayment
|
3
|
+
module Concern
|
4
|
+
module UserGoods
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
|
8
|
+
has_many :payment_orders, as: :goods, class_name: "WechatPayment::PaymentOrder"
|
9
|
+
has_many :refund_orders, as: :goods, class_name: "WechatPayment::PaymentOrder"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module WechatPayment
|
2
|
+
class InvokeResult < ::Hash
|
3
|
+
SUCCESS_FLAG = 'SUCCESS'.freeze
|
4
|
+
|
5
|
+
def initialize(result)
|
6
|
+
super nil # Or it will call `super result`
|
7
|
+
|
8
|
+
if result['xml'].class == Hash
|
9
|
+
result['xml'].each_pair do |k, v|
|
10
|
+
self[k] = v
|
11
|
+
end
|
12
|
+
else
|
13
|
+
result.each_pair do |k, v|
|
14
|
+
self[k] = v
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def success?
|
20
|
+
payment_success? || refund_success?
|
21
|
+
end
|
22
|
+
|
23
|
+
def payment_success?
|
24
|
+
self['return_code'] == SUCCESS_FLAG && self['result_code'] == SUCCESS_FLAG
|
25
|
+
end
|
26
|
+
|
27
|
+
def refund_success?
|
28
|
+
self['refund_status'] == SUCCESS_FLAG
|
29
|
+
end
|
30
|
+
|
31
|
+
def failure?
|
32
|
+
!success?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module WechatPayment
|
2
|
+
class RLogger
|
3
|
+
def initialize
|
4
|
+
raise Exceptions::InitializeDenied.new("please use 'ILogger.make' instead of 'ILogger.new'")
|
5
|
+
end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def make(log_file)
|
9
|
+
@logger ||= {}
|
10
|
+
|
11
|
+
log_file_name = if log_file.class.in? [String, Symbol]
|
12
|
+
log_file_name = log_file.to_sym
|
13
|
+
|
14
|
+
unless log_file_name.to_s.end_with? ".log"
|
15
|
+
log_file_name = "#{log_file_name}.log"
|
16
|
+
end
|
17
|
+
|
18
|
+
"#{root_path}/#{log_file_name}"
|
19
|
+
elsif log_file.respond_to? :to_path
|
20
|
+
log_file.to_path
|
21
|
+
else
|
22
|
+
raise Exceptions::UnsupportdParamType.new("log file parameter only support 'File' or 'String' Type.")
|
23
|
+
end
|
24
|
+
|
25
|
+
# 如果已经存在日志对象,则返回已有的日志对象
|
26
|
+
@logger[log_file_name] ||= ::Logger.new(log_file_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def root_path
|
30
|
+
@root ||= "#{Rails.root}/log"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|